开源web框架django知识总结(十二)
户中心界面 (一)
定义模型类基类
为了给项目中模型类补充数据创建时间和更新时间两个字段,我们需要定义模型类基类。 新建
aerf_mall.utils/BaseModel.py
文件,创建模型类基类。Django模型中
auto_now_add=True时为添加时的时间,更新对象时不会有变动。
auto_now=True无论是你添加还是修改对象,时间为你添加或者修改的时间。
from django.db import models
class BaseModel(models.Model):
"""为模型类补充字段"""
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
abstract = True # 说明是抽象模型类, 用于继承使用,数据库迁移时不会创建BaseModel的表
1、判断用户是否登录
需求:
- 当用户登录后,才能访问用户中心。
- 如果用户未登录,就不允许访问用户中心,将用户引导到登录界面。
实现方案:
- 需要判断用户是否登录。
- 根据是否登录的结果,决定用户是否可以访问用户中心。
2. is_authenticated
判断用户是否登录
介绍:
- Django用户认证系统提供了方法
request.user.is_authenticated()
来判断用户是否登录。- 如果通过登录验证则返回True。反之,返回False。
- 缺点:登录验证逻辑很多地方都需要,所以该代码需要重复编码好多次。
3. login_required装饰器
判断用户是否登录
3.1.定义View子类封装login_required装饰器
- 提示:
LoginRequired(object)
依赖于视图类View
,复用性很差。
3.2、定义验证用户是否登录扩展
在项目根木下utils包下,新建views.py
注意一下,这里为什么不在user.utils.py中写?
from django.http import JsonResponse
# 定义一个装饰器,验证是否已经登陆
def login_required(func):
# func:是视图函数
def wrapper(request, *args, **kwargs):
# 添加功能代码
if request.user.is_authenticated:
return func(request, *args, **kwargs)
else:
return JsonResponse({'code': 400, 'errmsg': '您未登陆!'}, status=401)
return wrapper
4、用户中心页接口
from aerf_mall.utils.views import login_required # 注意修改导包路径
from django.utils.decorators import method_decorator #主要的作用就是解决装饰器不能直接的装饰类视图函数
class UserInfoView(View):
@method_decorator(login_required)
def get(self, request):
# 1、获取用户对象
user = request.user
# 2、构造响应数据返回
return JsonResponse({
'code': 0,
'errmsg': 'ok',
'info_data': {
'username': user.username,
'mobile': user.mobile,
# 'email': user.email, # email模型没写,暂时先注释
# 'email_active': user.email_active # email模型没写,暂时先注释
}
})
urls.py
# 用户中心的子路由
re_path(r'^info/$', UserInfoView.as_view()),
用户中心
1. 用户基本信息逻辑分析
以下是要实现的后端逻辑
- 用户模型补充
email_active
字段 - 查询并展示用户基本信息
- 添加邮箱
- 发送邮箱验证邮件
- 验证邮箱
- 安装包:pip install itsdangerous
2.用户模型补充email_active字段 users.models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
from itsdangerous import TimedJSONWebSignatureSerializer,BadSignature
from django.conf import settings
# Create your models here.
class User(AbstractUser):
"""自定义用户模型类"""
mobile = models.CharField(
unique=True,
verbose_name='手机号',
null=True,
max_length=11
)
# 新增 email_active 字段
# 用于记录邮箱是否激活, 默认为 False: 未激活
email_active = models.BooleanField(default=False,verbose_name='邮箱验证状态')
class Meta:
db_table = 'tb_users'
verbose_name = '用户'
verbose_name_plural = verbose_name
def __str__(self):
return self.username
# 用户模型类中封装该方法
def generate_verify_email_url(self):
"""
生成当前用户的令牌;并且拼接邮箱确认的连接;
:return: 返回确认连接
"""
serializer = TimedJSONWebSignatureSerializer(secret_key=settings.SECRET_KEY)
user_info = {'user_id': self.id, 'email': self.email}
token = serializer.dumps(user_info) # b'....'
verify_url = settings.EMAIL_VERIFY_URL + token.decode()
return verify_url
# 校验token值,返回用户对象 @staticmethod 不访问实例属性、不调用实例方法
@staticmethod
def check_verify_email_token(token):
"""
校验token值
:param token: token值
:return: 用户对象 或 None
"""
serializer = TimedJSONWebSignatureSerializer(secret_key=settings.SECRET_KEY)
try:
user_info = serializer.loads(token)
except BadSignature as e:
print(e)
return None
user_id = user_info.get('user_id')
try:
user = User.objects.get(pk=user_id)
except User.DoesNotExist as e:
print(e)
return None
return user
itsdangerous的用法:
有时候你想向不可信的环境发送一些数据,但如何安全完成这个任务呢?解决的方法就是签名。使用只有你自己知道的密钥,来加密签名你的数据,并把加密后的数据发给别人。当你取回数据时,你就可以确保没人篡改过这份数据。
诚然,接收者可以破译内容,来看看你的包裹里有什么,但他们没办法修改你的内容,除非他们也有你的密钥。所以只要你保管好你的密钥,并且密钥足够复杂,一切就OK了。
itsdangerous内部默认使用了HMAC和SHA1来签名,基于 Django 签名模块。它也支持JSON Web 签名 (JWS)。这个库采用BSD协议,由Armin Ronacher编写,而大部分设计与实现的版权归Simon Willison和其他的把这个库变为现实的Django爱好者们。
>>>from itsdangerous import TimedJSONWebSignatureSerializer as ts
>>>serializer = ts('abc',3600) #指定密钥,及过期时间 秒
>>>data = serializer.dumps({"openid":"123456"})
>>>data
b'eyJhbGciOiJIUzUxMiIsImlhdCI6MTYxNjU2NDY0NCwiZXhwIjoxNjE2NTY4MjQ0fQ.eyJvcGVuaWQiOiIxMjM0NTYifQ.0nc4JtgPmohPz9yWRmslPJrBFbrgv6bC5gMv41QCNnRWIqvEe6RuDfksShPp9xEmUN4i-hhYBMEM0Hwgry0wpQ'
>>>data.decode()
'eyJhbGciOiJIUzUxMiIsImlhdCI6MTYxNjU2NDY0NCwiZXhwIjoxNjE2NTY4MjQ0fQ.eyJvcGVuaWQiOiIxMjM0NTYifQ.0nc4JtgPmohPz9yWRmslPJrBFbrgv6bC5gMv41QCNnRWIqvEe6RuDfksShPp9xEmUN4i-hhYBMEM0Hwgry0wpQ'
>>>serializer.loads(data)
{'openid': '123456'}
>>>servializer = ts('abc',1)
>>>data = servializer.dumps({"openid":"123456"})
>>>servializer.loads(data)
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "/home/pyvip/.virtualenvs/aerf_test/lib/python3.6/site-packages/itsdangerous/jws.py", line 205, in loads
date_signed=self.get_issue_date(header),
itsdangerous.exc.SignatureExpired: Signature expired
补充完字段后,需要进行迁移。
python manage.py makemigrations
python manage.py migrate
3.在users.views.py中 查询用户基本信息模块内添加:
class UserInfoView(View):
@method_decorator(login_required)
def get(self, request):
# 1、获取用户对象
user = request.user
# 2、构造响应数据返回
return JsonResponse({
'code': 0,
'errmsg': 'ok',
'info_data': {
'username': user.username,
'mobile': user.mobile,
'email': user.email, # email模型添加后写
'email_active': user.email_active # email模型添加后写
}
})
添加和验证邮箱(注意限制)
1.准备发邮件服务器 https://mail.163.com/
1.1.点击进入《设置》界面
3.开启《授权码》,并完成验证短信
5.完成《授权码》设置
6.配置邮件服务器 在dev.py中添加
# 发送短信的相关设置, 这些设置是当用户没有发送相关字段时, 默认使用的内容:
# 发送短信必须进行的设置:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# 我们使用的 smtp服务器 地址
EMAIL_HOST = 'smtp.163.com'
# 端口号
EMAIL_PORT = 25 # 或者 465/587是设置了 SSL 加密方式 163邮箱465/587有限制,QQ邮箱,可以用。
# 下面的内容是可变的, 随后台设置的不同而改变:
# 发送邮件的邮箱
EMAIL_HOST_USER = '你的163邮箱'
# 在邮箱中设置的客户端授权密码
EMAIL_HOST_PASSWORD = '授权码' # 如果重新设置了新的授权码,直接使用最新的授权码即可
EMAIL_USE_TLS = True # 这里必须是 True,否则发送不成功
# 收件人看到的发件人
EMAIL_FROM = '阿尔法商城<你的163邮箱>'
# 邮箱验证链接
EMAIL_VERIFY_URL = 'http://127.0.0.1/success_verify_email.html?token='
# 发送短信必须进行的设置:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# 我们使用的 smtp服务器 地址
EMAIL_HOST = 'smtp.163.com'
# 端口号
EMAIL_PORT = 25 # 或者 465/587是设置了 SSL 加密方式
# 下面的内容是可变的, 随后台设置的不同而改变:
# 发送邮件的邮箱
EMAIL_HOST_USER = 'suifeng_1228@163.com'
# 在邮箱中设置的客户端授权密码
EMAIL_HOST_PASSWORD = 'ELFMFARWZRGDIVUQ' # 如果重新设置了新的授权码,直接使用最新的授权码即可
EMAIL_USE_TLS = True # 这里必须是 True,否则发送不成功
# 收件人看到的发件人
EMAIL_FROM = '阿尔法商城<suifeng_1228@163.com>'
# 邮箱验证链接
EMAIL_VERIFY_URL = 'http://127.0.0.1/success_verify_email.html?token='
发送邮箱验证邮件
重要提示:
- 发送邮箱验证邮件是耗时的操作,不能阻塞阿尔法商城的响应,所以需要异步发送邮件。
- 我们继续使用Celery实现异步任务。
1. 定义和调用发送邮件异步任务
tasks.py
from django.core.mail import send_mail
from django.conf import settings
from celery_tasks.main import app
@app.task(name='send_verify_email')
def send_verify_email(to_email, verify_url):
subject = '阿尔法商城邮箱验证'
html_message = '<p>尊敬的用户您好!</p>' \
'<p>感谢您使用阿尔法商城。</p>' \
'<p>您的邮箱为:%s 。请点击此链接激活您的邮箱:</p>' \
'<p><a href="%s">%s<a></p>' % (to_email, verify_url, verify_url)
send_mail(
subject,
'',
settings.EMAIL_FROM,
[to_email],
html_message=html_message
)
补充:
# bind: 保证task对象会作为第一个参数自动传入。bind=True,函数加self,代表任务对象
# name:异步任务别名
# retry_backoff : 异常自动重试的时间间隔 第n次(retry_backoff*2^(n-1))s
# max_retries:异常自动重试次数的上限
@app.task(bind=True, name='send_verify_email', retry_backoff=3)
def send_verify_email(self, to_email, verify_url):
"""
发送验证邮箱邮件
:param to_email: 收件人邮箱
:param verify_url: 验证链接
:return: None
"""
subject = "阿尔法商城邮箱验证"
html_message = '<p>尊敬的用户您好!</p>' \
'<p>感谢您使用阿尔法商城。</p>' \
'<p>您的邮箱为:%s 。请点击此链接激活您的邮箱:</p>' \
'<p><a href="%s">%s<a></p>' % (to_email, verify_url, verify_url)
try:
send_mail(subject, "", settings.EMAIL_FROM, [to_email], html_message=html_message)
except Exception as e:
# 有异常自动重试三次
raise self.retry(exc=e, max_retries=3)
2.注册发邮件的任务:main.py
- 在发送邮件的异步任务中,我们用到了Django的配置文件。
- 所以我们需要修改celery的启动文件main.py。
- 在其中指明celery可以读取的Django配置文件。
- 最后记得注册新添加的email的任务
"""
该文件作为异步应用程序初始化的模块
"""
# 在异步任务程序中加载django的环境
import os
os.environ.setdefault(
'DJANGO_SETTINGS_MODULE',
'aerf_mall.settings.dev'
)
from celery import Celery
# 初始化一个应用程序对象
app = Celery("aerf")
# 加载配置文件——参数是配置文件(模块)的导包路径
# 我们将来是在celery_tasks包所在的目录为工作目录运行异步程序;
app.config_from_object('celery_tasks.config')
# 告知app监听的任务有哪些
# 该函数的参数是一个列表,列表里写的是任务包的导包路径
app.autodiscover_tasks([
'celery_tasks.sms', # 发送短信任务
'celery_tasks.email', # 发送邮件任务
])
添加邮箱后端逻辑
1. 添加邮箱接口设计和定义
1.请求方式
选项 | 方案 |
---|---|
请求方法 | PUT |
请求地址 | /emails/ |
2.请求参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
string | 是 | 邮箱 |
3.响应结果:JSON
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
2.更新邮箱
from celery_tasks.email.tasks import send_verify_email
# 更新邮箱
class EmailView(View):
@method_decorator(login_required)
def put(self, request):
# 1、提取参数
data = json.loads(request.body.decode())
email = data.get('email')
# 2、校验参数
if not email:
return JsonResponse({'code': 400, 'errmsg': '缺少email'})
if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
return JsonResponse({'code': 400, 'errmsg': '邮箱格式有误!'})
# 3、数据处理(部分更新) ———— 更新邮箱
user = request.user
try:
user.email = email
user.email_active = False
user.save()
except Exception as e:
print(e)
# ======发送邮箱验证邮件=======
verify_url = user.generate_verify_email_url()
send_verify_email.delay(email, verify_url) # 异步调用!
# 4、构建响应
return JsonResponse({'code': 0, 'errmsg': 'ok'})
3. 验证邮箱后端逻辑实现
验证邮箱的核心:就是将用户的
email_active
字段设置为True
# 确认邮箱接口
class VerifyEmailView(View):
def put(self, request):
# 1、提取查询字符串中token
token = request.GET.get('token')
# 2、校验token
user = User.check_verify_email_token(token)
if not user:
return JsonResponse({'code': 400, 'errmsg': '验证邮件无效!'})
# 3、如果token有效,把邮箱的激活状态设置为True
user.email_active = True
user.save()
return JsonResponse({'code': 0, 'errmsg': '邮箱激活成功!'})
4、添加url
users.urls.py
# 更新邮箱
re_path(r'^emails/$', EmailView.as_view()),
# 验证并激活邮箱接口
re_path(r'^emails/verification/$', VerifyEmailView.as_view()),
5.启动Celery
celery -A celery_tasks.main worker -l info