Django

Django 前戏

1、安装 Django

# 安装最新版本 django
pip install django

# 指定版本安装
pip install django==版本

# 更换pip源安装
pip install django -i https://pypi.tuna.tsinghua.edu.cn/simple/ 

国内源:

2、创建新项目

(1) 创建命令

django-admin startproject 项目名称

(2) 项目目录

|-- HelloWorld
|   |-- __init__.py
|   |-- asgi.py
|   |-- settings.py
|   |-- urls.py
|   `-- wsgi.py
`-- manage.py
  • HelloWorld:项目的容器。
  • manage.py:一个实用的命令行工具,可让你以各种方式与该 Django 项目进行交互。
  • HelloWorld/_init_.py:一个空文件,告诉 Python 该目录是一个 Python 包。
  • HelloWorld/asgi.py:一个 ASGI 兼容的 Web 服务器的入口,以便运行你的项目。
  • HelloWorld/settings.py:该 Django 项目的设置/配置。
  • HelloWorld/urls.py:该 Django 项目的 URL 声明; 一份由 Django 驱动的网站"目录"。
  • HelloWorld/wsgi.py:一个 WSGI 兼容的 Web 服务器的入口,以便运行你的项目。

(3) 创建应用

在manage.py 文件同级目录下输入创建应用项目

python manage.py startapp APP名称

(4) 启动项目

# 默认端口启动
python manage.py runserver

# 指定端口启动
python manage.py runserver 9090

# 内网访问
python manage.py runserver 0.0.0.0:9090

3、配置文件

# 全局配置文件
import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# 快速启动开发设置-不适合生产
# 安全警告:对生产中使用的密钥保密!
SECRET_KEY = 'e$#9p$&i_vjn*)h4&_(ew$2=g&(@s*!v5n!^ej-$y$8w4)j@x4'

# 安全警告:不要在生产中启用调试的情况下运行!
DEBUG = True

# 设置允许访问的ip, 如果不限制'*'
ALLOWED_HOSTS = ['*']

# 跨域增加忽略
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_METHODS = (
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
    'VIEW',
)
CORS_ALLOW_HEADERS = (
    'HTTP_AUTHORIZATION',
    'Authorzation',
    'XMLHttpRequest',
    'X_FILENAME',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
    'if-modified-since'
)

# 应用程序定义
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 自定义应用
    'rest_framework',
    'web',
    'api'
]

# 中间件
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    # 解决跨域问题
    # 'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'webH5.urls'

# 模板加载根目录设置
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',
            ],
        },
    },
]

# 连接本地数据库
# DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.mysql',
#         'NAME': 'webencapsulation',
#         'USER': 'root',
#         'PASSWORD': '19971215',
#         'HOST': '127.0.0.1',
#         'PORT': '3306',
#     }
# }

WSGI_APPLICATION = 'webH5.wsgi.application'

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



# 设置时区(国外)
# LANGUAGE_CODE = 'en-us'
# TIME_ZONE = 'UTC'
# USE_I18N = True
# USE_L10N = True
# USE_TZ = True

# 国际化(中国化)
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False
APPEND_SLASH = False


# 静态文件地址
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "/static/")
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)

# 上传文件地址
MEDIA_URL = '/media/'  # 访问上传文件的路径
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

4、Django 常用命令

# 创建项目命令
django-admin startproject 项目名称

# 创建APP命令
python manage.py startapp appname

# 启动项目命令
python manage.py runserver [ip] [端口]

# 同步数据库命令
python manage.py syncdb

# 数据库迁移命令
python manage.py makemigrations
python manage.py migrate

# 创建超级用户
python manage.py createsuperuser

Django 模板详解

  • 在根目录下创建 templates 文件夹

  • 在文件夹中创建 index.html

1、基本使用

(1) index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>hello {{ name }}</h1>
</body>
</html>

Django模板渲染变量用双大括号表示

(2) views.py

# 导入依赖
from django.shortcuts import render
from django.views import View

# 首页页面
class IndexView(View):
    # GET请求是调用该方法
    def get(self, request):
        # 返回页面给前端
        name = "World"
        return render(request, 'index.html', {'name': name}

(3) urls.py

from django.conf.urls import url
from . import views

urlpatterns = [
    url('index/$', views.IndexView.as_view()),
]

(4) 效果展示

2、模板循环

(1) index.html

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

(2) views.py

from django.shortcuts import render
from django.views import View

# 模板列表渲染的基本使用
class IndexView(View):
    def get(self, request, *args, **kwargs):
        data_list = ["小李子", "胡歌", "彭于晏"]
        return render(request, 'index.html', {'data_list': data_list})

(3) urls.py

from django.conf.urls import url
from . import views

urlpatterns = [
    url('index/$', views.IndexView.as_view()),
]

(4) 效果展示

3、模板字典

(1) index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>{{ user_info }}</h3>
    <h3>姓名:{{ user_info.name }}</h3>
    <h3>年龄:{{ user_info.age }}</h3>
    <h3>身高:{{ user_info.height }}</h3>
    <h3>性别:{{ user_info.gender }}</h3>
</body>
</html>

(2) views.py

from django.shortcuts import render
from django.views import View

# 模板字典渲染的基本使用
class IndexView(View):
    def get(self, request, *args, **kwargs):
        user_info = {
            "name": "彭于晏",
            "age": 18,
            "height": 1.88,
            "gender": True
        }
        return render(request, 'index.html', {'user_info': user_info})

(3) urls.py

from django.conf.urls import url
from . import views

urlpatterns = [
    url('index/$', views.IndexView.as_view()),
]

(4) 效果展示

4、条件判断

(1) index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <!-- if...endif -->
    <h3>{{ user_info }}</h3>
    {% if user_info.gender == True %}
        <h3>性别:男</h3>
    {% endif %}
    {% if user_info.gender == False %}
        <h3>性别:女</h3>
    {% endif %}

    <!-- if...elif...endif -->
    <h3>{{ user_info }}</h3>
    {% if user_info.gender == True %}
        <h3>性别:男</h3>
    {% elif user_info.gender == False %}
        <h3>性别:女</h3>
    {% endif %} 
</body>
</html>

(2) views.py

from django.shortcuts import render
from django.views import View

# 模板字典渲染的基本使用
class IndexView(View):
    def get(self, request, *args, **kwargs):
        user_info = {
            "name": "彭于晏",
            "age": 18,
            "height": 1.88,
            "gender": True
        }
        return render(request, 'index.html', {'user_info': user_info})

(3) urls.py

from django.conf.urls import url
from . import views

urlpatterns = [
    url('index/$', views.IndexView.as_view()),
]

(4) 效果展示

5、模板继承

模板可以用继承的方式来实现复用,减少冗余内容。

网页的头部和尾部内容一般都是一致的,我们就可以通过模板继承来实现复用。

父模板用于放置可重复利用的内容,子模板继承父模板的内容,并放置自己的内容。

(1) 部分继承

base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>我是base文件的</h3>
    {% block base %}
    {% endblock %}
    <h3>我也是base文件的</h3>
</body>
</html>

index.html

{% extends "base.html" %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    {% block base %}
        <h3>我是index中的内容</h3>
    {% endblock %}
</body>
</html>

views.py

from django.shortcuts import render
from django.views import View

# 模板继承的基本使用
class IndexView(View):
    def get(self, request, *args, **kwargs):
        return render(request, 'index.html')

urls.py

from django.conf.urls import url
from . import views

urlpatterns = [
    url('index/$', views.IndexView.as_view()),
]

效果展示

(2) 继承整个页面

header.html

<style>
    .header {
        width: 960px;
        height: 44px;
        margin: 0 auto;
        background-color: pink;
        text-align: center;
        line-height: 44px;
        font-size: 28px;
    }
</style>

<div class="header">
    我是导航栏部分
</div>

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .main {
            width: 960px;
            height: 100px;
            margin: 0 auto;
            background-color: purple;
            line-height: 100px;
            text-align: center;
            margin-top: 10px;
        }
    </style>
</head>
<body>
    <!-- 继承整个头部 -->
    {% include "header.html" %}
    <div class="main">
        我是身体
    </div>
</body>
</html>

views.py

from django.shortcuts import render
from django.views import View

# 模板继承的基本使用
class IndexView(View):
    def get(self, request, *args, **kwargs):
        return render(request, 'index.html')

urls.py

from django.conf.urls import url
from . import views

urlpatterns = [
    url('index/$', views.IndexView.as_view()),
]

效果展示

Django 模型详解

1、前戏

(1) 介绍

模型是您的数据唯一而且准确的信息来源。它包含您正在储存的数据的重要字段和行为。一般来说,每一个模型都映射一个数据库表。

基础:

  • 每个模型都是一个 Python 的类,这些类继承 django.db.models.Model
  • 模型类的每个属性都相当于一个数据库的字段。
  • 综上诉说,Django 给你一个自动生成访问数据库的 API;请参阅 Making queries

(2) 创建新的应用

python manage.py startapp test_models
# 安装pymysql
pip install pymysql

(3) 配置

settings.py

# 连接本地数据库
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        # 数据库名称
        'NAME': 'demo',
        # 账号
        'USER': 'root',
        # 密码
        'PASSWORD': '19971215',
        # IP
        'HOST': '127.0.0.1',
        # 端口
        'PORT': '3306',
    }
}

test_models/_init_.py

import pymysql
pymysql.install_as_MySQLdb()

2、字段

(1) 字段类型

