Django知识点笔记(2)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: Django知识点笔记(2)

五、模型层(ORM

Django中内嵌了ORM框架,不需要直接编写SQL语句进行数据库操作,而是通过定义模型类,操作模型类来完成对数据库中表的增删改查和创建等操作。当然,其内部是通过pymysql完成的,发送一些mysql语句。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

O是object,也就类对象的意思。


R是relation,翻译成中文是关系,也就是关系数据库中数据表的意思。


M是mapping,是映射的意思。


ORM的优点


数据模型类都在一个地方定义,更容易更新和维护,也利于重用代码。


ORM 有现成的工具,很多功能都可以自动完成,比如数据消除、预处理、事务等等。


它迫使你使用 MVC 架构,ORM 就是天然的 Model,最终使代码更清晰。


基于 ORM 的业务代码比较简单,代码量少,语义性好,容易理解。


新手对于复杂业务容易写出性能不佳的 SQL,有了ORM不必编写复杂的SQL语句, 只需要通过操作模型对象即可同步修改数据表中的数据.


开发中应用ORM将来如果要切换数据库.只需要切换ORM底层对接数据库的驱动【修改配置文件的连接地址即可】


ORM 也有缺点


ORM 库不是轻量级工具,需要花很多精力学习和设置,甚至不同的框架,会存在不同操作的ORM。


对于复杂的业务查询,ORM表达起来比原生的SQL要更加困难和复杂。


ORM操作数据库的性能要比使用原生的SQL差。


ORM 抽象掉了数据库层,开发者无法了解底层的数据库操作,也无法定制一些特殊的 SQL。【自己使用pymysql另外操作即可,用了ORM并不表示当前项目不能使用别的数据库操作工具了。】


我们可以通过以下步骤来使用django的数据库操作


1. 配置数据库连接信息

2. 在models.py中定义模型类

3. 生成数据库迁移文件并执行迁文件[注意:数据迁移是一个独立的功能,这个功能在其他web框架未必和ORM一块的]

4. 通过模型类对象提供的方法或属性完成数据表的增删改查操作

配置数据库链接

在settings.py中保存了数据库的连接配置信息,Django默认初始配置使用sqlite数据库。

  1. 使用MySQL数据库首先需要安装驱动程序

pip install PyMySQL

  1. 在Django的工程同名子目录的__init__.py文件中添加如下语句
from pymysql import install_as_MySQLdb
install_as_MySQLdb() # 让pymysql以MySQLDB的运行模式和Django的ORM对接运行
  1. 作用是让Django的ORM能以mysqldb的方式来调用PyMySQL。
  2. 修改DATABASES配置信息
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': '127.0.0.1',  # 数据库主机
        'PORT': 3306,  # 数据库端口
        'USER': 'root',  # 数据库用户名
        'PASSWORD': '123',  # 数据库用户密码
        'NAME': 'student'  # 数据库名字
    }
}

在MySQL中创建数据库

create database student; # mysql8.0默认就是utf8mb4;
create database student default charset=utf8mb4; # mysql8.0之前的版本
  1. 注意3: 如果想打印orm转换过程中的sql,需要在settings中进行如下配置:
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}  

定义模型类

定义模型类

  • 模型类被定义在"子应用/models.py"文件中。
  • 模型类必须直接或者间接继承自django.db.models.Model类。

接下来以学生管理为例进行演示。[系统大概3-4表,学生信息,课程信息,老师信息]

在models.py 文件中定义模型类。

from django.db import models
from datetime import datetime
# 模型类必须要直接或者间接继承于 models.Model
class BaseModel(models.Model):
  """公共模型[公共方法和公共字段]"""
  # created_time = models.IntegerField(default=0, verbose_name="创建时间")
  created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
  # auto_now_add 当数据添加时设置当前时间为默认值
  # auto_now= 当数据添加/更新时, 设置当前时间为默认值
  updated_time = models.DateTimeField(auto_now=True)
  class Meta(object):
    abstract = True # 设置当前模型为抽象模型, 当系统运行时, 不会认为这是一个数据表对应的模型.
