概念一览:URL和路由、模板层、数据层、中间件


Django操作顺序

  1. settings.py改语言和时区
  2. settings.py改默认数据库
  3. migrate生成表
  4. startapp创建应用
  5. settings.py添加应用
  6. 将应用注册到管理后台admin
  7. (在项目目录下)创建templates文件夹
  8. 将模板路径添加到settings.py
  9. 样式表等静态文件放到static目录下

Django介绍

  1. 纯python写的开源web框架——重量级,全面
  2. 包含:
    1. 基本的配置文件/路由系统
    2. MTV(模型-模板-视图)
    3. Cookies和Session(web中记录http状态的技术)
    4. 《特别著名》的admin管理后台——面向工作人员
    5. ……
  3. 用途:
    1. 网站
    2. 微信公众号(请求端,相当于浏览器)
    3. 小程序后端开发(同上),上面三个就是在http的应用
    4. 人工智能平台融合(语音识别等)
  4. 版本(最新版本4.02,选用3.2)

开始

1
2
3
django-admin startproject [projectname]
cd [projectname]
python manage.py runserver 127.0.0.1:8000

上面创建了一个项目并开启了一个开发服务器(developme server)。

需要注意的是开发服务器是牺牲了性能的,并不适合用来实际部署。

image-20220425160845517

此时终端显示请求的信息。

项目的一级目录为:

image-20220425161522469

  1. manage.py是项目的管理文件,里面包含了各种命令

  2. 还有一个项目同名文件夹[projectname]/[projectname],其结构为:

    image-20220425161938863

    配置文件settings.py包含了django项目启动的所有项目配置

    1. 公有配置,熟悉那几个
    2. 自定义配置,自己写的,添加进去

    比如关闭调试模式后去访问就是下面这样的,报错很简单

    image-20220425163745644

URL和视图函数

URL

image-20220425171354678