字段 类型 说明
CharField 字符串 字符串类型字段 必须加max_length参数
IntegerField 整数类型 -2147483648到2147483647。
SmallIntegerField 整数类型 -32768 ~ 32767
PositiveBigIntegerField 正整数 0 ~ 9223372036854775807
PositiveIntegerField 正整数 0 ~ 2147483647
PositiveSmallIntegerField 正整数 0 ~ 32767
BigIntegerField 整数字段 -9223372036854775808 ~ 9223372036854775807。
FloatField 浮点型 浮点数类型,对应Python的float。参考整数类型字段。
DecimalField 浮点型 固定精度的十进制小数。
AutoField 自增字段 一个自动增加的整数类型字段。(Django会自动帮你添加)
SmallAutoField 自增字段 Django3.0新增:1 ~ 32767。
BigAutoField 自增字段 1 ~ 9223372036854775807
BooleanField 布尔类型 一般用于状态的判断(True、False)
TextField 字符串
DateField 日期类型 2020-02-05
DateTimeField 日期时间类型 2020-02-05 17:26:50
TimeField 时间字段 17:26:50
DurationField 持续时间类型 存储一定期间的时间长度。常用于进行时间之间的加减运算。
ImageField 图片 upload/images/demo.png
FileField 文件 upload/files/demo.txt
FilePathField 文件路径 文件路径类型
EmailField 邮箱类型 默认max_length最大长度254位
JSONField JSON类型 Django3.1新增:class JSONField(encoder=None,decoder=None,**options)`。
URLField 字符串 一个用于保存URL地址的字符串类型,默认最大长度200。
UUIDField 唯一字段 用于保存通用唯一识别码的字段。使用Python的UUID类。
BinaryField 二进制类型 较少使用。
OneToOneField 一对一关系 models.OneToOneField(to="要关联的模型", on_delete=models.CASCADE)
ForeignKey 一对多关系 models.ForeignKey(to="要关联的模型", on_delete=models.CASCADE)
ManyToManyField 多对多关系 models.ManyToManyField(to="要关联的模型", on_delete=models.CASCADE)

(2) 字段属性

属性 说明
max_length=32 一般和CharField一起使用,用于限制字符长度。
blank=Ture 该字段是否可以为空。如果为False,则必须有值。
null=Ture 缺省设置为false.通常不将其用于字符型字段上。
default="默认值" 设定缺省值
choices=((‘F’,'Female’),(‘M’,'Male’),) 一个用来选择值的2维元组。第一个值是实际存储的值,第二个用来方便进行选择
unique=True 确保数据唯一
unique_for_date="字段名称" 日期唯一,如下例中系统将不允许 当前字段 和 指定字段 两个都相同的数据重复出现
unique_for_month="字段名称" 用法同上
unique_for_year="字段名称" 用法同上
verbose_name="详细名称" 详细名称
help_text="帮助信息" admin模式下帮助文档
editable=False admin模式下是否可编辑
db_index=True 如果为真将为此字段创建索引
on_delete=models.CASCADE
primary_key=True 如果为真将此字段设置为主键
name 数据库存储的名字

on_delete可选值:

说明
models.CASCADE 级联操作。如果外键对应的那条数据被删除了,那么这条数据也会被删除。
models.PROTECT 受保护。即只要这条数据引用了外键的那条数据,那么就不能删除外键的那条数据。
models.SET_NULL 设置为空。如果外键的那条数据被删除了,那么在本条数据上就将这个字段设置为空。如果设置这个选项,前提是要指定这个字段可以为空。
models.SET_DEFAULT 设置默认值。如果外键的那条数据被删除了,那么本条数据上就将这个字段设置为默认值。如果设置这个选项,前提是要指定这个字段一个默认值。
models.SET() 如果外键的那条数据被删除了。那么将会获取SET函数中的值来作为这个外键的值。SET函数可以接收一个可以调用的对象(比如函数或者方法),如果是可以调用的对象,那么会将这个对象调用后的结果作为值返回回去。
models.DO_NOTHING 不采取任何行为。一切全看数据库级别的约束。

(3) Meta属性

  • db_table:

    示例:db_table = ‘Students’

    说明:指定自定义数据库表名

  • verbose_name:

    示例:verbose_name = "学生"

    说明:verbose_name的意思很简单,就是给你的模型类起一个更可读的名字一般定义为中文

  • verbose_name_plural:

    示例:verbose_name_plural = "学生"(verbose_name_plural = verbose_name,一般这样写)

    说明:这个选项是指定,模型的复数形式是什么,如果不指定Django会自动在模型名称后加一个’s’。

  • app_label:

    示例:app_label = 'myapp'

    说明:如果一个model定义在默认的models.py,例如如果你的app的models在myapp.models子模块下,你必须定义app_label让Django知道它属于哪一个app

  • db_teblespace:

    示例:

    说明:定义这个model所使用的数据库表空间。如果在项目的settings中定义那么它会使用这个值

  • get_latest_by:

    示例:

    说明:在model中指定一个DateField或者DateTimeField。这个设置让你在使用model的Manager上的lastest方法时,默认使用指定字段来排序

  • managed:

    示例:managed=True

    说明:默认值为True,这意味着Django可以使用syncdb和reset命令来创建或移除对应的数据库。默认值为True,如果你不希望这么做,可以把manage的值设置为False

  • order_with_respect_to:

    示例:

    说明:这个选项一般用于多对多的关系中,它指向一个关联对象,就是说关联对象找到这个对象后它是经过排序的。指定这个属性后你会得到一个get_xxx_order()和set_xxx_order()的方法,通过它们你可以设置或者回去排序的对象

  • ordering:

    示例:ordering=[‘add_time’, '-number', '?order_date']

    说明:这个字段是告诉Django模型对象返回的记录结果集是按照哪个字段排序的。这是一个字符串的元组或列表,没有一个字符串都是一个字段和用一个可选的表明降序的’-‘构成。当字段名前面没有’-‘时,将默认使用升序排列。使用’?'将会随机排列

  • permissions:

    示例:permissions = (('can_deliver_pizzas','Can deliver pizzas'))

    说明:permissions主要是为了在Django Admin管理模块下使用的,如果你设置了这个属性可以让指定的方法权限描述更清晰可读。Django自动为每个设置了admin的对象创建添加,删除和修改的权限。

  • proxy:

    示例:proxy = True

    说明:这是为了实现代理模型使用的,如果proxy = True,表示model是其父的代理 model

  • unique_together:

    示例:unique_together = (("first_name", "last_name"),)

    说明:unique_together这个选项用于:当你需要通过两个字段保持唯一性时使用。比如假设你希望,一个Person的FirstName和LastName两者的组合必须是唯一的

(4) 字段的增删改查

4-1 字段的增加

  • 当表中已经有数据 可以终端内直接给出默认值
  • 该字段可以为空 info = models.CharField(max_length=255, null=True, blank=True, verbose_name="个人简介")
  • 直接给字段默认值 hobby = models.CharField(max_length=32, default="study", verbose_name="兴趣安好")

4-2 字段的修改

  • 直接修改字段属性,然后执行数据库迁移的命令

4-3 字段的删除(三思而后行)

  • 注释或删除对应的字段,然后执行数据库迁移的命令,执行完毕之后字段对应的数据也没有了

3、模型方法

from django.db import models
from datetime import date

# 用户表
class User(models.Model):
    id = models.AutoField(primary_key=True)
    username = models.CharField(max_length=32, verbose_name="用户名")
    password = models.CharField(max_length=64, verbose_name="用户密码")
    age = models.IntegerField(default=18, verbose_name="年龄")
    info = models.CharField(max_length=255, null=True, blank=True, verbose_name="个人简介")
    hobby = models.CharField(max_length=32, default="study", verbose_name="兴趣安好")
    register_time = models.DateField(default=date.today, verbose_name="注册日期")
    """
    CharField 必须指定 max_length 参数 不指定会报错
    verbose_name 该参数是所有字段都有的 就是用来解释字段的
    """

    class Mate:
        managed = True
        db_table = 'user'
        verbose_name = "用户"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.username


# 作者表
class Author(models.Model):
    """
    由于一张表中必须要有一个逐渐字段 并且一般情况下都叫id
    所以 orm 当你不定义逐渐的时候 orm 会自动帮你创建一个名为 id 的主键字段
    也就意味着后续我们在创建模型表的时候如果主键名没有额外的叫法 那么主键字段可以省略不写
    """
    name = models.CharField(max_length=32, verbose_name="用户名")
    age = models.IntegerField(default=18, verbose_name="用户密码")
    # 一对一
    author_dateil = models.OneToOneField(to='AuthorDateil', on_delete=models.CASCADE)
    class Mate:
        managed = True
        db_table = 'author'
        verbose_name = "作者"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name


class AuthorDateil(models.Model):
    # 电话号码 int 不够
    phone = models.BigIntegerField()
    addr = models.CharField(max_length=64)

    class Mate:
        managed = True
        db_table = 'author_dateil'
        verbose_name = "作者详情"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.phone


# 书籍表
class Books(models.Model):
    title = models.CharField(max_length=32, verbose_name="标题")
    price = models.DecimalField(max_digits=8, decimal_places=2)
    publish_date = models.DateField(auto_now_add=True, verbose_name="发布时间")
    # 一对多
    publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
    # 多对多
    authors = models.ManyToManyField(to='Author')
    class Mate:
        managed = True
        db_table = 'books'
        verbose_name = "书籍"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.title


# 出版社
class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=64)
    email = models.EmailField()

    class Mate:
        managed = True
        db_table = 'publish'
        verbose_name = "出版社"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name

(1) 增

1-1 object.create()

res = models.Books.objects.create(title="Test_001", price=88.88, publish_id=1)
print(res)

# 执行结果:
# Test_001

1-2 object.bulk_create()

批量创建

方式一:

create_books_list = []
books_list = [
    {"title": "Test", "price": 88.88, "publish_id": 1},
    {"title": "Test", "price": 88.88, "publish_id": 1},
    {"title": "Test", "price": 88.88, "publish_id": 1}
]
for book in books_list:
    create_books_list.append(models.Books(title=book["title"], price=book["price"], publish_id=book["publish_id"]))
    res = models.Books.objects.bulk_create(create_books_list)
    print(res)

# 执行结果:
# [<Books: Test>, <Books: Test>, <Books: Test>]

方式二:

create_books_list = []
create_books_list.append(models.Books(title="Test", price=88.88, publish_id=1))
create_books_list.append(models.Books(title="Test", price=88.88, publish_id=1))
create_books_list.append(models.Books(title="Test", price=88.88, publish_id=1))
res = models.Books.objects.bulk_create(create_books_list)
print(res)

# 执行结果:
# [<Books: Test>, <Books: Test>, <Books: Test>]

1-3 object.save()

方式一:先实例数据,再保存

book_obj = models.Books.objects.create(title="Test", price=88.88, publish_id=1)
res = book_obj.save()
print(res)

# 执行结果
# None

方式二: 实例化数据,再赋值,然后保存

book_obj = models.Books.objects.create(title="Test", price=88.88, publish_id=1)
book_obj.publish_date = "2020-01-01"
res = book_obj.save()
print(res)

# 执行结果
# None

1-4 objects.get_or_create()

先判断是否已存在。存在就返回false,不存在就创建

这种方法是防止重复很好的方法,但是速度要相对慢些,返回一个元组,第一个为Person对象,第二个为True或False, 新建时返回的是True, 已经存在时返回False..

注意:如果数据库存在多条则会报错

方式一

res = models.Books.objects.get_or_create(title="Test_001", price=88.88, publish_id=1)
print(res)

# 执行结果
# (<Books: Test_001>, False)

方式二

defaults = {
    "price": 88.88,
    "publish_id": 1
}
res = models.Books.objects.get_or_create(title="Test_002", defaults=defaults)
print(res)

# 执行结果
# (<Books: Test_002>, True)

1-5 objects.upadte_or_create()

存在就修改,不存在就创建

# 当数据库中已经存在 Test_002 这本书,就修改该书籍的 价格 和 出版社,不存在则创建 Test_002 这本书
defaults = {
    "price": 99.99,
    "publish_id": 2
}
res = models.Books.objects.update_or_create(title="Test_002", defaults=defaults)
print(res)

# 执行结果
# (<Books: Test_002>, False)

(2) 删

2-1 objects.delete()

删除书名为 "Test_002" 的所有数据(下面两种方式效果是一样的)

方式一

res = models.Books.objects.filter(title="Test_002").delete()
print(res)
    
# 执行结果
# (1, {'app.Books_authors': 0, 'app.Books': 1})

方式二

books_obj = models.Books.objects.filter(title="Test_002")
res = books_obj.delete()
print(res)

# 执行结果
# (0, {})

(3) 改

方式一:

# 批量更新
# 将书名为 "Test" 的书名都修改为 "Django"
res = models.Books.objects.filter(title="Test").update(title="Django")
print(res)

# 执行结果
# 6

方式二

# 单个更新
# 将书名为 "Test_001" 的第一个书名都修改为 "Flask"
book_obj = models.Books.objects.filter(title="Test_001").first()
book_obj.title = "Flask"
res = book_obj.save()
print(res)

# 执行结果
# None

方式三

# 单个更新
# 将书名为 "Flask" 的第一个书名都修改为 "Tornado"
# 注意:如果有多条数据会报错呦
book_obj = models.Books.objects.get(title="Flask")
book_obj.title = "Tornado"
res = book_obj.save()
print(res)

# 执行结果
# None

(4) 查

4-0 常用的过滤属性

属性 说明
__exact 精确等于 like 'aaa'
__iexact 精确等于 忽略大小写 ilike 'aaa'
__contains 包含 like '%aaa%'
__icontains 包含 忽略大小写 ilike '%aaa%',但是对于sqlite来说,contains的作用效果等同于icontains。
__gt 大于
__gte 大于等于
__lt 小于
__lte 小于等于
__in 存在于一个list范围内
__startswith 以...开头
__istartswith 以...开头 忽略大小写
__endswith 以...结尾
__iendswith 以...结尾,忽略大小写
__range 在...范围内
__year 日期字段的年份
__month 日期字段的月份
__day 日期字段的日
__isnull=True/False 字段值是否为空

4-1 objects.all()

# 将书籍表中的数据全部查询出来
books_obj = models.Books.objects.all()
print(books_obj)

# 将书籍名为 “Django” 的所有数据查询出来
books_obj = models.Books.objects.filter(title="Django").all()
print(books_obj)

# 执行结果
"""
<QuerySet [<Books: Python>, <Books: Java>, <Books: C>, <Books: C++>, <Books: Vue>, <Books: JavaScript>, <Books: Tornado>, <Books: Django>, <Books: Django>, <Books: Django>, <Books: Django>, <Books: Django>, <Books: Django>]>
<QuerySet [<Books: Django>, <Books: Django>, <Books: Django>, <Books: Django>, <Books: Django>, <Books: Django>]>
"""

4-2 objects.fileter()

# 查询书籍名为 "Django" 的所有数据
books_obj = models.Books.objects.filter(title="Django")
print(books_obj)

# 查询书籍名为 "abc | ABC | Abc | aBC" 的所有数据(不区分大小写)
books_obj = models.Books.objects.filter(title__iexact="abc")
print(books_obj)

# 查询书籍名中包含 "abc" 的所有数据(模糊搜索)
books_obj = models.Books.objects.filter(title__contains="abc")
print(books_obj)

# 查询书籍名中包含 "abc | ABC | Abc | aBC" 的所有数据(不区分大小写)
books_obj = models.Books.objects.filter(title__icontains="abc")
print(books_obj)

# 正则表达式查询
books_obj = models.Books.objects.filter(title__regex="^abc")
print(books_obj)

# 正则表达式查询(不区分大小写)
books_obj = models.Books.objects.filter(title__iregex="^abc")
print(books_obj)

# 执行结果
"""
<QuerySet [<Books: Django>, <Books: Django>, <Books: Django>, <Books: Django>, <Books: Django>, <Books: Django>]>
<QuerySet []>
<QuerySet []>
<QuerySet []>
<QuerySet []>
<QuerySet []>
"""

4-3 objects.first()

# 获取书籍中的第一条数据
book_obj = models.Books.objects.first()
print(book_obj, book_obj.id)

# 获取书籍名为 "Django" 的第一条数据
book_obj = models.Books.objects.filter(title="Django").first()
print(book_obj, book_obj.id)

# 执行结果
"""
Python 1
Django 8
"""

4-4 objects.last()

# 获取书籍中的最后一条数据
book_obj = models.Books.objects.last()
print(book_obj, book_obj.id)

# 获取书籍名为 "Django" 的最后一条数据
book_obj = models.Books.objects.filter(title="Django").last()
print(book_obj, book_obj.id)

# 执行结果
"""
Django 13
Django 13
"""

4-5 objects.get()

# 获取书籍名为 "Tornado" 的单条数据
# 注意:如果有多条数据符合条件 则会报错:get() returned more than one Books -- it returned 6!
# 注意:找不到也会报错:Books matching query does not exist.
book_obj = models.Books.objects.get(title="Tornado")
print(book_obj)

# 执行结果
"""
Tornado
"""

4-6 objects.exclude()

# 找出书籍名不为 "Django" 的所有书籍
books_obj = models.Books.objects.exclude(title="Django")
print(books_obj)

# 找出售价大于88的书籍 但是排除书名为 "Django" 的所有数据
books_obj = models.Books.objects.filter(price__gt=88).exclude(title="Django")
print(books_obj)

# 执行结果
"""
<QuerySet [<Books: Python>, <Books: Java>, <Books: C>, <Books: C++>, <Books: Vue>, <Books: JavaScript>, <Books: Tornado>]>
<QuerySet [<Books: Python>, <Books: Java>, <Books: C>, <Books: C++>, <Books: Vue>, <Books: JavaScript>, <Books: Tornado>]>
"""

4-7 objects.count()

# 找出书籍名为 "Django" 的所有书籍数量
books_count = models.Books.objects.filter(title="Django").count()
print(books_count)

# 执行结果
"""
6
"""

4-8 objects.order_by()

# 查询所有书籍表中的数据 并按照价格进行 从小到大 排序
books_obj = models.Books.objects.order_by('price').values("title", "price")
print(books_obj)

# 查询书籍名为 "Django" 的所有数据 并按照价格进行 从大到小 排序
books_obj = models.Books.objects.filter(title="Django").order_by('-price').values("title", "price", "id")
print(books_obj)

# 查询书籍名为 "Django" 的所有数据 并按照价格进行 从大到小 排序 如果价格相同则按照 id 倒序排列
books_obj = models.Books.objects.filter(title="Django").order_by('-price', '-id').values("title", "price", "id")
print(books_obj)

# 执行结果
"""
<QuerySet [{'title': 'Tornado', 'price': Decimal('88.88')}, {'title': 'Django', 'price': Decimal('88.88')}, {'title': 'Django', 'price': Decimal('88.88')}, {'title': 'Django', 'price': Decimal('88.88')}, {'title': 'Django', 'price': Decimal('88.88')}, {'title': 'Django', 'price': Decimal('88.88')}, {'title': 'Django', 'price': Decimal('88.88')}, {'title': 'Java', 'price': Decimal('188.88')}, {'title': 'Python', 'price': Decimal('199.80')}, {'title': 'JavaScript', 'price': Decimal('555.55')}, {'title': 'Vue', 'price': Decimal('666.66')}, {'title': 'C++', 'price': Decimal('888.88')}, {'title': 'C', 'price': Decimal('999.88')}]>
<QuerySet [{'title': 'Django', 'price': Decimal('88.88'), 'id': 8}, {'title': 'Django', 'price': Decimal('88.88'), 'id': 9}, {'title': 'Django', 'price': Decimal('88.88'), 'id': 10}, {'title': 'Django', 'price': Decimal('88.88'), 'id': 11}, {'title': 'Django', 'price': Decimal('88.88'), 'id': 12}, {'title': 'Django', 'price': Decimal('88.88'), 'id': 13}]>
<QuerySet [{'title': 'Django', 'price': Decimal('88.88'), 'id': 13}, {'title': 'Django', 'price': Decimal('88.88'), 'id': 12}, {'title': 'Django', 'price': Decimal('88.88'), 'id': 11}, {'title': 'Django', 'price': Decimal('88.88'), 'id': 10}, {'title': 'Django', 'price': Decimal('88.88'), 'id': 9}, {'title': 'Django', 'price': Decimal('88.88'), 'id': 8}]>
"""

4-9 objects.values()

# 获取指定字段的数据列表
books_obj = models.Books.objects.values('id', 'title')
print(books_obj)

# 这里也可以指定外键字段 使用双下划线指定,返回的键值与values中的参数一致
books_obj = models.Books.objects.values('id', 'title', 'publish__name')
print(books_obj)

# 执行结果
"""
<QuerySet [{'id': 1, 'title': 'Python'}, {'id': 2, 'title': 'Java'}, {'id': 3, 'title': 'C'}, {'id': 4, 'title': 'C++'}, {'id': 5, 'title': 'Vue'}, {'id': 6, 'title': 'JavaScript'}, {'id': 7, 'title': 'Tornado'}, {'id': 8, 'title': 'Django'}, {'id': 9, 'title': 'Django'}, {'id': 10, 'title': 'Django'}, {'id': 11, 'title': 'Django'}, {'id': 12, 'title': 'Django'}, {'id': 13, 'title': 'Django'}]>
<QuerySet [{'id': 1, 'title': 'Python', 'publish__name': '新华出版社'}, {'id': 2, 'title': 'Java', 'publish__name': '新华出版社'}, {'id': 7, 'title': 'Tornado', 'publish__name': '新华出版社'}, {'id': 8, 'title': 'Django', 'publish__name': '新华出版社'}, {'id': 9, 'title': 'Django', 'publish__name': '新华出版社'}, {'id': 10, 'title': 'Django', 'publish__name': '新华出版社'}, {'id': 11, 'title': 'Django', 'publish__name': '新华出版社'}, {'id': 12, 'title': 'Django', 'publish__name': '新华出版社'}, {'id': 13, 'title': 'Django', 'publish__name': '新华出版社'}, {'id': 3, 'title': 'C', 'publish__name': '出版社1'}, {'id': 4, 'title': 'C++', 'publish__name': '出版社1'}, {'id': 5, 'title': 'Vue', 'publish__name': '出版社2'}, {'id': 6, 'title': 'JavaScript', 'publish__name': '出版社2'}]>
"""

4-10 objects.valves_list()

# 检索记录特定字段 values_list,flat=True返回值数据格式是列表,否则为字典套元组的那种。
books_obj = models.Books.objects.values_list('title', flat=False)
print(books_obj)

books_obj = models.Books.objects.values_list('title', flat=True)
print(books_obj)

# 当使用多个字段调用values_list时,'flat'无效。
books_obj = models.Books.objects.values_list('id', 'title')
print(books_obj)

# 执行结果
"""
<QuerySet [('Python',), ('Java',), ('C',), ('C++',), ('Vue',), ('JavaScript',), ('Tornado',), ('Django',), ('Django',), ('Django',), ('Django',), ('Django',), ('Django',)]>
<QuerySet ['Python', 'Java', 'C', 'C++', 'Vue', 'JavaScript', 'Tornado', 'Django', 'Django', 'Django', 'Django', 'Django', 'Django']>
<QuerySet [(1, 'Python'), (2, 'Java'), (3, 'C'), (4, 'C++'), (5, 'Vue'), (6, 'JavaScript'), (7, 'Tornado'), (8, 'Django'), (9, 'Django'), (10, 'Django'), (11, 'Django'), (12, 'Django'), (13, 'Django')]>
"""

4-11 objects.aggregate()

聚合查询

集合查询通常情况下都是配合分组一起使用的

只要是跟数据库相关的模块

基本上都在 django.db.models里面

如果上述没有那么应该在django.db里面

from django.db.models import Max, Min, Sum, Count, Avg
res = models.Books.objects.aggregate(
    Max("price"),
    Min("price"),
    Sum("price"),
    Count("pk"),
    Avg("price"),
)
print(res)

# 执行结果
"""
{'price__max': Decimal('999.88'), 'price__min': Decimal('88.88'), 'price__sum': Decimal('4121.81'), 'pk__count': 13, 'price__avg': 317.062308}
"""

4-12 objects.annotate()

分组查询

MySQL 分组查询都有哪些特点

  • 分组之后默认只能获取到分组的依据 组内其他字段都无法直接获取
  • 严格模式:ONLY_FULL_GROUP_BY

基本使用:

from django.db.models import Max, Min, Sum, Count, Avg
# 1. 统计每一本书得到作者个数
# author_num 使我们自己定义的字段 用来存储统计出来的每本书对应的作者个数
# res = models.Books.objects.annotate(author_num=Count('authors__id')).values("title", "author_num")
res = models.Books.objects.annotate(author_num=Count('authors')).values("title", "author_num")
print(res)

# 2. 统计每个出版社买的最便宜的数的价格
res = models.Publish.objects.annotate(min_price=Min("books__price")).values("name", "min_price")
print(res)

# 3. 统计不止一个作者的图书
# 3.1 先按照图书分组, 求每一本书对应的作者个数
# 3.2 过滤出不止一个作者的图书
res = models.Books.objects.annotate(author_num=Count("authors")).filter(author_num__gt=2).values("title", "author_num")
"""
只要你的 orm 语句得出的结果还是一个 queryset 对象
那么它就可以继续无限制得到点 queryset 对象封装的方法
"""
print(res)

# 4. 查询每个作者出版的数的总价格
res = models.Author.objects.annotate(sum_price=Sum("books__price")).values("name", "sum_price")
print(res)

# 执行结果
"""
<QuerySet [{'title': 'Python', 'author_num': 2}, {'title': 'Java', 'author_num': 3}, {'title': 'C', 'author_num': 3}, {'title': 'C++', 'author_num': 3}, {'title': 'Vue', 'author_num': 4}, {'title': 'JavaScript', 'author_num': 4}, {'title': 'Tornado', 'author_num': 0}, {'title': 'Django', 'author_num': 0}, {'title': 'Django', 'author_num': 0}, {'title': 'Django', 'author_num': 0}, {'title': 'Django', 'author_num': 0}, {'title': 'Django', 'author_num': 0}, {'title': 'Django', 'author_num': 0}]>
<QuerySet [{'name': '新华出版社', 'min_price': Decimal('88.88')}, {'name': '出版社1', 'min_price': Decimal('888.88')}, {'name': '出版社2', 'min_price': Decimal('555.55')}]>
<QuerySet [{'title': 'Java', 'author_num': 3}, {'title': 'C', 'author_num': 3}, {'title': 'C++', 'author_num': 3}, {'title': 'Vue', 'author_num': 4}, {'title': 'JavaScript', 'author_num': 4}]>
<QuerySet [{'name': 'test_1', 'sum_price': Decimal('944.23')}, {'name': 'test_2', 'sum_price': Decimal('2055.22')}, {'name': 'test_3', 'sum_price': Decimal('3299.85')}, {'name': 'test_4', 'sum_price': Decimal('2555.42')}, {'name': 'test_5', 'sum_price': Decimal('2111.09')}, {'name': 'test_6', 'sum_price': None}, {'name': 'test_7', 'sum_price': Decimal('555.55')}]>
"""

如果我想按照指定的字段进行分组给如何处理呢

4-13 F 查询

from django.db.models import F
# ----- F 查询 -----
# F: 能够帮助你直接获取到表中某个字段对应的数据
# 1. 查询卖出数大于库存数的书籍
res = models.Books.objects.filter(sell__gt=F('inventory'))
print(res)

# 2. 将所有书籍的价格提升50块
res = models.Books.objects.update(price=F("price") + 50)
print(res)

# 3. 将所有书的名称后面加上爆款两个字
# 在操作字符类型的数据的时候 F 不能直接做到字符串的拼接
from django.db.models.functions import Concat
from django.db.models import Value
res = models.Books.objects.update(title=Concat(F("title"), Value("爆款")))
print(res)

# 执行结果
"""
<QuerySet [<Books: C>, <Books: Vue>, <Books: Tronado>]>
13
13
"""

4-14 Q 查询

from django.db.models import Q
# 1. 查询卖出数大于 100 或者 价格小于 600 的书籍
# filter 括号内多个参数是 and 关系
# res = models.Books.objects.filter(sell__gt=100, price__lt=600)
# Q包裹逗号分隔 还是 and 关系
res = models.Books.objects.filter(Q(sell__gt=100), Q(price__lt=600))
print(res)

# | or 关系
res = models.Books.objects.filter(Q(sell__gt=100) | Q(price__lt=600))
print(res)

# ~ NOT 关系
res = models.Books.objects.filter(~Q(sell__gt=100) | ~Q(price__lt=600))
print(res)
    
# 执行结果
"""
<QuerySet []>
<QuerySet [<Books: Python>, <Books: Java>, <Books: JavaScript>, <Books: C>, <Books: C++>, <Books: Go>, <Books: Vue>, <Books: Flask>, <Books: Tronado>, <Books: Django>, <Books: Django>, <Books: Django>, <Books: Django>]>
<QuerySet [<Books: Python>, <Books: Java>, <Books: JavaScript>, <Books: C>, <Books: C++>, <Books: Go>, <Books: Vue>, <Books: Flask>, <Books: Tronado>, <Books: Django>, <Books: Django>, <Books: Django>, <Books: Django>]>
"""

4-15 Q的高阶用法

from django.db.models import Q
# 能够将查询条件的左边也变成字符串的形式
q = Q()
q.connector = "or"
q.children.append(("sell__gt", 100))
q.children.append(("price__lt", 600))
# filter 括号内也支持q对象, 默认还是and关系
res = models.Books.objects.filter(q)
print(res)

# 执行结果
"""
<QuerySet [<Books: Python爆款>, <Books: Java爆款>, <Books: JavaScript爆款>, <Books: C爆款>, <Books: C++爆款>, <Books: Go爆款>, <Books: Vue爆款>, <Books: Flask爆款>, <Books: Tronado爆款>, <Books: Django爆款>, <Books: Django爆款>, <Books: Django爆款>, <Books: Django爆款>]>
"""

4、关系表操作

(1) 多对多三种创建方式

1-1 全自动

利用orm 自动帮我们创建第三张关系表

class Book(models.Model):
    name = models.CharField(max_length=32, verbose_name="书籍名称")
    authors = models.ManyToManyField(to='Author')

class Author(models.Model):
    name = models.CharField(max_length=32, verbose_name="作者名称")

优点:代码不要你自己写 还支持 orm 提供操作第三张关系表的方法 add,...

缺点:第三张关系表的扩展性极差(没有办法额外添加字段...)

1-2 全手动

class Book(models.Model):
    name = models.CharField(max_length=32, verbose_name="书籍名称")

class Author(models.Model):
    name = models.CharField(max_length=32, verbose_name="作者名称")

class Book2Author(models.Model):
    book_id = models.ForeignKey(to='Book', on_delete=models.CASCADE)
    author_id = models.ForeignKey(to='Author', on_delete=models.CASCADE)

优点:第三张表完全取决于你自己进行额外的扩展

缺点:需要些的代码较多,不能在使用 orm 提供的简单的方法

不建议使用该方式

1-3 半自动

class Book(models.Model):
    name = models.CharField(max_length=32, verbose_name="书籍名称")
    authors = models.ManyToManyField(to='Author', through="Book2Author", through_fields=('book', 'author'))

class Author(models.Model):
    name = models.CharField(max_length=32, verbose_name="作者名称")
    books = models.ManyToManyField(to='Book', through="Book2Author", through_fields=('author', 'book'))

class Book2Author(models.Model):
    book_id = models.ForeignKey(to='Book', on_delete=models.CASCADE)
    author_id = models.ForeignKey(to='Author', on_delete=models.CASCADE)
    # 这里面可以添加你自定义的字段
    status = models.BooleanField(default=True, verbose_name="状态")

注意:

through_fields 字段先后顺序

判断的本质: 通过第三张表查询对应的表 需要用到哪个字段就把哪个字段放前面

也可以简化判断:当前表是谁 就把对应的关联字段放在前面

缺点: 可以使用 orm 反向查询 但是没办法使用 add, set, remove, clear这四个方法

总结:你需要掌握的是全自动和半自动 为了扩展性更高 一般我们都会采用半自动方式

(2) 一对多外键CRUD

2-1 增

# 方式一: 直接写实际字段 id
res = models.Books.objects.create(title="三国演义", price=88.88, publish_id=1)
print(res)

# 方式二: 虚拟字段 对象
publish_obj = models.Publish.objects.first()
res = models.Books.objects.create(title="红楼梦", price=99.99, publish=publish_obj)
print(res)

执行结果:

三国演义
红楼梦

原生SQL

INSERT INTO `app_books` (`title`, `price`, `publish_date`, `inventory`, `sell`, `publish_id`) VALUES ('三国演义', '88.88', '2021-02-14', 1000, 100, 1); args=['三国演义', '88.88', '2021-02-14', 1000, 100, 1]
SELECT `app_publish`.`id`, `app_publish`.`name`, `app_publish`.`addr`, `app_publish`.`email` FROM `app_publish` ORDER BY `app_publish`.`id` ASC LIMIT 1;
INSERT INTO `app_books` (`title`, `price`, `publish_date`, `inventory`, `sell`, `publish_id`) VALUES ('红楼梦', '99.99', '2021-02-14', 1000, 100, 1); args=['红楼梦', '99.99', '2021-02-14', 1000, 100, 1]

2-2 删

# 级联删除
res = models.Publish.objects.filter(pk=4).delete()
print(res)

执行结果:

(1, {'app.Publish': 1})

原生SQL

SELECT `app_publish`.`id`, `app_publish`.`name`, `app_publish`.`addr`, `app_publish`.`email` FROM `app_publish` WHERE `app_publish`.`id` = 4;
SELECT `app_books`.`id`, `app_books`.`title`, `app_books`.`price`, `app_books`.`publish_date`, `app_books`.`inventory`, `app_books`.`sell`, `app_books`.`publish_id` FROM `app_books` WHERE `app_books`.`publish_id` IN (4);
DELETE FROM `app_publish` WHERE `app_publish`.`id` IN (4);

2-3 改

# 方式一
res = models.Books.objects.filter(title="三国演义").update(publish_id=2)
print(res)

# 方式二
publish_obj = models.Publish.objects.filter(name="新华出版社").first()
res = models.Books.objects.filter(title="红楼梦").update(publish=publish_obj)
print(res)

执行结果:

2
2

原生SQL

UPDATE `app_books` SET `publish_id` = 2 WHERE `app_books`.`title` = '三国演义';
SELECT `app_publish`.`id`, `app_publish`.`name`, `app_publish`.`addr`, `app_publish`.`email` FROM `app_publish` WHERE `app_publish`.`name` = '新华出版社' ORDER BY `app_publish`.`id` ASC LIMIT 1; args=('新华出版社',);
UPDATE `app_books` SET `publish_id` = 1 WHERE `app_books`.`title` = '红楼梦'; args=(1, '红楼梦');

(3) 多对多外键CRUD

其实本质上就是在操作第三张表

3-1 增

# 如何给书籍添加作者
book_obj = models.Books.objects.first()
# 这句话的意思就类似于你已经到了第三张关系表了
# print(book_obj.authors)
# 1. 单个添加 给第一个书籍 绑定一个主键为5的作者
res = book_obj.authors.add(3)
book_obj.save()
print(res)
# 2. 批量添加
res = book_obj.authors.add(4, 5)
book_obj.save()
print(res)

# 3. 添加单个对象
author_obj = models.Author.objects.filter(pk=6).first()
res = book_obj.authors.add(author_obj)
book_obj.save()
print(res)
# 4. 添加多个对象
author_obj1 = models.Author.objects.filter(pk=6).first()
author_obj2 = models.Author.objects.filter(pk=7).first()
res = book_obj.authors.add(author_obj1, author_obj2)
book_obj.save()
print(res)

执行结果

None
None
None
None

原生SQL

SELECT `app_books_authors`.`author_id` FROM `app_books_authors` WHERE (`app_books_authors`.`author_id` IN (3) AND `app_books_authors`.`books_id` = 1);
INSERT INTO `app_books_authors` (`books_id`, `author_id`) VALUES (1, 3);
SELECT `app_books_authors`.`author_id` FROM `app_books_authors` WHERE (`app_books_authors`.`author_id` IN (4, 5) AND `app_books_authors`.`books_id` = 1);
INSERT INTO `app_books_authors` (`books_id`, `author_id`) VALUES (1, 4), (1, 5);
SELECT `app_author`.`id`, `app_author`.`name`, `app_author`.`age`, `app_author`.`author_dateil_id` FROM `app_author` WHERE `app_author`.`id` = 6 ORDER BY `app_author`.`id` ASC LIMIT 1;
SELECT `app_books_authors`.`author_id` FROM `app_books_authors` WHERE (`app_books_authors`.`author_id` IN (6) AND `app_books_authors`.`books_id` = 1);
INSERT INTO `app_books_authors` (`books_id`, `author_id`) VALUES (1, 6);

总结:

add 给第三张关系表添加数据,括号内可以穿对象 并且支持多个

3-2 删

book_obj = models.Books.objects.first()
# 删除单个
res = book_obj.authors.remove(3)
print(res)

# 删除多个
res = book_obj.authors.remove(3, 4)
print(res)

# 删除单个(对象)
author_obj = models.Author.objects.filter(pk=5).first()
res = book_obj.authors.remove(author_obj)
print(res)

# 删除多个(对象)
author_obj1 = models.Author.objects.filter(pk=5).first()
author_obj2 = models.Author.objects.filter(pk=6).first()
res = book_obj.authors.remove(author_obj1, author_obj2)
print(res)

执行结果:

None
None
None
None

原生SQL

DELETE FROM `app_books_authors` WHERE (`app_books_authors`.`books_id` = 1 AND `app_books_authors`.`author_id` IN (3));
DELETE FROM `app_books_authors` WHERE (`app_books_authors`.`books_id` = 1 AND `app_books_authors`.`author_id` IN (3, 4));
SELECT `app_author`.`id`, `app_author`.`name`, `app_author`.`age`, `app_author`.`author_dateil_id` FROM `app_author` WHERE `app_author`.`id` = 5 ORDER BY `app_author`.`id` ASC LIMIT 1;
DELETE FROM `app_books_authors` WHERE (`app_books_authors`.`books_id` = 1 AND `app_books_authors`.`author_id` IN (5));
SELECT `app_author`.`id`, `app_author`.`name`, `app_author`.`age`, `app_author`.`author_dateil_id` FROM `app_author` WHERE `app_author`.`id` = 5 ORDER BY `app_author`.`id` ASC LIMIT 1;
SELECT `app_author`.`id`, `app_author`.`name`, `app_author`.`age`, `app_author`.`author_dateil_id` FROM `app_author` WHERE `app_author`.`id` = 6 ORDER BY `app_author`.`id` ASC LIMIT 1;
DELETE FROM `app_books_authors` WHERE (`app_books_authors`.`books_id` = 1 AND `app_books_authors`.`author_id` IN (5, 6));

3-3 改

book_obj = models.Books.objects.first()
# 括号内必须放一个可迭代对象 元祖 或 列表
# 方式一
# 将之前的和第一个书籍关联的所有关系删除 重新创建一个作者为3的关系
res = book_obj.authors.set((3,))
print(res)

# 方式二
res = book_obj.authors.set((1, 3))
print(res)

# 方式三
res = book_obj.authors.set([1, 2])
print(res)

# 方式四
author_obj1 = models.Author.objects.filter(pk=3).first()
author_obj2 = models.Author.objects.filter(pk=4).first()
res = book_obj.authors.set([author_obj1, author_obj2])
print(res)

执行结果

None
None
None
None

原生SQL

SELECT `app_author`.`id` FROM `app_author` INNER JOIN `app_books_authors` ON (`app_author`.`id` = `app_books_authors`.`author_id`) WHERE `app_books_authors`.`books_id` = 1;
DELETE FROM `app_books_authors` WHERE (`app_books_authors`.`books_id` = 1 AND `app_books_authors`.`author_id` IN (1, 2));
SELECT `app_books_authors`.`author_id` FROM `app_books_authors` WHERE (`app_books_authors`.`author_id` IN (3) AND `app_books_authors`.`books_id` = 1);
INSERT INTO `app_books_authors` (`books_id`, `author_id`) VALUES (1, 3);
SELECT `app_author`.`id` FROM `app_author` INNER JOIN `app_books_authors` ON (`app_author`.`id` = `app_books_authors`.`author_id`) WHERE `app_books_authors`.`books_id` = 1;
SELECT `app_books_authors`.`author_id` FROM `app_books_authors` WHERE (`app_books_authors`.`author_id` IN (1) AND `app_books_authors`.`books_id` = 1);
INSERT INTO `app_books_authors` (`books_id`, `author_id`) VALUES (1, 1);
SELECT `app_author`.`id` FROM `app_author` INNER JOIN `app_books_authors` ON (`app_author`.`id` = `app_books_authors`.`author_id`) WHERE `app_books_authors`.`books_id` = 1;
DELETE FROM `app_books_authors` WHERE (`app_books_authors`.`books_id` = 1 AND `app_books_authors`.`author_id` IN (3));
SELECT `app_books_authors`.`author_id` FROM `app_books_authors` WHERE (`app_books_authors`.`author_id` IN (2) AND `app_books_authors`.`books_id` = 1);
INSERT INTO `app_books_authors` (`books_id`, `author_id`) VALUES (1, 2);
SELECT `app_author`.`id`, `app_author`.`name`, `app_author`.`age`, `app_author`.`author_dateil_id` FROM `app_author` WHERE `app_author`.`id` = 3 ORDER BY `app_author`.`id` ASC LIMIT 1;
SELECT `app_author`.`id`, `app_author`.`name`, `app_author`.`age`, `app_author`.`author_dateil_id` FROM `app_author` WHERE `app_author`.`id` = 4 ORDER BY `app_author`.`id` ASC LIMIT 1;
SELECT `app_author`.`id` FROM `app_author` INNER JOIN `app_books_authors` ON (`app_author`.`id` = `app_books_authors`.`author_id`) WHERE `app_books_authors`.`books_id` = 1;
DELETE FROM `app_books_authors` WHERE (`app_books_authors`.`books_id` = 1 AND `app_books_authors`.`author_id` IN (1, 2));
SELECT `app_books_authors`.`author_id` FROM `app_books_authors` WHERE (`app_books_authors`.`author_id` IN (3, 4) AND `app_books_authors`.`books_id` = 1);
INSERT INTO `app_books_authors` (`books_id`, `author_id`) VALUES (1, 3), (1, 4);

注意:set 括号内必须传一个可迭代,该对象内既可以传数字也可以传对象 并且都支持多个

3-4 清空

# 在第三张关系表中清空某个书籍与作者的绑定关系
book_obj = models.Books.objects.first()
res = book_obj.authors.clear()
print(res)

执行结果

None

原生SQL

DELETE FROM `app_books_authors` WHERE `app_books_authors`.`books_id` = 1;

总结:

clear 括号内不要写任何参数

(4) 多表查询

4-1 正反向的概念

概念:

  • 外键字段在我手上那么,我查你就是正向

  • 外键字段如果不在我手上,我查你就是反向

  • 一对一 多对多 正反向的判断也是如此

口诀:

  • 正向查询按字段
  • 反向查询按表名小写

4-2 子查询 - 基于对象的跨表查询

正向查询
# 基于对象的跨表查询
# 1. 查询书籍主键为 1 的出版社名称
book_obj = models.Books.objects.filter(pk=1).first()
# 根据书籍查出版社
res = book_obj.publish
print(res)
print(res.name)
print(res.addr)

# 2. 查询书籍主键为2的作者
book_obj = models.Books.objects.filter(pk=2).first()
# res = book_obj.authors    # app.Author.None
res = book_obj.authors.all()
print(res)


# 3. 查询作者 test_1 的电话号码
author_obj = models.Author.objects.filter(name="test_1").first()
res = author_obj.author_dateil
print(res)
print(res.phone)
print(res.addr)

执行结果

新华出版社
新华出版社
xxxxx
<QuerySet [<Author: test_1>, <Author: test_2>, <Author: test_3>]>
AuthorDateil object (1)
1888888888
xxxxxx

原生SQL

SELECT `app_publish`.`id`, `app_publish`.`name`, `app_publish`.`addr`, `app_publish`.`email` FROM `app_publish` WHERE `app_publish`.`id` = 1; args=(1,)

SELECT `app_books`.`id`, `app_books`.`title`, `app_books`.`price`, `app_books`.`publish_date`, `app_books`.`inventory`, `app_books`.`sell`, `app_books`.`publish_id` FROM `app_books` WHERE `app_books`.`id` = 2 ORDER BY `app_books`.`id` ASC LIMIT 1;

SELECT `app_author`.`id`, `app_author`.`name`, `app_author`.`age`, `app_author`.`author_dateil_id` FROM `app_author` INNER JOIN `app_books_authors` ON (`app_author`.`id` = `app_books_authors`.`author_id`) WHERE `app_books_authors`.`books_id` = 2 LIMIT 21;

SELECT `app_author`.`id`, `app_author`.`name`, `app_author`.`age`, `app_author`.`author_dateil_id` FROM `app_author` WHERE `app_author`.`name` = 'test_1' ORDER BY `app_author`.`id` ASC LIMIT 1;

SELECT `app_authordateil`.`id`, `app_authordateil`.`phone`, `app_authordateil`.`addr` FROM `app_authordateil` WHERE `app_authordateil`.`id` = 1;

注意:正向什么时候需要 .all()

  • 当你的看结果可能有多个的时候就需要 .all()
  • 如果是一个则直接拿到数据对象则不需要
反向查询
# 1. 查询出版社是 "新华出版社" 出版的书
publish_obj = models.Publish.objects.filter(name="新华出版社").first()
res = publish_obj.books_set.all()
print(res)

# 2. 查询作者是 "test_1" 写过的书
author_obj = models.Author.objects.filter(name="test_1").first()
res = author_obj.books_set.all()
print(res)

# 3. 查询手机号是 "18888888888" 的作者姓名
author_detail_obj = models.AuthorDateil.objects.filter(phone=18888888888).first()
res = author_detail_obj.author

print(res.name)

执行结果

<QuerySet [<Books: Python爆款>, <Books: Java爆款>, <Books: Vue爆款>, <Books: Flask爆款>, <Books: Tronado爆款>, <Books: Django爆款>, <Books: Django爆款>, <Books: Django爆款>, <Books: Django爆款>, <Books: 红楼梦>, <Books: 红楼梦>]>

<QuerySet [<Books: Java爆款>, <Books: Go爆款>]>

test_1

原生SQL

SELECT `app_books`.`id`, `app_books`.`title`, `app_books`.`price`, `app_books`.`publish_date`, `app_books`.`inventory`, `app_books`.`sell`, `app_books`.`publish_id` FROM `app_books` WHERE `app_books`.`publish_id` = 1 LIMIT 21;

SELECT `app_author`.`id`, `app_author`.`name`, `app_author`.`age`, `app_author`.`author_dateil_id` FROM `app_author` WHERE `app_author`.`name` = 'test_1' ORDER BY `app_author`.`id` ASC LIMIT 1;

SELECT `app_books`.`id`, `app_books`.`title`, `app_books`.`price`, `app_books`.`publish_date`, `app_books`.`inventory`, `app_books`.`sell`, `app_books`.`publish_id` FROM `app_books` INNER JOIN `app_books_authors` ON (`app_books`.`id` = `app_books_authors`.`books_id`) WHERE `app_books_authors`.`author_id` = 1 LIMIT 21;

SELECT `app_authordateil`.`id`, `app_authordateil`.`phone`, `app_authordateil`.`addr` FROM `app_authordateil` WHERE `app_authordateil`.`phone` = 18888888888 ORDER BY `app_authordateil`.`id` ASC LIMIT 1; args=(18888888888,)

SELECT `app_author`.`id`, `app_author`.`name`, `app_author`.`age`, `app_author`.`author_dateil_id` FROM `app_author` WHERE `app_author`.`author_dateil_id` = 1;

注意:

  • 当你的查询结果可以有多个的时候就必须加 _set.all()
  • 当你的查询结果只有一个的时候 就不需要加 _set.all()

4-3 连表查询 - 基于双下划线的跨表查询

# 基于双下划线的跨表查询
# 1. 查询作者 test_1 的电话号码和作者姓名 (一行代码搞定)
# 1-1 正向查询
res = models.Author.objects.filter(name="test_1").values('author_dateil__phone', 'name')
print(res)
# 1-2 反向查询
res = models.AuthorDateil.objects.filter(author__name="test_1").values('phone', 'author__name')
print(res)


# 2. 查询书籍主键为 1 的出版社名称 和 书的名称
# 2-1 正向查询
res = models.Books.objects.filter(pk=1).values('publish__name', "title")
print(res)
# 2-2 反向查询
res = models.Publish.objects.filter(books__id=1).values('name', "books__title")
print(res)


# 3. 查询书籍主键为 2 的作者姓名 和 书的名称
# 3-1 正向查询
res = models.Books.objects.filter(pk=2).values('authors__name')
print(res)
# 3-2 反向查询
res = models.Author.objects.filter(books__id=2).values('name')
print(res)

# 4. 查询书籍主键是 1 的作者的手机号
# 4-1 正向查询
res = models.Books.objects.filter(pk=2).values('authors__author_dateil__phone')
print(res)
# 4-2 反向查询
res = models.AuthorDateil.objects.filter(author__books__id=1).values('phone')
print(res)

执行结果

<QuerySet [{'author_dateil__phone': 18888888888, 'name': 'test_1'}]>
<QuerySet [{'phone': 18888888888, 'author__name': 'test_1'}]>
<QuerySet [{'publish__name': '新华出版社', 'title': 'Python爆款'}]>
<QuerySet [{'name': '新华出版社', 'books__title': 'Python爆款'}]>
<QuerySet [{'authors__name': 'test_1'}, {'authors__name': 'test_2'}, {'authors__name': 'test_3'}]>
<QuerySet [{'name': 'test_1'}, {'name': 'test_2'}, {'name': 'test_3'}]>
<QuerySet [{'phone': 18888888888}, {'phone': 18999999999}, {'phone': 18555555555}]>

原生SQL

SELECT `app_authordateil`.`phone`, `app_author`.`name` FROM `app_author` INNER JOIN `app_authordateil` ON (`app_author`.`author_dateil_id` = `app_authordateil`.`id`) WHERE `app_author`.`name` = 'test_1' LIMIT 21;

SELECT `app_authordateil`.`phone`, `app_author`.`name` FROM `app_authordateil` INNER JOIN `app_author` ON (`app_authordateil`.`id` = `app_author`.`author_dateil_id`) WHERE `app_author`.`name` = 'test_1' LIMIT 21;

SELECT `app_publish`.`name`, `app_books`.`title` FROM `app_books` INNER JOIN `app_publish` ON (`app_books`.`publish_id` = `app_publish`.`id`) WHERE `app_books`.`id` = 1 LIMIT 21;

SELECT `app_publish`.`name`, `app_books`.`title` FROM `app_publish` INNER JOIN `app_books` ON (`app_publish`.`id` = `app_books`.`publish_id`) WHERE `app_books`.`id` = 1 LIMIT 21;

SELECT `app_author`.`name` FROM `app_books` LEFT OUTER JOIN `app_books_authors` ON (`app_books`.`id` = `app_books_authors`.`books_id`) LEFT OUTER JOIN `app_author` ON (`app_books_authors`.`author_id` = `app_author`.`id`) WHERE `app_books`.`id` = 2 LIMIT 21;

SELECT `app_author`.`name` FROM `app_author` INNER JOIN `app_books_authors` ON (`app_author`.`id` = `app_books_authors`.`author_id`) WHERE `app_books_authors`.`books_id` = 2 LIMIT 21;

SELECT `app_authordateil`.`phone` FROM `app_books` LEFT OUTER JOIN `app_books_authors` ON (`app_books`.`id` = `app_books_authors`.`books_id`) LEFT OUTER JOIN `app_author` ON (`app_books_authors`.`author_id` = `app_author`.`id`) LEFT OUTER JOIN `app_authordateil` ON (`app_author`.`author_dateil_id` = `app_authordateil`.`id`) WHERE `app_books`.`id` = 2 LIMIT 21;

SELECT `app_authordateil`.`phone` FROM `app_authordateil` INNER JOIN `app_author` ON (`app_authordateil`.`id` = `app_author`.`author_dateil_id`) INNER JOIN `app_books_authors` ON (`app_author`.`id` = `app_books_authors`.`author_id`) WHERE `app_books_authors`.`books_id` = 1 LIMIT 21;

5、测试

(1) 控制台输出原生SQL

在项目的配置文件 settings.py 中添加以下代码即可

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level': 'DEBUG',
        },
    }
}

(2) 测试

在应用中的 test.py 中测试

from django.test import TestCase

# Create your tests here.
import os

if __name__ == '__main__':
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_xadmin_ueditor.settings")
    import django
    django.setup()
    # 所有代码都必须等待环境准备完毕之后才能书写
    from app import models
    # ---------- 1. object.create() ----------
    res = models.Books.objects.create(title="Test_001", price=88.88, publish_id=1)
    print(res)
    #...

6、事务

(1) 如何开启事务

from django.db import transaction
try:
    with transaction.atomic():
        # SQL1
        # SQL2
        # ...
        # 在 with 代码块中书写的所有 orm 操作都属于同一个事务
        pass
except Exception as e:
	print(e)
print("执行其他操作")

7、数据库查询优化

orm 语句的特点:

  • 惰性查询:如果你仅仅只是书写了orm 语句 在后面根本没有用到该语句所查询出来的参数 那么 orm 会自动识别 直接不执行

(1) only 与 defer

1-1 objects.only()

# 实现获取到的是一个数据对象,然后点title就能够拿到书名,并且没有其他字段
res = models.Books.objects.only("title")
print(res)
for i in res:
    # 点击 only 括号内存在的字段 不会走数据库
    print(i.title)
    # 点击 only 括号内不存在的字段 会重新走数据库查询 而 all 不需要走
    print(i.price)

执行结果

<QuerySet [<Books: Python爆款>, <Books: Java爆款>, <Books: JavaScript爆款>, <Books: C爆款>, <Books: C++爆款>, <Books: Go爆款>, <Books: Vue爆款>, <Books: Flask爆款>, <Books: Tronado爆款>, <Books: Django爆款>, <Books: Django爆款>, <Books: Django爆款>, <Books: Django爆款>]>
Python爆款
999.80
Java爆款
888.88
JavaScript爆款
1099.88
C爆款
988.88
C++爆款
766.66
Go爆款
655.55
Vue爆款
688.88
Flask爆款
688.88
Tronado爆款
688.88
Django爆款
688.88
Django爆款
688.88
Django爆款
688.88
Django爆款
688.88

原生SQL

SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books`; args=()
SELECT `app_books`.`id`, `app_books`.`price` FROM `app_books` WHERE `app_books`.`id` = 1;
SELECT `app_books`.`id`, `app_books`.`price` FROM `app_books` WHERE `app_books`.`id` = 2;
SELECT `app_books`.`id`, `app_books`.`price` FROM `app_books` WHERE `app_books`.`id` = 3;
SELECT `app_books`.`id`, `app_books`.`price` FROM `app_books` WHERE `app_books`.`id` = 4;
SELECT `app_books`.`id`, `app_books`.`price` FROM `app_books` WHERE `app_books`.`id` = 5;
SELECT `app_books`.`id`, `app_books`.`price` FROM `app_books` WHERE `app_books`.`id` = 6;
SELECT `app_books`.`id`, `app_books`.`price` FROM `app_books` WHERE `app_books`.`id` = 7;
SELECT `app_books`.`id`, `app_books`.`price` FROM `app_books` WHERE `app_books`.`id` = 8;
SELECT `app_books`.`id`, `app_books`.`price` FROM `app_books` WHERE `app_books`.`id` = 9;
SELECT `app_books`.`id`, `app_books`.`price` FROM `app_books` WHERE `app_books`.`id` = 10;
SELECT `app_books`.`id`, `app_books`.`price` FROM `app_books` WHERE `app_books`.`id` = 11;
SELECT `app_books`.`id`, `app_books`.`price` FROM `app_books` WHERE `app_books`.`id` = 12;
SELECT `app_books`.`id`, `app_books`.`price` FROM `app_books` WHERE `app_books`.`id` = 13;

1-2 objects.defer()

defer 与 only 刚好相反

  • defer 括号内放的字段不在查询出来的对象里面 查询该字段需要重新走数据库查询
  • 而如果查询的是非括号内的字段 则不需要走数据库了
# 获取到的对象 除了 title 属性没有之外 其他的属性都有
res = models.Books.objects.defer("title")
print(res)
for i in res:
    # 点击 defer 括号内存在的字段 会重新走数据库查询
    print(i.title)
    # 点击 only 括号内不存在的字段 不会走数据库查询
    print(i.price)

执行结果

<QuerySet [<Books: Python爆款>, <Books: Java爆款>, <Books: JavaScript爆款>, <Books: C爆款>, <Books: C++爆款>, <Books: Go爆款>, <Books: Vue爆款>, <Books: Flask爆款>, <Books: Tronado爆款>, <Books: Django爆款>, <Books: Django爆款>, <Books: Django爆款>, <Books: Django爆款>]>
Python爆款
999.80
Java爆款
888.88
JavaScript爆款
1099.88
C爆款
988.88
C++爆款
766.66
Go爆款
655.55
Vue爆款
688.88
Flask爆款
688.88
Tronado爆款
688.88
Django爆款
688.88
Django爆款
688.88
Django爆款
688.88
Django爆款
688.88

原生SQL

SELECT `app_books`.`id`, `app_books`.`price`, `app_books`.`publish_date`, `app_books`.`inventory`, `app_books`.`sell`, `app_books`.`publish_id` FROM `app_books`;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 1;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 2;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 3;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 4;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 5;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 6;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 7;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 8;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 9;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 10;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 11;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 12;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 13;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 1;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 2;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 3;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 4;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 5;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 6;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 7;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 8;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 9;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 10;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 11;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 12;
SELECT `app_books`.`id`, `app_books`.`title` FROM `app_books` WHERE `app_books`.`id` = 13;

select_related 内部直接先将book与publish连起来 然后一次性将大表里面的所有数据全部封装给查询出来的随想

这个时候对象无论点击 book 表的数据还是 pubilsh 的数据都无需再走数据库查询了

注意:select_related 括号内只能放外键字段 一对多 一对一,不能放多对多字段

# INNER JOIN
res = models.Books.objects.select_related("publish")
print(res)
for i in res:
    print(i.publish.name)

执行结果

<QuerySet [<Books: Python爆款>, <Books: Java爆款>, <Books: Vue爆款>, <Books: Flask爆款>, <Books: Tronado爆款>, <Books: Django爆款>, <Books: Django爆款>, <Books: Django爆款>, <Books: Django爆款>, <Books: JavaScript爆款>, <Books: C爆款>, <Books: C++爆款>, <Books: Go爆款>]>
新华出版社
新华出版社
新华出版社
新华出版社
新华出版社
新华出版社
新华出版社
新华出版社
新华出版社
出版社1
出版社1
出版社2
出版社2

原生SQL

SELECT `app_books`.`id`, `app_books`.`title`, `app_books`.`price`, `app_books`.`publish_date`, `app_books`.`inventory`, `app_books`.`sell`, `app_books`.`publish_id`, `app_publish`.`id`, `app_publish`.`name`, `app_publish`.`addr`, `app_publish`.`email` FROM `app_books` INNER JOIN `app_publish` ON (`app_books`.`publish_id` = `app_publish`.`id`);

prefetch_related 该方法内部其实就是子查询

将子查询查询出来的所有结果也给你封装到对象中

给你的感觉好像也是一次性搞定的

# 子查询
res = models.Books.objects.prefetch_related("publish")
print(res)
for i in res:
    print(i.publish.name)

执行结果

<QuerySet [<Books: Python爆款>, <Books: Java爆款>, <Books: JavaScript爆款>, <Books: C爆款>, <Books: C++爆款>, <Books: Go爆款>, <Books: Vue爆款>, <Books: Flask爆款>, <Books: Tronado爆款>, <Books: Django爆款>, <Books: Django爆款>, <Books: Django爆款>, <Books: Django爆款>]>
新华出版社
新华出版社
出版社1
出版社1
出版社2
出版社2
新华出版社
新华出版社
新华出版社
新华出版社
新华出版社
新华出版社
新华出版社

原生SQL

SELECT `app_books`.`id`, `app_books`.`title`, `app_books`.`price`, `app_books`.`publish_date`, `app_books`.`inventory`, `app_books`.`sell`, `app_books`.`publish_id` FROM `app_books`;
SELECT `app_publish`.`id`, `app_publish`.`name`, `app_publish`.`addr`, `app_publish`.`email` FROM `app_publish` WHERE `app_publish`.`id` IN (1, 2, 3);

Django 视图详解

1、基本使用

(1) url.py

from django.contrib import admin
from django.urls import path
 
from app_view.views import view_function, view_class
 
urlpatterns = [
    path('admin/', admin.site.urls),
    path('app_view/function',view_function),
    path('app_view/view_class', view_class.as_view()),
 
]

(2) views.py

from django.http import HttpResponse
from django.views import View
 
# 基于函数的视图
def view_function(request):
    if request.method == "GET":
        return HttpResponse('我是 view_function 方法的 GET 请求')
    elif request.method == "POST":
        return HttpResponse('我是 view_function 方法的 POST 请求')
 
# 基于类的视图
class view_class(View):
    def dispatch(self, request, *args, **kwargs):
        print('before')
        obj = super(view_class, self).dispatch(request, *args, **kwargs)
        print('after')
        return obj
 
    def get(self, request):
        return HttpResponse('我是 view_class 类的 GET 请求')
 
    def post(self, request):
        return HttpResponse('我是 view_class 类的 POST 请求')

通常情况下我们都使用CBV的方式编写视图,可扩展性强,结构更清晰

2、API

(1) View

所有视图的基类

from django.http import HttpResponse
from django.views import View

class view_class(View):
    # GET 请求一般用于返回数据
    def get(self, request):
        return HttpResponse('我是 view_class 类的 GET 请求')
 
	# POST 请求一般用于添加数据:(登录,注册)post请求较为安全
    def post(self, request):
        return HttpResponse('我是 view_class 类的 POST 请求')
    
    # PUT 请求一般用于修改数据
    def put(self, request):
        return HttpResponse('我是 view_class 类的 PUT 请求')
    
    # PATCH 请求一般用于修改单个字段
    def patch(self, request):
        return HttpResponse('我是 view_class 类的 PATCH 请求')
    
    # DELETE 请求一般用于删除数据
    def delete(self, request):
        return HttpResponse('我是 view_class 类的 DELETE 请求')

(2) TemplateView

from django.http import HttpResponse
from django.views.generic import TemplateView

class view_class(TemplateView):
    # get 请求返回一个页面
    template_name = "index.html"

(3) RedirectView

from django.http import HttpResponse
from django.views.generic import RedirectView

