Django Model

1. ORM 简介

MTV 设计模式中,模型(M)就是对数据库的操作,在 Web 开发中,使用最频繁的也是对数据库的操作,那么该怎么样去实现呢?

我们不可能自己手动去写大量的 SQL 语句,因为我们也不是专业 DBA 人员,那么我们就只能奢求有一种能够使用 Python 程序对数据库操作的方法了。这就是 ORM(Object Relation Mapping)对象关系映射,以面向对象的方式去操作数据库。

其实现模式大致是这样的:

Django 本身给我们提供了强大的 ORM 系统,不需要再额外的
安装,当然还有一些其他的 ORM ,如:SQLAlch 等。

2. 字段

Model 中的字段 fileds 即数据表中的列,用来存储数据(记录),字段类型对应列的数据类型。

2.1 常用字段类型

Django 内置了很多字段类型,都位于 django.db.models 中,以下为常用字段类型:

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):
    - 小整数 -32768 ~ 32767

PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
    - 正小整数 0 ~ 32767

IntegerField(Field)
    - 整数列(有符号的) -2147483648 ~ 2147483647

PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
    - 正整数 0 ~ 2147483647

BigIntegerField(IntegerField):
    - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807

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
    - auto_now_add=True     当在首次创建对象时自动将字段设置为现在。用于创建时间戳
    - auto_add=True         每次保存对象时,自动将字段设置为现在。用于“最后修改”的时间戳

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

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

FloatField(Field)   
    - 浮点型,精确的数不能用 FloatField

DecimalField(Field)
    - 10进制小数,对于比较精确的数可以用 DecimalField
    - 参数:
        max_digits,小数总长度
        decimal_places,小数位长度

BinaryField(Field)
    - 二进制类型

2.2 字段参数

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中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作
                    如:gf = models.IntegerField(choices=[(0, '何穗'),(1, '大表姐'),],default=1)

error_messages      自定义错误信息(字典类型),从而定制想要显示的错误信息;
                    字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date
                    如:{'null': "不能为空.", 'invalid': '格式错误'}

validators          自定义错误验证(列表类型),从而定制想要的验证规则
                    from django.core.validators import RegexValidator
                    from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\
                    MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator
                    如:
                        test = models.CharField(
                            max_length=32,
                            error_messages={
                                'c1': '优先错信息1',
                                'c2': '优先错信息2',
                                'c3': '优先错信息3',
                            },
                            validators=[
                                RegexValidator(regex='root_\d+', message='错误了', code='c1'),
                                RegexValidator(regex='root_112233\d+', message='又错误了', code='c2'),
                                EmailValidator(message='又错误了', code='c3'), ]
                        )
1. null
用来保存空值,默认值 false。对于保存字符串的字段,尽量避免将其设置为 true,否则可能会导致出现是 null 或空字符串

2. blank
表示字段可以为空,默认 false。常用语表单输入验证是否为空,与数据库无关。

3. choices
页面上选择框标签,二维元组格式。两个参数,第一个为存储在数据库中,第二个显示在页面上内容。
数据库存储的是0 、1 、2,而页面上显示的是篮球、足球、羽毛球
hobby_choices = [(0, '篮球'), (1, '足球'), (2, '羽毛球'),]

hobby = models.IntegerField(
    choices=hobby_choices
)

4. primary_key 
主键,如果没有指定主键,那么 Django会自动创建一个 AutoField的自增字段,名为 id,并将其设置为主键。

primary_key 相当于 null=False和unique=True,即唯一且不能为空,你也可以自己将其他字段设置为空,若有必要。 

示例

from django.db import models

class UserInfo(models.Model):
    username = models.CharField(
        null=True,
        db_column='user',
        max_length=32,
        db_index=True,
        verbose_name='用户名',
        help_text='帮助信息',
        default='',
    )

    hobby_choices = [(0, '篮球'), (1, '足球'), (2, '羽毛球'),]

    hobby = models.IntegerField(
        choices=hobby_choices
    )

    def __str__(self):
        return self.username

2.3 元类 Meta

模型中的元类是指除了字段外的其他非必须内容,如修改数据表的名字,设置代理等。

使用方式

class User(models.Model):
    ...

    class Meta:
        verbose_name = '用户'
1. abstract
为 true 时,模型会被认为是一个抽象类,长用来作为其他模型的父类被继承。

2. app_label
如果没有在 settings 中注册 app,那么必须在元类中声名是属于哪个 app
app_label = 'polls'

3. base_manager_name
自定义模型的 _base_manager 管理器的名字,模型管理器是 Django 为模型提供的 API

4. db_table
指定模型生成数据表时,数据表的表名
db_table = 'user'

5. db_tablespace
自定义数据库表空间的名字。默认值是工程的DEFAULT_TABLESPACE设置

6. default_manager_name
自定义模型的_default_manager管理器的名字

7. default_related_name
反向查询时,默认我们使用的 `_set` 也就是源模型名字+下划线+set 方法查询,当我们定义了 default_related_name时,就可以使用它来反向查询。

