Django框架之数据库操作

一、前言

Django框架本身自带数据库操作功能,即ORM框架,其实我们是可以使用pymysql模块执行原生的sql语句去操作数据库,但是缺点如下:

  • sql语句一般比较复杂,并且维护困难;
  • sql语句的安全性无法得到保障,可能会有sql注入的风险;
  • 数据库的创建、数据表的生成、数据备份以及数据库的迁移非常麻烦;
  • sql语句性能无法保障;

ORM:Object Relational Mapping(关系对象映射)

ORM优势:

根据对接的数据库引擎翻译成对应的sql语句,所以我们不用关注使用的是MySQL还是Oracle等,我们只需要修改数据库配置即可;

二、数据库配置

指定在全局配置文件settings.py中的DATABASES字典里配置需要连接的数据库信息

DATABASES = {
    # 指定数据库的别名/标签
    # 指定的是django默认使用的数据库
    'default': {
        # 指定当前使用的数据库引擎
        # django.db.backends.mysql、oracle、sqlite3
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'django_test',  # 数据库
        'USER': 'root',   		# 用户名
        'PASSWORD': '123456789', # 密码
        'PORT': 3306,   # 端口
        'HOST': '127.0.0.1' # 主机ip
    }
}

然后在settings.py所在目录下的__init__py文件中设置Django默认连接MySQL的方式

import pymysql
pymysql.install_as_MySQLdb()

三、数据表创建

1、单表创建

  • 一般在子应用models.py中定义模型类(相当于数据库中的一张表);

  • 必须继承Model或者Model的子类;

  • 在模型类中定义类属性(必须得为Field子类)相当于数据表中字段;

  • 字段参数,常见字段如下:

    字段描述
    AutoField主键字段:primary_key=True
    CharField字符串,对应的是MySQL的varchar数据类型
    IntegerField/BigIntergerField整型:int
    FloatField浮点
    DecimalField精确浮点
    EmailField邮件,底层仍然是VarChar(254)
    DateField/DateTimeField日期
    BooleanField布尔值类型
    TextField长文本
    FileField文件

我们来看实际怎么创建数据表:

1)先在子应用models.py中编写模型类

from django.db import models


class People(models.Model):
    # CharField等价于varchar
    # 属性名就是字段名,
    name = models.CharField(max_length=50)
    # IntegerField等价于int
    age = models.IntegerField()
    # BooleanField等价于bool
    gender = models.BooleanField()

class Projects(models.Model):
    # 在一个模型类中仅仅只能为一个字段指定primary_key=True
    # 一旦在模型类中的某个字段上指定了primary_key=True,那么ORM框架就不会自动创建名称为id的主键,否则会自动生成AutoField的id
    id = models.IntegerField(primary_key=True, verbose_name='项目主键', help_text='项目主键')
    
    # a.CharField类型必须指定max_length参数(改字段的最大字节数),其他是可选项:verbose_name和help_text是字段释义
    # b.如果需要给一个字段添加唯一约束,unique=True(默认为False)
    name = models.CharField(max_length=20, verbose_name='项目名称', help_text='项目名称', unique=True)
    leader = models.CharField(max_length=10, verbose_name='项目负责人', help_text='项目负责人')

    # c.使用default指定默认值(如果指定默认值后,在创建记录时,改字段传递,会使用默认值)
    is_execute = models.BooleanField(verbose_name='是否启动项目', help_text='是否启动项目', default=True)

    # d.null=True指定前端创建数据时,可以指定该字段为null,默认为null=False,DRF进行反序列化器输入时才有效
    #   null :针对数据库,如果 null=True, 表示数据库的该字段可以为空,即在Null字段显示为YES。
    # e.blank=True指定前端创建数据时,可以指定该字段为空字符串,默认为blank=False,DRF进行反序列化器输入时才有效
    #   blank :针对表单,如果 blank=True,表示你的表单填写该字段时可以不填,但是对数据库来说,没有任何影响
    desc = models.TextField(verbose_name='项目描述信息', help_text='项目描述信息',
                            null=True, blank=True, default='')

    # f.在DateTimeField、DateField等字段中,指定auto_now_add=True,在创建一条记录时,会自动将创建记录时的时间作为该字段的值,后续在更新数据时,就不再修改
    # g.在DateTimeField、DateField等字段中,指定auto_now=True,在更新一条记录时,会自动将更新记录时的时间作为该字段的值
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间', help_text='创建时间')
    update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间', help_text='更新时间')

    # h.可以在任意一个模型类中创建Meta内部类,用于修改数据库的元数据信息
    class Meta:
        # i.db_table指定创建的数据表名称
        db_table = 'tb_projects'
        # 为当前数据表设置中文描述信息
        verbose_name = '项目表'
        verbose_name_plural = '项目表'
        ordering = ['id']

    def __str__(self):
        return f"Projects({self.name})"

2)命令行内执行命令