class Student(BaseModel):
  """Student模型类"""
  #1. 字段[数据库表字段对应]
  SEX_CHOICES = (
    (0,"女"),
    (1,"男"),
    (2,"保密"),
  )
  # 字段名 = models.数据类型(约束选项1,约束选项2, verbose_name="注释")
  # SQL: id bigint primary_key auto_increment not null comment="主键",
    # id = models.AutoField(primary_key=True, null=False, verbose_name="主键") # django会自动在创建数据表的时候生成id主键/还设置了一个调用别名 pk
    # SQL: name varchar(20) not null comment="姓名"
    # SQL: key(name),
    name = models.CharField(max_length=20, db_index=True, verbose_name="姓名" )
    # SQL: age smallint not null comment="年龄"
    age = models.SmallIntegerField(verbose_name="年龄")
    # SQL: sex tinyint not null comment="性别"
    # sex = models.BooleanField(verbose_name="性别")
    sex = models.SmallIntegerField(choices=SEX_CHOICES, default=2)
    # SQL: class varchar(5) not null comment="班级"
    # SQL: key(class)
    classmate = models.CharField(db_column="class", max_length=5, db_index=True, verbose_name="班级")
    # SQL: description longtext default "" not null comment="个性签名"
    description = models.TextField(default="", verbose_name="个性签名")
  #2. 数据表结构信息
  class Meta:
    db_table = 'tb_student'  # 指明数据库表名,如果没有指定表明,则默认为子应用目录名_模型名称,例如: users_student
    verbose_name = '学生信息表'  # 在admin站点中显示的名称
    verbose_name_plural = verbose_name  # 显示的复数名称
        # 除此之外还可以设置联合索引 联合唯一索引等(注意可能会与unique=True发生冲突)
  #3. 自定义数据库操作方法
  def __str__(self):
    """定义每个数据对象的显示信息"""
    return "<User %s>" % self.name

真正的项目,一些登陆注册等的功能,对于用户表,后面会讲到用户认证组件Auth模块,我们一般在models.py里自定义一个class User()继承自带的Auth表。


(1) 数据库表名


模型类如果未指明表名db_table,Django默认以 小写app应用名_小写模型类名 为数据库表名。


可通过db_table 指明数据库表名。


(2) 关于主键


django会为表创建自动增长的主键列,每个模型只能有一个主键列。


如果使用选项设置某个字段的约束属性为主键列(primary_key)后,django不会再创建自动增长的主键列。

class Student(models.Model):
    # django会自动在创建数据表的时候生成id主键/还设置了一个调用别名 pk
    id = models.AutoField(primary_key=True, null=False, verbose_name="主键") # 设置主键

默认创建的主键列属性为id,可以使用pk代替,pk全拼为primary key。


(3) 属性命名限制


不能是python的保留关键字。


不允许使用连续的2个下划线,这是由django的查询方式决定的。__ 是关键字来的,不能使用!!!


定义属性时需要指定字段类型,通过字段类型的参数指定选项,语法如下:

属性名 = models.字段类型(约束选项, verbose_name="注释")

(4)字段类型

image.png

注:手机号不要用 IntegerField 超过了范围。

(5)约束选项

image.png

verbose_name admin里显示的名字



注意:null是数据库范畴的概念,blank是表单验证范畴的(同理default也是)。更改一些class属性参数的时候可以不用重新数据库迁移 ,而修改数据库结构的需要重新迁移。


数据可范畴出错会发生报错,用户输入范畴不符合规定会提示不能为空,或者自增、默认值。


后加东西要是设置默认值


(6) 外键


在设置外键时,需要通过on_delete选项指明主表删除数据时,对于外键引用表数据如何处理,在django.db.models中包含了可选常量:


CASCADE 级联,删除主表数据时连通一起删除外键表中数据


PROTECT 保护,通过抛出ProtectedError异常,来阻止删除主表中被外键应用的数据


SET_NULL 设置为NULL,仅在该字段null=True允许为null时可用


SET_DEFAULT 设置为默认值,仅在该字段设置了默认值时可用


SET() 设置为特定值或者调用特定方法,例如:

from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
def get_sentinel_user():
    return get_user_model().objects.get_or_create(username='deleted')[0]
class UserModel(models.Model):
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET(get_sentinel_user),
    )

DO_NOTHING 不做任何操作,如果数据库前置指明级联性,此选项会抛出IntegrityError异常

商品分类表

id category
1 蔬菜
2 电脑

商品信息表

id goods_name cid
1 冬瓜 1
2 华为笔记本A1 2
3 茄子 1

当模型字段的on_delete=CASCADE, 删除蔬菜(id=1),则在外键cid=1的商品id1和3就被删除。


当模型字段的on_delete=PROTECT,删除蔬菜,mysql自动检查商品信息表,有没有cid=1的记录,有则提示必须先移除掉商品信息表中,id=1的所有记录以后才能删除蔬菜。


当模型字段的on_delete=SET_NULL,删除蔬菜以后,对应商品信息表,cid=1的数据的cid全部被改成cid=null


