NWNU-Sun | 技术沉思录

代码是诗,bug是谜

   ::  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
  77 随笔 :: 49 文章 :: 6 评论 :: 40763 阅读

1.初识Django

Python的Web框架

  • Fask,自身短小精悍+第三方框架
  • Django,内部集成了很多组件+ 第三方组件
pip install django

目录结构

D:\python3
 - python.exe
 - DLLs
 - Doc
 - include
 - Lib
 	- 内置模块
 	- site-packages
 		- openpyxl
 		- python-docx
 		- flask
 		- django 【框架的源码】
 - libs
 - Scripts
 	- pip.exe
 	-django-admin.exe  【工具,创建django项目中的文件和文件夹】
 - tcl
 - Tools
 

2. 创建项目

  • 打开终端

  • 进入某个目录(项目路径)

    cd /opt
    
  • 命令创建

    # Path配置了`Scripts`的环境变量
    django-admin startproject my_first_project
    # 绝对路径
    d:\python3\Scripts\django-admin startproject my_frist_project
    
    • 目录结构

      my_frist_project
         - my_frist_project
              - asgi.py [接受网络请求]
              - settings.py [项目配置]
              - urls.py [url和函数的对应关系]
              - wsgi.py [接受网络请求]
         - manage.py [项目的管理,启动项目,创建app\数据管理] [常用]
      
  • Pycharm创建

    image-20231118190915878

  • 目录结构

    djangodemo
    	-djangodemo
    		- _init__.py
    		- asgi.py
    		- setting.py
    		-urls.py
    		- wsgi.py
    	-templates
    	-manage.py
    
  • 特殊说明

    • 命令行,创建的项目是标准的

    • pycharm,在标准的基础上默认加了一些东西

      • 创建templates目录(删除)

      • settings.py中

        image-20231118192109293

3.创建app

类似Java开发中模块化,每个功能一个模块

命令行创建

python3 manage.py startapp app1  # 后面跟app名称

image-20231118225531428

可以看到系统创建出一个app1的包,接下来查看app1包具体包含哪些信息

├─app1
│  │  admin.py [默认提供的admin后台管理]
│  │  apps.py [固定,不能动] app启动类
│  │  models.py [重要] 操作数据库
│  │  tests.py [固定不动,单元测试]
│  │  views.py [视图] 
│  │  __init__.py
│  │
│  ├─migrations [固定,不能动]

4.快速启动

修改settings.py, 添加app1到项目

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app1.apps.App1Config' # 新增
]                                                                                         

编写url与视图函数之间的关系

from django.urls import path
from app1 import views # 必须被导入
urlpatterns = [
    # path('admin/', admin.site.urls), # 注释系统自动配置的
    path('index/',views.index)  # index函数必须存在
]

编写视图函数 views.py

from django.shortcuts import render, HttpResponse


# Create your views here.

def index(request):
    return HttpResponse('Hello, world.')

如何,关于url和视图之间的配置就设置好了,可以直接启动manage.py,并访问列出的url地址

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
November 18, 2023 - 23:54:05
Django version 4.2.7, using settings 'djangoProject.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

注意:带上url完整地址

image-20231118235600707

添加html页面返回

urlpatterns = [
    # path('admin/', admin.site.urls),
    path('index/', views.index),
    path('login/', views.login)
]
from django.shortcuts import render, HttpResponse
# Create your views here.

def index(request):
    return HttpResponse('Hello, world.')

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

此时,根据app的加载顺序,会先找app1模块中的templates,如果没找到则会从父目录的templates文件夹中寻找,前提是DIRS为空

image-20231119221432329

但是,可以在settings.py中设置加载指定目录,这样,只会在父目录的模板文件中寻找

'DIRS': [BASE_DIR / 'templates']

静态文件

静态文件默认在配置settings.py中是

STATIC_URL = 'static/'

在app下创建静态文件 static,子文件夹 css,js,img,这是一个单体应用的常用静态文件配置

创建完后如图

image-20231119222904437

修改app1下的templates下html文件

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<img src="{% static 'img/huge.png' %}">
</body>
</html>

{% load static %} 是用来加载静态文件的模板标签。在使用这个标签之后,你就可以在模板中使用 {% static 'path/to/your/file' %} 来指定静态文件的路径,而不需要硬编码整个 URL 或者文件路径

{% load static %} 实际上是加载了 Django 的静态文件处理器,它会根据你在 settings.py 中配置的静态文件目录,来帮助你管理和处理静态文件。一旦加载了静态文件处理器,你就能够在模板中使用 {% static %} 标签来引用你的静态文件(比如图片、CSS 文件、JavaScript 文件等)

5.模板语法

本质:在HTML中写入一些占位符,由数据对这些占位符进行处理,跟JSP有点类似

推荐:了解即可,目前的数据渲染都由Nginx去做

image-20231120102810881

添加视图映射

urls.py

from django.urls import path
from app1 import views

urlpatterns = [
    # path('admin/', admin.site.urls),
    path('index/', views.index),
    path('login/', views.login),
    path('news', views.news) # 新添加映射
]

视图函数 news

views.py

def news(request):
    # 模拟数据
    import requests
    response = requests.get('https://weibo.com/ajax/side/hotSearch')
    data = response.json()['data']['realtime']
    return render(request, 'news.html', {'data': data})

app1的templates下创建页面

news.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul>
    {% for item in data %}
        <li>{{ item.word }}</li>
    {% endfor %}
    </ul>
</body>
</html>

启动服务,访问127.0.0.1:8000/news查看

image-20231120113633425

复用模板

母模板

templates/base.html

{% load static %}<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>{% block title %}Django Boards{% endblock %}</title>
    <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
  </head>
  <body>
    <div class="container">
      <ol class="breadcrumb my-4">
        {% block breadcrumb %}
        {% endblock %}
      </ol>
      {% block content %}
      {% endblock %}
    </div>
  </body>
</html>

这是我们的母版页。每个我们创建的模板都 extend(继承) 这个特殊的模板。现在我们介绍 {% block %} 标签。它用于在模板中保留一个空间,一个”子”模板(继承这个母版页的模板)可以在这个空间中插入代码和 HTML。

{% block title %} 中我们还设置了一个默认值 “Django Boards.”。如果我们在子模板中未设置 {% block title %} 的值它就会被使用。

templates/topics.html

{% extends 'base.html' %}
{% block title %}
  {{ board.name }} - {{ block.super }}
{% endblock %}
{% block breadcrumb %}
  <li class="breadcrumb-item"><a href="{% url 'home' %}">Boards</a></li>
  <li class="breadcrumb-item active">{{ board.name }}</li>
{% endblock %}
{% block content %}
    <!-- just leaving it empty for now. we will add core here soon. -->
{% endblock %}

6.请求与响应

