Learn Ajax requests of Django framework together; Pager; form component verification field; form component source code analysis

Posted by ajanoult on Tue, 08 Mar 2022 22:15:00 +0100


Introduction:

In previous chapters, we used form to submit requests. In this chapter, we will learn a new way to submit requests: Ajax. It is a new way to interact with the back end. Let's learn about it!


Ajax

1, What is Ajax

AJAX (Asynchronous Javascript And XML) is translated into Chinese as "Asynchronous Javascript And XML". That is, use Javascript language to interact asynchronously with the server, and the transmitted data is XML (of course, the transmitted data is not only XML, but now more json data is used).

Synchronous interaction: after the client sends a request, it needs to wait for the server to respond before sending the second request;

Asynchronous interaction: after the client sends a request, it can send a second request without waiting for the end of the server response.

In addition to the asynchronous features of AJAX, there is another one: local refresh of browser pages; (this feature gives users the feeling that they complete the request and response process unconsciously)

For example:

We didn't click the button to submit, because Ajax will automatically submit the data we entered to the back end for matching in the database after we click other places after we input, and then get the matching results and return to the front end.

Advantages of Ajax:

  • AJAX uses Javascript technology to send asynchronous requests to the server
  • AJAX does not need to refresh the entire page

2, Implementation of Ajax based on jQuery

At present, Ajax is generally not written in native JavaScript, because the compatibility of different browsers needs to be considered. We implement it here through jQuery, which is simpler and does not need to consider the compatibility problems caused by different browsers.

Process: we send an Ajax request to a URL at the front end to let the back end process the request and return the data to Ajax to receive.

First configure the route in Django

from django.contrib import admin
from django.urls import path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index),
    path('sum/', views.sums) # ajax will submit a request to this route and execute the susm view function
]

Note: it must be in settings Comment a line of Middleware in the PY configuration file

The function of this line of code will be explained later. At present, comment it out first, otherwise the request will not be submitted successfully!

views.py process request

from django.shortcuts import render,HttpResponse

# Create your views here.

def index(request): # Return to an index page
    return render(request,'index.html')

def sums(request): # Handling ajax requests
    num1 = int(request.POST.get('num1'))
    num2 = int(request.POST.get('num2')) # Get the data carried in the post request and perform type conversion

    return HttpResponse(num1+num2) # Returns the calculated value

index.html file, which defines the interaction between Ajax and the back end

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<input type="text" id="num1" placeholder="Number 1">+
<input type="text" id="num2" placeholder="Number 2">=
<input type="text" id="num3" placeholder="the sum">
<button id="btn">calculation</button>

<script>
    $('#btn').click(function() {/ / an anonymous function is triggered when a button is clicked
        var num1 = $('#num1').val() / / click the button to get the value of id attribute num1
        var num2 = $('#num2').val() / / click the button to get the value with the id attribute value of num2
        $.ajax({ // Define ajax send request
            url: '/sum/', // Address to send the request
            method: 'post', // How the request is sent
            data: {'num1': num1, 'num2': num2}, // Requested data
            success: function (data) { // Wait asynchronously. When the back-end response succeeds, it will call back to execute the anonymous function and pass the data to the data parameter
                $('#num3').val(data) / / set the data to the text box whose id attribute value is Num3
            }
        })
    })
</script>
</body>
</html>

We have written a simple small case, which sends a request to the back end through Ajax, the back end processes the data response to Ajax, and then uses the obtained data on the page, so as to achieve the effect of locally updating the page. In order to achieve this effect, the form needs to update the data globally and use reorientation.

Final effect:


3, Problems needing attention in using Ajax

1. Ajax should not be submitted at the same time as the form

  • If the button button is written in the form form or the input is of submit type, clicking it will trigger the submission of the form.

  • If clicking the button triggers both Ajax submission and form submission, two requests will be sent, resulting in the problem that the page data may be disordered or not displayed.

  • If it is inevitable to use ajax to submit the form control under the form form at the same time, please use the button type for the input button in the form. In this way, if the button is bound to the Ajax request, it will only submit the Ajax request, not the form

    <!-- This type of button cannot be triggered form Submission of forms -->
    <input type="button" value="Submit">
    
    <!-- If button stay form Within, it will be triggered form Submission of forms -->
    <button>Submit</button>
    