当模型字段的on_delete=SET_DEFAULT,删除蔬菜以后,对应商品信息表,cid=1的数据记录的cid被被设置默认值。

数据库迁移

将模型类定义表架构的代码转换成SQL同步到数据库中,这个过程就是数据迁移。django中的数据迁移,就是一个类,这个类提供了一系列的终端命令,帮我们完成数据迁移的工作。


(1)生成迁移文件


所谓的迁移文件, 是类似模型类的迁移类,主要是描述了数据表结构的类文件.


python manage.py makemigrations

(2)同步到数据库中


python manage.py migrate

补充:在django内部提供了一系列的功能,这些功能也会使用到数据库,所以在项目搭建以后第一次数据迁移的时候,会看到django项目中其他的数据表被创建了。其中就有一个django内置的admin站点管理。(这个后面会写)


# admin站点默认是开启状态的,我们可以通过http://127.0.0.1:8000/admin

# 这个站点必须有个管理员账号登录,所以我们可以在第一次数据迁移,有了数据表以后,就可以通过以下终端命令来创建一个超级管理员账号。

python manage.py createsuperuser

后续:稍微补充下admin知识

(后台超级管理员看到的表需要注册,且一些自增字段不会显示)

这里我注册用户名root 密码root123 进入admin

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

这是因为我之前用auth组件设置过一个普通用户,浏览器中还保存着sessionid,这个普通用户显然不是管理员,无权登录。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

我们进入自己的root账户

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

在admin.py里注册表

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

记得在models.py里加上def__str__ 这样后台也就能显示相应的中文表示的对象了。

Django自带数据库可视化工具

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

数据库基本操作

脚本orm操作数据库

注意导入的顺序  不然会报错

(父级models类里有__str__和__repr__方法,也可以把__repr__重写)

import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'homework06.settings')
import django
django.setup()
from students.models import *
ret = Course.objects.all()# <QuerySet [<Course: 课程1>, <Course: 课程2>, <Course: 课程3>]>
ret = Course.objects.get(pk=1) # 课程1
ret = Course.objects.filter(pk=1)# <QuerySet [<Course: 课程1>]> 

(1)save方法

通过创建模型类对象,执行对象的save()方法保存到数据库中。

student = Student(
    name="刘德华",
    age=17,
    sex=True,
    clasmate=301,
    description="一手忘情水"
)
student.save()
print(student.id) # 判断是否新增有ID

(2)create方法

通过模型类.objects.create()保存,返回生成的模型类对象。

student = Student.objects.create(
    name="赵本山",
    age=50,
    sex=True,
    class_number=301,
    description="一段小品"
)
print(student.id)

1、基础查询

ORM中针对查询结果的限制,提供了一个查询集[QuerySet].这个QuerySet,是ORM中针对查询结果进行保存数据的一个类型,我们可以通过了解这个QuerySet进行使用,达到查询优化,或者限制查询结果数量的作用。

后面会详细讲QuerySet,orm进阶及优化搜索,到这里要先了解一些基本操作。

def index(request):
    print(Students.objects, type(Students.objects))#app01.Students.objects <class 'django.db.models.manager.Manager'>
    return HttpResponse('ok')

(1)all()

查询所有对象,返回queryset对象。查询集,也称查询结果集、QuerySet,表示从数据库中获取的对象集合。

students = Student.objects.all()
print("students:",students)

(2)filter()

筛选条件相匹配的多个对象,返回queryset对象。

# 查询所有的女生
students = Student.objects.filter(sex=0)
print(students)

(3)get()

返回与所给筛选条件相匹配的对象,返回结果有且只有一个, 如果符合筛选条件的对象超过一个或者没有都会抛出错误。

student = Student.objects.get(pk=1)
print(student)
print(student.description)
get使用过程中的注意点:get是根据条件返回多个结果或者没有结果,都会报错
try:
    student = Student.objects.get(name="刘德华")
    print(student)
    print(student.description)
except Student.MultipleObjectsReturned:
    print("查询得到多个结果!")
except Student.DoesNotExist:
    print("查询结果不存在!")

(4)first()、last()

分别为查询集的第一条记录和最后一条记录

# 没有结果返回none,如果有多个结果,则返回模型对象
students = Student.objects.all()
# print(students.name)
print(students[0].name)
stu01 = Student.objects.first()
stu02 = Student.objects.last()
print(stu01.name)
print(stu02.name)

(5)exclude()

筛选条件不匹配的对象,返回queryset对象。

