diff options
author | Alvin Li <liweitianux@gmail.com> | 2013-10-04 23:56:35 +0800 |
---|---|---|
committer | Alvin Li <liweitianux@gmail.com> | 2013-10-04 23:56:35 +0800 |
commit | f552b41f4b337e6844f71c29ff177915abbfa417 (patch) | |
tree | 7ade59430c6767a5b379c7a8cb95af3387622b13 /97suifangqa/apps/sfaccount | |
parent | 816730ff659e1338ab3e37a1d45ea337e337b3dd (diff) | |
download | 97dev-f552b41f4b337e6844f71c29ff177915abbfa417.tar.bz2 |
* indicator/static/javascripts/card_chart.js:
improved the display position of 'detail_card_info'
* indicator/templates/indicator/SheetDefault.html:
destroy 'qtip' when close card
* added new app 'apps/sfaccount'
* implemented 'signup' and 'activate' functions
* implemented async sending activation mail
(using 'django-celery' and 'redis')
* moved 'registration/*' templates to 'sfaccount/templates'
* implemented 'password_change' function:
o password_change
o password_change_done
* implemented 'password_reset' function
o password_reset
o password_reset_done
o password_reset_confirm
o password_reset_complete
o re-write 'sfaccount.fomrs.SFPasswordResetForm'
o re-write 'sfaccount.views.password_reset_view'
* improved 'sfaccount.functional' send mail functions
o to send 'multipart' mail
* added 'README.txt'
* added app 'apps/recommend':
for comparing with the SCI papers and then recommending
most related papers for user.
Diffstat (limited to '97suifangqa/apps/sfaccount')
28 files changed, 1185 insertions, 0 deletions
diff --git a/97suifangqa/apps/sfaccount/README.txt b/97suifangqa/apps/sfaccount/README.txt new file mode 100644 index 0000000..44eda9e --- /dev/null +++ b/97suifangqa/apps/sfaccount/README.txt @@ -0,0 +1,23 @@ +using 'django-celery' and 'redis' to implement the function +of 'async sending email' with the activation key for +newly registered user. + +REF: +(1) use Celery in Django with a Redis backend + http://killtheyak.com/django-celery-redis/ + +HOWTO run: +1) pip install django-celery redis +2) OS install package 'redis' (maybe 'redis-server') +3) add 'djcelery' to 'INSTALLED_APPS' +4) add settings for 'redis' & 'djcelery' in 'settings.py' + SF_MAIL +5) system: $ redis-server +6) ./manage.py syncdb +7) ./manage.py celeryd worker -E + +TEST: +a) ./manage.py shell + >>> from sfaccount.tasks import send_mail + >>> send_mail(to, subject, body) + diff --git a/97suifangqa/apps/sfaccount/__init__.py b/97suifangqa/apps/sfaccount/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/97suifangqa/apps/sfaccount/__init__.py diff --git a/97suifangqa/apps/sfaccount/forms.py b/97suifangqa/apps/sfaccount/forms.py new file mode 100644 index 0000000..d2a3bf1 --- /dev/null +++ b/97suifangqa/apps/sfaccount/forms.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- + +from django import forms +from django.template import loader +from django.utils.http import int_to_base36 +from django.contrib.auth.models import User +from django.contrib.auth.tokens import default_token_generator +from django.contrib.sites.models import get_current_site + +from django.utils.translation import ugettext, ugettext_lazy as _ + +from sfaccount.tasks import send_mail + +import re + + +# AccountForm {{{ +class AccountForm(forms.Form): + """ + form for signing up a new account + """ + username = forms.RegexField(regex=r'^[A-Za-z0-9_-]+$', + max_length=30, label=u"用户名", + help_text=u"由字母、数字和下划线组成,长度6-30位", + error_messages={'invalid': u"用户名仅能包含字母、数字和下划线"}, + ) + email = forms.EmailField(max_length=75, label=u"邮箱") + password1 = forms.CharField(label=u"密码", max_length=30, + help_text=u"密码长度6-30位", + widget=forms.PasswordInput) + password2 = forms.CharField(label=u"确认密码", max_length=30, + widget=forms.PasswordInput) + + def clean_username(self): + username = self.cleaned_data['username'] + # check length + if len(username) < 6: + raise forms.ValidationError(u'用户名长度需大于6位') + # check first letter + p = re.compile('[a-zA-Z_]') + if p.match(username[0]): + pass + else: + raise forms.ValidationError(u'首字母必须是字母或下划线') + # check if exists + try: + User.objects.get(username=username) + except User.DoesNotExist: + return username + raise forms.ValidationError(u'用户名已经被占用') + + def clean_email(self): + try: + User.objects.get(email__iexact=self.cleaned_data['email']) + except User.DoesNotExist: + return self.cleaned_data['email'] + raise forms.ValidationError(u'邮箱地址已经被占用') + + def clean_password1(self): + password1 = self.cleaned_data['password1'] + if len(password1) < 6: + raise forms.ValidationError(u'密码长度需大于6位') + return password1 + + def clean(self): + cd = self.cleaned_data + if 'password1' in cd and 'password2' in cd: + if cd['password1'] != cd['password2']: + raise forms.ValidationError(u'两次输入的密码不一致') + # + return cd +# }}} + + +# SFPasswordResetForm {{{ +class SFPasswordResetForm(forms.Form): + """ + to replace django's 'PasswordResetForm' + to use djcelery's async send mail + """ + error_messages = { + 'unknown': _("That e-mail address doesn't have an associated " + "user account. Are you sure you've registered?"), + 'unusable': _("The user account associated with this e-mail " + "address cannot reset the password."), + } + email = forms.EmailField(label=_("E-mail"), max_length=75) + + def save(self, domain_override=None, + subject_template_name='registration/password_reset_subject.txt', + email_template_name='registration/password_reset_email.txt', + use_https=False, token_generator=default_token_generator, + from_email=None, request=None, + html_email_template_name=None): + """ + Generates a one-use only link for resetting password + and sends to the user. + """ + # validate first + if not self.is_valid(): + return self + # validated: has 'self.cleaned_data' + email = self.cleaned_data['email'] + users = User.objects.filter(email__iexact=email) + if not len(users): + raise forms.ValidationError(self.error_messages['unknown']) + for user in users: + # make sure that no email is sent to a user that actually + # has a password marked as unusable + if not user.has_usable_password(): + continue + if not domain_override: + current_site = get_current_site(request) + site_name = current_site.name + domain = current_site.domain + else: + site_name = domain = domain_override + c = { + 'email': user.email, + 'domain': domain, + 'site_name': site_name, + 'uid': int_to_base36(user.id), + 'user': user, + 'token': token_generator.make_token(user), + 'protocol': 'https' if use_https else 'http', + } + subject = loader.render_to_string(subject_template_name, c) + # Email subject *must not* contain newlines + subject = ''.join(subject.splitlines()) + body_text = loader.render_to_string(email_template_name, c) + # html email + if html_email_template_name: + body_html = loader.render_to_string(html_email_template_name, c) + else: + body_html = None + # send mail + to = user.email + send_mail(to, subject, body_text, body_html) +# }}} + + diff --git a/97suifangqa/apps/sfaccount/functional/__init__.py b/97suifangqa/apps/sfaccount/functional/__init__.py new file mode 100644 index 0000000..6cfbcbf --- /dev/null +++ b/97suifangqa/apps/sfaccount/functional/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +from django.conf import settings +from sfaccount.functional.mail import send_mail_multipart + +EMAIL = settings.SF_EMAIL + +def send_mail(to, subject, content_text=None, content_html=None): + send_mail_multipart( + host=EMAIL['smtp_host'], + port=EMAIL['smtp_port'], + username=EMAIL['username'], + password=EMAIL['password'], + mail_from=EMAIL['from'], + mail_to=to, + subject=subject, + content_text=content_text, + content_html=content_html, + display_from=EMAIL['display_from'] + ) + diff --git a/97suifangqa/apps/sfaccount/functional/mail.py b/97suifangqa/apps/sfaccount/functional/mail.py new file mode 100644 index 0000000..30b1701 --- /dev/null +++ b/97suifangqa/apps/sfaccount/functional/mail.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- + +import smtplib + +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart + + +def send_mail_multipart(host, + port, + username, + password, + mail_from, + mail_to, + subject, + content_text=None, + content_html=None, + display_from=None): + # create message container + # correct MIME type is 'multipart/alternative' + msg = MIMEMultipart('alternative') + # from & to + msg['From'] = display_from or mail_from + if isinstance(mail_to, (list, tuple)): + msg['To'] = ', '.join(mail_to) + else: + msg['To'] = mail_to + # subject + msg['Subject'] = subject + # body (utf-8 encode required) + if isinstance(content_text, unicode): + content_text = content_text.encode('utf-8') + if isinstance(content_html, unicode): + content_html = content_html.encode('utf-8') + text_part = MIMEText(content_text, 'plain') + html_part = MIMEText(content_html, 'html') + msg.attach(text_part) + msg.attach(html_part) + # send + s = smtplib.SMTP() + s.connect(host, port) + s.login(username, password) + s.sendmail(mail_from, mail_to, msg.as_string()) + s.quit() + diff --git a/97suifangqa/apps/sfaccount/management/__init__.py b/97suifangqa/apps/sfaccount/management/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/97suifangqa/apps/sfaccount/management/__init__.py diff --git a/97suifangqa/apps/sfaccount/management/commands/__init__.py b/97suifangqa/apps/sfaccount/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/97suifangqa/apps/sfaccount/management/commands/__init__.py diff --git a/97suifangqa/apps/sfaccount/management/commands/cleanupaccounts.py b/97suifangqa/apps/sfaccount/management/commands/cleanupaccounts.py new file mode 100644 index 0000000..c79e037 --- /dev/null +++ b/97suifangqa/apps/sfaccount/management/commands/cleanupaccounts.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + +""" +A management command which deletes expired accounts (e.g., +accounts which signed up but never activated) from the database. + +Calls ``Account.objects.delete_expired_accounts()'', +which contains the actual logic for determining which +accounts are deleted. +""" + +from django.core.management.base import NoArgsCommand + +from accounts.models import Account + + +class Command(NoArgsCommand): + help = "Delete expired accounts from the database" + + def handle_noargs(self, **options): + Account.objects.delete_expired_accounts() + diff --git a/97suifangqa/apps/sfaccount/models.py b/97suifangqa/apps/sfaccount/models.py new file mode 100644 index 0000000..bb1fe29 --- /dev/null +++ b/97suifangqa/apps/sfaccount/models.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- + +from django.conf import settings +from django.db import models +from django.contrib import admin +from django.contrib.auth.models import User +from django.utils.hashcompat import sha_constructor +from django.utils.timezone import utc +from django.template.loader import render_to_string + +from sfaccount.tasks import send_mail + +import re +import random +import datetime + + +# SHA1 Hash regex +SHA1 = re.compile('^[a-f0-9]{40}$') + + +class AccountManager(models.Manager): # {{{ + """ + custom manager for 'Account' model + """ + def activate(self, activation_key): + """ + validate an activation key and activate the corresponding + 'User' if valid. + + if the key is valid and not expired, return the 'Account' + if the key is invalid or expired, return 'False' + if the key is valid but the 'User' is already activated, + return 'False' + + reset the key string to prevent reactivation of an account + which has been deactivated by the admin + """ + if SHA1.search(activation_key): + try: + account = self.get(activation_key=activation_key) + except self.model.DoesNotExist: + return False + if not account.activation_key_expired(): + user = account.user + user.is_active = True + user.save() + account.activation_key = self.model.ACTIVATED + account.save() + return account + return False + + def create_inactive_account(self, username, email, password, + send_email=True): + """ + create a new, *local*, inactive 'User', + and generate an 'Account' and + email the activation key. return the new 'User' + + the activation key is a SHA1 hash, generated from + a combination of the 'username' and a random slat + """ + new_user = User.objects.create_user(username, email, password) + new_user.is_active = False + new_user.save() + # create corresponding 'Account' + salt = sha_constructor(str(random.random())).hexdigest()[:5] + activation_key = sha_constructor(salt+username).hexdigest() + new_account = self.create(user=new_user, is_social=False, + activation_key=activation_key) + new_account.save() + # send email + if send_email: + new_account.send_activation_email() + return new_account + + def delete_expired_accounts(self): + """ + Remove expired instances of 'Account's and their + associated 'User's. + """ + for account in self.all(): + if account.activation_key_expired(): + user = account.user + if not user.is_active: + user.delete() + account.delete() +# }}} + + +class Account(models.Model): # {{{ + """ + Account model for 97suifang + """ + ACTIVATED = u'ALREADY_ACTIVATED' + + user = models.OneToOneField(User, related_name="account") + # username -> user.username + # date_joined -> user.date_joined + screen_name = models.CharField(u"昵称", max_length=30, + null=True, blank=True) + avatar = models.ImageField(u"头像", upload_to="uploads/avatars/", + null=True, blank=True) + # if social account + is_social = models.BooleanField(default=False) + # activation (SHA1 hash) + activation_key = models.CharField(u"激活密钥", max_length=40) + + objects = AccountManager() + + class Meta: + verbose_name_plural = u"账户信息" + + def __unicode__(self): + if self.is_social: + type = u"social" + else: + type = u"local" + if self.user.is_active: + state = u"activated" + else: + state = u"nonactivated" + # + return u'< Account: %s, %s, %s >' % (self.user.username, + type, state) + + def activation_key_expired(self): + """ + determine whether the activation key has expired + + Key expiration is determined by a two-step process: + + 1. If the user has already activated, the key will have been + reset to the string constant ``ACTIVATED``. Re-activating + is not permitted, and so this method returns ``True`` in + this case. + + 2. Otherwise, the date the user signed up is incremented by + the number of days specified in the setting + ``ACCOUNT_ACTIVATION_DAYS`` (which should be the number of + days after signup during which a user is allowed to + activate their account); if the result is less than or + equal to the current date, the key has expired and this + method returns ``True``. + """ + expiration_days = datetime.timedelta( + days=settings.ACCOUNT_ACTIVATION_DAYS) + now_utc = datetime.datetime.utcnow().replace(tzinfo=utc) + return self.user.is_active or ( + self.user.date_joined + expiration_days <= now_utc) + + def send_activation_email(self): + """ + send an activation email to the newly registered user + """ + ctx_dict = { + 'username': self.user.username, + 'activation_key': self.activation_key, + 'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS, + } + subject = render_to_string('sfaccount/activation_email_subject.txt', ctx_dict) + subject = ''.join(subject.splitlines()) + body_text = render_to_string('sfaccount/activation_email_body.txt', ctx_dict).encode('utf-8') + to = self.user.email + # send email + send_mail.delay(to, subject, body_text, None) +# }}} + + +admin.site.register([Account]) + +# vim: set ts=4 sw=4 tw=0 fenc=utf-8 ft=python: # diff --git a/97suifangqa/apps/sfaccount/tasks.py b/97suifangqa/apps/sfaccount/tasks.py new file mode 100644 index 0000000..94b520a --- /dev/null +++ b/97suifangqa/apps/sfaccount/tasks.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- + +from celery import task + +from sfaccount.functional import send_mail as _send_mail + +@task +def send_mail(to, subject, content_text, content_html): + _send_mail(to, subject, content_text, content_html) + diff --git a/97suifangqa/apps/sfaccount/templates/sfaccount/activate.html b/97suifangqa/apps/sfaccount/templates/sfaccount/activate.html new file mode 100644 index 0000000..a81af6d --- /dev/null +++ b/97suifangqa/apps/sfaccount/templates/sfaccount/activate.html @@ -0,0 +1,98 @@ +{% extends "picture-base.html" %} +{% load staticfiles %} + +{% block title %} +激活账户 | 97 随访 +{% endblock %} + +{% block bodyclasses %}{{ block.super }} registration signup{% endblock %} +{% block othercss %} +<link rel="stylesheet" href="{% static "stylesheets/sass/registration.css" %}"> +{% endblock %} + +{% block scripts %} + <script type="text/javascript"> + // activate account url + var activate_url = '{% url activate %}'; + + $(document).ready(function(){ + // validate key input + $("#activation_key").focus(function() { + $(this).removeClass("valid invalid"); + }); + $("#activation_key").on("validate", null, function() { + var sha1_regex = /^[0-9a-f]{40}$/; + var key_raw = $(this).val(); + var key = htmlEscape(key_raw.toLowerCase()); + if (sha1_regex.test(key)) { + // key in valid format + $(this).removeClass("invalid"); + $(this).addClass("valid"); + } + else { + $(this).removeClass("valid"); + $(this).addClass("invalid"); + } + }); + + // sumbit key to activate account + $("#activate_account").on('submit', null, function() { + // validate key first + $("#activation_key").trigger("validate"); + if ($(".invalid").length) { + // there exists invalid input + return false; + } + else { + // submit + var key_raw = $("#activation_key").val(); + var key = htmlEscape(key_raw.toLowerCase()); + var target_url = activate_url + key + '/'; + //console.log(target_url); + window.location.href = target_url; + } + }); + $("#activate_account").on('click', null, function() { + $(this).trigger('submit'); + }); + $("#activation_key").on('keypress', null, function(e) { + var keycode = (e.keyCode ? e.keyCode : e.which); + if (keycode == 13) { + $("#activate_account").trigger('submit'); + return false; + } + }); + + }); + + function htmlEscape(str) { + return String(str) + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(/</g, '<') + .replace(/>/g, '>'); + } + </script> +{% endblock %} + +{% block body %} + <h2>加入97随访   科学了解乙肝治疗</h2> + + <div class="activate_failed" style="display: {% if activate_failed %}block{% else %}none{% endif %} ;"> + <h4>激活账户失败</h4> + 请检查激活码或激活链接。 + <br /> + 您也可以直接在下方输入激活码来完成账户激活。 + </div> + + <div class="activation"> + <span class="prompt">激活码</span> + <input type="text" id="activation_key" /> + <br /> + <input type="button" id="activate_account" value="激活账户" /> + </div> + +{% endblock body %} + +<!-- vim: set ts=8 sw=2 tw=0 fenc=utf-8 ft=htmldjango.html: --> diff --git a/97suifangqa/apps/sfaccount/templates/sfaccount/activation_email_body.txt b/97suifangqa/apps/sfaccount/templates/sfaccount/activation_email_body.txt new file mode 100644 index 0000000..32be3e9 --- /dev/null +++ b/97suifangqa/apps/sfaccount/templates/sfaccount/activation_email_body.txt @@ -0,0 +1,9 @@ +尊敬的 {{ username }}, + +感谢您注册97随访(97suifang.com)。 + +您的激活码为 {{ activation_key }},请在 {{ expiration_days }} 天内激活账户,直接打开以下链接进行激活: +http://www.97suifang.com/accounts/activate/{{ activation_key }}/ + + +97随访 diff --git a/97suifangqa/apps/sfaccount/templates/sfaccount/activation_email_subject.txt b/97suifangqa/apps/sfaccount/templates/sfaccount/activation_email_subject.txt new file mode 100644 index 0000000..9a9a040 --- /dev/null +++ b/97suifangqa/apps/sfaccount/templates/sfaccount/activation_email_subject.txt @@ -0,0 +1 @@ +97随访(97suifang.com)账户激活 diff --git a/97suifangqa/apps/sfaccount/templates/sfaccount/login.html b/97suifangqa/apps/sfaccount/templates/sfaccount/login.html new file mode 100644 index 0000000..f5c7942 --- /dev/null +++ b/97suifangqa/apps/sfaccount/templates/sfaccount/login.html @@ -0,0 +1,57 @@ +{% extends "picture-base.html" %} +{% load staticfiles %} +{% block bodyclasses %}{{ block.super }} registration login{% endblock %} +{% block othercss %} +<link rel="stylesheet" href="{% static "stylesheets/sass/registration.css" %}"> +{% endblock %} + +{% block title %} +登录 | 97随访 +{% endblock %} + +{% block body %} +<h2>加入97随访   科学了解乙肝治疗</h2> +<p></p> +<p> + <img src="{% static "images/sinalogo.png" %}" alt="" style="width: 2.5em; vertical-align: middle;"> + 新浪微博账号登录 + </p> + + <p> + <img src="{% static "images/qqlogo.png" %}" alt="" style="width: 2.5em; vertical-align: middle;"> + 腾讯QQ账号登录 + </p> + + <form method="post">{% csrf_token %} + <table class="reg-form login-form"> + <tr> + <!-- + <td class="login-prompt"> + <span class="prompt">或者直接用邮箱登陆</span> + </td> + <td></td> + --> + </tr> + <tr> + <td> + <input type="text" name="{{ form.username.name }}" value="{{ form.username.value|default_if_none:"" }}" maxlength="80" placeholder="{{ form.username.label }}" class="username "> + </td> + <td rowspan="2" class="error"> + {{form.non_field_errors|first}} + </td> + </tr> + <tr> + <td><input type="password" name="{{ form.password.name }}" placeholder="{{ form.password.label }}" class="password"></td> + </tr> + </table> + + <input type="hidden" name="next" value="{{ next }}" /> + <input type="submit" value="登 录" class="submit login"/> +   |   + <a href="{% url signup %}">还没有帐号?</a> +   |   + <a href="{% url password_reset %}">忘记密码?</a> + </form> +{% endblock body%} + +{# vim: set ts=2 sw=2 tw=0 fenc=utf-8 ft=htmldjango.html: #} diff --git a/97suifangqa/apps/sfaccount/templates/sfaccount/logout.html b/97suifangqa/apps/sfaccount/templates/sfaccount/logout.html new file mode 100644 index 0000000..0d05ae5 --- /dev/null +++ b/97suifangqa/apps/sfaccount/templates/sfaccount/logout.html @@ -0,0 +1,35 @@ +{% extends "picture-base.html" %} +{% load staticfiles %} + +{% block title %} +注销 | 97 随访 +{% endblock %} + +{% block othercss %} +<link rel="stylesheet" href="{% static "stylesheets/sass/registration.css" %}"> +{% endblock %} + +{% block scripts %} + <script type="text/javascript"> + // login url + var login_url = '{% url login %}'; + + $(document).ready(function() { + $("#re-login").bind("click", function() { + window.location.href = login_url; + }); + }); + </script> +{% endblock %} + +{% block body %} + <h2>加入97随访   科学了解乙肝治疗</h2> + + <p>感谢您使用97随访!</p> + + <p> + <input type="button" id="re-login" value="重新登录" /> + </p> +{% endblock body %} + +<!-- vim: set ts=8 sw=2 tw=0 fenc=utf-8 ft=htmldjango.html: --> diff --git a/97suifangqa/apps/sfaccount/templates/sfaccount/password_change.html b/97suifangqa/apps/sfaccount/templates/sfaccount/password_change.html new file mode 100644 index 0000000..7918016 --- /dev/null +++ b/97suifangqa/apps/sfaccount/templates/sfaccount/password_change.html @@ -0,0 +1,32 @@ +{% extends "picture-base.html" %} +{% load staticfiles %} +{% block bodyclasses %}{{ block.super }} registration login{% endblock %} +{% block othercss %} +<link rel="stylesheet" href="{% static "stylesheets/sass/registration.css" %}"> +{% endblock %} + +{% block title %} +修改密码 | 97随访 +{% endblock %} + +{% block body %} + <h2>加入97随访   科学了解乙肝治疗</h2> + + <h3>修改密码</h3> + <form action="" method="post">{% csrf_token %} + <table class="change-password-form login-form"> + {% for item in form %} + <tr> + <td> + <input type="{{ item.field.widget.input_type }}" name="{{ item.name }}" {% if item.field.widget.input_type == "text" %}value="{{ item.value|default_if_none:"" }}"{% endif %} placeholder="{{ item.label }}" class="{{ item.name }}" /> + </td> + <td class="error">{{ item.errors|join:"" }}</td> + </tr> + {% endfor %} + </table> + + <input type="submit" value="修改密码" class="submit change-password" /> + </form> +{% endblock body%} + +{# vim: set ts=2 sw=2 tw=0 fenc=utf-8 ft=htmldjango.html: #} diff --git a/97suifangqa/apps/sfaccount/templates/sfaccount/password_change_done.html b/97suifangqa/apps/sfaccount/templates/sfaccount/password_change_done.html new file mode 100644 index 0000000..ed91216 --- /dev/null +++ b/97suifangqa/apps/sfaccount/templates/sfaccount/password_change_done.html @@ -0,0 +1,35 @@ +{% extends "picture-base.html" %} +{% load staticfiles %} + +{% block title %} +修改密码 | 97 随访 +{% endblock %} + +{% block othercss %} +<link rel="stylesheet" href="{% static "stylesheets/sass/registration.css" %}"> +{% endblock %} + +{% block scripts %} + <script type="text/javascript"> + // login url + var home_url = '{% url go_home %}'; + + $(document).ready(function() { + $("#go-home").bind("click", function() { + window.location.href = home_url; + }); + }); + </script> +{% endblock %} + +{% block body %} + <h2>加入97随访   科学了解乙肝治疗</h2> + + <h4>密码修改成功!</h4> + + <p> + <input type="button" id="go-home" value="返回个人主页" /> + </p> +{% endblock body %} + +<!-- vim: set ts=8 sw=2 tw=0 fenc=utf-8 ft=htmldjango.html: --> diff --git a/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset.html b/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset.html new file mode 100644 index 0000000..87421d3 --- /dev/null +++ b/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset.html @@ -0,0 +1,36 @@ +{% extends "picture-base.html" %} +{% load staticfiles %} + +{% block title %} +重设密码 | 97 随访 +{% endblock %} + +{% block othercss %} +<link rel="stylesheet" href="{% static "stylesheets/sass/registration.css" %}"> +{% endblock %} + +{% block body %} + <h2>加入97随访   科学了解乙肝治疗</h2> + + <p> + 忘记了密码? + </p> + <p> + 请在下面输入您注册时使用的邮箱地址, + 我们将把重设密码的链接通过邮件发给您。 + </p> + + <form action="" method="post">{% csrf_token %} + <table class="password-reset"> + <tr> + <td> + <input type="text" name="email" value="{{ form.email.value|default_if_none:"" }}" placeholder="您注册时的邮箱地址" class="email"> + </td> + <td class="error">{{ form.email.errors|join:"" }}</td> + </tr> + </table> + <input type="submit" value="申请重设密码" /> + </form> +{% endblock %} + +<!-- vim: set ts=8 sw=2 tw=0 fenc=utf-8 ft=htmldjango.html: --> diff --git a/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_complete.html b/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_complete.html new file mode 100644 index 0000000..2027cd2 --- /dev/null +++ b/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_complete.html @@ -0,0 +1,37 @@ +{% extends "picture-base.html" %} +{% load staticfiles %} + +{% block title %} +重设密码 | 97 随访 +{% endblock %} + +{% block othercss %} +<link rel="stylesheet" href="{% static "stylesheets/sass/registration.css" %}"> +{% endblock %} + +{% block scripts %} + <script type="text/javascript"> + // login url + var login_url = '{% url login %}'; + + $(document).ready(function() { + $("#login").bind("click", function() { + window.location.href = login_url; + }); + }); + </script> +{% endblock %} + +{% block body %} + <h2>加入97随访   科学了解乙肝治疗</h2> + + <h4>重设密码成功!</h4> + + <p> + 您的密码已经重新设置,现在您可以继续登录账户。 + </p> + + <input type="button" id="login" value="登录账户" /> +{% endblock %} + +<!-- vim: set ts=8 sw=2 tw=0 fenc=utf-8 ft=htmldjango.html: --> diff --git a/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_confirm.html b/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_confirm.html new file mode 100644 index 0000000..8522af5 --- /dev/null +++ b/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_confirm.html @@ -0,0 +1,53 @@ +{% extends "picture-base.html" %} +{% load staticfiles %} + +{% block title %} +重设密码 | 97随访 +{% endblock %} + +{% block othercss %} +<link rel="stylesheet" href="{% static "stylesheets/sass/registration.css" %}"> +{% endblock %} + +{% block scripts %} + <script type="text/javascript"> + // password_reset url + var password_reset_url = '{% url password_reset %}'; + + $(document).ready(function() { + $("#password-reset").bind("click", function() { + window.location.href = password_reset_url; + }); + }); + </script> +{% endblock %} + +{% block body %} + <h2>加入97随访   科学了解乙肝治疗</h2> + + {% if validlink %} + <h4>请设置新密码</h4> + + <form action="" method="post">{% csrf_token %} + <table class="password-reset"> + {% for item in form %} + <tr> + <td> + <input type="{{ item.field.widget.input_type }}" name="{{ item.name }}" {% if item.field.widget.input_type == "text" %}value="{{ item.value|default_if_none:"" }}"{% endif %} placeholder="{{ item.label }}" class="{{ item.name }}"> + </td> + <td class="error">{{ item.errors|join:"" }}</td> + </tr> + {% endfor %} + </table> + <input type="submit" class="sumbit password-reset" value="重设密码" /> + </form> + {% else %} + <h4>重设密码失败</h4> + + <p>您使用的密码重设链接无效,可能因为该链接已被使用过。</p> + <p>您可以尝试重新申请重设密码。</p> + <input type="button" id="password-reset" value="申请重设密码" /> + {% endif %} +{% endblock %} + +<!-- vim: set ts=8 sw=2 tw=0 fenc=utf-8 ft=htmldjango.html: --> diff --git a/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_done.html b/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_done.html new file mode 100644 index 0000000..c7bd9a3 --- /dev/null +++ b/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_done.html @@ -0,0 +1,45 @@ +{% extends "picture-base.html" %} +{% load staticfiles %} + +{% block title %} +重设密码 | 97 随访 +{% endblock %} + +{% block othercss %} +<link rel="stylesheet" href="{% static "stylesheets/sass/registration.css" %}"> +{% endblock %} + +{% block scripts %} + <script type="text/javascript"> + // password_reset url + var password_reset_url = '{% url password_reset %}'; + + $(document).ready(function() { + $("#password-reset").bind("click", function() { + window.location.href = password_reset_url; + }); + }); + </script> +{% endblock %} + +{% block body %} + <h2>加入97随访   科学了解乙肝治疗</h2> + + <h4> + 密码重设邮件已发送! + </h4> + <p> + 我们已经向您提交的邮箱地址发送了密码重设说明, + 请注意查收邮件,并按邮件说明来重新设置密码。 + </p> + + <p> + 还没收到邮件?您可以尝试再次申请重设密码。 + </p> + <p> + <input type="button" id="password-reset" value="申请重设密码" /> + </p> + +{% endblock %} + +<!-- vim: set ts=8 sw=2 tw=0 fenc=utf-8 ft=htmldjango.html: --> diff --git a/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_email.html b/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_email.html new file mode 100644 index 0000000..beae46f --- /dev/null +++ b/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_email.html @@ -0,0 +1,25 @@ +{% autoescape off %} +<html> + <head></head> + <body> + <p>尊敬的 {{ user.username }},</p> + + <p>您收到该邮件是因为您已请求重设97随访({{ domain }})账户的密码。</p> + + <p>请打开以下链接来为您的账户设置新密码:<br /> + {% block reset_link %} + {{ protocol }}://{{ domain }}{% url django.contrib.auth.views.password_reset_confirm uidb36=uid, token=token %} + {% endblock %} + </p> + + <p>您的登录用户名为: {{ user.username }}</p> + <br /> + + + <p>感谢您使用我们的产品!</p> + <br /> + + <p>97随访 团队</p> + </body> +</html> +{% endautoescape %} diff --git a/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_email.txt b/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_email.txt new file mode 100644 index 0000000..20c817c --- /dev/null +++ b/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_email.txt @@ -0,0 +1,13 @@ +尊敬的 {{ user.username }}, + +您收到该邮件是因为您已请求重设97随访({{ domain }})账户的密码。 + +请打开以下链接来为您的账户设置新密码: +{{ protocol }}://{{ domain }}{% url django.contrib.auth.views.password_reset_confirm uidb36=uid, token=token %} + +您的登录用户名为: {{ user.username }} + + +感谢您使用我们的产品! + +97随访 团队 diff --git a/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_subject.txt b/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_subject.txt new file mode 100644 index 0000000..b980ba1 --- /dev/null +++ b/97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_subject.txt @@ -0,0 +1 @@ +97随访(97suifang.com)密码重设 diff --git a/97suifangqa/apps/sfaccount/templates/sfaccount/signup.html b/97suifangqa/apps/sfaccount/templates/sfaccount/signup.html new file mode 100644 index 0000000..bf6c193 --- /dev/null +++ b/97suifangqa/apps/sfaccount/templates/sfaccount/signup.html @@ -0,0 +1,38 @@ +{% extends "picture-base.html" %} +{% load staticfiles %} + +{% block title %} +注册账户 | 97随访 +{% endblock %} + +{% block bodyclasses %}{{ block.super }} registration signup{% endblock %} +{% block othercss %} +<link rel="stylesheet" href="{% static "stylesheets/sass/registration.css" %}"> +{% endblock %} +{% block body %} +<h2>加入97随访   科学了解乙肝治疗</h2> +<form action="" method="post">{% csrf_token %} + <table class="reg-form register-form"> + {% for item in form %} + <tr> + <td> + <input type="{{ item.field.widget.input_type }}" name="{{ item.name }}" {% if item.field.widget.input_type == "text" %}value="{{ item.value|default_if_none:"" }}"{% endif %} placeholder="{{ item.label }}" class="{{ item.name }}"> + </td> + <td class="help">{{ item.help_text }}</td> + <td class="error">{{ item.errors|join:"" }}</td> + </tr> + {% endfor %} + </table> + <table class="form-errors"> + {% for error in form.non_field_errors %} + <tr> + <td>{{ error }}</td> + </tr> + {% endfor %} + </table> + + <input type="submit" value="提交注册" class="submit register"/> +   |   + <a href="{% url login %}">已有账号</a> +</form> +{% endblock body %} diff --git a/97suifangqa/apps/sfaccount/tests.py b/97suifangqa/apps/sfaccount/tests.py new file mode 100644 index 0000000..501deb7 --- /dev/null +++ b/97suifangqa/apps/sfaccount/tests.py @@ -0,0 +1,16 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" + +from django.test import TestCase + + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.assertEqual(1 + 1, 2) diff --git a/97suifangqa/apps/sfaccount/urls.py b/97suifangqa/apps/sfaccount/urls.py new file mode 100644 index 0000000..f2a930b --- /dev/null +++ b/97suifangqa/apps/sfaccount/urls.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- + +from django.core.urlresolvers import reverse +from django.conf import settings +from django.conf.urls import patterns, url +from django.views.generic.simple import direct_to_template + +from django.contrib.auth import views as auth_views + + +urlpatterns = patterns('sfaccount.views', + url(r'^signup/$', 'signup_view', name='signup'), + # activate account + url(r'^activate/$', 'activate_view', name='activate'), + url(r'^activate/(?P<activation_key>.+)/$', + 'activate_view'), + # go home + url(r'^home/$', 'go_home_view', name='go_home'), +) + +urlpatterns += patterns('', + # login & logout + url(r'^login/$', + auth_views.login, + {'template_name': 'sfaccount/login.html'}, + name='login'), + url(r'^logout/$', + auth_views.logout, + {'template_name': 'sfaccount/logout.html'}, + name='logout'), + # change password + url(r'^password/change/$', + auth_views.password_change, + {'template_name': 'sfaccount/password_change.html'}, + name='password_change'), + url(r'^password/change/done/$', + auth_views.password_change_done, + {'template_name': 'sfaccount/password_change_done.html'}, + name='password_change_done'), + # reset password + # use own 'password_reset_view' to able to send multipart mail + # use own 'SFPasswordResetForm' to use 'djcelery' to send email + url(r'^password/reset/$', + 'sfaccount.views.password_reset_view', + name='password_reset'), + url(r'^password/reset/done/$', + auth_views.password_reset_done, + {'template_name': 'sfaccount/password_reset_done.html'}, + name='password_reset_done'), + url(r'^password/reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', + auth_views.password_reset_confirm, + {'template_name': 'sfaccount/password_reset_confirm.html'}, + name='password_reset_confirm'), + url(r'^password/reset/complete/$', + auth_views.password_reset_complete, + {'template_name': 'sfaccount/password_reset_complete.html'}, + name='password_reset_complete'), +) + + +USING_SOCIAL_LOGIN = getattr(settings, 'USING_SOCIAL_LOGIN', False) +if USING_SOCIAL_LOGIN: + urlpatterns += patterns('sfaccount.views', + url(r'^oauth/(?P<sitename>\w+)/$', + 'social_login_callback', name='social_login_callback'), + ) + + +# test view +urlpatterns += patterns('', + ## test + url(r'^test/$', + direct_to_template, + { 'template': 'sfaccount/logout.html' }, + name='sfaccount_test'), +) + diff --git a/97suifangqa/apps/sfaccount/views.py b/97suifangqa/apps/sfaccount/views.py new file mode 100644 index 0000000..94670a6 --- /dev/null +++ b/97suifangqa/apps/sfaccount/views.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- + +from django.conf import settings +from django.http import HttpResponse, HttpResponseRedirect +from django.template.response import TemplateResponse +from django.core.urlresolvers import reverse +from django.views.decorators.csrf import csrf_protect +from django.utils.translation import ugettext as _ +from django.shortcuts import render, redirect + +from django.contrib.auth.tokens import default_token_generator + +from sfaccount.models import Account +from sfaccount.forms import AccountForm, SFPasswordResetForm + +# email address shown in the sent mail +FROM_EMAIL = getattr(settings, 'SF_EMAIL').get('display_from') + + +# go_home {{{ +def go_home_view(request): + """ + go to home page (profile) + """ + if request.user.is_authenticated(): + username = request.user.username + return redirect(reverse('profile_home', + kwargs={'username': username})) + else: + # not logged in + return redirect(reverse('login')) +# }}} + + +# signup {{{ +def signup_view(request): + """ + 用户注册 + """ + if request.user.is_authenticated(): + return HttpResponseRedirect(settings.LOGIN_REDIRECT_URL) + + if request.method == 'POST': + form = AccountForm(data=request.POST) + if form.is_valid(): + cd = form.cleaned_data + new_account = Account.objects.create_inactive_account( + username=cd['username'], + email=cd['email'], + password=cd['password1'], + send_email=True + ) + return HttpResponseRedirect(request.REQUEST.get('next')) + else: + form = AccountForm() + + data = { + 'form': form, + } + return render(request, 'sfaccount/signup.html', data) +# }}} + + +# activate {{{ +def activate_view(request, activation_key=None): + """ + activate account + + if activation_key=None, then render a page ask user + to provide the activation key; + otherwise, directly activate the account and redirect + """ + if activation_key: + account = Account.objects.activate(activation_key) + if account: + # activated + home_url = '/profile/%s/' % account.user.username + return HttpResponseRedirect(home_url) + else: + # activate failed + data = {'activate_failed': True} + return render(request, 'sfaccount/activate.html', data) + else: + # ask user for the 'activation_key' + return render(request, 'sfaccount/activate.html') +# }}} + + +# password_reset_view {{{ +# own password_reset_view: enable to send multipart email +@csrf_protect +def password_reset_view(request, is_admin_site=False, + template_name='sfaccount/password_reset.html', + email_template_name='sfaccount/password_reset_email.txt', + subject_template_name='sfaccount/password_reset_subject.txt', + password_reset_form=SFPasswordResetForm, + token_generator=default_token_generator, + post_reset_redirect=None, + from_email=FROM_EMAIL, + current_app=None, + extra_context=None, + html_email_template_name='sfaccount/password_reset_email.html'): + """ + re-write view to replace django's one + able to send multipart email by using + own 'SFPasswordResetForm' and 'send_mail' + """ + if post_reset_redirect is None: + post_reset_redirect = reverse('password_reset_done') + if request.method == "POST": + form = password_reset_form(request.POST) + if form.is_valid(): + opts = { + 'use_https': request.is_secure(), + 'token_generator': token_generator, + 'from_email': from_email, + 'email_template_name': email_template_name, + 'subject_template_name': subject_template_name, + 'request': request, + 'html_email_template_name': html_email_template_name, + } + if is_admin_site: + opts = dict(opts, domain_override=request.get_host()) + form.save(**opts) + return HttpResponseRedirect(post_reset_redirect) + else: + form = password_reset_form() + context = { + 'form': form, + 'title': _('Password reset'), + } + if extra_context is not None: + context.update(extra_context) + return TemplateResponse(request, template_name, context, + current_app=current_app) +# }}} + + +# social_login_callback {{{ +def social_login_callback(request, sitename): + return HttpResponse('%s' % sitename) +# }}} + |