在Web应用程序中,请求和响应模式是非常重要的概念。当用户在浏览器地址栏输入一个URL或者点击某个链接时,会向服务器发送一个请求。服务器处理完这个请求后,会返回一个响应给浏览器。这就是典型的HTTP请求-响应模式。

Django 的请求对象HttpRequest

在 Django 中,当一个 HTTP 请求到达 Django 应用时,它首先会被某个URLconf文件转化为一个 HttpRequest 对象。这个对象包含了这次HTTP请求的所有相关的信息。

def view(request):
    # request 是一个 HttpRequest 对象
    print(request.method)  # 输出请求方法,比如 "GET" 或 "POST"

Django 的响应对象HttpResponse

Django的视图必须返回一个 HttpResponse 对象。这个对象表示服务器给客户端(通常是浏览器)的响应。这个 HttpResponse 对象会被转化为一个 HTTP 响应,然后被发送到客户端。

from django.http import HttpResponse

def view(request):
    # 创建一个 HttpResponse 对象
    response = HttpResponse("Hello, World!")
    return response  # 这个响应将会被发送给客户端

示例:

设置视图映射

from django.urls import path
from app1 import views

urlpatterns = [
    path('index/', views.index),
    path('login/', views.login),
    path('news', views.news)
]

上述采用常规信息,有登陆与主页

编写首页视图函数

def index(request):
    # 如果用户没有登陆则跳转到登陆页面
    if not request.session.get('is_login', None):
        return redirect('/login')
    return render(request, 'index.html')

编写登陆函数

def login(request):
    if  request.method == 'GET':
        return render(request, 'login.html')

    if request.POST.get('username') == 'root' and request.POST.get('password') == '123456':
        return render(request, 'index.html',{'msg':'登录成功','username':request.POST.get('username')})

    return render(request, 'login.html', {'msg': '用户名或密码错误'})

7.数据库操作

Mysql数据库+pyMysql

import pymysql

class Mysql:
    def __init__(self, host, user, password, database):
        self.host = host
        self.user = user
        self.password = password
        self.database = database

    def connect(self):
        self.conn = pymysql.connect(
            host=self.host, user=self.user, password=self.password, database=self.database
        )
        self.cursor = self.conn.cursor()

    def close(self):
        self.cursor.close()
        self.conn.close()

    def execute(self, sql):
        self.cursor.execute(sql)
        self.conn.commit()

    def fetchall(self):
        return self.cursor.fetchall()

    def fetchone(self):
        return self.cursor.fetchone()

    def __del__(self):
        self.close()

class Mysql_pool:
    def __init__(self, host, user, password, database):
        self.host = host
        self.user = user
        self.password = password
        self.database = database
        self.pool = []

    def connect(self):
        self.conn = pymysql.connect(
            host=self.host, user=self.user, password=self.password, database=self.database
        )
        self.cursor = self.conn.cursor()

    def close(self):
        self.cursor.close()
        self.conn.close()

    def execute(self, sql):
        self.cursor.execute(sql)
        self.conn.commit()

    def fetchall(self):
        return self.cursor.fetchall()

    def fetchone(self):
        return self.cursor.fetchone()

    def __del__(self):
        self.close()

    def get_conn(self):
        if len(self.pool) == 0:
            self.connect()
            self.pool.append(self.conn)
        else:
            self.conn = self.pool[0]

if __name__ == '__main__':
	# 注意所有的SQL语句必须是大写
    mysql_ = Mysql('127.0.0.1', 'root', '123456', 'test')
    mysql_.connect()
    mysql_.execute('CREATE DATABASE IF NOT EXISTS test')
    # 创建表
    mysql_.execute('DROP TABLE IF EXISTS user')
    mysql_.execute('CREATE TABLE user(id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255))')
    # 插入数据
    mysql_.execute('INSERT INTO user(name) VALUES("test")')
    mysql_.execute('INSERT INTO user(name) VALUES("test2")')
    mysql_.execute('INSERT INTO user(name) VALUES("test3")')
    mysql_.execute('SELECT * FROM user')
    # 打印所有表名
    print(mysql_.fetchall())   

上述是常规写法,如果想采用方法链的方式创建一个mysql对象并获取链接,可以修改为

def execute(self, sql):
    self.cursor.execute(sql)
    self.conn.commit()
    return self

这样,便可以采用链式写法

mysql_ = Mysql('127.0.0.1', 'root', '123456', 'test').connect()

Django开发操作数据库,内部提供了ORM框架

image-20231126220735293

安装第三方模块

pip install mysqlclient

pymysqlmysqlclient区别

  1. pymysql:
    • pymysql 是一个纯 Python 实现的 MySQL 客户端库。
    • 它使用 Python 的 socket 模块直接连接 MySQL 服务器。
    • 在性能上可能不如 mysqlclient,因为它是纯 Python 实现,没有底层 C 扩展。
  2. mysqlclient:
    • mysqlclient 是一个 Python 的 MySQL 客户端库,是 MySQL 官方 C API 的一个封装。
    • 它是用 C 语言编写的,因此在性能上可能更高效。
    • mysqlclient 实际上是 MySQL-Python 项目的一个分支,而 pymysql 则是从头开始的一个独立项目。
ORM

orm可以帮助我们做两件事

  • 创建、修改、删除数据库的表无法创建数据库
  • 自带工具创建表

测试

首先创建数据库

create database sun_django

修改settings.py文件,将默认的sqlite3替换为mysql

# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases

# DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.sqlite3',
#         'NAME': BASE_DIR / 'db.sqlite3',
#     }
# }
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "NAME": "sun_django",
        "USER": "root",
        "PASSWORD": "123456",
        "HOST": "127.0.0.1",
        "PORT": "3306",
    }
}
模型设计

models.py

from django.db import models


# Create your models here.

class Department(models.Model):
    """部门表"""
    id = models.BigAutoField(primary_key=True, verbose_name='部门id')
    name = models.CharField(max_length=32, verbose_name='部门名称')


class UserInfo(models.Model):
    """用户表"""
    id = models.BigAutoField(primary_key=True, verbose_name='用户id')
    name = models.CharField(max_length=32, verbose_name='用户名')
    password = models.CharField(max_length=32, verbose_name='密码')
    age = models.IntegerField(verbose_name='年龄')
    account = models.DecimalField(verbose_name='账户余额', decimal_places=2, max_digits=10, default=0)
    """ 
    to 指定关联对象
    on_delete指定级联删除
    to_fields指定关联字段
    级联删除
        department = models.ForeignKey(to='Department', to_fields='id', on_delete=models.CASCADE, verbose_name='所属部门')
    置空
         department = models.ForeignKey(to='Department', to_fields='id', null=True, blank=True, on_delete=models.SET_NULL,
                                 verbose_name='所属部门')
    """
    department = models.ForeignKey(to='Department', to_field='id', null=True, blank=True, on_delete=models.SET_NULL,
                                   verbose_name='所属部门')

    createtime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    # 添加性别
    gender = models.SmallIntegerField(choices=((1, '男'), (2, '女')), default=1, verbose_name='性别')

