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

11 篇文章 52 订阅


  Django是目前比较火爆的框架,之前有在知乎刷到,很多毕业生进入大厂实习后因为不会git和Django框架3天就被踢掉了,因为他们很难把自己的工作融入到整个组的工作中。因此,我尝试自学Django并整理出如下笔记。

0. Why Ajax?

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

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

1. 既有工作

  已经完成的是login界面和几个管理界面,但是大多数请求都不是用Ajax写的。

在这里插入图片描述

  我们基于Ajax请求完成过简单的操作,算是入门级的实战(传送门),这次的实战是上一次的进阶版。

2. 实战

2.1. 效果展示

  新建订单

在这里插入图片描述

  删除订单

在这里插入图片描述

  修改订单

在这里插入图片描述

  查询订单

在这里插入图片描述

2.2. 讲在前面

  因为我们的工作是建立在以前工作的基础上的,因此不可避免的会用到以前的一些成果,现在这里罗列。

2.2.0. 文件夹介绍

img

static							包含各种静态文件,css样式,jquery等
templates						包含各种html文件,所有的静态页面都是这里出来的
utils							包含各种中途会用到的工具
	- bootstrap_input.py		继承ModelForm,在其中嵌入Bootstrap样式
	- encrypt.py				利用md5加密密码
	- form.py					所有的ModelForm存这里
	- page.py					分页类,负责为界面添加分页功能
views
	- admin.py					管理员界面的views放这里
	- depart.py					部门界面的views放这里
	- num.py					号码界面的views放这里
	- user.py					用户界面的views放这里
__init__.py						略
admin.py						略
apps.py							略
models.py						存放我们对数据库的数据表的定义
test.py 						略
Employee_Management
	- __init__.py				略
	- asgi.py					略
	- settings.py				略
	- urls.py					views内的py文件中各个函数与templates内html文件的对应关系
	- wsgi.py					略

2.2.1. 分页功能

  因为Django自带的分页组件不太好用,我们自己写了个分页类PAGE

# -*- 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.2.2. 前端模板

  Django有自己的模板语言,可以在前端继承模板,这里附上模板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>
                <li><a href="/task/list">任务管理</a></li>
                <li><a href="/order/list">订单管理</a></li>
                <li><a href="/chart/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>

2.3. 代码展示

  前端代码order_list.html,主要包含订单新建按钮、模板语言展示订单、对话框内添加/编辑订单、Ajax请求的js函数。有点小长,但是注释比较详细。

{% extends 'layout.html' %}
{% block content %}
<div class="container">
    <div>
        <!--        如果不用js代码可以通过设置data-toggle data-target完成点击后弹出对话框-->
        <!--        <input id="btnAdd" type="button" value="新建订单" class="btn btn-primary" data-toggle="modal" data-target="#myModal">-->
        <input id="btnAdd" type="button" value="新建订单" class="btn btn-primary">
        <div style="float: right; width: 300px;">
            <form method="get">
                <div class="input-group">
                    <input type="text" class="form-control" placeholder="搜索订单号" name="search" value="{{search_data}}">
                    <span class="input-group-btn">
                        <button class="btn btn-default" type="submit">
                            <span class="glyphicon glyphicon-search"></span>
                        </button>
                    </span>
                </div>
            </form>
        </div>
    </div>
    <p style="margin: 0;">&emsp;</p>

    <div class="panel panel-default">
        <!-- Default panel contents -->
        <div class="panel-heading">
            <span class="glyphicon glyphicon-th-list" aria-hidden="true"></span>
            订单列表
        </div>
        <!-- Table -->
        <table class="table table-bordered">
            <thead>
            <tr>
                <th>ID</th>
                <th>订单号</th>
                <th>名称</th>
                <th>价格</th>
                <th>状态</th>
                <th>管理员</th>
                <th>操作</th>
            </tr>
            </thead>
            <tbody>
            {% for obj in queryset %}
            <tr uid="{{ obj.id }}">
                <th>{{ obj.id }}</th>
                <td>{{ obj.oid }}</td>
                <td>{{ obj.title }}</td>
                <td>{{ obj.price }}</td>
                <td>{{ obj.get_status_display }}</td>
                <td>{{ obj.admin.username }}</td>
                <td>
                    <input uid="{{ obj.id }}" type="button" class="btn btn-warning btn-xs btn-edit" href="#" value="编 辑">
                    <input uid="{{ obj.id }}" type="button" class="btn btn-danger btn-xs btn-delete" href="#" value="删 除">
                </td>
            </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>

    <ul class="pagination" style="width:100%;">
        {{ page_string }}
        {{ search_page }}
    </ul>
</div>

<!--新建/编辑 对话框-->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span>
                </button>
                <h4 class="modal-title" id="myModalLabel">新 建</h4>
            </div>
            <div class="modal-body">
                <form id="addForm" novalidate>
                    <div class="clearfix">
                        {% for field in form %}
                        <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>
                        {% endfor %}
                    </div>
                </form>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">取 消</button>
                <button id="btnSave" type="button" class="btn btn-primary">保 存</button>
            </div>
        </div>
    </div>
</div>


<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
    <div class="modal-dialog" role="document">
        <div class="alert alert-danger alert-dismissible fade in" role="alert">
            <h3>是否确定删除?</h3>
            <p style="margin: 10px 0;">删除后数据不可恢复</p>
            <p>删除该数据将导致其他数据表相关数据也被删除</p>
            <p style="text-align: right;">
                <button type="button" id="btnConfirmDelete" class="btn btn-danger">确 定</button>
                <button type="button" class="btn btn-default" data-dismiss="modal">取 消</button>
            </p>
        </div>
    </div>
</div>

{% endblock %}

{% block js %}
<script type="text/javascript">
$(function () {
    var DELETE_ID;              // 用于标注是否为删除命令以及操作对象
    var EDIT_ID;                // 用于标注是否为编辑命令以及操作对象
    bindBtnAddEvent();          // 这个函数完成点击按钮跳出对话框
    bindBtnSaveEvent();         // 这个函数完成保存数据
    bindBtnDeleteEvent();       // 这个函数用于删除数据
    bindBtnConfirmDeleteEvent();// 这个函数用于删除时跳出是否确认删除的框
    bindBtnEditEvent();         // 这个函数用于编辑数据
})

function bindBtnAddEvent() {
    $("#btnAdd").click(function () {
        EDIT_ID = undefined;                // 清空编辑标志,不然编辑后新建还是编辑效果
        $("#addForm")[0].reset();           // 注意要清空
        $('#myModalLabel').text('新 建');
        $('#myModal').modal('show');        // 显示对话框
    });
}

function bindBtnSaveEvent() {
    $("#btnSave").click(function () {
        $(".error-msg").empty();            // 清空报错
        if(EDIT_ID) {
            Edit_ajax();
        } else {
            Add_ajax();
        }
        function Add_ajax() {
            $.ajax({
                url: "/order/add",
                type: "post",
                data: $("#addForm").serialize(),        // 序列化addForm的信息,传入
                dataType: "JSON",
                success: function (res) {
                    if(res.status){
                        alert('创建成功')
                        // 清空表单, jquerry无置空功能,我们这里算是取一个dom对象(加了[0]之后)操作
                        $("#addForm")[0].reset();
                        // 关闭对话框
                        $('#myModal').modal('hide');
                        // 刷新
                        location.reload();
                    }else{
                        $.each(res.error, function(name, error_list){
                            $('#id_' + name).next().text(error_list[0]);
                        })
                    }
                }
            })
        }
        function Edit_ajax() {
            $.ajax({
                url: "/order/edit/?uid=" + EDIT_ID,
                type: "post",
                data: $("#addForm").serialize(),
                dataType: "JSON",
                success: function (res) {
                    if(res.status){
                        alert('编辑成功')
                        // 清空表单, jquerry无置空功能,我们这里算是取一个dom对象(加了[0]之后)操作
                        $("#addForm")[0].reset();
                        // 关闭对话框
                        $('#myModal').modal('hide');
                        // 刷新
                        location.reload();
                    }else{
                        if(res.tips){
                            alert(res.tips);
                        } else {
                            $.each(res.error, function(name, error_list){
                                $('#id_' + name).next().text(error_list[0]);
                            })
                        }
                    }
                }
            })
        }
    });
}

function bindBtnDeleteEvent() {
    $(".btn-delete").click(function () {
        $('#deleteModal').modal('show');
        // 获取当前行的id并赋值给全局变量(=>当前点击到这个标签的时候)
        DELETE_ID = $(this).attr("uid");
    });
}

function bindBtnConfirmDeleteEvent(){
    $("#btnConfirmDelete").click(function () {
        $.ajax({
            // url: "/order/" + DELETE_ID + "/delete",
            url: "/order/delete",
            type: "GET",
            data:{
                uid: DELETE_ID
            },
            dataType: "JSON",
            success: function (res) {
                if(res.status){
                    // alert("删除成功");
                    // $('#myModal').modal('hide');
                    // $("tr[uid='" + DELETE_ID + "']").remove();
                    // DELETE_ID = 0;
                    location.reload();
                }else{
                    alert(res.error);
                }
            }
        })
    })
}

function bindBtnEditEvent() {
    $(".btn-edit").click(function () {
        var currentId = $(this).attr('uid');
        EDIT_ID = currentId;
        $.ajax({
            url: "/order/detail",
            type: "GET",
            data:{
                uid: currentId
            },
            dataType: "JSON",
            success: function (res) {
                if(res.status){
                    // 利用循环给edit里面的输入框内加入默认值
                    $.each(res.data, function(name, value) {
                        $("#id_" + name).val(value);
                    })
                    $('#myModalLabel').text('编 辑');
                    $('#myModal').modal('show');
                }else{
                    alert(res.error);
                }
            }
        })
    });
}
</script>
{% endblock %}

  在上面的html文件 中提到了几个url,这里附上urls.py中的url路径:

