模型层-基本操作

orm#

在我们常说的MVC模型或MTV模型中, ORM操作就占了很大的比重, 其中的模型(M)就代表的对数据库的操作. 没有ORM, 我们只能通过pymysql等模块操作数据库, 然后再传入原生的sql语句字符串来实现对数据库的增删改查, 而这就导致一个问题, 我们作为程序员, 不是专业的DBA, 写出来的sql语句可能效率很低, 并且不易维护, 并且还极易出错, 可能会把许多时间花在调试sql语句上, 这样开发效率就非常低下.

此外, 数据模型与数据库不能实现解耦, 一旦我们需要更换数据库, 就需要对代码进行大量的修改. 基于这种种原因, orm (对象关系映射)就被发明出来, 它能让程序员以熟悉的操作对象的方式来操作数据库, 能够轻松的实现创造表, 增删改查记录, 而无需编写一条原生sql语句, 并且它能让我们的数据模型不依赖与具体的数据库, 只需要在配置文件中简单的改动一些配置, 就能轻松的更换数据库.

总结上面的话, ORM就是能将程序员编写的代码翻译成sql语句, 再通过pymysql等模块来直接操作数据库,相当于在它的基础上又封装了一层.

创建模型表和数据库表#

在Django利用orm操作可以帮我们自动在数据库中创建表.

  1. 首先在app目录下的models模块下创建一个User模型类
Copy
from django.db import models

class User(models.Model):
    name = models.CharField(max_length=20)
    age = models.IntegerField()
  1. 配置数据库

这里我们选择mysql作为最后的存储引擎, 这时候就可以在settings文件做一个简单的配置了.

Copy
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'test',
        'USER': 'root',
        'PASSWORD': '123',
        'HOST': 'localhost',
        'PORT': 3306
    }
}

更改数据库之后还不要忘记在项目或app目录下的__init__文件中添加下面两句话

Copy
import pymysql
pymysql.install_as_MySQLdb()
  1. 迁移

现在只是简单的在逻辑层面上和数据库建立了关系, 还需要在数据库中真正的创建表.

这需要在shell环境下执行下面两句命令行:

Copy
python3 manage.py makemigrations
python3 manage.py migrate

创建上面的表相当于在数据库执行下面的sql语句

Copy
CREATE TABLE `app01_user` ( 
    `id` INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY, 
    `name` VARCHAR ( 20 ) NOT NULL, 
    `age` INTEGER NOT NULL 
);
  1. 完成

到此模型表就与数据的库表建立一对一的关系了.

需要注意的是

  • 当我们在表中没有显示的声明哪个字段是主键的时候, ORM会自动为我们创建一个名为id的主键字段
  • 表名app01_user是由Django默认生成的, 规则是app名称_模型类名
  • 我们每一次更新了模型表中的字段的信息了, 需要重新执行上面两条迁移命令, 来与数据实现同步.

常用字段和参数#

字段合集#

Copy
AutoField(Field)
    - int自增列,必须填入参数 primary_key=True

BigAutoField(AutoField)
    - bigint自增列,必须填入参数 primary_key=True

    注:当model中如果没有自增列,则自动会创建一个列名为id的列
    from django.db import models

    class UserInfo(models.Model):
        # 自动创建一个列名为id的且为自增的整数列
        username = models.CharField(max_length=32)

    class Group(models.Model):
        # 自定义自增列
        nid = models.AutoField(primary_key=True)
        name = models.CharField(max_length=32)

SmallIntegerField(IntegerField):
    - 小整数 -3276832767

PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
    - 正小整数 032767
IntegerField(Field)
    - 整数列(有符号的) -21474836482147483647

PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
    - 正整数 02147483647

BigIntegerField(IntegerField):
    - 长整型(有符号的) -92233720368547758089223372036854775807

BooleanField(Field)
    - 布尔值类型

NullBooleanField(Field):
    - 可以为空的布尔值

CharField(Field)
    - 字符类型
    - 必须提供max_length参数, max_length表示字符长度

TextField(Field)
    - 文本类型

EmailField(CharField):
    - 字符串类型,Django Admin以及ModelForm中提供验证机制

IPAddressField(Field)
    - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制

GenericIPAddressField(Field)
    - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
    - 参数:
        protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
        unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启此功能,需要protocol="both"

