Knowledge summary of open source web framework django

Posted by Kilgore Trout on Tue, 14 Dec 2021 02:42:17 +0100

Knowledge summary of open source web framework django (12)

User center interface (I)

Define model class base class

In order to supplement the two fields of creation time and update time for the model class data in the project, we need to define the model class base class. New aerf_mall.utils/BaseModel.py file to create a model class base class.

Django model

auto_ now_ When add = true, it is the time when adding, and there will be no change when updating the object.

auto_now=True whether you add or modify an object, the time is the time you add or modify it.

from django.db import models
 
class BaseModel(models.Model):
    """Supplementary fields for model classes"""
 
    create_time = models.DateTimeField(auto_now_add=True, verbose_name="Creation time")
    update_time = models.DateTimeField(auto_now=True, verbose_name="Update time")
 
    class Meta:
        abstract = True  # Description is an abstract model class, which is used for inheritance and use. BaseModel tables will not be created during database migration

1. Judge whether the user logs in

Requirements:

  • When a user logs in, he can access the user center.
  • If the user is not logged in, it is not allowed to access the user center and guide the user to the login interface.

Implementation scheme:

  • You need to determine whether the user logs in.
  • Determine whether the user can access the user center according to the result of login.

2. is_authenticated determines whether the user logs in

Introduction:

  • Django user authentication system provides the method request user. is_ Authenticated () to determine whether the user logs in.
  • Returns True if login authentication is passed. Otherwise, False is returned.
  • Disadvantages: login verification logic is required in many places, so the code needs to be repeatedly encoded many times.

3. login_ The required decorator determines whether the user logs in

3.1. Define the View subclass to encapsulate login_required decorator

  • Tip: LoginRequired(object) depends on the View class View, and its reusability is very poor.

3.2. Define the extension to verify whether the user logs in

Under the utils package of the project root, create new views py

Notice why it's not in user utils. Py?

from django.http import JsonResponse

# Define a decorator to verify whether it has logged in
def login_required(func):
    # func: is a view function
    def wrapper(request, *args, **kwargs):
        # Add function code
        if request.user.is_authenticated:
            return func(request, *args, **kwargs)
        else:
            return JsonResponse({'code': 400, 'errmsg': 'You are not logged in!'}, status=401)

    return wrapper

4. User center page interface

from aerf_mall.utils.views import login_required  # Pay attention to modifying the guide package path
from django.utils.decorators import method_decorator #The main function is to solve the problem that the decorator cannot directly use the decorating class view function

class UserInfoView(View):

    @method_decorator(login_required)
    def get(self, request):

        # 1. Get user object
        user = request.user

        # 2. Construction response data return
        return JsonResponse({
            'code': 0,
            'errmsg': 'ok',
            'info_data': {
                'username': user.username,
                'mobile': user.mobile,
                # 'email': user.email,    # The email model is not written. Please comment first for the time being
                # 'email_active': user.email_active   # The email model is not written. Please comment first for the time being
            }
        })

urls.py

# User centric sub routing
 re_path(r'^info/$', UserInfoView.as_view()),

User center

1. Logical analysis of basic user information

The following is the back-end logic to implement

  1. User model supplement email_active field
  2. Query and display basic user information
  3. Add Email
  4. Send mailbox verification mail
  5. Verify mailbox
  6. Installation package: pip install itsdangerous

