From f552b41f4b337e6844f71c29ff177915abbfa417 Mon Sep 17 00:00:00 2001 From: Alvin Li Date: Fri, 4 Oct 2013 23:56:35 +0800 Subject: * 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. --- 97suifangqa/apps/sfaccount/README.txt | 23 +++ 97suifangqa/apps/sfaccount/__init__.py | 0 97suifangqa/apps/sfaccount/forms.py | 141 +++++++++++++++++ 97suifangqa/apps/sfaccount/functional/__init__.py | 21 +++ 97suifangqa/apps/sfaccount/functional/mail.py | 45 ++++++ 97suifangqa/apps/sfaccount/management/__init__.py | 0 .../apps/sfaccount/management/commands/__init__.py | 0 .../management/commands/cleanupaccounts.py | 22 +++ 97suifangqa/apps/sfaccount/models.py | 172 +++++++++++++++++++++ 97suifangqa/apps/sfaccount/tasks.py | 10 ++ .../sfaccount/templates/sfaccount/activate.html | 98 ++++++++++++ .../templates/sfaccount/activation_email_body.txt | 9 ++ .../sfaccount/activation_email_subject.txt | 1 + .../apps/sfaccount/templates/sfaccount/login.html | 57 +++++++ .../apps/sfaccount/templates/sfaccount/logout.html | 35 +++++ .../templates/sfaccount/password_change.html | 32 ++++ .../templates/sfaccount/password_change_done.html | 35 +++++ .../templates/sfaccount/password_reset.html | 36 +++++ .../sfaccount/password_reset_complete.html | 37 +++++ .../sfaccount/password_reset_confirm.html | 53 +++++++ .../templates/sfaccount/password_reset_done.html | 45 ++++++ .../templates/sfaccount/password_reset_email.html | 25 +++ .../templates/sfaccount/password_reset_email.txt | 13 ++ .../templates/sfaccount/password_reset_subject.txt | 1 + .../apps/sfaccount/templates/sfaccount/signup.html | 38 +++++ 97suifangqa/apps/sfaccount/tests.py | 16 ++ 97suifangqa/apps/sfaccount/urls.py | 77 +++++++++ 97suifangqa/apps/sfaccount/views.py | 143 +++++++++++++++++ 28 files changed, 1185 insertions(+) create mode 100644 97suifangqa/apps/sfaccount/README.txt create mode 100644 97suifangqa/apps/sfaccount/__init__.py create mode 100644 97suifangqa/apps/sfaccount/forms.py create mode 100644 97suifangqa/apps/sfaccount/functional/__init__.py create mode 100644 97suifangqa/apps/sfaccount/functional/mail.py create mode 100644 97suifangqa/apps/sfaccount/management/__init__.py create mode 100644 97suifangqa/apps/sfaccount/management/commands/__init__.py create mode 100644 97suifangqa/apps/sfaccount/management/commands/cleanupaccounts.py create mode 100644 97suifangqa/apps/sfaccount/models.py create mode 100644 97suifangqa/apps/sfaccount/tasks.py create mode 100644 97suifangqa/apps/sfaccount/templates/sfaccount/activate.html create mode 100644 97suifangqa/apps/sfaccount/templates/sfaccount/activation_email_body.txt create mode 100644 97suifangqa/apps/sfaccount/templates/sfaccount/activation_email_subject.txt create mode 100644 97suifangqa/apps/sfaccount/templates/sfaccount/login.html create mode 100644 97suifangqa/apps/sfaccount/templates/sfaccount/logout.html create mode 100644 97suifangqa/apps/sfaccount/templates/sfaccount/password_change.html create mode 100644 97suifangqa/apps/sfaccount/templates/sfaccount/password_change_done.html create mode 100644 97suifangqa/apps/sfaccount/templates/sfaccount/password_reset.html create mode 100644 97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_complete.html create mode 100644 97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_confirm.html create mode 100644 97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_done.html create mode 100644 97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_email.html create mode 100644 97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_email.txt create mode 100644 97suifangqa/apps/sfaccount/templates/sfaccount/password_reset_subject.txt create mode 100644 97suifangqa/apps/sfaccount/templates/sfaccount/signup.html create mode 100644 97suifangqa/apps/sfaccount/tests.py create mode 100644 97suifangqa/apps/sfaccount/urls.py create mode 100644 97suifangqa/apps/sfaccount/views.py (limited to '97suifangqa/apps/sfaccount') 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 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 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 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 %} + +{% endblock %} + +{% block scripts %} + +{% endblock %} + +{% block body %} +