2. Back end response format problem

  • If the back-end returns a value through a non JsonResponse, the response format is text/html. After the front-end obtains the data, it needs to be manually converted into the form of an object.

        if request.is_ajax(): # Determine whether the request is Ajax
            import json
            dic = {'name':'jack'}
            dic_json = json.dumps(dic,ensure_ascii=False)
            return HttpResponse(dic_json)
    

    Even if we have converted the dictionary into Json format on the back end, after returning through HttpResponse, it still becomes a pile of strings to the front end, and the response format is text/html.

    <script>
        $('#btn').click(function () {
            $.ajax({
                url: '/test/',
                method: 'post',
                data: {'name': 'tom'},
                success: function (data) {
                    console.log(data)
                    console.log(data.name) // If Jason is returned, it can be printed
                }
            })
        })
    </script>
    

    To view the data format of the back-end response:


    Console effect:


    If you need to manually convert the data to JSON format at the front end, you will first convert the data to JSON and then print it next time

    success: function (data) {
        data = JSON.parse(data)
        console.log(data)
        console.log(data.name)
    }
    
  • If we return through JsonResponse, the response status code will be application/json, and the data received in Ajax will also be in JSON format

    from django.http import JsonResponse
    
    def test(request):
        if request.is_ajax():
            dic = {'name':'jack'}
            return JsonResponse(dic)
        return render(request,'test.html')
    

    There is no need to manually convert it to JSON format after it is received in AJax

    success: function (data) {
        console.log(data)
        console.log(data.name)
    }
    

    View response header encoding format


    Console printing effect:

3. Ajax is used as a post request note

  • The backend should not return render, redirect or HttpResponse. Because these contents will be returned to Ajax as a string

    def test(request):
        if request.is_ajax():
            return render(request,'login.html')
    
        return render(request,'test.html')
    

    Console printing effect:

Summary: after AJax is used as the request, it is recommended to use JsonResponse to return data. Although it is finally returned to the web through HttpResponse, the response format is converted inside JsonResponse


This is different from returning JSON format data directly through HttpResponse. AJax will only process according to the response code


4, Ajax upload file

In the form form, we can upload the file to the back end only by specifying the encoding format. Through Ajax, we will no longer use the form form to achieve this effect

file_upload.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>file_upload</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>

<h2>be based on Ajax Upload file</h2>

<p>
    <input type="file" id="myfils">

</p>

<p>
    <button id="btn">File upload</button>

</p>

<script>
    $("#btn").click(function () {

        var form_data = new FormData() // Instantiate an object. The FormData object is used to save the data of the key/value structure. It is usually used to transfer data from the form

        var file = $('#myfils')[0].files[0] // $('#myfils')[0] get native JS, $(' #myfils')[0] Files get all files uploaded by the user, $('#myfils')[0] Files [0] get the first file uploaded by the user
        form_data.append('myfile',file) // Define a value name for the file. The first parameter is user defined, and the second parameter is value
        $.ajax({
            url: '/upload/',
            method:'post',
            processData: false, // Do not preprocess data
            contentType: false, //No encoding format is specified. The default encoding of the formdata object is formdata format, which supports file transfer
            data: form_data,
            success:function (data) {
                console.log(data.msg)
            }
        })
    })
</script>
</body>
</html>

Back end views Py, it is recommended to create a media folder under the app to store the files uploaded by users

def upload_file(request):
    if request.is_ajax():
        file = request.FILES.get('myfile')

        import os
        DIR_PATH = os.path.dirname(__file__)

        with open(DIR_PATH + '/media/' + file.name, 'wb') as f:
            for i in file:
                f.write(i)

        return JsonResponse({'status':100,'msg':'Upload successful'})

    return render(request, 'file_upload.html')

Final effect:


If we want to upload multiple files, we can make some changes:

file_upload.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>file_upload</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<h2>be based on Ajax Upload file</h2>

<p>
    <input type="file" id="myfils" multiple> <!-- Allow uploading multiple files  -->
</p>

<p>
    <button id="btn">File upload</button>
</p>