class view_class(RedirectView):
    # 跳转至指定的路由
    url = "/index/"

3、请求

(1) 请求方式

请求方式 说明
get() 一般用于从服务器获取数据
post() 一般用于添加数据,(登录,注册)
put() 一般用于修改数据
patch() 一般用于修改单个字段
delete() 一般用于删除数据
options() 一般用于查询url指定资源支持方法

(2) 获取请求数据

请求 说明
request.COOKIES 接收cookie的方法
request.GET 获取get请求数据的方法
request.POST 获取post请求数据的方法
request.FILES 获取文件上传请求数据的方法
request.method 获取请求的方法
request.path 请求的路径
request.get_raw_uri 请求的url
request.META 请求的详细参数
# postman访问:http://127.0.0.1:8000/app/login/
# 浏览器访问:http://127.0.0.1:8000/app/index/?key1=value1&key2=value2
# 登录成功设置cookie
class LoginView(View):
    def post(self, request, *args, **kwargs):
        # 获取请求参数
        username = request.POST.get("username")
        password = request.POST.get("password")
        # 验证
        user_obj = models.UserModel.objects.filter(username=username, password=password).first()
        response = HttpResponse("登录成功")
        if user_obj:
            response.set_cookie("username", username)
            response.set_cookie("nickname", user_obj.nickname)
        else:
            response = HttpResponse("用户名或密码错误")
        return response

class IndexView(View):
    def get(self, request):
        # 接收cookie的方法
        print("Cookie:", request.COOKIES)
        print("Cookie:", request.COOKIES.get("username"))

        # 获取get请求参数 url上携带的 ?key1=value1&key2=value2
        print("请求参数:", request.GET.get("key1"))
        print("请求参数:", request.GET.get("key2"))

        # 获取用户 请求方式
        print("请求方式:", request.method)

        # 获取用户 请求的路径
        print("请求的路径:", request.path)

        # 获取用户 请求的url
        print("请求的url:", request.get_raw_uri)

        # 获取请求的详细参数
        print("详细参数:", request.META)
        return HttpResponse("我是 IndexView 类的 GET 请求")

结果

Cookie: {'username': 'admin', 'nickname': '小李子'}
Cookie: admin
请求参数: value1
请求参数: value1
请求方式: GET
请求的路径: /app/index/
请求的url: <bound method HttpRequest.get_raw_uri of <WSGIRequest: GET '/app/index/?key1=value1&key2=value1'>>
详细参数:{
    'ALLUSERSPROFILE': 'C:\\ProgramData', 
    'APPDATA': 'C:\\Users\\Administrator\\AppData\\Roaming', 
    'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', 
    'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files', 
    'COMMONPROGRAMW6432': 'C:\\Program Files\\Common Files', 
    'COMPUTERNAME': 'DESKTOP-3HD1UJN', 
    'COMSPEC': 'C:\\Windows\\system32\\cmd.exe', 
    'DJANGO_SETTINGS_MODULE': 'django_xadmin_ueditor.settings', 
    'DRIVERDATA': 'C:\\Windows\\System32\\Drivers\\DriverData', 
    'FPS_BROWSER_APP_PROFILE_STRING': 'Internet Explorer', 
    'FPS_BROWSER_USER_PROFILE_STRING': 'Default', 
    'HOMEDRIVE': 'C:', 
    'HOMEPATH': '\\Users\\Administrator', 
    'IDEA_INITIAL_DIRECTORY': 
    'C:\\Users\\Administrator\\Desktop', 
    'INSTALL_PATH': 'C:\\Users\\Administrator\\AppData\\Roaming\\快压', 
    'INTELLIJ IDEA': 'D:\\Software\\Java_IDEA\\IntelliJ IDEA 2020.2.3\\bin;', 
    'JAVA_HOME': 'D:\\Software\\Java', 
    'LOCALAPPDATA': 'C:\\Users\\Administrator\\AppData\\Local', 
    'LOGONSERVER': '\\\\DESKTOP-3HD1UJN', 
    'NUMBER_OF_PROCESSORS': '6', 
    'ONEDRIVE': 'C:\\Users\\Administrator\\OneDrive', 
    'OS': 'Windows_NT', 
    'PATH': 'F:\\Python_Venv\\django2_demo\\venv\\Scripts;D:\\Software\\Ja.......', 
    'PATHEXT': '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC', 
    'PROCESSOR_ARCHITECTURE': 'AMD64', 
    'PROCESSOR_IDENTIFIER': 'Intel64 Family 6 Model 158 Stepping 13, GenuineIntel', 
    'PROCESSOR_LEVEL': '6', 
    'PROCESSOR_REVISION': '9e0d', 
    'PROG27B48B2C053': '1', 
    'PROG27B48B2C057': '1', 
    'PROGRAMDATA': 'C:\\ProgramData', 
    'PROGRAMFILES': 'C:\\Program Files', 
    'PROGRAMFILES(X86)': 'C:\\Program Files (x86)', 
    'PROGRAMW6432': 'C:\\Program Files', 
    'PROMPT': '(venv) $P$G', 
    'PSMODULEPATH': 'C:\\Program Files\\WindowsPowerShell\\Modules;C:\\Windows\\system32\\Win...', 
    'PUBLIC': 'C:\\Users\\Public', 
    'PYCHARM': 'D:\\Software\\pycharm\\PyCharm 2020.1.1\\bin;', 
    'PYCHARM_DISPLAY_PORT': '63342', 
    'PYCHARM_HOSTED': '1', 
    'PYTHONIOENCODING': 'UTF-8', 
    'PYTHONPATH': 'F:\\Django_Projects\\django_xadmin_ueditor;D:\\Software\\py.....', 
    'PYTHONUNBUFFERED': '1', 
    'SESSIONNAME': 'Console', 
    'SYSTEMDRIVE': 'C:', 
    'SYSTEMROOT': 'C:\\Windows', 
    'TEMP': 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp', 
    'TMP': 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp', 
    'USERDOMAIN': 'DESKTOP-3HD1UJN', 
    'USERDOMAIN_ROAMINGPROFILE': 'DESKTOP-3HD1UJN', 
    'USERNAME': 'Administrator', 
    'USERPROFILE': 'C:\\Users\\Administrator', 
    'VIRTUAL_ENV': 'F:\\Python_Venv\\django2_demo\\venv', 
    'WINDIR': 'C:\\Windows', 
    '_OLD_VIRTUAL_PATH': 'D:\\Software\\Java\\bin;D:\\Softwa......', 
    '_OLD_VIRTUAL_PROMPT': '$P$G', 
    'RUN_MAIN': 'true', 
    'SERVER_NAME': 'activate.navicat.com', 
    'GATEWAY_INTERFACE': 'CGI/1.1', 
    'SERVER_PORT': '8000', 
    'REMOTE_HOST': '', 
    'CONTENT_LENGTH': '', 
    'SCRIPT_NAME': '', 
    'SERVER_PROTOCOL': 'HTTP/1.1', 
    'SERVER_SOFTWARE': 'WSGIServer/0.2', 
    'REQUEST_METHOD': 'GET', 
    'PATH_INFO': '/app/index/', 
    'QUERY_STRING': 'key1=value1&key2=value1', 
    'REMOTE_ADDR': '127.0.0.1', 
    'CONTENT_TYPE': 'text/plain', 
    'HTTP_USER_AGENT': 'PostmanRuntime/7.26.8', 
    'HTTP_ACCEPT': '*/*', 
    'HTTP_POSTMAN_TOKEN': '9e55695e-55f5-4ef6-8526-014690e9fd23', 
    'HTTP_HOST': '127.0.0.1:8000', 
    'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 
    'HTTP_CONNECTION': 'keep-alive', 
    'HTTP_COOKIE': 'username=admin; nickname="å°\x8fæ\x9d\x8eå\xad\x90"', 
    'wsgi.input': <_io.BufferedReader name=768>, 
    'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>, 
    'wsgi.version': (1, 0), 
    'wsgi.run_once': False, 
    'wsgi.url_scheme': 'http', 
    'wsgi.multithread': True, 
    'wsgi.multiprocess': False, 
    'wsgi.file_wrapper': <class 'wsgiref.util.FileWrapper'>
}

4、响应

(1) HttpResponse()

语法:

HttpResponse(content=响应体, content_type=响应体数据类型, status=状态码)

参数:

  • content:可以是一个任意的字符串 或 html 片段
  • content_type:响应类型
    • text/plain:普通文本
    • application/json:json数据
    • text/html:html 片段
  • status:状态码

1-1 返回一个字符串

# 1. 最简单的方式是传递一个字符串作为页面的内容到HttpResponse构造函数,并返回给用户
class HttpResponseView(View):
    def get(self, request, *args, **kwargs):
        return HttpResponse("我是 HttpResponseView 类的 GET 请求")
    	return HttpResponse("我是 HttpResponseView 类的 GET 请求", content_type="text/plain")
		return HttpResponse("我是 HttpResponseView 类的 GET 请求", content_type="text/plain", status=200)
    
class HttpResponseView(View):
    def get(self, request, *args, **kwargs):
        response = HttpResponse()
        response.content = "我是 HttpResponseView 类的 GET 请求"
        response.content_type = "text/plain"
        response.status_code = 200
        response.charset = 'utf-8'
        print(response.charset)
        print(response.content)
        print(response.content_type)
        print(response.status_code)
        return response
    
# 2. 可以将response看做一个类文件对象,使用write()方法不断地往里面添加内容。
class HttpResponseView(View):
    def get(self, request, *args, **kwargs):
        response = HttpResponse()
        response.write("<h3>我是 HttpResponseView 类的 GET 请求</h3>")
        response.write("<h3>我是 HttpResponseView 类的 GET 请求</h3>")
        response.write("<h3>我是 HttpResponseView 类的 GET 请求</h3>")
        response.status_code = 200
        return response

1-2 HttpResponse 属性

属性 示例值 说明
HttpResponse.content 响应内容 响应的内容。bytes类型
HttpResponse.charset utf-8 编码的字符集。 如果没指定,将会从content_type中解析出来。
HttpResponse.status_code 200 响应的状态码,比如200。
HttpResponse.reason_phrase 响应的HTTP原因短语。 使用标准原因短语。除非明确设置,否则reason_phrasestatus_code的值决定。
HttpResponse.streaming False 这个属性的值总是False。由于这个属性的存在,使得中间件能够区别对待流式响应和常
HttpResponse.closed False 如果响应已关闭,那么这个属性的值为True

1-3 HttpResponse 方法

HttpResponse.init()

响应的实例化方法。使用content参数和 content-type 实例化一个HttpResponse对象。

语法:

HttpResponse.init(content='', content_type=None, status=200, reason=None, charset=None)

参数:

参数 说明
content 应该是一个迭代器或者字符串。如果是迭代器,这个迭代期返回的应是一串字符串,并且这些字符串连接起来形成response的内容。 如果不是迭代器或者字符串,那么在其被接收的时候将转换成字符串。
content_type 用于填充HTTP的 Content-Type 头部。如果未指定,默认情况下由DEFAULT_CONTENT_TYPE 和 DEFAULT_CHARSET 设置组成:text/html; charset=utf-8
status 是响应的状态码。
reason HTTP响应短语
charset 编码方式
HttpResponse.has_header()

检查头部中是否有给定的名称(不区分大小写),返回True或 False。

语法:

HttpResponse.has_header(header)

参数:

  • header:
HttpResponse.setdefault()

设置一个头部,除非该头部已经设置过了

语法:

HttpResponse.setdefault(header, value)

参数:

参数 说明
header
value

设置一个Cookie。 参数与Python标准库中的Morsel.Cookie对象相同。

语法:

HttpResponse.set_cookie(key, value='', max_age=None, expires=None, path='/', domain=None, secure=None, httponly=False)

参数:

参数 说明
key 键名
value
max_age 生存周期,以秒为单位
expires 到期时间
domain 用于设置跨域的Cookie。例如 domain=".lawrence.com"
httponly 如果你想阻止客服端的JavaScript访问Cookie,可以设置httponly=True

set_cookie()类似,但是在设置之前将对cookie进行加密签名。通常与HttpRequest.get_signed_cookie()一起使用。

语法:

HttpResponse.set_signed_cookie(key, value, salt='', max_age=None, expires=None, path='/', domain=None, secure=None, httponly=True)

参数:

参数 说明
key 键名
value
salt 加盐,加密,提高复杂度
max_age 生存周期,以秒为单位
expires 到期时间
domain 用于设置跨域的Cookie。例如 domain=".lawrence.com"
secure
httponly 如果你想阻止客服端的JavaScript访问Cookie,可以设置httponly=True

删除Cookie中指定的key。由于Cookie的工作方式,path和domain应该与 set_cookie() 中使用的值相同,否则Cookie不会删掉。

语法:

HttpResponse.delete_cookie(key, path='/', domain=None)

参数:

参数 说明
key 键名
path 路径
domain 指定域名
HttpResponse.write()

将HttpResponse实例看作类似文件的对象,往里面添加内容。

语法:

HttpResponse.write(content)

参数:

参数 说明
content 文本内容,或HTML片段
HttpResponse.flush()

清空HttpResponse实例的内容。

HttpResponse.tell()

将HttpResponse实例看作类似文件的对象,移动位置指针

HttpResponse.getvalue()

返回HttpResponse.content的值。 此方法将HttpResponse实例看作是一个类似流的对象

HttpResponse.writelines()

将一个包含行的列表写入响应对象中。 不添加分行符。

语法:

HttpResponse.writelines(lines)

参数:

参数 说明
lines

1-4 HttpResponse 子类

Django 包含了一系列的HttpResponse衍生类(子类),用来处理不同类型的HTTP响应。与HttpResponse相同, 这些衍生类存在于django.http之中

子类 说明
HttpResponseRedirect 重定向,返回302状态码。已经被 redirect() 替代
HttpResponsePermanentRedirect 永久重定向,返回301状态码
HttpResponseNotModified 未修改页面,返回304状态码
HttpResponseBadRequest 错误的请求,返回400状态码
HttpResponseNotFound 页面不存在,返回404状态码
HttpResponseForbidden 禁止访问,返回403状态码
HttpResponseNotAllowed 禁止访问,返回405状态码
HttpResponseGone 过期,返回405状态码
HttpResponseServerError 服务器错误,返回500状态码
JsonResponse 用于返回json数据

(2) JsonResponse()

class JsonResponse(data,encoder = DjangoJSONEncoder,safe = True,json_dumps_params = None ,** kwargs)[source]

JsonResponse是HttpResponse的一个子类,是Django提供的用于创建JSON编码类型响应的快捷类。

它从父类继承大部分行为,并具有以下不同点:

  • 它的默认Content-Type头部设置为application/json。

  • 它的第一个参数data,通常应该为一个字典数据类型。 如果safe参数设置为False,则可以是任何可JSON 序列化的对象。

  • encoder默认为django.core.serializers.json.DjangoJSONEncoder,用于序列化数据。

  • 布尔类型参数safe默认为True。 如果设置为False,可以传递任何对象进行序列化(否则,只允许dict 实例)。

2-1 基本用法

from django.views import View
from django.http import JsonResponse

# 方式一:字典类型
class JsonResponseView(View):
    def get(self, request, *args, **kwargs):
        response_data = {
            "code": 200,
            "message": "测试json数据",
            "data": {
                "name": "JsonResponseView",
                "age": 18,
                "gender": "F",
                "tag": ["Python", "Django", "Flase"]
            }
        }
        return JsonResponse(data=response_data)
    
# 方式二:
class JsonResponseView(View):
    def get(self, request, *args, **kwargs):
        response_data = {
            "code": 200,
            "message": "测试json数据",
            "data": {
                "name": "JsonResponseView",
                "age": 18,
                "gender": "F",
                "tag": ["Python", "Django", "Flase"]
            }
        }
        response = JsonResponse(data=response_data)
        print(response.content)
        return response

# 方式三:列表类型
class JsonResponseView(View):
    def get(self, request, *args, **kwargs):
        data_list = ["Python", "Django", "Flase"]
        return JsonResponse(data=data_list, safe=False) 

若要序列化非dict对象,必须设置safe参数为False:

如果不传递safe=False,将抛出一个TypeError。

TypeError: In order to allow non-dict objects to be serialized set the safe parameter to False.

2-2 指定 JSON 编码器类

如果你需要使用不同的JSON 编码器类,可以传递encoder参数给构造函数:

import json
from django.views import View
from django.http import JsonResponse

class MyJSONEncoder(json.JSONEncoder):
    def default(self, o):
        pass

class JsonResponseView(View):
    def get(self, request, *args, **kwargs):
        response_data = {
            "code": 200,
            "message": "测试json数据",
            "data": {
                "name": "JsonResponseView",
                "age": 18,
                "gender": "F",
                "tag": ["Python", "Django", "Flase"]
            }
        }
        response = JsonResponse(data=response_data, encoder=MyJSONEncoder)
        print(response.content)
        return response

(3) StreamingHttpResponse()

StreamingHttpResponse类被用来从Django响应一个流式对象到浏览器。如果生成的响应太长或者是占用的内存较大,这么做可能更有效率。 例如,它对于生成大型的CSV文件非常有用。

StreamingHttpResponse不是HttpResponse的衍生类(子类),因为它实现了完全不同的应用程序接口。但是,除了几个明显不同的地方,两者几乎完全相同。

import os
from django.http import Http404, StreamingHttpResponse

class StreamingHttpResponseView(View):
    def get(self, request, *args, **kwargs):
        file_path = "static/demo.jpg"
        try:
            response = StreamingHttpResponse(open(file_path, 'rb'))
            response['content_type'] = "application/octet-stream"
            response['Content-Disposition'] = 'attachment; filename=' + os.path.basename(file_path)
            return response
        except Exception:
            raise Http404

(4) FileResponse()

文件类型响应。通常用于给浏览器返回一个文件附件。

FileResponse是StreamingHttpResponse的衍生类,为二进制文件专门做了优化。

FileResponse需要通过二进制模式打开文件,如下:

import os
from django.http import Http404, FileResponse
class FileResponseView(View):
    def get(self, request, *args, **kwargs):
        file_path = "static/demo.jpg"
        try:
            response = FileResponse(open(file_path, 'rb'))
            response['content_type'] = "application/octet-stream"
            response['Content-Disposition'] = 'attachment; filename=' + os.path.basename(file_path)
            return response
        except Exception:
            raise Http404

