Django

0x01 入门

(1)简介

  • 官方网站链接
  • Django 是一个开放源代码的Web应用框架,由Python写成
  • Django 采用 MTV 的框架模式,即模型 M,视图 V 和模版 T

(2)安装

a. 创建虚拟环境

  • 安装 virtualenv:pip install virtualenv virtualenvwrapper-win
  • 查看虚拟环境:workon
  • 创建新的虚拟环境 env:mkvirtualenv env
  • 进入虚拟环境 env:workon env
  • 删除虚拟环境 env:rmvirtualenv env

b. 安装 Django

  • 安装 Django:pip install django=4.2 -i
  • 查看安装结果:pip show django

也可以在 Pycharm 新建 Django 项目时自动安装 Django

(3)创建项目

  • 方法一:在 cmd 中使用命令 django-admin startproject Projectname
  • 方法二:在 Pycharm 中创建 Django 项目

(4)项目目录与配置文件

a. 项目目录

  • manage.py:管理项目的命令行工具,进行站点运行以及数据库自动生成都通过本文件
  • ProjectName
    • __ init__.py:用于工具初始化
    • asgi.py:Python 异步服务器网关接口
    • settings.py:项目配置文件,定义了项目引用的组件、项目名、数据库、静态资源等
    • urls.py:维护项目的 URL 路由映射
    • wsgi.py:Python 服务器网关接口

b. 配置文件

settings.py(该文件新建项目时自动生成)

# 项目根目录
BASE_DIR = Path(__file__).resolve().parent.parent

# 加密解密的密钥
SECRET_KEY = 'django-insecure-xxxxxx'

# 是否开启调试模式
DEBUG = True

# 被允许的域名、IP
ALLOWED_HOSTS = []

# 插入自定义引用名称
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    ...
]

# 中间件
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    ...
]

# 根路由文件
ROOT_URLCONF = 'djangoProject.urls'

# 模板
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',
            ],
        },
    },
]

# WSGI 目录
WSGI_APPLICATION = 'djangoProject.wsgi.application'


# 数据库
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

# 密码验证
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    ...
]

# 国际化信息配置
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True

# 静态资源目录
STATIC_URL = 'static/'

# 默认主键字段类型
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

(5)运行项目

  • 设置 settings.py 中 ALLOWED_HOSTS = ['*']
  • 使用命令 python manage.py runserver [IP Address]:[Port]

(6)数据迁移

  • 迁移就是将模型映射到数据库的过程
  • 使用命令 python manage.py makemigrations 生成迁移文件
  • 使用命令 python manage.py migrate 执行迁移

不需要初始化迁移文件夹,每个应用默认有迁移文件夹 migrations

(7)创建应用 App

  • 使用命令 python manager.py startapp App 创建名为 App 的应用
  • 使用应用前需要将应用配置到 settings.py 的 INSTALLED_APPS
  • 应用目录:
    • migrations 包:生成迁移文件
    • __ init__.py:初始化包,暂空
    • admin.py:管理站点模型的声明文件,默认空
    • apps.py:应用信息定义文件
    • models.py:添加模型层数据类文件
    • tests.py:代码测试文件
    • views.py:定义 URL 相应函数

(8)HTTP 前后端交互

graph TB A(服务器端 Server)--响应 Response-->B(客户端 Client) B--请求 Request-->A

(9)Django 框架流程

graph LR 浏览器-->A(后端) A-->B(路由 urls) -->C(视图 views) --增删改查-->D(模型 models) D-->C C--模板渲染-->E(模板 Templates) -->浏览器 D--ORM-->F(数据库 MySQL) -->D

(10)视图函数基本使用

  • 在 views.py 中建立一个路由响应函数

    from django.http import HttpResponse
    
    def hello(request):
        return HttpResponse('Hello')
    
  • 在 urls.py 中注册

    from App.views import *
    
    urlpatterns = [
        path('hello/', hello)
    ]
    
  • 启动项目并访问 127.0.0.1:8000/hello

(11)路由基本使用

  • 子路由 App/urls.py

    from django.urls import path
    from App.views import *
    
    urlpatterns = [
        path('/hello', hello)
    ]
    
  • 根路由 urls.py

    from django.urls import path, include
    
    urlpatterns = [
        path('app/', include('App.urls')),
    ]
    
  • 启动项目并访问 127.0.0.1:8000/app/hello

(12)模板基本使用

  • 模板是 HTML 写好的页面
  • 创建模板文件夹 templates,并在其中创建模板文件
  • 在 views.py 中通过 return render(request, 'xxx.html') 来加载并渲染模板

(13)模型基本使用

  • 在 models.py 中引入 models:from django.db import models

  • 创建继承于 models.Model 的自定义模型类:如用户类

    class User(models.Model):
        name = models.CharField(max_length=30)  # 对应 SQL 的 name varchar(30)
        age = models.IntegerField(20)  # 对应 SQL 的 age int default 20
    
  • 模型 models 表结构一旦改变就需要进行数据迁移

  • 使用模型

    • views.py

      from App.models import *
      
      def get_users(request):
          users = UserModel.objects.all()
          return render(request, 'users.html', {'users': users})
      
    • users.html

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
      </head>
      <body>
          <ul>
              {% for user in users %}
                  <li>{{ user.name }}, {{ user.age }}</li>
              {% endfor %}
          </ul>
      </body>
      </html>
      