2. User model supplement email_active field 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):
    """Custom user model class"""
    mobile = models.CharField(
        unique=True,
        verbose_name='cell-phone number',
        null=True,
        max_length=11
    )

    # New email_active field
    # Used to record whether the mailbox is activated. The default is False: not activated
    email_active = models.BooleanField(default=False,verbose_name='Mailbox verification status')

    class Meta:
        db_table = 'tb_users'
        verbose_name = 'user'
        verbose_name_plural = verbose_name

    def __str__(self):
		return self.username

    # Encapsulate the method in the user model class
    def generate_verify_email_url(self):
        """
        Generate the token of the current user; And splicing the connection confirmed by the mailbox;
        :return: Return to confirm connection
        """
        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

    # Verify the token value and return the user object @ staticmethod. Do not access instance properties or call instance methods
    @staticmethod     
    def check_verify_email_token(token):
        """
        check token value
        :param token: token value
        :return: User object or 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
        
     

Usage of itsdangerous:

Sometimes you want to send some data to an untrusted environment, but how to accomplish this task safely? The solution is to sign. Use a key that only you know to encrypt and sign your data, and send the encrypted data to others. When you retrieve the data, you can ensure that no one usurps and modifies the data.

Admittedly, recipients can decipher the content to see what's in your package, but they can't modify your content unless they also have your key. So as long as you keep your key and the key is complex enough, everything is OK.

itsdangerous uses HMAC and SHA1 to sign by default, based on the Django signature module. It also supports JSON Web signature (JWS). This library adopts BSD protocol and is written by Armin Ronacher. Most of the design and implementation copyright belongs to Simon Willison and other Django enthusiasts who turn this library into reality.

>>>from itsdangerous import TimedJSONWebSignatureSerializer as ts
>>>serializer = ts('abc',3600)   #Specify the key and expiration time in seconds
>>>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

After the fields are supplemented, they need to be migrated.

python manage.py makemigrations
python manage.py migrate

3. In users views. Py to query basic user information, add:

class UserInfoView(View):

    @method_decorator(login_required)
    def get(self, request):

        # 1. Get user object
        user = request.user

        # 2. Construction response data return
        return JsonResponse({
            'code': 0,
            'errmsg': 'ok',
            'info_data': {
                'username': user.username,
                'mobile': user.mobile,
                'email': user.email,    # Write after adding email model
                'email_active': user.email_active   # Write after adding email model
            }
        })

Add and verify mailboxes (note restrictions)

1. Prepare email server https://mail.163.com/

1.1. Click to enter the setting interface

3. Open the authorization code and complete the verification SMS

5. Complete the authorization code setting

6. Configure the mail server and add it in dev.py

# Settings related to sending SMS. These settings are used by default when the user does not send related fields:
# Settings for sending SMS:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# The smtp server address we use
EMAIL_HOST = 'smtp.163.com'
# Port number
EMAIL_PORT = 25  # Or 465 / 587 is set with SSL encryption. 163 mailbox 465 / 587 is limited. QQ mailbox can be used.
# The following contents are variable and change with different background settings:
# Mailbox to send mail
EMAIL_HOST_USER = 'Your email 163'
# Client authorization password set in mailbox
EMAIL_HOST_PASSWORD = 'Authorization code'  # If you reset the new authorization code, you can directly use the latest authorization code
EMAIL_USE_TLS = True  # This must be True, otherwise the sending will not succeed
# Sender seen by recipient
EMAIL_FROM = 'Alpha mall<Your email 163>'
# Mailbox validation link
EMAIL_VERIFY_URL = 'http://127.0.0.1/success_verify_email.html?token='
# Settings for sending SMS:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# The smtp server address we use
EMAIL_HOST = 'smtp.163.com'
# Port number
EMAIL_PORT = 25  # Or 465 / 587 has SSL encryption set
# The following contents are variable and change with different background settings:
# Mailbox to send mail
EMAIL_HOST_USER = 'suifeng_1228@163.com'
# Client authorization password set in mailbox
EMAIL_HOST_PASSWORD = 'ELFMFARWZRGDIVUQ'  # If you reset the new authorization code, you can directly use the latest authorization code
EMAIL_USE_TLS = True  # This must be True, otherwise the sending will not succeed
# Sender seen by recipient
EMAIL_FROM = 'Alpha mall<suifeng_1228@163.com>'
# Mailbox validation link
EMAIL_VERIFY_URL = 'http://127.0.0.1/success_verify_email.html?token='

Send mailbox verification mail

Important:

  • Sending mailbox verification mail is a time-consuming operation and cannot block the response of alpha mall, so it is necessary to send mail asynchronously.
  • We continue to use Celery to implement asynchronous tasks.

1. Define and call the asynchronous task of sending mail

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 = 'Alpha mall email verification'

    html_message = '<p>Dear user!</p>' \
                   '<p>Thank you for using alpha mall.</p>' \
                   '<p>Your email address is:%s . Please click this link to activate your email:</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
    )

Supplement:

# bind: ensure that the task object will be automatically passed in as the first parameter. bind=True, the function plus self represents the task object
# name: asynchronous task alias
# retry_ Backoff: the nth time interval of abnormal automatic retry (retry_backoff*2^(n-1))s
# max_retries: the maximum number of abnormal automatic retries
@app.task(bind=True, name='send_verify_email', retry_backoff=3)
def send_verify_email(self, to_email, verify_url):
    """
    Send verification email
    :param to_email: Recipient mailbox
    :param verify_url: Verify link
    :return: None
    """
    subject = "Alpha mall email verification"
    html_message = '<p>Dear user!</p>' \
                   '<p>Thank you for using alpha mall.</p>' \
                   '<p>Your email address is:%s . Please click this link to activate your email:</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:
        # Automatically retry three times if there are exceptions
        raise self.retry(exc=e, max_retries=3)

2. Register the task of sending e-mail: main py

  • In the asynchronous task of sending mail, we use Django's configuration file.
  • So we need to modify the startup file main py.
  • Indicate the Django configuration file that celery can read.
  • Finally, remember the task of registering the newly added email
"""
This file is used as a module for asynchronous application initialization
"""
# Environment for loading django in asynchronous task program
import os
os.environ.setdefault(
    'DJANGO_SETTINGS_MODULE',
    'aerf_mall.settings.dev'
)


from celery import Celery

# Initialize an application object
app = Celery("aerf")

# Load configuration file - the parameter is the package guide path of the configuration file (module)
# We will be in celery in the future_ The directory where the tasks package is located is the working directory to run asynchronous programs;
app.config_from_object('celery_tasks.config')

# Tell the app what tasks to listen for
# The parameter of this function is a list, in which the guided package path of the task package is written
app.autodiscover_tasks([
    'celery_tasks.sms',  # Send SMS task
    'celery_tasks.email',  # Send mail task

])

Add mailbox backend logic

1. Add mailbox interface design and definition

1. Request method

optionprogramme
Request methodPUT
Request address/emails/

2. Request parameters

Parameter nametypeIs it necessary to passexplain
emailstringyesmailbox

3. Response result: JSON

fieldexplain
codeStatus code
errmsgerror message

2. Update mailbox

from celery_tasks.email.tasks import send_verify_email
# Update mailbox
class EmailView(View):

    @method_decorator(login_required)
    def put(self, request):
        # 1. Extract parameters
        data = json.loads(request.body.decode())
        email = data.get('email')
        # 2. Calibration parameters
        if not email:
            return JsonResponse({'code': 400, 'errmsg': 'lack 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': 'Incorrect email format!'})

        # 3. Data processing (partial update) - update mailbox
        user = request.user
        try:
            user.email = email
            user.email_active = False
            user.save()
        except Exception as e:
            print(e)


        # ======Send mailbox verification mail=======
        verify_url = user.generate_verify_email_url()
        send_verify_email.delay(email, verify_url) # Asynchronous call!

        # 4. Build response
        return JsonResponse({'code': 0, 'errmsg': 'ok'})

3. Verify the mailbox backend logic implementation

The core of email verification is to send the user's email_ The active field is set to True

# Confirm mailbox interface
class VerifyEmailView(View):

    def put(self, request):
        # 1. Extract token from query string
        token = request.GET.get('token')
        # 2. Verification token
        user = User.check_verify_email_token(token)
        if not user:
            return JsonResponse({'code': 400, 'errmsg': 'Invalid verification message!'})

        # 3. If the token is valid, set the activation status of the mailbox to True
        user.email_active = True
        user.save()

        return JsonResponse({'code': 0, 'errmsg': 'Mailbox activation succeeded!'})

4. Add url

users.urls.py

# Update mailbox
re_path(r'^emails/$', EmailView.as_view()),
# Verify and activate the mailbox interface
re_path(r'^emails/verification/$', VerifyEmailView.as_view()),

5. Start Celery

celery -A celery_tasks.main worker -l info

I wish you all success in learning python!

Topics: Python Django Ubuntu Back-end