42.深入模型.md 23.1 KB
Newer Older
J
jackfrued 已提交
1
## 深入模型
2

3
在上一个章节中,我们提到了Django是基于MVC架构的Web框架,MVC架构追求的是“模型”和“视图”的解耦合。所谓“模型”说得更直白一些就是数据(的表示),所以通常也被称作“数据模型”。在实际的项目中,数据模型通常通过数据库实现持久化操作,而关系型数据库在过去和当下都是持久化的首选方案,下面我们以MySQL为例来说明如何使用关系型数据库来实现持久化操作。
4 5 6

### 配置关系型数据库MySQL 

7 8
我们继续来完善上一个章节中的OA项目,首先从配置项目使用的数据库开始。

J
jackfrued 已提交
9
1. 修改项目的settings.py文件,首先将我们之前创建的应用hrs添加已安装的项目中,然后配置MySQL作为持久化方案。
10 11

   ```Shell
12
   (venv)$ vim oa/settings.py
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
   ```

   ```Python
   # 此处省略上面的代码
   
   INSTALLED_APPS = [
       'django.contrib.admin',
       'django.contrib.auth',
       'django.contrib.contenttypes',
       'django.contrib.sessions',
       'django.contrib.messages',
       'django.contrib.staticfiles',
       'hrs',
   ]
   
   DATABASES = {
       'default': {
           'ENGINE': 'django.db.backends.mysql',
           'NAME': 'oa',
J
jackfrued 已提交
32
           'HOST': '1.2.3.4',
33
           'PORT': 3306,
J
jackfrued 已提交
34 35
           'USER': 'yourname',
           'PASSWORD': 'yourpass',
36 37 38 39 40 41 42 43
       }
   }
   
   # 此处省略下面的代码
   ```

   在配置ENGINE属性时,常用的可选值包括:

44 45 46 47
   - `'django.db.backends.sqlite3'`:SQLite嵌入式数据库。
   - `'django.db.backends.postgresql'`:BSD许可证下发行的开源关系型数据库产品。
   - `'django.db.backends.mysql'`:转手多次目前属于甲骨文公司的经济高效的数据库产品。
   - `'django.db.backends.oracle'`:甲骨文公司的关系型数据库旗舰产品。