8. ordering
指定该模型生成的所有对象的排序方式,接收一个字段名组成的元组或列表。默认按升序排列,如果在字段名前加上字符“-”则表示按降序排列,如果使用字符问号“?”表示随机排列。

ordering = ['pub_date']             # 表示按'pub_date'字段进行升序排列
ordering = ['-pub_date']            # 表示按'pub_date'字段进行降序排列
ordering = ['-pub_date', 'author']  # 表示先按'pub_date'字段进行降序排列,再按`author`字段进行升序排列。

9. permissions
该元数据用于当创建对象时增加额外的权限。它接收一个所有元素都是二元元组的列表或元组,每个元素都是(权限代码, 直观的权限名称)的格式。比如下面的例子:

permissions = (("can_deliver_pizzas", "可以送披萨"),)

10. default_permissions
Django默认给所有的模型设置('add', 'change', 'delete')的权限,也就是增删改。你可以自定义这个选项,比如设置为一个空列表,表示你不需要默认的权限,但是这一操作必须在执行migrate命令之前

11. proxy

若为 true,表示使用代理模式的模型继承方式。

12. required_db_vendor

声明模型支持的数据库。Django默认支持sqlite, postgresql, mysql, oracle

13. indexes

接收一个应用在当前模型上的索引列表

from django.db import models

class Customer(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)

    class Meta:
        indexes = [
            models.Index(fields=['last_name', 'first_name']),
            models.Index(fields=['first_name'], name='first_name_idx'),
        ]

14. unique_together

等同于数据库的联合约束,无法应用多对多字段。
比如一张用户表,保存用户姓名、出生日期、地址等,现在有两个叫张伟的人,那么就可以使用联合唯一。

# 表示 name、birth_day、address 联合唯一,即不能在同一地方、同一时刻出生且都叫张伟
unique_together = (('name', 'birth_day', 'address'),)

14. verbose_name
给 Admin 提供人性化的名称,支持中文,如:
verbose_name = '用户'   # 那么 Admin 中显示的就是 用户

15. label
只读元数据,不可修改,相当于 polls.Question

3. 多表关系及参数

  • 一对一:Foreignkey 基础上加一个唯一索引 unique
  • 一对多:ForeignKey
  • 多对多:ManyToMany(两个一对多,两个 ForeignKey 相连)

3.1 一对多模型

一对多模型,如:员工部门表,一个部门可以有多个员工。那么 Django 怎么建立这种关系呢?

其实就是通过外键 ForeignKey 进行关联,在多的一方,字段指定外键即可

ForeignKey 字段参数

ForeignKey(ForeignObject) # ForeignObject(RelatedField)
        to,                         # 要进行关联的表名
        to_field=None,              # 要关联的表中的字段名称
        on_delete=None,             # 当删除关联表中的数据时,当前表与其关联的行的行为
                - models.CASCADE,删除关联数据,与之关联也删除
                - models.DO_NOTHING,删除关联数据,引发错误IntegrityError
                - models.PROTECT,删除关联数据,引发错误ProtectedError
                - models.SET_NULL,删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
                - models.SET_DEFAULT,删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
                - models.SET,删除关联数据,
                                a. 与之关联的值设置为指定值,设置:models.SET(值)
                                b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)

                        def func():
                            return 10

                        class MyModel(models.Model):
                            user = models.ForeignKey(
                                to="User",      # 关联 User 表
                                to_field="id"   # 关联 User 表 的  id 字段
                                on_delete=models.SET(func),)

        related_name=None,          # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
        related_query_name=None,    # 反向操作时,使用的连接前缀,用于替换【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
        
        limit_choices_to=None,      # 在Admin或ModelForm中显示关联数据时,提供的条件:
                # 如:
                - limit_choices_to={'nid__gt': 5}
                - limit_choices_to=lambda : {'nid__gt': 5}

                from django.db.models import Q
                - limit_choices_to=Q(nid__gt=10)
                - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
                - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
                
        db_constraint=True          # 是否在数据库中创建外键约束
        parent_link=False           # 在Admin中是否显示关联数据

示例:

class Department(models.Model):
    """部门表"""
    name = models.CharField(max_length=32)
    create_data = models.DateField(auto_now_add=True)   # 创建时间
    id_delete = models.BooleanField(default=False)      # 是否可删除
    
    class Meta:
        db_table = 'department'
    
    
class Employee(models.Model):
    """员工表"""
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    gender = models.IntegerField(default=0)
    salary = models.DecimalField(max_digits=8, decimal_places=2)    # max_digits 表示八个数字,包括两位小数,decimal_places 表示保留两位小数
    
    # null=True 表示可以为空, blank=True 表示 Django admin 后台可以输入空
    comment = models.CharField(max_length=300, null=True, blank=True)   
    hire_data = models.DateField(auto_now_add=True)
    department = models.ForeignKey('Department')        # 外键字段
    
    class Meta:
        db_table = 'employee'       # 数据表名字