打开管理工具

image-20231209161615768

执行makemigrations

makemigrations会把我们写的model生成数据库迁移文件

image-20231209161819524

生成后,可以在应用 的migrations中看到文件生成

image-20231209161926749

执行migrate

将迁移文件集同步到数据库中.

image-20231209162035538

成功后,登陆数据库查看是否有上述2张表生成

mysql> use sun_django;
Database changed
mysql> show tables;
+----------------------+
| Tables_in_sun_django |
+----------------------+
| app1_department      |
| app1_userinfo        |
| django_migrations    |
+----------------------+
3 rows in set (0.00 sec)
mysql> desc app1_userinfo;
+---------------+---------------+------+-----+---------+----------------+
| Field         | Type          | Null | Key | Default | Extra          |
+---------------+---------------+------+-----+---------+----------------+
| id            | bigint        | NO   | PRI | NULL    | auto_increment |
| name          | varchar(32)   | NO   |     | NULL    |                |
| password      | varchar(32)   | NO   |     | NULL    |                |
| age           | int           | NO   |     | NULL    |                |
| account       | decimal(10,2) | NO   |     | NULL    |                |
| createtime    | datetime(6)   | NO   |     | NULL    |                |
| gender        | smallint      | NO   |     | NULL    |                |
| department_id | bigint        | YES  | MUL | NULL    |                |
+---------------+---------------+------+-----+---------+----------------+
8 rows in set (0.00 sec)
部门列表展示

设置url映射

urls.py

path('depart/list', views.depart_list)

设置函数

@require_http_methods(["GET"])
def depart_list(request):
    if request.method == 'GET':
        departs = models.Department.objects.all()
        return render(request, 'departments.html', {'departs': departs})

编写html函数

通过 {% for depart in departs %} {%endfor%} 渲染数据

//部分代码
  <div class="panel panel-default">
        <!-- Default panel contents -->
        <div class="panel-heading">
            <span class="glyphicon glyphicon-list" aria-hidden="true"></span>
            部门列表
        </div>

        <!-- Table -->
        <table class="table table-bordered">
            <thead>
            <tr>
                <th>ID</th>
                <th>部门名称</th>
                <th>操作</th>
            </tr>
            </thead>
            <tbody>
            {% for depart in departs %}
                <tr>
                    <th scope="row">{{ depart.id }}</th>
                    <td class="editable-cell">{{ depart.name }}</td>
                    <td>
                        <button type="button" class="btn btn-primary btn-xs edit-btn"
                                data-department-id="{{ depart.id }}">编辑
                        </button>
                        <a href="#" class="btn btn-danger btn-xs" onclick="deleteDepartment({{ depart.id }})">删除</a>
                        <button type="button" class="btn btn-success btn-xs save-btn"
                                data-department-id="{{ depart.id }}" style="display: none;">保存
                        </button>
                    </td>

                </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>
部门删除

添加url映射路径,指定的 URL 地址映射到名为 depart_delete 的视图函数上,并传递一个整数类型的部门 ID 参数

path('depart/delete/<int:department_id>', views.depart_delete, name='depart_delete')

增加视图函数

@require_http_methods(["DELETE"])
def depart_delete(request, department_id):
    if request.method == 'DELETE':
        try:
            department = models.Department.objects.get(id=department_id)
            department.delete()
            return HttpResponse('删除成功')
        except models.Department.DoesNotExist:
            return HttpResponse('部门不存在', status=404)
    else:
        return HttpResponse('无效的请求方法', status=405)

前端代码片段

    function deleteDepartment(departmentId) {
        if (confirm("确定要删除该部门吗?")) {
            // 发送删除请求
            $.ajax({
                url: "/depart/delete/" + departmentId,
                type: "DELETE",
                success: function (response) {
                    $('#myModal').modal('hide');
                    // 显示模态框
                    $('#myModal1 .modal-body').text('删除成功!');
                    $('#myModal1').modal('show');
                    // 设置定时器,2秒后自动关闭模态框
                    setTimeout(function () {
                        $('#myModal1').modal('hide');
                        window.location.reload(); // 如果需要的话,重新加载页面
                    }, 1500);
                },
                error: function (xhr, status, error) {
                    $('#myModal').modal('hide');
                    // 显示模态框
                    $('#myModal1 .modal-body').text('删除失败!');
                    $('#myModal1').modal('show');
                    // 设置定时器,2秒后自动关闭模态框
                    setTimeout(function () {
                        $('#myModal1').modal('hide');
                        window.location.reload(); // 如果需要的话,重新加载页面
                    }, 1500);
                }
            });
        }
    }
部门更新

路径映射

path('depart/update/<int:department_id>', views.depart_update, name='depart_update')

视图函数

@require_http_methods(["PUT"])
def depart_update(request, department_id):
    """
    PUT /departments/{department_id}
    更新部门
    :param request:
    :param department_id:
    :return:
    """
    if request.method == 'PUT':
        try:
            department = models.Department.objects.get(id=department_id)
        except models.Department.DoesNotExist:
            return HttpResponse('部门不存在', status=404)

        # PUT请求的数据通常包含在请求的主体中
        try:
            body_data = json.loads(request.body)
        except json.JSONDecodeError as e:
            return HttpResponse('无效的JSON数据', status=400)

        department_name = body_data.get('departmentName')
        department.name = department_name
        department.save()
        return HttpResponse('修改成功')
    else:
        return HttpResponse('无效的请求方法', status=405)

前端代码采用Bootstrap3,当点击编辑的时候,使得原先表格处于可修改状态,点击保存,则出发Ajax请求,提交数据。

    $(document).ready(function () {
        // 绑定编辑按钮点击事件
        $(".edit-btn").click(function () {
            var departmentId = $(this).data("department-id");
            var editableCell = $(this).closest("tr").find(".editable-cell");
            var saveBtn = $(this).closest("tr").find(".save-btn");

            // 切换编辑状态
            editableCell.attr("contenteditable", "true");
            editableCell.addClass("editing");

            // 显示保存按钮
            saveBtn.show();
        });

        // 绑定保存按钮点击事件
        $(".save-btn").click(function () {
            var departmentId = $(this).data("department-id");
            var editableCell = $(this).closest("tr").find(".editable-cell");
            var saveBtn = $(this);

            // 获取编辑后的部门名称
            var departmentName = editableCell.text().trim();
            // 发送请求
            $.ajax({
                url: "/depart/update/" + departmentId,  // 替换为您的API接口地址
                type: "PUT",  // 根据需要选择请求类型,比如POST、GET等
                data: JSON.stringify({
                    "departmentName": departmentName
                }),
                contentType: "application/json",
                success: function (response) {
                    $('#myModal').modal('hide');
                    // 显示模态框
                    $('#myModal1 .modal-body').text('更新成功!');
                    $('#myModal1').modal('show');
                    // 设置定时器,2秒后自动关闭模态框
                    setTimeout(function () {
                        $('#myModal1').modal('hide');
                        // 切换回非编辑状态
                        editableCell.attr("contenteditable", "false");
                        editableCell.removeClass("editing");

                        // 隐藏保存按钮
                        saveBtn.hide();
                        window.location.reload(); // 如果需要的话,重新加载页面
                    }, 1500);
                },
                error: function (xhr, status, error) {
                    $('#myModal').modal('hide');
                    // 显示模态框
                    $('#myModal1 .modal-body').text('更新失败!');
                    $('#myModal1').modal('show');
                    // 设置定时器,2秒后自动关闭模态框
                    setTimeout(function () {
                        $('#myModal1').modal('hide');
                        window.location.reload(); // 如果需要的话,重新加载页面
                    }, 1500);
                }
            });
        });
    });