(14)后台管理基本使用

  • 在 admin.py 中将 model 加入后台管理:admin.site.register(Grade)
  • 创建超级用户:python manage.py createsuperuser
  • 访问 admin 后台:127.0.0.1:8000/admin

0x02 路由

(1)简介

  • 在项目开发过程中,通常在每个应用中创建各自的 urls.py 路由模块
  • 路由的分发:从根路由出发,将每个应用所属的 url 请求转发到各自的路由模块中的过程

(2)创建用户模型

  • models.py

    class UserModel(models.Model):
        name = models.CharField(max_length=30)
        age = models.PositiveIntegerField()  # 非负数
    
  • 数据迁移

    python manage.py makemigrations
    python manage.py migrate
    
  • 与数据库连接

    • 新建 SQLite 数据源
    • 常规 -> 文件:打开项目根目录下的 db.sqlite3
    • 确认已配置相关数据库驱动即可完成连接

(3)添加子路由

  • 添加模板:App/templates/index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>首页</title>
    </head>
    <body>
        <h2>首页</h2>
        <hr />
    
    </body>
    </html>
    
  • 添加视图:views.py

    from django.shortcuts import render
    
    def index(request):
        return render(request, 'index.html')
    
  • 根路由:urls.py

    from django.urls import path, include
    
    urlpatterns = [
        path('user/', include('App.urls'))
    ]
    
  • 子路由:App/urls.py

    from django.urls import path
    from App.views import *
    
    urlpatterns = [
        path('index/', index)
    ]
    
  • 启动项目并访问 http://127.0.0.1:8000/user/index/

(4)反向解析和命名空间

a. 基本概念

  • 反向解析:使用路由别名替代 URL 路径,避免在修改 URL 时产生硬编码依赖

  • 命名空间:一种将路由命名为层次结构的方式,使得查询路由时可以限定在该命名空间内,防止路由冲突

b. 项目应用-页面跳转

  • 添加模板:App/templates/user_list.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>用户列表</title>
    </head>
    <body>
        <h2>用户列表</h2>
        <hr />
    </body>
    </html>
    
  • 修改视图:views.py

    def user_list(request):
        return render(request, 'user_list.html')
    
  • 修改子路由:App/urls.py

    path('userList/', user_list, name='userListName')
    
  • 修改模板:App/templates/index.html

    <a href="/user/userList/">URL 路由方式</a>
    <hr />
    <a href="{% url 'userListName' %}">反向解析方式</a>
    <hr />
    <a href="{% url 'App:userListName' %}">使用命名空间的反向解析方式</a>
    
    • 使用命名空间的反向解析方式前需要修改根路由:urls.py

      path('user/', include(('App.urls', 'App'), namespace='App'))
      
    • “反向解析方式”和“使用命名空间的反向解析方式”不能同时作用于同一个页面

(5)路由传参

a. 单个参数

  • 添加模板:App/templates/user_detail.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>用户详情</title>
    </head>
    <body>
        <h2>用户详情</h2>
        <hr />
        <h3>{{ user.name }} 的年龄是 {{ user.age }}</h3>
    </body>
    </html>
    
  • 修改视图:views.py

    from App.models import *
    
    def user_list(request):
        users = UserModel.objects.all()
        return render(request, 'user_list.html', {'users': users})
    
    def user_detail(request, uid):
        user = UserModel.objects.get(pk=uid)
        return render(request, 'user_detail.html', {'user': user})
    
  • 修改子路由:App/urls.py

    path('userDetail/<int:uid>/', user_detail, name='userDetailName')
    
  • 修改模板:App/templates/user_list.html

    <ul>
        {% for user in users %}
        <li>
            <a href="{% url 'App:userDetailName' user.id %}">
                {{ user.name }}
            </a>
        </li>
        {% endfor %}
    </ul>
    

b. 多个参数

  • 修改视图:views.py

    def user_ab(request, a, b):
        return HttpResponse(f"{a} - {b}")
    
  • 修改子路由:App/urls.py

    path('userAB/<int:a>/<int:b>', user_ab, name='userABName')
    
  • 启动项目并访问 http://127.0.0.1:8000/user/userAB/1/2

(6)重定向 Redirect

  • 修改子路由:App/urls.py

    path('redirect/', my_redirect)
    
  • 修改视图:views.py

    • 重定向到指定链接

      def my_redirect(request):
          return redirect('https://www.baidu.com')
      
    • 重定向并反向解析传值

      • 依序传值

        def my_redirect(request):
            return redirect(reverse('App:userDetailName', args=(2,)))
        
      • 对象传值

        def my_redirect(request):
            return redirect(reverse('App:userDetailName', kwargs={'uid': 2}))
        

0x03 模板

(1)简介

  • 模板用于帮助开发者快速呈现给用户页面的工具
  • 模板处理分为两个过程:加载 HTML、渲染数据
  • 模板主要有两个部分:HTMl 静态代码、模板语言和动态代码段
    • 动态代码段可以做基本的静态填充和运算、转换、逻辑
  • 页面可以按页面数据来源进行分类
    • 静态页面:数据来源于本地固定数据
    • 动态页面:数据来源于后台服务器

(2)动态代码段

a. 变量

  • 模板中的变量是视图传递给模板的,遵守标识符规则
  • 一般变量的语法:{{ var }}
    • 如果变量不存在,则插入空字符串
  • 当变量是方法时,不能有参数
  • 当变量是列表时,使用正索引
    • python 中:items = ["a", "b", "c"]
    • HTML 中:{{ items.1 }}

b. 标签

  • 模板中的标签语法:{% tag %}
  • 作用:
    • 加载外部传入的变量
    • 在输出中创建文本
    • 控制循环或逻辑

c. 注释

  • 单行注释

    {# 注释 #}
    
  • 多行注释

    {% comment %}
    	注释1
    	注释2
    {% endcomment %}
    

(3)if 语句

a. 格式

  • 单分支

    {% if 表达式 %}
    	语句
    {% endif %}
    
  • 双分支

    {% if 表达式 %}
    	语句
    {% else %}
    	语句
    {% endif %}
    
  • 多分支

    {% if 表达式 %}
    	语句
    {% else if %}
    	语句
    {% else %}
    	语句
    {% endif %}
    

b. 举例

  • 无逻辑条件判断

    {% if isDelete %}
    	<h2>Boolean</h2>
    {% endif %}
    
  • 与或非判断

    {% if a and b %}
    	<p>and</p>
    {% else if a or b %}
    	<p>or</p>
    {% else if not a %}
    	<p>not</p>
    {% endif %}
    
  • 成员判断

    {% if "a" in "bcd" %}
    	<p>bcd</p>
    {% else if "a" not in "efg" %}
    	<p>a</p>
    {% endif %}
    

(4)for 语句

  • 一般格式:

    {% for 变量 in 列表 %}
    	语句1
    {% empty %}
    	语句2
    {% endfor %}
    
    • 当列表为空或不存在时,执行 empty 语句
  • forloop

    {% for 变量 in 列表 %}
    	<p>{{ forloop.counter }}</p>
    {% endfor %}
    
    • {{ forloop.counter }}:表示当前是第几次循环,从 1 开始
    • {{ forloop.counter0 }}:表示当前是第几次循环,从 0 开始
    • {{ forloop.revcounter }}:表示当前是第几次循环,倒序数,至 1 停
    • {{ forloop.revcounter0 }}:表示当前是第几次循环,倒序数,至 0 停
    • {{ forloop.first }}:表示是否是第一个元素,返回布尔值
    • {{ forloop.last }}:表示是否是最后一个元素,返回布尔值

(5)过滤器

  • 一般过滤器格式:{{ var | 过滤器 }}

    • 作用:在变量显示前修改
  • 加法过滤器:`{{ value | add:2 }}

  • 大写过滤器:{{ name | upper }}

    • 首字母大写:{{ name | first | upper }}
    • 尾字母大写:{{ name | last | upper }}
  • 小写过滤器:{{ name | lower }}

  • 截断过滤器:{{ name | truncatechars: 30 }}

  • 连接过滤器:{{ name | join: '=' }}

  • 默认过滤器:{{ var | default: value }}

  • 日期过滤器:{{ day | date: 'y-m-d' }}

  • 转义过滤器:{{ code | safe }}

    • 打开/关闭自动转义

      {% autoescape on/off %}
      	语句
      {% endautoescape %}
      

(6)继承

  • block:

    {% block XXX %}
    	语句
    {% endblock %}
    
    • XXX 为该 block 的名称
  • extends:继承

    {% extends '父模块路径' %}
    
  • include:加载模板进行渲染

    {% include '模板文件' %}
    
  • {{ block.super }}:获取父模版中 block 中的内容

  • 举例:

    • 父模板:block.html

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
      </head>
      <body>
          <h2>父模版</h2>
          <hr />
      
          {% block head %}
              <div>
                  <button>Head</button>
              </div>
          {% endblock %}
      </body>
      </html>
      
    • 其他模板:other.html

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
      </head>
      <body>
          <h2>其他模板</h2>
          <hr />
      </body>
      </html>
      
    • 子模版:child.html

      {# 继承父模版 #}
      {% extends 'block.html' %}
      
      {% block head %}
          <div>
              <button>head</button>
          </div>
          
          {# 保留父模版内容 #}
          {{ block.super }}
      
          {# 导入其他模板 #}
          {% include 'other.html' %}
      {% endblock %}
      

(7)Jinja2 模板引擎

a. 简介

  • Jinja2 是一个模板引擎,在 Flask 中也有应用
  • Jinja2 基于 Django 默认模板引擎开发,性能更好,功能更全

b. 安装与配置

  • 使用命令 pip install jinja2 安装

  • 在 settings.py 同目录下新建 jinja2_env.py 文件配置 Jinja2

    from django.templates.static import static
    from django.urls import reverse
    from jinja2 import Environment
    
    def environment(**options):
        env = Environment(**options)
        env.globals.update({
            'static': static,
            'url': reverse
        })
        return env
    
  • 在 settings.py 中配置 Jinja2 模板引擎

    TEMPLATES = [
        # 新增 Jinja2 模板引擎
        {
            'BACKEND': 'django.template.backends.jinja2.Jinja2',
            'DIRS': [],
            'APP_DIRS': True,
            'OPTIONS': {
                'environment': 'DjangoPro2.jinja2_env.environment',
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
        
        # 原 Django 引擎模板
        {
            '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',
                ],
            },
        },
    ]
    
  • 在设置中修改模板语言为 Jinja2

c. 使用

  • Jinja2 可以使用函数调用

    {% for i in range(1, 4) %}
    	<h3>{{ i }}</h3>
    {% endfor %}
    
  • 循环下标读取方法改变

    {% for i in range(1, 4) %}
    	<h3>{{ loop.index }}</h3>
    {% endfor %}
    
  • ……

0x04 模型基础

(1)简介

  • Django 根据属性的类型确认以下信息

    • 当前选择的数据库支持字段的类型
    • 渲染管理表单时使用的默认 HTML 控件
    • 在管理站点最低限度的验证
  • Django 会为表增加自动增长的主键列,当设置过主键列后,默认主键列不会生成

  • 属性名遵循标识符命名规则,但由于 Django 的查询方式,不允许使用连续的下划线

    • 定义属性时需要设置字段类型
  • 使用模型:

    • 导入模型:from django.db import models
    • 通过 models.Field 创建字段类型的对象并赋值给属性
  • 逻辑删除和物理删除

    • 对于重要数据仅作逻辑删除

      • 实现方法:设置 is_delete 属性为 False
      is_delete = models.BooleanField(default=False)
      
  • 基本操作

    • Django 自动生成与模型相应的 SQL 语句并执行

    • Django 使用对象关系映射框架操控数据库

      • 对象关系映射(Object Relational Mapping,简称 ORM):一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换

      • ORM 关系:

        graph LR 模型-->表 -->模型 类对象-->表结构 对象-->表的一条数据 类属性-->表的字段

(2)字段类型

a. 常用字段类型

  • AutoField:根据实际 ID 自动增长的 IntegerField,通常不指定
  • CharField(max_length=字符长度):字符串,默认表单控件为 Input
  • TextField:文本字段,默认表单控件为 Textarea
  • IntegerField:整数
  • DecimalField(max_digits=None, decimal_places=None):使用 Python 的 Decimal 实例表示十进制浮点数
    • max_digits:位数总数
    • decimal_places:小数点后的位数
  • FloatField:使用 Python 的 float 实例表示浮点数
  • BooleanField:布尔值,默认表单控件为 CheckboxInput
  • DateField([auto_now=None, auto_now_add=False]):使用 Python 的 datetime.date 实例表示日期
    • auto_now:保存对象时设置为当前时间时间戳,一般用于表示最后一次修改的时间
    • auto_now_add:保存对象创建的时间戳
  • TimeField:使用 Python 的 datetime.time 实例表示时间,参数同 DateField
  • DateTimeField:使用 Python 的 datetime.datetime 实例表示日期和时间,参数同 DateField
  • FileField:上传文件的字段
  • ImageField:继承自 FileField,但会对文件进行校验
    • 需要安装 Pillow:pip install Pillow

b. 常用字段参数

  • null=True:数据库字段是否可以为空

  • blank=True:Django 的 Admin 中添加数据时是否可以为空

  • primary_key=True:设置主键

  • auto_nowauto_now_add:详见 DateField 中的说明

  • choices:设置 Admin 下拉菜单

    • 举例:设置用户类型下拉菜单

      USER_TYPE_LIST = (
          (1, '一般用户'),
          (2, 'Vip 客户'),
          (3, '超级用户')
      )
      user_type = models.IntegerField(choices=USER_TYPE_LIST, default=1, verbose_name='用户类型')
      
  • max_length:最大长度

  • default:默认值

  • verbose_name:Admin 中字段的显示名称

  • name|db_column:数据库中字段的名称

  • unique=True:是否不允许重复

  • db_index=True:数据库索引

  • editable=True:是否允许 Admin 编辑,否则不显示

  • 设置表名

    class Meta:
        db_table = 'person'
    

(3)单表操作

a. 增加数据

  • 方法一:创建对象实例并调用 save 方法

    obj = Author()
    obj.first_name = 'zhang'
    obj.last_name = 'san'
    obj.save()
    
  • 方法二:创建对象并初始化后调用 save 方法

    obj = Author(first_name='zhang', last_name='san')
    obj.save()
    
  • 方法三:调用 create 方法

    Author.objects.create(first_name='zhang', last_name='san')
    
    • 调用 get_or_create 方法防止重复创建

      Author.objects.get_or_create(first_name='zhang', last_name='san')
      

b. 删除数据

  • 方法一:使用 Queryset 的 delete 方法

    • 删除指定条件的数据

      Author.objects.filter(first_name='zhang').delete()
      
    • 删除所有数据

      Author.objects.all().delete()
      
  • 方法二:使用模型对象的 delete 方法

    obj = Author.objects.get(id=1)
    obj.delete()
    

c. 修改数据

  • 方法一:使用模型对象的 update 方法

    Author.objects.filter(last_name='san').update(last_name='si')
    
  • 方法二:重新赋值后使用 save 方法保存

    obj = Author.objects.get(id=1)
    obj.first_name = 'li'
    obj.save()
    
    • 如果只更新某个字段,则使用以下方法

      obj = Author.objects.get(id=1)
      obj.first_name = 'li'
      obj.save(update_fields=['first_name'])
      

d. 查找数据

I. 基础查询

  • get():获取单条数据,如 get(id=1)
    • 如果没有符合的结果:DoesNotExist 异常
    • 如果有多个符合的结果:MultipleObjectsReturned 异常
  • first():返回查询集中的第一个对象
  • last():返回查询集最后一个对象
  • count():返回查询集中对象的个数
  • exists():判断查询集中是否有数据
  • all():获取全部数据
  • values():获取指定列的值,可传多个参数,返回形式是字典
  • values_list():获取指定列的值,可传多个参数,返回形式是元组

II. 条件过滤

  • filter(id__gt=2):获取 id 大于 2 的值
  • filter(id__gte=2):获取 id 大于等于 2 的值
  • filter(id__lt=2):获取 id 小于 2 的值
  • filter(id__lte=2):获取 id 小于等于 2 的值
  • filter(id__lt=4, id__gt=2):获取 id 大于 2 且 小于 4 的值
  • filter(id__in=[1, 2, 4]):获取 id 在 [1, 2, 4] 之间的值
  • exclude(id__in=[1, 2, 4]):获取 id 不在 [1, 2, 4] 之间的值
  • filter(age__range=[10, 20]):获取整数 age 在 10~20 之间的值
  • filter(name__contains="van"):获取 name 中含 van 的值
  • filter(name__icontains="van"):获取 name 中含 van 的值(大小写不敏感)
  • filter(name__regex="van"):正则匹配
  • filter(name__iregex="van"):正则匹配(大小写不敏感)

III. 排序

  • filter(name='zhangsan').order_by('id'):升序排序
  • filter(name='zhangsan').order_by('-id'):降序排序
    • 按年龄排序,相同则按 id 排序:filter(name='zhangsan').order_by('age', 'id')

IV. 聚合

  • aggregate(Max('age')):最大值
  • aggregate(Min('age')):最小值
  • aggregate(Sum('age')):求和
  • aggregate(Avg('age')):平均值
  • aggregate(Count('age')):计数

(4)手动分页

  • all()[10:20]:切片,取所有数据的 10 条到 20 条,下标从 0 开始,不为负

  • 在 views.py 中通过切片进行分页

    def paginate(request, page=1):
        per_page = 10
        items = Model.objects.all()[(page-1) * per_page : page * per_page]
        total = Model.objects.count()
        total_page = math.ceil(total / 10)
        pages = range(1, total_page+1)
        return render(request, 'paginate.html', {'items': items, 'pages': pages})
    
  • 添加子路由

    path('paginate/<int:page>', paginate)
    
  • 网页中添加按钮访问指定页

    <ul>
        {% for page in pages %}
        <li><a href="{% url 'paginate' page %}"><button>{{ page }}</button></a></li>
        {% endfor %}
    </ul>
    

(5)分页器

  • Django 中有自动分页器

  • views.py

    from django.core.paginator import Paginator
    
    def paginate(request, page=1):
        per_page = 10
        datas = Model.objects.all()
        
        # 分页器
        paginator = Paginator(datas, per_page)
        # 获取第 page 页的数据
        items = paginator.page(page)
        # 获取页码范围
        pages = paginator.page_range
        
        return render(request, 'paginate.html', {'items': items, 'pages': pages})
    

0x05 模型进阶

(1)配置 MySQL 数据库

  • 安装 MySQL

  • 安装 MySQL 驱动:pip install mysqlclient

  • 在 Django 中配置和使用 MySQL

    • settings.py

      DATABASES = {
          'default': {
              'ENGINE': 'django.db.backends.mysql',
              'NAME': 'django',
              'USER': 'root',
              'PASSWORD': 'password',
              'HOST': '127.0.0.1',
              'PORT': '3306'
          }
      }
      

(2)多表关系

  • OneToOneField:一对一(学号对学生),将字段定义在任意一端
  • ForeignKey:一对多(班级对学生),将字段定义在“多”的一端
  • ManyToManyField:多对多(老师对学生),将字段定义在任意一端

(3)一对多关系

  • 举例:一个班级有多个学生,一个学生只属于一个班级

    class Grade(models.Model):
        name = models.CharField(max_length=20)
    class Student(models.Model):
        name = models.CharField(max_length=20)
        grade = models.ForeignKey(Grade)
    
  • 对象的使用

    • 正向,Student 视角,包含外键
      • 获取学生所在班级(对象):student.grade
      • 获取学生所在班级名称:student.grade.name
    • 反向,Grade 视角,不包含外键
      • 获取班级所有学生(Manager 对象):grade.student_set
      • 获取班级所有学生(QuerySet 查询集):grade.student_set.all()

a. 增加数据

I. 正向

  • 方法一:创建对象实例并使用 save 方法

    obj = Student(name='zhangsan', grade_id=1)
    obj.save()
    
  • 方法二:对象初始化后使用 create 方法

    Student.objects.create(name='zhangsan', grade_id=1)
    
    • 使用 get_or_create 方法防止重复创建

      Student.objects.get_or_create(name='zhangsan', grade_id=1)
      
    • 使用字典

      dic = {'name': 'zhangsan', 'grade_id': 1}
      Student.objects.create(**dic)
      
    • 使用对象

      grade = Grade.objects.get(id=1)
      Student.objects.get_or_create(name='zhangsan', grade=grade)
      

II. 反向

  • 同正向增加数据操作

b. 删除数据

I. 正向

  • 一般操作:Student.objects.filter(id=1).delete()

II. 反向

  • 删除操作可以在定义外键时,通过 on_delete 参数来配置
    • models.CASCADE:默认值,级联删除
    • models.PROJECT:保护模式,组织级联删除
    • models.SET_NULL:置空模式,null=True 参数必备
    • models.SET_DEFAULT:置默认值,default 参数必备
    • models.SET():删除时重新动态指定一个实体访问元素
    • models.DO_NOTHING:无操作

c. 修改数据

I. 正向

  • 一般操作:Student.objects.filter(id=1).delete(grade_id=2)

II. 反向

  • 同正向修改数据操作

d. 查询数据

I. 正向

  • 一般操作

    students = Student.objects.filter(name='zhangsan')
    grade = students[0].grade_id.name
    

II. 反向

  • 同正向查询数据操作

  • 可以在定义外键时,通过指定 related_name 参数来配置

    grade = models.ForeignKey('Grade', related_name="info")
    
    obj,info.all()
    
  • 可以通过 Student 提供的信息查看

    obj = Student.objects.get(grade__name)
    

(4)多对多关系

  • 对于多对多关系,Django 会自动创建第三张表,也可以通过 through 参数指定

  • 举例:用户与小组

    class Group(models.Model):
        name = models.CharField(max_length=20)
        
        def __str__(self):
            return self.name
    
    class User(models.Model):
        name = models.CharField(max_length=20)
        groups = models.ManyToManyField(Group)
        
        def __str__(self):
            return self.name
    

a. 增加数据

  • 分别创建 user 和 group

    user = User(name='zhangsan')
    user.save()
    group = Group(name="zuiyi")
    g.save()
    
  • 通过 Manager 对象使用 add() 方法

    user.groups.add(group)
    # 或者
    group,user_set.add(user)
    

b. 删除数据与修改数据

  • 同一对多类似

c. 查询数据

I. 正向

  • 查询 id 为 1 的用户所在的所有组

    user = User.objects.get(id=1)
    user.groups.all()
    

II. 反向

  • 查询 id 为 1 的组中包含的用户

    group = Group.objects.get(id=1)
    group.user_set.all()
    

(5)一对一关系

  • 一对一关系是 Django 独有的一个连表操作,是特殊的一对多关系,相当于加了 unique=True

  • 举例:身份证号和人

    class IdCard(models.Model):
        num = models.IntegerField()
        
        def __str__(self):
            return str(self.num)
    
    class Person(models.Model):
        idcard = models.OneToOneField(IdCard)
        name = models.CharField(max_length=20)
        
        def __str__(self):
            return self.name
    

0x06 视图

(1)简介

  • Django 中的视图主要用来接受 Web 的请求并作出响应

  • 视图的本质是 Python 中的函数

  • 视图的响应分为两大类:

    • 以 JSON 数据形式返回(JsonResponse)
    • 以网页的形式返回
      • 重定向到另一个网页(HttpResponseRedirect)
      • 错误视图(HttpResponseNotFound/HttpResponseForbidden/HttpResponseNotAllowed)
  • 视图响应过程:

    graph LR 浏览器输入-->A(URLs 路由匹配) -->视图响应 -->回馈到浏览器
  • 视图参数

    • HttpRequest 的实例
    • 通过 URL 正则表达式传参
  • 位置:通常在应用下的 views.py 中定义

(2)HttpRequest 请求对象

  • Django 框架接收到 http 请求后会将 http 请求包装为 HttpRequest 对象并传递给视图

  • 常用属性与方法

    • 属性
      • path:请求的完整路径
      • method:请求的方法,如 GET、POST 等
      • GET/POST:类似字典,包含了 GET/POST 的所有参数
      • FILES:类似字典,包含了上传的文件
      • COOKIES:字典,包含了所有 COOKIE
      • session:类似字典,表示会话
    • 方法
      • is_ajax():判断是否是 Ajax
      • get_full_path():返回包含参数字符串的请求路径
  • QueryDict:类字典对象,与字典区别在于可以有相同的键

    • 获取数据

      dict['name']dict.get('name')

    • 获取指定键对应的所有值

      dict.getlist('name')

(3)HttpResponse 响应对象

  • 服务器返回给客户端的数据,HttpResponse 由开发者自行创建

    • 方法一:不使用模板,直接调用 HttpResponse(),返回 HttpResponse 对象
    • 方法二:使用模板进行渲染
      • 使用 render:render(request, template_name[, context])
        • request:请求体对象
        • template_name:模板路径
        • context:字典参数
  • 常用属性与方法

    • 属性
      • content:返回的内容
      • charset:编码格式
      • status_code:响应状态码
    • 方法
      • write(xxx):写出文本
      • flush():冲刷缓冲区
      • set_cookie(key, vale='xxx', max_age=None):设置 Cookie
      • delete_cookie(key):删除 Cookie
  • 子类

    • HttpResponseRedirect

      • 响应重定向,可以实现服务器内部跳转
      • 使用时一般需要反向解析
      return HttpResponseRedircet('/page/1')
      
    • JsonResponse

      • 返回 JSON 数据的请求,一般用于异步请求上
      • 返回 JSON 数据时 Content-typeapplication/json
      JsonResponse(dict)
      

0x07 会话技术

(1)Cookie 简介

  • Cookie实际上是一小段的文本信息

    • 客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie
    • 客户端浏览器会把Cookie保存起来,当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器
    • 服务器检查该Cookie,以此来辨认用户状态
      • 服务器还可以根据需要修改Cookie的内容
  • Cookie 本身由服务器生成,通过 Response 将 Cookie 写到浏览器上,当再次访问时浏览器会根据不同的规则携带 Cookie

    • Cookie 既不能跨浏览器,也不能跨域
  • 通过 response 设置 Cookie:response.set_cookie(key, value[, max_age=None, expires=None])

    • max_age:整数,单位为秒,指定 Cookie 过期时间,默认值为None,表示关闭

    • expires:指定过期时间,支持 datetime 或 timedelta,可以指定一个具体日期和时间

      expires=datetime.datetime(2000, 1, 1, 0, 0, 0)
      # or
      expires=datetime.datetime.now()+datetime.timedelta(days=10)
      
  • 通过 request 获取 Cookie:request.COOKIES.get('username')

  • 通过 response 删除 Cookie:response.delete_cookie('username')

  • Cookie 存储于客户端

    • 优点:减轻服务器压力,提高网站性能
    • 缺点:安全性不高

(2)创建模型与模板

  • models.py

    class UserModel(models.Model):
        username = models.CharField(max_length=30, unique=True)
        password = models.CharField(max_length=225)
        age = models.IntegerField(default=20)
    
  • templates

    • index.html

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>首页</title>
      </head>
      <body>
      <a href="{% url 'login' %}">登录</a>
      <a href="{% url 'register' %}">注册</a>
      </body>
      </html>
      
    • register.html

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>注册</title>
      </head>
      <body>
      </body>
      </html>
      
    • login.html

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>登录</title>
      </head>
      <body>
      </body>
      </html>
      
  • views.py

    from django.shortcuts import render
    
    def index(request):
        return render(request, 'index.html')
    
    def register(request):
        return render(request, 'register.html')
    
    def login(request):
        return render(request, 'login.html')
    
  • urls.py

    from django.urls import path, include
    
    urlpatterns = [
        path('user/', include('App.urls'))
    ]
    
  • App/urls.py

    from django.urls import path
    from App.views import *
    
    urlpatterns = [
        path('index/', index, name='index'),
        path('register/', register, name='register'),
        path('login/', login, name='login'),
    ]
    

(3)实现用户注册

  • 修改 register.html

    <body>
    <form method="post" action="">
        <p>Username: <input type="text" name="un" /></p>
        <p>Password: <input type="password" name="pw" /></p>
        <p>Age: <input type="number", name="age" /></p>
        <p><button>Submit</button></p>
    </form>
    </body>
    
  • 修改 views.py

    def register(request):
        if request.method == 'GET':
            return render(request, 'register.html')
        elif request.method == 'POST':
            un = request.POST.get('un')
            pw = request.POST.get('pw')
            age = request.POST.get('age')
            return render(request, 'register.html')
    
  • 启动项目并测试,出现 CSRF 相关错误时使用以下方法解决:

    • 方法一:修改 settings.py(不推荐)

      MIDDLEWARE = [
          'django.middleware.security.SecurityMiddleware',
          'django.contrib.sessions.middleware.SessionMiddleware',
          'django.middleware.common.CommonMiddleware',
          # 'django.middleware.csrf.CsrfViewMiddleware',
          'django.contrib.auth.middleware.AuthenticationMiddleware',
          'django.contrib.messages.middleware.MessageMiddleware',
          'django.middleware.clickjacking.XFrameOptionsMiddleware',
      ]
      
    • 方法二:修改 register.html

      <form method="post" action="">
          {% csrf_token %}
          
          <p>Username: <input type="text" name="un" /></p>
          <p>Password: <input type="password" name="pw" /></p>
          <p>Age: <input type="number", name="age" /></p>
          <p><button>Submit</button></p>
      </form>
      
  • 实现注册功能:修改 views.py

    def register(request):
        if request.method == 'GET':
            return render(request, 'register.html')
        elif request.method == 'POST':
            un = request.POST.get('un')
            pw = request.POST.get('pw')
            age = request.POST.get('age')
    
            users = UserModel.objects.filter(username=un)
            if users.exists():
                return HttpResponse('该用户已存在')
    
            try:
                user = UserModel()
                user.username = un
                user.password = pw
                user.age = age
                user.save()
            except:
                return HttpResponse('注册失败')
    
            return redirect(reverse('login'))
    

(4)CSRF

  • Cross Site Request Forgery,跨站请求伪造,指盗用身份后以该身份发送恶意请求
  • 防止 CSRF
    • Django 下的 CSRF 预防机制
    • 在 POST 请求时,在表单中添加 {% csrf_token %}

(5)Cookie 实现登录功能

  • 修改 login.html

    <body>
    <form method="post" action="">
        {% csrf_token %}
    
        <p>Username: <input type="text" name="un" /></p>
        <p>Password: <input type="password" name="pw" /></p>
        <p><button>Submit</button></p>
    </form>
    </body>
    
  • 修改 views.py

    def index(request):
        userid = request.COOKIES.get('userid', 0)
        user = UserModel.objects.filter(id=userid).first()
        return render(request, 'index.html', {'user': user})
    
    def login(request):
        if request.method == 'GET':
            return render(request, 'login.html')
        elif request.method == 'POST':
            un = request.POST.get('un')
            pw = request.POST.get('pw')
            # 登录验证
            users = UserModel.objects.filter(username=un, password=pw)
            if users.exists():
                user = users.first()
                # 设置 Cookie
                response = redirect(reverse('index'))
                response.set_cookie('userid', user.id)
                return response
    
    def logout(request):
        # 删除 Cookie
        response = redirect(reverse('index'))
        response.delete_cookie('userid')
        return response
    
  • 修改 index.html

    <body>
    {% if user %}
        User: {{ user.username }} - {{ user.age }}
        <a href="{% url 'logout' %}">注销</a>
    {% else %}
        <a href="{% url 'login' %}">登录</a>
        <a href="{% url 'register' %}">注册</a>
    {% endif %}
    </body>
    
  • 修改子路由

    path('logout/', logout, name='logout')
    

(6)Session 实现登录功能

a. 启用 Session

  • 服务器端会话技术 Session 依赖于 Cookie

  • Session 的数据存储到数据库中会进行 Base64 编码

  • 每个 HttpRequest 对象都有一个类字典的 session属性

  • 修改 settings.py 以启用 Session

    INSTALLED_APPS = [
        # ...
        'django.contrib.sessions'
    ]
    
    MIDDLEWARE = [
        # ...
        'django.contrib.sessions.middleware.SessionMiddleware'
    ]
    

b. 基本操作

  • 通过 request 设置 Sessions 值

    request.session['user_id'] = user.id
    request.session.set_expiry(86400)	# 设置过期时间
    
  • 获取 Sessions 值

    username = request.session.get("user_id")
    # or
    session_name = request.session["session_name"]
    
  • 删除 Sessions 值

    session_key = request.session.session_key
    
    del request.session[session_key]
    # or
    request.session.delete(session_key)
    
    • flush():删除当前的会话数据并删除会话的 Cookie
    • clear():清除所有会话

c. 实现登录

  • 修改 views.py

    def index(request):
        userid = request.session.get('userid', 0)
        user = UserModel.objects.filter(id=userid).first()
        return render(request, 'index.html', {'user': user})
    
    def login(request):
        if request.method == 'GET':
            return render(request, 'login.html')
        elif request.method == 'POST':
            un = request.POST.get('un')
            pw = request.POST.get('pw')
            # 登录验证
            users = UserModel.objects.filter(username=un, password=pw)
            if users.exists():
                user = users.first()
                # 设置 Session
                response = redirect(reverse('index'))
                request.session['userid'] = user.id
                return response
    
    def logout(request):
        session_key = request.session.session_key
        request.session.delete(session_key)
        return redirect(reverse('index'))
    

0x08 静态文件和媒体文件

静态文件:存放在服务器的 css、js、image等,一般称为 static

媒体文件:用户上传的文件,一般称为 media

(1)文件使用

a. 静态文件

  1. 修改 settings.py

    INSTALLED_APPS = [
        # ...
        'django.contrib.staticfiles'
    ]
    
    # ...
    
    STATIC_URL = '/static/'
    
  2. 在 App/static 目录下存放静态文件

    • 可以通过 STATICFILES_DIRS 来指定额外的静态文件搜索目录

      STATICFILES_DIRS = [
          os.path.join(BASE_DIR, "static"),
      ]
      
  3. 在模板中使用 load 标签加载静态文件

    {% load static %}
    <img src="{% static 'App/example.jpg' %}" />
    

b. 媒体文件

  • 修改 settings.py

    MEDIA_ROOT = BASE_DIR / 'static/uploads'
    

(2)文件上传

a. 单文件

  • 修改 upload.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>上传文件</title>
    </head>
    <body>
    <form method="post" action="" enctype="multipart/form-data">
        {% csrf_token %}
    
        <input type="file" name="myfile" />
        <br />
        <input type="submit" value="upload">
    </form>
    </body>
    </html>
    
  • 修改 views.py

    def upload(request):
        if request.method == 'POST':
            myFile = request.FILES.get('myfile', None)
            file_name = '1.jpg'
            if not myFile:
                return HttpResponse("无文件上传")
            file_path = os.path.join(settings.MEDIA_ROOT, file_name)
            with open(file_path, 'ab') as fp:
                for part in myFile.chunks():
                    fp.write(part)
                    fp.flush()
            # 将路径保存到数据库
            user = UserModel()
            user.icon = 'uploads/' + file_name
            user.save()
        return render(request, 'upload.html')
    

b. 多文件

  • 修改 upload.html

    <input type="file" name="myfile" multiple />
    

-End-

posted @ 2023-07-27 20:56  SRIGT  阅读(60)  评论(0编辑  收藏  举报