前期准备
登录和注册功能应该定义在用户模块中,即apps/user应用中
准备工作:
- 将项目的前端页面放入static文件夹中,
- 将登录/注册的页面,和首页一起放入templates中,然后需要把相应的静态文件引用目录进行改变,否则样式会无法显示
- 对相关的views,urls等文件进行编码
其中引用静态文件static可以使用Django的标签,在之前需要对setting.py进行引入static文件夹的设置。
STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
引入和使用标签:
首页模块应该在商品模块中定义,因此在apps/goods中,对urls.py和views.py进行编辑:
urls.py
from django.conf.urls import url
from goods import views
# goods在pycharm中可以会显示报错,但是项目运行起来是正确的,因为在搭建项目时,已经把apps的路径放到了配置文件中
urlpatterns = [
url(r'^$', views.index, name='index'), # 首页
]
views.py
from django.shortcuts import render
# Create your views here.
# http://127.0.0.1:8000
def index(request):
'''首页'''
return render(request, 'index.html')
登录、注册基本代码实现
urls.py文件,
from django.conf.urls import url
from user.views import RegisterView, ActiveView, LoginView
urlpatterns = [
# url(r'^register$', views.register, name='register'), # 注册
# url(r'^register_handle$', views.register_handle, name='register_handle'), # 注册处理
url(r'^register$', RegisterView.as_view(), name='register'), # 注册
url(r'^active/(?P<token>.*)$', ActiveView.as_view(), name='active'), # 用户激活
url(r'^login$', LoginView.as_view(), name='login'), # 登录
]
这里一般的方式是采用上面两行被注释的url语句,即用方法的方式来定义视图,register和register_handle都是方法,Django还提供了用类的方式来定义视图,即下面未被注释的url语句。这该urls.py文件中,有几点需要注意的地方:
- ?P<token>方式,即把url传递的参数命名为token变量,如果不指定固定名称,用(.*)即可。
- name属性是为了域名的反向解析,配合setting.py中的namespace,可以实现反向解析。
views.py文件
from django.shortcuts import render,redirect
# 域名反向解析
from django.core.urlresolvers import reverse
# 发送邮件
from django.core.mail import send_mail
# Django认证系统的包
from django.contrib.auth import authenticate, login
from django.views.generic import View
from django.http import HttpResponse
# Django的配置文件
from django.conf import settings
from user.models import User
# 自己定义的celery异步任务
from celery_tasks.tasks import send_register_active_email
# 发送邮件时,邮件中的URL包含用户的信息,这里对用户信息进行加密
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from itsdangerous import SignatureExpired
import re
import time
# Create your views here.
# /user/register
# GET POST PUT DELETE OPTION
def register(request):
'''注册'''
if request.method == 'GET':
# 显示注册页面
return render(request, 'register.html')
else:
# 进行注册处理
# 接收数据
username = request.POST.get('user_name')
password = request.POST.get('pwd')
email = request.POST.get('email')
allow = request.POST.get('allow')
# 进行数据校验
if not all([username, password, email]):
# 数据不完整
return render(request, 'register.html', {'errmsg': '数据不完整'})
# 校验邮箱
if not re.match(r'^[a-z0-9][\w.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
return render(request, 'register.html', {'errmsg': '邮箱格式不正确'})
if allow != 'on':
return render(request, 'register.html', {'errmsg': '请同意协议'})
# 校验用户名是否重复
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
# 用户名不存在
user = None
if user:
# 用户名已存在
return render(request, 'register.html', {'errmsg': '用户名已存在'})
# 进行业务处理: 进行用户注册
user = User.objects.create_user(username, email, password)
user.is_active = 0
user.save()
# 返回应答, 跳转到首页
return redirect(reverse('goods:index'))
def register_handle(request):
'''进行注册处理'''
# 接收数据
username = request.POST.get('user_name')
password = request.POST.get('pwd')
email = request.POST.get('email')
allow = request.POST.get('allow')
# 进行数据校验
if not all([username, password, email]):
# 数据不完整
return render(request, 'register.html', {'errmsg':'数据不完整'})
# 校验邮箱
if not re.match(r'^[a-z0-9][\w.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
return render(request, 'register.html', {'errmsg':'邮箱格式不正确'})
if allow != 'on':
return render(request, 'register.html', {'errmsg':'请同意协议'})
# 校验用户名是否重复
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
# 用户名不存在
user = None
if user:
# 用户名已存在
return render(request, 'register.html', {'errmsg':'用户名已存在'})
# 进行业务处理: 进行用户注册
user = User.objects.create_user(username, email, password)
user.is_active = 0
user.save()
# 返回应答, 跳转到首页
return redirect(reverse('goods:index'))
# /user/register
class RegisterView(View):
'''注册'''
def get(self, request):
'''显示注册页面'''
return render(request, 'register.html')
def post(self, request):
'''进行注册处理'''
# 接收数据
username = request.POST.get('user_name')
password = request.POST.get('pwd')
email = request.POST.get('email')
allow = request.POST.get('allow')
# 进行数据校验
if not all([username, password, email]):
# 数据不完整
return render(request, 'register.html', {'errmsg': '数据不完整'})
# 校验邮箱
if not re.match(r'^[a-z0-9][\w.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
return render(request, 'register.html', {'errmsg': '邮箱格式不正确'})
if allow != 'on':
return render(request, 'register.html', {'errmsg': '请同意协议'})
# 校验用户名是否重复
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
# 用户名不存在
user = None
if user:
# 用户名已存在
return render(request, 'register.html', {'errmsg': '用户名已存在'})
# 进行业务处理: 进行用户注册,Django认证系统的create_user可以进行注册
user = User.objects.create_user(username, email, password)
user.is_active = 0
user.save()
# 发送激活邮件,包含激活链接: http://127.0.0.1:8000/user/active/3
# 激活链接中需要包含用户的身份信息, 并且要把身份信息进行加密
# 加密用户的身份信息,生成激活token
serializer = Serializer(settings.SECRET_KEY, 3600)
info = {'confirm':user.id}
token = serializer.dumps(info) # bytes
token = token.decode()
# 发邮件
send_register_active_email.delay(email, username, token)
# 返回应答, 跳转到首页
return redirect(reverse('goods:index'))
class ActiveView(View):
'''用户激活'''
def get(self, request, token):
'''进行用户激活'''
# 进行解密,获取要激活的用户信息
serializer = Serializer(settings.SECRET_KEY, 3600)
try:
info = serializer.loads(token)
# 获取待激活用户的id
user_id = info['confirm']
# 根据id获取用户信息
user = User.objects.get(id=user_id)
user.is_active = 1
user.save()
# 跳转到登录页面
return redirect(reverse('user:login'))
except SignatureExpired as e:
# 激活链接已过期
return HttpResponse('激活链接已过期')
# /user/login
class LoginView(View):
'''登录'''
def get(self, request):
'''显示登录页面'''
# 判断是否记住了用户名
if 'username' in request.COOKIES:
username = request.COOKIES.get('username')
checked = 'checked'
else:
username = ''
checked = ''
# 使用模板
return render(request, 'login.html', {'username':username, 'checked':checked})
def post(self, request):
'''登录校验'''
# 接收数据
username = request.POST.get('username')
password = request.POST.get('pwd')
# 校验数据
if not all([username, password]):
return render(request, 'login.html', {'errmsg':'数据不完整'})
# 业务处理:登录校验 authenticate是认证系统提供的方法,登录成功则返回User对象
user = authenticate(username=username, password=password)
if user is not None:
# 用户名密码正确
if user.is_active:
# 用户已激活
# 记录用户的登录状态
login(request, user)
# 跳转到首页
response = redirect(reverse('goods:index')) # HttpResponseRedirect
# 判断是否需要记住用户名
remember = request.POST.get('remember')
if remember == 'on':
# 记住用户名
response.set_cookie('username', username, max_age=7*24*3600)
else:
response.delete_cookie('username')
# 返回response
return response
else:
# 用户未激活
return render(request, 'login.html', {'errmsg':'账户未激活'})
else:
# 用户名或密码错误
return render(request, 'login.html', {'errmsg':'用户名或密码错误'})
异步发送邮件
在发送邮件等操作时,操作本身需要花费大量的时间,如果采用同步的方式处理,那么就会给用户不好的用户体验(在注册页面会停留很久),这时候需要采用异步的方式来发送邮件,这时候注册操作会立即完成,而发送邮件操作则交给中间件完成,常用的可以采用redis,python中负责异步处理的第三方模块是celery。
新建tasks.py文件,在中间写一些需要异步处理的函数。如注册的发送邮件:
# 使用celery
from django.core.mail import send_mail
from django.conf import settings
from celery import Celery
import time
# 在任务处理者一端加这几句
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh3.settings")
django.setup()
# 创建一个Celery类的实例对象
app = Celery('celery_tasks.tasks', broker='redis://127.0.0.1:6379/8')
# 定义任务函数
@app.task
def send_register_active_email(to_email, username, token):
'''发送激活邮件'''
# 组织邮件信息
subject = '天天生鲜欢迎信息'
message = ''
sender = settings.EMAIL_FROM
receiver = [to_email]
html_message = '<h1>%s, 欢迎您成为天天生鲜注册会员</h1>请点击下面链接激活您的账户<br/><a href="http://127.0.0.1:8000/user/active/%s">http://127.0.0.1:8000/user/active/%s</a>' % (username, token, token)
send_mail(subject, message, sender, receiver, html_message=html_message)
time.sleep(5) # 模拟发送邮件的时间
代码中注意事项:
- celery涉及中间件的概念,即broker,这里发送任务的和负责处理任务的都是同一台电脑,因此地址是127.0.0.1上,实际上负责处理的可能在另一台电脑上。
- 任务处理者这里执行任务也需要项目的源代码,因此需要将项目代码拷贝到任务处理着一方。并且在任务处理着一方需要额外的代码(在上方已经注明),因为发送邮件需要读取Django的配置文件。
- 具体操作为:代码完成之后,在任务处理中一方进入项目的文件夹(如dailyfresh3),执行命令:
celery -A celery_tasks.tasks worker -l info
这个时候任务处理中已经等待任务的到达:(忽略警告,警告产生的原因是没用将debug置为false)
- 在register页面成功注册一个用户时,就会异步发送邮件,而在用户界面不需要等待。