Tips

在设置外键时,需要给子表(有外键的表)指定当主表(被连接的表)删除数据时,从表该如何处理。Django 通过设置 on_delete 属性来控制,它有三种值:

  • 删除时报错:DO_NOTHING/PROTECT
  • 级联删除:主表删除数据,从表与之关联的数据也将被删除:CASCADE
  • 设置默认值:SET_NULL/SET_DEFAULTset_null 仅在字段可以是 null 时才能使用

3.2 一对一模型

OneToOneField(ForeignKey)
    to,                         # 要进行关联的表名
    to_field=None               # 要关联的表中的字段名称
    on_delete=None,             # 当删除关联表中的数据时,当前表与其关联的行的行为
###### 对于一对一 ######
# 1. 一对一其实就是 一对多 + 唯一索引
# 2.当两个类之间有继承关系时,默认会创建一个一对一字段
# 如下会在A表中额外增加一个c_ptr_id列且唯一:
class C(models.Model):
    nid = models.AutoField(primary_key=True)
    part = models.CharField(max_length=12)

class A(C):
    id = models.AutoField(primary_key=True)
    code = models.CharField(max_length=1)
    
# 使用 OneToOneField 字段创建一对一模型
class Person(models.Model):
    name = models.CharField(max_length=32)
    o2o = models.OneToOneField(to='Person_detail', to_field='id', on_delete=models.CASCADE)
    
class Person_detal(models.Model):
    age = models.IntegerField()
    gender_choices = [(0, '男'), (1, '女')]
    gender = models.IntegerField(
        choices=gender_choices,
        default=0,
    )
    height = models.PositiveIntegerField()      # 正整数
    email = models.EmailField(max_length=64)

3.3 多对多模型

多对多其实就是两个一对多,通过两个外键将三张表相连,其中第三张表存储了前面两张表的对应关系。例如:名字和爱好表,一个人可以有多个爱好,一个爱好也可以是多个人有。

如何创建三张表:

  • 自动创建:ManyToMangField 字段自动创建,不能新增列
  • 手动创建:元 Meta,可以新增列
  • 手动加自动:元 Meta + ManyToMangField

多对多 ManyToManyField 字段参数:

ManyToManyField(RelatedField)
        to,                         # 要进行关联的表名
        related_name=None,          # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
        related_query_name=None,    # 反向操作时,使用的连接前缀,用于替换【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
        limit_choices_to=None,      # 在Admin或ModelForm中显示关联数据时,提供的条件:
                                    # 如:
                                            - limit_choices_to={'nid__gt': 5}
                                            - limit_choices_to=lambda : {'nid__gt': 5}

                                            from django.db.models import Q
                                            - limit_choices_to=Q(nid__gt=10)
                                            - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
                                            - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
                                    
        symmetrical=None,           # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段
                                    # 做如下操作时,不同的symmetrical会有不同的可选字段
                                        models.BB.objects.filter(...)

                                        # 可选字段有:code, id, m1
                                            class BB(models.Model):

                                            code = models.CharField(max_length=12)
                                            m1 = models.ManyToManyField('self',symmetrical=True)

                                        # 可选字段有: bb, code, id, m1
                                            class BB(models.Model):

                                            code = models.CharField(max_length=12)
                                            m1 = models.ManyToManyField('self',symmetrical=False)

        through=None,               # 自定义第三张表时,使用字段用于指定关系表
        through_fields=None,        # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表
                                        from django.db import models

                                        class Person(models.Model):
                                            name = models.CharField(max_length=50)

                                        class Group(models.Model):
                                            name = models.CharField(max_length=128)
                                            members = models.ManyToManyField(
                                                Person,
                                                through='Membership',
                                                through_fields=('group', 'person'),
                                            )

                                        class Membership(models.Model):
                                            group = models.ForeignKey(Group, on_delete=models.CASCADE)
                                            person = models.ForeignKey(Person, on_delete=models.CASCADE)
                                            inviter = models.ForeignKey(
                                                Person,
                                                on_delete=models.CASCADE,
                                                related_name="membership_invites",
                                            )
                                            invite_reason = models.CharField(max_length=64)
        db_constraint=True,         # 是否在数据库中创建外键约束
        db_table=None,              # 默认创建第三张表时,数据库中表的名称

自动创建

class UserInfo(models.Model):
    username = models.CharField(max_length=32, verbose_name='用户名')
    m = models.ManyToManyField(
        to = 'Tag',
        related_name = 'bb',
    )
    
class Tag(models.Model):
    title = models.CharField(max_length=32)

手动创建

class UserInfo(models.Model):
    username = models.CharField(max_length=32, verbose_name='用户名')
    
class Tag(models.Model):
    title = models.CharField(max_length=32)
    