<script>
    $("#btn").click(function () {

        var form_data = new FormData() // Instantiate an object. The FormData object is used to save the data of the key/value structure. It is usually used to transfer data from the form

        var file = $('#myfils')[0].files / / get all the files that need to be uploaded and get them in the form of a list

        for(var i = 0;i < file.length;i++) { // Traverse according to the number of uploaded files
            form_data.append(file[i].name, file[i]) // Append the name of each file as a key and the file as a value to the FormData object
        }
        
        $.ajax({
            url: '/upload/',
            method:'post',
            processData: false, // Do not preprocess data
            contentType: false, //No encoding format is specified. The default encoding of the formdata object is formdata format, which supports file transfer
            data: form_data,
            success:function (data) {
                console.log(data)
            }
        })
    })
</script>
</body>
</html>

Python backend views py

def upload_file(request):
    if request.is_ajax():
        import os
        DIR_PATH = os.path.dirname(__file__)
        file = request.FILES

        for k in file:
            fl = file.get(k)
            with open(DIR_PATH + '/media/' + fl.name, 'wb') as f:
                for i in fl:
                    f.write(i)

        return JsonResponse({'status':100,'msg':'Upload successful'})

    return render(request, 'file_upload.html')

Execution result:


5, Ajax upload JSON format

By specifying the encoding format in Ajax, and then converting the data type of JS into JSON format, the data is uploaded to the back end

test.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>

<h1>ajax Submit json format</h1>


<p>user name: <input type="text" id="id_name"></p>
<p>password: <input type="password" id="id_password"></p>
<button id="id_button">Submit</button>
</body>

<script>
    $('#id_button').click(function () {
        var username = $('#id_name').val()
        var password = $('#id_password').val()
        $.ajax({
            url: '/ajax_json/',
            method: 'post',
            contentType: 'application/json',  // Specifies the encoding format of the upload
            data: JSON.stringify({name: username, password: password}), // Pass the object through JSON Convert stringify to JSON format
            success: function (data) {
                console.log(data)
            }
        })
    })
</script>
</html>

Back end views Py: the data in the request cannot be processed with each other

def ajax_json(request):
    if request.is_ajax():
        # json format, which cannot be retrieved from POST
        name = request.POST.get('name')
        print(type(request.POST)) # Print < class' Django http. request. QueryDict'>

        print(name) # Print: None

        # The JSON format data uploaded by Ajax is in request Body, and it is of type bytes
        print(request.body) # b'{"name":"123","password":"123"}'

        request.data = json.loads(request.body) # In Python 3 After 6, loads can convert the data in bytes into JSON format if any

        name = request.data.get('name')
        password = request.data.get('password')
        print(name) # 123
        print(password) # 123
        return JsonResponse({'status':200,'msg':'Submitted successfully'})

    return render(request, 'test.html')

Final effect:


Pager

Here we will use a paging module provided by Django to help our page achieve a paging effect

1. Construction of experimental environment

We first establish a model class to store data, because a certain amount of data is required to realize paging.

models.py; It is recommended to connect to MySQL database for experiment

class UserInfo(models.Model):
    name = models.CharField(max_length=10)
    age = models.CharField(max_length=10)
    address = models.CharField(max_length=30)

Remember to execute the database migration commands first: make migrations and migrate

urls.py

from django.contrib import admin
from django.urls import path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('info/',views.info)
]

tests.py creates the data that we will page later and writes it into the database

import os

if __name__ == '__main__':
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Day06.settings")
    import django

    django.setup()

    from app01 import models
    from faker import Faker  # Generate added data
    import random  # Used to create random ages

    obj_lis = []

    for i in range(100): # Create 100 pieces of virtual data
        fake = Faker(locale='zh-CN') # Select region

        name = fake.name()  # Generate random name
        age = random.randint(15, 45)  # Generate random age
        address = fake.address()  # Generate random address

        obj = models.UserInfo(name=name,age=age,address=address,birthday=birthday) # Generate user objects one by one

        obj_lis.append(obj)

    models.UserInfo.objects.bulk_create(obj_lis) # Write one object into the database and save it. One object corresponds to one record

views.py: first, build a simple model to see the data effect on the web

def info(request):
    
    data = models.UserInfo.objects.all()
    
    return render(request,'info.html',locals())