# 查询张三以外的所有的学生
students = Student.objects.exclude(name="张三")
print(students)

(6)order_by()

对查询结果排序

# order_by("字段")  # 按指定字段正序显示,相当于 asc  从小到大
# order_by("-字段") # 按字段倒序排列,相当于 desc 从大到小
# order_by("第一排序","第二排序",...)
# 查询所有的男学生按年龄从高到低展示
# students = Student.objects.all().order_by("-age","-id")
students = Student.objects.filter(sex=1).order_by("-age", "-id")
print(students)

(7)count()

查询集中对象的个数

结果其实与len(Student.objects.all())一样

# 查询所有男生的个数
count = Student.objects.filter(sex=1).count()
print(count)

(8)exists()

判断查询集中是否有数据,如果有则返回True,没有则返回False

# 查询Student表中是否存在学生
print(Student.objects.exists())

(9)values()、values_list()

value()把结果集中的模型对象转换成字典,并可以设置转换的字段列表,达到减少内存损耗,提高性能


values_list(): 把结果集中的模型对象转换成元组(即没有键只有值),并可以设置转换的字段列表,达到减少内存损耗,提高性能


二者都返回QuerySet 只是里面的类型不同

# values 把查询结果中模型对象转换成字典
student_list = Student.objects.filter(classmate="301")
student_list = student_list.order_by("-age")
student_list = student_list.filter(sex=1)
ret1 = student_list.values() # 默认把所有字段全部转换并返回
ret2 = student_list.values("id","name","age") # 可以通过参数设置要转换的字段并返回
ret3 = student_list.values_list() # 默认把所有字段全部转换并返回
ret4 = student_list.values_list("id","name","age") # 可以通过参数设置要转换的字段并返回
print(ret4)
return JsonResponse({},safe=False)
def index(request):
    print(Students.objects.values('name'))
    return HttpResponse('ok')
#<QuerySet [{'name': '张三'}, {'name': '李四'}, {'name': '王五'}]>

(10)distinct()

从返回结果中剔除重复纪录。返回queryset。

# 查询所有学生出现过的年龄
print(Student.objects.values("age").distinct())

(11)reverse()

对已经排序了的queryset进行反转 相当于order_by(’加个负号‘)

students = Student.objects.all().order_by("-age").reverse()

小总结:


返回QuerySet:

       all()、filter()、exclude()、order_by()、values()、values_list()、reverse、distinct()


直接 Students.objects相当于.all() 可以在后面根对QuerySet的操作


返回对象:

       get()、first()、last()


返回数字:

       count()


返回布尔:

       exist()  

2、模糊查询

(1)模糊查询之contains

说明:如果要包含%无需转义,直接写即可。

例:查询姓名包含'华'的学生。


Student.objects.filter(name__contains='华')

(2)模糊查询之startswith、endswith


例:查询姓名以'文'结尾的学生


Student.objects.filter(name__endswith='文')

以上运算符都区分大小写,在这些运算符前加上i表示不区分大小写,如iexact、icontains、istartswith、iendswith.

(3)模糊查询之isnull

例:查询个性签名不为空的学生。

# 修改Student模型description属性允许设置为null,然后数据迁移
description = models.TextField(default=None, null=True, verbose_name="个性签名")
# 添加测试数据
NSERT INTO student.db_student (name, age, sex, class, description, created_time, updated_time) VALUES ('刘德华', 17, 1, '407', null, '2020-11-20 10:00:00.000000', '2020-11-20 10:00:00.000000');
# 代码操作
tudent_list = Student.objects.filter(description__isnull=True)

4)模糊查询之in


例:查询编号为1或3或5的学生


Student.objects.filter(id__in=[1, 3, 5])

(5)模糊查询之比较查询


gt 大于 (greater then)


gte 大于等于 (greater then equal)


lt 小于 (less then)


lte 小于等于 (less then equal)


例:查询编号大于3的学生


Student.objects.filter(id__gt=3)

(6)模糊查询之日期查询


year、month、day、week_day、hour、minute、second:对日期时间类型的属性进行运算。


例:查询2010年被添加到数据中的学生。


Student.objects.filter(born_date__year=1980)

例:查询2016年6月20日后添加的学生信息。

from django.utils import timezone as datetime
student_list = Student.objects.filter(created_time__gte=datetime.datetime(2016,6,20),created_time__lt=datetime.datetime(2016,6,21)).all()
print(student_list)

3、进阶查询

(1) F查询

之前的查询都是对象的属性与常量值比较,两个属性怎么比较呢? 答:使用F对象,被定义在django.db.models中。

