• 从零开始的Django框架入门到实战教程(内含实战实例) - 09 初试Ajax之任务界面(学习笔记)



      Django是目前比较火爆的框架,之前有在知乎刷到,很多毕业生进入大厂实习后因为不会git和Django框架3天就被踢掉了,因为他们很难把自己的工作融入到整个组的工作中。因此,我尝试自学Django并整理出如下笔记。
      原理部分包括Ajax请求简介,实战部分围绕完成任务管理界面进行,包括Ajax请求前后端传输数据,数据库建立(关联外键 级联删除),ModelForm完成输入框样式,输入数据校验,错误信息返回,数据展示页面分页展示。

    0. Why Ajax?

      之前我们的浏览器向网页发送请求都是以URL和表单的形式提交的(POST, GET),基本上都能实现我们想要的功能。那为什么要用Ajax请求?之前的方法不是挺好的嘛。

      之前的方法在每次提交请求的时候都会刷新页面,这是一个弊端。举个例子,在做验证码的时候,如果我们还是用原来的方法发送请求,就会导致每次换验证码的时候,都要重新输入账号和密码。

    在这里插入图片描述

    1. 简单应用

      一般写Ajax请求都会配上jQuery,这样方便一点。先放上两段比较常用的写法:

    2.1. GET请求

      前端代码:

    $.ajax({
    	url: '地址',
    	type: 'get',
    	data: {
    		x: 123456,
    		y: '123456',
    	},
        dataType: 'JSON',
    	success: function(res){
    		console.log(res);
    	}
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

      后端代码:

    def aaa(request):
        print(request.GET)
    
    • 1
    • 2

    2.2. POST请求

      我们知道平时POST请求是需要csrf验证的,我们这里暂时绕过这个,直接采用装饰器里的csrf_exempt免除这个校验。

      前端代码:

    $.ajax({
    	url: '地址',
    	type: 'post',
    	data: {
    		x: 123456,
    		y: '123456',
    	},
        dataType: 'JSON',
    	success: function(res){
    		console.log(res);
    	}
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

      后端代码:

    from django.views.decorators.csrf import csrf_exempt
    @csrf_exempt
    def aaa(request):
        print(request.POST)
    
    • 1
    • 2
    • 3
    • 4

    2. 实战

      我们已经有了管理员账户、部门管理、用户管理、靓号管理4块管理界面。在此之前,这四个界面都是通过刷新页面传递参数的,也就是说都未用到Ajax请求。

    在这里插入图片描述

      在这里我再创建一个任务管理,使用Ajax完成数据的传递。

      我的思路是利用task_list函数产生一个网页页面,网页页面中动态生成部分靠传入的form和模板语言完成利用按钮响应事件将数据从前端读取过来传到task/add网页中,这个网页对应的函数是task_add,在此函数中,我们完成数据校验(决定校验成功和失败时的返回值)和数据的保存。

      注意点有:

    1. 数据表关联外表,级联删除on_delete=models.CASCADE
    2. 任务详情采用输入框样式,空间更大更方便widgets = {'detail': forms.Textarea}
    3. Ajax传输数据,不需要每次刷新页面location.reload();
    4. 自制分页类PAGE,比Django自带的分页更方便
    5. 每次接收Ajax请求后我们采用is_valid()自主完成信息校验
    6. Ajax请求时不可以直接返回redirect的,会直接被忽略,建议用json格式HttpResponse(json.dumps(data_dict, ensure_ascii=False))
    7. Ajax请求不需要csrf校验,我们手动免除掉@csrf_exempt

    2.1. 创建数据表

      在开始之前,我们需要先创建Task数据表,在之后的工作中将数据存入这个数据表。

      application01/models.py

    class Task(models.Model):
        title = models.CharField(verbose_name='任务名', max_length=64)
        # 选择 
        level_choices = {
            (1, '紧急'),
            (2, '必要'),
            (3, '重要'),
            (4, '无ddl'),
            (5, '可搁置')
        }
        level = models.SmallIntegerField(verbose_name='级别', choices=level_choices, default=3)
        # 关联外表,级联删除
        user = models.ForeignKey(verbose_name='负责人(管理员)', to='Admin', on_delete=models.CASCADE)
        detail = models.TextField(verbose_name='详细信息')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2.2. ModelForm结合Bootstrap

      数据表的前端样式将在这里生成。

    class Task(models.Model):
        title = models.CharField(verbose_name='任务名', max_length=64)
        # 选择
        level_choices = {
            (1, '紧急'),
            (2, '必要'),
            (3, '重要'),
            (4, '无ddl'),
            (5, '可搁置')
        }
        level = models.SmallIntegerField(verbose_name='级别', choices=level_choices, default=3)
        # 关联外表,级联删除
        user = models.ForeignKey(verbose_name='负责人(管理员)', to='Admin', on_delete=models.CASCADE)
        detail = models.TextField(verbose_name='详细信息')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2.3. 前端界面

      这部分由模板语言配合html完成。

      模板layout.html

    {% load static %}
    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Olsentitle>
        <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
        <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1-dist/css/bootstrap-datetimepicker.min.css' %}">
        <style>
            .navbar{
                border-radius: 0;
            }
        style>
    head>
    <body>
    
    <nav class="navbar navbar-default">
        
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                        data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                    <span class="sr-only">Toggle navigationspan>
                    <span class="icon-bar">span>
                    <span class="icon-bar">span>
                    <span class="icon-bar">span>
                button>
                <a class="navbar-brand" href="#">Olsen用户管理系统a>
            div>
            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                <ul class="nav navbar-nav">
                    <li><a href="/admin/list">管理员账户a>li>
                    <li><a href="/depart/list">部门管理a>li>
                    <li><a href="/user/list">用户管理a>li>
                    <li><a href="/num/list">靓号管理a>li>
                ul>
                <ul class="nav navbar-nav navbar-right">
    
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                           aria-expanded="false">{{ request.session.info.username }} <span class="caret">span>a>
                        <ul class="dropdown-menu">
                            <li><a href="#">个人资料a>li>
                            <li><a href="#">我的信息a>li>
                            <li role="separator" class="divider">li>
                            <li><a href="/logout">注销a>li>
                        ul>
                    li>
                ul>
            div>
        div>
    nav>
    
    <div>
        {% block content %}
        {% endblock %}
    div>
    
    
    <script src="{% static 'js/jquery.js' %}">script>
    <script src="{% static 'plugins/bootstrap-3.4.1-dist/js/bootstrap.min.js' %}">script>
    {% block js %}
    {% endblock %}
    
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

      这部分分为输入面板、展示面板、分页三部分。其中输入面板主要完成Ajax指令,最为重要;展示面板跟之前的差不多,就是将接受的json数据展示出来,分页是我们之前完成的分页类。

    {% extends 'layout.html' %}
    {% block content %}
    
    <div class="container">
        
        <div class="panel panel-default">
            <div class="panel-heading">新建任务div>
            <div class="panel-body">
    
                <form id="addForm" novalidate>
                    <div class="clearfix">
    
                        {% for field in form %}
                        {% if field.label == '详细信息' %}
                        <div class="col-xs-12">
                            <div class="form-group">
                                <label>{{ field.label }}label>
                                {{ field }}
    
                                <span class="error-msg" style="color: red; position: absolute;">span>
                            div>
                        div>
                        {% else %}
                        <div class="col-xs-6">
                            <div class="form-group" style="position: relative; margin-bottom: 20px">
                                <label>{{ field.label }}label>
                                {{ field }}
                                
                                <span class="error-msg" style="color: red; position: absolute;">span>
                            div>
                        div>
                        {% endif %}
                        {% endfor %}
                        <p>p>
    
                        <div class="col-xs-12">
                            <button id="btnAdd" type="button" , class="btn btn-primary">提 交button>
                        div>
                    div>
                form>
            div>
    
            form>
        div>
    
        
        <div class="panel panel-default">
            <div class="panel-heading">
                <span class="glyphicon glyphicon-th-list" aria-hidden="true">span>
                任务列表
            div>
            <table class="table table-bordered">
                <thead>
                <tr>
                    <th>IDth>
                    <th>任务名th>
                    <th>优先级th>
                    <th>负责人th>
                tr>
                thead>
                <tbody>
                {% for obj in queryset %}
                <tr>
                    <th>{{ obj.id }}th>
                    <td>{{ obj.title }}td>
                    <td>{{ obj.get_level_display }}td>
                    <td>{{ obj.user.username }}td>
                    <td>
                        <a class="btn btn-primary btn-xs" href="/admin/{{ obj.id }}/edit">编辑a>
                        <a class="btn btn-danger btn-xs" href="/admin/{{ obj.id }}/del">删除a>
                    td>
                tr>
                {% endfor %}
                tbody>
            table>
        div>
    
        
        <ul class="pagination" style="width:100%;">
            {{ page_string }}
            {{ search_page }}
        ul>
    div>
    {% endblock %}
    {% block js %}
    <script type="text/javascript">
        $(function () {
            bindBtnAddEvent();
        })
        function bindBtnAddEvent(){
        //   找到btnAdd
            $("#btnAdd").click(function (){
                // 每次使用的时候清空,防止上次的报错这次还显示
                $(".error-msg").empty();
                $.ajax({
                    url: "/task/add",
                    type: "post",
                    data: $("#addForm").serialize(),
                    dataType: "JSON",
                    success: function (res) {
                        if(res.status){
                            alert('添加成功')
                            // 每次添加成功后刷新界面,即可在下方列表立刻更新数据
                            location.reload();
                        }else{
                            // 通过循环匹配错误信息,并输出
                            $.each(res.error, function(name, data){
                                $('#id_' + name).next().text(data[0]);
                            })
                        }
                    }
                })
            })
        }
    script>
    {% endblock %}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116

    2.4. 分页类

      application01/utils/page.py

    # -*- coding:utf-8 -*-
    # 自定义分页主键
    from django.utils.safestring import mark_safe
    
    
    class PAGE(object):
        def __init__(self, request, queryset, page_size=10, plus=5, page_param="page"):
            """
            :param request: 进入这个页面时的request,请求对象
            :param queryset: 符合条件的对象,就是经过搜索筛选之后仍符合要求的数据
            :param page_size: 每页发的那个多少行数据
            :param plus: 分页时显示本页前后plus页的页码
            :param page_param: 再url中传递的获取分页的参数
            """
            # 获取当前页码
            page = request.GET.get(page_param, "1")
            # 防止页码为字母等等
            if page.isdecimal():
                page = int(page)
            else:
                page = 1
    
            # 获取总样本数
            total_count = queryset.count()
            total_page_count, div = divmod(total_count, page_size)
            if div:
                total_page_count += 1
            self.total_page_count = total_page_count
    
            self.page = page
            # 防止搜索的时候超限
            if self.page < 1:
                self.page = 1
            if self.page > self.total_page_count:
                self.page = self.total_page_count
    
            self.plus = plus
            self.page_param = page_param
            self.page_size = page_size
            self.start = (self.page - 1) * self.page_size * (self.page != 0)
            self.end = self.page * self.page_size
    
            # 搜索的时候分页会把原来搜索的条件替换掉,用这四行代码解决
            import copy
            querydict = copy.deepcopy(request.GET)      # 把包含原来所有参数的网址给弄过来
            querydict.mutable = True                    # 这句话要看源码去找_mutable
            self.query_dict = querydict
    
            self.page_queryset = queryset[self.start: self.end]
    
        """生成html代码"""
        def create_html(self):
            # 显示当前页的前plus页和后plus页
            if self.total_page_count <= 2 * self.plus + 1:
                start_page = 1
                end_page = self.total_page_count
            else:
                if self.page <= self.plus:
                    start_page = 1
                    end_page = 2 * self.plus + 1
                else:
                    if (self.page + self.plus) > self.total_page_count:
                        start_page = self.total_page_count - 2 * self.plus
                        end_page = self.total_page_count
                    else:
                        start_page = self.page - self.plus
                        end_page = self.page + self.plus
    
            page_str_list = list()
            self.query_dict.setlist(self.page_param, [1])
            # 首页
            page_str_list.append('
  • 首页
  • '
    .format(self.query_dict.urlencode())) # 上一页 if self.page > 1: self.query_dict.setlist(self.page_param, [self.page - 1]) prev = '
  • 上一页
  • '
    .format(self.query_dict.urlencode()) else: self.query_dict.setlist(self.page_param, [1]) prev = '
  • 上一页
  • '
    .format(self.query_dict.urlencode()) page_str_list.append(prev) # 展示页 for i in range(start_page, end_page + 1): self.query_dict.setlist(self.page_param, [i]) if i == self.page: ele = '
  • {}
  • '
    .format(self.query_dict.urlencode(), i) else: ele = '
  • {}
  • '
    .format(self.query_dict.urlencode(), i) page_str_list.append(ele) # 下一页 if self.page < self.total_page_count: self.query_dict.setlist(self.page_param, [self.page + 1]) nex = '
  • 下一页
  • '
    .format(self.query_dict.urlencode()) else: self.query_dict.setlist(self.page_param, [self.total_page_count]) nex = '
  • 下一页
  • '
    .format(self.query_dict.urlencode()) page_str_list.append(nex) # 尾页 self.query_dict.setlist(self.page_param, [self.total_page_count]) page_str_list.append('
  • 尾页
  • '
    .format(self.query_dict.urlencode())) page_string = mark_safe("".join(page_str_list)) return page_string """搜索页码""" def search(self): search_string = """
    """
    return mark_safe(search_string)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115

    2.5. views部分连接前后端

      task_list用于是展示页面,当json数据从前端发来时task_add负责校验、保存数据。

    # -*- coding:utf-8 -*-
    import json
    
    from django.shortcuts import render, HttpResponse
    from application01.utils.form import TaskModelForm
    from django.views.decorators.csrf import csrf_exempt
    from application01 import models
    from application01.utils.page import PAGE
    
    
    def task_list(request):
        form = TaskModelForm()
        queryset_list = models.Task.objects.all().order_by('-id')
        page_obj = PAGE(request, queryset_list, page_size=5)
        context = {
            'form': form,
            'queryset': page_obj.page_queryset,
            'page_string': page_obj.create_html(),
            "search_page": page_obj.search()
        }
        return render(request, 'task_list.html', context)
    
    
    @csrf_exempt
    def task_add(request):
        # 校验用户发来的数据(是否为空,是否符合格式等等)
        form = TaskModelForm(data=request.POST)
        if form.is_valid():
            form.save()
            # 注意这里是不能直接return redirect的,不会执行这个跳转,只能返回json
            data_dict = {'status': True}
            return HttpResponse(json.dumps(data_dict))
        data_dict = {'status': False, 'error': form.errors}
        return HttpResponse(json.dumps(data_dict, ensure_ascii=False))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    2.6. 结果展示

    在这里插入图片描述

  • 相关阅读:
    Windows服务器TLS协议
    数据结构与算法【递归】Java实现
    【python基础(三)】操作列表:for循环、正确缩进、切片的使用、元组
    vue项目通过json-bigint在前端处理java雪花id过长导致失去精度问题
    docker 安装jenkins
    如何快速提取设计地形等高线?
    Spring的注解开发-作用范围等注解的使用
    轻松入门自然语言处理系列 专题7 基于FastText的文本分类
    基于lora技术对Gemma(2B)大模型的微调实践
    如何通过沉浸式投影技术提升文旅夜游的互动体验?
  • 原文地址:https://blog.csdn.net/Hjh1906008151/article/details/126679403