部门新增

urls.py

path('department/add/', views.depart_add)

视图函数

@require_http_methods(["POST"])
def depart_add(request):
    if request.method == 'GET':
        return render(request, 'departments.html')

    depart = request.POST.get('departmentName')

    try:
        with transaction.atomic():
            models.Department.objects.create(name=depart)
    except Exception as e:
        return HttpResponse('添加失败', status=500)

    return HttpResponse('添加成功', status=200)

前端添加模态框,当点击新增的时候,弹出表单,输入并保存

<!-- 模态框 -->
    <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>
                        <div class="form-group">
                            <label for="departmentName">部门名称</label>
                            <input type="text" class="form-control" id="departmentName" placeholder="请输入部门名称">
                        </div>
                        <!-- 在这里可以添加其他表单字段 -->
                    </form>
                </div>

                <!-- 模态框底部 -->
                <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
                    <button type="button" class="btn btn-primary">保存</button>
                </div>
            </div>
        </div>
    </div>

    <div style="margin-bottom: 10px;">
        <button type="button" class="btn btn-success" data-toggle="modal" data-target="#myModal">新建部门</button>
    </div>
用户展示

同部门展示一样,只是有个外键是部门ID,展示的是部门名称

urls.py

path('departuser/list/', views.departuser_list)

视图函数

@require_http_methods(["GET"])
def departuser_list(request):
    if request.method == 'GET':
        query_set = models.UserInfo.objects.all()
        depart_list = models.Department.objects.all() 
        return render(request, 'departuser.html', {'users': query_set, 'depart_list': depart_list})

上述多查了部门列表是为了新增用户时的下拉框

前端代码

    <div class="panel panel-default">
        <!-- Default panel contents -->
        <div class="panel-heading">
            <span class="glyphicon glyphicon-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 user in users %}
                <tr>
                    <th scope="row">{{ user.id }}</th>
                    <td class="editable-cell name">{{ user.name }}</td>
                    <td class="editable-cell age">{{ user.age }}</td>
                    <td class="editable-cell gender">{{ user.get_gender_display }}</td>
                    <td class="editable-cell departname">{{ user.department.name }}</td>
                    <td class="editable-cell account ">{{ user.account }}</td>
                    <td class="editable-cell createtime">{{ user.createtime | date:"Y-m-d" }}</td>

                    <td>
                        <button type="button" class="btn btn-primary btn-xs edit-btn"
                                data-department-id="{{ user.id }}">编辑
                        </button>
                        <a href="#" class="btn btn-danger btn-xs" onclick="deleteDepartment({{ user.id }})">删除</a>
                        <button type="button" class="btn btn-success btn-xs save-btn"
                                data-department-id="{{ user.id }}" style="display: none;">保存
                        </button>
                    </td>

                </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>

上述前端代码中

获取性别时,采用django的语法,user.get_gender_display,性别在表以0 1数字存,django采用.get_<字段>_display方式直接转换为解释,且语法不能带()

获取部门ID时,在视图函数查询到的department不是ID而是一个Department对象,这块也是django自动实现的,不然像Java还得再查表或者写连表SQL,前端就可以直接用user.department.name获取属性值

获取创建时间,可视化写法如user.createtime | date:"Y-m-d"

用户更新

写法跟部门一样,不同的是性别和部门的下拉框

用户新增

一样,唯一不同的是新增时,性别和部门属性是下拉框

完整前端代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>管理系统</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css"
          integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">

    <!-- 可选的 Bootstrap 主题文件(一般不用引入) -->
    <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap-theme.min.css"
          integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous">

    <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"
            integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd"
            crossorigin="anonymous"></script>


    <style>
        .navbar {
            border-radius: 0;
        }
    </style>
    <!-- 针对搜索输入框的额外样式(可选) -->
    <style>
        #searchInput {
            margin-bottom: 10px;
        }

        .table tbody tr[style="display: none;"] {
            display: none !important;
        }
    </style>

</head>
<body>
<!-- Modal -->
<div class="modal fade" id="myModal1" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h4 class="modal-title" id="myModalLabel">提示信息</h4>
            </div>
            <div class="modal-body">
                操作成功!
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
            </div>
        </div>
    </div>
</div>

<nav class="navbar navbar-default">
    <div class="container">
        <!-- Brand and toggle get grouped for better mobile display -->
        <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="#">管理系统</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li><a href="/depart/list">部门管理</a></li>
                <li class="active"><a href="/departuser/list">用户管理 <span class="sr-only">(current)</span></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">用户 <span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        <li><a href="#">注销</a></li>

                    </ul>
                </li>
            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>

<div class="container mt-4">
    <!-- 模态框 -->
    <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>
                        <div class="form-group">
                            <label for="add_username">用户名</label>
                            <input type="text" class="form-control" id="add_username" placeholder="请输用户名称">
                            <label for="add_pwd">密码</label>
                            <input type="text" class="form-control" id="add_pwd" placeholder="请输入密码">
                            <label for="add_age">年龄</label>
                            <input type="text" class="form-control" id="add_age" placeholder="请输入年龄">
                            <label for="add_gender">性别</label>
                            <select class="form-control" id="add_gender">
                                <option value="1"></option>
                                <option value="2"></option>
                            </select>
                            <label for="add_depart">部门</label>
                            <select class="form-control" id="add_depart">
                                {% for depart in depart_list %}
                                    <option value="{{ depart.id }}">{{ depart.name }}</option>
                                {% endfor %}
                            </select>
                        </div>
                        <!-- 在这里可以添加其他表单字段 -->
                        <label for="add_account">薪资</label>
                        <input type="text" class="form-control" id="add_account" placeholder="请输入薪资">
                    </form>
                </div>

                <!-- 模态框底部 -->
                <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
                    <button type="button" class="btn btn-primary">保存</button>
                </div>
            </div>
        </div>
    </div>

    <div style="margin-bottom: 10px;">
        <button type="button" class="btn btn-success" data-toggle="modal" data-target="#myModal">新建用户</button>
    </div>

    <div class="panel panel-default">
        <!-- Default panel contents -->
        <div class="panel-heading">
            <span class="glyphicon glyphicon-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 user in users %}
                <tr>
                    <th scope="row">{{ user.id }}</th>
                    <td class="editable-cell name">{{ user.name }}</td>
                    <td class="editable-cell age">{{ user.age }}</td>
                    <td class="editable-cell gender">{{ user.get_gender_display }}</td>
                    <td class="editable-cell departname">{{ user.department.name }}</td>
                    <td class="editable-cell account ">{{ user.account }}</td>
                    <td class="editable-cell createtime">{{ user.createtime | date:"Y-m-d" }}</td>

                    <td>
                        <button type="button" class="btn btn-primary btn-xs edit-btn"
                                data-department-id="{{ user.id }}">编辑
                        </button>
                        <a href="#" class="btn btn-danger btn-xs" onclick="deleteDepartment({{ user.id }})">删除</a>
                        <button type="button" class="btn btn-success btn-xs save-btn"
                                data-department-id="{{ user.id }}" style="display: none;">保存
                        </button>
                    </td>

                </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>