"""Employee_Management URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from application01.views import depart, num, user, admin, account, task, order, chart

urlpatterns = [
    # path('admin/', admin.site.urls),
    path('depart/list', depart.depart_list),
    path('depart/add', depart.depart_add),
    path('depart/del', depart.depart_del),
    path('depart/<int:nid>/edit', depart.depart_edit),
    path('user/list', user.user_list),
    path('user/add', user.user_add),
    path('user/madd', user.user_madd),
    path('user/<int:nid>/edit', user.user_edit),
    path('user/<int:nid>/del', user.user_del),
    path('num/list', num.num_list),
    path('num/add', num.num_add),
    path('num/a', num.num_a),
    path('num/<int:nid>/edit', num.num_edit),
    path('num/<int:nid>/del', num.num_del),
    path('admin/list', admin.admin_list),
    path('admin/add', admin.admin_add),
    path('admin/<int:nid>/edit', admin.admin_edit),
    path('admin/<int:nid>/del', admin.admin_del),
    path('admin/<int:nid>/reset', admin.admin_reset),
    path('login', account.login),
    path('logout', account.logout),
    path('image/code', account.image_code),
    path('task/list', task.task_list),
    path('task/add', task.task_add),
    path('order/list', order.order_list),		// 订单列表显示
    path('order/add', order.order_add),			// 添加订单
    path('order/delete', order.order_delete),	// 删除订单
    path('order/detail', order.order_detail),	// 加载编辑操作中的订单信息
    path('order/edit/', order.order_edit),		// 编辑订单
    path('chart/list', chart.chart_list),
    path('chart/bar', chart.chart_bar),
    path('chart/pie', chart.chart_pie),
    path('chart/line', chart.chart_line)
]

  这些函数所对应的路径置于views文件夹中的order.py文件。

# -*- coding:utf-8 -*-
import random
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from application01.utils.form import OrderModelForm
from django.http import JsonResponse
from datetime import datetime
from application01.utils.page import PAGE
from application01 import models


def order_list(request):
    search_dict = dict()
    # 搜索功能
    value = request.GET.get("search", "")
    if value:
        search_dict["oid__contains"] = value

    form = OrderModelForm()
    # 保证展示的是搜索后的结果
    queryset_list = models.Order.objects.filter(**search_dict).order_by('-id')
    # 调用分页类
    page_obj = PAGE(request, queryset_list, page_size=7)
    context = {
        'form': form,
        "search_data": value,
        'queryset': page_obj.page_queryset,
        'page_string': page_obj.create_html(),
        "search_page": page_obj.search()
    }
    return render(request, 'order_list.html', context)


# Ajax的post请求需要加这个
@csrf_exempt
def order_add(request):
    form = OrderModelForm(data=request.POST)
    # 判断输入是否为空
    if form.is_valid():
        # 用户没权限输入oid,我们动态计算出一个
        form.instance.oid = datetime.now().strftime('%Y%m%d%H%M%S') + str(random.randint(1000, 9999))
        # 管理员直接在session中拿,因为管理员已经登录了
        form.instance.admin_id = request.session['info']['id']
        form.save()
        return JsonResponse({"status": True})
    return JsonResponse({"status": False, 'error': form.errors})


def order_delete(request):
    uid = request.GET.get('uid')
    # 先判断这个uid还在不在
    exists = models.Order.objects.filter(id=uid).exists()
    if not exists:
        return JsonResponse({'status': False, 'error': '删除失败,数据不存在!!!'})
    models.Order.objects.filter(id=uid).delete()
    return JsonResponse({'status': True})


def order_detail(request):
    uid = request.GET.get('uid')
    # 得到对象
    row_obj = models.Order.objects.filter(id=uid).values("title", "price", "status").first()
    if not row_obj:
        return JsonResponse({'status': False, 'error': '删除失败,数据不存在!!!'})
    # 返回对象的详细信息
    result = {
        "status": True,
        "data": row_obj,
    }
    return JsonResponse(result)


@csrf_exempt
def order_edit(request):
    uid = request.GET.get('uid')
    row_obj = models.Order.objects.filter(id=uid).first()
    if not row_obj:
        return JsonResponse({'status': False, 'tips': '编辑失败!数据不存在!!!'})
    form = OrderModelForm(data=request.POST, instance=row_obj)
    if form.is_valid():
        form.save()
        return JsonResponse({'status': True})
    return JsonResponse({'status': True, 'error': form.errors})
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

铖铖的花嫁

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

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

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

打赏作者

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

抵扣说明:

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

余额充值