(According to the content of teacher's live broadcast)
1. Basic user information
1. Logical analysis of user basic information
The following is the backend logic to be implemented
User model supplement email_active field
Query and render basic user information
Add mailbox
Send email verification email
Verify email
2. Add email_active field to user model
There is an email field in the user model, but there is no field for whether the mailbox is activated or not. You need to add a field
For existing data in the table, when adding fields, you generally need to add default values
email_active = models.BooleanField(default=False, verbose_name='邮箱验证状态')
After adding the fields, you need to migrate.
python manage.py makemigrations
python manage.py migrate
3. Query basic user information
When getting the user center page, query user information and pass relevant content as parameters to the front end.
In the UserInfoView.get() method, the request will carry users information
# ./apps/users/views.py
context = {
"username": request.user.username,
"mobile": request.user.mobile,
"email": request.user.email,
"email_active": request.user.email_active,
}
return render(request, 'user_center_info.html',context=context)
4. Render user basic information
When displaying user information on the front end, there are 3 methods:
Ajax way to query and get display
Django's DTL template syntax display
Vue way display
In order to unify the front-end implementation method, the vue method is used in the project template to display
In order to keep the template syntax consistent, write template rendering variables in django into js and assign them to js variables
user_center_info.html:
user_center_info.html
user_center_info.js
5. Add email information
After filling in the mailbox, save the mailbox information by put method
5.1 Request method
Options
Program
Request method
PUT
Request address
/email/
5.2 Request parameters:
parameter name
Types of
Must pass
Description
email
string
Yes
{email:mailbox name}
5.3 Response result: HTML
Field
Description
Email verification failed
Respond to error prompts
Email verification succeeded
Redirect to user center
5.4 Implementation of back-end views
Define an EmailView class in users.views.py, use the put method to save the mailbox name to the user table
Receiving parameters: email
Put method parameters are stored in request.body
The request.body data is of byte type and needs to be converted into a string by decode()
Convert to json type
Check mailbox:
Regular expression to verify whether the mailbox is correct
If not correct, return an error
Save email information
Under normal circumstances, the user will arrive at this interface after logging in. It is necessary to verify whether the user is logged in (optimize later)
After confirming the login, modify the attribute value of request.user.email and save it to complete the saving of the mailbox information
Send activation email (optimize later)
Return response result
# ./apps/users/views.py
class EmailView(View):
"""添加邮箱"""
def put(self,request):
# 接收参数
# print(request.body) # 建议调试看一下数据及数据类型
json_str = request.body.decode()
# print(type(json_str))
json_dict = json.loads(json_str)
email = json_dict.get('email')
# 校验参数
if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
return http.HttpResponseForbidden('参数邮箱有误')
# 存数据
try:
request.user.email = email
request.user.save()
except Exception as e:
return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '添加邮箱失败'})
# 发送邮件
# 响应结果
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK'})
5.5 Define routing
# 保存邮件
path('email/', views.EmailView.as_view()),
5.6 Front end user_center_info.js
When click save, execute the save_mail function of this js
First to judge whether the mailbox is correct,
If it is correct, submit it to the background
Define url
Define put data transfer method
Send the request using ajax
The return type is json
6. The backend judges whether the user is logged in
6.1 Customize the extension class that determines whether the user is logged in: return JSON
When getting the personal user center page, the function to judge whether the user is logged in has been written
The UserInfoView class continues django's LoginRequiredMixin class and view class
HttpResponseRedirect returned by LoginRequiredMixin
The put method returns JsonResponse
Therefore, inherit the LoginRequiredMixin class and rewrite the return method
Store the custom class in ./utils/views.py
Override the handle_no_permission method
# /utils/views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django import http
from utils.response_code import RETCODE
class LoginRequiredJSONMixin(LoginRequiredMixin):
"""自定义判断用户是否登录的扩展类:返回JSON"""
def handle_no_permission(self):
"""直接响应JSON数据"""
return http.JsonResponse({'code': RETCODE.SESSIONERR, 'errmsg': '用户未登陆'})
6.2 Optimize the EmailView class
Inherit the custom extension class that judges whether the user is logged in to realize the user login judgment
class EmailView(LoginRequiredJSONMixin,View):
.....
7. Send email verification email
7.1 Django sending mail configuration
7.2 Define and call the asynchronous task of sending mail
Sending email verification emails is a time-consuming operation and cannot block the response from the mall, so emails need to be sent asynchronously.
We continue to use Celery to implement asynchronous tasks.
7.2.1 Define an asynchronous task for sending emails
Create an email package in the celery_tasks application directory
Define tasks.py: complete the business logic related to sending emails
7.2.2 Register to send mail asynchronous task
Register tasks: only the package name, the system will automatically find the tasks.py task file
7.2.3 Load configuration file
Celery can run independently with django, which is a different process, so configuration parameters cannot be shared, celery needs to load configuration parameters
7.2.4 Related code implementation
/celery_tasks/email/tasks.py
from django.core.mail import send_mail
from django.conf import settings
from celery_tasks.main import celery_app
@celery_app.task(name="send_verify_email")
def send_sms_code(email,verify_url):
'''
发送邮件的异步任务
:param email: 发送邮箱
:param verify_url: 激活链接
'''
subject = "商城邮箱验证"
html_message = '<p>尊敬的用户您好!</p>' \
'<p>感谢您使用商城。</p>' \
'<p>您的邮箱为:%s 。请点击此链接激活您的邮箱:</p>' \
'<p><a href="%s">%s<a></p>' % (email, verify_url, verify_url)
send_mail(subject, '', from_email=settings.EMAIL_FROM, recipient_list=[email], html_message=html_message)
from celery import Celery
import os
# 加载配置文件
if not os.getenv('DJANGO_SETTINGS_MODULE'):
os.environ['DJANGO_SETTINGS_MODULE'] = 'lgshop.dev'
# 创建celery实例
celery_app = Celery('lg')
# 加载celery配置
celery_app.config_from_object('celery_tasks.config')
# 注册任务
celery_app.autodiscover_tasks(['celery_tasks.sms', 'celery_tasks.email'])
7.2.5 Invoke asynchronous mail sending task
/apps/user/views.py must pay attention to: send_verify_email.delay() and send_verify_email() are not sent asynchronously
8. Generate email verification link
Email verification link: http://www.meiduo.site:8000/users/emails/verification/?token=encrypted {'user_id': 1,'email':'[email protected] '}
http://www.meiduo.site:8000/ should be defined as a constant and placed in the configuration file
/users/emails/verification/ is the route
token is a parameter variable
Encryption algorithm: Django's itsdangerous encryption and decryption
It is recommended to write the encryption and decryption method to utils.py
/lgshop/dev.py
/apps/users/utils.py
def generate_verify_email_url(user):
"""
生成邮箱激活链接
:return: 邮箱激活链接
http://www.meiduo.site:8000/users/emails/verification/?token=eyJhbGciOiJIUzUxMiIsImlhdCI6MTYxMjUxMzEwMSwiZXhwIjoxNjEyNTk5NTAxfQ.eyJ1c2VyX2lkIjoxLCJlbWFpbCI6IjI3MDUxODU4MzRAcXEuY29tIn0.bNHVCzCjBw2yO3RKqnI3tICLN97xwvKcqFC-qip2XAEpG2hXuCl2vn3E8Q_WxRF0i_z3scqsWokz8rnOV-pxQw
"""
s = Serializer(settings.SECRET_KEY, constants.VERIFY_EMAIL_TOKEN_EXPIRES)
data = {'user_id': user.id, 'email': user.email}
token = s.dumps(data)
return settings.EMAIL_VERIFY_URL + '?token=' + token.decode()
def check_verify_email_token(token):
"""
反序列token 获取user
:param token: 序列化之后的用户信息
:return: user
"""
#反序化参数
s = Serializer(settings.SECRET_KEY, constants.VERIFY_EMAIL_TOKEN_EXPIRES)
print("s=",type(s),s)
try:
data = s.loads(token)
except:
return None
else:
user_id = data.get('user_id')
email = data.get('email')
try:
user = User.objects.get(id=user_id, email=email)
except User.DoesNotExist:
return None
else:
return user
/apps/users/views.py
9. Verify the background logic of the activation email
9.1 Request method
Options
Program
Request method
GET
Request address
/emails/verification/
9.2 Request parameters: query parameters
parameter name
Types of
Must pass
Description
token
string
Yes
Email activation link
9.3 Response result: HTML
Field
Description
Email verification failed
Respond to error prompts
Email verification succeeded
Redirect to user center
9.4 veiw implementation
Get parameters
request.GET.get(“token”).decode()
Deserialize parameters and query users
user=check_verify_email_token(token)
Modify activation status
If it has been activated, prompt it is activated
If it is not activated, modify the activation status
Return response status
# /apps/users/views.py
class VerifyEmailView(View):
"""验证邮箱"""
def get(self, request):
# 接收参数
token=request.GET.get("token")
if not token:
return http.HttpResponseForbidden('缺少token')
# 解密 token => user {'user_id': 1, 'email': '[email protected] '}
# 查询用户
user=check_verify_email_token(token)
if user.email_active == 0:
# 没有激活 email_active 设置为true
user.email_active = True
user.save()
else:
# email_active 是否已经激活
return http.HttpResponseForbidden('邮箱已经被激活')
# 响应结果
return redirect(reverse('users:info'))
9.5 Register Route
/apps/users/url.py
Two, change the password
After the user clicks to modify the password, it is judged whether the user is logged in, and if logged in, the registration page can be obtained
After the user enters the original password and the new password twice, click OK to submit the backend
After the backend receives the data, it will verify the password: whether it meets the specified requirements, whether the new password is the same twice, whether the old and new password is the same, etc.
The check fails, and an error message is returned
Verify that the original password is correct
Save new password
Clean up the session, jump to the login interface
1. Interface design and definition
1.1 Request method:
Options
Program
Request method
POST
Request address
/users/changepassword/
1.2 Request parameters:
parameter name
Types of
Must pass
Description
old_password
string
Yes
old password
new_password
string
Yes
New Password
new_password2
string
Yes
Confirm the new password
1.3 Response result: html
Response result
Response content
change_password_errmsg
Error message
2. Back-end view implementation
class ChangePassword ( LoginRequiredMixin, View) :
def get ( self, request) :
"""提供修改密码界面"""
return render( request, 'user_center_pass.html' )
def post ( self, request) :
changepassword_form = ChangepasswordForm( request. POST)
if changepassword_form. is_valid( ) :
old_passwd = changepassword_form. cleaned_data. get( 'old_password' )
new_password = changepassword_form. cleaned_data. get( 'new_password' )
if not request. user. check_password( old_passwd) :
return render( request, 'user_center_pass.html' , {
'change_password_errmsg' : '原始密码错误' } )
try :
request. user. set_password( new_password)
request. user. save( )
except Exception as e:
logger. error( e)
return render( request, 'user_center_pass.html' , {
'change_password_errmsg' : '修改密码失败' } )
logout( request)
response = redirect( reverse( 'users:login' ) )
response. delete_cookie( 'username' )
return response
else :
print ( type ( changepassword_form. errors. get_json_data( ) ) )
print ( changepassword_form. errors. get_json_data( ) )
print ( changepassword_form. errors[ "__all__" ] )
context = {
'change_password_errmsg' : changepassword_form. errors[ "__all__" ]
}
return render( request, 'user_center_pass.html' , context= context)
username = forms. CharField( max_length= 20 , min_length= 5 , required= True ,
error_messages= {
"max_length" : "用户名长度最长为20" , "min_length" : "用户名最少长度为5" } )
password = forms. CharField( max_length= 20 , min_length= 8 , required= True ,
error_messages= {
"max_length" : "密码长度最长为20" , "min_length" : "密码最少长度为8" } )
remembered = forms. BooleanField( required= False )
class ChangepasswordForm ( forms. Form) :
old_password = forms. CharField( max_length= 20 , min_length= 8 , required= True ,
error_messages= {
"max_length" : "密码长度最长为20" , "min_length" : "密码最少长度为8" ,
"required" : "密码必须填写" } )
new_password = forms. CharField( max_length= 20 , min_length= 8 , required= True ,
error_messages= {
"max_length" : "确认密码长度最长为20" , "min_length" : "确认密码最少长度为8" ,
"required" : "确认密码必须填写" } )
new_password2 = forms. CharField( max_length= 20 , min_length= 8 , required= True ,
error_messages= {
"max_length" : "确认密码长度最长为20" , "min_length" : "确认密码最少长度为8" ,
"required" : "确认密码必须填写" } )
def clean ( self) :
cleaned_data = super ( ) . clean( )
password = cleaned_data. get( 'new_password' )
password2 = cleaned_data. get( 'new_password2' )
password3 = cleaned_data. get( 'old_password' )
if password != password2:
raise forms. ValidationError( '两次密码不一致' )
if password == password3:
raise forms. ValidationError( '输入的新旧密码不能相同' )
return cleaned_data
3. Route definition
4. Front-end implementation