第6天:数据库配置与模型

  •  数据库配置
  •  Django模型类定义
  •  字段类型与选项
  •  模型迁移
  •  Django交互环境
  •  增
  •  删
  •  改
  •  基本查询
  •  过滤查询
  •  F对象
  •  Q对象
  •  排序
  •  聚合
  •  关联查询
  •  查询集QuerySet
  •  自定义模型管理器

数据库配置

ORM Object relational mapping 对象关系映射,把类和数据库表对应,把对象和表记录对应,通过类和对象操作数据库表中的数据,而不需要编写SQL语句

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

pip install PyMySQL

2、在Django的工程同名子目录的__init__.py中添加如下语句,作用是让Django的ORM能以mysqldb的方式来调用PyMySQL

from pymysql import install_as_MySQLdb

install_as_MySQLdb()

3、编辑settings.py,修改DATABASE配置信息

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': '127.0.0.1',#主机 
        'PORT': 3306,       #端口
        'USER': 'root',     #数据库用户名
        'PASSWORD': 'root', #数据库用户密码
        'NAME': 'demo'   #数据名字
    }
}
settings.py

4、在MySQL中创建数据库

create database demo default charset=utf8;

Django模型类的定义

  • 模型类被定义到‘应用/models.py文件中’
  • 模型类必须继承Model类,位于包django.db.models中

创建应用book,在models.py文件中定义模型

from django.db import models


class Book(models.Model):
    title = models.CharField(max_length=20, verbose_name='名称')
    pub_date = models.DateField(verbose_name='发布日期')
    read_num = models.IntegerField(default=0, verbose_name='阅读量')
    comment_num = models.IntegerField(default=0, verbose_name='评论量')
    is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')

    class Meta:
        db_table = 'book'   #指明数据库表名
        verbose_name = '图书'  #在admin站点中显示的名称
        verbose_name_plural = verbose_name #显示复数的名称

    def __str__(self):
        """定义每个数据对象的显示信息"""
        return self.title


class Hero(models.Model):
    GENDER_CHOICES = (
        (0, 'male'),
        (1, 'female')
    )
    name = models.CharField(max_length=20, verbose_name='名称')
    gender = models.SmallIntegerField(choices=GENDER_CHOICES, default=0, verbose_name='性别')
    comment = models.CharField(max_length=200, null=True, verbose_name='描述信息')
    book = models.ForeignKey(Book, on_delete=models.CASCADE, verbose_name='图书') #CASCADE级联,删除主表数据时连同一起删除外键表中的数据
    is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')

    class Meta:
        db_table = 'hero'
        verbose_name = '英雄'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name
book.models

 关于主键

  • 主键:primary key,简写 pk
  • 不需要主动定义,django会自动生成自增长的主键,属性名叫 id,
  • 如果开发者自己定义了主键,则django不会再生成默认的主键

字段类型与选项

类型说明
AutoField 自动增长的IntegerField,通常不用指定,不指定时Django会自动创建名为id的自动增长属性
BooleanField 布尔字段,值为True或False
NullBooleanField 支持Null、True、False三种值
CharField 字符串,必须指定:max_length,表示最大字符个数
TextField 大文本字段,一般超过4000个字符时使用
IntegerField 整数
DecimalField 十进制浮点数,用python中的Decimal实例来表示
必须指定: max_digits总位数,decimal_places小数位数。 
FloatField 浮点数
DateField 日期 
1) 参数auto_now表示每次修改保存对象时,自动设置该字段为当前时间,用于保存"最后一次修改"时间,默认为False; 
2) 参数auto_now_add表示当对象第一次被创建时自动设置保存当前时间,用于保存"创建时间"时间,默认为值为False; 
3) 参数auto_now_addauto_now是相互排斥的,不能同时用到一个属性中
TimeField 时间,参数同DateField
DateTimeField 日期时间,参数同DateField
FileField 上传文件字段
ImageField 继承于FileField,对上传的内容进行校验,确保是有效的图片
选项默认值描述是否要迁移修改表结构
null False True表示表字段允许为空
unique False True表示表字段不能重复
db_column 属性名称 表字段名称
primary_key False True表示字段设置为了主键,一般作为AutoField的选项使用
default - 默认值
blank False 在django管理后台新增或编辑一条表数据时,该字段是否允许为空;
null是数据库范畴的概念,blank是表单验证范畴的
choices - 在django管理后台新增或编辑一条表数据时,该字段显示为下拉框,默认为编辑框
  • choices: 性别属性使用了choices选项后,在录入一条数据时,会以下拉框显示
  • blank:blank属性默认值为false, 表示录入一条数据时,当前字段必须填写,不能为空,否则js端js校验不通过,例如:下图的comment员工备注信息字段。