URLField(CharField)
    - 字符串类型,Django Admin以及ModelForm中提供验证 URL

SlugField(CharField)
    - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)

CommaSeparatedIntegerField(CharField)
    - 字符串类型,格式必须为逗号分割的数字

UUIDField(Field)
    - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证

FilePathField(Field)
    - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
    - 参数:
            path,                      文件夹路径
            match=None,                正则匹配
            recursive=False,           递归下面的文件夹
            allow_files=True,          允许文件
            allow_folders=False,       允许文件夹

FileField(Field)
    - 字符串,路径保存在数据库,文件上传到指定目录
    - 参数:
        upload_to = ""      上传文件的保存路径
        storage = None      存储组件,默认django.core.files.storage.FileSystemStorage

ImageField(FileField)
    - 字符串,路径保存在数据库,文件上传到指定目录
    - 参数:
        upload_to = ""      上传文件的保存路径
        storage = None      存储组件,默认django.core.files.storage.FileSystemStorage
        width_field=None,   上传图片的高度保存的数据库字段名(字符串)
        height_field=None   上传图片的宽度保存的数据库字段名(字符串)

DateTimeField(DateField)
    - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]

DateField(DateTimeCheckMixin, Field)
    - 日期格式      YYYY-MM-DD

TimeField(DateTimeCheckMixin, Field)
    - 时间格式      HH:MM[:ss[.uuuuuu]]

DurationField(Field)
    - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型

FloatField(Field)
    - 浮点型

DecimalField(Field)
    - 10进制小数
    - 参数:
        max_digits,小数总长度
        decimal_places,小数位长度

BinaryField(Field)
    - 二进制类型

字段参数#

通用字段
Copy
(1)null
如果为True,Django 将用NULL 来在数据库中存储空值。 默认值是 False.
 
(2)blank
 
如果为True,该字段允许不填。默认为False。
要注意,这与 null 不同。null纯粹是数据库范畴的,而 blank 是数据验证范畴的。
如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。
 
(3)default
 
字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用。
 
(4)primary_key
 
如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True,
Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为,
否则没必要设置任何一个字段的primary_key=True。
 
(5)unique
 
如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的
 
(6)choices
由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,<br>而且这个选择框的选项就是choices 中的选项。
时间字段参数:
Copy
auto_now_add
配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。

auto_now
配置上auto_now=True,每次更新数据记录的时候会更新该字段。

上面两个参数互斥,不能共存
关系字段-ForeignKey
Copy
to
设置要关联的表

to_field
设置要关联的表的字段

on_delete
当删除关联表中的数据时,当前表与其关联的行的行为。

models.CASCADE

删除关联数据,与之关联也删除

db_constraint
是否在数据库中创建外键约束,默认为True
关系字段-OneToOne
Copy
to
设置要关联的表。

to_field
设置要关联的字段。

on_delete
当删除关联表中的数据时,当前表与其关联的行的行为。(参考上面的例子)

与外键的行为类似, 一对一字段一般应用在扩展字段上.

单表操作#

添加表记录#

添加单条数据

假设当前以User表为例

Copy
class User(models.Model):
    name = models.CharField(max_length=20)
    
    def __str__(self):
        return self.name

添加单条数据有两种方式:

方式一

Copy
# create方法添加数据会直接返回创建出来的对象
user = models.User.objects.create(name='user1')
print(user)
# out: user1

方式2

Copy
# save方法是先创建一个User对象, 再通过调用对象的save方法, 才能执行保存到数据库的操作.
user = models.User(name='user2')
user.save()
添加多条数据

添加多条数据, 我们第一反应就是通过循环调用create方法或save方法来执行插入操作, 但是这样的效率非常低. 需要通过频繁的请求数据库才能完成操作

为了避免这种效率低下的方式, 就需要用到bulk_create方式来进行批量操作

Copy
users = [models.User(name=f'user{i}') for i in range(10)]
models.User.objects.bulk_create(users)  
# (0.003) INSERT INTO `user` (`name`) VALUES ('user0'), ('user1') ...; args=('user0', 'user1', 'user2', ...) 
# 循环创建多个对象
for i in range(10):
    models.User.objects.create(name=f'user{i}')
    
# (0.003) INSERT INTO `app01_user` (`name`) VALUES ('user0'); args=['user0']
# (0.000) INSERT INTO `app01_user` (`name`) VALUES ('user1'); args=['user1']
# (0.001) INSERT INTO `app01_user` (`name`) VALUES ('user2'); args=['user2']
...