语法如下:

"""F对象:2个字段的值比较"""
# 获取从添加数据以后被改动过数据的学生
from django.db.models import F
# SQL: select * from db_student where created_time=updated_time;
student_list = Student.objects.exclude(created_time=F("updated_time"))
print(student_list)

(2) Q查询

多个过滤器逐个调用表示逻辑与关系,同sql语句中where部分的and关键字。

例:查询年龄大于20,并且编号小于30的学生。

Student.objects.filter(age__gt=20,id__lt=30)
#或
Student.filter(age__gt=20).filter(id__lt=30)


如果需要实现逻辑或or的查询,需要使用Q()对象结合|运算符,Q对象被义在django.db.models中。

语法如下:

Q(属性名__运算符=值)
Q(属性名__运算符=值) | Q(属性名__运算符=值)

例:查询年龄小于19或者大于20的学生,使用Q对象如下。

from django.db.models import Q
student_list = Student.objects.filter( Q(age__lt=19) | Q(age__gt=20) ).all()

Q对象可以使用&、|连接,&表示逻辑与,|表示逻辑或**

例:查询年龄大于20,或编号小于30的学生,只能使用Q对象实现

Student.objects.filter(Q(age__gt=20) | Q(pk__lt=30))

Q对象左边可以使用~操作符,表示非not。但是工作中,我们只会使用Q对象进行或者的操作,只有多种嵌套复杂的查询条件才会使用&和~进行与和非得操作

例:查询编号不等于30的学生。

Student.objects.filter(~Q(pk=30))

(3)聚合查询

使用aggregate()过滤器调用聚合函数。聚合函数包括:Avg 平均,Count 数量,Max 最大,Min 最小,Sum 求和,被定义在django.db.models中。

例:查询学生的平均年龄。

from django.db.models import Sum,Count,Avg,Max,Min
Student.objects.aggregate(Avg('age'))

注意:aggregate的返回值是一个字典类型,格式如下:

 {'属性名__聚合类小写':值} 如{'age__avg': 13.2}

使用count时一般不使用aggregate()过滤器。

例:查询学生总数。

Student.objects.count() # count函数的返回值是一个数字。

(4)分组查询

annotate() 返回值依然是 queryset对象,增加了分组统计后的键值对

QuerySet对象.annotate()
# annotate() 进行分组统计,按前面select 的字段进行 group by
模型对象.objects.values("id").annotate(course=Count('course__sid')).values('id','course')
# 查询指定模型, 按id分组 , 将course下的sid字段计数,返回结果是 name字段 和 course计数结果 
# SQL原生语句中分组之后可以使用having过滤,在django中并没有提供having对应的方法,但是可以使用filter对分组结果进行过滤
# 所以filter在annotate之前,表示where,在annotate之后代表having
# 同理,values在annotate之前,代表分组的字段,在annotate之后代表数据查询结果返回的字段

(5)原生查询

执行原生SQL语句,也可以直接跳过模型,才通用原生pymysql.

 ret = Student.objects.raw("SELECT id,name,age FROM db_student")  # student 可以是任意一个模型
 # 这样执行获取的结果无法通过QuerySet进行操作读取,只能循环提取
 print(ret,type(ret))
 for item in ret:
    print(item,type(item))
相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2月前
|
前端开发 数据库 数据安全/隐私保护
|
6月前
|
SQL 前端开发 JavaScript
Django零基础-快速了解基本框架笔记-附案例
Django零基础-快速了解基本框架笔记-附案例
72 0
|
6月前
|
存储 安全 数据库
关于“Python”Django 管理网站的核心知识点整理大全52
关于“Python”Django 管理网站的核心知识点整理大全52
40 0
|
6月前
|
JSON 前端开发 JavaScript
前端知识笔记(三十七)———Django与Ajax
前端知识笔记(三十七)———Django与Ajax
51 0
|
6月前
|
JSON 前端开发 JavaScript
前端知识笔记(二)———Django与Ajax
前端知识笔记(二)———Django与Ajax
60 0
|
存储 缓存 中间件
Django相关知识点回顾(二)
Django相关知识点回顾(二)
|
JSON 关系型数据库 数据库
|
Python
Django知识点-URL路由 name=
Django知识点-URL路由 name=
71 0
|
存储 Web App开发 前端开发
Django知识点笔记(4)
Django知识点笔记(4)
Django知识点笔记(4)
|
SQL XML 缓存
Django知识点笔记(3)
Django知识点笔记(3)
Django知识点笔记(3)
下一篇
无影云桌面