</div>

</body>

<script>
    //https://blog.csdn.net/qq_21649903/article/details/129022356
    // using jQuery
    function getCookie(name) {
        let cookieValue = null;
        if (document.cookie && document.cookie !== '') {
            let cookies = document.cookie.split(';');
            for (let i = 0; i < cookies.length; i++) {
                let cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) === (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }

    let csrftoken = getCookie('csrftoken');

    function csrfSafeMethod(method) {
        // these HTTP methods do not require CSRF protection
        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }

    $.ajaxSetup({
        beforeSend: function (xhr, settings) {
            if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("X-CSRFToken", csrftoken);
            }
        }
    });

    $(document).ready(function () {
        // 绑定保存按钮点击事件
        $.ajaxSetup({
            data: {csrfmiddlewaretoken: '{{ csrf_token }}'}
        });

        $(".modal-footer .btn-primary").click(function () {
            // 获取表单中的值
            var username = document.getElementById('add_username').value;
            var password = document.getElementById('add_pwd').value;
            var age = document.getElementById('add_age').value;
            var gender = document.getElementById('add_gender').value;
            var department = document.getElementById('add_depart').value;
            var account = document.getElementById('add_account').value;


            // 发送请求
            $.ajax({
                url: "/departuser/add/",  // 替换为您的API接口地址
                type: "POST",  // 根据需要选择请求类型,比如POST、GET等
                data: {
                    username: username,
                    password: password,
                    age: age,
                    gender: gender,
                    department: department,
                    account: account
                },
                success: function (response) {
                    $('#myModal').modal('hide');
                    // 显示模态框
                    $('#myModal1 .modal-body').text('操作成功!');
                    $('#myModal1').modal('show');
                    // 设置定时器,2秒后自动关闭模态框
                    setTimeout(function () {
                        $('#myModal1').modal('hide');
                        window.location.reload(); // 如果需要的话,重新加载页面
                    }, 1500);

                },
                error: function (xhr, status, error) {
                    $('#myModal').modal('hide');
                    // 显示模态框
                    $('#myModal1 .modal-body').text('添加失败');
                    $('#myModal1').modal('show');
                    // 设置定时器,2秒后自动关闭模态框
                    setTimeout(function () {
                        $('#myModal').modal('hide');
                        window.location.reload(); // 如果需要的话,重新加载页面
                    }, 1500);

                }
            });
        });
    });

    function deleteDepartment(userid) {
        if (confirm("确定要删除该用户吗?")) {
            // 发送删除请求
            $.ajax({
                url: "/departuser/delete/" + userid,
                type: "DELETE",
                success: function (response) {
                    $('#myModal').modal('hide');
                    // 显示模态框
                    $('#myModal1 .modal-body').text('删除成功!');
                    $('#myModal1').modal('show');
                    // 设置定时器,2秒后自动关闭模态框
                    setTimeout(function () {
                        $('#myModal1').modal('hide');
                        window.location.reload(); // 如果需要的话,重新加载页面
                    }, 1500);
                },
                error: function (xhr, status, error) {
                    $('#myModal').modal('hide');
                    // 显示模态框
                    $('#myModal1 .modal-body').text('删除失败!');
                    $('#myModal1').modal('show');
                    // 设置定时器,2秒后自动关闭模态框
                    setTimeout(function () {
                        $('#myModal1').modal('hide');
                        window.location.reload(); // 如果需要的话,重新加载页面
                    }, 1500);
                }
            });
        }
    }

    $(document).ready(function () {
        // 绑定编辑按钮点击事件
        $(".edit-btn").click(function () {
            var departmentId = $(this).data("department-id");
            var editableCell = $(this).closest("tr").find(".editable-cell");
            var saveBtn = $(this).closest("tr").find(".save-btn");

            // 切换编辑状态
            editableCell.attr("contenteditable", "true");
            editableCell.addClass("editing");

            // 显示保存按钮
            saveBtn.show();
        });

        // 绑定保存按钮点击事件
        $(".save-btn").click(function () {
            var editableCell = $(this).closest("tr").find(".editable-cell");

            var departmentId = $(this).data("department-id");
            var depart_name = $(this).closest("tr").find(".editable-cell.name");
            var depart_age = $(this).closest("tr").find(".editable-cell.age");
            var depart_gender = $(this).closest("tr").find(".editable-cell.gender");
            var depart_account = $(this).closest("tr").find(".editable-cell.account");
            var depart_createtime = $(this).closest("tr").find(".editable-cell.createtime");
            var saveBtn = $(this);

            // 获取编辑后的部门名称
            var username = depart_name.text().trim();
            var age = depart_age.text().trim();
            var gender = depart_gender.text().trim();
            var account = depart_account.text().trim();
            var createtime = depart_createtime.text().trim();
            $.ajax({
                url: "/departuser/update/" + departmentId,  // 替换为您的API接口地址
                type: "PUT",  // 根据需要选择请求类型,比如POST、GET等
                data: JSON.stringify({
                    "username": username,
                    "age": age,
                    "gender": gender,
                    "account": account,
                    "createtime": createtime
                }),
                contentType: "application/json",
                success: function (response) {
                    // 显示模态框                    $('#myModal').modal('hide');

                    $('#myModal1 .modal-body').text('更新成功!');
                    $('#myModal1').modal('show');
                    // 设置定时器,2秒后自动关闭模态框
                    setTimeout(function () {
                        $('#myModal1').modal('hide');
                        // 切换回非编辑状态
                        editableCell.attr("contenteditable", "false");
                        editableCell.removeClass("editing");

                        // 隐藏保存按钮
                        saveBtn.hide();
                        window.location.reload(); // 如果需要的话,重新加载页面
                    }, 1500);
                },
                error: function (xhr, status, error) {
                    $('#myModal').modal('hide');
                    // 显示模态框
                    $('#myModal1 .modal-body').text('更新失败!');
                    $('#myModal1').modal('show');
                    // 设置定时器,2秒后自动关闭模态框
                    setTimeout(function () {
                        $('#myModal1').modal('hide');
                        window.location.reload(); // 如果需要的话,重新加载页面
                    }, 1500);
                }
            });
        });
    });

</script>
</html>

基本实现了增删改查的功能,但是还存在着许多问题

8.表单处理

问题
  • 原始方式理思路,不会采用
- 用户提交数据没有校验
- 错误,页面应该有错误提示
- 页面上,每一个字段都要重新写一遍
- 关联的数据,手动取获取并循环展示在页面
  • Django组件
    • Form组件(小便捷)
    • ModelForm(最简便)
Form组件

通常在编写html过程中,需要手动编写form标签和其他元素,数据校验比较麻烦,Django内部集成表单模块

功能

  • 准备和重组数据,用于页面渲染
  • 为数据创建HTML表单元素
  • 接受和处理用户从客户端提交的表单数据【有点类似Spring的@RequestBody 实体类】

基本语法

表单类的书写

class UserModelForm(forms.ModelForm):
    name = forms.CharField(label="姓名", min_length=3)
    password = forms.CharField(label="密码", max_length=6)

    class Meta:
        model = UserInfo
        fields = ["name", "age", "gender", "account", "password", "department"]

Form类的namepassword会验证数据,还会转换数据格式

视图函数

def form_userlist(request):
    """Model Form版本。列出所有用户"""
    from app1.myform import UserModelForm
    model_form = UserModelForm()
    return render(request, 'form_userlist.html', {'model_form': model_form})

将UserModelForm对象传到页面,这个过程中,ModelForm会帮我们生成表单

部分页面

Django 使用 CSRF Token(Cross-Site Request Forgery Token) 保护所有的 POST 请求。这是一个避免外部站点或者应用程序向我们的应用程序提交数据的安全措施。应用程序每次接收一个 POST 时,都会先检查 CSRF Token。如果这个 request 没有 token,或者这个 token是无效的,它就会抛弃提交的数据。

<form>
    {% csrf_token %}
    {{ model_form.name }}
    {{ model_form.age }}
    {{ model_form.gender }}
</form>

如果属性比较多,写起来比较麻烦,可以才采用如下写法

<form method="post">
    {% csrf_token %}
    {% for user in model_form %}
        <label>{{ user.label }}</label>
        {{ user }}
        <span>{{ user.errors.0 }}</span>
    {% endfor %}
    <button type="submit">提交</button>
</form>

user.label取的值如 forms.CharField(label="姓名", min_length=3)中的label

user.erros.0作为表单校验

查看源代码

<label>账户余额</label>
<input type="number" name="account" value="0" step="0.01" required id="id_account">
<span></span>

<label>密码</label>
<input type="text" name="password" maxlength="6" required id="id_password">
<span></span>

<label>所属部门</label>
<select name="department" id="id_department">
    <option value="" selected>---------</option>
    <option value="12">121</option>
    <option value="13">121</option>
    <option value="17">人事部</option>
    <option value="18">89898889</option>
    <option value="20">后勤部</option>
    <option value="21">开发部</option>
    <option value="22">开发部</option>
    <option value="23">测试部162</option>
    <option value="24">后勤部</option>
    <option value="25">xxx</option>
</select>
<span></span>
<button type="submit">提交</button>
</form>

可以看到ModelForm帮我们生成了表单HTML

如果要给表单添加样式

class UserModelForm(forms.ModelForm):
    name = forms.CharField(label="姓名", min_length=3)
    password = forms.CharField(label="密码", max_length=6)

    class Meta:
        model = UserInfo
        fields = ["name", "age", "gender", "account", "password", "department"]
        # 增加样式,前后端混合了,oh my god
        widgets = {
            "name": forms.TextInput(attrs={"class": "form-control"}),
            "age": forms.EmailInput(attrs={"class": "form-control"}),
            # "gender": forms.PasswordInput(attrs={"class": "form-control"}),
            "account": forms.TextInput(attrs={"class": "form-control"}),
            "password": forms.PasswordInput(attrs={"class": "form-control"}),
            "department": forms.TextInput(attrs={"class": "form-control"}),
        }

如果要提交表单数据,则视图函数如下

def form_add(request):
    """Model Form版本。添加用户"""
    from app1.myform import UserModelForm
    model_form = UserModelForm()
    if request.method == 'GET':
        return render(request, 'form_userlist.html', {'model_form': model_form})

    if request.method == 'POST':
        model_form = UserModelForm(data=request.POST)
        if model_form.is_valid():
            model_form.save()

UserModelForm(data=request.POST)将表单与数据库字段做个转换,这就是ModelForm的作用

.is_valid()用于校验数据

.save()实际是插入一条新的数据

编辑用户提交表单的时候,如果想更新数据而不是新增

if request.method == 'POST':
    user_id = models.UserInfo.objects.filter(id=request.POST.get('id')).first()
    model_form = UserModelForm(data=request.POST, instance=user_id)
    if model_form.is_valid():
    	model_form.save()

9.靓号管理

接下来再增加一张靓号的表

models.py

class PrettyNum(models.Model):
    id = models.CharField(max_length=32, verbose_name='编号', primary_key=True)
    moblie = models.CharField(max_length=11, verbose_name='手机号')
    price = models.DecimalField(verbose_name='价格', decimal_places=2, max_digits=10, default=0)

    choices_level = (
        (1, '1级'),
        (2, '2级'),
        (3, '3级')
    )
    level = models.SmallIntegerField(verbose_name='等级', choices=choices_level, default=1)

    choices_status = (
        (1, '待审核'),
        (2, '审核通过')
    )
    status = models.SmallIntegerField(verbose_name='状态', choices=choices_status, default=1)

编写增删改查视图函数

@require_http_methods(['GET'])
def pretty_list(request):
	"""查询所有靓号"""
    if request.method == 'GET':
        # from app1.prettyform import PrettyForm
        # model_form = PrettyForm()
        pretty_obj = models.PrettyNum.objects.all()
        levels = models.PrettyNum.choices_level
        status = models.PrettyNum.choices_status

        return render(request, 'pretty_list.html',
                      {
                          'pretty_obj': pretty_obj,
                          'levels': levels,
                          'status': status})


@require_http_methods(['POST'])
def pretty_add(request):
    try:
        objects_filter = models.PrettyNum.objects.filter(moblie=request.POST.get('moblie'))
        if objects_filter.exists():
            return render(request, 'pretty_list.html', {'error_msg': '手机号已存在'})

        model_form = PrettyForm(data=request.POST)
        if model_form.is_valid():
            instance = model_form.save(commit=False)
            random_uuid = uuid.uuid4()
            random_id = str(random_uuid)[:32]
            instance.id = random_id
            instance.save()
            return HttpResponse('添加成功', status=200)
        else:
            # Form is not valid, return an HttpResponse with error message
            return HttpResponse('表单数据验证失败', status=400)
    except forms.ValidationError as e:
        # Handle the form validation error
        return HttpResponse(str(e), status=400)
    except Exception as e:
        # Handle other exceptions
        return HttpResponse('添加失败: {}'.format(str(e)), status=500)


@require_http_methods(['DELETE'])
def pretty_delete(request, pretty_id):
    if request.method == 'DELETE':
        try:
            pretty_instace = get_object_or_404(models.PrettyNum, id=pretty_id)
            pretty_instace.delete()
            return HttpResponse('删除成功', status=200)
        except models.PrettyNum.DoesNotExist:
            return HttpResponse('用户不存在', status=404)
    else:
        return HttpResponse('无效的请求方法', status=405)


@require_http_methods(['PUT'])
def pretty_update(request, pretty_id):
    try:
        if request.method == 'PUT':
            pretty_instace = get_object_or_404(models.PrettyNum, id=pretty_id)

            # PUT请求的数据通常包含在请求的主体中
            try:
                body_data = json.loads(request.body)
            except json.JSONDecodeError as e:
                return HttpResponse('无效的JSON数据', status=400)

            from app1.prettyform import PrettyForm
            pretty_instace = models.PrettyNum.objects.filter(id=pretty_id).first()
            form = PrettyForm(body_data, instance=pretty_instace)
            if form.is_valid():
                form.save()
                return HttpResponse('修改成功', status=200)
    except forms.ValidationError as e:
        return HttpResponse(e.message, status=400)

注意

model_form.save(commit=False):

  • Django 模型表单的 save 方法用于将表单数据保存到关联的模型。通过将 commit=False 作为参数传递,它防止数据立即保存到数据库

get_object_or_404(models.PrettyNum, id=pretty_id):

  • Django 的 get_object_or_404 函数,它尝试从数据库中获取指定模型(PrettyNum)的实例,通过指定的 id 进行筛选。如果找不到具有给定条件的实例,它将引发 HTTP 404 异常,表示未找到资源。

form = PrettyForm(body_data, instance=pretty_instace):

  • 创建 PrettyForm 表单实例,使用请求的数据和获取的模型实例进行初始化

增加搜索

前端代码

 <div class="search-bar" style="float: right;width: 300px;">
     <form class="form-inline" method="get">
         <div class="input-group">
         <input type="text" name="key" class="form-control" placeholder="输入手机号">
         <span class="input-group-btn">
         <button class="btn btn-default" type="submit">搜索</button>
         </span>
         </div>
     </form>
 </div>

请求方式为get,请求资源为本地提交

视图函数

表单是GET提交方式,所以仍然是查询接口,修改

@require_http_methods(['GET'])
def pretty_list(request):
    if request.method == 'GET':
        moblie = request.GET.get('key', '').strip()
        pretty_obj = models.PrettyNum.objects.filter(moblie=moblie)
        if not pretty_obj:
            pretty_obj = models.PrettyNum.objects.all()

        levels = models.PrettyNum.choices_level
        status = models.PrettyNum.choices_status

        context = {'pretty_obj': pretty_obj,
                   'levels': levels,
                   'key': request.GET.get('key', ''),
                   'status': status}

        return render(request, 'pretty_list.html', context)

其中,通过Request对象拿到搜索关键字,并检索返回,再渲染页面时将关键字key一并返回,为了在页面搜索框中显示搜索关键字,并且用来后面的搜索后的分页

搜索前端页面

  <div class="search-bar" style="float: right;width: 300px;">
          <form class="form-inline" method="get">
              <div class="input-group">
                  <input type="text" name="key" class="form-control" value="{{ key }}"
                  placeholder="输入手机号">
                  <span class="input-group-btn">
                  <button class="btn btn-default" type="submit">搜索</button>
                  </span>
                 </div>
          </form>
  </div>

{{key}} 在搜索后持续显示关键字

10.分页

首先采用最原始不用自带模板

修改展示视图函数

@require_http_methods(['GET'])
def pretty_list(request):
    if request.method == 'GET':
        moblie = request.GET.get('key', '').strip()
        pretty_obj = models.PrettyNum.objects.filter(moblie=moblie)
        if not pretty_obj:
            pretty_obj = models.PrettyNum.objects.all()

        from app1.utils.pageutils import pagepaging
        pageobject = pagepaging(request, pretty_obj, moblie)
        pretty_obj = pretty_obj[pageobject.start:pageobject.end]

        levels = models.PrettyNum.choices_level
        status = models.PrettyNum.choices_status

        context = {'pretty_obj': pretty_obj,
                   'levels': levels,
                   'key': request.GET.get('key', ''),
                   'status': status, 'pagestring': pageobject.pagestring}

        return render(request, 'pretty_list.html', context)

将分页功能抽取出来

-- utils

​ -pageutils.py

from django.utils.safestring import mark_safe


class pagepaging(object):
    """
    https://www.codenong.com/p12370258/
    """

    def __init__(self, request, pretty_obj, key, page='page', items_per_page=10):
        self.current_page = int(request.GET.get(page, 1))
        self.items_per_page = items_per_page
        start = (self.current_page - 1) * items_per_page
        self.start = start
        self.end = start + items_per_page
        total, div = divmod(pretty_obj.count(), items_per_page)
        if div:
            total += 1
        self.total = total
        self.key = key
        pre_page_start = self.current_page - 5
        next_page_end = self.current_page + 5
        if pre_page_start <= 0:
            pre_page_start = 1
        if next_page_end > total:
            next_page_end = total
        self.pre_page_start = pre_page_start
        self.next_page_end = next_page_end
        self.pre_page = self.current_page - 1 if self.current_page - 1 > 0 else 1
        self.next_page = self.current_page + 1 if self.current_page + 1 <= total else total

        self.pagestring = mark_safe(''.join(self.get_pagestring()))

    def get_pagestring(self):
        pagelist = []
        pagelist.append(f'<li><a href="?page=1&key={self.key}">首页</a></li>')
        pagelist.append(f'<li><a href="?page={self.pre_page}&key={self.key}"><<</a></li>')
        for i in range(self.pre_page_start, self.next_page_end + 1):

            if i == self.current_page:
                elem = f'<li class="active"><a href="?page={i}&key={self.key}">{i}</a></li>'
            else:
                elem = f'<li><a href="?page={i}&key={self.key}">{i}</a></li>'
            pagelist.append(elem)
        pagelist.append(f'<li><a href="?page={self.next_page}&key={self.key}">>></a></li>')
        pagelist.append(f'<li><a href="?page={self.total}&key={self.key}">尾页</a></li>')

        return pagelist

前端

                <!-- 显示分页导航 -->
                <nav aria-label="Page navigation">
                    <ul class="pagination">
                        {{ pagestring|safe }}
                    </ul>
                    <ul class="pagination">
                        <form class="form-inline skip">
                            <div class="row">
                                <div class="col-xs-8 col-sm-9 col-md-10">
                                    <div class="form-group">
                                        <label for="pageInput">跳转到:</label>
                                        <input type="text" class="form-control hidden" value="{{ key }}" name="key">
                                        <input type="text" class="form-control" name="page">
                                    </div>
                                </div>
                                <div class="col-xs-4 col-sm-3 col-md-2">
                                    <button type="submit" class="btn btn-default">Go</button>
                                </div>
                            </div>
                        </form>
                    </ul>
                </nav>

主要是计算页码,并设置返回的页面数,然后将数据渲染到页面中,返回页面'pagestring': pageobject.pagestring

Django模板 ,safe 过滤器用于指示内容应该被视为安全的HTML,不应该被转义 , {{ pagestring|safe }}

问题

点击搜索后,翻页内容应是搜索内容

在页面提交请求的时候带上搜索关键字,如下

<li><a href="?page={self.pre_page}&key={self.key}"><<</a></li>

每次从Request中取出参数并拼接到li

moblie = request.GET.get('key', '').strip()

优化,希望将所有的参数都带入

print(request.GET)
print(request.GET.urlencode)

<QueryDict: {'page': ['30'], 'key': ['']}>
page=30&key=

urlencode方法主要是对查询的参数进行了特定形式的拼接

优化后代码

在通用分页中添加

query_dict = copy.deepcopy(request.GET)
query_dict._mutable = True # 使得request.GET对象可以修改
self.query_dict = query_dict

修改页面分页展示

    def get_pagestring(self):
        pagelist = []
        self.query_dict.setlist('page', [self.pre_page_start])
        pagelist.append(f'<li><a href="?{self.query_dict.urlencode()}">首页</a></li>')
        self.query_dict.setlist('page', [self.pre_page])
        pagelist.append(f'<li><a href="?{self.query_dict.urlencode()}"><<</a></li>')
        for i in range(self.pre_page_start, self.next_page_end + 1):
            self.query_dict.setlist('page', [i])
            if i == self.current_page:
                elem = f'<li class="active"><a href="?{self.query_dict.urlencode()}">{i}</a></li>'
            else:
                elem = f'<li><a href="?{self.query_dict.urlencode()}">{i}</a></li>'
            pagelist.append(elem)

        self.query_dict.setlist('page', [self.next_page])
        pagelist.append(f'<li><a href="?{self.query_dict.urlencode()}">>></a></li>')
        self.query_dict.setlist('page', [self.total])
        pagelist.append(f'<li><a href="?{self.query_dict.urlencode()}">尾页</a></li>')

        return pagelist

这样,不管前端传入什么参数,后端渲染后总会带上,就不用手动一个个添加

11.中间件

简介

中间件是 Django 请求/响应处理的钩子框架。它是一个轻量级的、低级的“插件”系统,用于全局改变 Django 的输入或输出。

每个中间件组件负责做一些特定的功能。例如,Django 包含一个中间件组件 AuthenticationMiddleware,它使用会话将用户与请求关联起来

在请求阶段,在调用视图之前,Django 按照定义的顺序应用中间件 MIDDLEWARE,自顶向下。

你可以把它想象成一个洋葱:每个中间件类都是一个“层”,它覆盖了洋葱的核心。如果请求通过洋葱的所有层(每一个调用 get_response )以将请求传递到下一层,一直到内核的视图,那么响应将在返回的过程中通过每个层(以相反的顺序)。

如果其中一层决定停止并返回响应而不调用get_response,那么该层(包括视图)中的洋葱层都不会看到请求或响应。响应将只通过请求传入的相同层返回。

image-20240103105747867

测试

示例

from django.utils.deprecation import MiddlewareMixin
from django.http import HttpResponse
 
 
class TestMiddleware(object):
    '''中间件类'''
    def __init__(self,request):
        '''服务器重启之后,接收第一个请求时调用'''
        print('----init----')
 
    def process_request(self, request):
        '''产生request对象之后,url匹配之前调用'''
        print('----process_request----')
        # return HttpResponse('process_request')
 
    def process_view(self, request, view_func, *view_args, **view_kwargs):
        '''url匹配之后,视图函数调用之前调用'''
        print('----process_view----')
        return HttpResponse('process_view')
 
    def process_response(self, request, response):
        '''视图函数调用之后,内容返回浏览器之前'''
        print('----process_response----')
        return response

app1/middleware/auth_middleware.py

from django.utils.deprecation import MiddlewareMixin


class Middleware1(MiddlewareMixin):

    def process_request(self, request):
        print('m1开始')

    def process_response(self, request, response):
        print('m1结束')
        return response


class Middleware2(MiddlewareMixin):

    def process_request(self, request):
        print('m2开始')

    def process_response(self, request, response):
        print('m2结束')
        return response

激活中间件

settings.py

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'app1.middleware.auth_middleware.Middleware1', # 中间件1
    'app1.middleware.auth_middleware.Middleware2' # 中间件2
]

