3、Django开发总结:Model模型的介绍、字段属性、字段选项(参数)与设计详解

3、Django开发总结:Model模型的介绍、字段属性、字段选项(参数)与设计详解_第1张图片

 

Model模型

Model (模型) 简而言之即数据模型。模型不是数据本身(比如数据库里的数据),而是抽象的描述数据的构成和逻辑关系。每个Django model实际上是个类,继承了models.Model。每个Model应该包括属性,关系(比如单对单,单对多和多对多)和方法。当定义好Model模型后,Django的接口会自动在数据库生成相应的数据表(table)。这样就不用自己用SQL语言创建表格或在数据库里操作创建表格了。

案例:书与出版社的实际案例。出版社有名字和地址。书有名字,描述和添加日期。利用ForeignKey定义出版社与书单对多的关系,因为一个出版社可以出版很多书。定义了如下模型

# django_foundation_pro/foundation_app/models.py

from django.db import models

class Publisher(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField()

    def __str__(self):
        return self.name

class Book(models.Model):
    name = models.CharField(max_length=30)
    description = models.TextField(blank=True, null=True)
    publisher = ForeignKey(Publisher)
    add_date = models.DateField()

    def __str__(self):
        return self.name

模型创建好后,当运行python manage.py migrate 创建表格的时候会遇到错误,错误原因如下:

  1. CharField里的max_length选项没有定义
  2. ForeignKey(Publisher)里的on_delete选项有没有定义

所以当定义Django模型Model的时候,一定要十分清楚两件事:

  1. 这个Field是否有必选项, 比如CharField的max_length和ForeignKey的on_delete选项是必须要设置的。
  2. 这个Field是否必需(blank = True or False),是否可以为空 (null = True or False)。这关系到数据的完整性。

其实在上述案例中还有一个隐藏的错误,即TextField(blank = True, null = True)。blank = True 表示字段不是必需的,在客户端不是必填选项。null = True表示这个字段可以存储为null空值。但是Django对于空白的CharField和TextField永远不会存为null空值,而是存储空白字符串'',所以正确的做法是设置default=''。

下表才是一个比较正确的Django模型(Model):

# django_foundation_pro/foundation_app/models.py

from django.db import models

class Publisher(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Book(models.Model):
    name = models.CharField(max_length=30)
    description = models.TextField(blank=True, default="")
    # blank = True 表示字段不是必需的,在客户端不是必填选项
    # null = True 表示这个字段可以存储为null空值。
    publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
    add_date = models.DateField(auto_now_add=True)

    def __str__(self):
        return self.name

修改模型后,运行python manage.py makemigrations和python manage.py migrate这两个命令,前者检查模型有无变化,后者将变化迁移至数据表,Django会在数据库(默认sqlite)中生成或变更由appname_modelname组成的数据表,本例两张数据表分别为foundation_app_publisher和foundation_app_book。

3、Django开发总结:Model模型的介绍、字段属性、字段选项(参数)与设计详解_第2张图片

模型类属性命名限制

参考:https://docs.djangoproject.com/en/4.1/topics/db/models/

  1. 不能是python的保留关键字。
  2. 不允许使用连续的下划线,这是由django的查询方式决定的。例如:b__title = models.CharField(max_length=20)就不行。报错信息:foundation_app.DemoModel.b__title: (fields.E002) Field names must not contain "__".
  3. 定义属性时需要指定字段类型,通过字段类型的参数指定选项,语法如下:

属性名=models.字段类型(选项)

例如

btitle = models.CharField(max_length=20)

模型的组成

一个标准的Django模型分别由模型字段、META选项和方法三部分组成。Django官方编码规范建议按如下方式排列:

  • 定义的模型字段:包括基础字段和关系字段
  • 自定义的Manager方法:改变模型
  • class Meta选项: 包括排序、索引等等(可选)。
  • def __str__():定义单个模型实例对象的名字(可选)。
  • def save():重写save方法(可选)。
  • def get_absolute_url():为单个模型实例对象生成独一无二的url(可选)
  • 其它自定义的方法。

Django Model的字段(Field)以及可选项和必选项

models.Model提供的常用模型字段包括基础字段和关系字段。

参考:https://docs.djangoproject.com/en/4.1/ref/models/fields/

字段选项(字段参数)

参考:https://docs.djangoproject.com/en/4.1/topics/db/models/#field-options

通过选项实现对字段的约束,选项如下:

  1. default   默认值。设置默认值。
  2. primary_key   若为True,则该字段会成为模型的主键字段,默认值是False,一般作为AutoField的选项使用。
  3. unique   如果为True, 这个字段在表中必须有唯一值,默认值是False。
  4. db_index 若值为True, 则在表中会为此字段创建索引(相当于目录),默认值是False。
  5. db_column 字段的名称,如果未指定,则使用属性的名称。
  6. null 如果为True,表示允许为空,默认值是False。
  7. blank

如果为True,则该字段允许为空白,默认值是False。(一般是后台管理处输入是否可为空格等)

【对比】:null是数据库范畴的概念,blank是后台管理页面表单验证范畴的。

【经验】:当修改模型类之后,如果添加的选项不影响表的结构,则不需要重新做迁移,商品的选项中default和blank不影响表结构。

  1. choices

一个2元组的序列,用作此字段的选项。如果给定了这个选项,默认的表单小部件将是一个选择框,而不是标准文本字段,并将选择限制为给定的选项。

示例如下:

YEAR_IN_SCHOOL_CHOICES = [
('FR', 'Freshman'),
('SO', 'Sophomore'),
('JR', 'Junior'),
('SR', 'Senior'),
('GR', 'Graduate'),
]

例1:btitle = models.CharField(max_length=20,unique=True) #该字段不能重复

例2:btitle = models.CharField(max_length=20,db_column='title') #自定义表字段的名称为title

from django.db import models
# 设计和表对应的类,模型类

# 图书类
class BookInfo(models.Model):
    # 图书名称,CharField说明是一个字符串,max_length指定字符串的最大长度
    btitle = models.CharField(max_length=20,unique=True) #该字段不能重复
    # 出版日期,DateField说明是一个日期类型
    bpub_date = models.DateField()
    # 整型,阅读量
    bread = models.IntegerField(default=0)
    # 整型,评论量
    bcomment = models.IntegerField(default=0)
    # 布尔类型,删除标记
    is_delete = models.BooleanField(default=False)

    def __str__(self): #重定义系统的str方法,让它返回对应图书的名字
        return self.btitle

class HeroInfo(models.Model):
# 英雄名称
    hname = models.CharField(max_length=20)
    # 性别,BooleanField说明是bool类型,default指定默认值,False代表男
    hgender = models.BooleanField(default=False)
    # 备注
    hcomment = models.CharField(max_length=128)

    # 关系属性 hbook,建立图书类和英雄人物类之间的一对多关系
    # 关系属性对应的表的字段名格式: 关系属性名_id
    hbook = models.ForeignKey('BookInfo', on_delete=models.CASCADE) #对应BookInfo表的主键ID
    # 删除标记
    is_delete = models.BooleanField(default=False)

    def __str__(self): #返回英雄名
        return self.hname

基础字段

AutoField () 自动增长的IntegerField,通常不用指定,不指定时Django会自动创建属性名为id的自动增长属性。

CharField() 字符字段

  • max_length = xxx or None  max_length表示最大字符个数,必填
  • 如不是必填项,可设置blank = True和default = ''
  • 如果用于username, 想使其唯一,可以设置unique = True
  • 如果有choice选项,可以设置 choices = XXX_CHOICES

TextField() 适合大量文本字段

  • max_length = xxx
  • 如不是必填项,可设置blank = True和default = ''
  • 一般超过4000个字符时使用

DateField() and DateTimeField() and TimeField()日期与时间字段

  • 一般建议设置默认日期default date.
  • For DateField: default=date.today - 先要from datetime import date
  • For DateTimeField: default=timezone.now - 先要from django.utils import timezone
  • 对于上一次修改自动记录日期(last_modified date),可以设置: auto_now=True
  • 参数auto_now表示每次保存对象时,自动设置该字段为当前时间,用于"最后一次修改"的时间戳,它总是使用当前日期,默认为false。
  • 参数auto_now_add表示当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为false。
  • 参数auto_now_add和auto_now是相互排斥的,组合将会发生错误。

EmailField() 邮件字段

  • 如不是必填项,可设置blank = True和default = ''
  • 一般Email用于用户名应该是唯一的,建议设置unique = True

IntegerField()  整数

SlugField()

URLField()

BooleanField()布尔字段

  • 可以设置blank = True or null = True
  • 对于BooleanField一般建议设置default = True or False

FileField(upload_to=None, max_length=100) - 文件字段

  • upload_to = "/some folder/"   上传文件夹路径,必填
  • max_length = xxxx  文件最大长度

ImageField(upload_to=None, height_field=None, width_field=None, max_length=100,)

  • upload_to = "/some folder/"   指定上传图片路径,必填
  • 其他选项是可选的.
  • 继承于FileField,对上传的内容进行校验,确保是有效的图片。

ForeignKey(to, on_delete, **options) - 单对多关系

  • to必需指向其他模型,比如 Book or 'self'. 必填
  • 必需指定on_delete options(删除选项): i.e, "on_delete = models.CASCADE" or "on_delete = models.SET_NULL" .必填
  • 可以设置"default = xxx" or "null = True" .
  • 如果有必要,可以设置 "limit_choices_to = "

如下示例:

staff_member = models.ForeignKey( User, on_delete=models.CASCADE, limit_choices_to={'is_staff': True}, )

  • 可以设置 "related_name = xxx" 便于反向查询。

ManyToManyField(to, **options) - 多对多关系

  • to 必需指向其他模型,比如 User or 'self' .必填
  • 设置 "symmetrical = False " if 多对多关系不是对称的
  • 设置 "through = 'intermediary model' " 如果需要建立中间模型来搜集更多信息
  • 可以设置 "related_name = xxx" 便于反向查询。

NullBooleanField  支持Null、True、False三种值。

DecimalField(max_digits=None, decimal_places=None) 十进制浮点数。

  • 参数max_digits表示总位。
  • 参数decimal_places表示小数位数。
  • 常用于商品价格(精确度高)。

FloatField    浮点数。参数同上(精确度比上一个低)

关系字段

OneToOneField(to, on_delete=xxx, options) - 单对单关系

  1. to必需指向其他模型,比如 Book or ‘self’。
  2. 必需指定on_delete选项(删除选项): i.e, “on_delete = models.CASCADE” or “on_delete = models.SET_NULL”。
  3. 可以设置 “related_name = xxx” 便于反向查询。

ForeignKey(to, on_delete=xxx, options) - 单对多关系

  1. to必需指向其他模型,比如 Book or ‘self’ .
  2. 必需指定on_delete选项(删除选项): i.e, “on_delete = models.CASCADE” or “on_delete = models.SET_NULL” .
  3. 可以设置”default = xxx” or “null = True” ;
  4. 如果有必要,可以设置 “limit_choices_to = “,
  5. 可以设置 “related_name = xxx” 便于反向查询。

ManyToManyField(to, options) - 多对多关系

  1. to 必需指向其他模型,比如 User or ‘self’ .
  2. 设置 “symmetrical = False “ 表示多对多关系不是对称的,比如A关注B不代表B关注A
  3. 设置 “through = 'intermediary model' “ 如果需要建立中间模型来搜集更多信息。
  4. 可以设置 “related_name = xxx” 便于反向查询。

示例:一个人加入多个组,一个组包含多个人,需要额外的中间模型记录加入日期和理由。

# django_foundation_pro/foundation_app/models.py

from django.db import models

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

    def __str__(self):
        return self.name

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

    def __str__(self):
        return self.name

class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateField()
    invite_reason = models.CharField(max_length=64)

对于OneToOneField和ForeignKey, on_delete选项和related_name是两个非常重要的设置,前者决定了关联外键删除方式,后者决定了模型反向查询的名字。

on_delete删除选项

Django提供了如下几种关联外键删除选项, 可以根据实际需求使用。

  1. CASCADE:级联删除。当删除publisher记录时,与之关联的所有 book 都会被删除。
  2. PROTECT: 保护模式。如果有外键关联,就不允许删除,删除的时候会抛出ProtectedError错误,除非先把关联了外键的记录删除掉。例如想要删除publisher,那要把所有关联了该publisher的book全部删除才可能删publisher。
  3. SET_NULL: 置空模式。删除的时候,外键字段会被设置为空。删除publisher后,book 记录里面的publisher_id 就置为null了。
  4. SET_DEFAULT: 置默认值,删除的时候,外键字段设置为默认值。
  5. SET(): 自定义一个值。
  6. DO_NOTHING:什么也不做。删除不报任何错,外键值依然保留,但是无法用这个外键去做查询。

related_name选项

related_name用于设置模型反向查询的名字,非常有用。在Publisher和Book模型里,可以通过book.publisher获取每本书的出版商信息,这是因为Book模型里有publisher这个字段。但是Publisher模型里并没有book这个字段,那么如何通过出版商反查其出版的所有书籍信息呢?

Django对于关联字段默认使用模型名_set进行反查,即通过publisher.book_set.all查询。但是book_set并不是一个很友好的名字,更希望通过publisher.books获取一个出版社已出版的所有书籍信息,这时就要修改的模型了,将related_name设为books, 如下所示:

# django_foundation_pro/foundation_app/models.py

from django.db import models

class Publisher(models.Model):
    name = models.CharField(max_length=50)
    # address = models.CharField()  # CharFields must define a 'max_length' attribute.
    address = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Book(models.Model):
    name = models.CharField(max_length=30)
    description = models.TextField(blank=True, default="")
    # blank = True 表示字段不是必需的,在客户端不是必填选项
    # null = True 表示这个字段可以存储为null空值。
    # publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
    # 将related_name设置为books
    publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE, related_name='books')
    add_date = models.DateField(auto_now_add=True)

    def __str__(self):
        return self.name

再来对比一下如何通过publisher查询其出版的所有书籍,觉得哪个更好呢?

  1. 设置related_name前:publisher.book_set.all
  2. 设置related_name后:publisher.books.all

常见的Django Model META类选项

Django Model自带的META有很多的选项,都非常有用,如下:

from django.db import models

class DemoModel(models.Model):
    class Meta:
        # 按priority降序, order_date升序排列
        get_latest_by = ['-priority', 'order_name']
        # 自定义数据库里的表名字
        db_table = 'custom_table'
        # 自定义按哪个字段排序,-代表逆序
        ordering = ['pub_date']

        # 定义APP的标签
        app_label = 'foundation_app'  # 表示本模型所属的APP:foundation_app

        # 指定该模型为抽象模型。声明此类是否为抽象
        abstract = True

        # 添加授权,为模型自定义权限
        permissions = (
            ('can_deliver_pizzas', 'Can deliver pizzas'),
        )

        # 指定该模型为代理模型
        proxy = True

        # verbose_name、verbose_name_plural为模型设置便于人类阅读的别名
        verbose_name = '字段别名'
        verbose_name_plural = '这是字段别名'

        # 默认为True,如果为False,Django不会为这个模型生成数据表
        managed = False

        # 为数据表设置索引,对于频繁查询的字段,建议设置索引
        indexes = []

        # 给数据库中的数据表增加约束
        constraints = 'name'

模型的方法

标准方法

以下三个方法是Django模型自带的三个标准方法:

  • def __str__():给单个模型对象实例设置人为可读的名字(可选)
  • def save():重写save方法(可选)
  • def get_absolute_url():为单个模型实例对象生成独一无二的url(可选)

除此以外,经常自定义方法或Manager方法

自定义方法

# django_foundation_pro/foundation_app/models.py

def get_absolute_url(self):
    print('模型实例对象生成独一无二的URL')
    return reverse('demo:demo_detail', args=[str(self.id)])

def custom_func(self):
    """
    自定义方法
    :return:
    """
    print('自定义方法')
    self.views += 1
    self.save(update_fields=['views'])

自定义Manager方法

# django_foundation_pro/foundation_app/models.py

class CustomManager(models.Model):
    def get_queryset(self):
        return super().get_queryset().filter(author='Rocket')

class Book2(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)
    objs = models.Manager()  # 默认manager方法
    custom_objs = CustomManager() # 自定义manager方法

案例:Django Model模型

假设要开发一个餐厅(restaurant)的在线点评网站,允许用户(user)上传菜肴(dish)的图片并点评餐厅,可以设计如下模型。用户与餐厅,餐厅与菜肴,及用户与菜肴都是单对多的关系。可以这样理解:一个用户可以访问点评多个餐厅,一个餐厅有多个菜肴,一个用户可以上传多个菜肴的图片。

# django_foundation_pro/foundation_app/models.py

from django.db import models
from django.contrib.auth.models import User
from datetime import date

class Restaurant(models.Model):
    name = models.TextField()
    address = models.TextField(blank=True, default='')
    telephone = models.CharField(max_length=22)
    url = models.URLField(blank=True, null=True)
    user = models.ForeignKey(User, default=1, on_delete=models.CASCADE)
    date = models.DateField(default=date.today)

    def __str__(self):
        return self.name

class Dish(models.Model):
    name = models.TextField()
    description = models.TextField(blank=True, default='')
    price = models.DecimalField('USD amount', max_digits=8, decimal_places=2, blank=True, null=True)
    user = models.ForeignKey(User, default=1, on_delete=models.CASCADE)
    date = models.DateField(default=date.today)
    image = models.ImageField(upload_to='myrestaurants', blank=True, null=True)
    # Related name "dishes" allows you to use restaurant.dishes.all to access all dishes objects
    # instead of using restaurant.dish_set.all
    restaurant = models.ForeignKey(Restaurant, null=True, related_name='dishes', on_delete=models.CASCADE)

    def __str__(self):
        return self.name

# This Abstract Review can be used to create RestaurantReview and DishReview
class Review(models.Model):
    RATING_CHOICES = ((1, 'one'), (2, 'two'), (3, 'three'), (4, 'four'), (5, 'five'))
    rating = models.PositiveSmallIntegerField("Rating", blank=False, default=3, choices=RATING_CHOICES)
    comment = models.TextField(blank=True, null=True)
    user = models.ForeignKey(User, default=1, on_delete=models.CASCADE)
    date = models.DateField(default=date.today)

    class Meta:
        abstract = True

class RestaurantReview(Review):
    restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)

    def __str__(self):
        return '{} review'.format(self.restaurant.name)

Django自带的models里有两个非常重要的类,一个是models.Model, 另一个是models.Manager。models.Manager的使用参考:Django开发总结(8):模型Models高级进阶。

Django模型设计细节

Dish模型里有一个restaurant的字段,建立了一个单对多的关系。可以通过dish.restaurant.name直接查询到菜肴所属的餐厅的名字。然而Restaurant模型里并没有dish的字段,如何根据restaurant查询到某个餐厅的所有菜肴呢?Django非常聪明,可以通过在dish小写后面加上'_set'进行反向查询。本来可以直接通过restaurant.dish_set.all的方法来进行查找的,然而这个方法并不直观。为了解决这个问题,在dish模型里设置'related_name = dishes", 这样就可以直接通过restaurant.dishes.all来反向查询所有菜肴了。

注意一但设置了related name, 将不能再通过_set方法来反向查询。

restaurant = models.ForeignKey(Restaurant, related_name='dishes', on_delete=models.CASCADE)

需要关注的是Review模型里,设置了META选项: Abstract = True。这样一来Django就会认为这个模型是抽象类,而不会在数据库里创建review的数据表


微信公众号搜索【CTO Plus】关注后,获取更多,我们一起学习交流。


3、Django开发总结:Model模型的介绍、字段属性、字段选项(参数)与设计详解_第3张图片

输入才有输出,吸收才能吐纳。——码字不易

你可能感兴趣的:(#,Django进阶,django,python,后端)