lolicute

导航

django学习笔记

第一章 Web框架和Django基础

1.web框架

Web框架(Web framework)或者叫做Web应用框架(Web application framework),是用于进行Web开发的一套软件架构。大多数的Web框架提供了一套开发和部署网站的方式。为Web的行为提供了一套支持支持的方法。使用Web框架,很多的业务逻辑外的功能不需要自己再去完善,而是使用框架已有的功能就可以。

1.1 Web框架的功能

Web框架使得在进行Web应用开发的时候,减少了工作量。Web框架主要用于动态网络开发,动态网络主要是指现在的主要的页面,可以实现数据的交互和业务功能的完善。使用Web框架进行Web开发的时候,在进行数据缓存数据库访问数据安全校验等方面,不需要自己再重新实现,而是将业务逻辑相关的代码写入框架就可以。也就是说,通过对Web框架进行主观上的“缝缝补补”,就可以实现自己进行Web开发的需求了。

常见的web框架:django、flask、tornado、sanic、fastapi..

image-20220619111206007
web application`通信的规范。是Web服务器和Web应用程序之间或框架之间的通用接口标准

  • wsgiref

    wsgiref则是官方给出的一个实现了WSGI标准用于演示用的简单Python内置库,它实现了一个简单的WSGI Server和WSGI Application(在simple_server模块中),主要分为五个模块:simple_server, util, headers, handlers, validate。本质上就是编写一个socket服务端,用于接收用户请求(django)

from wsgiref.simple_server import make_server


def run_server(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ]


if __name__ == '__main__':
    httpd = make_server('127.0.0.1', 8000, run_server)
    httpd.serve_forever()
  • werkzeug

    erkzeug 不是一个web服务器,也不是一个web框架,而是一个工具包,它为Python Web应用程序的开发提供了许多有用的功能。它包括请求和响应处理、中间件、调试器等,可以帮助我们快速开发Web应用程序。Werkzeug是Flask框架的核心组件之一,但也可以独立使用。

pip install werkzeug
from werkzeug.wrappers import Response


def application(environ, start_response):
    response = Response('Hello World!', mimetype='text/plain')
    return response(environ, start_response)


if __name__ == '__main__':
    from werkzeug.serving import run_simple

    run_simple('localhost', 4000, application)

image-20220619112446494

1.2 各框架的区别

django、flask、tornado、sanic、fastapi..
  • 内部集成功能的多少

    • django,内部提供了很多组件。 【相对大】
    • flask、tornado、sanic、fastapi... 本身自己功能很少+第三方组件。【相对小】
  • 同步框架 vs 异步非阻塞

    • 异步非阻塞:tornado、sanic、fastapi、django

    • 同步:django、flask、bottle、webpy..

      1.django、flask
      2.tornado,异步非阻塞,特别NB。
      	- 同步:常见应用。
      	- 异步:IO应用 + conroutine装饰器 + redis/MySQL/...
      3.sanic,路飞小猿圈平台
      4.fastapi
      	- 参考flask
      	- py最新注解
      	- restfulAPI
      	- 异步
      	
      目前不看好:
      	- 增加编程的难度,功能&效率
      	- 项目中不会有那么IO操作 ---> 100功能/2-IO ---> celery
      

image-20220619120944641
image-20220619122238391

2.快速上手django框架

2.1 安装

pip install django==3.2
C:\Python39
	- python.exe
	- Scripts
		- pip.exe
		- django-admin.exe
	- Lib
		- re.py
		- random.py
		- site-pakages
			- django==3.2
			  ...

2.2 命令行

  • 创建项目

    cd 指定目录
    django-admin startproject 项目名
    

    image-20220619144658273
    image-20220619144642767

    mysite
    ├── manage.py              [项目的管理工具]  
    └── mysite
        ├── __init__.py
        ├── settings.py        【配置文件,只有一部分。程序启动时,先读取django内部配置,再读settings.py】
        ├── urls.py			   【主路由,在里面编写  /xxx/xxx/xxx ---> index 】
        ├── asgi.py            【异步】
        └── wsgi.py            【同步,主】
    
  • 编写代码 urls.py

    from django.contrib import admin
    from django.urls import path
    
    from django.shortcuts import HttpResponse
    
    def info(request):
        print("请求来执行了")
        return HttpResponse("xxxx")
    
    def xxxx(request):
        print("请求来执行了")
        return HttpResponse("。。。。。。")
    
    urlpatterns = [
        # path('admin/', admin.site.urls),
        path('api/index/', info),
        path('api/show/', xxxx),
    ]
    
  • 运行

    cd 项目
    python3.9 manage.py runserver
    python3.9 manage.py runserver 127.0.0.1:8000
    python3.9 manage.py runserver 127.0.0.1:9000
    
  • app概念

    cd 项目
    python manage.py startapp 名字
    
    mysite
    ├── manage.py              [项目的管理工具]  
    ├── web
        ├── __init__.py
        ├── views.py           [视图函数]
        ├── models.py          [ORM,基于models可以对数据库进行简便的操作]
        ...
    └── mysite
        ├── __init__.py
        ├── settings.py        【配置文件,只有一部分。程序启动时,先读取django内部配置,再读settings.py】
        ├── urls.py			   【主路由,在里面编写  /xxx/xxx/xxx ---> index 】
        ├── asgi.py            【异步】
        └── wsgi.py            【同步,主】
    
    mysite
    ├── manage.py
    ├── mysite
    │   ├── __init__.py
    │   ├── asgi.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    └── web
        ├── __init__.py
        ├── admin.py
        ├── apps.py
        ├── migrations
        │   └── __init__.py
        ├── models.py
        ├── tests.py
        └── views.py
    

2.3 Pycharm

image-20220619152319131
image-20220619152635594

cd 项目目录
python manage.py startapp
python manage.py runserver

3.虚拟环境

3.1 创建虚拟环境 - 命令行

  • venv,Python官方用于创建虚拟环境的工具。

    cd xxx/xxx/crm
    python3.9 -m venv ddd
    python3.7 -m venv xxxx
    python3.7 -m venv /xxx/xxx/xxx/xx/ppp
    
  • virtualenv 【推荐】

    pip install virtualenv
    
    cd /xxx/xx/
    virtualenv ddd --python=python3.9
    
    virtualenv /xxx/xx/ddd --python=python3.7
    

操作:

  • F:\envs\ 创建虚拟环境。

    cd F:\envs
    virtualenv crm --python=python3.9
    

image-20220619154057087

  • 激活虚拟环境

    • win

      cd F:\envs\crm\Scripts
      activate
      
    • mac

      source /虚拟环境目录/bin/activate
      

image-20220619154311091

  • 安装包

    pip install 包名
    
  • 创建django项目 D:\project\crm

    cd D:\project
    django-admin startproject crm
    
    D:\project\crm
    ├── manage.py              [项目的管理工具]  
    └── crm
        ├── __init__.py
        ├── settings.py        【配置文件,只有一部分。程序启动时,先读取django内部配置,再读settings.py】
        ├── urls.py			   【主路由,在里面编写  /xxx/xxx/xxx ---> index 】
        ├── asgi.py            【异步】
        └── wsgi.py            【同步,主】
    
    python manage.py startapp xxxx
    python manage.py runserver 
    
  • 退出虚拟环境

    deactivate
    

image-20220619154739419

3.2 Pycharm

3.2.1 项目+虚拟环境

image-20220619161448289

image-20220619161630875

你需要做的:

  • 创建项目+虚拟环境

  • code.py,写代码运行

  • 在虚拟环境中安装 requests

    pip install requests
    

3.2.2 django+虚拟环境【最新】

pip install django

image-20220619165608097

注意:创建django最新版可以。

3.2.3 django+虚拟环境【指定版本】

image-20220619165825674

pip install django==3.2

image-20220619170104365

image-20220619171603578

image-20220619171631057

image-20220619171758949

image-20220619171835577

4.关于创建app

  • 项目只需要一个app,目录机构的建议。

    python manage.py startapp xxxx .
    python manage.py runserver 
    

image-20220619173934518

  • 项目需要多个app,目录结构的建议。

    day002
    	.venv
        day002
        	...
            ...
        manage.py
        apps
        	web
            backend
            api
    

image-20220619174747333

python manage.py startapp xxxx apps/xxxx

5.关于纯净版

image-20220619175853145

INSTALLED_APPS = [
    # 'django.contrib.admin', #内置的后台系统;
    # 'django.contrib.auth', #内置的用户认证系统;
    # 'django.contrib.contenttypes',#记录项目中所有model元数据(Django的ORM框架);
    # 'django.contrib.sessions',#Session会话功能,用于标识当前访问网站的用户身份,记录相关用户信息;
    # 'django.contrib.messages',#消息提示功能;
    'django.contrib.staticfiles',#查找静态资源路径;
]

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',
]

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                # 'django.contrib.auth.context_processors.auth',
                # 'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

6.django启动方法(pycharm)

1.在Django界面右上角找到运行框,点击文件展开(就是那个朝下的三角形),再点如图的Edit Configurations...

img

2.点击弹出界面左上角的加号+

img

3.找到Python的选项

img

4.选择完Python后,在你新建的Python运行设置里,找到如图的Script path位置,选中你Django项目中的manage.py文件

img

5.再在下面的Parameters栏里面写入runserver

img

6.设置完成,点击右下角的OK

img

7.之后点击框中的绿色右三角就可以运行Django项目啦。

img

第二章 django必备知识点

1.路由系统

本质上:URL和函数的对应关系。

根据用户请求的 URL 链接来判断对应的处理程序,并返回处理结果,也就是 URL 与 Django 的视图建立映射关系。

1.1 传统的路由

from django.contrib import admin
from django.urls import path
from apps.web import views

urlpatterns = [
    path('home/', views.home),
    path('news/<int:nid>/edit/', views.news),
    path('article/', views.article),
]
from django.shortcuts import render, HttpResponse


def home(request):
    return HttpResponse("成功")


def news(request, nid):
    print(nid)
    page = request.GET.get("page")
    return HttpResponse("新闻")


def article(request):
    nid = request.GET.get("nid")
    print(nid)
    return HttpResponse("文章")
  • int,整数
  • str,字符串 /
  • slug,字母+数字+下滑线+-
  • uuid,uuid格式
  • path,路径,可以包含 /

1.2 正则表达式路由

  • 在django1版本用的多。
  • 在django2+版本用的少

image-20220626103534535

re_path("^index/([0-9]{4})/$", views.index)

1.3 路由分发

应用场景和意义,想要把一些URL前缀提取出来。

假如:200个功能。inlucde + app(一般),将功能拆分不到不同的app中。

image-20220626105027266

手动路由分发,可以与app无关。

path('user/add/', views.login),
path('user/delete/', views.login),
path('user/edit/', views.login),
path('user/list/', views.login),


path('user/', ([
                   path('add/', views.login),
                   path('delete/', views.login),   # /user/delete/
                   path('edit/', views.login),
                   path('list/', views.login),
               ], None, None)),

#纯粹帮助提取功能的URL,防止重复编写。include或手动元组列表,本质相同。

路由分发的本质:

  • URL对应函数

    path('user/add/', views.login),
    
  • URL对应元组

    path('user/add/',    (元素,appname元素,namespance元素)    ),
    
    path('user/add/',    include("apps.api.urls")    ), #include本质上也是元组
    path('user/add/',     ([],None,None)     ),
    

1.4 name

给一个路由起个名字 + 根据名字反向生成URL。

urlpatterns = [
    path('login/', views.login),
]
# 很多功能,很多URL
urlpatterns = [
    path('login/', views.login, name="v1"),
    path('auth/', views.auth, name="v2"),
]

有了名字后,以后一般有两处会用到:

  • 在视图函数中生成URL

    from django.urls import reverse
    url = reverse("v2")   # /auth/
    url = reverse("v1")   # /login/
    
    • 当url中存在动态参数时,如str:role、(?P\d+), 后面需要加上kwargs参数进行接收

      from django.urls import reverse
      url = reverse("v2",kwargs={"role":"hhhh"})  # /auth/
      print(url)
      url = reverse("v1",kwargs={"role":"pppp"})  # /login/
      print(url)
      
  • HTML模板,页面上有一个a标签,添加xx。

    <a href="/xxx/xxx/xx/">添加</a>
    
    <a href="{% url 'v1' %}">添加</a>
    <a href="{% url 'v2' %}">添加</a>
    
  • 扩展

    以后做权限管理,让name属性配合。
    

image-20220626142656240

image-20220626143129040

1.5 namespace

当一个项目中包含多个app,每个app中都有命名相同的标识符时,为了能够使每次访问都能得到想要的结果(访问指定变量),就需要使用namespace。

  • 主路由

    from django.urls import path, re_path, include
    
    # 很多功能,很多URL
    urlpatterns = [
        path('api/', include(("apps.api.urls","api"),namespace='x1')),
        path('web/', include(("apps.web.urls","web"),namespace='x2'))
    ]
    
  • api/urls.py

    from django.urls import path, re_path
    from . import views
    # 很多功能,很多URL
    urlpatterns = [
        path('login/', views.login,name="login"),
        path('auth/', views.auth, name='auth'),
    ]
    
    
  • web/urls.py

    from django.urls import path, re_path
    from . import views
    # 很多功能,很多URL
    urlpatterns = [
        path('home/', views.home,name='home'),
        path('order/', views.order,name='order'),
        path('auth/', views.order, name='auth'),
    ]
    

以后再某个URL或者视图中反向生成:

from django.urls import reverse
url = reverse("x1:login")    # /api/login/
url = reverse("x1:order")    # /web/login/

url = reverse("x1:auth")    # /api/login/
url = reverse("x2:auth")    # /web/login/

两个扩展:

  • 如果include中只有url地址参数,那么namespace需要设置app_name

    urlpatterns = [
        path('api/', include("apps.api.urls", namespace='x1')),
        #与include(("apps.api.urls","api"), namespace='x1')区分
    ]
    
    from django.urls import path, re_path
    from apps.api import views
    
    # 很多功能,很多URL
    urlpatterns = [
        path('login/', views.login, name="login"),
        path('auth/', views.auth, name='auth'),
    ]
    
    app_name = "api"
    
  • 手动分发
    image-20220626145020707

namespace多层嵌套:

image-20220626145624565

1.4 SLASH

URL当中最后的 / 要不要加?(是否能够重定向)

  • APPEND_SLASH = True
path('login/', views.login),
	http://127.0.0.1:8000/login/   成功

	http://127.0.0.1:8000/login    django自动做的,重定向301
	http://127.0.0.1:8000/login/   成功
path('login', views.login),
	http://127.0.0.1:8000/login    成功

	http://127.0.0.1:8000/login    
	http://127.0.0.1:8000/login/   失败#不带/,django就不能自动重定向
  • APPEND_SLASH = False (django不会自动加/,无论哪种情况都是严格按照url访问)
path('login/', views.login),
	http://127.0.0.1:8000/login/   成功

	http://127.0.0.1:8000/login    失败
path('login', views.login),
	http://127.0.0.1:8000/login/   失败

	http://127.0.0.1:8000/login    成功

1.5 当前匹配对象

request.resolver_match

![image-20220626151042970]image-20220626151042970

有什么用呀?

某用户,具有一些权限。   permissions = ["xx","login",'account']
某用户,具有一些权限。   permissions = ["login",'account']

2.视图

2.1 文件or文件夹

image-20220626154246243

如果功能比较多,可以把views.py中的函数拆分出来,变成文件夹的形式访问

image-20220626154404724

2.2 相对和绝对导入urls

image-20220626154723460

注意实现:不要再项目根目录做相对导入。

原则:

  • 绝对导入
  • 相对导入(层级深).表示当前目录,相当于找兄弟

2.3 视图参数

urlpatterns = [
    path('login/', account.login, name="login"),
    path('auth/', order.auth, name='auth'),
]

from django.shortcuts import HttpResponse


def login(request):
    return HttpResponse("login")

requests是什么呢?

对象,包裹,可以放很多东西。

requests是一个对象,存放了浏览器给咱们发过来的所有内容,所以含有:
- 请求相关所有的数据: 当前访问的url、请求方式、...
- django额外添加的数据
from django.shortcuts import HttpResponse


def login(request):
    # 1.当前URL  /api/login/
    print(request.path_info)

    # 2.URL传递的参数
    print(request.GET)
    print(request.GET.get("age"))

    # 3.请求方式  GET/POST
    print(request.method)

    # 4.如果post请求,传递请求体(原始数据)
    print(
        request.body)  # b'{"code":"083Sjmll2yla694F3bll2DguCM2SjmlG","unionId":"oP6QCsyT_9bk1dfSaVf0GEV5Y-yE"}'  b'v1=123&v2=456'

    # 4.1 请求体+请求头       b'v1=123&v2=456'  +  content-type:application/x-www-form-urlencoded
    print(request.POST)
    print(request.POST.get("v1"))
    print(request.POST.get("v2"))

    # 4.2 请求体+请求头   文件
    print(request.FILES)  # 文件格式           + multipart/form-data
    print(request.FILES.get("n1"))
    print(request.FILES.get("n2"))

    # 5.请求头
    # {'Content-Length': '', 'Content-Type': 'text/plain', 'Host': '127.0.0.1:8000', 'Connection': 'keep-alive', 'Cache-Control': 'max-age=0', 'Sec-Ch-Ua': '" Not A;Brand";v="99", "Chromium";v="102", "Google Chrome";v="102"', 'Sec-Ch-Ua-Mobile': '?0', 'Sec-Ch-Ua-Platform': '"macOS"', 'Upgrade-Insecure-Requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 'Sec-Fetch-Site': 'none', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-User': '?1', 'Sec-Fetch-Dest': 'document', 'Accept-Encoding': 'gzip, deflate, br', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7', 'Cookie': 'csrftoken=CdidpKSGbLxzmOXnbmlkvrZep1eJmKLAA81T73UjcjxEnMOa4YOZqtc849AkYfUy'}
    print(request.headers)

    # 5.1 请求头有个特殊的cookie
    # request.headers['cookie']  # 'csrftoken=CdidpKSGbLxzmOXnbmlkvrZep1eJmKLAA81T73UjcjxEnMOa4YOZqtc849AkYfUy;session=xxxx'
    # {'csrftoken': 'CdidpKSGbLxzmOXnbmlkvrZep1eJmKLAA81T73UjcjxEnMOa4YOZqtc849AkYfUy'}
    print(request.COOKIES)

    # 6.requests中其他值
    print(request.resolver_match)

    return HttpResponse("login")

2.4 返回值

  • HttpResponse
  • JsonResponse
  • render
  • redirect
from django.shortcuts import HttpResponse, redirect, render
from django.http import JsonResponse


def auth(request):
    pass


def login(request):
    # 1.获取请求数据
    print(request)

    # 2.根据请求数据进行条件的判断 GET/POST   GET.get("xx")    POST.get("xx")

    # 3.返回数据

    # 3.1 字符串/字节/文本数据(图片验证码)
    # return HttpResponse("login")

    # 3.2 JSON格式(前后端分离、app小程序后端、ajax请求)
    # data_dict = {"status": True, 'data': [11, 22, 33]}
    # return JsonResponse(data_dict)

    # 3.3 重定向
    # return redirect("https://www.baidu.com")
    # return redirect("http://127.0.0.1:8000/api/auth/")
    # return redirect("http://127.0.0.1:8000/api/auth/")
    # return redirect("/api/auth/")
    # return redirect("/api/auth/")  # name
    #
    # from django.urls import reverse
    # url = reverse("auth")
    # return redirect(url)  # name
    # return redirect("auth")

    # 3.4 渲染
    # - a.找到 'login.html' 并读取的内容,问题:去哪里找?
    # -   默认先去settings.TEMPLATES.DIRS指定的路径找。(公共)
    # -   按注册顺序每个已注册的app中找他templates目录,去这个目录中寻找'login.html'
    # -   一般情况下,原则,那个app中的的模板,去哪个那个app中寻找。
    # - b.渲染(替换)得到替换完成的字符串
    # - c.返回浏览器
    return render(request, 'api/login.html')

2.5 响应头

from django.shortcuts import HttpResponse, redirect, render
from django.http import JsonResponse


def login(request):
    res = HttpResponse("login")
    res['xx1'] = "hahaha"
    res['xx2'] = "hahaha"
    res['xx3'] = "hahaha"

    res.set_cookie('k1',"aaaaaaaa")
    res.set_cookie('k2',"bbbbbb")

    return res

2.6 FBV和CBV

FBV(function base views) 基于函数的视图,就是在视图里使用函数处理请求。

CBV(class base views) 基于类的视图,就是在视图里使用类处理请求。

  • FBV,视图用函数的形式编写。(目前主流)
  • CBV,视图用类的形式编写。

image-20220626174330406

请注意,这一些都是表象,本质一模一样。as_view()返回值本质上就是view函数

3.静态资源

静态资源:

  • 开发需要:css、js、图片。

    - 根目录的 /static/
    - 已经app目录下载 /static/ 文件夹下
    
  • 媒体文件:用户上传的数据(excel/pdf/video)

    - 根目录的 /media/
    

3.1 静态文件

INSTALLED_APPS = [
    # 'django.contrib.admin',
    # 'django.contrib.auth',
    # 'django.contrib.contenttypes',
    # 'django.contrib.sessions',
    # 'django.contrib.messages',
    'django.contrib.staticfiles',
    "apps.api.apps.ApiConfig",
    "apps.web.apps.WebConfig",
]
...

STATIC_URL = '/static/'
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)
  • 顺序:项目根目录static--->注册app中的static

  • 多app开发:各自app的图片放在各自 /static/app名字/。。。

  • 在开发过程中

    • 禁止

      <img src="/static/api/1.png">
      
    • 建议

      {% load static %}
      
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
      </head>
      <body>
      <h1>登录页面</h1>
      <a href="/xxx/xxxxx/">调换dao xx</a>
      <a href="{% url 'login' %}">跳转</a>
      
      <img src="{% static 'api/1.png' %}">
      
      </body>
      </html>
      

3.2 媒体文件

urls.py

from django.contrib import admin
from django.urls import path, re_path, include
from django.conf.urls.static import static
from django.conf import settings

from apps.api import views


# 很多功能,很多URL
urlpatterns = [
    path('api/', include('apps.api.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) #一定要在路由里设置才能找到media下的文件

image-20220626181505349

image-20220626181515182

4.模板

4.1 寻找html模板

  • settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                # 'django.contrib.auth.context_processors.auth',
                # 'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

优先去项目根目录 > 每个已注册的app的templates目录找。

如何选择:

  • 简单的项目,模板都放在根目录。
  • 复杂的项目,模板放在各自的app中,公共部分放在templates目录。

扩展:修改内置app的模板也是同样的套路。

4.2 模板处理的本质

渲染完成后,生成了字符串,再返回给浏览器。

  • views.py

    import datetime
    from django.shortcuts import render
    
    
    class Person(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def get_data(self):
            return "哈哈哈哈哈"
    
    
    def fetch_data():
        return "我是一个函数"
    
    
    def gen_data():
        yield 123
        yield 456
        yield 789
    
    
    def index(request):
        context = {
            "n1": "仙人掌",
            "n2": [11, 22, 33, 44],
            "n3": {
                "name": "武沛齐",
                "age": 19,
            },
            'n4': [
                {"id": 1, "name": "新晨1", "age": 18},
                {"id": 2, "name": "新晨2", "age": 18},
                {"id": 3, "name": "新晨3", "age": 18},
                {"id": 4, "name": "新晨4", "age": 18},
            ],
            "n5": Person("江春", 19),
            "n6": fetch_data,
            "n7": gen_data,
            'n8': "zhangkai",
            "n9": datetime.datetime.now(),
            "n10": datetime.datetime.now().strftime("%Y-%m-%d"),
            "n11": [
                {"id": 1, "name": "xinchen1", "age": 18},
                {"id": 2, "name": "xinchen2", "age": 18},
                {"id": 3, "name": "xinchen3", "age": 18},
                {"id": 4, "name": "xinchen4", "age": 18},
            ],
            "n12": 18
        }
        return render(request, 'app01/index.html', context)
    
    
    def home(request):
        return render(request, 'app01/home.html', {"title": "路飞学城", "info": "倒闭了"})
    
    
    def user(request):
        # int("哈哈哈")
        print("函数")
        print(request.resolver_match)  # None
        # return render(request, 'app01/user.html')
        # 事务 + 锁
        from django.template.response import TemplateResponse
        return TemplateResponse(request, 'app01/user.html')
    
    
  • app01/index.html

    {% load jp %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1 style="color:red;">首页{{ n1 }}</h1>
    <p>{{ n2.0 }}</p>
    <p>{{ n2.1 }}</p>
    <ul>
        {% for e in n2 %}
            <li>{{ e }}</li>
        {% endfor %}
    </ul>
    <hr/>
    
    <p>{{ n3.name }}</p>
    <p>{{ n3.age }}</p>
    <ul>
        {% for k,v in n3.items %}
            <li>{{ k }} = {{ v }}</li>
        {% endfor %}
    </ul>
    
    <hr/>
    
    <table border="1">
        {% for info in n4 %}
            <tr>
                <td>{{ info.id }}</td>
                <td>{{ info.name }}</td>
                <td>{{ info.age }}</td>
            </tr>
        {% endfor %}
    </table>
    
    <hr/>
    <p>{{ n5.name }}</p>
    <p>{{ n5.age }}</p>
    <p>{{ n5.get_data }}</p>
    <hr/>
    <p>{{ n6 }}</p>
    <hr/>
    <p>{{ n7 }}</p>
    <ul>
        {% for item in n7 %}
            <li>{{ item }}</li>
        {% endfor %}
    </ul>
    <hr/>
    
    <h2>{{ n8 }}</h2>
    <h2>{{ n8|upper }}</h2>
    <h2>{{ n8|lower }}</h2>
    <h2>{{ n9 }}</h2>
    <h2>{{ n9|date:"Y-m-d H:i:s" }}</h2>
    <h2>{{ n10 }}</h2>
    
    <hr/>
    <h2>{{ n8|myfunc }}</h2>
    
    <hr/>
    <table border="1">
        {% for info in n11 %}
            <tr>
                <td>{{ info.id }}</td>
                <td>{{ info.name|myfunc }}</td>
                <td>{{ info.age }}</td>
            </tr>
        {% endfor %}
    </table>
    
    <hr/>
    
    <p>{% mytag1 %}</p>
    <p>{% mytag2 "太卡了" "终于不用听了" %}</p>
    
    <p>{% xxxxx 123 %}</p>
    
    <hr/>
    
    {% if mytag1 %}
        <h4>小伙子</h4>
    {% else %}
        <h4>少年</h4>
    {% endif %}
    
    <hr/>
    {% menu 'admin' %}
    
    </body>
    </html>
    
    

image-20220703100004360

image-20220703100634467

4.3 常用语法

image-20220703104019949

4.4 内置函数

在django模板语法中提供了内置函数让我们来。

image-20220703104857782

4.5 自定义模板功能

  • templatetags/jp.py

    from django import template
    
    register = template.Library()
    
    
    @register.filter
    def myfunc(value):
        return value.upper()
    
    
    @register.filter
    def con(value):
        # ...
        return False
    
    
    @register.simple_tag
    def mytag1():
        return "哈哈哈哈"
    
    
    @register.simple_tag
    def mytag2(a1, a2):
        return a1 + "哈哈哈哈" + a2
    
    
    @register.inclusion_tag("app01/xxxxx.html")
    def xxxxx(arg):
        # <h1>大开哥--73</h1>
        return {"name": "大开哥", "age": 73}
    
    
    @register.inclusion_tag("app01/menu.html")
    def menu(role):
    
        if role == "user":
            return {
                'data': [
                    {"title": "用户管理", "url": "/xxxx/xxx"},
                    {"title": "账户管理", "url": "/xxxx/xxx"}
                ]
            }
        if role == 'admin':
            return {
                'data': [
                    {"title": "用户管理", "url": "/xxxx/xxx"},
                    {"title": "账户管理", "url": "/xxxx/xxx"},
                    {"title": "财务", "url": "/xxxx/xxx"},
                    {"title": "订单", "url": "/xxxx/xxx"},
                ]
            }
    
    

image-20220703110604253

三种方式:

  • filter

    - 数据处理,参数:1~2个
    - 数据处理,if条件
    @register.filter
    def myfunc(value):
        return value.upper()
    
    
    @register.filter
    def con(value):
        # ...
        return False
    
  • simple_tag

    参数无限制 & 返回文本
    @register.simple_tag
    def mytag1():
        return "哈哈哈哈"
    
    
    @register.simple_tag
    def mytag2(a1, a2):
        return a1 + "哈哈哈哈" + a2
    
    
  • inclusion_tag

    参数无限制 & HTML片段
    @register.inclusion_tag("app01/xxxxx.html")
    def xxxxx(arg):
        # <h1>大开哥--73</h1>
        return {"name": "大开哥", "age": 73}
    
    
    @register.inclusion_tag("app01/menu.html")
    def menu(role):
    
        if role == "user":
            return {
                'data': [
                    {"title": "用户管理", "url": "/xxxx/xxx"},
                    {"title": "账户管理", "url": "/xxxx/xxx"}
                ]
            }
        if role == 'admin':
            return {
                'data': [
                    {"title": "用户管理", "url": "/xxxx/xxx"},
                    {"title": "账户管理", "url": "/xxxx/xxx"},
                    {"title": "财务", "url": "/xxxx/xxx"},
                    {"title": "订单", "url": "/xxxx/xxx"},
                ]
            }
    
    
  • xxxxx.html

<h1>{{ name }}--{{ age }}</h1>
  • menu.html 根据用户权限不同显示不同的菜单
<ul>
    {% for item in data %}
        <li><a href="{{ item.url }}">{{ item.title }}</a></li>
    {% endfor %}
</ul>

  • jp.py

    @register.inclusion_tag("menu.html")
    def menu(role):
    
        if role == "user":
            return {
                'data': [
                    {"title": "用户管理", "url": "/xxxx/xxx"},
                    {"title": "账户管理", "url": "/xxxx/xxx"}
                ]
            }
        if role == 'admin':
            return {
                'data': [
                    {"title": "用户管理", "url": "/xxxx/xxx"},
                    {"title": "账户管理", "url": "/xxxx/xxx"},
                    {"title": "财务", "url": "/xxxx/xxx"},
                    {"title": "订单", "url": "/xxxx/xxx"},
                ]
            }
    
    
  • index.html

    {%menu 'user'%}
    

image-20220703112043922

4.6 继承和母版

  • layout.html作为母版,其余的html都可以对其进行继承,使得头部和尾部固定,无需重复编写

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <link rel="stylesheet" href="公共css文件">
        {% block css %}{% endblock %}
    </head>
    <body>
    <h1>头部</h1>
    {% block body %}{% endblock %}
    <h1>尾部</h1>
    
    
    <script src="公共js文件"></script>
    {% block js %}{% endblock %}
    </body>
    </html>
    
    

image-20220703114930894

image-20220703114941168

4.7 模板的导入

image-20220703115519438

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

{% endblock %}

{% block body %}
    <h1>主页</h1>

    {% include "app01/header.html" %}

    <p>{{ info }}</p>

    {% verbatim %}
        <p>{{ info }}</p>
    {% endverbatim %}


{% endblock %}


{% block js %}

{% endblock %}

5.django中间件

image-20220703140649928

  • 定义方法
  • 注册

5.1 原始方式

class MyMd():
    def __init__(self,getresponse):
        self.getresponse = getresponse

    def __call__(self, request):
        print("来了")
        response = self.getresponse(request)
        print("走了")
        return response

image-20220703141259948

image-20220703141313488

5.2 MiddlewareMixin(建议)

from django.shortcuts import HttpResponse

class MyMd(MiddlewareMixin):
    def process_request(self, reqeust):
        print("来了")

    def process_response(self, request, response):
        print('走了')
        return response

image-20220703142923194

image-20220703142936794

注意:django1版本。

源码:

  • 面向对象

    class MyMd(object):
        def __init__(self....):
            pass
        
        def __call__(self,....):
            pass
            
    django内部默认执行call方法,传入参数。
    
  • 反射

    class MyMd(object):
        def __init__(self....):
            pass
        
        def __call__(self,....):
            if hasattr(self,'process_request'):
                response = self.process_request(request)
    		...
         
    django内部默认执行call方法,传入参数。
    
    class MiddlewareMixin:
        def __init__(self, get_response=None):
            self.get_response = get_response
            
        def __call__(self, request):
            response = None
            if hasattr(self, 'process_request'):
                response = self.process_request(request)
            response = response or self.get_response(request)
            if hasattr(self, 'process_response'):
                response = self.process_response(request, response)
            return response
        
        
    class MyMd(MiddlewareMixin):
        
        def process_request(self,request):
            ...
        
        def process_response(self,request, response):
            ...
        
    django内部默认执行call方法,传入参数。
    

image-20220703145158733

image-20220703145210733

image-20220703145219921

疑问:prcess_request的执行时,是否已执行了路由匹配?

request.resolver_match

注意:process_view是在django中源码中写死了。

image-20220703151639813

5.3 其他

image-20220703153559256

image-20220703153616241

  • 类方法
    • process_request
    • process_view
    • process_reponse
    • process_exception,视图函数出现异常,自定义异常页面。
    • process_template_response,视图函数返回TemplateResponse对象 or 对象中含有.render方法。

image-20220703155414569

6.ORM操作

orm,关系对象映射,本质翻译的。

image-20220703155844071

6.1 表结构

实现:创建表、修改表、删除表。

在app中的models.py中按照规则编写类 ===> 表结构。

  • 编写类models.py

    from django.db import models
    
    
    class UserInfo(models.Model):
        name = models.CharField(max_length=16)
        age = models.IntegerField()
    
  • 注册app

    INSTALLED_APPS = [
        # 'django.contrib.admin',
        # 'django.contrib.auth',
        # 'django.contrib.contenttypes',
        # 'django.contrib.sessions',
        # 'django.contrib.messages',
        'django.contrib.staticfiles',
        'apps.api.apps.ApiConfig',
        'apps.web.apps.WebConfig'
    ]
    
  • 命令,django根据models中类生成一个 对数据库操作的配置文件 => migrations

    python manage.py makemigrations
    

image-20220703160914596

  • 命令,读取已经注册么给app中的migrations目录将配置文件 -> 转换成:生成表,修改表 SQL -> 连接数据库去运行。

    python manage.py migrate
    
    • 那个数据库?
    • 数据库账户和密码?
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        }
    }
    

image-20220703162050698

常见问题:请不要再手动去修改数据的表结构 + 时刻保证 ORM和数据表是对应。

6.1.1 常见字段和参数

  • 字段

    CharField
    
    SmallIntegerField
    IntegerField
    BigIntegerField
    
    DateField
    DateTimeField
    
    BooleanField  -> 其实数据库不支持真假,根据SmallIntegerField创造出来出来。 0  1
    
    DecimalField  -> 精确的小数
    
  • 参数

    name = models.CharField(verbose_name="姓名", max_length=16)
    name = models.CharField(verbose_name="姓名", max_length=16, default="哈哈哈")
    
    # 经常查询,速度快(MySQL, https://www.bilibili.com/video/BV15R4y1b7y9 )
    name = models.CharField(verbose_name="姓名", max_length=16, default="哈哈哈", null=True, blank=True, db_index=True)
    email = models.CharField(verbose_name="姓名", max_length=16, default="哈哈哈", null=True, blank=True, unique=True)
    
    # 在数据库存储时只能是:sh、bj (上海、北京一般用于页面显示中文)
    code = models.CharField(verbose_name="姓名", max_length=16, choices=(("sh", "上海"), ("bj", "北京")),default="sh")
    
    # 不用 max_length=16
    count = models.IntegerField(verbose_name="数量", default=1, null=True, blank=True, unique=True)
    code = models.IntegerField(verbose_name="性别",choices=((1, "男"), (2, "女")),default=1)
    
    register_date = models.DateField(verbose_name="注册时间", auto_now=True)
    
    amount = models.DecimalField(verbose_name="余额", max_digits=10, decimal_places=2)
    

示例:

from django.db import models


class UserInfo(models.Model):
    name = models.CharField(verbose_name="姓名", max_length=16, db_index=True)
    age = models.PositiveIntegerField(verbose_name="年龄")
    email = models.CharField(verbose_name="邮箱", max_length=128, unique=True)
    amount = models.DecimalField(verbose_name="余额", max_digits=10, decimal_places=2, default=0)
    register_date = models.DateField(verbose_name="注册时间", auto_now=True)


class Goods(models.Model):
    title = models.CharField(verbose_name="标题", max_length=32)
    # detail = models.CharField(verbose_name="详细信息", max_length=255)
    detail = models.TextField(verbose_name="详细信息")
    price = models.PositiveIntegerField(verbose_name="价格")
    count = models.PositiveBigIntegerField(verbose_name="库存", default=0)

6.1.2 表关系

  • 一对多(一个部分对应多个员工)

image-20220703173556969

image-20220703174433414

class Department(models.Model):
    """部门表"""
    title = models.CharField(verbose_name="标题")


class UserInfo(models.Model):
    """用户表"""
    name = models.CharField(verbose_name="姓名")

    depart = models.ForeignKey(verbose_name="部门ID", to="Department", to_field="id", on_delete=models.CASCADE, db_constraint=False)
    # depart = models.ForeignKey(verbose_name="部门ID", to="Department", to_field="id", on_delete=models.SET_NULL,null=True,blank=True)
    # depart = models.ForeignKey(verbose_name="部门ID", to="Department", to_field="id", on_delete=models.SET_DEFAULT, default=2)


  • 多对多

image-20220703175051329

image-20220703175312854

class Boy(models.Model):
    name = models.CharField(verbose_name="姓名")


class Girl(models.Model):
    name = models.CharField(verbose_name="姓名")
    relation = models.ManyToManyField(verbose_name="男女关系", to="Boy")

class B2G(models.Model):
    boy = models.ForeignKey(verbose_name="男", to="Boy", to_field="id", on_delete=models.CASCADE)
    girl = models.ForeignKey(verbose_name="女", to="Girl", to_field="id", on_delete=models.CASCAD

image-20220703175556444

注意:ManyToManyField生成的表字段只能id/bid/gid

设计自己项目的业务时,理清楚表与表之间的关系。

强调:设计项目表结构:表名和字段都不要拼音。

6.2 数据

实现:增删改查。

第三章 django进阶

1.orm

1.1 基本操作

orm,关系对象映射。

类      --> SQL -->     表
对象    --> SQL -->     数据

特点:开发效率高、执行效率低( 程序写的垃圾SQL )。

编写ORM操作的步骤:

  • settings.py,连接数据库

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': BASE_DIR / 'db.sqlite3',
        }
    }
    
  • settings.py,注册app

    INSTALLED_APP = [
    	...
    	"app01.apps.App01Config"
    ]
    
  • 编写models.类

    class UserInfo(models.Model):
        ....
        .....
    
  • 执行命令

    python manage.py makemigrations    # 找到所有已注册的app中的models.py中的类读取 -> migrations配置
    python manage.py migrate           # 读取已注册的app下的migrations配置 -> SQL语句  -> 同步数据库
    

1.2 连接数据库

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'xxxxxxxx',  # 数据库名字
        'USER': 'root',
        'PASSWORD': 'root123',
        'HOST': '127.0.0.1',  # ip
        'PORT': 3306,
    }
}

在django中为了使用MySQL,一般是在项目目录下的__init__.py中添加

import  pymysql

pymysql.install_as_MySQLdb()# 使用pymysql代替mysqldb连接数据库

当mysqlclient版本过低时,可修改至如下代码

import pymysql
pymysql.version_info = (1, 4, 13, "final", 0)
pymysql.install_as_MySQLdb() 
# 使用pymysql代替mysqldb连接数据库

项目连接MySQL:

  • 安装MySQL & 启动MySQL服务

  • 手动创建数据库

  • django的settings.py配置

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'xxxxxxxx',  # 数据库名字
            'USER': 'root',
            'PASSWORD': 'root123',
            'HOST': '127.0.0.1',  # ip
            'PORT': 3306,
        }
    }
    
  • 安装第三方组件

    • pymysql

      pip install pymysql
      
      项目根目录/项目名目录/__init__.py
      	import pymysql
      	pymysql.install_as_MySQLdb()
      
    • mysqlclient

      pip install mysqlclient
      
      电脑上先提前安装MySQL。
      

其他数据库:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydatabase',
        'USER': 'mydatabaseuser',
        'PASSWORD': 'mypassword',
        'HOST': '127.0.0.1',
        'PORT': 5432,
    }
}

# 需要 pip install psycopg2
DATABASES = {
	'default': {
        'ENGINE': 'django.db.backends.oracle',
        'NAME': "xxxx",  # 库名
        "USER": "xxxxx",  # 用户名
        "PASSWORD": "xxxxx",  # 密码
        "HOST": "127.0.0.1",  # ip
        "PORT": 1521,  # 端口
    }
}
# 需要 pip install cx-Oracle

1.3 连接池

连接池的作用是防止一个链接频繁的建立和断开导致时间开销较大。

django默认内置没有数据库连接池 。

pymysql   -> 操作数据库
DBUtils   -> 连接池

https://pypi.org/project/django-db-connection-pool/

pip install django-db-connection-pool
DATABASES = {
    "default": {
        'ENGINE': 'dj_db_conn_pool.backends.mysql',
        'NAME': 'day04',  # 数据库名字
        'USER': 'root',
        'PASSWORD': 'root123',
        'HOST': '127.0.0.1',  # ip
        'PORT': 3306,
        'POOL_OPTIONS': {
            'POOL_SIZE': 10,  # 最小
            'MAX_OVERFLOW': 10,  # 在最小的基础上,还可以增加10个,即:最大20个。
            'RECYCLE': 24 * 60 * 60,  # 连接可以被重复用多久,超过会重新创建,-1表示永久。
            'TIMEOUT':30, # 池中没有连接最多等待的时间。
        }
    }
}

注意:组件django-db-connection-pool不是特别厉害。拿了另外一个支持SQLAchemy数据库连接池的组件。

1.4 多数据库

django支持项目连接多个数据库。

DATABASES = {
    "default": {
        'ENGINE': 'dj_db_conn_pool.backends.mysql',
        'NAME': 'day05db',  # 数据库名字
        'USER': 'root',
        'PASSWORD': 'root123',
        'HOST': '127.0.0.1',  # ip
        'PORT': 3306,
        'POOL_OPTIONS': {
            'POOL_SIZE': 10,  # 最小
            'MAX_OVERFLOW': 10,  # 在最小的基础上,还可以增加10个,即:最大20个。
            'RECYCLE': 24 * 60 * 60,  # 连接可以被重复用多久,超过会重新创建,-1表示永久。
            'TIMEOUT': 30,  # 池中没有连接最多等待的时间。
        }
    },
    "bak": {
        'ENGINE': 'dj_db_conn_pool.backends.mysql',
        'NAME': 'day05bak',  # 数据库名字
        'USER': 'root',
        'PASSWORD': 'root123',
        'HOST': '127.0.0.1',  # ip
        'PORT': 3306,
        'POOL_OPTIONS': {
            'POOL_SIZE': 10,  # 最小
            'MAX_OVERFLOW': 10,  # 在最小的基础上,还可以增加10个,即:最大20个。
            'RECYCLE': 24 * 60 * 60,  # 连接可以被重复用多久,超过会重新创建,-1表示永久。
            'TIMEOUT': 30,  # 池中没有连接最多等待的时间。
        }
    },
}

1.4.1 读写分离

192.168.1.2       default master   [写]
                  组件
192.168.2.12      bak slave    [读]
  • 生成数据库表

    python manage.py makemigrations    # 找到所有已注册的app中的models.py中的类读取 -> migrations配置
    
    python manage.py migrate
    python manage.py migrate --database=default
    python manage.py migrate --database=bak
    
  • 后续再进行开发时

    models.UserInfo.objects.using("default").create(title="武沛齐")
    
    models.UserInfo.objects.using("bak").all()
    
  • 编写router类,简化【后续再进行开发时】

    class DemoRouter(object):
        
        def db_for_read(...):
            return "bak"
        
        def db_for_write(...):
            return "default"
    
    router = ["DemoRouter"]
    

image-20220710104821310

1.4.2 分库(多个app ->多数据库)

100张表,50表-A数据库【app02】;50表-B数据库【app02】。

  • app01/models

    from django.db import models
    
    
    class UserInfo(models.Model):
        title = models.CharField(verbose_name="标题", max_length=32)
    
    
  • app02/models

    from django.db import models
    
    
    class Role(models.Model):
        title = models.CharField(verbose_name="标题", max_length=32)
    
  • 命令

    python manage.py makemigrations
    
    python manage.py migrate app01 --database=default
    
    python manage.py migrate app02 --database=bak
    

image-20220710105925961

  • 读写操作

    from django.shortcuts import render, HttpResponse
    
    from app01 import models as m1
    from app02 import models as m2
    
    
    def index(request):
        # app01中的操作 -> default
        v1 = m1.UserInfo.objects.all()
        print(v1)
    
        # app02中的操作 -> bak
        v2 = m2.Role.objects.using('bak').all()
        print(v2)
        return HttpResponse("返回")
    
  • router

image-20220710110711518

1.4.3 分库(单app)

100张表,50表-A数据库;50表-B数据库。

image-20220710111923985

  • app01/models.py
from django.db import models

#default
class UserInfo(models.Model):
    name = models.CharField(verbose_name="姓名", max_length=32)
    pwd = models.CharField(verbose_name="密码", max_length=32)
    roles = models.ManyToManyField(verbose_name="角色", to="Role")

#bak
class Role(models.Model):
    title = models.CharField(verbose_name="标题", max_length=32)
#bak
class Depart(models.Model):
    title = models.CharField(verbose_name="标题", max_length=32)
DATABASE_ROUTERS = ["utils.router.DemoRouter"]
  • utiles/router.py

    class DemoRouter(object):
        def allow_migrate(self, db, app_label, model_name=None, **hints):
            # python manage.py migrate app01 --database=default
            # python manage.py migrate app01 --database=bak
            # True  ->生成到数据库
            # False -> 不生成
    
            if db == 'bak':
                if model_name in ["role", 'depart']:
                    return True
                else:
                    return False
    
            if db == 'default':
                if model_name in ['userinfo']:
                    return True
                else:
                    return False
    
  • app01/views.py

from django.shortcuts import render, HttpResponse

from app01 import models as m1


def index(request):
    # app01中的操作 -> default
    v1 = m1.UserInfo.objects.all()
    print(v1)

    # app01中的操作 -> bak
    v2 = m1.Role.objects.using('bak').all()
    print(v2)

    return HttpResponse("返回")

image-20220710112344681

1.4.4 注意事项

  • 分库,表拆分到不用数据库。

    一定不要跨数据库做关联  -> django不支持
    
    怎么办?
    尽可能的将有关联的表放在一个库中。
    

image-20220710112908647

  • 为什么表拆分到不同的库?

1.5 表关系

  • 单表

    class Role(models.Model):
        title = models.CharField(verbose_name="标题", max_length=32)
    
  • 一对多

    class Department(models.Model):
        """部门表"""
        title = models.CharField(verbose_name="标题")
    
    
    class UserInfo(models.Model):
        """用户表"""
        name = models.CharField(verbose_name="姓名")
    
        depart = models.ForeignKey(verbose_name="部门ID", to="Department", to_field="id", on_delete=models.CASCADE, db_constraint=False)
        # depart = models.ForeignKey(verbose_name="部门ID", to="Department", to_field="id", on_delete=models.SET_NULL,null=True,blank=True)
        # depart = models.ForeignKey(verbose_name="部门ID", to="Department", to_field="id", on_delete=models.SET_DEFAULT, default=2)
    

image-20220710115245095

  • 多对多

image-20220710115902277

如果关系表中只有3列。

class Boy(models.Model):
    """
    1   杰森斯坦森
    2   汤普森
    """
    name = models.CharField(verbose_name="标题", max_length=32, unique=True)
    b = models.ManyToManyField(to="Girl")

class Girl(models.Model):
    """
    1   alex
    2   苑昊
    """
    name = models.CharField(verbose_name="标题", max_length=32, unique=True)
class Boy(models.Model):
    """
    1   杰森斯坦森
    2   汤普森
    """
    name = models.CharField(verbose_name="标题", max_length=32, unique=True)
    
class Girl(models.Model):
    """
    1   alex
    2   苑昊
    """
    name = models.CharField(verbose_name="标题", max_length=32, unique=True)
    b = models.ManyToManyField(to="Boy")
class Boy(models.Model):
    name = models.CharField(verbose_name="标题", max_length=32, unique=True)


class Girl(models.Model):
    name = models.CharField(verbose_name="标题", max_length=32, unique=True)


class B2G(models.Model):
    bid = models.ForeignKey(to="Boy", on_delete=models.CASCADE)
    gid = models.ForeignKey(to="Girl", on_delete=models.CASCADE)
    address = models.CharField(verbose_name="地点", max_length=32)
  • 一对一

    表,100列     ->  50A表      50B表
    
    博客园为例:权限管理
    	- 注册,用户名、密码,无法创建博客
    	- 开通博客  地址/
    

image-20220710121216875

class UserInfo(models.Model):
    name = models.CharField(verbose_name="姓名", max_length=32, db_index=True)
    pwd = models.CharField(verbose_name="密码", max_length=32)


class Blog(models.Model):
    user = models.OneToOneField(to="UserInfo", on_delete=models.CASCADE)
    blog = models.CharField(verbose_name="博客地址", max_length=255)
    summary = models.CharField(verbose_name="简介", max_length=128)

1.6 数据操作

  • 单表

    class Role(models.Model):
        title = models.CharField(verbose_name="标题", max_length=32)
        od = models.IntegerField(verbose_name="排序", default=0)
    
    # obj1 = models.Role.objects.create(title="管理员", od=1)
    # obj2 = models.Role.objects.create(**{"title": "管理员", "od": 1})
    
    # 内存 -> save
    # obj = models.Role(title="客户", od=1)
    # obj.od = 100
    # obj.save()
    
    # obj = models.Role(**{"title": "管理员", "od": 1})
    # obj.od = 100
    # obj.save()
    

image-20220710143537101

# models.Role.objects.all().delete()
models.Role.objects.filter(title="管理员").delete()
models.Role.objects.all().update(od=99)
models.Role.objects.filter(id=7).update(od=99, title="管理员")
models.Role.objects.filter(id=7).update(**{"od": 99, "title": "管理员"})
# QuerySet = [obj, obj]
v1 = models.Role.objects.all()
for obj in v1:
    print(obj, obj.id, obj.title, obj.od)

# QuerySet = []
# v2 = models.Role.objects.filter(od=99, id=99)
v2 = models.Role.objects.filter(**{"od": 99, "id": 99})
for obj in v2:
    print(obj, obj.id, obj.title, obj.od)
    

v3 = models.Role.objects.filter(id=99)
print(v3.query)

v3 = models.Role.objects.filter(id__gt=2)
print(v3.query)

v3 = models.Role.objects.filter(id__gte=2)
print(v3.query)

v3 = models.Role.objects.filter(id__lt=2)
print(v3.query)

v3 = models.Role.objects.filter(id__in=[11, 22, 33])
print(v3.query)

v3 = models.Role.objects.filter(title__contains="户")
print(v3.query)

v3 = models.Role.objects.filter(title__startswith="户")
print(v3.query)

v3 = models.Role.objects.filter(title__isnull=True)
print(v3.query)
v3 = models.Role.objects.filter(id=99)
print(v3.query)
# 不等于
v3 = models.Role.objects.exclude(id=99).filter(od=88)
print(v3.query)
# queryset=[obj,obj]
v3 = models.Role.objects.filter(id=99)

# queryset=[{'id': 6, 'title': '客户'}, {'id': 7, 'title': '客户'}]
v4 = models.Role.objects.filter(id__gt=0).values("id", 'title')

# QuerySet = [(6, '客户'), (7, '客户')]
v5 = models.Role.objects.filter(id__gt=0).values_list("id", 'title')
print(v5[0])
v6 = models.Role.objects.filter(id__gt=0).first()
# print(v6)  # 对象

v7 = models.Role.objects.filter(id__gt=10).exists()
print(v7)  # True/False
# asc
v8 = models.Role.objects.filter(id__gt=0).order_by("id")

# id desc  od asc
v9 = models.Role.objects.filter(id__gt=0).order_by("-id", 'od')
  • 一对多

    class Depart(models.Model):
        """ 部门 """
        title = models.CharField(verbose_name="标题", max_length=32)
    
    
    class Admin(models.Model):
        name = models.CharField(verbose_name="姓名", max_length=32)
        pwd = models.CharField(verbose_name="密码", max_length=32)
    
        depart = models.ForeignKey(verbose_name="部门", to="Depart", on_delete=models.CASCADE)
    
    models.Admin.objects.create(name='武沛齐1', pwd='123123123', depart_id=2)
    # models.Admin.objects.create(**{..})
    
    obj = models.Depart.objects.filter(id=2).first()
    models.Admin.objects.create(name='武沛齐2', pwd='123123123', depart=obj)
    models.Admin.objects.create(name='武沛齐2', pwd='123123123', depart_id=obj.id)
    
    # filter()   # 当前表的字段 + depart__字段    -> 连表和条件
    
    # 找到部门id=3的所有的员工,删除
    # models.Admin.objects.filter(depart_id=3).delete()
    
    # 删除销售部的所有员工
    # obj = models.Depart.objects.filter(title="销售部").first()
    # models.Admin.objects.filter(depart_id=obj.id).delete()
    
    # models.Admin.objects.filter(depart__title="销售部", name='武沛齐').delete()
    
    # 1. select * from admin    					queryset=[obj,obj,]
    v1 = models.Admin.objects.filter(id__gt=0)
    for obj in v1:
        print(obj.name, obj.pwd, obj.id, obj.depart_id)
    
    # 2. select * from admin inner join depart      queryset=[obj,obj,]
    v2 = models.Admin.objects.filter(id__gt=0).select_related("depart")
    for obj in v2:
        print(obj.name, obj.pwd, obj.id, obj.depart_id, obj.depart.title)
    
    # 3. select id,name.. from admin inner join depart      queryset=[{},{}]
    v3 = models.Admin.objects.filter(id__gt=0).values("id", 'name', 'pwd', "depart__title")
    print(v3)
    
    # 4. select id,name.. from admin inner join depart      queryset=[(),()]
    v4 = models.Admin.objects.filter(id__gt=0).values_list("id", 'name', 'pwd', "depart__title")
    print(v4)
    
    # 查询
    # models.Admin.objects.filter(id=2).update(name='xxx', pwd='xxxx')
    # models.Admin.objects.filter(name="武沛齐").update(depart_id=2)
    
    # models.Admin.objects.filter(id=2).update(depart__title="技术部")  -> 只能更新自己表字段
    

image-20220710164040495

  • 多对多

image-20220710170440957

from django.db import models


class Boy(models.Model):
    name = models.CharField(verbose_name="姓名", max_length=32, db_index=True)


class Girl(models.Model):
    name = models.CharField(verbose_name="姓名", max_length=32, db_index=True)


class B2G(models.Model):
    bid = models.ForeignKey(to="Boy", on_delete=models.CASCADE)
    gid = models.ForeignKey(to="Girl", on_delete=models.CASCADE)
    address = models.CharField(verbose_name="地点", max_length=32)
def index(request):
    # models.Boy.objects.create(name="宝强")
    # models.Boy.objects.create(name="羽凡")
    # models.Boy.objects.create(name="乃亮")
    #
    # models.Girl.objects.bulk_create(
    #     objs=[models.Girl(name="小路"), models.Girl(name="百合"), models.Girl(name="马蓉")],
    #     batch_size=3
    # )

    # 创建关系
    # models.B2G.objects.create(bid_id=1, gid_id=3, address="北京")
    # models.B2G.objects.create(bid_id=1, gid_id=2, address="北京")
    # models.B2G.objects.create(bid_id=2, gid_id=2, address="北京")
    # models.B2G.objects.create(bid_id=2, gid_id=1, address="北京")

    # b_obj = models.Boy.objects.filter(name='宝强').first()
    # g_object = models.Girl.objects.filter(name="小路").first()
    # models.B2G.objects.create(bid=b_obj, gid=g_object, address="北京")

    # 1.宝强都与谁约会。
    # queyset=[obj,obj,obj]
    # q = models.B2G.objects.filter(bid__name='宝强').select_related("gid")
    # for item in q:
    #     print(item.id, item.address, item.bid.name, item.gid.name)

    # q = models.B2G.objects.filter(bid__name='宝强').values("id", 'bid__name', 'gid__name')
    # for item in q:
    #     print(item['id'], item['bid__name'], item['gid__name'])

    # 2.百合 都与谁约会。
    # q = models.B2G.objects.filter(gid__name='百合').values("id", 'bid__name', 'gid__name')
    # for item in q:
    #     print(item['id'], item['bid__name'], item['gid__name'])

    # 3.删除
    # models.B2G.objects.filter(id=1).delete()
    # models.Boy.objects.filter(id=1).delete()

    return HttpResponse("返回")
  • 一对一
    image-20220710173043956
def index(request): # 一对一
    # models.UserInfo.objects.create(name='大军', pwd='123123123')
    # models.UserInfo.objects.create(name='小王', pwd='123123123')
    models.Blog.objects.create(blog="www.xxx.com", summary='xxxxx', user_id=1)
    user_object = models.UserInfo.objects.filter(name='大军').first()
    models.Blog.objects.create(blog="www.xxx.com", summary='xxxxx', user=user_object)

    user_object = models.UserInfo.objects.filter(name='大军').select_related('blog').first()
    print(user_object.name)
    print(user_object.pwd)
    print(user_object.blog.blog)
    print(user_object.blog.summary)

    blog_object = models.Blog.objects.filter(id=1).select_related("user").first()
    print(blog_object.blog)
    print(blog_object.summary)
    print(blog_object.user.name)
    print(blog_object.user.pwd)
    models.UserInfo.objects.filter(name="大军").values("name", 'pwd', 'blog__blog', "blog__summary")

    return HttpResponse("返回")


2.cookie和session

image-20220710174352897

def login(request):
    # 在session中设置值 + cookie中写入凭证
    res = HttpResponse("....")
    res.set_cookie("v1", "1224214214")
    return res


def home(request):
    print(request.COOKIES)
    print(request.COOKIES.get("v1"))
    return HttpResponse("HOME")
127.0.0.1       v1.wupeiqi.com
127.0.0.1       v2.wupeiqi.com

image-20220710175925008

2.2 配置session

def login(request):
    # 在session中设置值 + cookie中写入凭证
    request.session['user_info'] = "武沛齐"
    return HttpResponse("登录")


def home(request):
    user_info = request.session.get("user_info")
    print(user_info)
    return HttpResponse("HOME")
  • 文件版

    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',
    ]
    
    
    # session
    SESSION_ENGINE = 'django.contrib.sessions.backends.file'
    SESSION_FILE_PATH = 'xxxx' 
    
    SESSION_COOKIE_NAME = "sid"  # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
    SESSION_COOKIE_PATH = "/"  # Session的cookie保存的路径
    SESSION_COOKIE_DOMAIN = None  # Session的cookie保存的域名
    SESSION_COOKIE_SECURE = False  # 是否Https传输cookie
    SESSION_COOKIE_HTTPONLY = True  # 是否Session的cookie只支持http传输
    SESSION_COOKIE_AGE = 1209600  # Session的cookie失效日期(2周)
    
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False  # 是否关闭浏览器使得Session过期
    SESSION_SAVE_EVERY_REQUEST = True  # 是否每次请求都保存Session,默认修改之后才保存
    
  • 数据库

    INSTALLED_APPS = [
        # 'django.contrib.admin',
        # 'django.contrib.auth',
        # 'django.contrib.contenttypes',
        'django.contrib.sessions',
        # 'django.contrib.messages',
        'django.contrib.staticfiles',
        "app01.apps.App01Config",
    ]
    
    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',
    ]
    
    
    # session
    SESSION_ENGINE = 'django.contrib.sessions.backends.db'
    
    SESSION_COOKIE_NAME = "sid"  # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
    SESSION_COOKIE_PATH = "/"  # Session的cookie保存的路径
    SESSION_COOKIE_DOMAIN = None  # Session的cookie保存的域名
    SESSION_COOKIE_SECURE = False  # 是否Https传输cookie
    SESSION_COOKIE_HTTPONLY = True  # 是否Session的cookie只支持http传输
    SESSION_COOKIE_AGE = 1209600  # Session的cookie失效日期(2周)
    
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False  # 是否关闭浏览器使得Session过期
    SESSION_SAVE_EVERY_REQUEST = True  # 是否每次请求都保存Session,默认修改之后才保存
    

3.缓存

  • 服务器 + redis安装启动

  • django

    • 安装连接redis包

      pip install django-redis
      
    • settings.py

      CACHES = {
          "default": {
              "BACKEND": "django_redis.cache.RedisCache",
              "LOCATION": "redis://127.0.0.1:6379",
              "OPTIONS": {
                  "CLIENT_CLASS": "django_redis.client.DefaultClient",
                  "CONNECTION_POOL_KWARGS": {"max_connections": 100}
                  # "PASSWORD": "密码",
              }
          }
      }
      
    • 手动操作redis

      from django_redis import get_redis_connection
      
      conn = get_redis_connection("default")
      conn.set("xx","123123")
      conn.get("xx")
      
INSTALLED_APPS = [
    # 'django.contrib.admin',
    # 'django.contrib.auth',
    # 'django.contrib.contenttypes',
    # 'django.contrib.sessions',
    # 'django.contrib.messages',
    'django.contrib.staticfiles',
    "app01.apps.App01Config",
]

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',
]


# session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default' 

SESSION_COOKIE_NAME = "sid"  # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
SESSION_COOKIE_PATH = "/"  # Session的cookie保存的路径
SESSION_COOKIE_DOMAIN = None  # Session的cookie保存的域名
SESSION_COOKIE_SECURE = False  # 是否Https传输cookie
SESSION_COOKIE_HTTPONLY = True  # 是否Session的cookie只支持http传输
SESSION_COOKIE_AGE = 1209600  # Session的cookie失效日期(2周)

SESSION_EXPIRE_AT_BROWSER_CLOSE = False  # 是否关闭浏览器使得Session过期
SESSION_SAVE_EVERY_REQUEST = True  # 是否每次请求都保存Session,默认修改之后才保存

第四章 订单管理系统

image-20220710184409818

核心的功能模块:

  • 认证模块,用户名密码 或 手机短信登录(60s有效)。

  • 角色管理,不同角色具有不同权限 和 展示不同菜单。

    管理员,充值
      客户,下单
    
  • 客户管理,除了基本的增删改查以外,支持对客户可以分级,不同级别后续下单折扣不同。

  • 交易中心

    • 管理员可以给客户余额充值/扣费
    • 客户可以下单/撤单
    • 生成交易记录
    • 对订单进行多维度搜索,例如:客户姓名、订单号。
  • worker,去执行订单并更新订单状态。

1.单点知识

1.1 发送短信

腾讯云短信服务,来进行发送短信。

  • 注册账号

  • 开通服务 + 缴费 (实名、企业认证,公众号

  • API服务、SDK服务

    • API,接口

      import requests
      
      # 处理签名和加密
      res = requests.get("......",params={"key":"xxx",'token':'...'})
      
    • SDK,服务

      pip install tencentcloud-sdk-python
      
      import xxxx
      
      xxxxx.send(...)
      
# -*- coding: utf-8 -*-
from tencentcloud.common import credential
from tencentcloud.sms.v20210111 import sms_client, models

cred = credential.Credential("AKIDa0B7nhOq3zf5G8l9TMzNVO0MRHrAE3Yn", "4rPincBUYMuCEzUjsdIiuqWv3vYu0qPh")
client = sms_client.SmsClient(cred, "ap-guangzhou")

req = models.SendSmsRequest()

req.SmsSdkAppId = "1400455481"
req.SignName = "Python之路"
req.TemplateId = "548762"
req.TemplateParamSet = ["449739"]
req.PhoneNumberSet = ["+8615131255000"]

resp = client.SendSms(req)
print(resp)

1.2 权限和菜单管理

1.2.1 菜单

不同角色的用户登录,看到不同的菜单。

  • 页面写死 HTML模板

    <html>
        {% if 角色 "管理员"%}
        	<a href="/xxx/x">用户管理</a>
        	<a href="/xxx/x">级别管理</a>
        	<a href="/xxx/x">级别管理</a>
        	...
        {% else %}
        	<a href="/xxx/x">xxx管理</a>
        	<a href="/xxx/x">级别管理</a>
        {% endif %}
    </html>
    
  • 将菜单放在配置文件中 (选择

    # settings.py
    
    ADMIN = [
        {"title":"用户管理", "url":"...." },
        {"title":"用户管理", "url":"...." },
        {"title":"用户管理", "url":"...." },
        {"title":"用户管理", "url":"...." },
    ]
    
    USER = [
        {"title":"用户管理", "url":"...." },
        {"title":"用户管理", "url":"...." },
        {"title":"用户管理", "url":"...." },
        {"title":"用户管理", "url":"...." },
    ]
    
    <html>
        {% if 角色 "管理员"%}
        	{% for item in ADMIN%}
    		    <a href="{{item.url}}">{{item.title}}</a>
    	    {%endfor%}
        {% else %}
        	{% for item in USER%}
    		    <a href="{{item.url}}">{{item.title}}</a>
    	    {%endfor%}
        {% endif %}
    </html>
    
  • 将 菜单+角色 写入数据库

    id title url
    1 用户管理 ...
    2 级别管理 ...
    3 订单管理 ...
    id 角色
    1 管理员
    2 用户
    id role_id menu_id
    1 1 1
    2 1 2
    3 2 2
    # 在页面展示数据库
    # 1.查询特定角色关联的所有的菜单
    
    # 2.在页面上进行展示
    

如果选择使用配置文件的方式:

  • 菜单,需要显示几个级别的菜单。

    ADMIN = [
        {"title":"用户管理", "url":"...." },
        {"title":"用户管理", "url":"...." },
        {"title":"用户管理", "url":"...." },
        {"title":"用户管理", "url":"...." },
    ]
    
    ADMIN = [
        {
            "title":"用户管理", 
            "children":[
                {"title":"级别列表","url":"....", "name":"level_list",}
                {"title":"级别列表","url":"...."}
                {"title":"级别列表","url":"...."}
            ]
        },
        {
            "title":"订单管理", 
            "children":[
                {"title":"订单列表","url":"...."}
                {"title":"订单列表","url":"...."}
                {"title":"订单列表","url":"...."}
            ]
        },
    ]
    
  • 菜单选中和展开问题

    1.获取当前用户请求的 URL   pricepolicy/list/ 或 url对应的name
    
    2. pricepolicy/list/ 配置 ADMIN中的URL   ->默认选中
    
  • 路径导航的问题

    1.获取当前用户请求的 URL   pricepolicy/list/ 或 url对应的name
    
    2.获取上级,展示导航信息
    
    3.设置菜单与下级关系
    

扩展:菜单多级关系

id title url parent_id
1 客户管理 null null
2 级别列表 ... 1
3 客户列表 ... 1
4 订单管理 null null
5 价格 ... 4
6 交易 ... 4
7 其他 ... 6

注意:类似与平台的评论。

id content root_id parent_id depth
1 优秀 null null 0
2 不咋样 null null 0
3 确实 1 1 1
4 哈哈 1 1 1
5 你说的都对 1 3 2
- 优秀
	确实
		你说的都对
	哈哈
- 不咋样

1.2.2 权限

权限的判断时,要考虑:正常的点击、非法输入。

v1 = [11,22,33,44]
if 33 in v1:
    pass
v1 = {11,22,33,44}
if 33 in v1:
    pass
v1 = {
    11:123123,
    22:123123
    33:123123
    44:123123
}

if 33 in v1:
    pass
  • 文件settings.py的方式(编写)

    admin_permisions = {
        "level_list":{...},
        "level_edit":{..., 'parent':'level_list'},
        "level_add":{... 'parent':'level_list'},
        "level_delete":{..'parent':'level_list'.},
        
        "user_list":{...},
        "user_edit":{...},
        "user_add":{...},
        "user_delete":{...},
    }
    
    user_permisions = {
        ...
    }
    
    admin访问某个URL + 路由信息(name、namespace),获取当前的URL  /level/edit/4/ -> 是否存在URL
    
    在中间件中根据URL中的name进行权限的校验。
    
  • 数据库的方式
    image-20220717112806916

1.3 队列

image-20220710184409818-17787474470143

  • rabbitMQ,Linux命令+服务构建+python代码。
  • kafka,Linux命令+服务构建+python代码。
  • redis的列表

image-20220717114057232-17787474470335

基于redis实现上述的过程和代码示例:

  • 安装redis

  • 启动redis

    win:  https://pythonav.com/wiki/detail/10/82/
    
    mac:
    	1.去官方下载redis文件
    	2.解压编译
    	3.修改配置文件
    	4.启动
    
  • Python操作redis

    pip install redis
    
    import redis
    
    conn = redis.Redis(host='127.0.0.1', port=6379, password='qwe123', encoding='utf-8')
    
    # 短信验证码
    conn.set('15131255089', 9999, ex=10)
    value = conn.get('15131255089')
    print(value)
    
    import redis
    
    conn = redis.Redis(host='127.0.0.1', port=6379, password='qwe123', encoding='utf-8')
    
    # 放值
    # conn.lpush('my_queue', "root")
    # conn.lpush('my_queue', "good")
    
    # 取值
    v1 = conn.brpop("my_queue", timeout=5)
    print(v1)
    

1.4 worker和线程池

# 1.去redis中获取任务

# 2.再将此订单在数据库中的状态修改为 执行中

# 3.获取任务详细: 视频,10000

# 4.线程池或协程
from concurrent.futures import ThreadPoolExecutor


def task(video_url):
    # 根据视频地址实现刷播放
    # ..
    pass


pool = ThreadPoolExecutor(50)
for i in range(10000):
    pool.submit(task, "视频地址")

pool.shutdown()  # 卡主,等待所有的任务执行完毕。

# 5.更新订单状态,已完成

1.5 ajax请求

  • form表单

    <form method='get' action='xxx' >
        <input />
        <input type='submit' />
    </form>
    
  • ajax

    本质,利用浏览器上XMLhttpRequest
    
    <html>
        <head>
            
        </head>
        <body>
            <script>
            	 xhr = new XMLHttpRequest();
                 xhr.onreadystatechange = function(){
                     if(xhr.readyState == 4){
                        // 已经接收到全部响应数据,执行以下操作
                        var data = xhr.responseText;
                        console.log(data);
                    }
                 };
                 xhr.open('POST', "/test/", true );
                 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;');
                 xhr.send('n1=1;n2=2;');
            </script>
        </body>
    </html>
    
    利用jQuery类库,内部封装,使用简单了。
    
    <html>
    	...
        <body>
            
            <script src='jquery.js'></script>
            <script>
            	$.ajax({
                    url:"....",
                    type:"post",
                    data:{n1:123,n2:456},
                    success:function(res){
                        console.log(res);
                    }
                })
            </script>
        </body>
    </html>
    

大致的应用场景:

  • 提交数据,页面可以刷新 -> form表单
  • 提交数据,不想刷新,ajax形式提交。

1.6 csrf token

  • form表单形式提交

    <form ...>
        {% csrf_token %}  <input type='hidden' value='xxxxxxxx' />
        <input ... />
        <input type='submit' />
    </form>
    
  • ajax方式

    <form>
        {% csrf_token %}
        <input ... />
        <input type='submit' />
    </form>
    
    浏览器打开网址时,django在cookie中也给我们返回了一段值。
    
    $.ajax({
    	url:"...",
    	type:"get"
    	data:{user:"wupeiqi",pwd:"xxxx"},
    	header:{
    		"X-HTTP...":"cookie中写的那段值"
    	},
    	success:function(arg){
    		
    	}
    })
    

1.7 form组件

class LoginForm(forms.Form):
    role = forms.ChoiceField(
        required=True,
        choices=(("2", "客户"), ("1", "管理员")),
        widget=forms.Select(attrs={"class": "form-control"})
    )
    username = forms.CharField(
        initial="wupeiqi",
        required=True,
        正则表达式
        widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "用户名"})
    )
    
    # 自定义方法(钩子)
    def clean_username(self):
        raise 异常
		return 123
    
form = LoginForm(initial={"username":"xxx","password":"xx"})

1.7.1 form中的组件

Field
    required=True,               是否允许为空
    widget=None,                 HTML插件
    label=None,                  用于生成Label标签或显示内容
    initial=None,                初始值
    help_text='',                帮助信息(在标签旁边显示)
    error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
    show_hidden_initial=False,   是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
    validators=[],               自定义验证规则
    localize=False,              是否支持本地化
    disabled=False,              是否可以编辑
    label_suffix=None            Label内容后缀
 
 
CharField(Field)
    max_length=None,             最大长度
    min_length=None,             最小长度
    strip=True                   是否移除用户输入空白
 
IntegerField(Field)
    max_value=None,              最大值
    min_value=None,              最小值
 

DecimalField(IntegerField)
    max_value=None,              最大值
    min_value=None,              最小值
    max_digits=None,             总长度
    decimal_places=None,         小数位长度
 
BaseTemporalField(Field)
    input_formats=None          时间格式化   
 
DateField(BaseTemporalField)    格式:2015-09-01
TimeField(BaseTemporalField)    格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
 
DurationField(Field)            时间间隔:%d %H:%M:%S.%f
    ...
 
RegexField(CharField)
    regex,                      自定制正则表达式
    max_length=None,            最大长度
    min_length=None,            最小长度
    error_message=None,         忽略,错误信息使用 error_messages={'invalid': '...'}
 
EmailField(CharField)      
    ...
 
FileField(Field)
    allow_empty_file=False     是否允许空文件
 
ImageField(FileField)      
    ...
    注:需要PIL模块,pip3 install Pillow
    以上两个字典使用时,需要注意两点:
        - form表单中 enctype="multipart/form-data"
        - view函数中 obj = MyForm(request.POST, request.FILES)

 
ChoiceField(Field)
    ...
    choices=(),                选项,如:choices = ((0,'上海'),(1,'北京'),)
    required=True,             是否必填
    widget=None,               插件,默认select插件
    label=None,                Label内容
    initial=None,              初始值
    help_text='',              帮助提示
 
  • 生成HTML标签 + 携带数据

    - 保留原来提交的数据,不再担心form表单提交时页面刷新。
    - 显示默认值,做编辑页面显示默认值。
    
  • 数据校验,对用户提交的数据格式校验

    form = LoginForm(data=request.POST)
    if form.is_valid():
        print(form.cleaned_data)
    else:
        print(form.errors)
    

1.7.2 form表单提交

  • form的功能:

    生成HTML标签 + 携带数据,数据校验,对用户提交的数据格式校验

  • 生成标签

    • 循环 + 单独某个字段

    • label页面显示文本信息

    • 自定义错误信息位置,position

      <!-- 在Django模板中,表单被渲染成HTML。Django的表单类提供了多种将表单渲染为HTML的方法。-->
      
      <!-- contact.html -->
      <form method="post">
          {% csrf_token %}
          {{ form.as_p }}
          <input type="submit" value="Submit">
      </form>
      使用{{ form.as_p }}将表单字段渲染为拥有<p>标签的段落。
      
  • 校验

    • 验证流程图示

    image-20260519185601372

    • 定义

      完整调用链
      is_valid() errorsfull_clean()_clean_fields()_clean_form()_post_clean()

      • 验证入口:form.is_valid

        class BaseForm:
            def is_valid(self):
                """返回表单数据是否全部验证通过"""
                return self.is_bound and not self.errors
        
            @property
            def errors(self):
                """触发完整验证流程"""
                if self._errors is None:
                    self.full_clean()
                return self._errors
        
        
      • 核心验证流程:form.errors

        class BaseForm:
            def full_clean(self):
                """
                执行完整验证的三阶段:
                1. 字段级验证 (_clean_fields)
                2. 表单级验证 (_clean_form)
                3. 模型级后处理 (_post_clean)
                """
                self._errors = ErrorDict()
                if not self.is_bound:  # 未绑定数据直接返回
                    return
                self.cleaned_data = {}
                
                # 阶段1:逐个字段验证
                self._clean_fields()  
                
                # 阶段2:表单级交叉验证
                self._clean_form()     
                
                # 阶段3:模型表单扩展点
                self._post_clean()    
        
        
      • 字段级验证详解:form.full_clean()

        class BaseForm:
            def full_clean(self):
                """
                执行完整验证的三阶段:
                1. 字段级验证 (_clean_fields)
                2. 表单级验证 (_clean_form)
                3. 模型级后处理 (_post_clean)
                """
                self._errors = ErrorDict()
                if not self.is_bound:  # 未绑定数据直接返回
                    return
                self.cleaned_data = {}
                
                # 阶段1:逐个字段验证
                self._clean_fields()  
                
                # 阶段2:表单级交叉验证
                self._clean_form()     
                
                # 阶段3:模型表单扩展点
                self._post_clean()    
        
        
      • 表单级验证 (_clean_form)

        class BaseForm:
            def _clean_fields(self):
                for name, field in self.fields.items():
                    # 获取原始值
                    raw_value = self.data.get(name, field.initial)
                    
                    try:
                        # 第一步:字段的to_python转换
                        value = field.to_python(raw_value)  
                        
                        # 第二步:执行字段验证器
                        self.clean_validate_field(field, value)
                        
                        # 第三步:执行字段的clean方法
                        value = field.clean(value)          
                        
                        # 第四步:执行clean_<fieldname>钩子
                        value = self.clean_field_hook(name, value)
                        
                        self.cleaned_data[name] = value
                        
                    except ValidationError as e:
                        self.add_error(name, e)
                        
            def clean_validate_field(self, field, value):
                """执行字段的validators验证器"""
                if value in field.empty_values:
                    return
                for validator in field.validators:
                    validator(value)
        
        
        • 执行字段验证器(字段上定义校验规则 正则、空、长度)

          validators=[RegexValidator(r'^1[358]\d{9}$', '手机格式错误'), ],
          
        • 执行字段的clean方法(表单级钩子)

        try:
            cleaned_data = self.clean()
        except ValidationError as e:
            self.add_error(None, e)
        else:
            if cleaned_data is not None:
                self.cleaned_data = cleaned_data
                
        self.errors["__all__"] = 错误
        
        • clean_方法名(字段级钩子)

          def clean_code(self):
              mobile = self.cleaned_data.get("mobile")
              code = self.cleaned_data.get("code")
              if not mobile:
                  return code
          
              # conn = get_redis_connection("default")
              cache_code = '1111'.encode('utf-8')
              if not cache_code:
                  raise ValidationError("短信验证码未发送或失效")
          
                  if code != cache_code.decode('utf-8'):
                      raise ValidationError("短信验证码未发送或失效")
          
                      return code
          '''
          校验成功:
          	self.cleaned_data["字段"] = 值
          校验失败
          	self.errors["字段"] = 错误
          '''
          
          
      • 模型验证扩展 (_post_clean)

      class ModelForm(BaseModelForm):
          def _post_clean(self):
              """模型表单特有验证阶段"""
              opts = self._meta
              
              # 模型实例的clean方法
              try:
                  self.instance.full_clean(exclude=opts.exclude)
              except ValidationError as e:
                  self._update_errors(e)
              
              # 唯一性约束检查(需数据库访问)
              self.validate_unique()
      
      
    • 业务校验

      form = 对象
      
      if not form.is_valid():
          print(form.errors)
          form.errors.username.0
          form.errors.__call__.0   ->  模板语言中 form.errors.__ 不支持
          form.non_field_errors()  <==> form.errors.__call__
          在页面上去适合的位置显示错误信息
      else:
          print(form.cleaned_data)
          ...
      

    image-20220724115728298

    • 扩展

      如果想要让某个错误信息,展示在特定的字段旁,就可以使用:
      form.add_error("password", "用户名或密码错误")
      

      image-20220724120233450

2.刷B站播放量项目设计

2.1 数据表结构相关

2.1.1 人员表结构

from django.db import models


class ActiveBaseModel(models.Model):
    active = models.SmallIntegerField(verbose_name="状态", default=1, choices=((1, "激活"), (0, "删除"),))

    class Meta:#用来保证不创建额外的数据库
        abstract = True


class Administrator(ActiveBaseModel):
    """ 管理员表 """
    username = models.CharField(verbose_name="用户名", max_length=32, db_index=True)
    password = models.CharField(verbose_name="密码", max_length=64)
    mobile = models.CharField(verbose_name="手机号", max_length=11, db_index=True)
    create_date = models.DateTimeField(verbose_name="创建日期", auto_now_add=True)


class Level(ActiveBaseModel):
    """ 级别表 """
    title = models.CharField(verbose_name="标题", max_length=32)
    percent = models.IntegerField(verbose_name="折扣")


class Customer(ActiveBaseModel):
    """ 客户表 """
    username = models.CharField(verbose_name="用户名", max_length=32, db_index=True)
    password = models.CharField(verbose_name="密码", max_length=64)
    mobile = models.CharField(verbose_name="手机号", max_length=11, db_index=True)
    balance = models.DecimalField(verbose_name="账户余额", default=0, max_digits=10, decimal_places=2)
    level = models.ForeignKey(verbose_name="级别", to="Level", on_delete=models.CASCADE)
    create_date = models.DateTimeField(verbose_name="创建日期", auto_now_add=True)
    creator = models.ForeignKey(verbose_name="创建者", to="Administrator", on_delete=models.CASCADE)

2.1.2 订单表结构

class PricePolicy(models.Model):
    """ 价格策略(原价,后续可以根据用级别不同做不同折扣)
    1  1000 10
    2  2000 18
    """
    count = models.IntegerField(verbose_name="数量")
    price = models.DecimalField(verbose_name="价格", default=0, max_digits=10, decimal_places=2)


class Order(ActiveBaseModel):
    """ 订单表 """
    status_choices = (
        (1, "待执行"),
        (2, "正在执行"),
        (3, "已完成"),
        (4, "失败"),
    )
    status = models.SmallIntegerField(verbose_name="状态", choices=status_choices, default=1)

    # 202211022123123123
    oid = models.CharField(verbose_name="订单号", max_length=64, unique=True)
    url = models.URLField(verbose_name="视频地址", db_index=True)
    count = models.IntegerField(verbose_name="数量")

    price = models.DecimalField(verbose_name="价格", default=0, max_digits=10, decimal_places=2)
    real_price = models.DecimalField(verbose_name="实际价格", default=0, max_digits=10, decimal_places=2)

    old_view_count = models.CharField(verbose_name="原播放量", max_length=32, default="0")

    create_datetime = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)
    customer = models.ForeignKey(verbose_name="客户", to="Customer", on_delete=models.CASCADE)
    memo = models.TextField(verbose_name="备注", null=True, blank=True)


class TransactionRecord(ActiveBaseModel):
    """ 交易记录 """
    charge_type_class_mapping = {
        1: "success",
        2: "danger",
        3: "default",
        4: "info",
        5: "primary",
    }
    charge_type_choices = ((1, "充值"), (2, "扣款"), (3, "创建订单"), (4, "删除订单"), (5, "撤单"),)
    charge_type = models.SmallIntegerField(verbose_name="类型", choices=charge_type_choices)

    customer = models.ForeignKey(verbose_name="客户", to="Customer", on_delete=models.CASCADE)
    amount = models.DecimalField(verbose_name="金额", default=0, max_digits=10, decimal_places=2)

    creator = models.ForeignKey(verbose_name="管理员", to="Administrator", on_delete=models.CASCADE, null=True, blank=True)

    order_oid = models.CharField(verbose_name="订单号", max_length=64, null=True, blank=True, db_index=True)
    create_datetime = models.DateTimeField(verbose_name="交易时间", auto_now_add=True)
    memo = models.TextField(verbose_name="备注", null=True, blank=True)

2.1.3 往表中插入一些数据

  • scrips/init_admin
# 启动django
import os
import sys
import django

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(base_dir)

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'day06.settings')
django.setup()  # 伪造让django启动

from web import models
from utils.encrypt import md5

models.Administrator.objects.create(username='root', password=md5("root"), mobile="1888888889")

#也可以直接通过命令python manage.py shell 来在终端执行这行代码

  • scrips/init_customer
# 启动django
import os
import sys
import django

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(base_dir)

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'day06.settings')
django.setup()  # 伪造让django启动

from web import models
from utils.encrypt import md5

# 创建级别
# level_object = models.Level.objects.create(title="VIP", percent=90)

models.Customer.objects.create(
    username='xinchen',
    password=md5("xinchen"),
    mobile='1999999998',
    level_id=1,
    creator_id=1
)

2.2 用户认证相关

2.2.1 前端登录界面展示

  • 用户名和密码登录

    {% load static %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <link rel="stylesheet" href="{% static 'plugins/bootstrap/css/bootstrap.css' %}">
        <style>
            .box {
                width: 450px;
                border: 1px solid #f0f0f0;
                margin-left: auto;
                margin-right: auto;
                margin-top: 100px;
    
                padding-left: 40px;
                padding-right: 40px;
                padding-bottom: 30px;
    
                box-shadow: 5px 10px 10px rgb(0 0 0 / 5%);
            }
        </style>
    </head>
    <body>
    
    <div class="box">
        <h2 style="text-align: center;">用户登录</h2>
        <form method="post">
            {% csrf_token %}
            {% for field in form %}
                <div class="form-group" style="position: relative;margin-bottom:25px; ">
                    <label>{{ field.label }}</label>
                    {{ field }}
                    <span style="color: red;position: absolute">{{ field.errors.0 }}</span>
                </div>
    
            {% endfor %}
            <h1>{{ form.non_field_errors.0 }}</h1>
            <button type="submit" class="btn btn-primary">登 录</button>
            <span style="color: red;">{{ error }}</span>
    
            <a href='{% url 'sms_login' %}' style="float: right;">短信登录</a>
        </form>
    </div>
    
    </body>
    </html>
    
    
  • 短信登录(ajax请求)

    {% load static %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <link rel="stylesheet" href="{% static 'plugins/bootstrap/css/bootstrap.css' %}">
        <style>
            .box {
                width: 450px;
                border: 1px solid #f0f0f0;
                margin-left: auto;
                margin-right: auto;
                margin-top: 100px;
    
                padding-left: 40px;
                padding-right: 40px;
                padding-bottom: 30px;
    
                box-shadow: 5px 10px 10px rgb(0 0 0 / 5%);
            }
    
            .error-message {
                color: red;
                position: absolute;
            }
        </style>
        <script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
        <script src="{% static 'js/csrf.js' %}"></script>
    </head>
    <body>
    <div class="box">
        <form method="post" id="smsForm">
            <h2 style="text-align: center;">短信登录</h2>
            {% csrf_token %}
    
            {% for field in form %}
                {% if field.name == 'code' %}{#修改验证码的style类型,使其和输入框位于同一行#}
                    <div class="form-group" style="position: relative;margin-bottom: 25px">
                        <label>{{ field.label }}</label>
                        <div class="row">
                            <div class="col-xs-7">
                                {{ field }}
                                <span class="error-message">{{ field.errors.0 }}</span>
                            </div>
                            <div class="col-xs-5">
                                <input id="sendBtn" type="button" value="发送短信" class="btn btn-default"/>
                            </div>
                        </div>
                    </div>
                {% else %}
                    <div class="form-group" style="position: relative;margin-bottom: 25px">
                        <div class="form-group">
                            <label>{{ field.label }}</label>
                            {{ field }}
                            <span class="error-message">{{ field.errors.0 }}</span>
                        </div>
                    </div>
                {% endif %}
            {% endfor %}
            <button type="button" class="btn btn-primary" id="loginBtn">登 录</button>
            <a href="{% url 'login' %}" style="float: right;">用户名登录</a>
        </form>
    </div>
    
    
    <script>
        $(function () {
            // 当页面框架加载完成之后,自动执行里面的代码。
            bindSendSmsEvent();
    
            bindLoginEvent();
        })
    
        function bindLoginEvent() {
            $("#loginBtn").click(function () {
                // 清楚所有的错误
                $(".error-message").empty();
    
                $.ajax({
                    url: "{% url 'sms_login' %}",
                    type: "POST",
                    data: $("#smsForm").serialize(),
                    dataType: "JSON",
                    success: function (res) {
                        console.log(res);
                        if (res.status) {
                            // res.data = "/level/list/
                            location.href = res.data;
                        } else {
                            $.each(res.detail, function (k, v) {
                                $("#id_" + k).next().text(v[0]);
                            })
                        }
                    }
                })
            });
        }
    
        function bindSendSmsEvent() {
            // 按钮绑定点击事件
            $("#sendBtn").click(function () {
                $(".error-message").empty();
                $.ajax({
                    url:"{% url 'sms_send' %}",
                    type:"POST",
                    data:{
                        mobile:$("#id_mobile").val(),
                        role:$("#id_role").val()
                    },
                    dataType:"JSON",
                    success:function (res){
                        console.log(res);
                        if(res.status){
                            sendSmsRemind()
                        }else{
                            $.each(res.detail,function (k,v){
                                $("#id_"+k).next().text(v[0])
                            })
    
                        }
                    }
    
    
                })
    
            });
        }
    
        /**
         * 发送短信按钮倒计时效果
         */
        function sendSmsRemind() {
            var $smsBtn = $("#sendBtn");
            // 2.1 禁用
            $smsBtn.prop("disabled", true);
    
            // 2.2 改内容
            var time = 60;
            var remind = setInterval(function () {
                $smsBtn.val(time + "秒重新发送");
                time = time - 1;
                if (time < 1) {
                    clearInterval(remind);
    
                    $smsBtn.val("点击获取验证码");
                    $smsBtn.prop("disabled", false);
                }
            }, 1000);
        }
    
    
    </script>
    
    
    </body>
    </html>
    
    

2.2.2 后端管理登录请求

登录成功后,保存用户的信息session【文件、数据库、缓存中】

  • forms/account.py

    # -*- coding: utf-8 -*-
    '''
    @IDE : PyCharm
    @version : 3.9
    @Auth : 恍惚
    @time : 2026/5/19 18:08
    @Description: Null
    '''
    
    from django.core.validators import RegexValidator,ValidationError
    from utils.encrypt import md5
    # 1.定义类
    from django import forms
    
    class LoginForm(forms.Form):
        role = forms.ChoiceField(
            required=True,
            choices = (("2", "客户"), ("1", "管理员")),
            widget=forms.Select(attrs={"class": "form-control"}),
            label="角色"
        )
    
        username = forms.CharField(
            required=True,
            widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "用户名"}),
            label="用户名"
        )
        password = forms.CharField(
            required=True,
            min_length=6,
            max_length=12,
            validators=[RegexValidator(r'^[0-9]+$', '密码必须是数字'), ],
            widget=forms.PasswordInput(attrs={"class": "form-control", "placeholder": "密码"},
                                       render_value=True),
            label="密码"
        )
    
        def clean_username(self):
            user = self.cleaned_data['username']
            # 校验规则
            # 校验失败
            if len(user) < 3:
                from django.core.exceptions import ValidationError
                raise ValidationError("用户名格式错误")
            else:
                return user
    
        def clean_password(self):
            return md5(self.cleaned_data['password'])
    
        def clean(self):
            # 对所有值进行校验
            from django.core.exceptions import ValidationError
            # 1.不返回值,默认 self.cleaned_data
            # 2.返回值,self.cleaned_data=返回的值
            # 3.报错,ValidationError ->  self.add_error(None, e)
            pass
    
    class SmsForm(forms.Form):
        role = forms.ChoiceField(
            label="角色",
            required=True,
            choices=(("2", "客户"), ("1", "管理员")),
            widget=forms.Select(attrs={"class": "form-control"})
        )
    
        mobile = forms.CharField(
            label="手机号",
            validators=[RegexValidator(r'^1[358]\d{9}$', '手机格式错误'), ],
            widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "手机号"})
        )
    
        code = forms.CharField(
            label="短信验证码",
            validators=[RegexValidator(r'^[0-9]{4}$', '验证码格式错误'), ],
            widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "短信验证码"})
        )
    
    
        def clean_code(self):
            mobile = self.cleaned_data.get("mobile")
            code = self.cleaned_data.get("code")
            if not mobile:
                return code
    
            # conn = get_redis_connection("default")
            cache_code = '1111'.encode('utf-8')
            if not cache_code:
                raise ValidationError("短信验证码未发送或失效")
    
            if code != cache_code.decode('utf-8'):
                raise ValidationError("短信验证码未发送或失效")
    
            return code
    
    class MobileForm(forms.Form):
        role = forms.ChoiceField(
            label="角色",
            required=True,
            choices=(("2", "客户"), ("1", "管理员")),
            widget=forms.Select(attrs={"class": "form-control"})
        )
    
        mobile = forms.CharField(
            label="手机号",
            required=True,
            validators=[RegexValidator(r'^1[358]\d{9}$', '手机格式错误'), ]
        )
    
    
       
    
  • utils/response.py

    class BaseResponse(object):
        def __init__(self): #固定返回值的类型
            self.status = False
            self.detail = None
            self.data = None
    
        @property
        def dict(self):
            return self.__dict__
    
    
  • views/account.py

    from django.shortcuts import render, redirect
    from web import models
    from utils.encrypt import md5
    from django.http import JsonResponse
    from utils.response import BaseResponse
    from django.conf import settings
    
    # 1.定义类
    from web.forms.account import LoginForm,MobileForm,SmsForm
    
    
    def login(request):
        if request.method == "GET":
            form = LoginForm() #initial={"username":"xxx","password":"xx"}
            return render(request, "login.html", {"form": form})
    
        # 1.接收并获取数据(数据格式或是否为空验证 - Form组件 & ModelForm组件)
        form = LoginForm(data=request.POST)
        if not form.is_valid():
            return render(request, "login.html", {"form": form})
    
        # {'role': '1', 'username': 'asdfasdf', 'password': '123123'}
        # print(form.cleaned_data)  # {"username":'',"password":'xxx","role":xxx}
        role = form.cleaned_data.get("role")
        username = form.cleaned_data.get("username")
        password = form.cleaned_data.get("password")
        password = md5(password)
    
        # 2.去数据库校验  1管理员  2客户
        mapping = {"1": "ADMIN", "2": "CUSTOMER"}
        if role not in mapping:
            return render(request, "login.html", {'form': form, 'error': "角色不存在"})
    
        if role == "1":
            user_object = models.Administrator.objects.filter(active=1, username=username, password=password).first()
        else:
            user_object = models.Customer.objects.filter(active=1, username=username, password=password).first()
    
        # 2.1 校验失败
        if not user_object:
            return render(request, "login.html", {'form': form, 'error': "用户名或密码错误"})
    
        # 2.2 校验成功,用户信息写入session+进入项目后台
        request.session['user_info'] = {'role': mapping[role], 'name': user_object.username, 'id': user_object.id}
        print("success")
        return redirect("/level/list/")
    
    def sms_send(request):
        """ 发送短信  """
        res = BaseResponse()
    
        # 校验数据合法性:手机号的格式 + 角色
        form = MobileForm(data=request.POST)
        if not form.is_valid():
            res.detail = form.errors
            return JsonResponse(res.dict, json_dumps_params={"ensure_ascii": False})
        res.status = True
        return JsonResponse(res.dict)
    
    
    def sms_login(request):
        if request.method == "GET":
            form = SmsForm()
            return render(request, "sms_login.html", {'form': form})
    
        res = BaseResponse()
        # 1.手机格式校验
        form = SmsForm(data=request.POST)
    
        if not form.is_valid():
            res.detail = form.errors
            return JsonResponse(res.dict)
    
        role = form.cleaned_data['role']
        mobile = form.cleaned_data['mobile']
        # 3.登录成功 + 注册  (监测手机号是否存在)
        #     - 未注册,自动注册
        #     - 已注册,直接登录
        if role == "1":
            user_object = models.Administrator.objects.filter(active=1, mobile=mobile).first()
        else:
            user_object = models.Customer.objects.filter(active=1, mobile=mobile).first()
    
        if not user_object:
            res.detail = {"mobile": ["手机号不存在"]}
            return JsonResponse(res.dict)
    
        # 2.2 校验成功,用户信息写入session+进入项目后台
        mapping = {"1": "ADMIN", "2": "CUSTOMER"}
        request.session['user_info'] = {'role': mapping[role], 'name': user_object.username, 'id': user_object.id}
        res.status = True
        res.data = settings.LOGIN_HOME
        return JsonResponse(res.dict)
    
    

2.3 权限管理

2.3.1 中间件

class UserInfo(object):
    def __init__(self, role, name, id):
        self.id = id
        self.name = name
        self.role = role
        self.menu_name = None
        self.text_list = []


def is_white_url(self, request):
    if request.path_info in settings.NB_WHITE_URL:
        return True
'''
process_request,基于他实现用户是否已登录,继续;未登录则返回登录界面。
	- return None,继续向后访问
	- return 对象,直接返回。
'''
def process_request(self, request):
    '''检验用户是否已经登录'''
    # 1.不需要登录就能访问的URL
    if self.is_white_url(request):
        return

    if request.path_info in settings.NB_WHITE_URL:
        return

    user_dict = request.session.get(settings.NB_SESSION_KEY)
    if not user_dict:
        return redirect(settings.NB_LOGIN_URL)

    request.nb_user = UserInfo(**user_dict)


'''
process_view,权限校验
	- return None,继续向后访问
	- return 对象,直接返回。
	- 在他的request对象中有 resolver_match  ,包含当前请求的视图路由信息  .name->sms_login
		admin = ['sms_login',"xxx"]
'''

def process_view(self, request, callback, callback_args, callback_kwargs):
    # 1.不需要登录就能访问的URL
    if self.is_white_url(request):
        return
    '''检验用户是否有权限访问当前页面'''
    match = request.resolver_match.url_name
    if match in settings.NB_PERMISSION_PUBLIC: #公共权限
        return
    #获取用户角色具备权限

    user_permission_dict = settings.NB_PERMISSION[request.nb_user.role]
    #判断自己是否具有权限
    if match not in user_permission_dict:
        return render(request,'permission.html')

    # 有权限 → 生成导航:[{'text':'首页','url':'home'}, ...]
    breadcrumb_list = []

    menu_name = match
    # 循环获取所有父级菜单
    while menu_name:
        item = {
            'text': user_permission_dict[menu_name]['text'],
            'url': menu_name  # 把URL别名存进去
        }
        breadcrumb_list.append(item)
        # 找父级
        menu_name = user_permission_dict[menu_name]['parent']

        # 反转顺序 → 首页在最前面
        breadcrumb_list.reverse()

        # 赋值给 request
        request.nb_user.breadcrumb_list = breadcrumb_list

        # 当前菜单
        request.nb_user.menu_name = match

2.3.2 权限信息 settings

NB_MENU = {
    'ADMIN': [
        {
            'text': "用户信息",
            'icon': "fa-bed",
            'children': [
                {'text': "级别管理", 'url': "/level/list/", 'name': "level_list"},
            ]
        },
        {
            'text': "管理信息",
            'icon': "fa-code",
            'children': [
                {'text': "人员管理", 'url': "/customer/list/", 'name': "customer_list"},
                {'text': "价格策略", 'url': "/policy/list/", 'name': "policy_list"}
                .....
            ]
        }


    ],
    'CUSTOMER': [
        {
            'text': "用户信息",
            'icon': "fa-bed",
            'children': [
            ]
        },
    ],
}
NB_PERMISSION_PUBLIC = {
    "home": {"text": "主页", 'parent': None},
    "login": {"text": "登录", 'parent': None},
    "logout": {"text": "注销", 'parent': None},
}
NB_PERMISSION = {
    "ADMIN": {
        "level_list": {"text": "级别列表", 'parent': None},
              .....,
    },
    "CUSTOMER": {
        "level_list": {"text": "级别列表", 'parent': None},
              .....
    }

}

2.4 菜单相关

2.4.1 模板html

  • layout.html, 标准模板框架,搭建上边框+左边框+头像
{% load static %}
{% load menu %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static '/plugins/bootstrap/css/bootstrap.min.css' %}">
    <link rel="stylesheet" href="{% static '/plugins/font-awesome/css/font-awesome.min.css' %}">
    <link rel="stylesheet" href="{% static '/css/commons.css' %}">
    <link rel="stylesheet" href="{% static '/css/menu.css' %}">
    <link rel="stylesheet" href="{% static '/css/nav.css' %}">
</head>
<body>
<div class="pg-header">
    <div class="nav">
        <div class="logo-area left">
            <a href="{% url 'home' %}">
                <span style="font-size: 18px;">NB的管理平台 </span>
            </a>
        </div>
        <div class="right-menu right clearfix">

            <div class="user-info right">
                <a href="#" class="avatar" style="text-decoration: none;">
                    <span style="color: white;margin-right: 5px;">{{ request.nb_user.name }}</span>
                    <img class="img-circle" style="width: 35px;height: 35px;" src="{% static 'images/default.png' %}">
                </a>

                <div class="more-info">
                    <a href="{% url 'logout' %}" class="more-item">注销1</a>
                    <a href="{% url 'logout' %}" class="more-item">注销2</a>
                    <a href="{% url 'logout' %}" class="more-item">注销3</a>
                    <a href="{% url 'logout' %}" class="more-item">注销</a>
                </div>
            </div>
        </div>
    </div>
</div>
<div class="pg-body">
    <div class="left-menu">
        <div class="menu-body">
            {% nb_menu request %}
        </div>
    </div>
    <div class="right-body">
        {% if request.nb_user.text_list %}
            <ol class="breadcrumb">
                {% for text in request.nb_user.text_list %}
                    <li><a>{{ text }}</a></li>
                {% endfor %}
            </ol>
        {% endif %}
        <div style="padding: 20px;">
            {% block content %}{% endblock %}
        </div>
    </div>
</div>

<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap/js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/menu.js' %}"></script>
</body>
</html>

  • form.html(form表单框架,通过遍历form中的fields字段生成一行行的输入框)

    {% extends 'layout.html' %}
    
    {% block content %}
    
        <form method="post" novalidate>
            {% for field in form %}
                {% csrf_token %}
                <div class="form-group"  style="position: relative;margin-bottom: 25px">
                    <label for="{{ field.id_for_label }}">
                        {{ field.label }}
                        {% if field.help_text %}
                            <span style="font-weight: 400;color: #333333;">({{ field.help_text }})</span>
                        {% endif %}
                    </label>
                    {{ field }}
                    <span style="color: red;position: absolute;">{{ field.errors.0 }}</span>
                </div>
            {% endfor %}
    
            <button type="submit" class="btn btn-primary">保 存</button>
        </form>
    
    {% endblock %}
    {#
    class CustomerEditModelForm(BootStrapForm,forms.ModelForm):
        class Meta:
            model = models.Customer
            fields = ["username",'password','level']
            widgets = {
                'password':forms.PasswordInput(render_value=True),
            }
        def __init__(self, request, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            # 此处可能用到 request
            self.fields['level'].queryset = models.Level.objects.filter(active=1)
    
        def clean_password(self):
            password = self.cleaned_data.get("password")
            return md5(password)
    
    form = CustomerEditModelForm(request, data=request.POST, instance=instance)
    return render(request, 'form2.html', {"form": form})
    
    #}
    
  • nb_menu(菜单标签,用来显示左边菜单栏中的内容)

    <div class="multi-menu">
        {% for item in menu_list %}
            <div class="item">
                <div class="title">
                    <span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span> {{ item.text }}
                </div>
                <div class="body {{ item.class }}">
                    {% for child in item.children %}
                        <a class="{{ child.class }}" href="{{ child.url }}">{{ child.text }}</a>
                    {% endfor %}
                </div>
            </div>
        {% endfor %}
    </div>
    
    from django.template import Library
    from django.conf import settings
    import copy
    
    register = Library()
    
    
    @register.inclusion_tag("tag/nb_menu.html")
    def nb_menu(request):
        # 1.读取当前用户的角色信息
        # 2.菜单信息
        user_menu_list = copy.deepcopy(settings.NB_MENU[request.nb_user.role])
        for item in user_menu_list:
            # item['class'] = 'hide'
            for child in item['children']:
                # if child['url'] == request.path_info: # v1版
                if child['name'] == request.nb_user.menu_name:
                    child['class'] = 'active'
                    # item['class'] = ""
    
        return {'menu_list': user_menu_list}
    
  • utils/bootstrap.py样式优化

    ### 2.2 BootStrap样式优化
    
    ```python
    from django.shortcuts import render, redirect
    from web import models
    from django import forms
    from django.urls import reverse
    
    
    class BootStrapForm:
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            # {'title':对象,"percent":对象}
            for name, field in self.fields.items():
                field.widget.attrs['class'] = "form-control"
                field.widget.attrs['placeholder'] = "请输入{}".format(field.label)
    
    
    class LevelForm(BootStrapForm, forms.Form):
        title = forms.CharField(
            label="标题",
            required=True,
        )
        percent = forms.CharField(
            label="折扣",
            required=True,
            help_text="填入0-100整数表示百分比,例如:90,表示90%"
        )
    
    
    class LevelModelForm(BootStrapForm, forms.ModelForm):
        class Meta:
            model = models.Level
            fields = ['title', 'percent']
    ```
    
    
    

2.4.2 级别管理菜单

  • level_list.html(前端显示级别列表)

    {% extends 'layout.html' %}
    {% block content %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <div class="level_table" >
        <a href="{% url 'level_add' %}" class="btn btn-success"><span class="glyphicon glyphicon-plus-sign"></span>添加</a>
        <table class="table table-bordered" border="1px">
            <thead>
            <tr>
                <th>id</th>
                <th>级别</th>
                <th>折扣</th>
                <th>操作</th>
            </tr>
            </thead>
            <tbody>
            {% for row in queryset %}
                <tr>
                    <td>{{ row.id }}</td>
                    <td>{{ row.title }}</td>
                    <td>{{ row.percent }}</td>
                    <td>
                        <a href="{% url 'level_edit' pk=row.id %}" class="btn btn-primary btn-sm">编辑</a>
                        <a href="{% url 'level_delete' pk=row.id %}" class="btn btn-danger btn-sm">删除</a>
                    </td>
                </tr>
    
            {% endfor %}
            </tbody>
        </table>
    
    </div>
    
    </body>
    </html>
    {% endblock %}
    
  • forms/account.py(存放form格式的数据,根据这种格式生成相应的输入框,如title和percent)

    class LevelForm(BootStrapForm, forms.Form):
        title = forms.CharField(
            label="标题",
            required=True,
        )
        percent = forms.CharField(
            label="折扣",
            required=True,
            help_text="填入0-100整数表示百分比,例如:90,表示90%"
        )
    
    
    class LevelModelForm(BootStrapForm, forms.ModelForm):
        confirm_percent = forms.CharField(label="确认")
    
        class Meta:
            model = models.Level
            fields = ['title', 'percent']
            # widgets = {
            #     'title': forms.TextInput(attrs={"class": "form-control", "placeholder": "请输入标题"}),
            #     "percent":forms.TextInput(attrs={"class": "form-control", "placeholder": "请输入折扣"}),
            # }
    
  • views/level.py(后端处理级别数据需要用到的函数,如edit和delete)

    from django.shortcuts import render, redirect
    from web import models
    from django import forms
    from django.urls import reverse
    from web.forms.account import LevelForm,LevelModelForm
    
    
    def level_list(request):
        queryset = models.Level.objects.filter(active=1)
        return render(request, 'level_list.html', {"queryset": queryset})
    
    
    def level_add(request):
        if request.method == "GET":
            form = LevelModelForm()
            return render(request, 'form.html', {"form": form})
    
        form = LevelModelForm(data=request.POST)
        if not form.is_valid():
            return render(request, 'form.html', {"form": form})
    
        # {'title': '1', 'percent': 2, 'confirm_percent': '3'}
        # form.instance.percent = 10
        form.save()
    
        return redirect(reverse('level_list'))
    
    
    def level_edit(request, pk):
        level_object = models.Level.objects.filter(id=pk, active=1).first()
        # print(level_object.__dict__)
        if request.method == "GET":
            form = LevelModelForm(instance=level_object)
            return render(request, 'form.html', {"form": form})
    
        form = LevelModelForm(data=request.POST, instance=level_object)
        if not form.is_valid():
            return render(request, 'form.html', {"form": form})
    
        # form.instance.percent = 10
        form.save()
        return redirect(reverse('level_list'))
    
    
    def level_delete(request, pk):
        models.Level.objects.filter(id=pk).update(active=1)
        return redirect(reverse('level_list'))
    

2.4.3 角色管理

  • templates/customer_list.html(客户管理html页面)

    {% extends "layout.html" %}
    {% load static %}
    {% block content %}
        <div style="margin-bottom: 5px">
            <a href="{% url 'customer_add' %}" class="btn btn-success">
                <span class="glyphicon glyphicon-plus-sign"></span> 新建
            </a>
    
            <div class="right">
                <form class="form-inline" method="get">
                    <div class="form-group">
                        <input name="keyword" type="text" class="form-control" placeholder="请输入关键字"
                               value="{{ keyword }}">
                    </div>
                    <button type="submit" class="btn btn-default">
                        <span class="glyphicon glyphicon-search"></span>
                    </button>
                </form>
            </div>
        </div>
        <table class="table table-bordered">
            <thead>
            <tr>
                <th>ID</th>
                <th>用户名</th>
                <th>手机号</th>
                <th>账户余额</th>
                <th>级别</th>
                <th>注册时间</th>
                <th>重置密码</th>
                <th>操作</th>
            </tr>
            </thead>
            <tbody>
            {% for row in queryset %}
                <tr row-id="{{ row.id }}">
                    <td>{{ row.id }}</td>
                    <td>{{ row.username }}</td>
                    <td>{{ row.mobile }}</td>
                    <td>{{ row.balance }}</td>
                    <td>{{ row.level.title }}({{ row.level.percent }}%)</td>
                    <td>{{ row.create_date|date:"Y-m-d H:i:s" }}</td>
                    <td>
                        <a href="{% url 'customer_reset' pk=row.id %}">重置密码</a>
                    </td>
                    <td>
                        <a href="{% url 'customer_edit' pk=row.id %}" class="btn btn-primary btn-xs">编辑</a>
                        <a cid="{{ row.id }}" class="btn btn-danger btn-xs btn-delete">删除</a>
                    </td>
                </tr>
            {% endfor %}
    
            </tbody>
        </table>
        {% include 'include/delete_model.html' %}
        <div class="pagination">
            {{ pager_string }}
        </div>
    
    {% endblock %}
    
    {% block js %}
        <script src="{% static 'js/delete.js' %}"></script>
        <script>
            var DELETE_ID;
            var DELETE_URL = "{% url 'customer_delete' %}";
        </script>
    
    {% endblock %}
    
    
  • forms/customer.py(form格式角色)

    from django import forms
    from utils.bootstrap import BootStrapForm
    from django.core.exceptions import ValidationError
    from web import models
    from utils.encrypt import md5
    class CustomerEditModelForm(BootStrapForm,forms.ModelForm):
        class Meta:
            model = models.Customer
            fields = ["username",'password','level']
            widgets = {
                'password':forms.PasswordInput(render_value=True),
            }
        def __init__(self, request, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            # 此处可能用到 request
            self.fields['level'].queryset = models.Level.objects.filter(active=1)
    
        def clean_password(self):
            password = self.cleaned_data.get("password")
            return md5(password)
    
    class CustomerModelForm(BootStrapForm,forms.ModelForm):
        exclude_field_list = ['level']
        confirm_password = forms.CharField(
            label="重复密码",
            widget=forms.PasswordInput(render_value=True)
        )
        class Meta:
            model = models.Customer
            fields = ["username", 'password', 'confirm_password','mobile', 'level']
            widgets = {
                'password': forms.PasswordInput(render_value=True),
                'level': forms.RadioSelect(attrs={'class': "form-radio"})
            }
    
        def __init__(self,request,*args,**kwargs):
            super().__init__(*args,**kwargs)
            self.fields['level'].queryset = models.Level.objects.filter(active=1)
    
        def clean_password(self):
            password = self.cleaned_data['password']
    
            return md5(password)
    
        def clean_confirm_password(self):
            password = self.cleaned_data.get('password')
            confirm_password = md5(self.cleaned_data.get('confirm_password', ''))
    
            if password != confirm_password:
                raise ValidationError("密码不一致")
            return confirm_password
    
    class CustomerResetModelForm(BootStrapForm, forms.ModelForm):
        confirm_password = forms.CharField(
            label="重复密码",
            widget=forms.PasswordInput(render_value=True)
        )
    
        class Meta:
            model = models.Customer
            fields = ['password', 'confirm_password']
            widgets = {
                'password': forms.PasswordInput(render_value=True),
            }
    
        def clean_password(self):
            password = self.cleaned_data['password']
    
            return md5(password)
    
        def clean_confirm_password(self):
            password = self.cleaned_data.get('password')
            confirm_password = md5(self.cleaned_data.get('confirm_password', ''))
    
            if password != confirm_password:
                raise ValidationError("密码不一致")
            return confirm_password
    
    
  • views/customer.py(后端逻辑)

    from web import models
    from django.shortcuts import render, redirect, reverse
    from web.forms.customer import CustomerEditModelForm, CustomerModelForm, CustomerResetModelForm
    from utils.response import BaseResponse
    from django.http import JsonResponse
    from utils.paper import Pagination
    from django.db.models import Q
    
    
    def customer_list(request):
        keyword = request.GET.get("keyword", '').strip()
        q1 = Q(active=1)
        q2 = Q()
        if keyword:
            q2.connector = "OR"
            q2.children.append(("username__contains", keyword))
            q2.children.append(("mobile__contains", keyword))
            q2.children.append(("level__title__contains", keyword))
        con = q1 & q2
        querset = models.Customer.objects.filter(con).select_related("level")
        obj = Pagination(request, querset)
        context = {
            "queryset": obj.queryset,
            "pager_string": obj.html(),
            "keyword": keyword
        }
        return render(request, 'customer_list.html', context)
    
    
    def customer_edit(request, pk):
        instance = models.Customer.objects.filter(id=pk, active=1).first()
        if request.method == "GET":
            form = CustomerEditModelForm(request, instance=instance)
            return render(request, 'form.html', {"form": form})
    
        print(request.POST)
        form = CustomerEditModelForm(request, data=request.POST, instance=instance)
        if not form.is_valid():
            return render(request, 'form.html', {"form": form})
        form.save()
        return redirect(reverse('customer_list'))
    
    
    def customer_add(request):
        if request.method == "GET":
            form = CustomerModelForm(request)
            return render(request, 'form2.html', {"form": form})
        form = CustomerModelForm(request, data=request.POST)
        if not form.is_valid():
            return render(request, 'form2.html', {"form": form})
        form.save()
        return redirect(reverse('customer_list'))
    
    
    def customer_reset(request, pk):
        if request.method == "GET":
            form = CustomerResetModelForm()
            return render(request, 'form.html', {'form': form})
        instance = models.Customer.objects.filter(id=pk, active=1).first()
        form = CustomerResetModelForm(data=request.POST, instance=instance)
        if not form.is_valid():
            return render(request, 'form.html', {'form': form})
        form.save()
        return redirect("/customer/list/")
    
    
    def customer_delete(request):
        cid = request.GET.get('cid', 0)
        if not cid:
            res = BaseResponse(status=False, detail="请选择要删除的数据")
            return JsonResponse(res.dict)
        exists = models.Customer.objects.filter(id=cid, active=1).exists()
        if not exists:
            res = BaseResponse(status=False, detail="数据不存在")
            return JsonResponse(res.dict)
        models.Customer.objects.filter(id=cid).update(active=0)
        res = BaseResponse(status=True)
        return JsonResponse(res.dict)
    
    
    
    def customer_charge(request,pk):
        queryset = models.TransactionRecord.objects.filter(customer_id=pk,customer__active=1,active=1).select_related('customer').order_by('id')
        pager = Pagination(request,queryset)#创建分页菜单
        form = ChargeModelForm(request,queryset)
        return render(request,'customer_charge.html',{"pager": pager, "form": form, 'pk': pk})
    
    def customer_charge_add(request,pk):
        form = ChargeModelForm(data=request.POST)
        if not form.is_valid():
            return JsonResponse({"status": False, "detail": form.errors})
    
        #校验成功
        amount = form.cleaned_data.get('amount')
        charge_type = form.cleaned_data.get('charge_type')
        try:
            with transaction.atomic():
                # 1.对当前操作的客户进行更新操作,账户余额:增加、减少 + 锁
                cus_object = models.Customer.objects.filter(id=pk, active=1).select_for_update().first()
    
                if charge_type == 2 and cus_object.balance < amount:
                    return JsonResponse(
                        {'status': False, 'detail': {"amount": ["余额不足,账户总余额只有:{}".format(cus_object.balance)]}})
    
                if charge_type == 1:
                    cus_object.balance = cus_object.balance + amount
                else:
                    cus_object.balance = cus_object.balance - amount
                cus_object.save()
    
                # 2.交易记录
                form.instance.customer = cus_object
                form.instance.creator_id = request.nb_user.id
                form.save()
    
        except Exception as e:
            return JsonResponse({'status': False, 'detail': {"amount": ["操作失败"]}})
    
        return JsonResponse({'status': True})
    
    

2.4.4 价格策略

  • views/policy.py

    # -*- coding: utf-8 -*-
    '''
    @IDE : PyCharm
    @version : 3.9
    @Auth : 恍惚
    @time : 2026/5/30 12:03
    @Description: Null
    '''
    from web import models
    from utils.paper import Pagination
    from django.shortcuts import render,redirect,reverse
    from web.forms.policy import PolicyModelForm
    from utils.response import BaseResponse
    from django.http import JsonResponse
    
    def policy_list(request):
        queryset = models.PricePolicy.objects.all().order_by('count')
        pager = Pagination(request,queryset)
        return render(request,'policy_list.html',{'queryset':pager.queryset,'page_string':pager.html()})
    
    
    def policy_add(request):
        if request.method == 'GET':
            form = PolicyModelForm()
            return render(request,'form2.html',{'form':form})
        form = PolicyModelForm(data=request.POST)
        if not form.is_valid():
            return render(request,'form2.html',{'form':form})
        form.save()
        return redirect(reverse('policy_list'))
    
    def policy_edit(request, pk):
        instance = models.PricePolicy.objects.filter(id=pk).first()
        if request.method == "GET":
            form = PolicyModelForm(instance=instance)
            return render(request, 'form2.html', {'form': form})
        form = PolicyModelForm(data=request.POST, instance=instance)
        if not form.is_valid():
            return render(request, 'form2.html', {'form': form})
        form.save()
        return redirect(reverse('policy_list'))
    
    
    def policy_delete(request):
        res = BaseResponse(status=True)
        cid = request.GET.get('cid')
        models.PricePolicy.objects.filter(id=cid).delete()
        return JsonResponse(res.dict)
    
    
  • templates/policy_list.html

    {% extends 'layout.html' %}
    {% load static %}
    
    {% block content %}
        <div style="margin-bottom: 5px">
            <a href="{% url 'policy_add' %}" class="btn btn-success">
                <span class="glyphicon glyphicon-plus-sign"></span> 新建
            </a>
    
        </div>
         <table class="table table-bordered">
            <thead>
            <tr>
                <th>ID</th>
                <th>数量</th>
                <th>价格</th>
                <th>操作</th>
            </tr>
            </thead>
            <tbody>
            {% for row in queryset %}
                <tr row-id="{{ row.id }}">
                    <td>{{ row.id }}</td>
                    <td>{{ row.count }}</td>
                    <td>{{ row.price }}</td>
                    <td>
                        <a href="{% url 'policy_edit' pk=row.id %}" class="btn btn-primary btn-xs">编辑</a>
                        <a cid="{{ row.id }}" class="btn btn-danger btn-xs btn-delete">删除</a>
                    </td>
                </tr>
            {% endfor %}
    
            </tbody>
        </table>
    
        <ul class="pagination">
            {{ page_string }}
        </ul>
    
    
        {% include 'include/delete_model.html' %}
    
    {% endblock %}
    
    
    
    {% block js %}
        <script src="{% static 'js/delete.js' %}"></script>
        <script>
            var DELETE_ID;
            var DELETE_URL = "{% url 'policy_delete' %}";
        </script>
    
    {% endblock %}
    
    
  • forms/policy.py

    from utils.bootstrap import BootStrapForm
    from django import forms
    from web import models
    
    class PolicyModelForm(BootStrapForm, forms.ModelForm):
        class Meta:
            model = models.PricePolicy
            fields = "__all__"
    
    

posted on 2026-06-12 15:10  恍惚aa  阅读(0)  评论(0)    收藏  举报