Django project practice (mall): Ten, personal information

Insert picture description here

(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
    Insert picture description here
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

# ./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:Insert picture description here
  • user_center_info.html
    Insert picture description here
  • user_center_info.js
    Insert picture description here

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
      Insert picture description here

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
        Insert picture description here
    • The put method returns JsonResponse Insert picture description here
  • 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
    Insert picture description here

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
    Insert picture description here

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
    Insert picture description here

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)
  • /celery_tasks/main.py
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
    Insert picture description here
    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
    Insert picture description here
  • /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
    Insert picture description here

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
    Insert picture description here

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
      Insert picture description here
  • 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

# /apps/users/veiw.py
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)
            # return http.HttpResponseForbidden(changepassword_form.errors)
    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

Insert picture description here

4. Front-end implementation

Insert picture description here

Guess you like

Origin blog.csdn.net/laoluobo76/article/details/113849249