关键:类视图的使用、用itsdangerous模块加密身份信息、使用celery异步发送邮件、使用 Django 的验证系统、redis作为缓存和session存储后端、login_required 装饰器、LoginRequired Mixin类的使用、redis存储浏览记录的流程...
仅用作个人笔记!
目录
1.注册
总体上,注册要做的就是:
- 验证注册信息合法性;
- 处理注册信息,保存用户到数据库
- 发送激活邮件,点击激活链接激活
(1)在templats目录下创建 register.html ,使用表单来提交注册信息;
(2)在 user/views.py 写一个视图 register 用来显示注册页面;
(3)在应用的urls中配置路径,为了反向解析,注意写上name:
(4)注意html文件中的静态文件路径,为了动态的生成路径,使用{% load static %}导入静态资源;
(5)视图register_handle处理提交过来的注册信息,进行注册处理。
使用all方法进行数据校验:python all() 函数用于判断给定的可迭代参数 iterable 中的所有元素是否都为 TRUE,如果是返回 True,否则返回 False。元素除了是 0、空、None、False 外都算 True。
if not all([username, password, email]):
# 若数据不完整
return render(request, 'register.html', {'errmsg': '数据不完整'})
使用正则匹配校验邮箱:
import re
...
# 校验邮箱
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': '邮箱格式不正确'})
用户注册前先校验用户名是否重复:
from user.models import User
...
# 校验用户名是否重复
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
# 用户名不存在
user = None
if user:
# 用户名已存在
return render(request, 'register.html', {'errmsg': '用户名已存在'})
用户注册:
# 进行业务处理: 使用Django认证系统 进行用户注册
user = User.objects.create_user(username, email, password)
user.is_active = 0 # 默认还没激活
user.save()
注册后返回应答,跳转首页(下面反向解析函数 reverse:goods应用下,name=index的视图)
return redirect(reverse('goods:index'))
(6)两个视图使用同一个url
上面视图register_handle和视图register 使用的是两个地址,完全没有必要(登录页和登录处理同理)。
方法:
先在html表单提交时,form 的 action="页面自身" ;
在视图register 中,通过请求方式来判断:if request.method == 'GET'显示注册页面,否则就是注册数据处理。
基于类的视图 使用Python 对象实现视图,提供了除函数视图之外的另一种方式。它们不能替代基于函数的视图,但与基于函数的视图相比,它们是有某些不同和优势的。
(7)使用类视图实现上面的函数视图
from django.views import View
...
class RegisterView(View):
def get(self, request):
"""显示注册页面"""
return render(request, 'register.html')
def post(self, request):
"""进行注册处理"""
...
配置该应用的urls.py:
# urls.py
from django.urls import path, include, re_path
from user.views import RegisterView
app_name = 'user'
urlpatterns = [
path('register', RegisterView.as_view(), name='register'), # 注册、注册处理
]
2.激活账户
2.1.生成激活用户的token用以发邮件
用户注册之后,还要通过邮件才能激活。激活链接中需要包含用户的身份信息, 并且要把身份信息进行加密(生成激活token)。
用到 itsdangerous模块 。
安装:
pip install -U itsdangerous
在注册处理视图中,使用serializer.dumps生成加密token,再发送邮件。
基本的发送邮件参考:【Django 笔记】发送邮件、celery异步任务_django selery-CSDN博客、官方文档:发送邮件
from django.conf import settings
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
...
class RegisterView(View):
......
# 发送激活邮件,包含激活链接: http://127.0.0.1:8000/user/active/用户id
# 激活链接中需要包含用户的身份信息, 并且要把身份信息进行加密
# 加密用户的身份信息,生成激活token
serializer = Serializer(settings.SECRET_KEY, 3600) # 参数1的秘钥借助了settings.py里的SECRET_KEY;参数2为过期时间
info = {'confirm': user.id} # 信息为用户的id
token = serializer.dumps(info) # 默认为bytes数据
token = token.decode('utf8')
# 发邮件
subject = '天天生鲜欢迎信息'
message = ''
sender = settings.EMAIL_FROM
receiver = [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)
# 返回应答, 跳转到首页
return redirect(reverse('goods:index'))
2.2.激活处理
# views.py
from itsdangerous import SignatureExpired
...
class ActiveView(View):
"""用户激活"""
def get(self, request, token): # 点击激活邮件为get请求方式
"""进行用户激活"""
# 进行解密,获取要激活的用户信息
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('激活链接已过期')
# urls.py
...
path('active/<token>', ActiveView.as_view(), name='active'), # 用户激活
2.3.使用selery异步发送邮件
为什么要用selery来异步发送邮件呢,因为耗时任务通常放在后台异步执行:【Django 笔记】发送邮件、celery异步任务_django selery-CSDN博客
(1)安装selery
pip install celery==4.1.0
(2)新建包用来放耗时任务
通常新建一个专门的包来放耗时任务。新建 python包 celery_tasks,在该目录下创建 tasks.py文件 来定义任务(发送邮件)。
此处使用redis作为中间人Broker,需要先启动redis:sudo redis-server /etc/redis/redis.conf 或 sudo service redis start
# 1. celery_tasks/tasks.py
# 使用celery
from celery import Celery
...
# 创建一个Celery类的实例对象
# Celery('celery_tasks.tasks', broker='redis://数据库ip:端口/第几个数据库')
app = Celery('celery_tasks.tasks', broker='redis://192.168.3.2: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)
# 2. 视图文件views.py中,发送邮件的代码改为
from celery_tasks.tasks import send_register_active_email
# 发邮件
send_register_active_email.delay(email, username, token) # 使用delay函数,把任务放入任务队列
...
(注:任务的发出者、中间人、处理者,可以在同一台电脑中启动,也可以不在同一台电脑中启动)
(3)启动任务的处理者
在worker所在的电脑中,启动worker。注意,处理者也需要知道任务的代码(若项目代码改了,这边也要改),将代码复制过去,并在环境中安装好celery等。
在任务处理者一端的,启动的不是整个Django工程,tasks.py 加上这几句:
# 在任务处理者一端加这几句:django环境的初始化
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh.settings")
django.setup()
启动任务的处理者需要指定创建的任务的app在哪一个文件里面,比如.../celery_tasks/tasks.py;-l info 或 --loglevel=info 设置打印信息的级别。
在项目目录下,启动celery
方法一:
celery -A celery_tasks.tasks worker -l info
(celery -A 任务所在的文件 worker -l info )方法二:守护进程方式启动,日志记录在celerylog.log里(celery_tasks.tasks为我项目任务所在的文件)
celery multi start w1 -A celery_tasks.tasks -l info --logfile=celerylog.log --pidfile=celerypid.pid停止:celery multi stop w1 -A celery_tasks.tasks -l info
重启:celery multi restart w1 -A celery_tasks.tasks -l info
如果报错或者celery无法启动,可能是版本的问题,参考celery无法启动的问题 SyntaxError: invalid syntax。
我的是python3.5,一些相关环境如下:
celery==4.1.0
Django==2.2
django-redis==4.11.0
django-redis-sessions==0.5.6
kombu==4.1.0
mysqlclient==1.4.6
redis==2.10.6
。。。
启动后,部分显示内容如下:
-------------- celery@ubuntu v4.1.0 (latentcall)
---- **** -----
--- * *** * -- Linux-4.4.0-174-generic-x86_64-with-Ubuntu-16.04-xenial 2020-04-09 16:40:52
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app: celery_tasks.tasks:0x7f2c673412b0
- ** ---------- .> transport: redis://192.168.43.202:6379/8
- ** ---------- .> results: disabled://
- *** --- * --- .> concurrency: 1 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery
[tasks]
. celery_tasks.tasks.send_register_active_email
。。。省略
...... celery@ubuntu ready.
在使用的过程中,如果报:AttributeError: 'float' object has no attribute 'items',可以把redis版本换回 大于2.10.5小于3 的版本之一。
没问题后,在处理端,可看到
......省略
[2020-04-09 22:15:11,567: INFO/MainProcess] Received task: celery_tasks.tasks.send_register_active_email[3fdbaf02-2238-49ba-ba2e-99c950db44d8]
[2020-04-09 22:15:18,830: INFO/ForkPoolWorker-1] Task celery_tasks.tasks.send_register_active_email[3fdbaf02-2238-49ba-ba2e-99c950db44d8] succeeded in 7.2602706589968875s: None
以上完成后在注册页注册,会自动发一封邮件到邮箱,在邮箱中点击链接,账户状态变为已激活,并跳转首页。
3.登录
(1)登录将使用 Django 的验证系统中的 验证用户 来认证一组给定的用户名和密码。并使用“用户如何登陆”来登录用户。
(2)配置redis作为Django缓存和session存储后端(记住用户名)
默认时,session是保存在 django_session 这个数据库里(session),很多情况下没必要,而且经常用到的信息还会造成数据库频繁访问、速度慢。使用redis存储session即可,可参考:Django中使用redis数据库存储session。
这里还可使用另外一种方式,使用 django-redis包 :django-redis 基于 BSD 许可, 是一个使 Django 支持 Redis cache/session 后端的全功能组件。依照下面的用户指南安装配置:django-redis 中文文档 — Django-Redis 4.7.0 文档、用户指南
# setting.py
...
# Djagno 的缓存配置
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://192.168.43.202:6379/9",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
# 配置session存储
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
# user/views.py
from django.contrib.auth import authenticate, login
...
# /user/login
class LoginView(View):
...
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': '数据不完整'})
# 业务处理:登录校验
user = authenticate(username=username, password=password)
if user is not None:
# 用户名密码正确
if user.is_active: # 用户已激活
login(request, user)
response = redirect(reverse('goods:index'))
# 判断是否需要记住用户名
remember = request.POST.get('remember')
if remember == 'on':
response.set_cookie('username', username, max_age=7 * 24 * 3600)
else:
response.delete_cookie('username')
return response
else:
return render(request, 'login.html', {'errmsg': '账户未激活'})
else:
return render(request, 'login.html', {'errmsg': '用户名或密码错误'})
4.用户中心
要点: Web 请求的认证、login_required 装饰器、
4.1.页面显示
注意模板中url反向解析的使用。格式:
href="{% url 'app_name:url的name' %}",如:
href="{% url 'user:address' %}"
4.2.登录验证和登录跳转
要点:login_required 装饰器、LoginRequired Mixin类的使用
注意,用户中心是在用户登录之后才能访问的。
login_required 装饰器
使用Djagno默认的认证系统,通过 login_required 装饰器 来判定登录状态。
- 如果用户没有登录,会重定向到 settings.LOGIN_URL ,并传递绝对路径到查询字符串中。例如:
/accounts/login/?next=/polls/3/
。- 如果用户已经登录,则正常执行视图。视图里的代码可以假设用户已经登录了
问题:
(1)由于我们的函数不是函数视图,是类视图,该如何使用装饰器呢?
参考装饰基于类的视图,可以在urls.py的配置中来使用装饰器,类似下面:
# 应用的urls.py
from django.contrib.auth.decorators import login_required
...
app_name = 'user'
urlpatterns = [
path('',login_required(UserInfoView.as_view()), name='user'), # 用户中心-信息页
...
]
(2)如果未登录,默认跳转的是 /accounts/login/?next=... ,如何把前面的 /accounts/login/ 改为我们项目的地址?如何获取next后面所要跳转的地址,并在登录后跳转?
在setting.py配置中进行配置:
# 配置登录url地址
LOGIN_URL = '/user/login'
这样,未登录时打开用户中心(http://127.0.0.1:8000/user)就会跳转 http://127.0.0.1:8000/user/login?next=/user/ 。
根据上面的跳转链接,这是一个get请求,所以,可以在views.py 的相关视图中获取next参数,并用来跳转:
...
# 获取登录后所要跳转到的地址,默认跳转到首页
next_url = request.GET.get('next', reverse('goods:index')) # 记得给get设置一个获取不到值时的默认值
response = redirect(next_url)
...
return response
这样,在登录后就会跳转http://127.0.0.1:8000/user
LoginRequired Mixin类的使用
上面虽然实现了登录后页面的跳转,但是每个相关视图的url配置都要用 login_required ,不方便。
使用基于类的视图时,可以使用 LoginRequiredMixin 实现和 login_required 相同的行为(一个是类、一个是装饰器)。这个 Mixin 应该在继承列表中最左侧的位置。
注:将共同的行为运用于多个类的一种方法是编写一个封装as_view()方法的mixin。比如有多个通用视图都使用login_required 装饰器 ,可以这样实现一个mixin:
from django.contrib.auth.decorators import login_required class LoginRequiredMixin(object): @classmethod def as_view(cls, **initkwargs): # 调用父类的as_view view = super(LoginRequiredMixin, cls).as_view(**initkwargs) return login_required(view)
其实Django新的版本已经集成了类似上面功能的类了,不需要我们自己定义。所以需要用户登录之后才能访问的视图,导入并继承于LoginRequired Mixin类和View类即可:
from django.contrib.auth.mixins import LoginRequiredMixin
...
class UserInfoView(LoginRequiredMixin, View):
def get(self, request):
return render(request, 'user_center_info.html', {'page': 'user'})
附:
限制访问页面最简单的办法就是检查 request.user.is_authenticated 并重定向到登录页面(Web 请求的认证)。
如果已经通过 django.contrib.auth.login() 登录的用户想退出登录,可以在视图中使用 django.contrib.auth.logout() 。需要传入 HttpRequest 对象,并且该函数不会返回值。
# 视图中 from django.contrib.auth import logout ... # /user/logout class LogoutView(View): def get(self, request): logout(request) return redirect(reverse('goods:index')) # 模板文件中 <a href="{% url 'user:logout' %}">退出</a>
4.3.用户中心
地址页
包含显示地址和添加地址两个部分。
为了方便地址的查询和减少代码冗余,在models.py定义一个地址相关的 模型管理器类 。
# models.py
class AddressManager(models.Manager):
"""地址模型管理器类"""
# 1.改变原有查询的结果集:all()
# 2.封装方法:用户操作模型类对应的数据表(增删改查)
def get_default_address(self, user):
"""获取用户默认收货地址"""
# self.model:获取self对象所在的模型类
try:
address = self.get(user=user, is_default=True) # models.Manager
except self.model.DoesNotExist:
# 不存在默认收货地址
address = None
return address
# views.py视图中调用
...
address = Address.objects.get_default_address(user)
个人信息页
包含两个部分,基本信息和最近浏览。
(1)何时需要添加历史浏览记录?
在访问商品的详情页时,添加浏览记录。
(2)何时需要获取浏览记录?
访问用户中心个人信息的时候获取浏览记录。
(3)历史浏览记录存储在哪?
可使用redis数据库存储浏览记录。因为内存型的数据库读写效率高。
(每个用户的浏览记录使用一条数据来保存,使用list类型来保存用户的浏览记录,每个用户对应的键名为 history_用户id , 值为一个列表,添加记录时从列表左侧插入)
获取用户的浏览记录:
# 获取用户的历史浏览记录
# 方法1
# from redis import StrictRedis
# ...
# sr = StrictRedis(host='192.168.43.2', port='6379', db=9)
# 方法2
# 用了django_redis包可以导入并用下面的方法
from django_redis import get_redis_connection
...
con = get_redis_connection('default') # default对应setting中缓存配置的default
history_key = 'history_%d' % user.id
# 获取用户最新浏览的5个商品的id
sku_ids = con.lrange(history_key, 0, 4)
# 遍历获取用户浏览的商品信息
goods_li = []
for id in sku_ids:
goods = GoodsSKU.objects.get(id=id)
goods_li.append(goods)
# 组织上下文
context = {'page': 'user',
'address': address,
'goods_list': goods_li}
return render(request, 'user_center_info.html',context)
02 注册、登录、用户中心 (itsdangerous模块加密、celery异步、 Django 的验证系统、redis作为缓存等)
-----end-----