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 创建表格的时候会遇到错误,错误原因如下:
所以当定义Django模型Model的时候,一定要十分清楚两件事:
其实在上述案例中还有一个隐藏的错误,即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/
属性名=models.字段类型(选项)
例如
btitle = models.CharField(max_length=20)
一个标准的Django模型分别由模型字段、META选项和方法三部分组成。Django官方编码规范建议按如下方式排列:
models.Model提供的常用模型字段包括基础字段和关系字段。
参考:https://docs.djangoproject.com/en/4.1/ref/models/fields/
参考:https://docs.djangoproject.com/en/4.1/topics/db/models/#field-options
通过选项实现对字段的约束,选项如下:
如果为True,则该字段允许为空白,默认值是False。(一般是后台管理处输入是否可为空格等)
【对比】:null是数据库范畴的概念,blank是后台管理页面表单验证范畴的。
【经验】:当修改模型类之后,如果添加的选项不影响表的结构,则不需要重新做迁移,商品的选项中default和blank不影响表结构。
一个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() 字符字段
|
TextField() 适合大量文本字段
|
DateField() and DateTimeField() and TimeField()日期与时间字段
|
EmailField() 邮件字段
|
IntegerField() 整数 SlugField() URLField() BooleanField()布尔字段
|
FileField(upload_to=None, max_length=100) - 文件字段
|
ImageField(upload_to=None, height_field=None, width_field=None, max_length=100,)
|
ForeignKey(to, on_delete, **options) - 单对多关系
如下示例: staff_member = models.ForeignKey( User, on_delete=models.CASCADE, limit_choices_to={'is_staff': True}, )
|
ManyToManyField(to, **options) - 多对多关系
|
NullBooleanField 支持Null、True、False三种值。 |
DecimalField(max_digits=None, decimal_places=None) 十进制浮点数。
|
FloatField 浮点数。参数同上(精确度比上一个低) |
OneToOneField(to, on_delete=xxx, options) - 单对单关系
ForeignKey(to, on_delete=xxx, options) - 单对多关系
ManyToManyField(to, options) - 多对多关系
示例:一个人加入多个组,一个组包含多个人,需要额外的中间模型记录加入日期和理由。
# 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是两个非常重要的设置,前者决定了关联外键删除方式,后者决定了模型反向查询的名字。
Django提供了如下几种关联外键删除选项, 可以根据实际需求使用。
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查询其出版的所有书籍,觉得哪个更好呢?
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模型自带的三个标准方法:
除此以外,经常自定义方法或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'])
# 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方法
假设要开发一个餐厅(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高级进阶。
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】关注后,获取更多,我们一起学习交流。