(5) rander()

此方法的作用---结合一个给定的模板和一个给定的上下文字典,并返回一个渲染后的 HttpResponse 对象。

通俗的讲就是把context的内容, 加载进templates中定义的文件, 并通过浏览器渲染呈现.

5-1 语法:

render(request, template_name, context=None, content_type=None, status=None, using=None)

5-2 参数:

参数 说明
request 是一个固定参数
template_name templates中定义的文件,注意路径名。比如:"templates/polls/index.html", 则参数这样写:"polls/index.html"
context 要传入文件中用于渲染呈现的数据, 默认是字典格式
content_type 生成的文档要使用的MIME 类型。默认为DEFAULT_CONTENT_TYPE 设置的值。
status http的响应代码,默认是200
using 用于加载模板使用的模板引擎的名称

5-3 基本使用

from django.shortcuts import render
from django.views import View

class RenderView(View):
    def get(self, request):
        context = {}
        context['hello'] = 'Hello World!'
        return render(request, 'index.html', context=context)
        # return render(request, 'hello.html', {'hello': 'Hello World!'})

(6) redirect()

页面的跳转

redirect 类似HttpResponseRedirect的用法,也可以使用 字符串的url格式。

urls中需要有所跳转的名字。

默认返回一个临时的重定向, 传递permanent=True可以返回一个永久的重定向.

将调用具体ORM对象的get_absolute_url()方法来获取重定向的URL.

6-1 语法

redirect(to, *args, permanent=False, **kwargs)

6-2 参数

参数可以是:

  • 一个模型: 将调用模型的get_absolute_url()函数
  • 一个视图, 可以带有函数: 可以使用urlresolvers.reverse来反向解析名称
  • 一个绝对的或相对的URL, 将原封不动的作为重定向的位置.

6-3 基本使用

from django.shortcuts import redirect

# 方式一: 传递一个具体的ORM对象
def my_view(request):
    ...
    object = UserModel.objects.get(...)
    return redirect(object)

# 方式二: 传递一个视图的名称
def my_view(request):
    ...
    return redirect("some-view-name", foo="bar")

# 方式三: 传递要重定向到的一个具体的网址
def my_view(request):
    ...
    return redirect("/some/url/")

# 方式四: 当然也可以是一个完整的网址
def my_view(request):
    ...
    return redirect("http://example.com")

# 默认情况下, redirect()返回一个临时重定向. 以上所有的形式都接收一个permanent参数; 如果设置为True, 将返回一个永久的重定向:
def my_view(request):
    ...
    object = UserModel.objects.get(...)
    return redirect(object, permanent=True)

6-4 扩展

临时重定向(响应状态码: 302)和永久重定向(响应状态码: 301)对普通用户来说是没什么区别的, 它主要面向的是搜索引擎的机器人。

A页面临时重定向B页面, 那搜索引擎收录的就是B页面。

Django 路由详解

https://www.cnblogs.com/Dominic-Ji/articles/10982293.html

路由即请求地址与视图函数的映射关系,如果把网站比喻为一本书,那路由就好比是这本书的目录,在Django中路由默认配置在urls.py中

1、基本使用

from django.conf.urls import url
from . import views

urlpatterns = [
    # 登录路由
    url('login/', views.LoginView.as_view()),
	# 首页路由
    url('index/', views.IndexView.as_view()),
	... 
]

2、单一路由对应

from django.conf.urls import url
from . import views

urlpatterns = [
    url('^index/', views.IndexView.as_view()),
]

3、基于正则的路由

from django.conf.urls import url
from . import views

urlpatterns = [
    url('^index/(\d*)', views.IndexView.as_view()),
    url('^manage/(?P<name>\w*)/(?P<id>\d*)', views.ManageView.as_view()),
]

4、添加额外的参数

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^manage/(?P<name>\w*)', views.ManageView.as_view(), {'id':333}),
]

5、为路由映射设置名称

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^home', views.HomeView.as_view(), name='h1'),
	url(r'^index/(\d*)', views.IndexView.as_view(), name='h2'),
]

6、根据app对路由规则进行分类

from django.conf.urls import include, url
from . import views

urlpatterns = [
    url(r'^web/',include('web.urls')),
    url(r'^api/',include('api.urls')),
]

7、命名空间

7-1 project.urls.py

from django.conf.urls import url,include
 
urlpatterns = [
    url(r'^a/', include('app01.urls', namespace='author-polls')),
    url(r'^b/', include('app01.urls', namespace='publisher-polls')),
]

7-2 app01.urls.py

from django.conf.urls import url
from app01 import views
 
app_name = 'app01'
urlpatterns = [
    url(r'^(?P<pk>\d+)/$', views.detail, name='detail')
]

7-3 app01.views.py

def detail(request, pk):
    print(request.resolver_match)
    return HttpResponse(pk)

以上定义带命名空间的url之后,使用name生成URL时候,应该如下:

  • v = reverse('app01:detail', kwargs={'pk':11})

django中的路由系统和其他语言的框架有所不同,在django中每一个请求的url都要有一条路由映射,这样才能将请求交给对一个的view中的函数去处理。其他大部分的Web框架则是对一类的url请求做一条路由映射,从而是路由系统变得简洁。

Django 中间件

https://www.cnblogs.com/Dominic-Ji/p/9229509.html

1、基本概念

中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出。因为改变的是全局,所以需要谨慎实用,用不好会影响到性能

django中间件是django的门户

  1. 请求来的时候需要先经过中间件才能到达真正的django后端
  2. 响应走的时候最后也需要经过中间件才能发送出去

2、Django生命周期

3、中间件方法

(1) process_request()

1-1 语法

def process_request(self, request):
    pass

process_request 方法有一个参数 request,这个 request 和视图函数中的 request 是一样的。

process_request 方法的返回值可以是 None 也可以是 HttpResponse 对象。

  • 返回值是 None 的话,按正常流程继续走,交给下一个中间件处理。
  • 返回值是 HttpResponse 对象,Django 将不执行后续视图函数之前执行的方法以及视图函数,直接以该中间件为起点,倒序执行中间件,且执行的是视图函数之后执行的方法。

process_request 方法是在视图函数之前执行的。

当配置多个中间件时,会按照 MIDDLEWARE中 的注册顺序,也就是列表的索引值,顺序执行。

不同中间件之间传递的 request 参数都是同一个请求对象。

1-2 基本使用

MiddleWare.py

# 自定义中间件
from django.utils.deprecation import MiddlewareMixin

class MD1(MiddlewareMixin):
    def process_request(self,request):
        print("Md1请求")

class MD2(MiddlewareMixin):
    def process_request(self,request):
        print("Md2请求")

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',
    # 自定义中间件
    'utils.MiddleWare.MD1',
    'utils.MiddleWare.MD2',
]

(2) process_view()

2-1 语法

def process_view(self, request, callback, callback_args, callback_kwargs):
    pass

process_view 方法有四个参数:

参数 说明
request 是 HttpRequest 对象
view_func 是 Django 即将使用的视图函数
view_args 是将传递给视图的位置参数的列表
view_kwargs 是将传递给视图的关键字参数的字典

view_args 和 view_kwargs 都不包含第一个视图参数(request)。

process_view 方法是在视图函数之前,process_request 方法之后执行的。

返回值可以是 None、view_func(request) 或 HttpResponse 对象。

返回值 说明
None 按正常流程继续走,交给下一个中间件处理。
HttpResponse Django 将不执行后续视图函数之前执行的方法以及视图函数,直接以该中间件为起点,倒序执行中间件,且执行的是视图函数之后执行的方法
view_func(request) Django 将不执行后续视图函数之前执行的方法,提前执行视图函数,然后再倒序执行视图函数之后执行的方法

当最后一个中间件的 process_request 到达路由关系映射之后,返回到第一个中间件 process_view,然后依次往下,到达视图函数。

2-2 基本使用

class MD1(MiddlewareMixin):
  	# 在视图之前执行 顺序执行
    def process_view(self,request, view_func, view_args, view_kwargs):
        print("md1  process_view 方法!") 
        # return view_func(request)

(3) process_template_response()

3-1 语法

def process_template_response(self, request, response):
    pass

它的参数,一个HttpRequest对象,response是TemplateResponse对象(由视图函数或者中间件产生)。

process_template_response是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象或等价方法)

3-2 基本使用

class MD1(MiddlewareMixin):
    def process_template_response(self, request, response):
        print("MD1 中的process_template_response")
        return response

(4) process_exception()

4-1 语法

def process_exception(self, request, exception):
    pass

参数说明:

参数 说明
request 是 HttpRequest 对象。
exception 是视图函数异常产生的 Exception 对象

process_exception 方法只有在视图函数中出现异常了才执行,按照 settings 的注册倒序执行。

在视图函数之后,在 process_response 方法之前执行。

process_exception 方法的返回值可以是一个 None 也可以是一个 HttpResponse 对象。

返回值是 None,页面会报 500 状态码错误,视图函数不会执行。

process_exception 方法倒序执行,然后再倒序执行 process_response 方法。

返回值是 HttpResponse 对象,页面不会报错,返回状态码为 200。

视图函数不执行,该中间件后续的 process_exception 方法也不执行,直接从最后一个中间件的 process_response 方法倒序开始执行。

若是 process_view 方法返回视图函数,提前执行了视图函数,且视图函数报错,则无论 process_exception 方法的返回值是什么,页面都会报错, 且视图函数和 process_exception 方法都不执行。

直接从最后一个中间件的 process_response 方法开始倒序执行:

4-2 基本使用

from django.utils.deprecation import MiddlewareMixin

class MD1(MiddlewareMixin):
    # 引发错误 才会触发这个方法
    def process_exception(self, request, exception):
        print("md1  process_exception 方法!")
        # return HttpResponse(exception) #返回错误信息

(5) process_response()

5-1 语法

def process_response(self, request, response):
    pass
	return response

process_response 方法有两个参数,一个是 request,一个是 response,request 是请求对象,response 是视图函数返回的 HttpResponse 对象,该方法必须要有返回值,且必须是response。

process_response 方法是在视图函数之后执行的。

当配置多个中间件时,会按照 MIDDLEWARE 中的注册顺序,也就是列表的索引值,倒序执行。

从下图看,正常的情况下按照绿色的路线进行执行,假设 【中间件1】 有返回值,则按照红色的路线走,直接执行该类下的 process_response 方法返回,后面的其他中间件就不会执行。

5-2 基本使用

from django.utils.deprecation import MiddlewareMixin

class MD1(MiddlewareMixin):
	# 基于请求响应
    def process_response(self,request, response):
        # 在视图之后
        print("md1  process_response 方法!", id(request))
        return response

4、案例

class MD1(MiddlewareMixin):
    # 在视图之前执行
    def process_request(self, request):
        print("md1  process_request 方法。", id(request))
	
    #基于请求响应
    def process_response(self,request, response):
        # 在视图之后
        print("md1  process_response 方法!", id(request))
        return response
    
	#在视图之前执行 顺序执行
    def process_view(self,request, view_func, view_args, view_kwargs):
        print("md1  process_view 方法!")
        #return view_func(request)

	# 引发错误 才会触发这个方法
    def process_exception(self, request, exception):
        print("md1  process_exception 方法!")
        # 返回错误信息
        # return HttpResponse(exception)
        
    def process_template_response(self, request, response):
        print("MD1 中的process_template_response")
        return response

Django 缓存

https://www.cnblogs.com/Dominic-Ji/p/9279742.html

由于Django是动态网站,所有每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显,最简单解决方式是使用:缓存,缓存将一个某个views的返回值保存至内存或者memcache中,5分钟内再有人来访问时,则不再去执行view中的操作,而是直接从内存或者Redis中之前缓存的内容拿到,并返回。

Django中提供了6种缓存方式:

  • 开发调试
  • 内存
  • 文件
  • 数据库
  • Memcache缓存(python-memcached模块)
  • Memcache缓存(pylibmc模块)

1、配置

(1) 开发调试

settings.py

# 此为开始调试用,实际内部不做任何操作
# 配置
CACHES = {
    'default': {
        # 引擎
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
        # 缓存超时时间(默认300,None表示永不过期,0表示立即过期)
        'TIMEOUT': 300,
        'OPTIONS':{
            # 最大缓存个数(默认300)
            'MAX_ENTRIES': 300,
            # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
            'CULL_FREQUENCY': 3,
        },
        # 缓存key的前缀(默认空)
        'KEY_PREFIX': '',
        # 缓存key的版本(默认1)
        'VERSION': 1,
        # 生成key的函数(默认函数会生成为:【前缀:版本:key】)
        'KEY_FUNCTION' 函数名
    }
}

cache.py

# 自定义key
def default_key_func(key, key_prefix, version):
    """
	Default function to generate keys.
	Constructs the key used by all other methods. By default it prepends
	the `key_prefix'. KEY_FUNCTION can be used to specify an alternate
	function with custom key making behavior.
	"""
    return '%s:%s:%s' % (key_prefix, version, key)

def get_key_func(key_func):
    """
	Function to decide which key function to use.
	Defaults to ``default_key_func``.
	"""
    if key_func is not None:
        if callable(key_func):
            return key_func
        else:
            return import_string(key_func)
        return default_key_func

(2) 内存

# 此缓存将内容保存至内存的变量中
# 配置:
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake',
    }
}
# 注:其他配置同开发调试版本

(3) 文件

# 此缓存将内容保存至文件
# 配置:
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': '/var/tmp/django_cache',
    }
}
# 注:其他配置同开发调试版本

(4) 数据库

# 此缓存将内容保存至数据库
# 配置:
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'my_cache_table', # 数据库表
    }
}
# 注:执行创建表命令 python manage.py createcachetable

(5) Memcache缓存

pip install python-memcached
# 此缓存使用python-memcached模块连接memcache

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': 'unix:/tmp/memcached.sock',
    }
}   

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': [
            '172.19.26.240:11211',
            '172.19.26.242:11211',
        ]
    }
}

(6) Memcache缓存

pip install pylibmc
# 此缓存使用pylibmc模块连接memcache
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': '/tmp/memcached.sock',
    }
}   

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': [
            '172.19.26.240:11211',
            '172.19.26.242:11211',
        ]
    }
}

(7) 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": "密码",
        }
    }
}

views.py

from django_redis import get_redis_connection
conn = get_redis_connection("default")

2、应用

(1) 全站使用

使用中间件,经过一系列的认证等操作,如果内容在缓存中存在,则使用FetchFromCacheMiddleware获取内容并返回给用户,当返回给用户之前,判断缓存中是否已经存在,如果不存在则UpdateCacheMiddleware会将缓存保存至缓存,从而实现全站缓存

MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',
    # 其他中间件...
    'django.middleware.cache.FetchFromCacheMiddleware',
]

CACHE_MIDDLEWARE_ALIAS = ""
CACHE_MIDDLEWARE_SECONDS = ""
CACHE_MIDDLEWARE_KEY_PREFIX = ""

(2) 单独视图缓存

# 方式一:
from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def my_view(request):
	...

# 方式二:
from django.views.decorators.cache import cache_page
urlpatterns = [
    url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)),
]

(3) 局部视图使用

# 引入TemplateTag
{% load cache %}

# 使用缓存

{% cache 5000 缓存key %}
缓存内容
{% endcache %}

Django cookie & session

1、cookie

1-1 Cookie的由来

大家都知道HTTP协议是无状态的。

无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不会直接影响后面的请求响应情况。

一句有意思的话来描述就是人生只如初见,对服务器来说,每次的请求都是全新的。

状态可以理解为客户端和服务器在某次会话中产生的数据,那无状态的就以为这些数据不会被保留。会话中产生的数据又是我们需要保存的,也就是说要“保持状态”。因此Cookie就是在这样一个场景下诞生。

1-2 什么是Cookie

Cookie具体指的是一段小信息,它是服务器发送出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务器提取有用信息。

1-3 Cookie的原理

cookie的工作原理是:由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断这个是“谁”了。

(2) Django中操作Cookie

2-1 获取Cookie

request.COOKIES['key']
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)

参数:

  • default: 默认值
  • salt: 加密盐
  • max_age: 后台控制过期时间

2-2 设置Cookie

rep = HttpResponse(...)
rep = render(request, ...)

rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,salt='加密盐', max_age=None, ...)

参数:

  • key, 键
  • value='', 值
  • max_age=None, 超时时间
  • expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)
  • path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
  • domain=None, Cookie生效的域名
  • secure=False, https传输
  • httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

2-3 删除Cookie

def logout(request):
    rep = redirect("/login/")
    rep.delete_cookie("user")  # 删除用户浏览器上之前设置的usercookie值
    return rep

4-4 Cookie版登陆校验

def check_login(func):
    @wraps(func)
    def inner(request, *args, **kwargs):
        next_url = request.get_full_path()
        if request.get_signed_cookie("login", salt="SSS", default=None) == "yes":
            # 已经登录的用户...
            return func(request, *args, **kwargs)
        else:
            # 没有登录的用户,跳转刚到登录页面
            return redirect("/login/?next={}".format(next_url))
    return inner


def login(request):
    if request.method == "POST":
        username = request.POST.get("username")
        passwd = request.POST.get("password")
        if username == "xxx" and passwd == "dashabi":
            next_url = request.GET.get("next")
            if next_url and next_url != "/logout/":
                response = redirect(next_url)
            else:
                response = redirect("/class_list/")
            response.set_signed_cookie("login", "yes", salt="SSS")
            return response
    return render(request, "login.html")

2、session

(1) Session的由来

Cookie虽然在一定程度上解决了“保持状态”的需求,但是由于Cookie本身最大支持4096字节,以及Cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是Session。

问题来了,基于HTTP协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的Cookie就起到桥接的作用。

我们可以给每个客户端的Cookie分配一个唯一的id,这样用户在访问时,通过Cookie,服务器就知道来的人是“谁”。然后我们再根据不同的Cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。

总结而言:Cookie弥补了HTTP无状态的不足,让服务器知道来的人是“谁”;但是Cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过Cookie识别不同的用户,对应的在Session里保存私密的信息以及超过4096字节的文本。

另外,上述所说的Cookie和Session其实是共通性的东西,不限于语言和框架。

(2) Django中Session相关方法

# 获取、设置、删除Session中数据
request.session['k1']
request.session.get('k1',None)
request.session['k1'] = 123
request.session.setdefault('k1',123) # 存在则不设置
del request.session['k1']

# 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
request.session.iterkeys()
request.session.itervalues()
request.session.iteritems()

# 会话session的key
request.session.session_key

# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()

# 检查会话session的key在数据库中是否存在
request.session.exists("session_key")

# 删除当前会话的所有Session数据
request.session.delete()
  
# 删除当前的会话数据并删除会话的Cookie。
request.session.flush() 
    这用于确保前面的会话数据不可以再次被用户的浏览器访问
    例如,django.contrib.auth.logout() 函数中就会调用它。

# 设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
    * 如果value是个整数,session会在些秒数后失效。
    * 如果value是个datatime或timedelta,session就会在这个时间后失效。
    * 如果value是0,用户关闭浏览器session就会失效。
    * 如果value是None,session会依赖全局session失效策略。

(3) Session流程解析

(4) Session版登陆验证

from functools import wraps


def check_login(func):
    @wraps(func)
    def inner(request, *args, **kwargs):
        next_url = request.get_full_path()
        if request.session.get("user"):
            return func(request, *args, **kwargs)
        else:
            return redirect("/login/?next={}".format(next_url))
    return inner


def login(request):
    if request.method == "POST":
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")

        if user == "alex" and pwd == "alex1234":
            # 设置session
            request.session["user"] = user
            # 获取跳到登陆页面之前的URL
            next_url = request.GET.get("next")
            # 如果有,就跳转回登陆之前的URL
            if next_url:
                return redirect(next_url)
            # 否则默认跳转到index页面
            else:
                return redirect("/index/")
    return render(request, "login.html")


@check_login
def logout(request):
    # 删除所有当前请求相关的session
    request.session.delete()
    return redirect("/login/")


@check_login
def index(request):
    current_user = request.session.get("user", None)
    return render(request, "index.html", {"user": current_user})

(5) Django中的Session配置

Django中默认支持Session,其内部提供了5种类型的Session供开发者使用。

1. 数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)

2. 缓存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
SESSION_CACHE_ALIAS = 'default'                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置

3. 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
SESSION_FILE_PATH = None                                    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() 

4. 缓存+数据库
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎

5. 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎

其他公用设置项:
SESSION_COOKIE_NAME = "sessionid"                       # 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 = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认)

(6) CBV中加装饰器相关

6-1 CBV实现的登录视图

class LoginView(View):

    def get(self, request):
        """
        处理GET请求
        """
        return render(request, 'login.html')

    def post(self, request):
        """
        处理POST请求 
        """
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        if user == 'alex' and pwd == "alex1234":
            next_url = request.GET.get("next")
            # 生成随机字符串
            # 写浏览器cookie -> session_id: 随机字符串
            # 写到服务端session:
            # {
            #     "随机字符串": {'user':'alex'}
            # }
            request.session['user'] = user
            if next_url:
                return redirect(next_url)
            else:
                return redirect('/index/')
        return render(request, 'login.html')

要在CBV视图中使用我们上面的check_login装饰器,有以下三种方式:

from django.utils.decorators import method_decorator

6-2 加在CBV视图的get或post方法上

from django.utils.decorators import method_decorator

class HomeView(View):
    def dispatch(self, request, *args, **kwargs):
        return super(HomeView, self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return render(request, "home.html")
    
    @method_decorator(check_login)
    def post(self, request):
        print("Home View POST method...")
        return redirect("/index/")

6-3 加在dispatch方法上

from django.utils.decorators import method_decorator

class HomeView(View):
    @method_decorator(check_login)
    def dispatch(self, request, *args, **kwargs):
        return super(HomeView, self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return render(request, "home.html")

    def post(self, request):
        print("Home View POST method...")
        return redirect("/index/")

因为CBV中首先执行的就是dispatch方法,所以这么写相当于给get和post方法都加上了登录校验。

6-4 直接加在视图类上

直接加在视图类上,但method_decorator必须传 name 关键字参数

如果get方法和post方法都需要登录校验的话就写两个装饰器。

from django.utils.decorators import method_decorator

@method_decorator(check_login, name="get")
@method_decorator(check_login, name="post")
class HomeView(View):

    def dispatch(self, request, *args, **kwargs):
        return super(HomeView, self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return render(request, "home.html")

    def post(self, request):
        print("Home View POST method...")
        return redirect("/index/")

(7) 补充

CSRF Token相关装饰器在CBV只能加到dispatch方法上,或者加在视图类上然后name参数指定为dispatch方法。

备注:

  • csrf_protect,为当前函数强制设置防跨站请求伪造功能,即便settings中没有设置全局中间件。
  • csrf_exempt,取消当前函数防跨站请求伪造功能,即便settings中设置了全局中间件。

7-1 方式一

from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator

class HomeView(View):
    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super(HomeView, self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return render(request, "home.html")

    def post(self, request):
        print("Home View POST method...")
        return redirect("/index/")

7-2 方式二

from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator

@method_decorator(csrf_exempt, name='dispatch')
class HomeView(View):
   
    def dispatch(self, request, *args, **kwargs):
        return super(HomeView, self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return render(request, "home.html")

    def post(self, request):
        print("Home View POST method...")
        return redirect("/index/")

Django 分页

1、封装

class Pagination(object):
    def __init__(self, current_page, all_count, per_page_num=2, pager_count=11):
        """
        封装分页相关数据
        :param current_page: 当前页
        :param all_count:    数据库中的数据总条数
        :param per_page_num: 每页显示的数据条数
        :param pager_count:  最多显示的页码个数
        """
        try:
            current_page = int(current_page)
        except Exception as e:
            current_page = 1

        if current_page < 1:
            current_page = 1

        self.current_page = current_page

        self.all_count = all_count
        self.per_page_num = per_page_num

        # 总页码
        all_pager, tmp = divmod(all_count, per_page_num)
        if tmp:
            all_pager += 1
        self.all_pager = all_pager

        self.pager_count = pager_count
        self.pager_count_half = int((pager_count - 1) / 2)

    @property
    def start(self):
        return (self.current_page - 1) * self.per_page_num

    @property
    def end(self):
        return self.current_page * self.per_page_num

    def page_html(self):
        # 如果总页码 < 11个:
        if self.all_pager <= self.pager_count:
            pager_start = 1
            pager_end = self.all_pager + 1
        # 总页码  > 11
        else:
            # 当前页如果<=页面上最多显示11/2个页码
            if self.current_page <= self.pager_count_half:
                pager_start = 1
                pager_end = self.pager_count + 1

            # 当前页大于5
            else:
                # 页码翻到最后
                if (self.current_page + self.pager_count_half) > self.all_pager:
                    pager_end = self.all_pager + 1
                    pager_start = self.all_pager - self.pager_count + 1
                else:
                    pager_start = self.current_page - self.pager_count_half
                    pager_end = self.current_page + self.pager_count_half + 1

        page_html_list = []
        # 添加前面的nav和ul标签
        page_html_list.append('''
                    <nav aria-label='Page navigation>'
                    <ul class='pagination'>
                ''')
        first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
        page_html_list.append(first_page)

        if self.current_page <= 1:
            prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
        else:
            prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)

        page_html_list.append(prev_page)

        for i in range(pager_start, pager_end):
            if i == self.current_page:
                temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
            else:
                temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
            page_html_list.append(temp)

        if self.current_page >= self.all_pager:
            next_page = '<li class="disabled"><a href="#">下一页</a></li>'
        else:
            next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
        page_html_list.append(next_page)

        last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
        page_html_list.append(last_page)
        # 尾部添加标签
        page_html_list.append('''
                                           </nav>
                                           </ul>
                                       ''')
        return ''.join(page_html_list)

2、自定义分页器使用

(1) 后端

 def get_book(request):
   book_list = models.Book.objects.all()
   current_page = request.GET.get("page",1)
   all_count = book_list.count()
   page_obj = Pagination(current_page=current_page,all_count=all_count,per_page_num=10)
   page_queryset = book_list[page_obj.start:page_obj.end]
   return render(request,'booklist.html',locals())

(2) 前端

<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            {% for book in page_queryset %}
            <p>{{ book.title }}</p>
            {% endfor %}
            {{ page_obj.page_html|safe }}
        </div>
    </div>
</div>

Django 信号

Django中提供了“信号调度”,用于在框架执行操作时解耦。通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者。

1、Django 内置信号

(1) 前戏

1-1 Model signals

信号 说明
pre_init django的modal执行其构造方法前,自动触发
post_init django的modal执行其构造方法后,自动触发
pre_save django的modal对象保存前,自动触发
post_save django的modal对象保存后,自动触发
pre_delete django的modal对象删除前,自动触发
post_delete django的modal对象删除后,自动触发
m2m_changed django的modal中使用m2m字段操作第三张表(add,remove,clear)前后,自动触发
class_prepared 程序启动时,检测已注册的app中modal类,对于每一个类,自动触发

1-2 Management signals

信号 说明
pre_migrate 执行migrate命令前,自动触发
post_migrate 执行migrate命令后,自动触发

1-3 Request/response signals

信号 说明
request_started 请求到来前,自动触发
request_finished 请求结束后,自动触发
got_request_exception 请求异常后,自动触发

1-4 Test signals

信号 说明
setting_changed 使用test测试修改配置文件时,自动触发
template_rendered 使用test测试渲染模板时,自动触发

1-5 Database Wrappers

信号 说明
connection_created 创建数据库连接时,自动触发

(2) 基本使用

对于Django内置的信号,仅需注册指定信号,当程序执行相应操作时,自动触发注册函数:

from django.core.signals import request_finished
from django.core.signals import request_started
from django.core.signals import got_request_exception

from django.db.models.signals import class_prepared
from django.db.models.signals import pre_init, post_init
from django.db.models.signals import pre_save, post_save
from django.db.models.signals import pre_delete, post_delete
from django.db.models.signals import m2m_changed
from django.db.models.signals import pre_migrate, post_migrate

from django.test.signals import setting_changed
from django.test.signals import template_rendered

from django.db.backends.signals import connection_created


def callback(sender, **kwargs):
    print("xxoo_callback")
    print(sender,kwargs)

xxoo.connect(callback)
# xxoo指上述导入的内容

带装饰器

from django.core.signals import request_finished
from django.dispatch import receiver

@receiver(request_finished)
def my_callback(sender, **kwargs):
    print("Request finished!")

2、自定义信号

1-1 定义信号

import django.dispatch
pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"]

1-2 注册信号

def callback(sender, **kwargs):
    print("callback")
    print(sender,kwargs)
 
pizza_done.connect(callback)

1-3 触发信号

from 路径 import pizza_done
 
pizza_done.send(sender='seven',toppings=123, size=456)

由于内置信号的触发者已经集成到Django中,所以其会自动调用,而对于自定义信号则需要开发者在任意位置触发。

官方文档

Django channels

1、前戏

django channels 是django支持websocket的一个模块。

(1) 参考

(2) HTTP 协议的特点

无状态&短连接

  • 客户端主动连接服务端
  • 客户端向服务器发送请求,服务端接收请求并返回数据
  • 客户端接收数据
  • 断开连接

(3) tcp/ip协议

  • 三次握手

    • 客户端知道我能连上服务端,服务端也能连上我;

    • 服务端知道我能连上客户端,客户端也能连上我。

    • 服务端和客户端确认链接

  • 四次挥手

    • 第一次挥手:服务器知道了客户端要和我断开连接,但此时服务端不一定最好准备,以为此时服务端可能还有未发送完的消息,还要继续发送。

    • 第二次挥手:此时对服务端而言,只能对消息进行一个确认,告诉客户端我知道你要和我断开连接了,但我这边可能还没做好准备,你得等我。

    • 第三次挥手:服务端发给客户端:我准备好和你断开连接了

    • 第四次挥手:客户端发给服务端:确认收到服务端断开连接的消息。

2、轮训

(1) 特点

让客户端每个两秒或者一秒向后台发送一次请求。缺点:延迟,请求太多服务器压力大

(2) 前端代码示例

<template>
  <div id="training-rotation">
  </div>
</template>

<script>
import { tainingRotationApi } from "../../request/api";
export default {
  name: "trainingRotation",
  // 定义当前页面中的数据
  data() {
    return {
      timer: null,
      dataInfo: {},
    }
  },
  // create: 在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
  create() {
  },
  // mounted: 在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。
  mounted() {
    this.getData();
    this.trainingRotation();
  },
  // 关闭会离开页面之前触发
  beforeDestroy() {
  },
  // 定义当前页面中的方法
  methods: {
    // 轮训
    trainingRotation() {
      this.timer = window.setInterval(() => {
        setTimeout(() => {
          this.getData()
        }, 0)
      }, 3000)
    },
    // 接口请求
    getData() {
      tainingRotationApi({}).then((res) => {
        this.dataInfo = res.data;
      })
    },
  },
  // 我们从destroyed的字面意思可知,中文意为是“销毁”的意思,当我们离开这个页面的时候,便会调用这个函数
  destroyed() {
    window.clearInterval(this.timer)
  }
}
</script>

<style lang="less" scoped>
</style>

(3) 后端代码示例

from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from config import cfg

# 轮训 (接口正常写 - 前端去做轮训)
class TrainingRotationView(APIView):
    def get(self, request, *args, **kwargs):
        response_data = {'code': status.HTTP_200_OK, 'msg': cfg.MSG.SUCCESS, 'data': None}
        response_data['data'] = {
            'memory_info': SystemInformation.memory_info(),
            'cpu_info': SystemInformation.cpu_info(),
            'disk_info': SystemInformation.disk_info(),
            'user_info': SystemInformation.user_info(),
            'gpu_info': SystemInformation.gpu_info(),
        }
        return Response(response_data)

3、长轮训

(1) 特点

客户端向服务器发送请求,服务器最多夯住20s,一旦有数据到来就立即返回,数据的响应式没有延迟的

(2) 前端代码示例

<template>
  <div id="long-training-rotation">
    <div class="content">
      <ul class="message-items">
        <li v-for="message, index in messageList" :key="index" class="message-item">
          {{ index }} - {{  message  }}
        </li>
      </ul>
      <div class="options-form">
        <el-input type="textarea" :rows="2" placeholder="请输入内容" v-model="message">
        </el-input>
        <el-button type="primary" plain :disabled="message ? false : true" @click="sendMessage">发送</el-button>
      </div>
    </div>
  </div>
</template>

<script>
import { getLongTainingRotationApi, postLongTainingRotationApi } from "../../request/api";
const url = window.location.href;
const obj = {};
if (url.split('?').length === 2) {
  let getqyinfo = url.split('?')[1]
  let getqys = getqyinfo.split('&')
  for (let i = 0; i < getqys.length; i++) {
    console.log(i)
    let item = getqys[i].split('=')
    let key = item[0]
    let value = item[1]
    obj[key] = value
  }
}
export default {
  name: "longTrainingRotation",
  data() {
    return {
      user_id: null,
      message: "",
      messageList: []
    }
  },
  created() {
    this.getMessage();
  },
  mounted() {
  },
  methods: {
    // 接口请求 - 轮训
    getMessage() {
      getLongTainingRotationApi({ user_id: obj.user_id }).then((res) => {
        if (res.data) {
          this.messageList.push(res.data)
        }
        this.getMessage();
      })
    },
    // 发送信息
    sendMessage() {
      postLongTainingRotationApi({ message: this.message }).then((res) => {
        if (res.code === 201) {
          this.message = "";
        }
      })
    }
  },
}
</script>

<style lang="less" scoped>
.content {
  margin: 0 auto;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  width: 800px;

  .message-items {
    width: 800px;
    height: 560px;
    margin-bottom: 20px;

    .message-item {
      line-height: 28px;
    }
  }

  .options-form {
    margin-top: 20px;
  }
}
</style>

(3) 后端代码示例

import queue
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from config import cfg


# 长轮训 (接口基于队列 - 前端去做递归)
class LongTrainingRotationView(APIView):
    # 用户消息队列
    USER_QUEUE = {}

    # 获取消息
    def get(self, request, *args, **kwargs):
        response_data = {'code': status.HTTP_200_OK, 'msg': cfg.MSG.SUCCESS, 'data': None}
        user_id = request.query_params.get('user_id')
        if user_id not in self.USER_QUEUE:
            self.USER_QUEUE[user_id] = queue.Queue()
        q = self.USER_QUEUE[user_id]
        try:
            response_data['data'] = q.get(timeout=10)
        except queue.Empty as e:
            pass
        return Response(response_data)

    # 创建消息
    def post(self, request, *args, **kwargs):
        response_data = {'code': status.HTTP_201_CREATED, 'msg': cfg.MSG.SUCCESS, 'data': None}
        message = request.data.get('message')
        for user_id, q in self.USER_QUEUE.items():
            print(user_id)
            q.put(message)
        return Response(response_data)

4、WebSocket

(1) 特点

客户端和服务端创建链接不断开,那么就可以实现双向通信

(2) 流程

  • 连接,客户端发起

  • 握手,客户端发送一个消息,后端接收到消息在做一些特殊处理并返回。服务端支持websocket协议

  • 收发数据(加密)

  • 断开连接

5、Channels

(1) 安装

pip install channels
pip install channels-redis

(2) 快速上手

  • 在settings中添加配置

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'channels',
    ]
    
    ASGI_APPLICATION = "django_channels_demo.routing.application"
    
  • asgi 中修改代码

    """
    # 默认配置
    import os
    
    from django.core.asgi import get_asgi_application
    
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_template.settings')
    
    application = get_asgi_application()
    """
    
    
    import os
    from django.core.asgi import get_asgi_application
    from channels.routing import ProtocolTypeRouter, URLRouter
    from django_template import routers
    
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_demo.settings')
    
    # application = get_asgi_application()
    application = ProtocolTypeRouter({
        "http": get_asgi_application(),
        "websocket": URLRouter(routers.websocket_urlpatterns)
    })
    
  • 在项目同名目录创建 routers.py

    # websocket 路由
    
    from django.urls import re_path
    from channels.views import ChatConsumer
    
    websocket_urlpatterns = [
        re_path(r'ws/chat/$', ChatConsumer.as_asgi()),
    ]
    
  • 编写处理websocket逻辑业务

    # 案例一:基本使用
    from channels.generic.websocket import WebsocketConsumer
    from channels.exceptions import StopConsumer
    
    class ChatConsumer(WebsocketConsumer):
    
        def websocket_connect(self, message):
            self.accept()
    
        def websocket_receive(self, message):
            print('接收到消息', message)
            self.send(text_data='收到了')
    
        def websocket_disconnect(self, message):
            print('客户端断开连接了')
            raise StopConsumer()
            
            
    # 案例二:基本使用
    from channels.generic.websocket import WebsocketConsumer
    from channels.exceptions import StopConsumer
    
    
    class SimpleChatConsumer(WebsocketConsumer):
        def connect(self):
            self.accept()
    
        def receive(self, text_data=None, bytes_data=None):
            self.send(text_data)
    
            # 主动断开连接
            # self.close()
    
        def disconnect(self, code):
            print('客户端要断开了')
    
    
    # 案例三:基础聊天室
    from channels.generic.websocket import WebsocketConsumer
    from channels.exceptions import StopConsumer
    
    
    CONN_LIST = []
    
    class ChatConsumer(WebsocketConsumer):
    
        def connect(self):
            self.accept()
            CONN_LIST.append(self)
    
        def receive(self, text_data=None, bytes_data=None):
            for conn in CONN_LIST:
                item.send(text_data)
    
            # 主动断开连接
            # self.close()
    
        def disconnect(self, code):
            CONN_LIST.remove(self)
    

(3) channel layer

3-1 基于 内存 的 channel layer

  • settings 配置

    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels.layers.InMemoryChannelLayer",
        }
    }
    
  • 业务处理

    from channels.generic.websocket import WebsocketConsumer
    from asgiref.sync import async_to_sync
    
    
    # WebSocket - 群聊 - 基于 channel layer 实现
    class GroupChatView(WebsocketConsumer):
    
        # 接收这个客户端的链接
        def websocket_connect(self, message):
            """
            # 1. 接受这个客户端的链接
            self.accept()
            # 2. 将这个客户端的链接对象加入到某个地方(内存 or redis)
            # async_to_sync 将异步功能转为同步功能
            async_to_sync(self.channel_layer.group_add)('x1', self.channel_name)
            """
            print(f'接受这个客户端的链接: {message}')
            # 获取群号, 路由中获取
            group = self.scope['url_route']['kwargs'].get('group')
            self.accept()
            async_to_sync(self.channel_layer.group_add)(group, self.channel_name)
    
        # 浏览器基于websocket链接的请求是,自动触发
        def websocket_receive(self, message):
            """
            当接收这个客户端发送的消息后会通知组(group1)内的所有客户端,在此方法中可以定义任意的功能
            """
            print(f'接收这个客户端发送的消息: {message}')
            # 获取群号, 路由中获取
            group = self.scope['url_route']['kwargs'].get('group')
            async_to_sync(self.channel_layer.group_send)(group, {'type': 'xx.oo', 'message': message})
    
        # 客户端与服务器断开连接时自动触发
        def websocket_disconnect(self, message):
            """
            将当前客户端链接从组(group1)内移除
            """
            print(f'客户端与服务器断开连接: {message}')
            group = self.scope['url_route']['kwargs'].get('group')
            async_to_sync(self.channel_layer.group_discard)(group, self.channel_name)
            raise StopConsumer()
    
        # 定义方法 根据 "xx.oo" 命名
        def xx_oo(self, event):
            text = event.get('message').get('text')
            request_data = json.loads(text)
            self.send(json.dumps(request_data)
    
  • 前端代码

    <template>
        <div id="group_chat">
            <div class="group-options">
                <el-select v-model="group" @change="groupChange" placeholder="请选择群聊">
                    <el-option v-for="item in groupOptions" :key="item.value" :label="item.label" :value="item.value">
                    </el-option>
                </el-select>
            </div>
    
            <ul class="message-items">
                <li v-for="item, index in messages" :key="index" class="message-item">
                    <div v-if="item.user_id === user.id" style="text-align: right;">{{  item.message  }} - {{  item.user_id  }}
                        -
                        {{  user.id  }}</div>
                    <div v-else style="text-align: left;">{{  item.message  }} - {{  item.user_id  }} - {{  user.id  }}</div>
                </li>
            </ul>
    
            <div class="message-input">
                <el-input type="textarea" :rows="2" placeholder="请输入内容" v-model="message">
                </el-input>
            </div>
    
            <el-row>
                <el-button type="primary" round @click="send">发送</el-button>
            </el-row>
        </div>
    </template>
    
    <script>
    
    export default {
        name: "groupChat",
        data() {
            return {
                socket: null,
                message: "",
                messages: [],
                user: null,
                group: 1,
                groupOptions: [
                    { label: '群聊1', value: 1 },
                    { label: '群聊2', value: 2 },
                    { label: '群聊3', value: 3 },
                    { label: '群聊4', value: 4 },
                    { label: '群聊5', value: 5 },
                ]
            }
        },
        created() {
            this.createWebSocket();
        },
        methods: {
            // 初始化websocket
            createWebSocket() {
                // 创建socket对象(内部自动会链接)
                let ws_url = `ws://192.168.20.158:8000/ws/group_chat/${this.group}/`
                this.socket = new WebSocket(ws_url);
                // 创建链接,成功之后自动触发
                this.socket.onopen = this.socketOnopen;
                // 接收消息,当websocket接收到服务端发送来的消息时,自动回触发这个函数
                this.socket.onmessage = this.socketOnmessage;
                // 服务端主动断开连接时,这个方法也被触发
                this.socket.onclose = this.socketOnclose;
                // 获取用户信息
                this.user = JSON.parse(localStorage.getItem("user"));
            },
            // 客户端 - 创建链接 - 回调函数
            socketOnopen(event) {
                console.log("客户端创建链接之后自动触发", event);
            },
            // 客户端 - 接收消息 - 回调函数
            socketOnmessage(event) {
                console.log("客户端接收来自服务端的消息", event)
                this.messages.push(JSON.parse(event.data))
            },
            // 服务端 - 断开连接 - 回调函数
            socketOnclose(event) {
                console.log("服务端断开连接之后自动触发", event);
            },
            // 客户端 - 发送消息 - 主动触发
            sendMessage(msg) {
                this.socket.send(JSON.stringify(msg));
            },
            // 客户端 - 关闭链接 - 主动触发
            closeConn() {
                this.socket.close();
                console.log("客户端断开连接之后自动触发");
            },
            // 发送
            send() {
                this.sendMessage({ message: this.message, user_id: this.user.id });
            },
            // 切换群聊
            groupChange() {
                this.closeConn();
                this.socket = null;
                this.messages = [];
                this.createWebSocket();
            }
        },
        // 组件销毁后 - 关闭socket 链接
        destroyed() {
            this.closeConn();
            this.socket = null;
        }
    }
    </script>
    
    <style lang="less" scoped>
    #group_chat {
        text-align: center;
    
        .group-options {
            margin-bottom: 20px;
        }
    
        .message-items {
            width: 800px;
            height: 560px;
            margin: 0 auto;
            overflow-x: hidden;
            overflow-y: scroll;
            // background-color: pink;
            margin-bottom: 20px;
            box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1)
        }
    
        .message-input {
    
            width: 800px;
            margin: 0 auto;
            margin-bottom: 20px;
        }
    }
    </style>
    

