模型和字段(2)
字段是模型中最重要的内容之一,也是唯一必须的部分。字段在Python中表现为一个类属性,体现了数据表中的一个列。请不要使用clean
、save
、delete
等Django内置的模型API名字,防止命名冲突
### **字段命名约束**
Django不允许下面两种字段名:
- 与Python关键字冲突。这会导致语法错误。例如:
```python
class Example(models.Model):
pass = models.IntegerField() # 'pass'是Python保留字!
```
- 字段名中不能有两个以上下划线在一起,因为两个下划线是Django的查询语法。例如:
```python
class Example(models.Model):
foo__bar = models.IntegerField() # 'foo__bar' 有两个下划线在一起!
```
- 字段名不能以下划线结尾,原因同上。
由于你可以自定义表名、列名,上面的规则可能被绕开,但是请养成良好的习惯,一定不要那么起名。
### ****常用字段类型****
Django内置了许多字段类型,它们都位于`django.db.models`中,例如`models.CharField`,它们的父类都是Field类。这些类型基本满足需求,如果还不够,你也可以自定义字段。
下表列出了所有Django内置的字段类型,但不包括关系字段类型(字段名采用驼峰命名法,初学者请一定要注意):
### 特殊字段-****FileField****
`class FileField(upload_to=None, max_length=100, **options)`
上传文件字段(不能设置为主键)。默认情况下,该字段在HTML中表现为一个`ClearableFileInput`标签。在数据库内,我们实际保存的是一个字符串类型,默认最大长度100,可以通过`max_length`参数自定义。真实的文件是保存在服务器的文件系统内的。
重要参数`upload_to`用于设置上传地址的目录和文件名。如下例所示:
```python
class MyModel(models.Model):
# 文件被传至`MEDIA_ROOT/uploads`目录,MEDIA_ROOT由你在settings文件中设置
upload = models.FileField(upload_to='uploads/')
# 或者
# 被传到`MEDIA_ROOT/uploads/2015/01/30`目录,增加了一个时间划分
upload = models.FileField(upload_to='uploads/%Y/%m/%d/')
```
**Django很人性化地帮我们实现了根据日期生成目录或文件的方式!**
**`upload_to`参数也可以接收一个回调函数,该函数返回具体的路径字符串**,如下例:
```python
def user_directory_path(instance, filename):
#文件上传到MEDIA_ROOT/user_<id>/<filename>目录中
return 'user_{0}/{1}'.format(instance.user.id, filename)
class MyModel(models.Model):
upload = models.FileField(upload_to=user_directory_path)
# user_directory_path这种回调函数,必须接收两个参数,然后返回一个Unix风格的路径字符串。参数instace代表一个定义了FileField的模型的实例,说白了就是当前数据记录。filename是原本的文件名。
```
从Django3.0开始,支持使用`pathlib.Path` 处理路径。
当你访问一个模型对象中的文件字段时,Django会自动给我们提供一个 FieldFile实例作为文件的代理,通过这个代理,我们可以进行一些文件操作,主要如下:
- FieldFile.name : 获取文件名
- FieldFile.size: 获取文件大小
- FieldFile.url :用于访问该文件的url
- FieldFile.open(mode='rb'): 以类似Python文件操作的方式,打开文件
- FieldFile.close(): 关闭文件
- FieldFile.save(name, content, save=True): 保存文件
- FieldFile.delete(save=True): 删除文件
这些代理的API和Python原生的文件读写API非常类似,其实本质上就是进行了一层封装,让我们可以在Django内直接对模型中文件字段进行读写,而不需要绕弯子。
### 特殊字段-****ImageField****
`class ImageField(upload_to=None, height_field=None, width_field=None, max_length=100, **options)`
用于保存图像文件的字段。该字段继承了FileField,其用法和特性与FileField基本一样,只不过多了两个属性height和width。默认情况下,该字段在HTML中表现为一个ClearableFileInput标签。在数据库内,我们实际保存的是一个字符串类型,默认最大长度100,可以通过max_length参数自定义。真实的图片是保存在服务器的文件系统内的。
`height_field`参数:保存有图片高度信息的模型字段名。
`width_field`参数:保存有图片宽度信息的模型字段名。
**使用Django的ImageField需要提前安装pillow模块,pip install pillow即可。**
### ****使用FileField或者ImageField字段的步骤****
1. 在settings文件中,配置`MEDIA_ROOT`,作为你上传文件在服务器中的基本路径(为了性能考虑,这些文件不会被储存在数据库中)。再配置个`MEDIA_URL`,作为公用URL,指向上传文件的基本路径。请确保Web服务器的用户账号对该目录具有写的权限。
2. 添加FileField或者ImageField字段到你的模型中,定义好`upload_to`参数,文件最终会放在`MEDIA_ROOT`目录的“`upload_to`”子目录中。
3. 所有真正被保存在数据库中的,只是指向你上传文件路径的字符串而已。可以通过url属性,在Django的模板中方便的访问这些文件。例如,假设你有一个ImageField字段,名叫`mug_shot`,那么在Django模板的HTML文件中,可以使用`{{ object.mug_shot.url }}`来获取该文件。其中的object用你具体的对象名称代替。
4. 可以通过`name`和`size`属性,获取文件的名称和大小信息。
安全建议:
无论你如何保存上传的文件,一定要注意他们的内容和格式,避免安全漏洞!务必对所有的上传文件进行安全检查,确保它们不出问题!如果你不加任何检查就盲目的让任何人上传文件到你的服务器文档根目录内,比如上传了一个CGI或者PHP脚本,很可能就会被访问的用户执行,这具有致命的危害。
### 特殊字段-****FilePathField****
`class FilePathField(path='', match=None, recursive=False, allow_files=True, allow_folders=False, max_length=100, **options)`
一种用来保存文件路径信息的字段。在数据表内以字符串的形式存在,默认最大长度100,可以通过max_length参数设置。
它包含有下面的一些参数:
`path`:必须指定的参数。表示一个系统绝对路径。path通常是个字符串,也可以是个可调用对象,比如函数。
`match`:可选参数,一个正则表达式,用于过滤文件名。只匹配基本文件名,不匹配路径。例如`foo.*\.txt$`,只匹配文件名`foo23.txt`,不匹配`bar.txt`与`foo23.png`。
`recursive`:可选参数,只能是True或者False。默认为False。决定是否包含子目录,也就是是否递归的意思。
`allow_files`:可选参数,只能是True或者False。默认为True。决定是否应该将文件名包括在内。它和`allow_folders`其中,必须有一个为True。
`allow_folders`: 可选参数,只能是True或者False。默认为False。决定是否应该将目录名包括在内。
比如:
`FilePathField(path="/home/images", match="foo.*", recursive=True)`
它只匹配`/home/images/foo.png`,但不匹配`/home/images/foo/bar.png`,因为默认情况,只匹配文件名,而不管路径是怎么样的。
```python
import os
from django.conf import settings
from django.db import models
def images_path():
return os.path.join(settings.LOCAL_FILE_DIR, 'images')
class MyModel(models.Model):
file = models.FilePathField(path=images_path)
```
### 特殊字段-****UUIDField****
数据库无法自己生成uuid,因此需要如下使用default参数:
```python
import uuid # Python的内置模块
from django.db import models
class MyUUIDModel(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# 其它字段
```
## **自定义的字段**
1. 自定义字段
```python
class MyCharField(models.Field):
"""
自定义的char类型的字段类
"""
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super(MyCharField, self).__init__(max_length=max_length, *args, **kwargs)
def db_type(self, connection):
"""
限定生成数据库表的字段类型为char,长度为max_length指定的值
"""
return 'char(%s)' % self.max_length #char(11)
```
1. 输入数据库迁移命令
```python
python manage.py makemigrations
#因为是添加字段,所以会出现以下图片的内容:
```
![Untitled](1%20%E6%A8%A1%E5%9E%8B%E5%92%8C%E5%AD%97%E6%AE%B5%20c15fa3714ce34dccb63c25454527b5f7/Untitled.png)
给前面字段添加默认参数,或者添加默认值(default)
1. 使用
```python
class Person(models.Model):
#添加自定义的字段
cname = MyCharField(max_length=11) #char(11)
```
## 关系型字段
### ****一对一(OneToOneField)****
一对一关系类型的定义如下:
`class OneToOneField(to, on_delete, parent_link=False, **options)`
从概念上讲,一对一关系非常类似具有`unique=True`属性的外键关系,但是反向关联对象只有一个。这种关系类型多数用于当一个模型需要从别的模型扩展而来的情况。比如,Django自带auth模块的User用户表,如果你想在自己的项目里创建用户模型,又想方便的使用Django的auth中的一些功能,那么一个方案就是在你的用户模型里,使用一对一关系,添加一个与auth模块User模型的关联字段。
该关系的第一位置参数为关联的模型,其用法和前面的多对一外键一样。
如果你没有给一对一关系设置`related_name`参数,Django将使用当前模型的小写名作为默认值。
示例
```python
from django.conf import settings
from django.db import models
# 两个字段都使用一对一关联到了Django内置的auth模块中的User模型
class MySpecialUser(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
supervisor = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='supervisor_of',
)
```
这样下来,你的User模型将拥有下面的属性:
```python
>>> user = User.objects.get(pk=1)
>>> hasattr(user, 'myspecialuser')
True
>>> hasattr(user, 'supervisor_of')
True
```
OneToOneField一对一关系拥有和多对一外键关系一样的额外可选参数,只是多了一个不常用的`parent_link`参数。
### ****多对一(ForeignKey)****
多对一的关系,通常被称为外键。外键字段类的定义如下:
`class ForeignKey(to, on_delete, **options)`
外键需要两个位置参数,一个是关联的模型,另一个是`on_delete`。在Django2.0版本后`on_delete`属于必填参数。
**外键要定义在‘多’的一方!**
```python
from django.db import models
class Manufacturer(models.Model):
# ...
pass
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
# ...
# 多对一字段的变量名一般设置为关联的模型的小写单数,而多对多则一般设置为小写复数。
```
**如果你要关联的模型位于当前模型之后,则需要通过字符串的方式进行引用**,看下面的例子:
```python
from django.db import models
class Car(models.Model):
manufacturer = models.ForeignKey(
'Manufacturer', # 注意这里
on_delete=models.CASCADE,
)
# ...
class Manufacturer(models.Model):
# ...
pass
```
**如果要关联的对象在另外一个app中,可以显式的指出。下例假设Manufacturer模型存在于production这个app中**,则Car模型的定义如下:
```python
class Car(models.Model):
manufacturer = models.ForeignKey(
'production.Manufacturer', # 关键在这里!!
on_delete=models.CASCADE,
)
```
**如果要创建一个递归的外键,也就是自己关联自己的的外键**,使用下面的方法:
```python
models.ForeignKey('self', on_delete=models.CASCADE)
```
核心在于‘self’这个引用。什么时候需要自己引用自己的外键呢?典型的例子就是评论系统!一条评论可以被很多人继续评论,如下所示:
```python
class Comment(models.Model):
title = models.CharField(max_length=128)
text = models.TextField()
parent_comment = models.ForeignKey('self', on_delete=models.CASCADE)
# .....
"""
注意上面的外键字段定义的是父评论,而不是子评论。为什么呢?因为外键要放在‘多’的一方!
在实际的数据库后台,Django会为每一个外键添加_id后缀,并以此创建数据表里的一列。在上面的工厂与车的例子中,Car模型对应的数据表中,会有一列叫做manufacturer_id。但实际上,在Django代码中你不需要使用这个列名,除非你书写原生的SQL语句,一般我们都直接使用字段名manufacturer。
关系字段的定义还有个小坑。在后面我们会讲到的verbose_name参数用于设置字段的别名。很多情况下,为了方便,我们都会设置这么个值,并且作为字段的第一位置参数。但是对于关系字段,其第一位置参数永远是关系对象,不能是verbose_name,一定要注意!
"""
```
### ****多对多(ManyToManyField)****
`class ManyToManyField(to, **options)`
多对多关系在数据库中也是非常常见的关系类型。比如一本书可以有好几个作者,一个作者也可以写好几本书。多对多的字段可以定义在任何的一方,请尽量定义在符合人们思维习惯的一方,但不要同时都定义,只能选择一个模型设置该字段(比如我们通常将披萨上的配料字段放在披萨模型中,而不是在配料模型中放置披萨字段)。
```python
from django.db import models
class Topping(models.Model):
# ...
pass
class Pizza(models.Model):
# ...
toppings = models.ManyToManyField(Topping)
```
**建议为多对多字段名使用复数形式。**
多对多关系需要一个位置参数:关联的对象模型,其它用法和外键多对一基本类似。
**如果要创建一个关联自己的多对多字段,依然是通过`'self'`引用。**
**在数据库后台,Django实际上会额外创建一张用于体现多对多关系的中间表**。默认情况下,该表的名称是“`多对多字段名+包含该字段的模型名+一个独一无二的哈希码`”,例如‘author_books_9cdf4’,当然你也可以通过`db_table`选项,自定义表名。
### ****关系类型字段-on_delete****
注意:这个参数在Django2.0之后,不可以省略了,需要显式的指定!这也是除了路由编写方式外,Django2和Django1.x最大的不同点之一!
```python
#添加外键 一对多 多对一
class Book(models.Model):
name = models.CharField(max_length=32)
# 添加外键ForeignKey
publishers = models.ForeignKey(Publisher, on_delete=models.CASCADE) #默认是级联删除
"""
on_delete=None, # 删除关联表中的数据时,当前表与其关联的field的行为
on_delete=models.CASCADE, # 删除关联数据,与之关联也删除
on_delete=models.DO_NOTHING, # 删除关联数据,什么也不做
on_delete=models.PROTECT, # 删除关联数据,引发错误ProtectedError
on_delete=models.SET_NULL, # 删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空,一对一同理)
on_delete=models.SET_DEFAULT, # 删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值,一对一同理)
on_delete=models.SET, # 删除关联数据,
a. 与之关联的值设置为指定值,设置:models.SET(值)
b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
"""
```
• **RESTRICT**: Django3.1新增。这个模式比较难以理解。它与PROTECT不同,在大多数情况下,同样不允许删除,但是在某些特殊情况下,却是可以删除的。看下面的例子,多揣摩一下:
### ****关系类型字段-limit_choices_to****
该参数用于限制外键所能关联的对象,只能用于Django的ModelForm(Django的表单模块)和admin后台,对其它场合无限制功能。其值可以是一个字典、Q对象或者一个返回字典或Q对象的函数调用,如下例所示:
```python
staff_member = models.ForeignKey(
User,
on_delete=models.CASCADE,
limit_choices_to={'is_staff': True},
)
```
这样定义,则ModelForm的`staff_member`字段列表中,只会出现那些`is_staff=True`的Users对象,这一功能对于admin后台非常有用。
可以参考下面的方式,使用函数调用:
```python
def limit_pub_date_choices():
return {'pub_date__lte': datetime.date.utcnow()}
# ...
limit_choices_to = limit_pub_date_choices
# ...
```
### ****关系类型字段-related_name****
用于关联对象反向引用模型的名称
通常情况下,这个参数我们可以不设置,Django会默认以模型的小写加上`_set`作为反向关联名,比如对于工厂就是`car_set`,如果你觉得`car_set`还不够直观,可以如下定义:
```python
class Car(models.Model):
manufacturer = models.ForeignKey(
'production.Manufacturer',
on_delete=models.CASCADE,
related_name='car_producted_by_this_manufacturer', # 看这里!!
)
```
也许我定义了一个蹩脚的词,但表达的意思很清楚。以后从工厂对象反向关联到它所生产的汽车,就可以使用`maufacturer.car_producted_by_this_manufacturer`了。
如果你不想为外键设置一个反向关联名称,可以将这个参数设置为“+”或者以“+”结尾,如下所示:
```python
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='+',
)
```
### ****关系类型字段-related_query_name****
反向关联查询名。用于从目标模型反向过滤模型对象的名称。
这个参数的默认值是定义有外键字段的模型的小写名,如果设置了`related_name`参数,那么就是这个参数值,如果在此基础上还指定了`related_query_name`的值,则是`related_query_name`的值。三者依次有优先顺序。
要注意`related_query_name`和`related_name`的区别,前者用于在做查询操作时候作为参数使用,后者主要用于在属性调用时使用。
```python
class Tag(models.Model):
article = models.ForeignKey(
Article,
on_delete=models.CASCADE,
related_name="tags",
related_query_name="tag", # 注意这一行
)
name = models.CharField(max_length=255)
# 现在可以使用‘tag’作为查询名了
Article.objects.filter(tag__name="important")
```
### **关系类型字段-to_field**
默认情况下,外键都是关联到被关联对象的主键上(一般为id)。如果指定这个参数,可以关联到指定的字段上,但是该字段必须具有`unique=True`属性,也就是具有唯一属性。
### **关系类型字段-db_constraint**
默认情况下,这个参数被设为True,表示遵循数据库约束,这也是大多数情况下你的选择。如果设为False,那么将无法保证数据的完整性和合法性。在下面的场景中,你可能需要将它设置为False:
- 有历史遗留的不合法数据,没办法的选择
- 你正在分割数据表
当它为False,并且你试图访问一个不存在的关系对象时,会抛出DoesNotExist 异常。
### **关系类型字段-swappable**
控制迁移框架的动作,如果当前外键指向一个可交换的模型。使用场景非常稀少,通常请将该参数保持默认的True。
### ****关系类型字段-symmetrical****
默认情况下,Django中的多对多关系是对称的。看下面的例子:
```python
from django.db import models
class Person(models.Model):
friends = models.ManyToManyField("self")
```
Django认为,如果我是你的朋友,那么你也是我的朋友,这是一种对称关系,Django不会为Person模型添加`person_set`属性用于反向关联。如果你不想使用这种对称关系,可以将symmetrical设置为False,这将强制Django为反向关联添加描述符。
### **关系类型字段-through**
如果你想自定义多对多关系的那张额外的关联表,可以使用这个参数!参数的值为一个中间模型。
最常见的使用场景是你需要为多对多关系添加额外的数据,比如添加两个人建立QQ好友关系的时间。
通常情况下,这张表在数据库内的结构是这个样子的:
`中间表的id列....模型对象的id列.....被关联对象的id列
## 各行数据
如果自定义中间表并添加时间字段,则在数据库内的表结构如下:
`中间表的id列....模型对象的id列.....被关联对象的id列.....时间对象列
## 各行数据
看下面的例子:
```python
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)
```
上面的代码中,通过`class Membership(models.Model)`定义了一个新的模型,用来保存Person和Group模型的多对多关系,并且同时增加了‘邀请人’和‘邀请原因’的字段。
**through参数在某些使用场景中是必须的,至关重要,请务必掌握!**
### **关系类型字段-through_fields**
接着上面的例子。Membership模型中包含两个关联Person的外键,Django无法确定到底使用哪个作为和Group关联的对象。所以,在这个例子中,必须显式的指定`through_fields`参数,用于定义关系。
`through_fields`参数接收一个二元元组('field1', 'field2'),field1是指向定义有多对多关系的模型的外键字段的名称,这里是Membership中的‘group’字段(注意大小写),另外一个则是指向目标模型的外键字段的名称,这里是Membership中的‘person’,而不是‘inviter’。
再通俗的说,就是`through_fields`参数指定从中间表模型Membership中选择哪两个字段,作为关系连接字段。
### **关系类型字段-db_table**
设置中间表的名称。不指定的话,则使用默认值。
**ManyToManyField多对多字段不支持Django内置的validators验证功能。**
**null参数对ManyToManyField多对多字段无效!设置null=True毫无意义**
## ****字段的参数****
所有的模型字段都可以接收一定数量的参数,比如CharField至少需要一个max_length参数。下面的这些参数是所有字段都可以使用的,并且是可选的。
```python
null=True 该字段在数据库可以为空
blank=True 允许用户输入为空(True就是必须输入)
db_column 数据库中字段的列名(别名)
defalut 默认值 (在admin中显示默认)
db_index 建立索引
unique=True 唯一约束(表示不能为重复的,如果有重复的就会报错) 如果新创建一个数据库和unique=True一起迁移,那一定不能有重复的元素,不然就删除了新迁移的数据,在重新迁移
verbose_name显示的字段名 (在admin中显示)
choices 用户选择的参数 models.BooleanField(choices=((True,"男"),(False,"女"))) 下拉选择框
更多更详细的数据:https://www.cnblogs.com/maple-shaw/articles/9323320.html
```
### **null**
该值为True时,Django在数据库用NULL保存空值。默认值为False。对于保存字符串类型数据的字段,请尽量避免将此参数设为True,那样会导致两种‘没有数据’的情况,一种是`NULL`,另一种是空字符串`''`。Django 的惯例是使用空字符串而不是 `NULL`。
### **blank**
True时,字段可以为空。默认False。和null参数不同的是,null是纯数据库层面的,而blank是验证相关的,它与表单验证是否允许输入框内为空有关,与数据库无关。所以要小心一个null为False,blank为True的字段接收到一个空值可能会出bug或异常。
### **choices**
用于页面上的选择框标签,需要先提供一个二维的二元元组,第一个元素表示存在数据库内真实的值,第二个表示页面上显示的具体内容。在浏览器页面上将显示第二个元素的值。例如:
```python
YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'),
('SO', 'Sophomore'),
('JR', 'Junior'),
('SR', 'Senior'),
('GR', 'Graduate'),
)
```
一般来说,最好将选项定义在类里,并取一个直观的名字,如下所示:
```python
from django.db import models
class Student(models.Model):
FRESHMAN = 'FR'
SOPHOMORE = 'SO'
JUNIOR = 'JR'
SENIOR = 'SR'
YEAR_IN_SCHOOL_CHOICES = (
(FRESHMAN, 'Freshman'),
(SOPHOMORE, 'Sophomore'),
(JUNIOR, 'Junior'),
(SENIOR, 'Senior'),
)
year_in_school = models.CharField(
max_length=2,
choices=YEAR_IN_SCHOOL_CHOICES,
default=FRESHMAN,
)
def is_upperclass(self):
return self.year_in_school in (self.JUNIOR, self.SENIOR)
```
**注意:每当 `choices` 的顺序变动时将会创建新的迁移。**
如果一个模型中有多个字段需要设置choices,可以将这些二维元组组合起来,显得更加整洁优雅,例如下面的做法:
```python
MEDIA_CHOICES = [
('Audio', (
('vinyl', 'Vinyl'),
('cd', 'CD'),
)
),
('Video', (
('vhs', 'VHS Tape'),
('dvd', 'DVD'),
)
),
('unknown', 'Unknown'),
]
```
反过来,要获取一个choices的第二元素的值,可以使用`get_FOO_display()`方法,其中的FOO用字段名代替。对于下面的例子:
```python
from django.db import models
class Person(models.Model):
SHIRT_SIZES = (
('S', 'Small'),
('M', 'Medium'),
('L', 'Large'),
)
name = models.CharField(max_length=60)
shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
# 使用方法
"""
>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'
"""
```
从Django3.0开始,新增了TextChoices、IntegerChoices和Choices三个类,用来达到类似Python的enum枚举库的作用,下面是一个例子
```python
from django.utils.translation import gettext_lazy as _
class Student(models.Model):
class YearInSchool(models.TextChoices):
FRESHMAN = 'FR', _('Freshman')
SOPHOMORE = 'SO', _('Sophomore')
JUNIOR = 'JR', _('Junior')
SENIOR = 'SR', _('Senior')
GRADUATE = 'GR', _('Graduate')
year_in_school = models.CharField(
max_length=2,
choices=YearInSchool.choices,
default=YearInSchool.FRESHMAN,
)
def is_upperclass(self):
return self.year_in_school in {
self.YearInSchool.JUNIOR,
self.YearInSchool.SENIOR,
}
```
简要解释一下:
- 第一句导入是废话,搞国际化翻译的,和本例的内容其实没关系
- 核心在Student模型中创建了个内部类YearInSchool
- YearInSchool继承了Django新增的TextChoices类
- TextChoices中没定义别的,只定义了一些类变量,这些类变量看起来和我们前面使用的二维二元元组本质上是一个套路
- Student模型中有一个`year_in_school` 字段,其中定义了choices参数,参数的值是`YearInSchool.choices`
- `year_in_school` 字段还定义了default参数,值是`YearInSchool.FRESHMAN`
- 从本质上来说,这和我们开始使用choice的方式是一样的,只不过换成了类的方式,而不是二维元组
吐个槽,这么设计除了增加学习成本有什么好处?有多少Choice选项需要你非得用类的形式管理起来封装起来?二维元组它就不香吗?新手学习就不累吗?
### **db_column**
该参数用于定义当前字段在数据表内的列名。如果未指定,Django将使用字段名作为列名。
### **db_index**
该参数接收布尔值。如果为True,数据库将为该字段创建索引。
### **db_tablespace**
用于字段索引的数据库表空间的名字,前提是当前字段设置了索引。默认值为工程的`DEFAULT_INDEX_TABLESPACE`设置。如果使用的数据库不支持表空间,该参数会被忽略。
### **default**
字段的默认值,可以是值或者一个可调用对象。如果是可调用对象,那么每次创建新对象时都会调用。设置的默认值不能是一个可变对象,比如列表、集合等等。lambda匿名函数也不可用于default的调用对象,因为匿名函数不能被migrations序列化。
注意:在某种原因不明的情况下将default设置为None,可能会引发`intergyerror:not null constraint failed`,即非空约束失败异常,导致`python manage.py migrate`失败,此时可将None改为False或其它的值,只要不是None就行。
### **editable**
如果设为False,那么当前字段将不会在admin后台或者其它的ModelForm表单中显示,同时还会被模型验证功能跳过。参数默认值为True。
### **error_messages**
用于自定义错误信息。参数接收字典类型的值。字典的键可以是`null`、 `blank`、 `invalid`、 `invalid_choice`、 `unique`和`unique_for_date`其中的一个。
### **help_text**
额外显示在表单部件上的帮助文本。即便你的字段未用于表单,它对于生成文档也是很有用的。
该帮助文本默认情况下是可以带HTML代码的,具有风险:
`help_text="Please use the following format: <em>YYYY-MM-DD</em>."`
所以使用时请注意转义为纯文本,防止脚本攻击。
### **primary_key**
如果你没有给模型的任何字段设置这个参数为True,Django将自动创建一个AutoField自增字段,名为‘id’,并设置为主键。也就是`id = models.AutoField(primary_key=True)`。
如果你为某个字段设置了primary_key=True,则当前字段变为主键,并关闭Django自动生成id主键的功能。
**`primary_key=True`隐含`null=False`和`unique=True`的意思。一个模型中只能有一个主键字段!**
另外,主键字段不可修改,如果你给某个对象的主键赋个新值实际上是创建一个新对象,并不会修改原来的对象。
```python
from django.db import models
class Fruit(models.Model):
name = models.CharField(max_length=100, primary_key=True)
###############
>>> fruit = Fruit.objects.create(name='Apple')
>>> fruit.name = 'Pear'
>>> fruit.save()
>>> Fruit.objects.values_list('name', flat=True)
['Apple', 'Pear']
```
### **unique**
设为True时,在整个数据表内该字段的数据不可重复。
注意:对于ManyToManyField和OneToOneField关系类型,该参数无效。
注意: 当unique=True时,db_index参数无须设置,因为unqiue隐含了索引。
### **unique_for_date**
日期唯一。可能不太好理解。举个栗子,如果你有一个名叫title的字段,并设置了参数`unique_for_date="pub_date"`,那么Django将不允许有两个模型对象具备同样的title和pub_date。有点类似联合约束。
### **unique_for_month**
同上,只是月份唯一。
### **unique_for_year**
同上,只是年份唯一。
### **verbose_name**
为字段设置一个人类可读,更加直观的别名。
对于每一个字段类型,除了`ForeignKey`、`ManyToManyField`和`OneToOneField`这三个特殊的关系类型,其第一可选位置参数都是`verbose_name`。如果没指定这个参数,Django会利用字段的属性名自动创建它,并将下划线转换为空格。
下面这个例子的`verbose name`是"person’s first name":
`first_name = models.CharField("person's first name", max_length=30)`
下面这个例子的`verbose name`是"first name":
`first_name = models.CharField(max_length=30)`
对于外键、多对多和一对一字字段,由于第一个参数需要用来指定关联的模型,因此必须用关键字参数`verbose_name`来明确指定。如下:
```python
poll = models.ForeignKey(
Poll,
on_delete=models.CASCADE,
verbose_name="the related poll",
)
sites = models.ManyToManyField(Site, verbose_name="list of sites")
place = models.OneToOneField(
Place,
on_delete=models.CASCADE,
verbose_name="related place",
)
```
另外,你无须大写`verbose_name`的首字母,Django自动为你完成这一工作。
### **validators**
运行在该字段上的验证器的列表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
编辑 (opens new window)