Loading

Django之ORM

对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。在业务逻辑层和数据库层之间充当了桥梁的作用.

Django项目使用MySQL数据库

  1. 在Django项目的settings.py文件中,配置数据库连接信息:

    DATABASES = {
        "default": {
            "ENGINE": "django.db.backends.mysql",
            "NAME": "你的数据库名称",  # 需要自己手动创建数据库
            "USER": "数据库用户名",
            "PASSWORD": "数据库密码",
            "HOST": "数据库IP",
            "POST": 3306,
            'OPTIONS': {
                'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"},
        }
    

}
```

  1. 在与Django项目同名的目录下的__init__.py文件中写如下代码,告诉Django使用pymysql模块连接MySQL数据库:

    import pymysql
    pymysql.install_as_MySQLdb()
    

    注:数据库迁移的时候出现一个警告

    WARNINGS: 
    ?: (mysql.W002) MySQL Strict Mode is not set for database connection 'default'
    HINT: MySQL's Strict Mode fixes many data integrity problems in MySQL, such as data truncation upon insertion, by escalating warnings into errors. It is strongly recommended you activate it.
    

    在配置中多加一个OPTIONS参数:Django官网解释

     'OPTIONS': {
        'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"},
    

ORM模型

在Django中model是你数据的单一、明确的信息来源。它包含了你存储的数据的重要字段和行为。通常,一个模型(model)映射到一个数据库表。

基本情况:

  • 每个模型都是一个Python类,它是django.db.models.Model的子类。
  • 模型的每个属性都代表一个数据库字段。
  • 综上所述,Django为您提供了一个自动生成的数据库访问API,详询官方文档链接

简单上手

下面这个例子定义了一个 Person 模型,包含 first_namelast_name

from django.db import models
 
class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

first_namelast_name 是模型的字段。每个字段被指定为一个类属性,每个属性映射到一个数据库列。

上面的 Person 模型将会像这样创建一个数据库表:

CREATE TABLE myapp_person (
    "id" serial NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(30) NOT NULL
);

说明:

  • 表明myapp_person是自动生成的,app名_类名,如果你要自定义表名,需要在model的Meta类中指定 db_table 参数.
  • id字段是自动添加的,如果你想要指定自定义主键,只需在其中一个字段中指定 primary_key=True 即可。如果Django发现你已经明确地设置了Field.primary_key,它将不会添加自动ID列。
  • Django支持MySQL5.5及更高版本。

ORM的字段

  • AutoField 主键

    自增的整形字段,必填参数primary_key=True,则成为数据库的主键。无该字段时,django自动创建。
    当model中如果没有自增列,则自动会创建一个列名为id的列
    一个model不能有两个AutoField字段。
    # 自定义自增列
        nid = models.AutoField(primary_key=True)
    
  • IntegerField

    整数列(有符号的) -2147483648 ~ 2147483647
    PositiveIntegerField正整数
    BigIntegerField长整型(有符号的)
    
  • BooleanField

    布尔值类型
    gender = models.BooleanField('性别', choices=((0, '女'), (1, '男')))
    
  • CharField

    字符类型
    必须提供max_length参数, max_length表示字符长度
    
  • TextField

    文本类型
    
  • DateTimeField DateField 日期时间

    auto_now_add=True    # 新增数据的时候会自动保存当前的时间
    auto_now=True        # 新增、修改数据的时候会自动保存当前的时间
    default
    
  • DecimalField 十进制的小数

    max_digits       小数总长度   5
    decimal_places   小数位长度   2
    
  • EmailField

    字符串类型,Django Admin以及ModelForm中提供验证机制
    
  • IPAddressField

    字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制
    
  • 其他类型

    SmallIntegerField(IntegerField):
        - 小整数 -32768 ~ 32767
    
    PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
        - 正小整数 0 ~ 32767
    
    IntegerField(Field)
        - 整数列(有符号的) -2147483648 ~ 2147483647
    
    PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
        - 正整数 0 ~ 2147483647
    
    BigIntegerField(IntegerField):
        - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807
    
    
    NullBooleanField(Field):
        - 可以为空的布尔值
    
    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)
        - 浮点型
        
    BinaryField(Field)
        - 二进制类型
    

自定义字段类型

class MyCharField(models.Field):#继承models.Field,不会生成表
    """
    自定义的char类型的字段类
    """
    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        super().__init__(max_length=max_length, *args, **kwargs)
 
    def db_type(self, connection):
        """
        限定生成数据库表的字段类型为char,长度为max_length指定的值
        """
        return 'char(%s)' % self.max_length

使用自定义char类型字段:

class Class(models.Model):
    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=25)
    # 使用自定义的char类型的字段
    cname = MyCharField(max_length=25)

ORM的字段参数

	null                数据库中字段是否可以为空
    db_column           数据库中字段的列名
    default             数据库中字段的默认值
    primary_key         数据库中字段是否为主键
    db_index            数据库中字段是否可以建立索引
    unique              数据库中字段是否可以建立唯一索引
    
    unique_for_date     数据库中字段【日期】部分是否可以建立唯一索引
    unique_for_month    数据库中字段【月】部分是否可以建立唯一索引
    unique_for_year     数据库中字段【年】部分是否可以建立唯一索引
 
    verbose_name        Admin中显示的字段名称
    blank               Admin中是否允许用户输入为空
    editable            Admin中是否可以编辑
    help_text           Admin中该字段的提示信息
    choices             Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作
    					如:gender = models.IntegerField(choices=[(0, '女'),(1, '男'),],default=1)
 
    error_messages      自定义错误信息(字典类型),从而定制想要显示的错误信息;
    					字典健:null, blank, invalid, invalid_choice, unique,unique_for_date
                        如:{'null': "不能为空.", 'invalid': '格式错误'}
    validators          自定义错误验证(列表类型),从而定制想要的验证规则

Model Meta 表参数

class UserInfo(models.Model):
     def __str__(self):#定义打印该对象显示内容
        return self.name
    nid = models.AutoField(primary_key=True)
    username = models.CharField(max_length=32)
 
    class Meta:
        # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
        db_table = "table_name"
 
        # admin中显示的表名称
        verbose_name = '个人信息'
 
        # verbose_name加s
        verbose_name_plural = '所有用户信息'
 
        # 联合索引 
        index_together = [
            ("pub_date", "deadline"),   # 应为两个存在的字段
        ]
 
        # 联合唯一索引
        unique_together = (("driver", "restaurant"),)   # 应为两个存在的字段

ORM查询

  • all() 获取所有的数据 ——QuerySet对象列表

    ret = models.Person.objects.all()
    
  • get() 获取满足条件的一个数据 ——对象

    获取不到或者多个都报错

    ret = models.Person.objects.get(pk=1)
    
  • filter() 获取满足条件的所有数据 —— QuerySet 对象列表

    ret = models.Person.objects.filter(pk=1)
    
  • exclude() 获取不满足条件的所有数据 —— QuerySet 对象列表

    ret = models.Person.objects.exclude(pk=1)
    
  • values() 拿到对象所有的字段和字段的值 QuerySet [ {} ,{} ]

    拿到对象指定的字段和字段的值    QuerySet  [ {} ,{} ]
    ret = models.Person.objects.values('pid','name')
    <QuerySet [{'pid': 1, 'name': 'bbb'}]>
    
  • values_list() 拿到对象所有的字段的值 QuerySet [ () ,() ]

    values('字段')   拿到对象指定的字段的值      QuerySet  [ {} ,{} ]
    ret = models.Person.objects.values_list('name','pid')
    
  • order_by 排序 前面加负号-表示降序 ——》 QuerySet [ () ,() ]

    ret = models.Person.objects.all().order_by('age','-pid')
    
  • reverse 反向排序 只能对已经排序的QuerySet进行反转

    ret = models.Person.objects.all().order_by('age','-pid').reverse()
    
  • distinct 去重 完全相同的内容才能去重

    对于对象的去重没有意义,因为id永远不一样。不可能存在两个相同的对象。

    ret = models.Person.objects.values('age').distinct()
    
  • count() 计数

    ret = models.Person.objects.all().count()
    
  • first 取第一元素 没有元素 None

    ret = models.Person.objects.filter(pk=1).values().first()
    
  • last 取最后一元素 没有元素 None

  • exists 查询的数据是否存在

    ret = models.Person.objects.filter(pk=1000).exists()
    

单表双下划线

ret = models.Person.objects.filter(pk__gt=1)   # gt  greater than   大于
ret = models.Person.objects.filter(pk__lt=3)   # lt  less than   小于
ret = models.Person.objects.filter(pk__gte=1)   # gte  greater than   equal    大于等于
ret = models.Person.objects.filter(pk__lte=3)   # lte  less than  equal  小于等于
ret = models.Person.objects.filter(pk__range=[2,3])   # range  范围
ret = models.Person.objects.filter(pk__in=[1,3,10,100])   # in  成员判断
ret = models.Person.objects.filter(name__contains='A')
ret = models.Person.objects.filter(name__icontains='A')   # 忽略大小写
ret = models.Person.objects.filter(name__startswith='a')  # 以什么开头
ret = models.Person.objects.filter(name__istartswith='A')
ret = models.Person.objects.filter(name__endswith='a')  # 以什么结尾
ret = models.Person.objects.filter(name__iendswith='I')
date字段可以:
ret  = models.Person.objects.filter(birth__year='2019')
ret  = models.Person.objects.filter(birth__contains='2018-06-24')

ret  = models.Person.objects.filter(phone__isnull=False)

多对一操作

ForeignKey外键

class Publisher(models.Model):
    name = models.CharField(max_length=32, verbose_name="名称")

    def __str__(self):
        return self.name


class Book(models.Model):
    title = models.CharField(max_length=32)
    pub = models.ForeignKey(Publisher,
                            related_name='books',related_query_name='xxx',on_delete=models.CASCADE)

    def __str__(self):
        return self.title

基于对象查询

正向

从多的一方查询,查询到单个对象

语法:对象.关联字段.字段

book_obj.pub ——》 所关联的对象

book_obj.pub_id ——》 所关联的对象id

book_obj.pub.name

反向

从少的一方查询,查询到多个结果

语法:obj.表名_set

  • 没有指定related_name

    pub_obj.book_set ——》 关系管理对象 (类名小写_set)

    pub_obj.book_set.all() ——》 所关联的所有对象

  • 指定related_name='books'

    pub_obj.books ——》 关系管理对象

    pub_obj.books.all() ——》 所关联的所有对象

外键关系管理对象方法

print(pub_obj.books.all())
#创建一个新的对象,保存对象,并将它添加到关联对象集之中,返回新创建的对象。
obj = pub_obj.books.create(title='用python养猪')
#更新model对象的关联对象。
pub_obj.books.set(models.Book.objects.filter(pk__in=[4,5]))  # 不能用id  只能用对象
# 把指定的model对象添加到关联对象集中。
pub_obj.books.add(*models.Book.objects.filter(pk__in=[1,2]))# 不能用id  只能用对象

pub = models.ForeignKey(Publisher, related_name='books',null=True, on_delete=models.CASCADE)
# 只有外键可为空null=True时有remove  clear
pub_obj.books.remove(*models.Book.objects.filter(pk__in=[1,2]))
pub_obj.books.clear()

基于字段查询

正向

语法:关联字段__字段

models.Book.objects.filter(pub__name='xxxxx') 外键+双下划线+跨表字段

反向

语法:表名__字段

  • 没有指定related_name

    models.Publisher.objects.filter(book__title='xxxxx')

  • 指定related_name=‘books’

    models.Publisher.objects.filter(books__title='xxxxx')

  • 指定related_query_name='book‘,专门用于字段查询,优先级高于related_name

    models.Publisher.objects.filter(book__title='xxxxx')

多对多操作

ManyToManyField

实例解析:

class Book(models.Model):
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=6, decimal_places=2)  # 9999.99
    sale = models.IntegerField()
    kucun = models.IntegerField()
    pub = models.ForeignKey(Publisher, null=True,on_delete=models.CASCADE)

    def __str__(self):
        return self.title


class Author(models.Model):
    name = models.CharField(max_length=32, )
    books = models.ManyToManyField(Book)	#django自建,共三种创建方式.也可加related_name

    def __str__(self):
        return self.name

基于对象:

  1. 正向:

    author_obj = models.Author.objects.get(pk=1)
    author_obj.books	#关系管理对象
    author_obj.books.all() 
    
  2. 反向:

    book_obj = models.Book.objects.get(pk=1)
    # 不指定related_name
    print(book_obj.author_set)  #  ——》  关系管理对象
    print(book_obj.author_set.all())
    # related_name='authors'
    print(book_obj.authors)#  ——》  关系管理对象
    print(book_obj.authors.all())
    

基于字段:

  1. 正向:

    ret  =  models.Author.objects.filter(books__title='Django')
    
  2. 反向:

    # 不指定related_name
    ret = models.Book.objects.filter(author__name='yhp')    #类名字__字段
    # related_name='authors'
    ret = models.Book.objects.filter(authors__name='yhp')  #用其替换类名
    # related_query_name='xxx'
    ret = models.Book.objects.filter(xxx__name='yhp')
    

关系管理对象方法


author_obj = models.Author.objects.get(pk=1)

# all()  所关联的所有的对象

# set  重置多对多的关系    [id,id]    [ 对象,对象 ]清空关系,再重新设置
author_obj.books.set([1,2])
author_obj.books.set(models.Book.objects.filter(pk__in=[1,2,3]))

# add  添加多对多的关系   (id,id)   (对象,对象),再原有关系基础上添加,直接添加,不是列表形式
author_obj.books.add(4,5)
author_obj.books.add(*models.Book.objects.filter(pk__in=[4,5]))#打散添加

# remove 删除多对多的关系  (id,id)   (对象,对象)
author_obj.books.remove(4,5)
author_obj.books.remove(*models.Book.objects.filter(pk__in=[4,5]))

# clear()   清除所有的多对多关系
author_obj.books.clear()

# create()	创建对象并设置关系
obj = author_obj.books.create(title='书名',pub_id=1)
book_obj = models.Book.objects.get(pk=1)
obj = book_obj.author_set.create(name='烧饼')

关系管理对象详解

"关联管理对象"是在一对多或者多对多的关联上下文中使用的管理器。

它存在于下面两种情况:

  1. 外键关系的反向查询
  2. 多对多关联关系

简单来说就是当 点后面的对象 可能存在多个的时候就可以使用以下的方法。

create()

创建一个新的对象,保存对象,并将它添加到关联对象集之中,返回新创建的对象。

>>> import datetime
>>> models.Author.objects.first().book_set.create(title="番茄物语", publish_date=datetime.date.today())

add()

把指定的model对象添加到关联对象集中。

添加对象

>>> author_objs = models.Author.objects.filter(id__lt=3)
>>> models.Book.objects.first().authors.add(*author_objs)

添加id

>>> models.Book.objects.first().authors.add(*[1, 2])

set()

更新model对象的关联对象。

>>> book_obj = models.Book.objects.first()
>>> book_obj.authors.set([2, 3])

remove()

从关联对象集中移除执行的model对象

>>> book_obj = models.Book.objects.first()
>>> book_obj.authors.remove(3)

clear()

从关联对象集中移除一切对象。

>>> book_obj = models.Book.objects.first()
>>> book_obj.authors.clear()

注意:

对于ForeignKey对象,clear()和remove()方法仅在null=True时存在。

举个例子:

ForeignKey字段没设置null=True时,

class Book(models.Model):
    title = models.CharField(max_length=32)
    publisher = models.ForeignKey(to=Publisher)

没有clear()和remove()方法:

>>> models.Publisher.objects.first().book_set.clear()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
AttributeError: 'RelatedManager' object has no attribute 'clear'

当ForeignKey字段设置null=True时,

class Book(models.Model):
    name = models.CharField(max_length=32)
    publisher = models.ForeignKey(to=Class, null=True)

此时就有clear()和remove()方法:

>>> models.Publisher.objects.first().book_set.clear()

注意:

  1. 对于所有类型的关联字段,add()、create()、remove()和clear(),set()都会马上更新数据库。换句话说,在关联的任何一端,都不需要再调用save()方法。

分组&聚合

from app01 import models

from django.db.models import Max, Min, Avg, Sum, Count

# 聚合
ret = models.Book.objects.filter(pk__gt=3).aggregate(Max('price'),avg=Avg('price')) 
print(ret)
运行结果:{'avg':160.0,'price__max':Decimal('190.0')}
    数据结构为字典,终止语句.关键字传参可改变字典的key,默认为:字段__聚合函数名
说明:    
    models.Book.objects.filter(pk__gt=3) #表示对哪些数据进行聚合
    aggregate(Max('price'),avg=Avg('price'))#对哪些字段进行聚合,
    
# 分组
# 统计每一本书的作者个数
ret = models.Book.objects.annotate(count=Count('author')) # annotate 注释
# 统计出每个出版社的最便宜的书的价格
# 方式一
ret = models.Publisher.objects.annotate(Min('book__price')).values()
# 方式二
ret = models.Book.objects.values('pub_id').annotate(min=Min('price'))
# annotate前为分组依据,后面为聚合字段

aggregate()QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。

键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。

F和Q

from django.db.models import F

# F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
ret=models.Book.objects.filter(sale__gt=F('kucun'))

# 只更新sale字段,update效率高于save
models.Book.objects.all().update(sale=100)

# 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作
models.Book.objects.all().update(sale=F('sale')*2+10)

Q(条件)

filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR语句),你可以使用Q对象。

查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。

| 或

& 与
from django.db.models import Q
ret = models.Book.objects.filter(Q(Q(pk__gt=3) | Q(pk__lt=2)) & Q(price__gt=50))

事务

from django.db import transaction

try:
    with transaction.atomic():
        # 进行一系列的ORM操作
        models.Publisher.objects.create(name='xxxxx')
        models.Publisher.objects.create(name='xxx22')

except Exception as e :
    print(e)
# with语句内进行原子型操作
posted @ 2019-09-01 22:34  陌路麒麟  阅读(122)  评论(0编辑  收藏  举报