上述,中间件的执行顺序跟随列表顺序

重启项目,浏览器访问登陆页面

m1开始
m2开始
m2结束
m1结束
[03/Jan/2024 11:46:33] "GET /login/ HTTP/1.1" 200 2708

用户会话

app1/middleware/auth_middleware.py

import re
from django.shortcuts import redirect, HttpResponse

from django.utils.deprecation import MiddlewareMixin

exclued_path = ['/login/', '/logout/']


class AuthMiddleWare(MiddlewareMixin):
    """
    Middleware to authenticate user by token.
    """

    def process_request(self, request):
        url_path = request.path

        for each in exclued_path:
            if re.match(each, url_path):
                return

        if not request.session.get('is_login', None):
            return redirect('/login/')
        else:
            return

添加中间件

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'app1.middleware.auth_middleware.AuthMiddleWare' # 会话校验
]

登陆视图函数

def login(request):
    if request.method == 'GET':
        form = AdminForm()
        return render(request, 'login.html', {'form': form})

    form = AdminForm(request.POST)

    if not form.is_valid():
        form.add_error('name', '输入格式错误')
        return render(request, 'login.html', {'form': form})

    query_set = Admin.objects.get(**form.cleaned_data)

    if not query_set:
        form.add_error('name', '用户名或则密码错误')
        return render(request, 'login.html', {'form': form})

    request.session['is_login'] = {'id': query_set.id, 'name': query_set.name}
    return render(request, 'index.html', {'username': query_set.name})

注销视图函数

def logout(request):
    request.session.clear()
    return redirect('/login/')

参考

https://www.bilibili.com/video/BV1NL41157ph/

posted on   匿名者nwnu  阅读(162)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类
点击右上角即可分享
微信分享提示