#命令行执行
#根据app下的migrations目录中的记录,检测当前model层代码是否发生变化?
python manage.py makemigrations 子应用名(如果不指定子应用名,会把所有子应用生成迁移脚本)
#把orm代码转换成sql语句去数据库执行
python manage.py migrate 子应用名

然后可以用可视化工具连接数据库,即可看到数据库生成该表。

2、关联表创建

表与表之间的关系可分为以下三种:

  • 一对一: 一个人对应一个身份证号码,数据字段设置 unique。
    • 可以在任何一个模型类使用OneToOneField
  • 一对多: 一个家庭有多个人,一般通过外键来实现。
    • 需要在“多”的那个模型中使用ForeignKey
    • ForeignKey第一个参数为必传参数,指定需要关联的父表模型类(有2种方式):
      • 方式一:直接使用父表模型类的引用
      • 方式二:可以使用’子应用名称.父表模型类名’(推荐)
    • 使用on_delete指定级联删除策略:
      • CASCADE: 当父表数据删除时,相对应的从表数据会被自动删除
      • SET_NULL:当父表数据删除时,相对应的从表数据会被自动设置为null值
      • PROTECT:当父表数据删除时,如果有相对应的从表数据会抛出异常
      • SET_DEFAULT: 当父表数据删除时,相对应的从表数据会被自动设置为默认值,还需要额外指定default=True
  • 多对多: 一个学生有多门课程,一个课程有很多学生,一般通过第三个表来实现关联。
    • 可以在任何一个模型类使用ManyToManyField

下面来看一个示例:

from django.db import models

class Interfaces(models.Model):
    id = models.AutoField(primary_key=True, verbose_name='id主键', help_text='id主键')
    name = models.CharField(verbose_name='接口名称', help_text='接口名称', max_length=20, unique=True)
    tester = models.CharField(verbose_name='测试人员', help_text='测试人员', max_length=10)

	# 第一个参数:使用'子应用名称.父表模型类名'
    # CASCADE: 当父表数据删除时,相对应的从表数据会被自动删除
    projects = models.ForeignKey('projects.Projects', on_delete=models.CASCADE,
                                 verbose_name='所属项目', help_text='所属项目')
                                 
    class Meta:
        db_table = 'tb_interfaces'
        verbose_name = '接口表'
        verbose_name_plural = '接口表'
        ordering = ['id']

总结:以上就完成了tb_projects和tb_interfaces表就完成了关联,tb_interfaces表中的project_id是tb_projects的主键,所以tb_projects是父表,tb_interfaces是从表,后面的例子还会用到这2张表;

四、ORM增删改查

1、新增数据(C)

(1)创建单表数据

方式一:

    obj = Projects(name='xxx测试项目', leader='小李')
    obj.save()
  • 直接使用模型类(字段名1=值1, 字段名2=值2, …),来创建模型类实例
  • 必须模型实例调用save()方法,才会执行sql语句

方式二:

	obj = Projects.objects.create(name='xxx22', leader='小王')
  • 使用模型类.objects返回manager对象
  • 使用manager对象.create(字段名1=值1, 字段名2=值2, …),来创建模型类实例
  • 无需使用模型实例调用save()方法,会自动执行sql语句

(2)创建从表数据

创建从表数据的关键是:外键对应的父表如何传递?主要有以下两种方式:

方式一:

	# 主表
	project_obj = Projects.objects.get(name='测试项目')
	interface_obj = Interfaces.objects.create(name='测试项目-登录接口', tester='珍惜', projects=project_obj)
  • 先获取父表模型对象;
  • 将获取的父表模型对象以外键字段名作为参数来传递,这样就会把name为测试项目的id传递过去;

方式二:

	project_obj = Projects.objects.get(name='在线图书项目')
	interface_obj = Interfaces.objects.create(name='在线图书项目-注册接口', tester='珍惜', projects_id=project_obj.id)
  • 先获取父表模型对象,进而获取父表数据的id值;
  • 将父表数据的主键id值以外键名_id作为参数来传递;

2、读取数据(R)

(1)读取多条数据

  • 使用模型类.objects.all(),会将当前模型类对应的数据表中的所有数据读取出来;
  • 模型类.objects.all(),返回QuerySet对象(查询集对象);
  • QuerySet对象,类似于列表,具有惰性查询的特性(在‘用’数据时,才会执行sql语句);
	qs = Projects.objects.all()

(2)读取单条数据

方式一:使用模型类.objects.get(条件1=值1)

	obj = Projects.objects.get(id=1)
  • 如果使用指定条件查询的记录数量为0,会抛出异常;
  • 如果使用指定条件查询的记录数量超过1,也会抛出异常;
  • 最好使用具有唯一约束的条件去查询;
  • 如果使用指定条件查询的记录数量为1,会返回这条记录对应的模型实例对象,可以使用模型对象.字段名去获取相应的字段值