加入97随访   科学了解乙肝治疗

+ +
+

激活账户失败

+ 请检查激活码或激活链接。 +
+ 您也可以直接在下方输入激活码来完成账户激活。 +
+ +
+ 激活码 + +
+ +
+ +{% endblock body %} + + 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 %} + +{% endblock %} + +{% block title %} +登录 | 97随访 +{% endblock %} + +{% block body %} +

加入97随访   科学了解乙肝治疗

+

+

+ + 新浪微博账号登录 +

+ +

+ + 腾讯QQ账号登录 +

+ +
{% csrf_token %} + + + + + + + + + + + + + + + +   |   + 还没有帐号? +   |   + 忘记密码? +
+{% 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 %} + +{% endblock %} + +{% block scripts %} + +{% endblock %} + +{% block body %} +

加入97随访   科学了解乙肝治疗

+ +

感谢您使用97随访!

+ +

+ +

+{% endblock body %} + + 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 %} + +{% endblock %} + +{% block title %} +修改密码 | 97随访 +{% endblock %} + +{% block body %} +

加入97随访   科学了解乙肝治疗

+ +

修改密码

+
{% csrf_token %} + + {% for item in form %} + + + + + {% endfor %} + + + +
+{% 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 %} + +{% endblock %} + +{% block scripts %} + +{% endblock %} + +{% block body %} +

加入97随访   科学了解乙肝治疗

+ +

密码修改成功!

+ +

+ +

+{% endblock body %} + + 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 %} + +{% endblock %} + +{% block body %} +

加入97随访   科学了解乙肝治疗

+ +

+ 忘记了密码? +

+

+ 请在下面输入您注册时使用的邮箱地址, + 我们将把重设密码的链接通过邮件发给您。 +

+ +
{% csrf_token %} + + + + + +
+ + {{ form.email.errors|join:"" }}
+ +
+{% endblock %} + + 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 %} + +{% endblock %} + +{% block scripts %} + +{% endblock %} + +{% block body %} +

加入97随访   科学了解乙肝治疗

+ +

重设密码成功!

+ +

+ 您的密码已经重新设置,现在您可以继续登录账户。 +

+ + +{% endblock %} + + 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 %} + +{% endblock %} + +{% block scripts %} + +{% endblock %} + +{% block body %} +

加入97随访   科学了解乙肝治疗

+ + {% if validlink %} +

请设置新密码

+ +
{% csrf_token %} + + {% for item in form %} + + + + + {% endfor %} +
+ + {{ item.errors|join:"" }}
+ +
+ {% else %} +

重设密码失败

+ +

您使用的密码重设链接无效,可能因为该链接已被使用过。

+

您可以尝试重新申请重设密码。

+ + {% endif %} +{% endblock %} + + 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 %} + +{% endblock %} + +{% block scripts %} + +{% endblock %} + +{% block body %} +

加入97随访   科学了解乙肝治疗

+ +

+ 密码重设邮件已发送! +

+

+ 我们已经向您提交的邮箱地址发送了密码重设说明, + 请注意查收邮件,并按邮件说明来重新设置密码。 +

+ +

+ 还没收到邮件?您可以尝试再次申请重设密码。 +

+

+ +

+ +{% endblock %} + + 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 %} + + + +

尊敬的 {{ user.username }},

+ +

您收到该邮件是因为您已请求重设97随访({{ domain }})账户的密码。

+ +

请打开以下链接来为您的账户设置新密码:
+ {% block reset_link %} + {{ protocol }}://{{ domain }}{% url django.contrib.auth.views.password_reset_confirm uidb36=uid, token=token %} + {% endblock %} +

+ +

您的登录用户名为: {{ user.username }}

+
+ + +

感谢您使用我们的产品!

+
+ +

97随访 团队

+ + +{% 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 %} + +{% endblock %} +{% block body %} +

加入97随访   科学了解乙肝治疗

+
{% csrf_token %} + + {% for item in form %} + + + + + + {% endfor %} +
+ + {{ item.help_text }}{{ item.errors|join:"" }}
+ + {% for error in form.non_field_errors %} + + + + {% endfor %} +
{{ error }}
+ + +   |   + 已有账号 +
+{% 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.+)/$', + '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[0-9A-Za-z]+)-(?P.+)/$', + 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\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) +# }}} + -- cgit v1.2.2