# 手动创建第三张表
class UserToTag(models.Model):
    tid = models.AutoField(primary_key=True)
    u = models.ForeignKey('UserInfo', on_delete=models.CASCADE)     # 通过外键相连
    t = models.ForeignKey('Tag', on_delete=models.CASCADE)

    #  联合唯一索引
    class Meta:
        unique_together = [
            ('u', 't'),
        ]

手动加自动

class UserInfo(models.Model):
    username = models.CharField(max_length=32, verbose_name='用户名')
    m = models.ManyToManyField(
        through = 'UserToTag',      # 指定第三张表,不自动创建
        through_fields = ['u', 't']     # 指定第三张表的字段
    )
    
class Tag(models.Model):
    title = models.CharField(max_length=32)
    
# 手动创建第三张表
class UserToTag(models.Model):
    tid = models.AutoField(primary_key=True)
    u = models.ForeignKey('UserInfo', on_delete=models.CASCADE)     # 通过外键相连
    t = models.ForeignKey('Tag', on_delete=models.CASCADE)

    #  联合唯一索引
    class Meta:
        unique_together = [
            ('u', 't'),
        ]

3.4 自关联模型

自关联模型,即表中的某列关联表中另一列。最典型的自关联模型就是地区表(省市县三级联动)省的 pid 为 null,市的 pid 为省的 id,县的 pid 为市的 id。具体如下:

自关联表现形式

  • 省的 parent_id 为 null
  • 市的 parent_id 为对应省的 id
  • 区的 parent_id 为对应市的 id
  1. 数据库设计 models.py
class Area(models.Model):
    name = models.CharField(max_length=32, verbose_name='名称')
    parent = models.ForeignKey('self',          # 自关联字段的外键指向自身,也可以是 Area
                               verbose_name='上级行政区划',
                               on_delete=models.SET_NULL,
                               related_name='subs',
                               null=True,
                               blank=True
                               )

    class Meta:
        db_table = 'tb_areas'       # 自定义表名
        verbose_name = '行政区划'   # admin 中显示的名称
        verbose_name_plural = '行政区划'

    def __str__(self):
        return self.name

自关联模型,最核心的地方就是自己关联自己 self/Area,用一张表做两张表才能做的事。

  1. 路由系统 app/urls.py
from django.urls import path
from app import views

urlpatterns = [
    path('area/', views.area, name='area'),
    path('getPro/', views.getPro, name='getArea'),
    path('getCity/', views.getCity, name='getCity'),
    path('getDis/', views.getDis, name='getDis'),
]
  1. 视图函数 views.py
from django.shortcuts import render, HttpResponse
from app import models
from django.http import JsonResponse

# 访问 http://127.0.0.1:8000/app/area/,返回 area.html
def area(request):
    return render(request, 'area.html')


def getPro(request):
    """获取省份信息"""
    pro_list = models.Area.objects.filter(parent_id=None)       # 省份的 parent_id 为 None
    res = []
    for i in pro_list:
        res.append([i.id, i.name])
    print(res)      # [[1, '广东省'], [7, '湖南省']]
    return JsonResponse({'pro_list': res})      # JsonResponse 打包成 json 格式字符串


def getCity(request):
    """获取市信息"""
    city_id = request.GET.get('city_id')
    city_list = models.Area.objects.filter(parent_id=int(city_id))
    res = []
    for i in city_list:
        res.append([i.id, i.name])

    print('city', res)      #  [[2, '深圳市'], [3, '广州市'], [6, '湛江市']]
    return JsonResponse({'city_list': res})


def getDis(request):
    """获取区信息"""
    dis_id = request.GET.get('dis_id')
    dis_list = models.Area.objects.filter(parent_id=int(dis_id))
    res = []
    for i in dis_list:
        res.append([i.id, i.name])
    return JsonResponse({'dis_list': res})
  1. 模板 area.html
{% load static %}



    
    Title


    

    

    





当访问 http://127.0.0.1:8000/app/area/ 时,会向 /app/getPro/ 发送 ajax 请求,请求成功获得包含省份 id、name 的信息 {'pro_list': [[1, '广东省'],[7, '湖南省']]}。取出数据遍历循环,添加到 option 标签中,从而获得省份信息。

当我们改变省份时,将上一次省份的市、区信息清空,避免添加重复。获取当前省份的 id,并向 /app/getDis/ 发送 ajax 请求,从而获得相应市的信息,同理区也是一样。

  1. 添加数据 views.py

添加数据,可以用 pycharm 自带的数据库添加,也可以手动添加:

def add(request):
    models.Area.objects.create(id=7, name='湖南省', parent_id=None)
    models.Area.objects.create(id=8, name='长沙市', parent_id=7)
    models.Area.objects.create(id=9, name='雨花区', parent_id=8)

    return HttpResponse('添加成功')

参考博客:

  • Django实现省市县级联菜单
  • django 自关联之 - 省市县
  • Django实现 省 市 县 自关联的下拉级联
  • django一对多、多对多模型、自关联的建立

3.5 知识点补充