上面的方式可以通过在settings文件中配置打印sql语句就可以看出来, 批量操作只请求了数据库一次, 而create方法则每创建一个对象就需要请求一次数据库.

删除表记录#

方式一: 直接多个删除, 在QuerySet对象直接调用delete方法. 会把符合条件的全部都删除.

Copy
models.User.objects.filter(pk__gt=6).delete()

方式二: 获取到单个对象, 直接调用delete方法来进行删除

Copy
user = models.User.objects.filter(pk=2).first()  # type: models.User
user.delete()

注意点:

  • 不能直接在User.objects对象上执行delete操作
  • 不能在valuesvalues_list上执行delete操作
  • Django删除对象默认的是级联删除, 删除带有外键 约束的对象的时候, 会连带被约束的对象一起删除.

修改表记录#

修改表记录同样有两种方式.

方式一: 直接在QuerySet对象上执行修改操作

Copy
models.User.objects.filter(pk=4).update(name='user666')
# UPDATE `app01_user` SET `name` = 'user666' WHERE `app01_user`.`id` = 4;
user = models.User.objects.filter(pk=4).first()  # type: models.User
  

方式二: 在具体的对象上执行save方法, 来进行修改操作.

Copy
user.name = 'user777'
user.save()
# UPDATE `app01_user` SET `name` = 'user777', `age` = 18 WHERE `app01_user`.`id` = 4

区别:

  • 查看执行的sql语句, update只会对对应的字段进行修改, save方法会对所有字段进行修改, update效率更高
  • update是可以进行批量修改的.

小疑问: Django执行save方法如何正确的区分insert或update方法?

参考官方文档了解了原因.

内部是这么做逻辑判断的.

  1. 如果对象的主键存在的话(主键不为空或是空字符串), 就执行update操作
  2. 如果对象的主键存在的话或者当执行了update操作之后, 但是没有任何影响, 这时候会执行insert操作

针对上面第二条, 说明我们最好不要主动添加主键, 因为会多走一次数据库查询. 就像下面这样

Copy
# UPDATE `app01_user` SET `name` = 'user10', `age` = 18 WHERE `app01_user`.`id` = 10;
# INSERT INTO `app01_user` (`id`, `name`, `age`) VALUES (10, 'user10', 18);

实际多执行了一条sql语句.

查询表记录#

查询是ORM的重中之重, 熟练掌握查询的操作是十分有必要的, 初始, 如果我们需要理解ORM和数据操作之间的关系, 可以通过两种方式查看.

  • 当执行查询操作获得了querySet对象的时候, 可以调用query属性查看当前操作执行sql语句
  • 直接在settings文件中配置logging参数, 直接打印出每条sqla语句的转换结果.

logging配置文件

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

查询API#

假设当前的模型类的结构如下所示

Copy
class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.FloatField()
    pub_time = models.DateField()
    
    def __str__(self):
       return  self.name
    
表里数据:
1   三国演义    22.2     2019-08-14
2   红楼梦     33.3     2018-02-22
3   三国演义2   20      2019-10-25
4   水浒传     10      2017-01-02
5   射雕英雄传  24      2018-02-12
6   西游记     54.9    2019-07-10

<1> all(): 查询所有结果

Copy
models.Book.objects.all()
<QuerySet [<Book: 三国演义>, <Book: 红楼梦>, <Book: 三国演义2>, <Book: 水浒传>, <Book: 射雕英雄传>, <Book: 西游记>]>

<2> filter(**kwargs): 它包含了与所给筛选条件相匹配的对象, 筛选条件之间是and的关系.

Copy
models.Book.objects.filter(price__gt=21.0, name__contains='三国演义')
<QuerySet [<Book: 三国演义>]>

<3> get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误, 这个方法不是很好用

Copy
print(models.Book.objects.get(name='三国演义'))
print(models.Book.objects.get(name__contains='三国演义'))

# 第二个报错 get() returned more than one Book -- it returned 2!

<4> exclude(**kwargs): 它包含了与所给筛选条件不匹配的对象

Copy
print(models.Book.objects.exclude(name__contains='三国演义'))
<QuerySet [<Book: 红楼梦>, <Book: 水浒传>, <Book: 射雕英雄传>, <Book: 西游记>]>

<5> order_by(*field): 对查询结果排序, 默认是升序, 在字段前面加-号表示降序

Copy
print(models.Book.objects.order_by('price'))
print(models.Book.objects.order_by('-price'))

<QuerySet [<Book: 水浒传>, <Book: 三国演义2>, <Book: 三国演义>, <Book: 射雕英雄传>, <Book: 红楼梦>, <Book: 西游记>]>
<QuerySet [<Book: 西游记>, <Book: 红楼梦>, <Book: 射雕英雄传>, <Book: 三国演义>, <Book: 三国演义2>, <Book: 水浒传>]>

<6> reverse(): 对查询结果反向排序

Copy
print(models.Book.objects.order_by('price').reverse())
<QuerySet [<Book: 西游记>, <Book: 红楼梦>, <Book: 射雕英雄传>, <Book: 三国演义>, <Book: 三国演义2>, <Book: 水浒传>]>
# 结果与上面的反序相同

<8> count(): 返回数据库中匹配查询(QuerySet)的对象数量

Copy
print(models.Book.objects.all().count())
# 6

<9> first(): 返回第一条记录, 这个经常使用

Copy
print(models.Book.objects.first())
# 三国演义

<10> last(): 返回最后一条记录

Copy
print(models.Book.objects.last())
西游记

<11> exists(): 如果QuerySet包含数据,就返回True,否则返回False

Copy
print(models.Book.objects.filter(name='1234').exists())
False

<12> values(*field): 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,而是一个可迭代的字典序列.

Copy
print(models.Book.objects.filter(price__gt=24).values('name', 'pub_time'))
<QuerySet [{'name': '红楼梦', 'pub_time': datetime.date(2018, 2, 22)}, {'name': '西游记', 'pub_time': datetime.date(2019, 7, 10)}]>

<13> values_list(*field): 它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列

Copy
print(models.Book.objects.filter(price__gt=24).values_list('name', 'pub_time'))
<QuerySet [('红楼梦', datetime.date(2018, 2, 22)), ('西游记', datetime.date(2019, 7, 10))]>

<14> distinct(): 从返回结果中剔除重复纪录, 对查询结果的每一个字段都做比较.

注意点#

  • 返回结果是QuerySet对象的话, 支持链式操作, 可以一直做过滤操作
  • QuerySet对象实行的是惰性查询, 对QuerySet对象进行切片, 过滤, 创建, 传递值都不会真正查询数据库
  • 对于查询结果是否存在, 使用exists方法比bool值判断更高效

对于会真正执行数据操作的主要有以下操作

  • 迭代
  • 带有步长的切片
  • len()
  • list
  • bool

双下划线查询#

Copy
models.Tb1.objects.filter(id__lt=10, id__gt=1)   # 获取id大于1 且 小于10的值
 
models.Tb1.objects.filter(id__in=[11, 22, 33])   # 获取id等于11、22、33的数据
models.Tb1.objects.exclude(id__in=[11, 22, 33])  # not in
 
models.Tb1.objects.filter(name__contains="ven")  # 获取name字段包含"ven"的
models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感
 
models.Tb1.objects.filter(id__range=[1, 3])      # id范围是1到3的,等价于SQL的bettwen and
 
类似的还有:startswith,istartswith, endswith, iendswith 

date字段还可以:
models.Class.objects.filter(first_day__year=2017)
date字段可以通过在其后加__year,__month,__day等来获取date的特点部分数据
# date
        #
        # Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1))
        # Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1))

        # year
        #
        # Entry.objects.filter(pub_date__year=2005)
        # Entry.objects.filter(pub_date__year__gte=2005)

        # month
        #
        # Entry.objects.filter(pub_date__month=12)
        # Entry.objects.filter(pub_date__month__gte=6)

        # day
        #
        # Entry.objects.filter(pub_date__day=3)
        # Entry.objects.filter(pub_date__day__gte=3)

        # week_day
        #
        # Entry.objects.filter(pub_date__week_day=2)
        # Entry.objects.filter(pub_date__week_day__gte=2)
需要注意的是在表示一年的时间的时候,我们通常用52周来表示,因为天数是不确定的,老外就是按周来计算薪资的哦~
posted @ 2019-10-20 20:49  suwanbin_thought  阅读(92)  评论(0编辑  收藏  举报