3-2 基于 redis 的 channel layer

  • settings 配置

    # 方式一
    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels_redis.core.RedisChannelLayer",
            "CONFIG": {
                "hosts": [('10.211.55.25', 6379)]
            },
        },
    }
    
    # 方式二
    CHANNEL_LAYERS = {
        'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {"hosts": ["redis://10.211.55.25:6379/1"],},
        },
    }
     
    # 方式三
    CHANNEL_LAYERS = {
        'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {"hosts": [('10.211.55.25', 6379)],},},
    }
     
    # 方式四
    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels_redis.core.RedisChannelLayer",
            "CONFIG": {
                "hosts": ["redis://:password@10.211.55.25:6379/0"],
                "symmetric_encryption_keys": [SECRET_KEY],
            },
        },
    }
    
  • 业务处理

    from channels.generic.websocket import WebsocketConsumer
    from asgiref.sync import async_to_sync
    
    
    # WebSocket - 群聊 - 基于 channel layer 实现
    class GroupChatView(WebsocketConsumer):
    
        # 接收这个客户端的链接
        def websocket_connect(self, message):
            """
            # 1. 接受这个客户端的链接
            self.accept()
            # 2. 将这个客户端的链接对象加入到某个地方(内存 or redis)
            # async_to_sync 将异步功能转为同步功能
            async_to_sync(self.channel_layer.group_add)('x1', self.channel_name)
            """
            print(f'接受这个客户端的链接: {message}')
            # 获取群号, 路由中获取
            group = self.scope['url_route']['kwargs'].get('group')
            self.accept()
            async_to_sync(self.channel_layer.group_add)(group, self.channel_name)
    
        # 浏览器基于websocket链接的请求是,自动触发
        def websocket_receive(self, message):
            """
            当接收这个客户端发送的消息后会通知组(group1)内的所有客户端,在此方法中可以定义任意的功能
            """
            print(f'接收这个客户端发送的消息: {message}')
            # 获取群号, 路由中获取
            group = self.scope['url_route']['kwargs'].get('group')
            async_to_sync(self.channel_layer.group_send)(group, {'type': 'xx.oo', 'message': message})
    
        # 客户端与服务器断开连接时自动触发
        def websocket_disconnect(self, message):
            """
            将当前客户端链接从组(group1)内移除
            """
            print(f'客户端与服务器断开连接: {message}')
            group = self.scope['url_route']['kwargs'].get('group')
            async_to_sync(self.channel_layer.group_discard)(group, self.channel_name)
            raise StopConsumer()
    
        # 定义方法 根据 "xx.oo" 命名
        def xx_oo(self, event):
            text = event.get('message').get('text')
            request_data = json.loads(text)
            self.send(json.dumps(request_data))
    
  • 前端代码

    <template>
        <div id="group_chat">
            <div class="group-options">
                <el-select v-model="group" @change="groupChange" placeholder="请选择群聊">
                    <el-option v-for="item in groupOptions" :key="item.value" :label="item.label" :value="item.value">
                    </el-option>
                </el-select>
            </div>
    
            <ul class="message-items">
                <li v-for="item, index in messages" :key="index" class="message-item">
                    <div v-if="item.user_id === user.id" style="text-align: right;">{{  item.message  }} - {{  item.user_id  }}
                        -
                        {{  user.id  }}</div>
                    <div v-else style="text-align: left;">{{  item.message  }} - {{  item.user_id  }} - {{  user.id  }}</div>
                </li>
            </ul>
    
            <div class="message-input">
                <el-input type="textarea" :rows="2" placeholder="请输入内容" v-model="message">
                </el-input>
            </div>
    
            <el-row>
                <el-button type="primary" round @click="send">发送</el-button>
            </el-row>
        </div>
    </template>
    
    <script>
    
    export default {
        name: "groupChat",
        data() {
            return {
                socket: null,
                message: "",
                messages: [],
                user: null,
                group: 1,
                groupOptions: [
                    { label: '群聊1', value: 1 },
                    { label: '群聊2', value: 2 },
                    { label: '群聊3', value: 3 },
                    { label: '群聊4', value: 4 },
                    { label: '群聊5', value: 5 },
                ]
            }
        },
        created() {
            this.createWebSocket();
        },
        methods: {
            // 初始化websocket
            createWebSocket() {
                // 创建socket对象(内部自动会链接)
                let ws_url = `ws://192.168.20.158:8000/ws/group_chat/${this.group}/`
                this.socket = new WebSocket(ws_url);
                // 创建链接,成功之后自动触发
                this.socket.onopen = this.socketOnopen;
                // 接收消息,当websocket接收到服务端发送来的消息时,自动回触发这个函数
                this.socket.onmessage = this.socketOnmessage;
                // 服务端主动断开连接时,这个方法也被触发
                this.socket.onclose = this.socketOnclose;
                // 获取用户信息
                this.user = JSON.parse(localStorage.getItem("user"));
            },
            // 客户端 - 创建链接 - 回调函数
            socketOnopen(event) {
                console.log("客户端创建链接之后自动触发", event);
            },
            // 客户端 - 接收消息 - 回调函数
            socketOnmessage(event) {
                console.log("客户端接收来自服务端的消息", event)
                this.messages.push(JSON.parse(event.data))
            },
            // 服务端 - 断开连接 - 回调函数
            socketOnclose(event) {
                console.log("服务端断开连接之后自动触发", event);
            },
            // 客户端 - 发送消息 - 主动触发
            sendMessage(msg) {
                this.socket.send(JSON.stringify(msg));
            },
            // 客户端 - 关闭链接 - 主动触发
            closeConn() {
                this.socket.close();
                console.log("客户端断开连接之后自动触发");
            },
            // 发送
            send() {
                this.sendMessage({ message: this.message, user_id: this.user.id });
            },
            // 切换群聊
            groupChange() {
                this.closeConn();
                this.socket = null;
                this.messages = [];
                this.createWebSocket();
            }
        },
        // 组件销毁后 - 关闭socket 链接
        destroyed() {
            this.closeConn();
            this.socket = null;
        }
    }
    </script>
    
    <style lang="less" scoped>
    #group_chat {
        text-align: center;
    
        .group-options {
            margin-bottom: 20px;
        }
    
        .message-items {
            width: 800px;
            height: 560px;
            margin: 0 auto;
            overflow-x: hidden;
            overflow-y: scroll;
            // background-color: pink;
            margin-bottom: 20px;
            box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1)
        }
    
        .message-input {
    
            width: 800px;
            margin: 0 auto;
            margin-bottom: 20px;
        }
    }
    </style>
    

Django 扩展

1、xadmin

(1) 安装

https://github.com/sshwsfc/xadmin/archive/master.zip

将下载好的压缩包放到项目根目录

# 安装django
pip install django==2.0.5 -i https://pypi.douban.com/simple

# 源码安装xadmin
pip install xadmin-master.zip

将xadmin-master.zip解压,解压后将文件夹中的xadmin复制到django项目的根目录

(2) 配置

INSTALLED_APPS = [
    ...
    'xadmin',
    'crispy_forms',
    'reversion'
]

配置完成之后启动项目

(3) 问题

  • ModuleNotFoundError: No module named 'django.core.urlresolvers'

    from django.core.urlresolvers
    # 修改成
    django.urls
    
  • 关联关系ForeignKey引发的错误,打开xadmin文件中的模型文件 models.py

    凡是出现关联关系字段的地方全部加上 on_delete=models.CASCADE

  • TypeError: _init_() tasks 1 positional argument but 6 were given

    # 将 dashboard.py 中的
    forms.Field.__init__(self, required, widget, label, initial, help_text, *args, **kwargs)
    # 修改为
    forms.Field.__init__(self) 
    
  • ImportError: cannot import name 'login' from 'django.contrib.auth.views'

    # 将 website.py 中的
    from django.contrib.auth.views import login
    from django.contrib.auth.views import logout
    
    # 修改为
    from django.contrib.auth import authenticate, login, logout
    
  • importError: cannot import name 'QUERY_TERMS' from 'django.db.models.sql.query'

    # django2.1.1版本将xadmin\plugins\filters.py文件中的
    from django.db.models.sql.query import LOOKUP_SEP, QUERY_TERMS
    # 修改为
    from django.db.models.sql.query import LOOKUP_SEP, Query
    
    # 在Django2.0版本中把
    from django.db.models.sql.query import LOOKUP_SEP, QUERY_TERMS
    # 修改为:
    from django.db.models.sql.query import LOOKUP_SEP
    from django.db.models.sql.constants import QUERY_TERMS
    
  • ModuleNotFoundError: No module named 'django.contrib.formtools' 导入fromtools错误,版本太低

    # 卸载旧版本
    pip uninstall django-formtools
     
    # 安装新版本
    pip install django-formtools
    
  • AttributeError: 'Settings' object has no attribute 'MIDDLEWARE_CLASSES'

    # 将xadmin\plugins\language.py 中的
    if settings.LANGUAGES and 'django.middleware.locale.LocaleMiddleware' in settings.MIDDLEWARE_CLASSES:
    
    # 修改为:
    if settings.LANGUAGES and 'django.middleware.locale.LocaleMiddleware' in settings.MIDDLEWARE:
    
  • ImportError: cannot import name 'SKIP_ADMIN_LOG'

    # 将importexport.py 中的
    # from import_export.admin import DEFAULT_FORMATS, SKIP_ADMIN_LOG, TMP_STORAGE_CLASS
    # 修改为:
    from import_export.admin import DEFAULT_FORMATS, ImportMixin, ImportExportMixinBase
    
  • TypeError: login() got an unexpected keyword argument 'current_app'

    注释第61行

  • AttributeError: 'Media' object has no attribute 'add_css'

    def vendor(*tags):
        # media = Media()
        # for tag in tags:
        #     file_type = tag.split('.')[-1]
        #     files = xstatic(tag)
        #     if file_type == 'js':
        #         media.add_js(files)
        #     elif file_type == 'css':
        #         media.add_css({'screen': files})
        # return media
    
        css = {'screen': []}
        js = []
        for tag in tags:
            file_type = tag.split('.')[-1]
            files = xstatic(tag)
            if file_type == 'js':
                js.extend(files)
            elif file_type == 'css':
                css['screen'] += files
        return Media(css=css, js=js)
    
  • AttributeError: 'AutoField' object has no attribute 'rel'

    # if isinstance(field.rel, models.ManyToOneRel):
    if isinstance(field.remote_field, models.ManyToOneRel):
    
  • TypeError: 'NoneType' object is not reversible

    pass

  • 'ManyToOneRel' object has no attribute 'to'

    # 修改 xadmin\plugins\quickform.py 中80行
    self.add_url, (('Create New %s') % **self.rel.to.meta.verbose_name**), name,
    # 修改为
    self.add_url, (_('Create New %s') % self.rel), name,
    
posted @ 2023-05-31 09:26  菜鸟程序员_python  阅读(144)  评论(0)    收藏  举报