Ajax 的 get() 方法,它可以向后台发送 ajax 请求,请求成功可调用回调函数。如果需要在出错时执行函数,请使用 $.ajax,它是 $.ajax 简化版本。

语法

$(selector).get(url, data, success(response, status, xhr), dataType);   // 除了 url 必需,其他都可选,status 为请求状态,xhr - 包含 XMLHttpRequest 对象
$('button').click(function(){
    get('/app/getdata/', function(arg){});
});

3.6 Model 常用设计模型示例及方法

3.6.1 Model 设计

总共四张表,分别是班级、学生、学生个人信息以及老师表,之间的关系如下:

class Class(models.Model):
    """班级表"""
    class_name = models.CharField(max_length=32)
    create_data = models.DateField()

    def __str__(self):
        return self.class_name

    class Meta:
        verbose_name = '班级'


class Student(models.Model):
    """
    学生表
    一每个学生都有对应的个人信息(一对一),一个班级可以有多个学生(一对多)
    """
    student_name = models.CharField(max_length=32)

    # 一对多,与班级关联
    sc = models.ForeignKey(to='Class', to_field='id', related_name='stu', on_delete=models.CASCADE)

    # 一对一,与学生个人信息关联
    detail = models.OneToOneField('StudentDetail', to_field='id', on_delete=models.CASCADE)

    class Meta:
        verbose_name = '学生'

    def __str__(self):
        return self.student_name


class StudentDetail(models.Model):
    """学生个人信息表"""
    age = models.IntegerField()
    gender_choices = [(0, '男'), (1, '女')]
    gender = models.IntegerField(
        choices=gender_choices,
        default=0,
    )
    height = models.PositiveIntegerField()      # 正整数
    email = models.EmailField(max_length=64)


class Teacher(models.Model):
    """
    老师表
    一个老师可以教多个班,一个班也可以有多个老师
    """
    teacher_name = models.CharField(max_length=32)
    tc = models.ManyToManyField(to='Class', related_name='b')

    class Meta:
        verbose_name = '老师'

    def __str__(self):
        return self.teacher_name
  1. 老师表 app_teacher

  1. 学生个人信息表 app_student_detail

  1. 学生表 app_student

  1. 班级表 app_class

  1. 老师、班级关系表(第三张表)app_teacher_tc

3.6.2 查询操作

def query(request):
    ########### 一对一 #####################
    # 正向查询(根据外键字段)
    # 根据学生名字查询其个人信息
    # obj = models.Student.objects.filter(student_name='rose')[0]
    # print(obj)
    # print(obj.detail.age, obj.detail.gender, obj.detail.height, obj.detail.email)
    # # 17 1 170 [email protected]


    # 反向查询(根据要查询的数据表名)
    # 根据邮箱查询学生名字
    # obj2 = models.StudentDetail.objects.filter(email='[email protected]')[0]
    # print(obj2)         # ]>
    #
    # print(obj2.student.student_name)        # rose


    ############## 一对多(班级表和学生表) ##################
    # 正向查询
     # 根据学生名查询所属班级
    # obj3 = models.Student.objects.get(student_name='rose')
    # print(obj3.sc_id)   # 1
    # print(type(obj3.sc))      # 
    # print(obj3.sc.class_name)   # 一班


    # 反向查询
    # 二班有哪些学生
    # obj4 = models.Class.objects.filter(class_name='二班')[0]
    # print(obj4)
    # # res = obj4.student_set.all()     # 如果外键字段没有设置 related_name 就用 表名_set
    # res = obj4.stu.all()
    # print(res)       # , ]>
    # for i in res:
    #     print(i.student_name)   # john、lila

    # 方法二
    # ret = models.Student.objects.filter(sc=obj4).values('student_name')   # 字典形式
    # print(ret)      # 
    # for i in ret:
    #     print(i['student_name'])    # john、lila

    # # 双下划线
    # 正向
    obj4 = models.Class.objects.filter(class_name='二班').values('stu__student_name')

    # 反向
    obj5 = models.Student.objects.filter(sc__class_name='二班').values('student_name')



############################### 多对多(老师表与班级表) ############################################
    # 查看 孙老师教哪几个班
    # 正向查询(通过多对多字段)

    # obj5 = models.Teacher.objects.filter(teacher_name='孙老师')[0]
    # ret = obj5.tc.all()
    # print(ret)      # , ]>
    # for i in ret:
    #     print(i.class_name)

    # 查看一班有哪几个老师
    # 反向查询
    # obj5 = models.Class.objects.filter(class_name='一班')[0]
    # ret = obj5.b.all()
    # print(ret)          # < QuerySet[ < Teacher: 孙老师 >, < Teacher: 刘老师 >] >
    # for i in ret:
    #     print(i.teacher_name)       # 孙老师、刘老师

    # # 双下划线
    # # 如果没有设置 related_name = b,那么就是 values('teacher__name`) 即要查的表的表名__要查询的字段
    # obj6 = models.Class.objects.filter(class_name='一班').values('b__teacher_name')
    # print(obj6)     # 

    # # 正向  tc = models.ManyToManyField(to='Class', related_name='b')
    # obj6 = models.Teacher.objects.filter(tc__class_name='一班').values('teacher_name')

    return HttpResponse('查询成功!')

3.6.3 总结

一对一、一对多、多对多,最好都设置好 related_name 参数,没有设置时,反向查找用 **要查询的命名.查询字段 或 _set.all()**。

查询分为:

  • 基于对象查询(相当于 SQL 中子查询)
    • 先获取相关对象:models.Models.objects.filter(过滤字段)
    • 再通过对象(正向、反向)查询相关字段
  • 基于 QuerySet 和双下划线查询(相当于 SQL 中连表查询)
    • 前面是过滤条件,后面跟要显示(查询)的字段
    • models.Model.objects.filter(过滤字段).values(要显示的字段)

一对一

  • 正向查询:根据 OneToOneField 字段查询
  • 反向查询:根据 要查询的命名.查询字段查询
# detail = models.OneToOneField('StudentDetail', to_field='id', on_delete=models.CASCADE)
# 正向
obj = models.Student.objects.filter(student_name='rose')[0]
print(obj.detail.age)

# 反向
obj2 = models.StudentDetail.objects.filter(email='[email protected]')[0]
print(obj2.student.student_name)    # 这里没设置 related_name 因此用表名

一对多

  • 正向查询:根据外键字段查询
  • 反向查询
    • 设置了related_name,就用这个名字查询
    • 没有设置,用表名_set 方式查询
  • 双下划线查询
    • 正向:filter(条件).values(要查询的表名__要查询字段)
    • 反向:filter(外键字段__条件).values(要查询字段)
# sc = models.ForeignKey(to='Class', to_field='id', related_name='stu', on_delete=models.CASCADE)
# 正向
obj3 = models.Student.objects.get(student_name='rose')
print(obj3.sc.class_name)   # 根据外键字段 sc 查询

# 反向
obj4 = models.Class.objects.filter(class_name='二班')[0]
# res = obj4.student_set.all()  # 没设置 related_name,用表名_set
res = obj4.stu.all()        # 用 related_name 名字

# 双下划线
# 正向
obj5 = models.Class.objects.filter(class_name='二班').values('stu__student_name')

# 反向
obj6 = models.Student.objects.filter(sc__class_name='二班').values('student_name')

多对多

  • 正向查询:ManyToManyField 字段查询
  • 反向查询:表名_set、related_name名字查询
  • 双下划线与一对多一样
# tc = models.ManyToManyField(to='Class', related_name='b')
# 正向
obj5 = models.Teacher.objects.filter(teacher_name='孙老师')[0]
print(obj5.tc.all())

# 反向
obj5 = models.Class.objects.filter(class_name='一班')[0]
print(obj5.b.all())

参考博客:django 一对一、一对多、多对多操作、常用方法

4. 模型继承

Django中所有的模型都必须继承 django.db.models.Model 模型,同样地我们也可以自己创建一个父模型用来保存公共部分,子模型继承父模型。这样的好处就是有时会省去很多重复代码。

同样地 Django 也支持继承多个父类。

Django 中三种继承方式:

  • 抽象基类:被用来继承的模型被称为 Abstract base classes,将子类共同的数据抽离,供子类复用,它不会创建实际的数据表
  • 多表继承: Multi-table inheritance,每一个模型都有自己的数据库表
  • 代理模型:如果你只想修改模型的Python层面的行为,并不想改动模型的字段,可以使用代理模型

4.1 抽象基类

只需在模型中元类添加 abstract=True,即可将模型转换为抽象基类。但是它不会创建实际数据库表,只能用来被继承。

from django.db import models

class CommonInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)

    class Meta:
        abstract = True


class User(CommonInfo):
    email = models.EmailField(max_length=60)

模型 User 将会有 username、password、email 三个字段。

抽象基类元类

如果子类没有元类,那么它将继承基类的元类,同样地子类也可以对基类的元类进行拓展:

class CommonInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)

    class Meta:
        abstract = True
        ordering = ['username']


class User(CommonInfo):
    email = models.EmailField(max_length=60)

    # 继承基类的元类,并进行拓展
    class Meta(CommonInfo.Meta):
        db_table = 'username'
  • 基类有元类,子类也没有的话,直接继承
  • 基类有元类,子类也有,直接覆盖
  • 子类可以添加额外元数据
  • 基类的 abstract=true 不会被继承
  • 基类的 db_table 元数据无效,因为抽象基类不会创建数据表

related_name 和 related_query_name

若抽象基类中存在 ForeignkManyToManyField 字段,且设置了 related_namerelated_query_name 参数,其子类也将继承这两个参数。但是在查询时就会出现错误。

例如,对于 app01/models.py 中:

class Base(models.Model):
    m2m = models.ManyToManyField(User, related_name="%(app_label)s_%(class)s_related", related_query_name="%(app_label)s_%(class)ss")
    class Meta:
        abstract = True
        
class ChildA(Base):
    pass

class ChildB(Base):
    pass
  • app01.ChildA.m2m 字段:反向关系名(reverse name)为 app01_childa_related;反向查询名(reverse query name)为 app01_childas
  • app01.ChildB.m2m 字段:分别为: app01_childb_relatedapp01_childbs
  • 如果没有设置 related_name 或 related_query_name 就没有上述情况

4.2 多表继承

父类和子类都是可正常使用的模型,且都有自己的数据表,其本质为 一对一 关系。

class UserInfo(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    
    
class PersonalDetail(UserInfo):
    email = models.EmailField()
    description = models.CharField(max_length=4000)

PersonalDetail 继承 UserInfo,它会继承父类所有的字段,两个模型内部是一对一关系,如下所示:

>>> from polls.models import UserInfo, PersonalDetail
>>> q = PersonalDetail.objects.all()

>>> for i in q:
...     i.name
...
'rose'
'lina'

多表继承之元类

多表继承中子类不会继承父类的元类,但是有两个元类数据除外: ordering、get_latest_by,因此若希望不继承父类的元类的所有元数据,就需要指定或重写这两个元数据:

class PersonalDetail(UserInfo):
    email = models.EmailField()
    description = models.CharField(max_length=4000)

    class Meta:
    # 重写 ordering,移除父类元类的影响
        ordering = []

4.3 多重继承

与 Python 一样,模型也可以继承多个父类模型,当有多个父类都含有 Meta 类时,那么只有第一个父类的会被继承,其余将被忽略掉。

Tips

  • 尽量避免不用多重继承
  • 当父类有相同 id 主键字段时,将会报错,因此需要给父类显示地添加 AutoField 字段。
class Base1(models.Model):
    base1_id = models.AutoField(primary_key=True)
    pass

class Base2(models.Model):
    base2_id = models.AutoField(primary_key=True)
    pass

class Child(Base1, Base2):
    pass

4.4 代理模型

代理模型就是对源模型的一种代理,可以创建、删除、更新代理模型的实例,与源模型用的是同一张数据表,但是 它对源模型数据没有影响

要想将一个模型设置为代理模型,只需在 Meta 类中设置 proxy=True

class A(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()


class B(A):
    # 代理模型
    class Meta:
        proxy = True
        ordering = ['age']

    def do_something(self):
        pass

只会创建一张数据表,同时代理模型也可以操作数据表:

from .models import A, B


>>> A.objects.create(name='rose', age=18)

>>> q = B.objects.get(name='rose')
>>> q.age
18

# 对代理模型排序,按照年龄从小到大排列,被代理的模型,查询不会排序

obj_list = B.objects.all()
for obj in obj_list:
    print(obj.age)      # 17、18 、20
    print(obj.name)     # lila、rose、tom

Tips

  • 代理模型必须继承自一个非抽象的基类,并且不能同时继承多个非抽象基类;
  • 代理模型可以同时继承任意多个抽象基类,前提是这些抽象基类没有定义任何模型字段。
  • 代理模型可以同时继承多个别的代理模型,前提是这些代理模型继承同一个非抽象基类。

4.5 总结

  • 抽象基类不会创建数据表,直接设置 abstract=True 即可设置为抽象基类
  • 多表继承实质是一对一关系,不会继承基类 Meta,除 ordering 和 get_latest_by
  • 多表继承最好不要用
  • 代理模型与被代理模型通用一张数据表,可以对代理数据进行创建、删除、修改等操作,但对源模型无影响
  • 若基类是非抽象基类,基类与子类有相同名字的字段,那么将会覆盖子类(抽象基类除外)
# A 中的 name 将会覆盖 B 中 的 name,除非 A 是抽象基类
class A(models.Model):
    name = models.CharField(max_length=32)

    # class Meta:
    #     abstract = True

class B(A):
    name = models.CharField(max_length=32)

组织模型

当模型有很多时,我们最好将模型分割开来,分类存储,这样有利于组织。我们可以利用包来组织模型。

在应用中创建一个名为 models 的包(pycharm 可以直接创建包,不需要手动创建 init.py文件),然后将模型分类到各个 .py 文件中,最后将其导入 __init__.py 文件中:

# app/models/__init__.py
from polls.models.modelone import User

5. 聚合分组

5.1 聚合 aggregate

aggregate() 返回一个字典,键为聚合值标识符,值为计算处理的聚合值。

使用时需要先导入内置函数:

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

示例:

from django.db.models import Avg, Sum, Max, Min, Count
>>> models.UserInfo.objects.all.aggregate(avg_age=Avg('age'))   # 指定键为 avg_age,也可以用默认的
{'avg_age': 18}

多个聚合:

from django.db.models import Avg, Sum, Max, Min, Count
>>> models.UserInfo.objects.all.aggregate(avg_age=Avg('age'), max_length=Max('height')) 
{'avg_age': 18, 'max_length': 182}

5.2 分组 annotate

ORM 中 values()values_list() 选字段,就相当于原生 SQL 中 select 什么字段:

ret = models.Employee.objects.all()    # 相当于 select * from employee
"""
SELECT `employee`.`id`, `employee`.`name`, `employee`.`age`, `employee`.`salary`, `employee`.`province`, `employee`.`dept` FROM `employee` LIMIT 21; args=()
"""

ret = models.Employee.objects.all().values("dept", "age")
"""
SELECT `employee`.`dept`, `employee`.`age` FROM `employee` LIMIT 21; args=()
"""

select选中什么字段,group by 就只能是什么字段:

mysql> select name, Avg(salary) from app_employee group by name;
+------+-------------+
| name | Avg(salary) |
+------+-------------+
| rose |      2000.1 |
| lila |     3000.45 |
| john |     4000.56 |
| tom  |     5005.78 |
+------+-------------+

  1. 连表分组查询

    按照部门分组,查询每个部门的平均薪水:

# ORM 操作
# 前一个 values() 表示按照什么分组,后一个表示要显示的字段
>>> employee_list = models.Employee.objects.values('dept__name').annotate(avg=Avg('salary')).values('dept__name', 'avg')
>>> print(employee_list)    
    

# 对应 SQL 语句
SELECT `app_department`.`name`, AVG(`app_employee`.`salary`) AS `avg` FROM `app_employee` INNER JOIN `app_department` ON (`app_employee`.`dept_id` = `app_department`.`id`) GROUP BY `app_employee`.`name`, `app_department`.`name` ORDER BY NULL  LIMIT 21; args=()
  1. ORM 分组查询

按照国家分组,查询每个国家的平均薪水:

>>> employee_list = models.Employee.objects.values('addr').annotate(avg=Avg('salary')).values('addr', 'avg')
>>> print(employee_list)    

 
SELECT `app_employee`.`addr`, AVG(`app_employee`.`salary`) AS `avg` FROM `app_employee` GROUP BY `app_employee`.`addr` ORDER BY NULL  LIMIT 21; args=()

Tips

  • ORM 中的连表查询(跨表)相当于 SQL 中的连表(inner john)查询
  • ORM 查询,第一个 values(fields) 为分组字段,第二个 values(fileds1, fields2) 为要显示的字段
  • select选中什么字段,group by 就只能是什么字段

5. F 查询和 Q 查询

5.1 F 查询

上面我们都是在比较字段与某个常亮,要想两个字段进行比较,那就要用到 F 查询

  1. 查询书的价格大于评论数的书籍
from django.db.models import F
models.Book.objects.filter(price__gt=F('comment_count'))        
  1. 将书的价格整体提升 5 元
models.Book.objects.update(price=F('price')+5)
  1. F 对象和 F 对象以及常量之间可以进行加减乘除、取模等
models.Book.objects.update(price=F('price')*5)

5.2 Q 查询

Q 构建搜索条件(在这里解决了或、与的问题)

  1. Q 对象可以对关键字参数进行封装,从而更好地应用多个查询
obj1 = models.StudentDetail.objects.filter(Q(age=18))       # 构建过滤条件
  1. 组合使用 &、| 、~ 操作符
obj2 = models.StudentDetail.objects.filter(Q(age=18) | Q(age=19))   # 解决了 Django API 中没有或的问题
obj3 = models.StudentDetail.objects.filter(Q(age=18) & Q(age=19))   # 解决了 Django API 中没有与的问题
obj3 = models.StudentDetail.objects.filter(~ Q(age=19)) # 解决了 Django API 中没有非的问题

手动创建逻辑关系

q1 = Q()        # 创建 Q 对象
q1.connector = 'OR'     # q1 内部为 or 关系,即 age=18 或 age =19
q1.children.append(('age', 18))
q1.children.append(('age', 19))

q2 = Q()        # 创建 Q 对象
q2.connector = 'AND'    # q2 内部为 and 关系
q2.children.append(('gender', 0))

q3 = Q()            # 创建 Q 对象
q3.add(q1, 'AND')       # 将 q1、q2 都添加到 q3 中,创建总的过滤条件
q3.add(q2, 'AND')
obj = models.StudentDetail.objects.filter(q3)
print(obj)  # ]>

# 原生 SQL
SELECT "app_studentdetail"."id", "app_studentdetail"."age", "app_studentdetail"."gender", "app_studentdetail"."height", "app_studentdetail"."email" FROM "app_studentdetail" WHERE (("app_studentdetail"."age" = 18 OR "app_studentdetail"."age" = 19) AND "app_studentdetail"."gender" = 0)  LIMIT 21;
        
  1. Q 对象 可以和关键字参数一起查询使用,不管一定要在关键字参数前面
models.StudentDetail.filter(Q(age=18), email__startswith='123')
  1. 应用
import datetime
obj = models.Class.objects.filter(Q(create_data=datetime.date(2019,2,15)))
print(obj)

你可能感兴趣的:(Django Model)