方式二:使用模型类.objects.filter(条件1=值1)

	qs = Projects.objects.filter(id=1)
	# ORM框架中,会给每一个模型类中的主键设置一个别名(pk),所以当id是主键时上下两个语句是等价的
	# qs = Projects.objects.filter(pk=1)
  • 如果使用指定条件查询的记录数量为0,会返回空的QuerySet对象;
  • 如果使用指定条件查询的记录数量超过1,将符合条件的模型对象包裹到QuerySet对象中返回;
  • QuerySet对象,类似于列表,有如下特性:
    • 支持通过数值(正整数)索引取值;
    • 支持切片操作(正整数);
    • 获取第一个模型对象:QuerySet对象.first();
    • 获取最后一个模型对象:QuerySet对象.last();
    • 获取长度:len(QuerySet对象)、QuerySet对象.count();
    • 判断查询集是否为空:QuerySet对象.exists();
    • 支持迭代操作(for循环,每次循环返回模型对象);

单独说下常用的filter方法

  • 1)字段名__查询类型=具体值
  • 2)字段名__exact=具体值,缩写形式为:字段名=具体值
  • 3)字段名__gt:大于、字段名__gte:大于等于
  • 4)字段名__lt:小于、字段名_lte:小于等于
  • 5)contains:包含
  • 6)startswith:以xxx开头
  • 7)endswith:以xxx结尾
  • 8)isnull:是否为null
  • 9)一般在查询类型前添加“i”,代表忽略大小写

另外,还有个exclude方法:反向查询,filter方法支持的所有查询类型,都支持;

(3)读取关联表数据

获取父表数据

获取接口表中接口id为1的项目数据

	interface_obj = Interfaces.objects.get(id=1)
	# 返回父表模型对象
	interface_obj.projects

返回结果是projects模型类
在这里插入图片描述

获取从表数据
获取项目id为1的所有接口数据

	project_obj = Projects.objects.get(id=1)
	project_obj.interfaces_set.all()
  • 默认可以通过从表模型类名小写_set,返回manager对象,可以进一步使用filter进行过滤
  • 如果在从表模型类的外键字段指定了related_name参数,那么会使用related_name指定参数替换interfaces_set作为名称;

带关联表参数查询数据

如果想要通过父表参数来获取从表数据、或者通过从表参数获取父表数据,可以使用关联查询语句:

关联字段名称__关联模型类中的字段名称__查询类型

	Interfaces.objects.filter(projects__name__contains='xxx')
	Projects.objects.filter(interfaces__name__contains='登录')

逻辑关系

与关系:

#  方式一:在同一个filter方法内部,添加多个关键字参数,那么每个条件为“与”的关系
qs = Projects.objects.filter(name__contains='2', leader='keyou')

# 方式二:可以多次调用filter方法,那么filter方法的条件为“与”的关系  --- QuerySet链式调用特性
qs = Projects.objects.filter(name__contains='2').filter(leader='keyou')

或关系:

# 可以使用Q查询,实现逻辑关系,多个Q对象之间如果使用“|”,那么为“或”关系
qs = Projects.objects.filter(Q(name__contains='2') | Q(leader='多喝热水'))

排序

# 可以使用QuerySet对象(manager对象).order_by('字段名1', '字段名2', '-字段名3')
# 默认为ASC升序,可以在字段名称前添加“-”,那么为DESC降序
Projects.objects.filter(Q(name__contains='2') | Q(leader='多喝热水')).order_by('-name', 'leader')

3、更新数据(U)

# 方式一:一条数据
project_obj = Projects.objects.get(id=1)
project_obj.name = '在线图书项目(一期)'
project_obj.leader = '不语'
# 必须调用save方法才会执行sql语句,并且默认进行完整更新
# 可以在save方法中设置update_fields参数(序列类型),指定需要更新的字段名称(字符串)
project_obj.save()

# 方式二:多条数据
# Projects.objects.filter: QuerySet
# 可以在QuerySet对象.update(字段名称='字段值'),返回修改成功的值,无需调用save方法
qs = Projects.objects.filter(name__contains='2').update(leader='珍惜')

4、删除数据(D)

# 方式一:一条数据
project_obj = Projects.objects.get(id=1)
project_obj.delete()

# 方式二:多条数据
obj = Projects.objects.filter(name__contains='2').delete()

5、聚合运算

# a.可以使用QuerySet对象.aggregate(聚合函数('字段名'))方法,返回字典数据
# b.返回的字典数据中key为字段名__聚合函数名小写
# c.可以使用关键字参数形式,那么返回的字典数据中key为关键字参数名
qs = Projects.objects.filter(name__contains='2').aggregate(Count('id'))

6、分组查询

# a.可以使用Manager对象.values('父表主键id').annotate(聚合函数('从表模型类名小写'))
# b.会自动连接两张表,然后使用外键字段作为分组条件
Projects.objects.values('id').annotate(Count('interfaces'))

7、QuerySet对象特性

查询集QuerySet对象有什么特性?

  • 支持链式调用:可以在查询集上多次调用filter、exclude方法
  • 惰性查询:仅仅在使用数据时才会执行sql语句,为了提升数据库读写性能;(会执行sql语句的场景:len()、.count()、通过索引取值、print、for)
  • 5
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

克里斯蒂亚诺·罗纳尔达

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值