info.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>info</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-2"></div>
        <div class="col-md-8">
            <table class="table table-striped">
                <thead>
                <tr>
                    <th class="text-center">ID</th>
                    <th class="text-center">full name</th>
                    <th class="text-center">Age</th>
                    <th class="text-center">address</th>
                </tr>
                </thead>

                <tbody>
                {% for obj in data %}
                    <tr class="text-center">
                        <td>{{ obj.id }}</td>
                        <td>{{ obj.name }}</td>
                        <td>{{ obj.age }}</td>
                        <td>{{ obj.address }}</td>
                    </tr>
                {% endfor %}

                </tbody>

            </table>
        </div>
    </div>
</div>
</body>
</html>

Overall effect of final page:

2. Paging implementation

2.1 introduction to pager

We need to page the data first, and then we will return the corresponding page according to which page of data the user needs.

There is a module in Django that can help us realize paging. Let's learn how to use it:

from django.core.paginator import Paginator # Module for paging

def info(request):
    user_list = models.Books.objects.all() # Get all data objects
    paginator = Paginator(user_list,10) # Split data into 10 items per page
    
    # Properties of Paginator object
    print(paginator.count) # Total number of data
    print(paginator.num_pages) # PageCount 
    print(paginator.per_page) # Number of items displayed per page
    print(paginator.page_range) # Display the current number of pages in the form of iteration: range(1, 101)

    # Properties and methods of Page object
    page = paginator.page(2) # Get all the data of the second page
    print(page.has_next())
    print(page.next_page_number())
    print(page.has_previous())
    print(page.previous_page_number())
    print(page.object_list)
    print(page.number)
    
	# has_ Does next have a next page
    # next_page_number next page
    # has_previous whether there is a previous page
    # previous_page_number previous page
    # object_list the data list of the current page
    # Number current page number

2.2 paging effect

According to the usage of the upper order pager, we can edit the view function

from django.core.paginator import Paginator

def info(request):

    data_list = models.UserInfo.objects.all()

    paginator = Paginator(data_list,10) # Display 10 pieces of data per page

    nums = paginator.num_pages # Get total pages

    page = request.GET.get('page',1) # Get the page number entered on the page. If the user does not enter it, the number 1 is obtained by default

    try:
        data = paginator.page(page) # Get the data of the specified page according to the page number
    except: # Avoid the browser entering the wrong URL and missing the page
        
        page = 1
        data = paginator.page(page) # Get the data of the first page


    return render(request,'info.html',locals())

info.html; Follow the page prompts to understand the meaning of the code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>info</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-2"></div>
        <div class="col-md-8">
            <table class="table table-striped">
                <thead>
                <tr>
                    <th class="text-center">ID</th>
                    <th class="text-center">full name</th>
                    <th class="text-center">Age</th>
                    <th class="text-center">address</th>
                </tr>
                </thead>

                <tbody>
                {% for obj in data %} <!-- Get the data of the current page -->
                    <tr class="text-center">
                        <td>{{ obj.id }}</td>
                        <td>{{ obj.name }}</td>
                        <td>{{ obj.age }}</td>
                        <td>{{ obj.address }}</td>
                    </tr>
                {% endfor %}

                </tbody>

            </table>
        </div>
    </div>
</div>

<nav aria-label="Page navigation" class="text-center">
    <ul class="pagination">
        {% if data.has_previous %} <!-- Determine whether there is a previous page -->
            <li>
                <a href="?page={{ data.previous_page_number }}" aria-label="Previous"> <!-- Get previous page number -->
                    <span aria-hidden="true">&laquo;</span>
                </a>
            </li>
        {% endif %}

        <li class="active"><a href="#"> {{data. Number}} < / a > < / Li > <! -- current page -- >

        {% if data.has_next %} <!-- Determine whether there is a next page -->
            <li>
                <a href="?page={{ data.next_page_number }}" aria-label="Next">
                    <span aria-hidden="true">&raquo;</span>
                </a>
            </li>
        {% endif %}

    </ul>
</nav>
</body>
</html>

Then the page achieves the following effect:


When we click the next page, we will first obtain the page number of the next page, and then send it to the back end. The back end will take out the following number according to the page keyword and get the data of the corresponding page.

But at this time, we can click only three buttons. If we want to click to n pages, we need to make a jump to the number of buttons displayed, but it must be done by the back end.

views.py

