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

11 篇文章 55 订阅


  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);
	}
})

  后端代码:

def aaa(request):
    print(request.GET)

2.2. POST请求

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

  前端代码:

$.ajax({
	url: '地址',
	type: 'post',
	data: {
		x: 123456,
		y: '123456',
	},
    dataType: 'JSON',
	success: function(res){
		console.log(res);
	}
})

  后端代码:

from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def aaa(request):
    print(request.POST)

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='详细信息')

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='详细信息')

2.3. 前端界面

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

  模板layout.html

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Olsen</title>
    <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-fluid">-->
    <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 navigation</span>
                <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><a href="#">登录</a></li>-->
                <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>

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

{% extends 'layout.html' %}
{% block content %}

<div class="container">
    <!--    输入面板-->
    <div class="panel panel-default">
        <div class="panel-heading">新建任务</div>
        <div class="panel-body">
<!--            id定位,从form中获取输入内容-->
            <form id="addForm" novalidate>
                <div class="clearfix">
<!--                    循环生成输入框,用if判断是不是面积最大的‘详细信息’-->
                    {% 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>
<!--                    不能用submit-->
                    <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>ID</th>
                <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 %}

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('<li><a href="?{}">首页</a></li>'.format(self.query_dict.urlencode()))
        # 上一页
        if self.page > 1:
            self.query_dict.setlist(self.page_param, [self.page - 1])
            prev = '<li><a href="?{}">上一页</a></li>'.format(self.query_dict.urlencode())
        else:
            self.query_dict.setlist(self.page_param, [1])
            prev = '<li><a href="?{}">上一页</a></li>'.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 = '<li class="active"><a href="?{}">{}</a></li>'.format(self.query_dict.urlencode(), i)
            else:
                ele = '<li><a href="?{}">{}</a></li>'.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 = '<li><a href="?{}">下一页</a></li>'.format(self.query_dict.urlencode())
        else:
            self.query_dict.setlist(self.page_param, [self.total_page_count])
            nex = '<li><a href="?{}">下一页</a></li>'.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('<li><a href="?{}">尾页</a></li>'.format(self.query_dict.urlencode()))
        page_string = mark_safe("".join(page_str_list))
        return page_string

    """搜索页码"""
    def search(self):
        search_string = """
        <form method="get">
            <div class="input-group" style="width: 200px; float: right;">
                <input name = "page" type="text" class="form-control" style="width: 200px;" placeholder="页码">
                <span class="input-group-btn">
                    <button class="btn btn-default" type="submit">跳转</button>
                </span>
            </div>
        </form>
        """
        return mark_safe(search_string)

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))

2.6. 结果展示

在这里插入图片描述

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

铖铖的花嫁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值