外键

  • ForeignKey: 一对多,将 关联属性 定义在多的一端中
  • ManyToManyField: 多对多,将 关联属性 定义任意一方中
  • OneToOneField: 一对一,将 关联属性 定义在任意一方中

模型迁移

定义完模型类以后,我们需要把模型生成到数据库里面去

python manage.py mikemigrations
python manage.py migrate

Django交互环境

在Django交互环境中,可以直接执行django项目代码,类似ipython交换环境

通过shell命令进入Django交换环境

insert into book(name,pub_date,read_num,comment_num,is_delete) values
('射雕英雄传','1980-5-1',12,34,0),
('天龙八部','1986-7-24',36,40,0),
('笑傲江湖','1995-12-24',28,18,0),
('雪山飞狐','1987-11-11',58,24,0);

insert into hero(name,gender,book_id,comment,is_delete) values
('郭靖',1,1,'降龙十八掌',0),
('黄蓉',0,1,'打狗棍法',0),
('黄药师',1,1,'弹指神通',0),
('欧阳锋',1,1,'蛤蟆功',0),
('梅超风',0,1,'九阴白骨爪',0),
('乔峰',1,2,'降龙十八掌',0),
('段誉',1,2,'六脉神剑',0),
('虚竹',1,2,'天山六阳掌',0),
('王语嫣',0,2,'神仙姐姐',0),
('令狐冲',1,3,'独孤九剑',0),
('任盈盈',0,3,'弹琴',0),
('岳不群',1,3,'华山剑法',0),
('东方不败',0,3,'葵花宝典',0),
('胡斐',1,4,'胡家刀法',0),
('苗若兰',0,4,'黄衣',0),
('程灵素',0,4,'医术',0),
('袁紫衣',0,4,'六合拳',0);
给数据库添加测试数据

现在我们可以在Django交互环境测试数据库操作,首先我们来对数据库进行增加操作,有两种方式

1)模型类对象.save()

from book.models import Book, Hero
from datetime import date


book = Book(
  title='西游记',
  pu_date=date(1988,1,1),
  read_num=10,
  comment_num=10
)

book.save
增加一个图书

2)通过模型类.objects.creare()保存

from book.models import Book, Hero 

b = Book.objects.get(title='西游记')

Hero.objects.create(
  name='沙悟净',
  gender=0,
  book=book
)

或者
Hero.objects.create(
  name='沙悟净',
  gender=0,
  book_id=book.id
)
增加一个英雄

删除记录有两种方式

1)模型类对象.delete()

book = Book.objects.get(id=1)
book.delete()
删除id为1的图书

2)模型类.filter(条件).delete()

Book.objects.filter(id=1).delete()
删除id为1的图书

注意事项: on_delete选项

  • 默认值为models.CASCADE,当删除图书时,会删除相关联的英雄
  • 如果不想删除关联数据,可设置on_delete为 PROTECT

修改记录有以下两种方式

1)模型类对象.save()

h = Hero.objects.get(name='沙悟净')
h.name = '沙僧'
h.save()
把沙悟净的名字改为沙僧

2)模型类.filter()条件.update(属性1=值1, 属性2=值2, ...)

Hero.objetcs.filter(name='猪八戒').update(name='猪悟能')
把猪八戒的名字改为猪悟能

基本查询

get 查询单一结果,如果不存在或抛出模型类.DoesNotExist异常,通过存在多条记录会抛出MultipleObjectsReturned异常

#查询id为3的图书
In [7]: Book.objects.get(id=3)
Out[7]: <Book: 笑傲江湖>

#查询id为100的图书,该图书其实不存在
In [8]: Book.objects.get(id=100)
---------------------------------------------------------------------------
DoesNotExist                              Traceback (most recent call last)
...

#查询is_delete为false的图书,有多条
In [9]: Book.objects.get(is_delete=0)
---------------------------------------------------------------------------
MultipleObjectsReturned                   Traceback (most recent call last)
示例

all 查询多个结果

In [6]: Book.objects.all()
Out[6]: <QuerySet [<Book: 射雕英雄传>, <Book: 天龙八部>, <Book: 笑傲江湖>, <Book: 雪山飞狐>, <Book: 西游记>]>
查询出所有的图书

count 查询结果数量

In [10]: Hero.objects.count()
Out[10]: 20
查询英雄的总数

过滤查询

  •  调用filter方法:模型类.objects.filter(模型属性名__条件名=值)
  •  返回包含查询结果数据的QuerySet对象

1)判等: exact

In [11]: Book.objects.filter(id__exact=1)
Out[11]: <QuerySet [<Book: 射雕英雄传>]>

可简写为
In [12]: Book.objects.filter(id=1)
Out[12]: <QuerySet [<Book: 射雕英雄传>]>
查询id为1的图书

2)模糊查询: contains / endswith / startswith

In [13]: Hero.objects.filter(name__contains='')
Out[13]: <QuerySet [<Hero: 岳不群>, <Hero: 东方不败>]>
查询名字包含"不"的英雄
In [14]: Hero.objects.filter(name__startswith='')
Out[14]: <QuerySet [<Hero: 黄蓉>, <Hero: 黄药师>]>
查询名字以'黄'开头的英雄
  • 上面查询都是区分大小写的,如果不想区分大小写,只需要加个i,如 iexact、icontains、iendstartswith、istartswith
  • 如何想查询包含%的字符,%不需要转义

3)空查询: isnull

In [17]: Book.objects.filter(title__isnull=False)
Out[17]: <QuerySet [<Book: 射雕英雄传>, <Book: 天龙八部>, <Book: 笑傲江湖>, <Book: 雪山飞狐>, <Book: 西游记>]>
查看图书名不为空的图书

4)范围查询: in

In [20]: Book.objects.filter(id__in=[1,3,5])
Out[20]: <QuerySet [<Book: 射雕英雄传>, <Book: 笑傲江湖>, <Book: 西游记>]>
查询id编号为1,3,5的图书

5)比较查询: gt、lt、gte、lte

In [21]: Book.objects.filter(read_num__gt=20)
Out[21]: <QuerySet [<Book: 天龙八部>, <Book: 雪山飞狐>]>
查询阅读量大于20的图书

6)日期查询: year、month、day、week_day、hour、minute、second

In [22]: Book.objects.filter(pub_date__year=1980)                                                                                                               
Out[22]: <QuerySet [<Book: 射雕英雄传>]>
查询1980年发表的图书
In [23]: Book.objects.filter(pub_date__gt=date(1980,1,1))
Out[23]: <QuerySet [<Book: 射雕英雄传>, <Book: 西游记>]>
查询1980年1月1日后发表的图书

F对象

之前查询都是对象的属性与常量值比较,两个属性怎么比较?这就需要用到F对象

需要导包: from django.db.models import F

语法: F(属性名)

In [25]: from django.db.models import F

In [26]: Book.objects.filter(read_num__gte=F('comment_num'))
Out[26]: <QuerySet [<Book: 雪山飞狐>, <Book: 西游记>]>
查询阅读量大于等于评论量的图书

可以在F对象上使用算数运算

In [27]: from django.db.models import F

In [28]: Book.objects.filter(read_num__gt=F('comment_num') * 2)
Out[28]: <QuerySet [<Book: 雪山飞狐>]>
查询阅读量大于2倍评论量的图书

Q对象

作用: 对查询条件进行与 或 非(& | ~)的逻辑操作

需要导包: from django.db.models import Q

与: Q(查询条件1) & Q(查询条件2)

In [29]: from django.db.models import Q

In [30]: Hero.objects.filter(Q(id__gt=2) & Q(name__contains=''))
Out[30]: <QuerySet [<Hero: 黄药师>]>

可以简写为
In [31]: Hero.objects.filter(id__gt=2, name__contains='')
Out[31]: <QuerySet [<Hero: 黄药师>]>
查询id大于2且名字包含'黄'的英雄

或: Q(查询条件1) | Q(查询条件2)

In [34]: Hero.objects.filter(Q(id__lt=2) | Q(name__contains=''))
Out[34]: <QuerySet [<Hero: 郭靖>, <Hero: 岳不群>, <Hero: 东方不败>]>
查询id小于2或名字包含'不'的英雄

非: ~Q(查询条件)

Hero.objects.filter(~Q(id=3))
查询id不等于3的英雄

 排序

作用: 对查询结果进行排序,默认为升序

用法:

  • 升序: 模型类.objects.order_by('属性名')
  • 降序: 模型类.objects.order_by('-属性名')
In [37]: Book.objects.all().order_by('-id')
Out[37]: <QuerySet [<Book: 西游记>, <Book: 雪山飞狐>, <Book: 笑傲江湖>, <Book: 天龙八部>, <Book: 射雕英雄传>]>
查询所有图书,按照id从到小进行排序