def info(request):

    data_list = models.UserInfo.objects.all()

    paginator = Paginator(data_list,10) # Display 10 pieces of data per page

    nums = paginator.num_pages # Get total pages

    page = request.GET.get('page',1) # Get the page number entered by the user, if there is no number 1, get the default page number

    try:
        data = paginator.page(page) # Get the data of the specified page according to the page number
    except: # Avoid the browser entering the wrong URL and missing the page
        page = 1
        data = paginator.page(page) # Get the data of the first page

    page = int(page) # Convert the data type


    if paginator.num_pages > 7: # Judge whether the total number of pages is greater than the specified number
        if page - 5 < 1:
            page_range = range(1, 7) # Return to the front end to print the number of paging buttons (according to different judgment, the number of paging buttons will also be different)
        elif page + 5 > paginator.num_pages:
            page_range = range(paginator.num_pages - 6, paginator.num_pages + 1)
        else:
            page_range = range(page - 3, page + 5)
    else:
        page_range = paginator.page_range

    return render(request,'info.html',locals())

info.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>info</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-2"></div>
        <div class="col-md-8">
            <table class="table table-striped">
                <thead>
                <tr>
                    <th class="text-center">ID</th>
                    <th class="text-center">full name</th>
                    <th class="text-center">Age</th>
                    <th class="text-center">address</th>
                </tr>
                </thead>

                <tbody>
                {% for obj in data %}
                    <tr class="text-center">
                        <td>{{ obj.id }}</td>
                        <td>{{ obj.name }}</td>
                        <td>{{ obj.age }}</td>
                        <td>{{ obj.address }}</td>
                    </tr>
                {% endfor %}

                </tbody>

            </table>
        </div>
    </div>
</div>