48 49 50

   其他的配置可以参考官方文档中[数据库配置](https://docs.djangoproject.com/zh-hans/2.0/ref/databases/#third-party-notes)的部分。

51
   NAME属性代表数据库的名称,如果使用SQLite它对应着一个文件,在这种情况下NAME的属性值应该是一个绝对路径;使用其他关系型数据库,则要配置对应的HOST(主机)、PORT(端口)、USER(用户名)、PASSWORD(口令)等属性。
52

53
2. 安装Python操作MySQL的依赖库,Python 3中通常使用PyMySQL,Python 2中通常用MySQLdb。
54 55 56 57 58

   ```Shell
   (venv)$ pip install pymysql
   ```

59
   如果使用Python 3需要修改**项目目录**下的`__init__.py`文件并加入如下所示的代码,这段代码的作用是将PyMySQL视为MySQLdb来使用,从而避免Django找不到连接MySQL的客户端工具而询问你:“Did you install mysqlclient? ”(你安装了mysqlclient吗?)。
60 61 62 63 64 65 66

   ```Python
   import pymysql
   
   pymysql.install_as_MySQLdb()
   ```

67
3. 如果之前没有为应用程序创建数据库,那么现在是时候创建名为oa的数据库了。在MySQL中创建数据库的SQL语句如下所示:
68 69 70 71 72

   ```SQL
   create database oa default charset utf8;
   ```

73 74
4. Django框架本身有自带的数据模型,我们稍后会用到这些模型,为此我们先做一次迁移操作。所谓迁移,就是根据模型自动生成关系数据库中的二维表,命令如下所示:

75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
   ```Shell
   (venv)$ python manage.py migrate
   Operations to perform:
     Apply all migrations: admin, auth, contenttypes, sessions
   Running migrations:
     Applying contenttypes.0001_initial... OK
     Applying auth.0001_initial... OK
     Applying admin.0001_initial... OK
     Applying admin.0002_logentry_remove_auto_add... OK
     Applying contenttypes.0002_remove_content_type_name... OK
     Applying auth.0002_alter_permission_name_max_length... OK
     Applying auth.0003_alter_user_email_max_length... OK
     Applying auth.0004_alter_user_username_opts... OK
     Applying auth.0005_alter_user_last_login_null... OK
     Applying auth.0006_require_contenttypes_0002... OK
     Applying auth.0007_alter_validators_add_error_messages... OK
     Applying auth.0008_alter_user_username_max_length... OK
     Applying auth.0009_alter_user_last_name_max_length... OK
     Applying sessions.0001_initial... OK
   ```

96
5. 接下来,我们为自己的应用创建数据模型。如果要在hrs应用中实现对部门和员工的管理,我们可以先创建部门和员工数据模型,代码如下所示。
97 98

   ```Shell
J
jackfrued 已提交
99
   (venv)$ vim hrs/models.py
100 101 102 103 104 105 106 107 108
   ```

   ```Python
   from django.db import models
   
   
   class Dept(models.Model):
       """部门类"""
       
109 110 111
       no = models.IntegerField(primary_key=True, db_column='dno', verbose_name='部门编号')
       name = models.CharField(max_length=20, db_column='dname', verbose_name='部门名称')
       location = models.CharField(max_length=10, db_column='dloc', verbose_name='部门所在地')
112 113 114 115 116 117 118 119
   
       class Meta:
           db_table = 'tb_dept'
   
   
   class Emp(models.Model):
       """员工类"""
       
120 121
       no = models.IntegerField(primary_key=True, db_column='eno', verbose_name='员工编号')
       name = models.CharField(max_length=20, db_column='ename', verbose_name='员工姓名')
122
       job = models.CharField(max_length=10, verbose_name='职位')
123 124
       # 多对一外键关联(自参照)
       mgr = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, verbose_name='主管')
125 126
       sal = models.DecimalField(max_digits=7, decimal_places=2, verbose_name='月薪')
       comm = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True, verbose_name='补贴')
127
       # 多对一外键关联(参照部门模型)
128
       dept = models.ForeignKey(Dept, db_column='dno', on_delete=models.PROTECT, verbose_name='所在部门')
129 130 131 132
   
       class Meta:
           db_table = 'tb_emp'
   ```
133
   > 说明:上面定义模型时使用了字段类及其属性,其中IntegerField对应数据库中的integer类型,CharField对应数据库的varchar类型,DecimalField对应数据库的decimal类型,ForeignKey用来建立多对一外键关联。字段属性primary_key用于设置主键,max_length用来设置字段的最大长度,db_column用来设置数据库中与字段对应的列,verbose_name则设置了Django后台管理系统中该字段显示的名称。如果对这些东西感到很困惑也不要紧,文末提供了字段类、字段属性、元数据选项等设置的相关说明,不清楚的读者可以稍后查看对应的参考指南。
134

135
6. 再次执行迁移操作,先通过模型生成迁移文件,再执行迁移创建二维表。
136 137 138 139 140 141 142 143 144 145 146 147 148 149

   ```Shell
   (venv)$ python manage.py makemigrations hrs
   Migrations for 'hrs':
     hrs/migrations/0001_initial.py
       - Create model Dept
       - Create model Emp
   (venv)$ python manage.py migrate
   Operations to perform:
     Apply all migrations: admin, auth, contenttypes, hrs, sessions
   Running migrations:
     Applying hrs.0001_initial... OK
   ```

150
   执行完数据模型迁移操作之后,可以在通过图形化的MySQL客户端工具查看到E-R图(实体关系图)。
151 152 153

   ![](./res/er-graph.png)

154 155 156
### 利用Django后台管理模型

Django框架有自带的后台管理系统来实现对模型的管理。虽然实际应用中,这个后台可能并不能满足我们的需求,但是在学习Django框架时,我们暂时可以利用Django自带的后台管理系统来管理我们的模型,同时也可以了解一个项目的后台管理系统到底需要哪些功能。
157

158
1. 创建超级管理员账号。
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
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

   ```Shell
   (venv)$ python manage.py createsuperuser
   Username (leave blank to use 'hao'): jackfrued
   Email address: jackfrued@126.com
   Password: 
   Password (again): 
   Superuser created successfully.
   ```

2. 启动Web服务器,登录后台管理系统。

   ```Shell
   (venv)$ python manage.py runserver
   ```

   访问<http://127.0.0.1:8000/admin>,会来到如下图所示的登录界面。

   ![](./res/admin-login.png)

   登录后进入管理员操作平台。

   ![](./res/admin-welcome.png)

   至此我们还没有看到之前创建的模型类,需要在应用的admin.py文件中模型进行注册。

185
3. 注册模型类。
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
186 187

   ```Shell
J
jackfrued 已提交
188
   (venv)$ vim hrs/admin.py
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
   ```

   ```Python
   from django.contrib import admin
   
   from hrs.models import Emp, Dept
   
   admin.site.register(Dept)
   admin.site.register(Emp)
   ```

   注册模型类后,就可以在后台管理系统中看到它们。

   ![](./res/admin-model.png)

204
4. 对模型进行CRUD操作。
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
205

206
   可以在管理员平台对模型进行C(新增)、R(查看)、U(更新)、D(删除)操作,如下图所示。
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
207

208
   - 添加新的部门。
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
209 210 211

   ![](./res/admin-model-create.png)

212
   - 查看所有部门。
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
213 214 215

   ![](./res/admin-model-read.png)

216
   - 更新和删除部门。
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
217 218 219

   ![](./res/admin-model-delete-and-update.png)

220
5. 注册模型管理类。
221

222
   可能大家已经注意到了,刚才在后台查看部门信息的时候,显示的部门信息并不直观,为此我们再修改admin.py文件,通过注册模型管理类,可以在后台管理系统中更好的管理模型。
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
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

   ```Python
   from django.contrib import admin
   
   from hrs.models import Emp, Dept
   
   
   class DeptAdmin(admin.ModelAdmin):
   
       list_display = ('no', 'name', 'location')
       ordering = ('no', )
   
   
   class EmpAdmin(admin.ModelAdmin):
   
       list_display = ('no', 'name', 'job', 'mgr', 'sal', 'comm', 'dept')
       search_fields = ('name', 'job')
   
   
   admin.site.register(Dept, DeptAdmin)
   admin.site.register(Emp, EmpAdmin)
   ```

   ![](./res/admin-model-depts.png)

   ![](./res/admin-model-emps.png)

   为了更好的查看模型数据,可以为Dept和Emp两个模型类添加`__str__`魔法方法。

   ```Python
   from django.db import models
   
   
   class Dept(models.Model):
       """部门类"""
       
       # 此处省略上面的代码
       
       def __str__(self):
           return self.name
   
       # 此处省略下面的代码
   
   
   class Emp(models.Model):
       """员工类"""
       
       # 此处省略上面的代码
   
       def __str__(self):
           return self.name
   
       # 此处省略下面的代码
   ```

   修改代码后刷新查看Emp模型的页面,效果如下图所示。

   ![](./res/admin-model-emps-modified.png)

282 283
### 使用ORM完成模型的CRUD操作

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
284
在了解了Django提供的模型管理平台之后,我们来看看如何从代码层面完成对模型的CRUD(Create / Read / Update / Delete)操作。我们可以通过manage.py开启Shell交互式环境,然后使用Django内置的ORM框架对模型进行CRUD操作。
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
285 286 287 288 289 290 291 292 293 294

```Shell
(venv)$ python manage.py shell
Python 3.6.4 (v3.6.4:d48ecebad5, Dec 18 2017, 21:07:28) 
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> 
```

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
295
#### 新增
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
296

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
297 298
```Shell
>>> from hrs.models import Dept, Emp
299
>>>
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
300 301 302
>>> dept = Dept(40, '研发2部', '深圳')
>>> dept.save()
```
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
303

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
304
#### 更新
305

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
306 307 308 309
```Shell
>>> dept.name = '研发3部'
>>> dept.save()
```
310

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
311
#### 查询
312

313
1. 查询所有对象。
314

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
315 316 317 318
```Shell
>>> Dept.objects.all()
<QuerySet [<Dept: 研发1部>, <Dept: 销售1部>, <Dept: 运维1部>, <Dept: 研发3部>]>
```
319

320
2. 过滤数据。
321

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
322 323 324 325 326 327 328 329 330
```Shell
>>> Dept.objects.filter(name='研发3部') # 查询部门名称为“研发3部”的部门
<QuerySet [<Dept: 研发3部>]>
>>>
>>> Dept.objects.filter(name__contains='研发') # 查询部门名称包含“研发”的部门(模糊查询)
<QuerySet [<Dept: 研发1部>, <Dept: 研发3部>]>
>>>
>>> Dept.objects.filter(no__gt=10).filter(no__lt=40) # 查询部门编号大于10小于40的部门
<QuerySet [<Dept: 销售1部>, <Dept: 运维1部>]>
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
331 332 333
>>>
>>> Dept.objects.filter(no__range=(10, 30)) # 查询部门编号在10到30之间的部门
<QuerySet [<Dept: 研发1部>, <Dept: 销售1部>, <Dept: 运维1部>]>
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
334
```
335

336
3. 查询单个对象。
337

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
338 339 340
```Shell
>>> Dept.objects.get(pk=10)
<Dept: 研发1部>
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
341
>>>
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
342 343
>>> Dept.objects.get(no=20)
<Dept: 销售1部>
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
344
>>>
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
345 346
>>> Dept.objects.get(no__exact=30)
<Dept: 运维1部>
J
jackfrued 已提交
347 348 349
>>>
>>> Dept.objects.filter(no=10).first()
<Dept: 研发1部>
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
350
```
351

352
4. 排序数据。
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
353 354 355 356

```Shell
>>> Dept.objects.order_by('no') # 查询所有部门按部门编号升序排列
<QuerySet [<Dept: 研发1部>, <Dept: 销售1部>, <Dept: 运维1部>, <Dept: 研发3部>]>
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
357
>>>
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
358 359 360
>>> Dept.objects.order_by('-no') # 查询所有部门按部门编号降序排列
<QuerySet [<Dept: 研发3部>, <Dept: 运维1部>, <Dept: 销售1部>, <Dept: 研发1部>]>
```
361

362
5. 数据切片(分页查询)。
363

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
364 365 366
```Shell
>>> Dept.objects.order_by('no')[0:2] # 按部门编号排序查询1~2部门
<QuerySet [<Dept: 研发1部>, <Dept: 销售1部>]>
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
367
>>>
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
368 369 370 371
>>> Dept.objects.order_by('no')[2:4] # 按部门编号排序查询3~4部门
<QuerySet [<Dept: 运维1部>, <Dept: 研发3部>]>
```

372
6. 高级查询。
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
373 374 375 376

```Shell
>>> Emp.objects.filter(dept__no=10) # 根据部门编号查询该部门的员工
<QuerySet [<Emp: 乔峰>, <Emp: 张无忌>, <Emp: 张三丰>]>
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
377
>>>
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
378 379
>>> Emp.objects.filter(dept__name__contains='销售') # 查询名字包含“销售”的部门的员工
<QuerySet [<Emp: 黄蓉>]>
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
380
>>>
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
381 382 383 384
>>> Dept.objects.get(pk=10).emp_set.all() # 通过部门反查部门所有的员工
<QuerySet [<Emp: 乔峰>, <Emp: 张无忌>, <Emp: 张三丰>]>
```

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
385 386 387 388 389
> 说明1:由于员工与部门之间存在多对一外键关联,所以也能通过部门反向查询该部门的员工(从一对多关系中“一”的一方查询“多”的一方),反向查询属性默认的名字是`类名小写_set`(如上面例子中的`emp_set`),当然也可以在创建模型时通过`ForeingKey`的`related_name`属性指定反向查询属性的名字。如果不希望执行反向查询可以将`related_name`属性设置为`'+'`或以`'+'`开头的字符串。

> 说明2:查询多个对象的时候返回的是QuerySet对象,QuerySet使用了惰性查询,即在创建QuerySet对象的过程中不涉及任何数据库活动,等真正用到对象时(求值QuerySet)才向数据库发送SQL语句并获取对应的结果,这一点在实际开发中需要引起注意!

> 说明3:可以在QuerySet上使用`update()`方法一次更新多个对象。
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
390 391 392 393

#### 删除

```Shell
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
394 395
>>> Dept.objects.get(pk=40).delete()
(1, {'hrs.Dept': 1})
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
396
```
397 398 399

### Django模型最佳实践

400
1. 正确的为模型和关系字段命名。
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
401 402 403
2. 设置适当的`related_name`属性。
3.`OneToOneField`代替`ForeignKeyField(unique=True)`
4. 通过“迁移操作”(migrate)来添加模型。
404
5. 用NoSQL来应对需要降低范式级别的场景。
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
405
6. 如果布尔类型可以为空要使用`NullBooleanField`
406
7. 在模型中放置业务逻辑。
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
407
8.`<ModelName>.DoesNotExists`取代`ObjectDoesNotExists`
408
9. 在数据库中不要出现无效数据。
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
409 410 411 412
10. 不要对`QuerySet`调用`len()`函数。
11.`QuerySet``exists()`方法的返回值用于`if`条件。
12.`DecimalField`来存储货币相关数据而不是`FloatField`
13. 定义`__str__`方法。
413 414
14. 不要将数据文件放在同一个目录中。

415 416
> 说明:以上内容来自于STEELKIWI网站的[*Best Practice working with Django models in Python*](https://steelkiwi.com/blog/best-practices-working-django-models-python/),有兴趣的小伙伴可以阅读原文。

417 418 419 420 421 422 423 424 425 426 427
### 模型定义参考

#### 字段

对字段名称的限制

- 字段名不能是Python的保留字,否则会导致语法错误
- 字段名不能有多个连续下划线,否则影响ORM查询操作

Django模型字段类

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
| 字段类                |  说明                                                         |
| --------------------- | ------------------------------------------------------------ |
| AutoField             |自增ID字段                                                   |
| BigIntegerField       |64位有符号整数                                               |
| BinaryField           | 存储二进制数据的字段,对应Python的bytes类型                  |
| BooleanField          | 存储True或False                                              |
| CharField             | 长度较小的字符串                                             |
| DateField             | 存储日期,有auto_now和auto_now_add属性                       |
| DateTimeField         | 存储日期和日期,两个附加属性同上                             |
| DecimalField          |存储固定精度小数,有max_digits(有效位数)和decimal_places(小数点后面)两个必要的参数 |
| DurationField         |存储时间跨度                                                 |
| EmailField            | 与CharField相同,可以用EmailValidator验证                    |
| FileField             | 文件上传字段                                                 |
| FloatField            | 存储浮点数                                                   |
| ImageField            | 其他同FileFiled,要验证上传的是不是有效图像                  |
| IntegerField          | 存储32位有符号整数。                                         |
| GenericIPAddressField | 存储IPv4或IPv6地址                                           |
| NullBooleanField      | 存储True、False或null值                                      |
| PositiveIntegerField  | 存储无符号整数(只能存储正数)                               |
| SlugField             | 存储slug(简短标注)                                         |
| SmallIntegerField     | 存储16位有符号整数                                           |
| TextField             | 存储数据量较大的文本                                         |
| TimeField             | 存储时间                                                     |
| URLField              | 存储URL的CharField                                           |
| UUIDField             | 存储全局唯一标识符                                           |
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475

#### 字段属性

通用字段属性

| 选项           | 说明                                                         |
| -------------- | ------------------------------------------------------------ |
| null           | 数据库中对应的字段是否允许为NULL,默认为False                |
| blank          | 后台模型管理验证数据时,是否允许为NULL,默认为False          |
| choices        | 设定字段的选项,各元组中的第一个值是设置在模型上的值,第二值是人类可读的值 |
| db_column      | 字段对应到数据库表中的列名,未指定时直接使用字段的名称       |
| db_index       | 设置为True时将在该字段创建索引                               |
| db_tablespace  | 为有索引的字段设置使用的表空间,默认为DEFAULT_INDEX_TABLESPACE |
| default        | 字段的默认值                                                 |
| editable       | 字段在后台模型管理或ModelForm中是否显示,默认为True          |
| error_messages | 设定字段抛出异常时的默认消息的字典,其中的键包括null、blank、invalid、invalid_choice、unique和unique_for_date |
| help_text      | 表单小组件旁边显示的额外的帮助文本。                         |
| primary_key    | 将字段指定为模型的主键,未指定时会自动添加AutoField用于主键,只读。 |
| unique         | 设置为True时,表中字段的值必须是唯一的                       |
| verbose_name   | 字段在后台模型管理显示的名称,未指定时使用字段的名称         |

ForeignKey属性

476 477 478 479 480 481 482 483 484
1. limit_choices_to:值是一个Q对象或返回一个Q对象,用于限制后台显示哪些对象。
2. related_name:用于获取关联对象的关联管理器对象(反向查询),如果不允许反向,该属性应该被设置为`'+'`,或者以`'+'`结尾。
3. to_field:指定关联的字段,默认关联对象的主键字段。
4. db_constraint:是否为外键创建约束,默认值为True。
5. on_delete:外键关联的对象被删除时对应的动作,可取的值包括django.db.models中定义的:
   - CASCADE:级联删除。
   - PROTECT:抛出ProtectedError异常,阻止删除引用的对象。
   - SET_NULL:把外键设置为null,当null属性被设置为True时才能这么做。
   - SET_DEFAULT:把外键设置为默认值,提供了默认值才能这么做。
485 486 487

ManyToManyField属性

488 489 490 491
1. symmetrical:是否建立对称的多对多关系。
2. through:指定维持多对多关系的中间表的Django模型。
3. throughfields:定义了中间模型时可以指定建立多对多关系的字段。
4. db_table:指定维持多对多关系的中间表的表名。
492 493 494 495 496

#### 模型元数据选项

| 选项                  | 说明                                                         |
| --------------------- | ------------------------------------------------------------ |
497
| abstract              | 设置为True时模型是抽象父类                                   |
498 499
| app_label             | 如果定义模型的应用不在INSTALLED_APPS中可以用该属性指定       |
| db_table              | 模型使用的数据表名称                                         |
500
| db_tablespace         | 模型使用的数据表空间                                         |
501
| default_related_name  | 关联对象回指这个模型时默认使用的名称,默认为<model_name>_set |
502
| get_latest_by         | 模型中可排序字段的名称。                                     |
503
| managed               | 设置为True时,Django在迁移中创建数据表并在执行flush管理命令时把表移除 |
504
| order_with_respect_to | 标记对象为可排序的                                           |
505
| ordering              | 对象的默认排序                                               |
506
| permissions           | 创建对象时写入权限表的额外权限                               |
507
| default_permissions   | 默认为`('add', 'change', 'delete')`                          |
508 509 510 511
| unique_together       | 设定组合在一起时必须独一无二的字段名                         |
| index_together        | 设定一起建立索引的多个字段名                                 |
| verbose_name          | 为对象设定人类可读的名称                                     |
| verbose_name_plural   | 设定对象的复数名称                                           |
512

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
513
### 查询参考
514

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
515
按字段查找可以用的条件:
516

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
1. exact / iexact:精确匹配/忽略大小写的精确匹配查询
2. contains / icontains / startswith / istartswith / endswith / iendswith:基于`like`的模糊查询
3. in:集合运算
4. gt / gte / lt / lte:大于/大于等于/小于/小于等于关系运算
5. range:指定范围查询(SQL中的`between…and…`
6. year / month / day / week_day / hour / minute / second:查询时间日期
7. isnull:查询空值(True)或非空值(False)
8. search:基于全文索引的全文检索
9. regex / iregex:基于正则表达式的模糊匹配查询

Q对象(用于执行复杂查询)的使用:

```Shell
>>> from django.db.models import Q
>>> Emp.objects.filter(
...     Q(name__startswith='张'),
...     Q(sal__gte=5000) | Q(comm__gte=1000)
534
... ) # 查询名字以“张”开头且工资大于等于5000或补贴大于等于1000的员工
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
535 536
<QuerySet [<Emp: 张三丰>]>
```
537 538