其中:

  1. 默认端口是80,不写就默认是80端口
  2. path就是我们常说的“路由”,用于定位一个http对象
  3. [?query]为查询字符串,[#fragment]为锚点,可以定位文中某个片段,query通常为key=value的结构,多个用&连接,将其传递给服务器;fragment通常为标题。

访问一个url是,django会首先进入主路由文件[projectname]/urls.py,定位到其中的urlpatterns列表,然后从上往下匹配路由。一旦匹配成功就停止向下匹配,然后将请求传递给后面的视图函数

可以使用正则表达式,其规范应该是PCRE

路由配置

path()函数

1
2
3
4
# 导入path模块
from django.urls import path
# path函数的用法
path([route],[views],[name])

分别解析path函数的参数:

  1. route:匹配请求路径
  2. views:指定视图函数的名称——必须是函数名
  3. name:为地址取别名,route比较长的时候用比较好

path转换器

如果url过多且有一定的规律,如page/1/~page/100/,我们可以一个个地配置,但是,更好的处理方法应该是使用视图函数。

转换器语法:\<转换器类型:自定义名>

若转换器类型匹配到对应类型的数据,则将该数据按照关键字传参的方式传递给视图函数,这时使用的就是自定义名。

可以使用的转换器类型:

image-20220425190300520

其中高频使用的是strint

如,给页数设置转换器:

1
2
3
urlpatterns = [
path('page/<int:page>/', views.page_view)
]

然后,我们就可以通过关键字传参的形式传给视图函数了,视图函数如下:

1
2
3
def page_view(request,page):
html="<h1>这是第%d页</h1>"%(page)
return HttpResponse(html)

注意关键字在视图函数中的使用——使用格式化变量。

所以正则表达式也可以用了啊,只要知道怎么写就行……

re_path()函数

转换器只提供了初略的匹配。当我们需要更精细的匹配时(如对数据进行限制),就可以尝试使用正则表达式了。

1
2
3
4
# 引入re_path模块
from urls import re_path
# re_path的用法
re_path([reg],[view],[name])

其中reg是正则表达式的命名分组模式,具体为(?p<name>pattern)

匹配到的命名模块以关键字传参的方式传递给视图函数,示例如下:

1
2
3
4
5
6
7
8
9
# url配置
urlpatterns=[
# 匹配生日date
re_path(r'^(?P<year>\d{1,4})/(?P<month>\d|(11)|(12))/(?P<day>([12]\d)|30)',views.birth_view)
]
# 视图函数
def birth_view(request,year,month,day):
html="<h1>生日是%s年%s月%s日</h1>"%(year,month,day)
return HttpResponse(html)

视图函数

视图函数是用于接收浏览器http请求作为HttpRequest对象,并通过HttpRequest对象返回响应的一个函数。

可以看出,HttpRequest对象的作用是接收和响应http请求

视图函数的基本语法为:

1
2
3
def [function_name](request,[其它参数]):
[函数体]
return HttpResponse([HttpResponse对象])

HttpRequest对象可以是一个html字符串,也可以是一个html文件。

http请求和响应

请求

Django中的请求就是视图函数中的第一个参数request,即HttpRequest对象。Django收到http请求后,会根据请求数据报文创建HttpRequest对象,它的属性描述了请求的所有相关信息。

属性 描述
path_info URL字符串
method 请求方法
body 请求体字符串
scheme http请求的协议
GET 以类字典的形式返回查询字符串
META[‘REMOTE_ADDR’] 返回元数据字典,比较重要的客户机ip

可以通过打印到控制台查看相关属性。

响应

image-20220426170014041

200500,一个错,一个对;重定向更多的是临时重定向404是路由匹配失败的结果。

Django的响应对象:

1
2
# 构造形式
HttpResponse(content=[响应体],content_type=[响应体数据类型],status=[状态码])

content_type的默认值是html,其他的还有:

image-20220426170617928

GET请求

请求指定的页面信息,并返回实体。GET是最常见的请求

在视图函数中区分不同请求方法的操作:

1
2
3
4
if request.method=='GET':
# 处理GET请求
if request.method=='POST':
# 处理POST请求,一般是处理用户提交上来的数据

产生GET请求的场景:浏览器地址栏输入URL、点击html的\标签、强制表单为GET请求等。

下面我们关注查询字符串,这是用户使用GET请求向服务端请求时传递的参数。这些参数一般是无关痛痒,别人看见也无所谓的信息。

对于URL的查询字符串,可能有,也可能没有。如果直接使用request.GET['p']就可能报错——这时使用request.GET.get('p','no p')

如果查询字符串的一个key有多个value,那么应该使用request.GET.getlist()

POST 请求

向指定资源提交数据(如表单或文件),一般是比较隐秘性的信息。

(大量数据/隐私性数据)

用户通过POST提交了数据后,服务端就要对这些数据进行处理。

先创建一个表单:

1
2
3
4
5
6
POST_FORM='''
<form method='post' action='/log'>
用户名:<input type='text' name='username'>
<input type='submit' value='commit'>
</form>
'''

其中method指定请求方法为post,提交表单时,表单数据就会以关键字传参的形式通过POST请求上传到服务器。

在服务端可以对提交的数据(使用name)进行处理:

1
2
3
if request.method=='POST':
print("用户名是:",request.POST['username'])
return HttpResponse('<h1>Post is OK!</h1>')

image-20220426175913660

但是我们需要把防跨站攻击csrf中间件关闭。

Django设计模式

传统的:MVC

作用:降低代码的耦合度(解耦合)

image-20220426182722304

  1. Model(模型):与数据库通信,从而完成对数据库的封装,一般比传统的sql语句更便捷。
  2. View(视图):向用户展示结果(WHAT & HOW->html)
  3. Control(控制器):统筹(处理请求、获取数据、返回结果)——>核心

升级版:MTV

将MVC的V拆分成T和V,C层其实还在,Django的C就是前面的主路由,但是C已经很简化了,所以一般不说。(名字无所谓,只要能合理完成任务)

image-20220426183547283

  1. Model(模型):没变,学习的重点
  2. Template(模板):将内容呈现到浏览器(How——>html)
  3. View(视图):核心,负责接收请求、获取数据、返回结果(WHAT)

模板层入手

模板可以根据从View层传入的字典数据动态地生成相应的HTML网页

模板的使用:

  1. 创建模板文件夹,一般在项目目录下创建templates文件夹

  2. settings.py配置:

    1. BACKEND:模板引擎
    2. DIRS:模板的搜索目录
    3. APP_DIRS:是否在app中的templates文件夹下搜索模板
    4. OPTIONS:默认即可
    1
    2
    # 一般只需要配置DIRS
    'DIRS': [os.path.join(BASE_DIR,"templates")]
  3. 在视图函数中加载模板

    1. 方法一:通过loader加载模板:(相对麻烦)

      1
      2
      3
      4
      5
      6
      7
      from django.template import loader
      # 使用loader加载模板
      t=loader.get_template("[模板文件名]")
      # 向t中传入字典数据动态生成HTML字符串
      html=t.render([字典数据])
      # 响应,将HTML返回给浏览器
      return HttpResponse(html)
    2. 方法二:通过render加载并响应模板:(区别前面的render

      1
      2
      from django.shortcuts import render
      return render(request,[模板文件名],[字典数据])
  4. 和视图层交互:使用字典数据。在模板中,使用{{ 变量名 }}的语法即可调用视图传进来的变量。

模板层变量和标签

变量

image-20220426191352445

传递到模板中的变量的访问方式:

image-20220426191859776

需要注意的是传递到模板中的数据的最外层一定是字典!上面的数据类型都是字典中的数据!

image-20220426192544222

html中的使用:

image-20220426192718406

标签

标签一般可结合GET和POST请求使用

1
2
3
4
# 模板中的标签语法
{% [标签] %}
# [content]
{% [结束标签] %} # 少部分标签不需要结束标签

如:

1
2
3
4
5
# if标签
{% if [条件表达式] %} # if标签加括号是无效的
{% elif [条件表达式] %}
{% else %}
{% endif %}

需要注意的是,条件表达式中的空格不能省略,如op=='add'会报错,应该用op == 'add'

1
2
3
4
5
# for标签
{% for [变量] in [可迭代对象] %}
{% empty %}
# 可迭代对象无数据是执行,省去了自己判空的步骤
{% endfor %}

for循环提供了一个内置变量:forloop,它有如下属性(相当于成员变量):

image-20220427181735237

  1. 可以用forloop.counter来调出for循环的索引

  2. 可以用forloop.firstforloop.last来判断是否为循环入口和出口

  3. 如果是嵌套循环,内部的循环可以使用forloop.parentloop调出外层循环的forloop变量

过滤器

过滤器可以在变量输出时对变量的值进行处理,直接在模板内完成,不需要传入视图函数。

语法:

1
{{ [变量] | 过滤器:'[参数值]' }}

image-20220427182700795

在html模板中即可使用上面的过滤器对传进来的参数进行简单的处理。

继承

image-20220427184309137

一般的网页,都会有公共的头部和尾部,唯一的区别就是中间的那些部分。为了实现各页面有相同的头部和尾部,且提高代码的可复用性,我们可以使用继承!

模板的继承:使父模板在子模版中重用

语法:

  1. 父模板:

    ​ 定义block块,用于标识这部分可以修改

  2. 子模版:

    1. 使用extends标签继承父模板

    2. 使用block标签覆盖父模板中的相应内容块

      1
      2
      {% block block_name %}
      {% endblock block_name %}

!注意:模板继承时,视图函数传递给父模板的变量,子模版是拿不到的,子模版也需要单独传参。

url反向解析

思考url可能出现的位置

  1. 模板html中
    1. <a>标签的href属性
    2. <form>标签的action属性指定跳转的页面
  2. 视图函数中HttpResponseRedirect('url')将用户请求跳转到指定的url

书写url的规范

  1. 绝对地址:https://blog.fullcomb.top
  2. 相对地址:(区分下面两种)
    1. 斜杠开头:/categories/python,在前面直接加协议和主机名
    2. 没有斜杠:page/1,去找url中的最后一个斜杠,放在斜杠后面

下面来正式看url的反向解析

urls.py配置path时,使用path的第三个参数name=[别名]

1
2
3
urlpatterns=[
path('page/1',views.page_1,name='page1')
]

以后我们引用url时,直接使用别名page1即可访问这个链接,不需要考虑相对路径选哪个:

  1. html模板中,使用url标签:{% url '[别名]' %}

  2. 视图函数中,使用reverse函数:

    1
    2
    3
    from django.urls import reverse
    url=reverse('[别名]')
    return HttpResopnseRedirect(url)

url反向解析的好处是可以动态地拿到最新的路由地址,不管路由怎么变化,使网站维护更简单

静态文件

  1. 什么是静态文件?

    图片、css、js、视频、音频都是静态文件。浏览器使用http请求去加载相应的资源。

  2. 在Django中使用静态文件

    最简单的方法:直接使用静态文件的超链接,使用网上的静态文件

    如果静态文件放在本地,就需要一系列配置:

    1. 配置静态文件的url,默认为’/static/‘

    2. 配置静态文件的存储路径,需要自己添加和命名:

      image-20220427194645194

    3. 可以在static文件夹下创建二级文件夹

  3. 在html中使用’/static/‘这样的路由即可成功加载静态文件。此外,django还提供了static标签{% static '[文件在static文件夹下的路径]'%}

Django应用

什么是应用(app)?为什么要使用应用?

Django的业务逻辑在视图中实现,即编写views.py。如果一个网站业务太多,而所有业务逻辑的实现都在一个views.py中,成百上千人改一个文件,git直接懵逼。Django支持将一个网站的多个业务拆分(解耦合)到不同的app中,各app间互不干扰。

创建应用

1
python manage.py startapp music

这时项目目录下生成了music文件夹,但也仅此而已。要让Django知道这是一个应用,还需要到同名文件夹下去配置:

image-20220427201201293

即一创建,二注册。

应用文件夹的目录结构

image-20220427201449252

  1. migrations:放置数据库的同步迁移文件
  2. admin.py:应用的管理后台,可以让分别从人员对数据库进行增删改查
  3. models.py:模型层,也和DB有关
  4. views.py:视图层,实现应用的逻辑

分布式路由

views.py一样,如果所有路由信息都到同名文件夹下的urls.py里面去写,那么同样会遇到很多人同时修改一个文件的情况。所以,Django支持在app目录下的urls.py去配置路由信息。

但是app文件夹下我们还要手动创建urls.py

这时同名文件夹下的urls.py负责路由的分发。

配置方法如下:

  1. 主路由:

    image-20220427214317268

  2. 应用路由:

    1
    2
    3
    4
    5
    6
    from django.urls import URLPattern, path
    from . import views

    urlpatterns = [
    path('index',views.index_view)
    ]

应用下模板

image-20220427215253586

注意这里是逐层查找。如果两个应用下有同名的模板,那么Django查找到的总是前面那个,这是render的机制所致,render从项目目录的模板开始查找。

换言之,应用模板之间最好不要同名!

但是也可以指定目录来规定模板的搜索路径:

image-20220427220134020

模型层

MVC和MTV的M始终不变,模型层是与数据库沟通的桥梁。

具体怎么连接忘了,反正现在配好了就行,之后在在虚拟环境下看一看。

image-20220428154924121

settings.py的配置如下:

image-20220428155231126

一个网站对应一个数据库,而不是一个应用!

django还支持其他数据库:

image-20220428155534624

什么是模型?

模型是一个python类,与一般的类不同的是,它是由django.db.models.Model派生出来的子类。换言之,继承于django.db.models.Model的类都是模型类。

模型与数据库之间存在如下映射关系:

  1. 一个模型类代表数据库中的一个表
  2. 模型类中的一个类属性代表数据库中的一个字段

=>模型是数据交互的接口,是表示和操作数据的方法和方式

ORM简介

ORM即object relation mapping,对象关系映射,是通过类和对象对数据库进行操作,从而避免通过SQL语句操作数据库的一种程序技术。

好处:

  1. 只需要面向对象编程,不需要面向数据库写代码
  2. 实现了数据模型与数据库的解耦,屏蔽了不同数据库操作上的差别。

缺点:

  1. 复杂查询,使用ORM可能会有点难
  2. 查询过程中会有一些性能损失

image-20220428160830582

使用ORM

models.py编辑:

1
2
3
4
5
from django.db import models
# 定义模型类
class bookstore(models.Model):
title=models.CharField('书名',max_length=50,default='')
price=models.DecimalField('价格',max_digits=7,decimal_places=2)

然后完成数据库的迁移:

  1. 生成迁移文件:python manage.py makemigrations

    这时在migrations文件夹下会多出一个中间文件,这就是迁移脚本程序

  2. 执行迁移脚本:python manage.py migrate

    image-20220428162336508

​ 为什么会有这么多migrations?因为第一次迁移时还会由django的一些默认表。按照正常流程,默认表的迁移应该在创建项目后就执行的。

image-20220428162559990

image-20220428162628162

第一个图可以看到,我们自己写的表就是[应用名]_[类名],这也正好区分不同app下的同名类生成的表,比较合理。第二个图可以看到,django给我们的表添加了一个默认的主键id,字段名就是我们定义的类属性名。

image-20220428163014890

接下来我们要熟悉的就是不同的字段类型及其选项。

基础字段及选项

基础字段

CharField

  1. 数据库中对应类型为varchar(只有可变长的,没有定长的)
  2. 使用时必须要指定max_length的值!否则报错。

BooleanField

  1. 在数据库中对应的类型是tinyint(1)
  2. 数据库中使用0和1来表示具体的值
  3. 编程语言中,返回的是True和False

DateField

  1. 数据库中对应类型是date(年-月-日)
  2. 参数三选一:
    1. auto_now:每次更新相应数据时,自动将该字段设置为当前date
    2. auto_now_add:只有数据第一次被创建时设置为date
    3. default:使用默认时间

DateTimeField

  1. DB中datetime
  2. 三选一参数同上

FloatField

  1. DB中的类型为double
  2. DB和编程语言中都是小数

DecimalField

数据库中凡是跟”钱”相关的一般都要用decimal(x,y)

  1. 数据库中类型为decimal(x,y)
  2. 参数max_digits表示总位数,decimal_place表示小数位数

EmailField

专门存储邮箱,面向应用了属于是

它在数据库中的类型依然是varchar,但是Django使用了正则对输入的字符串进行了检测,如果邮箱格式不正确,那么就会抛出异常。这是在应用层面实现的。

Django使用这种方式给我们封装了一些常用的特殊字段。

IntegerField

DB中int

ImageField

和EmailField一样,它在数据库中也是varchar,它的作用是保存图片的存储路径。

TextField(比较高频)

DB中的数据类型为longtext,表示不定长字符数据。如提交论坛文章等

有了上面这些,我们就可以不用SQL的DDL了。

字段选项

字段选项,可以指定创建字段的额外信息,多个选项用逗号隔开,到多数选项都是布尔型。

  1. primary_key:设置为True,该字段则是主键。如果没有指定,那么就会默认生成一个主键id
  2. blank:设置为True,则可以为空。但是这里的空并不是数据库对应字段可以为NULL,而是在管理后台添加数据时该字段是否可以不填
  3. null:设置为True,则可以为空,db可以为NULL。建议不要使用NULL=True,否则会有一些坑,如聚合查询时查不到
  4. default:null=False时建议添加默认值。在原来的表基础上新增字段时必须要给出default,或者null=True
  5. db_index:设置为True,则为该字段添加索引
  6. unque:也是索引,和db_index不同的是,这是唯一索引
  7. db_column:指定列名,如果不用的话列名就是属性名,一般将列名设置为中文时用比较好,但一般也不需要把字段名设置为中文
  8. verbose_name:设置此字段在admin界面上显示的名称,也是中文化,方便非编程人员使用

好习惯:每次修改过字段选项后就执行makemigrationmigrate

Meta类

Meta类是model类的一个内部类,它有很多属性,用于在表层面上配置

例子:

1
2
3
4
5
6
class book(models.Model):
title=models.CharField('书名',max_length=50,default='')
price=models.DecimalField('价格',max_digits=7,decimal_places=2)
info=models.CharField('简介',max_length=100,default='')
class Meta:
db_table='book'

现在表名就是我们自己指定的了:

image-20220428174620857

注意Django的默认值不会再数据库层面体现,二是在软件层面实现的。这也实现了Django对各类数据库的解耦。

管理器对象

ORM基本操作包括增删改查CRUD——CREATE、READ、UPDATE、DELETE

而ORM进行CRUD的核心是模型类的管理器对象

什么是管理器对象objects?

每个继承自models.Model的模型类,都会有一个objects对象被同样继承下来,这就是管理器对象。

即模型类下有这样一个成员,它是另一个类的对象,名叫objects,它的作用是使用它的方法,就可对ORM的数据表进行增删改查。

之后我们需要掌握objects有哪些方法。掌握了这些方法,对数据库进行CURD就没问题了。

Django Shell

命令行输入python manage.py shell进入Django Shell。

和一般的python shell不同,django shell是基于当前的项目的,可以把当前项目的一些环境加载出来,便于对项目进行调试。

需要注意的是,django不会像开发服务器那样将改动立即生效,而是只在进入时引入一次。所以每次有变动的时候,应该重新进入Django Shell!

插入数据

数据的插入我们在Django Shell中进行。

使用管理器对象objectscreate操作可以直接插入一条数据:

1
2
from bookstore.models import book
b1=book.objects.create(title="Django从入门到入土",pub="清华大学出版社",price=20,market_price=25)

不要第一句忘了类的引入!

image-20220428183547855

这时一种方法,我们也可以通过实例化对象的方法来插入数据:

1
2
3
4
from bookstore.models import book
b2=book(title="Django实战训练",pub="机械工业出版社",price=24)
b2.market_price=30
b2.save()

注意这种方法最后还需要save才会将对象插入到数据库:

image-20220428183954746

1
2
3
4
5
6
# 插入更多数据
book.objects.create(title="Python",pub="清华大学出版社",price=20,market_price=25)
book.objects.create(title="Django",pub="清华大学出版社",price=70,market_price=75)
book.objects.create(title="JQuery",pub="机械工业出版社",price=90,market_price=85)
book.objects.create(title="Linux",pub="机械工业出版社",price=80,market_price=65)
book.objects.create(title="HTML5",pub="清华大学出版社",price=90,market_price=105)

ORM查询操作

Django的ORM查询依然通过管理器对象进行。

objects常用的查询方法:

  1. all():查询全部记录,返回QuerySet对象
  2. get():查询符合条件的单一记录
  3. filter():查询符合条件的多条记录
  4. exclude():查询除符合条件之外的全部记录

all方法

  1. 语法:

    1
    2
    3
    4
    from bookstore.models import book
    books=book.objects.all()
    for book in books:
    print("书名:",book.title,"出版社:",book.pub)
  2. 作用:查询这个模型类的说有实体的数据,数据库层面,相当于对表执行select * from [tablename]

  3. 这里返回的是QuerySet的容器对象

QuerySet

直接打印QuerySet对象时,我们得到的是如下值:

image-20220428230133800

如果我们想要直接了解数据,这是很不友好的。不过我们可以自定义QuerySet里面的对象的打印格式。

在模型类中定义函数:

1
2
def __str__(self):
return '%s %s %s %s\n'%(book.title,book.pub,book.price,book.market_price)

重进Django Shell后,直接打印模型类就可以直接得到我们想要的格式了:

image-20220428230533694

values方法

常言道,查询数据库时每次都使用select * from [表名]是不好的,.all方法同理。Django也提供了查询特定列的方法:values。

values(‘属性名1’,’属性名2’,……)即可查询到对应属性的值。

返回的也是QuerySet,但是与all()不同的是,values()返回的queryset里面存储的是字典,而all()的queryset存储的是每个对象。

image-20220428231306795

这时我们要取出某个字段就需要按照字典的方式:

1
2
3
infos=book.objects.values('title','pub')
for info in infos:
print(info['title'],"的出版社是",info['pub'])

values_list方法

作用和values()一样,区别是values_list()的queryset里面存放的是元组,所以取值方法应该和元组(列表)一样,使用索引。

image-20220428231947597

元组的顺序和函数内列举的顺序一样,所以单独获取的方法如下:

1
2
for info in infos:
print(info[1],'的出版社是',info[0])

order_by方法

可以按照里面的字段进行排序,默认为升序,加-则为倒序:

1
prices=book.objects.order_by('-price')

order_by()可以作为上面方法的子句。如果不使用上面的方法,则默认使用all方法,返回的queryset储存所有对象:

image-20220428232818223

即只要前面返回的是一个queryset,即可引用order_by方法。

对于一个queryset对象,我们还可以使用query()方法得到对应的SQL语句:

image-20220428233148032

ORM更新操作

单个数据

  1. 查:通过get()得到需要的实体对象
  2. 改:通过修改对象属性的值来修改对应字段的值
  3. 保存:最后执行save()方法保存修改

批量更新

同样是先查询,得到queryset后,使用queryset的update方法实现批量修改。

1
2
3
books=book.objects.all()
# 将所有书的零售价修改为100
books.update(market_price=100)

ORM删除操作

真实删除

先获取对象,再使用delete方法删除

1
2
3
4
try:
b1=book.objects.get(id=1)
except:
print(删除失败)

伪删除

在表中添加一个bool型的字段is_active,为True时则显示,为False时不显示。

所以显示数据的时候,需要用is_active来对数据进行过滤。

一般都是伪删除,要真删除的时候需要小心!

F对象与Q对象

F对象

1
from django.db.models import F

定义:一个F对象代表数据库中某条记录字段的信息。

理解:

  1. F对象只包裹这个字段名,但是不将它取出来,它的作用在转化sql语句的时候体现
  2. 可以实现+=的操作,而一般的操作转化为sql语句是=。

Q对象

1
from django.db.models import Q

当要对查询结果集进行逻辑操作(与、或、非等),需要用到Q对象。使用Q来包裹需要进行逻辑操作的条件即可:

1
2
3
Q(条件1) | Q(条件2) # 条件1或条件2成立
Q(条件1) & Q(条件2) # 条件1和条件2同时成立
Q(条件1) &~ Q(条件2) # 条件1成立且条件2不成立=>可以灵活组合

聚合查询

当我们需要对数据表的一个字段进行统计(如求和、计算最大值、最小值、平均值等),就是聚合查询(在数据库层面的操作)。

  1. 整表聚合
  2. 分组聚合

整表聚合的使用

  1. 导入聚合函数:from django.db.models import [聚合函数名],可以导入Sum、Avg、Count、Max、Min

  2. 使用语法:

    1
    book.objects.aggregate([结果变量名]=[聚合函数]('[列名]'))

    这里的结果变量名就是返回对应列的名字。(Mysql中使用AS)

    需要注意的是整表聚合返回的结果是一个字典,所以我们需要用字典的方式来引用结果。

分组聚合的使用

  1. 先得到需要使用聚合函数的列,得到queryset:

    1
    books=book.objects.values('[列名]')
  2. 再使用queryset的annotate方法,对查询的列进行分组聚合操作:

    1
    books.annotate(myconut=Count('[结果变量名]'))

数据库原生操作

原生数据库操作是专门用来查询的,使用sql语句时需要一步到位。

使用book.objects.raw(sql语句,拼接参数)即可。

常见的关系映射

关系映射是关系型数据库的灵魂,之后我们将学习如何在ORM中形成关系映射。

一对一

一个家庭只有一个户主,一个人只有一个指纹,这种关系就是一对一关系。

ORM使用OneToOneField来定义一对一关系。

使用OneToOneField是需要指定两个参数:类名和on_delete

  1. 类名指定外键跟哪个表关联
  2. on_delete指定进行级联删除时的操作

级联删除是指表与表存在外键关系时,删除一个表,另一个表应该怎么处理。

image-20220430144036055

  1. models.CASCADE:一起删除
  2. models.PROTECT:删不掉
  3. SET_NULL:将外键置空(前提是外键字段的NULL=True
  4. SET_DEFAULT:将外键设为默认值

注意mysql的字段名和类属性名不一样:

image-20220430145107191

名字是关联的表名+对应表的主键,下面在shell中插入数据:

1
2
3
4
5
6
7
8
# author:
a1=author.objects.create(name="王老师",age="30",email="wanglaoshi@qq.com")
author.objects.create(name="李老师",age="40",email="lilaoshi@qq.com")
a3=author.objects.create(name="杨老师",age="50",email="yanglaoshi@qq.com")
# wife
oto.objects.create(name="王夫人",author=a1) # 使用类属性名,等号另一边应该是对象
oto.objects.create(name="李夫人",author_id=2) # 使用字段名,等号另一边应该是值
oto.objects.create(name="杨夫人",author=a3)

image-20220430150312337

创建好后我们就可以查询了,分为正向查询反向查询

正向查询

通过使用了外键的表去查被引用的表:

1
2
wife=oto.objects.get(id=1)
print(wife.name,"的老公是",wife.author.name)

即直接.外键名.被引用类的属性名即可。

反向查询

反向查询不像正向查询那样显然,但是也是可以完成的,而且也很简单。

当我们定义onetoonefield时,Django给被引用类添加了一个隐藏的属性,称为反向关联属性,它为引用类名的小写。

1
2
a1=author.objects.get(id=1)
print(a1.name,'的妻子是',a1.oto.name)

image-20220430151550679

一对多

一个班级有多个学生,一个年级有多个班级,这样的关系就是一对多。

一对多需要明确出具体的角色,在“多”表上创建外键关联“一”表。

ORM使用ForeignKey定义一对多关系。

1
[外键名]=models.ForeignKey([一表类名],on_delete)

其实和一对一关系的创建时基本相同的。

接下来是数据的插入:

  1. 先插入一表的数据。
  2. 再插入多表的数据,同样,可以使用属性名+对象定义外键,也可以使用字段+值。

最后是查询:

  1. 正向查询:多表查一表是正向查询,多表.外键.一表属性即可。
  2. 反向查询:这是和一对一区别比较大的一点。依然是使用关联属性,但是这关联属性的名字是多类名_set,它相当于是一个对象管理器,后面可以直接使用all()等方法。

多对多

多对多表达对象之间多对多复杂关系,如每个人都有多个学校,每个学校有多个学生。

很显然,现在外键放哪个表都不太行(都会有数据的重复)。Django的做法是创建一个中间表,用于关联着两个表——这样重复率就降到最低了(只重复了两个表的主键)。

ORM实现多对多关系非常方便,直接再任意一个类里面添加一个多对多属性:

1
models.ManyToManyField([另一个类的名字])

即可自动生成那个中间表。

中间表名字由组和了两个关联表,只有三个字段,自己的主键,两个外键。

插入数据时,两个多对多表不用谁先,任选一个就行。插入完毕后再在中间表上插入多对多关系。

但是查询不需要依赖中间表,很方便:

  1. 正向查询(即有manytomanyfield属性查另外一方):同上、
  2. 反向查询:同一对多中多表的反向查询