<nav aria-label="Page navigation" class="text-center">
    <ul class="pagination">
        {% if data.has_previous %} <!-- Determine whether there is a previous page -->
            <li>
                <a href="?page={{ data.previous_page_number }}" aria-label="Previous"> <!-- Get previous page number -->
                    <span aria-hidden="true">&laquo;</span>
                </a>
            </li>
        {% endif %}

        {% for foo in page_range %} <!-- The number of buttons that can be displayed on the current page, in the form of numbers -->
            {% if foo == data.number %} <!-- If you match the same number as the current page number while traversing the Create button, it is the current page and change its color -->
                <li class="active"><a href="#"> {{foo} < / a > < / Li > <! -- current page -- >
            {% else %}
                <li><a href="?page={{ foo }}">{{ foo }}</a></li> <!-- Buttons to other pages -->
            {% endif %}

        {% endfor %}

        {% if data.has_next %} <!-- Determine whether there is a next page -->
            <li>
                <a href="?page={{ data.next_page_number }}" aria-label="Next">
                    <span aria-hidden="true">&raquo;</span>
                </a>
            </li>
        {% endif %}

    </ul>
</nav>
</body>
</html>

At this time, the effect of the page is that the number of buttons available for users to choose to reach the page becomes more considerable, as shown in the figure:


At this point, our page can achieve the effect of paging. The number of page buttons depends on the judgment of the back end. Usually, an iterative type is returned to the front end, such as range(1,5). Specify the number of loop creation buttons


form component

1. Introducing form components

Django Form component is used to initialize the page and generate HTML tags. In addition, it can verify the data submitted by the user (display error information).

Generally, the data submitted by the user will be checked at the back end to see whether it is qualified, such as account length, password length, whether it is empty, etc. If these are written in the front-end JS, they can be changed or deleted manually, and then the unqualified data will be submitted to the back-end, causing great potential data security problems.

Generally, a py file will be created to store our forms components, which will be called when verifying user data.

Next, we create an application named: my_form.py

from django import forms
from django.core.exceptions import ValidationError


class UserForm(forms.Form):  # Component for verifying user information
    name = forms.CharField(max_length=8, min_length=4, label='user name', error_messages={
        'max_length': 'The user name cannot exceed 8 characters',
        'min_length': 'The user name cannot be less than 4 digits',
        'required': 'User name cannot be empty'})

    password = forms.CharField(max_length=8, min_length=4, label='password', error_messages={
        'max_length': 'The password cannot exceed 8 digits',
        'min_length': 'The password cannot be less than 4 digits',
        'required': 'Password cannot be empty'})

    re_password = forms.CharField(max_length=8, min_length=4, label='Confirm password', error_messages={
        'max_length': 'The password cannot exceed 8 digits',
        'min_length': 'The password cannot be less than 4 digits',
        'required': 'Confirm password cannot be empty'})

    email = forms.EmailField(label='mailbox', error_messages={'required': 'Mailbox cannot be empty'})

forms is defined in a way similar to the model. Let's introduce the meaning of its field parameters:

  • label: text information in front of the input box
  • max_length: Specifies the maximum length of the contents of the input box
  • min_length: Specifies the minimum length of the contents of the input box
  • error_message: customize the error message displayed. The attribute value is a dictionary, where required is the key of the error message displayed when the setting cannot be empty.

views.py

def myform(request):
    form = UserForm(request.POST)

    return render(request,'myform.html',locals())

myform.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>myform</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<h2>adopt form Component automatic rendering</h2>
<form action="" method="post">
    <p>user name:{{ form.name }}</p>
    <p>password:{{ form.password }}</p>
    <p>Confirm password:{{ form.re_password }}</p>
    <p>Email:{{ form.email }}</p>
    <input type="submit" value="Submit">
</form>
</body>
</html>

Page effect: the text box generated by the form component, and carries some restriction attributes and id attribute values


2. How the form component renders the page

The above is only the first rendering method. Let's introduce two form s component rendering methods

Method 2: recommendation, recommendation, recommendation

<!-- ellipsis html At the beginning, readers must remember to add that the following code should be included in body in -->
<h2>adopt form Component automatic rendering</h2>
<form action="" method="post">
    {% for item in form %}
        <p>{{ item.label }}: {{ item }}</p>
    {% endfor %}
    <input type="submit" value="Submit">
</form>

Page effect:

Mode 3:

<!-- ellipsis html At the beginning, readers must remember to add that the following code should be included in body in -->
<h2>adopt form Component automatic rendering</h2>
<form action="" method="post">
    {{ form.as_p }}
    <input type="submit" value="Submit">
</form>

Page effect: this method is very poor for page expansion. We can't add other tags, and the error information is directly displayed.

Summary: Based on the above three methods, the second method is most commonly used to automatically render templates through form components


3. form component rendering error message

view

def myform(request):
    if request.POST:
        form = UserForm(request.POST)
        if form.is_valid(): # Judge whether the content input by the front end is qualified
            print(form.cleaned_data) # All clean fields and corresponding values, that is, what the user enters conforms to the rule defined by our component: qualified data
        else:
            print(form.cleaned_data)
            print(form.errors) # # ErrorDict: {field of verification error: ["error message",]}

        return render(request,'myform.html',locals())

    form = UserForm()

    return render(request,'myform.html',locals())

myform.html

<!-- Please put the following code body Inside label -->
<h2>adopt form Component automatic rendering</h2>
<form action="" method="post">
    {% for item in form %}
        <p>{{ item.label }}: {{ item }} <span style="color: red">{{ item.errors.0 }}</span><!-- Display error message --></p>
    {% endfor %}
    <input type="submit" value="Submit">
</form>

Step 1: This is a demonstration of submitting irregular data to the backend

Part II: the back end will return error prompt information to the page that does not meet the text box


5. Parameter configuration of form component

form. Charfeed creates a text type input box by default. Based on it, we can directly convert its type and add some additional attributes.

from django import forms
from django.forms import widgets # Import module

class UserForm(forms.Form):  # Component for verifying user information
    name = forms.CharField(max_length=8, min_length=4, label='user name', error_messages={
        'max_length': 'The user name cannot exceed 8 characters',
        'min_length': 'The user name cannot be less than 4 digits',
        'required': 'User name cannot be empty'},
        widget=widgets.TextInput(attrs={'class':'form-control'})) # Display the input box of text type and add the class='from-control 'attribute
        

    password = forms.CharField(max_length=8, min_length=4, label='password', error_messages={
        'max_length': 'The password cannot exceed 8 digits',
        'min_length': 'The password cannot be less than 4 digits',
        'required': 'Password cannot be empty'},
        widget=widgets.PasswordInput(attrs={'class':'form-control'})) # Display the input box of password type and add the class='from-control 'attribute                            

6. Local hook

If the above operations can not meet our complete verification of a field, then using local hooks can perform additional verification on a field, and finally make the value of this field meet our expectations.

In my_ form. Increase in PY

# Local hook for name field
def clean_name(self):
    name = self.cleaned_data.get('name') # Get name field data

    if name.startswith('sb'): # name field data cannot start with sb
        raise ValidationError('Can't take sb start') # If the verification fails, an exception is thrown

    else: # If the verification is passed, the value corresponding to name will be returned.
        return name

The local hook is implemented through the function clean_ Field name to help us verify the data of a field again.

Page effect:

You can only get the value of the corresponding field in the current local hook


7. Global hook

In the global hook: we can get the values of any field, verify these values as a whole, and then return.

my_form.py.py

# Global hooks for all fields
def clean(self):
    password = self.cleaned_data.get('password')
    re_password = self.cleaned_data.get('re_password')

    if password == re_password: # Verify whether the passwords entered by the user twice are consistent
        return self.cleaned_data # Because it is a global hook, you need to return all data if it is consistent, otherwise some fields will not receive values.
    else:
        raise ValidationError('The two passwords are inconsistent')

Based on the following integration, we will demonstrate the effects of global hooks and local hooks


8. Integrated use

We will use the fomr component and set local and global hooks internally to verify the data for many times, and finally store the verified data into our database. The following are all the codes of the front and rear end

my_form.py component

from django import forms
from django.core.exceptions import ValidationError
from django.forms import widgets


class UserForm(forms.Form):  # Component for verifying user information
    name = forms.CharField(max_length=8, min_length=4, label='user name', error_messages={
        'max_length': 'The user name cannot exceed 8 characters',
        'min_length': 'The user name cannot be less than 4 digits',
        'required': 'User name cannot be empty'},
                           widget=widgets.TextInput(attrs={'class': 'from-control'})
                           )

    password = forms.CharField(max_length=8, min_length=4, label='password', error_messages={
        'max_length': 'The password cannot exceed 8 digits',
        'min_length': 'The password cannot be less than 4 digits',
        'required': 'Password cannot be empty'},
                               widget=widgets.PasswordInput(attrs={'class': 'from-control'})
                               )

    re_password = forms.CharField(max_length=8, min_length=4, label='Confirm password', error_messages={
        'max_length': 'The password cannot exceed 8 digits',
        'min_length': 'The password cannot be less than 4 digits',
        'required': 'Confirm password cannot be empty'},
                                  widget=widgets.PasswordInput(attrs={'class': 'from-control'})

                                  )

    email = forms.EmailField(label='mailbox', error_messages={'required': 'Mailbox cannot be empty'})

    # Local hook for name field
    def clean_name(self):
        name = self.cleaned_data.get('name')  # Get name field data
        if name.startswith('sb'):  # name field data cannot start with sb
            raise ValidationError('Can't take sb start')  # If the verification fails, an exception is thrown

        else:  # If the verification is passed, the value corresponding to name will be returned.
            return name

    # Global hooks for all fields
    def clean(self):
        password = self.cleaned_data.get('password')
        re_password = self.cleaned_data.get('re_password')

        if password == re_password: # Verify whether the passwords entered by the user twice are consistent
            return self.cleaned_data # Because it is a global hook, you need to return all data if it is consistent, otherwise some fields will not receive values.
        else:
            raise ValidationError('The two passwords are inconsistent')

view

from django.shortcuts import render, HttpResponse
from app01.my_form import UserForm
from app01 import models


def myform(request):
    if request.POST:
        form = UserForm(request.POST)
        if form.is_valid(): # Whether the front-end input is qualified
            form.cleaned_data.pop('re_password')
            models.userinfo.objects.create(**form.cleaned_data) # cleaned_data itself is a dictionary. We break it up into the form of keyword = data by * * to add values to the database.

            return HttpResponse('Account registration succeeded')

       	try:
           	error = form.errors.get('__all__')[0] # Get the error information of the global hook
        except Exception as e:
       		error = '' # Avoid error reporting due to no error information returned by the global hook

        return render(request,'myform.html',locals())

    form = UserForm()

    return render(request,'myform.html',locals())

Front end: myform html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>myform</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<h2>Registration page</h2>
<form action="" method="post">
    {% for item in form %}
        <p>{{ item.label }}: {{ item }} <span style="color: red">{{ item.errors.0 }}</span></p>
    {% endfor %}
    <input type="submit" value="Submit"> {{ error }}
</form>
</body>
</html>

Page effect:


As for the layout and typesetting of the page, CSS or JS can be used to realize it.

So the above is how to use the form component. Let's understand the implementation principle of the form component


form component source code analysis

When we use form components to verify the field data, we must call the class of the form component to instantiate an object, and then call the is_ of the object. Only valid () method can be used for verification. This method will only return two values: True and False

Therefore, if we want to analyze the form source code, the entry is is_valid() method

Click in is_ The following code is found after valid (), which tells us that if the form component of this method does not return an error, it will return True, otherwise it will return False

def is_valid(self):
    """Return True if the form has no errors, or False otherwise."""
    return self.is_bound and not self.errors

Let's click self Errors, I found a method encapsulated into class attributes, self_ The default value of errors is None, so self. Is executed full_ Clean() method

@property
def errors(self):
    """Return an ErrorDict for the data provided for the form."""
    if self._errors is None:
        self.full_clean()
    return self._errors

For this method, we only need to focus on the following two methods, which are the focus of our form component

def full_clean(self):
    self._clean_fields() # Local hook implementation
    self._clean_form() # Global hook implementation

1. Local hook source code

self._clean_fields() is the implementation of local hooks

def _clean_fields(self):
	'''
		this self Is that we are based on form Object instantiated by component class
		self.fields.items()Get us form The type attribute and value under the component class are valued in the same way as the dictionary.
		name Is the attribute name field namely: CharField Other content objects
	'''
    for name, field in self.fields.items():
        if field.disabled:
			# Get the form data, and the value is equal to the value submitted in our input box
            value = self.get_initial_for_field(field, name)
        else:
            value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
        try:
        	# Judge whether the object is of FileField type. Obviously, we haven't defined this type at present, so skip
            if isinstance(field, FileField):
                initial = self.get_initial_for_field(field, name)
                value = field.clean(value, initial)
            else:
                value = field.clean(value) # Call some verification functions of the object itself (defined by ourselves)
            # Through the verification of the object itself, the data is saved in cleared_ In the data dictionary, name is the attribute name and value is the value submitted in our input box.
            self.cleaned_data[name] = value 

			# Judge whether there are local hooks in the form component class we write through reflection
            if hasattr(self, 'clean_%s' % name):
	
				# If you call this local hook, you will find the value returned by this local hook (now you know why you want to return the value of the local hook verification field)
                value = getattr(self, 'clean_%s' % name)() 
                self.cleaned_data[name] = value # Save this value in the dictionary again. Here, the data of the name field is equivalent to the secondary verification
        
        # The code within try threw an exception of ValidationError type (the exception we defined manually)
        except ValidationError as e:
            self.add_error(name, e) # The exception information is added to a dictionary according to the field name, but the values of this dictionary are a list, and the error information is saved in the list. It means that sometimes there may be more than one error

2. Global hook source code

self._clean_form() is the implementation method of the global hook.

The implementation of global hook is simpler than local hook, because it is triggered after its own object verification and local hook verification.

def _clean_form(self):
    try:
    	# Call the clean method in our form component class (this is why we return self.cleaned_data, because all the verified data is saved in it)
        cleaned_data = self.clean()
    except ValidationError as e:
        self.add_error(None, e)
    else:
        if cleaned_data is not None: # If we don't get a None, it means that we have returned the value
            self.cleaned_data = cleaned_data
            # self. cleaned_ data = self. The value returned by the clean () method (that is, the clean method must return self.cleaned_data)

If the clean method is not written in our form component class, the clean method in the form source code will be called

def clean(self):
    """
    Hook for doing any extra form-wide cleaning after Field.clean() has been
    called on every field. Any ValidationError raised by this method will
    not be associated with a particular field; it will have a special-case
    association with the field named '__all__'.
    """
    return self.cleaned_data

The clean method in the source code does nothing but returns data. Therefore, when we need to use the global hook, we can rewrite this method. Because the global hook is executed after the verification of the field itself and the local hook, it can get all the field data.


If this article is helpful to you, don't forget to click three times. Your support is the author's greatest encouragement. Thank you for reading!

Updating!!!


Technical Xiaobai records the learning process. Please point out the mistakes or puzzles. If this article is helpful to you, please like Collection + pay attention to midnight. We look forward to your attention. Thank you for your support!

Topics: Python Django source code