@@ -117,13 +117,18 @@ urlpatterns = [
117
117
!!! question 思考题
118
118
假如 ` <project>/urls.py ` 要把所有的请求都转发给 ` api/urls.py ` ,` <project>/urls.py ` 中 ` path ` 应如何填写?
119
119
120
+ ## Django 中的 MVC 框架
121
+
122
+ 我们认为, MVC 中的 M 对应 ` Model ` , V 对应 ` Template ` , C 对应 ` View ` .
120
123
121
124
122
125
## 模型(Models)
123
126
124
127
!!! note 数据库使用的相关知识
125
128
如果你对记录、主键、外键、索引、联合主键这些概念并不熟悉,你可以阅读 [ 这篇文档] ( https://zhuanlan.zhihu.com/p/64368422 ) 快速入门。在实际的开发过程中,这些元数据对于数据表的设计以及数据库使用起来的性能是至关重要的。
126
129
130
+ Model 是后端的核心: 其定义了如何高效保存数据, 使得应用运行时的任意与数据库有关的操作的复杂度能控制在可以接受的范围内
131
+
127
132
在 Django 中,模型用于数据库中数据表的结构设计以及数据表的元数据(如主键、外键、索引等)管理。我们使用 Django 提供的 ORM 机制来进行对数据表和数据表列属性的管理。具体来说,我们只需要在 ` <app>/models.py ` 中定义一个类继承 ` django.db.models.Model ` 即可,比如[ 官方文档] ( https://docs.djangoproject.com/zh-hans/4.1/intro/tutorial02/ ) 中给出的定义:
128
133
129
134
``` python
@@ -145,7 +150,7 @@ class Choice(models.Model):
145
150
python3 manage.py makemigrations < app_name>
146
151
```
147
152
148
- 注意请不要将 migrations 文件夹纳入到 .gitignore 文件中。进而,每次在服务端部署时,在其运行之前,请确保你的部署脚本会执行:
153
+ 注意请 ** 不要 ** 将 migrations 文件夹纳入到 .gitignore 文件中 (此时在你更改数据库之后你应该运行 makemigrations, 这会定义如何将之前的数据库正确迁移到新的数据表结构中, 以保证数据库的连续性) 。进而,每次在服务端部署时,在其运行之前,请确保你的部署脚本会执行:
149
154
150
155
``` bash
151
156
python3 manage.py migrate
@@ -158,10 +163,103 @@ python3 manage.py migrate
158
163
!!! question 思考题
159
164
请查阅文档,主键、外键、联合主键、唯一性约束、索引这些元数据都应该如何创建?
160
165
166
+ !!! tip 自动创建的主键
167
+ 如果没有显式定义主键, Django 会自动创建一个名为 ` id ` 的主键, 且会自动增加 ` unique=True ` 约束. 你可以通过 ` primary_key=True ` 来显式定义主键.
168
+
161
169
!!! note 参考资料
162
170
本节对应官方文档 “编写你的第一个 Django 应用,第 2 部分” 中“数据库配置”、“创建模型”、“激活模型”节。
163
171
172
+ 关于更多字段类型, 参阅 [Django Model Field Reference](https://docs.djangoproject.com/en/5.0/ref/models/fields/).
173
+
174
+ !!! warning 注意
175
+ Django 的不同 ORM 后端可能带来不一样的特性! 如在 SQLite 中, 以下定义
176
+
177
+ ```python
178
+ id = models.CharField(max_length=10, primary_key=True)
179
+ ```
180
+
181
+ 中虽然 `id` 的 `max_length` 是 10, 你依然可以存一个长度大于 10 的字符串进去而不引发错误. 但这并不代表着在 *任意* DB Backend 中都可以这样! 换句话说, Django 可能并不能完整校验你的数据, 导致你的代码在不同的数据库后端中可能会有不同的行为. 所以 **你需要在代码中显式校验你的数据**
182
+
183
+
184
+ 在完成字段定义之后, 可以使用 ` Question.objects ` 这个全局的对象来操作数据库. 这是一个 ` <django.db.models.manager.Manager object at 0x7dd182ee0ec0> ` 对象, 提供了增 (create) / 删 (delete) / 改 (update) / 查 (filter / get) 等等操作. 同时, 你也可以拿到实例之后和修改 Python 对象一样修改这个实例, 然后调用 ` save() ` 方法来保存修改.
185
+
186
+ ``` python
187
+ # Create
188
+ Question.objects.create(question_text = " Q1" )
189
+
190
+ # or
191
+ q = Question(question_text = " Q2" )
192
+ q.save() # This line raises if any constraints fail
193
+
194
+ # Query
195
+ Question.objects.filter(question_text = ' 1' )
196
+
197
+ # or
198
+ q = Question.objects.get(id = 1 ) # This line raises if none match / multiple items match
199
+
200
+ # Update
201
+ Question.objects.filter(id = 1 ).update(question_text = ' Q?' )
202
+ # Returns the number of items updated
203
+
204
+ # or
205
+ q = Question.objects.get(id = 1 )
206
+ q.question_text = " Updated Text"
207
+ q.save()
208
+
209
+ # Delete
210
+ Question.objects.filter(id = 1 ).delete()
211
+ ```
212
+
213
+ 更进阶的用法可以参考 [ Django ORM] ( https://docs.djangoproject.com/en/5.0/topics/db/queries/ ) . 我只想在此指出, 由于这些操作 ** 都是动态完成的** (IDE 没法帮你检查), 所以在更改 Model 的字段名称时, 需要格外小心检查是否所有静态和动态引用都得到了正确的更新.
214
+
215
+ ### 数据库的一致性
216
+
217
+ 在 Django 中, 除了部分操作 (create / update / delete) 之外, 对 object 的修改必须通过调用 ` save() ` 来保存; 而以上的操作都是实时写入 DB 的 (Auto-Commit).
218
+
219
+ 数据库有关的操作都不可避免地存在可能的异常, 不管是由于网络问题, 由于自身的数据校验问题, 或者并发问题. 如果在部分操作已经写入数据库时出现异常, 则可能导致数据库不一致.
164
220
221
+ !!! note 什么是数据库不一致
222
+ 举个例子, 如果你在一个事务中, 先创建了一个 User, 然后创建了一个 Profile, 如果在创建 Profile 时出现异常, 则数据库中会有一个 User, 但是没有对应的 Profile. 这种情况下, 数据库就是不一致的.
223
+
224
+ 数据库不一致会导致你的应用出现各种奇怪的问题, 因为在编写代码时我们常常假定数据库中的数据是正确的. 比如在使用 User 的 Profile 时, 我们一般不会额外检查 User 是否存在 Profile, 而是找到 User 后直接查询 Profile.
225
+
226
+ 因此, 期望的数据库操作应该是 ** 原子的** (Atomic), 即要么全部成功, 要么全部失败. 可以通过我们自己写 Revert 逻辑, 也可以通过 Django 提供的 [ ` transaction.atomic ` ] ( https://docs.djangoproject.com/en/5.0/topics/db/transactions/ ) 来实现.
227
+
228
+ 在 ` atomic ` 块中的所有数据库操作要么全部成功; 要么全部失败. 如果在 ` atomic ` 块中的某个地方抛出异常, 数据库会回滚到 ` atomic ` 块开始的状态. 这有效解决了因为奇怪问题导致的数据库不一致错误.
229
+
230
+ ``` python
231
+ # All operations in this block are atomic
232
+ with transaction.atomic():
233
+ u = User.objects.create(username = ' john' )
234
+ p = Profile.objects.create(user = u)
235
+ ```
236
+
237
+ ### Admin 管理面板
238
+
239
+ Django 提供了一个内建的管理面板以方便管理数据库中的数据.
240
+
241
+ 通过一两句话 (此处以另一个小项目作为例子)
242
+
243
+ ``` python
244
+ from .models import Student, Course
245
+
246
+ admin.site.register(Student)
247
+ admin.site.register(Course)
248
+ ```
249
+
250
+ 你就可以在管理面板中对这些 Model 进行增删改查操作了. 但首先, 你需要用
251
+
252
+ ``` sh
253
+ ./manage.py createsuperuser
254
+ ```
255
+
256
+ 来创建一个超级用户, 然后用这个超级用户登录管理面板.
257
+
258
+ ![ Django Admin Panel] ( ../../static/backend/django/django-admin-panel.png )
259
+
260
+ ![ Django Admin Manage Model] ( ../../static/backend/django/django-admin-manage-model.png )
261
+
262
+ Django Admin 可以让你在生产环境爆炸的时候迅速修数据库 (x)
165
263
166
264
## 视图(Views)
167
265
@@ -250,7 +348,6 @@ def message(request): # 这里 request 是 HttpRequest 类型的对象
250
348
本节对应官方文档 “编写你的第一个 Django 应用,第 1 部分” 中“编写第一个视图”节,“编写你的第一个 Django 应用,第 3 部分”中“编写更多视图”、“写一个真正有用的视图”、“抛出 404 错误”节。
251
349
252
350
253
-
254
351
## 单元测试(Unit Tests)
255
352
256
353
接下来我们介绍单元测试。在课程中我们学过,对模块进行测试,相比对模块拼接起来的系统直接测试所付出的代价要小得多。在真正的软件开发过程中,单元测试环节就是要对我们所开发的模块进行自动化的测试。在 Django 中,测试工程师会将开发工程师所编写的路由与视图视为黑盒,通过 ` django.test.TestCase ` 类与 ` django.test.Client ` 类来模拟前端与开发工程师所撰写的后端交互,并通过其提供的断言函数来断言响应所应该具有的属性或是数据库应被如何修改。以上述的留言板应用的视图函数为例,我们可以撰写如下测试:
@@ -405,3 +502,4 @@ def index(request):
405
502
406
503
- [ 《软件工程》课程小作业讲稿与文档] ( https://thuse-course.github.io/course-index/ )
407
504
- [ Django 官方文档] ( https://docs.djangoproject.com/en/4.2/ )
505
+ - [ 2024 科协暑培文档] ( https://summer24.net9.org/backend/django/django/ )
0 commit comments