聚合

作用: 聚合操作,对多行查询结果中的一列进行操作,返回一个值
用法: 模型类.objects.aggregate(聚合类('属性名'))

  • 常用聚合类有:Sum, Count, Max, Min, Avg等
  • 返回值是一个字典, 格式: {'属性名__聚合函数': 值}
  • 使用时需要先导入聚合类: from django.db.models import Sum, Count, Max, Min, Avg
In [38]: from django.db.models import Avg

In [39]: Book.objects.aggregate(Avg('read_num'))
Out[39]: {'read_num__avg': 25.2}
查询所有图书的平均阅读量

关联查询

1)、由 一类对象 查询 多类对象: 一类对象.多类名小写_set.all()

In [42]: b = Book.objects.get(title='西游记')

In [43]: b.hero_set.all()
Out[43]: <QuerySet [<Hero: 孙悟空>, <Hero: 猪悟能>, <Hero: 沙僧>]>
查询图书'西游记'的所有英雄

2)、由 多类对象 查询 一类对象: 多类对象.关联属性

In [44]: h = Hero.objects.get(name='郭靖')

In [45]: h.book
Out[45]: <Book: 射雕英雄传>
查询"郭靖"所属图书信息

 通过模型类实现上述两个案例

  • 一类名.objects.filter(多类名小写__多类属性名__条件名=值)
  • 多类名.objects.filter(关联属性__一类属性名__条件名=值)
In [48]: Hero.objects.filter(book__title='西游记')
Out[48]: <QuerySet [<Hero: 孙悟空>, <Hero: 猪悟能>, <Hero: 沙僧>]>

In [49]: Book.objects.filter(hero__name='郭靖')
Out[49]: <QuerySet [<Book: 射雕英雄传>]>

查询集 QuerySet

查询集,也称查询结果集、QuerySet,表示从数据库中获取的对象集合,当调用一下过滤器方法时,Django会返回查询集(而不是简单的列表)

  • all(): 返回所有数据
  • filter(): 返回满足条件的数据
  • exclude(): 返回满足条件之外的数据
  • order_by(): 对结果进行排序

对查询集可以再次调用过滤器进行过滤

Book.objects.filter(read_num__gt=30).order_by('pub_date')

查询集两大特性

1)惰性执行

创建查询集不会访问数据库,直到调用数据时,才会访问数据库,调用数据的情况包括迭代、序列化、与if合用

例如,当执行如下语句时,并未进行数据库查询,只是创建了一个查询集qs

qs = Book.objects.all()

继续执行遍历迭代操作后,才真正的进行数据库查询

for book in qs:
    print(book.title)

2)缓存

  • 第一次查询数据后,Django会将查询集缓存起来,并返回请求的结果
  • 再次查询相同数据时将重用缓存的结果
  from Book.models import Book

  [book.id for book in Book.objects.all()]
  [book.id for book in Book.objects.all()]
无缓存:查询集有两个
 from Book.models import Book

  list=Book.objects.all()
  [book.id for objects in list]
  [book.id for objectsin list]
有缓存:查询集只有一个

限制查询集

可以对查询集进行取下标或切片操作,等同于sql中的limit和offset子句(注意,不支持负数索引)

对查询集进行切片后返回一个新的查询集,不会立即执行查询

如果获取一个对象,直接使用[0],等同于[0:1].get(),但是如果没有数据,[0]引发IndexError异常,[0:1].get()引发DoesNotExist异常

qs = Book.objects.all()[0:2]
获取第1、2项

自定义模型管理器

可以自定义模型管理器,比如以下两种场景:

  •  需要重写模型管理器中现有的方法
    • 需求:调用Book.objects.all()时,返回的是is_delete等于False的图书
  • 封装增删改查的方法
    • 需求:在管理器类中,封装一个创建图书的方法,方便直接调用

注意:自定义后模型管理器后, Django 将不再自动生成默认的 objects

 # 在book.models.py文件添加以下代码, 自定义模型管理器
 class BookManager(Manager):

     def all(self):
         """重写all方法:只返回没有删除的部门"""
         return super().all().filter(is_delete=False)

     def create_book(self, title, pub_date):
         """封装新增图书的方法,方便调用"""

         book = Book()
         book.title = title
         book.pub_date = pub_date
         book.save()
         return book



# 自定义模型管理器
 class Book(models.Model):
     """部门类"""
     ...

     # 自定义模型管理器
     objects = BookManager()
实现参考
原文地址:https://www.cnblogs.com/sellsa/p/10804755.html