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 创建表格的时候会遇到错误,错误原因如下:

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

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

这个Field是否有必选项, 比如CharField的max_length和ForeignKey的on_delete选项是必须要设置的。这个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。

模型类属性命名限制

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

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

属性名=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

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

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

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

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

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

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 dateFor 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) - 单对单关系

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

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

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

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

to 必需指向其他模型,比如 User or ‘self’ .设置 “symmetrical = False “ 表示多对多关系不是对称的,比如A关注B不代表B关注A设置 “through = 'intermediary model' “ 如果需要建立中间模型来搜集更多信息。可以设置 “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提供了如下几种关联外键删除选项, 可以根据实际需求使用。

CASCADE:级联删除。当删除publisher记录时,与之关联的所有 book 都会被删除。PROTECT: 保护模式。如果有外键关联,就不允许删除,删除的时候会抛出ProtectedError错误,除非先把关联了外键的记录删除掉。例如想要删除publisher,那要把所有关联了该publisher的book全部删除才可能删publisher。SET_NULL: 置空模式。删除的时候,外键字段会被设置为空。删除publisher后,book 记录里面的publisher_id 就置为null了。SET_DEFAULT: 置默认值,删除的时候,外键字段设置为默认值。SET(): 自定义一个值。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查询其出版的所有书籍,觉得哪个更好呢?

设置related_name前:publisher.book_set.all设置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】关注后,获取更多,我们一起学习交流。

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

推荐链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: