-
Notifications
You must be signed in to change notification settings - Fork 47
Expand file tree
/
Copy pathframeHint.txt
More file actions
693 lines (635 loc) · 42 KB
/
Copy pathframeHint.txt
File metadata and controls
693 lines (635 loc) · 42 KB
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
对于Django、DRF的一些框架功能提示:
1、模型相关
字段的常用、通用Option
null = True # 该字段允许值为空,也就是MySQL上的NULL,默认为False
blank = True # 允许键为空,指定的字段可以不传,注意,这与null不同。null纯属数据库相关,而blank则与验证相关。默认为False
choices = ((0,'男'),('1','女'),) # 选项类型,类似于枚举类型,会被模型验证,如果传入的值不在选项中会引发验证异常。
default = 1 # 指定默认值,可以是一个值或者是个可调用的对象,如果是个可调用对象,每次实例化模型时都会调用该对象。
db_index=True # 为字段设置普通索引
verbose_name = '描述' # 指定label 或字段描述,相当于MySQL中字段的备注。
validators = [] # 接收一个验证器列表,用于对指定字段传入数据的验证。
db_column = 'test' # 指定这个字段要使用的数据库列名,如果没有给出,将使用字段名。
primary_key = False # 指定字段为主键,通常用不到,使用内置的ID作为主键即可。
unique = True # 指定字段唯一性约束也是唯一索引,通常会使用联合唯一,因此这个也不常用。
editable = True # 如果为False表示字段不会被修改,因为会被模型验证跳过。默认为True
常用模型字段类型:
CharField # 字符串类型,存在一个特殊的必传参数:
max_length = 255 # 指定字段容量,无论中英文值得都是字符总长度,必须要指定,并且最大为255。
那么需要更大一点但是又不需要使用大字段时怎么办呢?其实就是使用TextField设置max_length而已
TextField # 大文本类型,这个字段比较特殊,当你设置了max_length,并且没有超过65535个字节时,它实际在数据库使用的varchar类型。
JSONField # 存储json字符串类型,Django3.1开始新增的的一个类型,本质上是字符串类型。
DateField # 日期类型,存在两个特殊参数
auto_now = False # 每次保存对象时,自动将该字段设置为现在。对于 最后修改 的时间戳很有用。默认为False
auto_now_add = False # 当第一次创建对象时,自动将该字段设置为现在。对创建时间戳很有用。默认为False
注:目前 将 auto_now 或 auto_now_add 设置为 True,将导致该字段设置为 editable=False 和 blank=True。
auto_now_add、auto_now 和 default 选项是相互排斥的。这些选项的任何组合都会导致错误。
DateTimeField # 时间类型,同样支持 auto_now_add、auto_now
TimeField # 时间类型,同样支持 auto_now_add、auto_now
IntegerField # 一个整数类型,32位的,取值 -2147483648 到 2147483647
BigIntegerField # 一个大整数,64位的,取值 -9223372036854775808 到 9223372036854775807
BooleanField # bool类型,注:建议定义默认值,否则会导致null为True,就是允许为NULL
DecimalField # 一个固定精度的十进制数 可以指定长度和小数位数,存在两个必传参数:
max_digits = 15 # 指定这个数的最大长度,包括小数位在内
decimal_places = 2 # 指定小数点后小数位数
EmailField # 邮箱类型,本质上是字符串,好处是会自动进行邮箱格式验证。
FloatField # 浮点数类型
UUIDField # 一个用于存储唯一UUID的类型,可以通过default=uuid.uuid1 来赋默认值。
例如想指定主键为UUID:id = UUIDField(primary_key=True, default=uuid.uuid1, editable=False)
字段关系
ForeignKey(OtherModel, on_delete=PROTECT, verbose_name='label', related_name='related_name') # 外键类型
OneToOneField(OtherModel, on_delete=PROTECT, verbose_name='label', related_name='related_name') # 一对一的外键类型
ManyToManyField(OtherModel, verbose_name='label', blank=True, related_name='related_name') # 多对多类型,django会维护一个中间表。如果需求更多,可以自行使用中间表实现
related_name 的作用是将被外键关联的数据,通过related_name定义的命令来访问所有关联到它的数据,并且要求这个字段对于被关联表是唯一的。
on_delete属性选项的意义:
PROTECT:当外键数据删除时,指向外键的的数据不删除。
CASCADE:当外键数据删除时,指向外键的数据一起删除。
SET_NULL:当外键数据删除时,指向外键的数据该列设置为null。
SET_DEFAULT:当外键数据删除时,指向外键的数据该列恢复默认值。
SET(自定义方法):当外键数据删除时,指向外键的数据使用自定义方法来设置值。具体可参考官方文档。
模型元选项,也称Meta选项
abstract = True # 表示该模型是抽象基类,不能作为实际的模型,用于其他模型继承这个模型。
app_label = 'myapp' # 声明模型所属的app,可以不写,会默认获取。
db_table = 'Table_Name' # 自定义数据表在数据库中的名称
verbose_name = 'A表' # 自定义数据表的表述,官方叫 对象的人类可读名称 单数形式
verbose_name_plural = verbose_name # 对verbose_name的复数描述
ordering = ['-create_date'] # 指定数据的默认排序,该例表示根据创建时间倒序(不加-为升序)。建议不写,默认是主键升序。
unique_together = [['name1', 'name2']] # 设置联合唯一性约束、联合唯一索引,可以设置多个,为了方便起见,unique_together在处理一组字段时可以是一个一维列表。
index_together = [['name1', 'name2']] # 设置联合普通索引,可以设置多个,为了方便起见,index_together在处理一组字段时可以是一个一维列表。
indexes = [
models.Index(fields=['last_name', 'first_name']),
models.Index(fields=['first_name'], name='first_name_idx'),
] # 定义索引列表 索引列可以统一在这里定义
constraints = [
models.CheckConstraint(check=models.Q(age__gte=18), name='age_gte_18'), # 假设存在一个age字段,这个约束就是保证年龄必须大等于18
] # 定义模型的约束列表
2、ORM相关
ORM操作指南
由于本项目使用了假删除(或称软删除),因此在实际使用上会多出一些功能。
objects 返回没有删除的所有数据
all_objects 返回所有数据,包括已删除的数据
delete 删除数据,假删
undelete 恢复假删数据 意味着之前获取的实例是通过 all_objects 获取的
hard_delete 硬删除,数据会真的从数据表中删除
假设有一个 User 模型类如下示:
class User(models.Model):
gender_choice = (
('privacy', '保密'),
('male', '男'),
('female', '女')
)
name = models.CharField(max_length=32, verbose_name='用户账号')
mobile = models.CharField(max_length=12, verbose_name='用户手机号')
email = models.EmailField(default='', null=True, verbose_name='用户邮箱')
gender = models.CharField(max_length=16, choices=gender_choice, default='privacy', verbose_name='性别')
class Meta:
db_table = 'user'
verbose_name = '用户表'
verbose_name_plural = verbose_name
indexes = [
models.Index(fields=['name']),
]
unique_together = [
['mobile'],
]
新增数据:
方法一:
user = User(name='asd', mobile='123').save()
方法二:
User.objects.create(name='asd', mobile='123')
方法三:
users = [User(name='asd', mobile='123'), User(name='qwe', mobile='345')]
User.objects.bulk_create(users) # 批量创建
理解QuerySet:
在Django中模型建立后,可以通过ORM和数据库交互,其中最重要的一个就是QuerySet对象,它包含在模型中,通过模型类的 Manager 构建一个 QuerySet。
一个 QuerySet 代表来自数据库中对象的一个集合。它可以有 0 个,1 个或者多个 filters. Filters,可以根据给定参数缩小查询结果量。
简单来说QuerySet就是专门用来通过ORM查询数据库数据,并且返回后会映射为你定义好的模型实例。
QuerySet支持链式调用,也就意味着你得到一个QuerySet对象后,可以返回的通过链式写法调用不同的QuerySet方法。
最简单的 例如 User.objects.all() 返回了一个 QuerySet。示例一个链式调用 User.objects.filter().all()
详细的QuerySet文档地址:https://docs.djangoproject.com/zh-hans/3.2/ref/models/querysets/#filter
主要的QuerySet方法释义:
filter(*args, **kwargs) 用于传递参数做where查询,多个字段为and
all() 用于返回所有数据
excluede(*args, **kwargs) 用于查询不匹配的数据
order_by(*fields) 用于对传入字段进行排序
reverse() 用于翻转查询得到的数据集
distinct(*fields) 用于查询去重,可以指定比较哪些字段,不传表示比较所有字段
union(*other_qs, all=False) 用于组合两个或多个 QuerySet 的结果 例如 qs1.union(qs2, qs3)
values(*fields, **expressions) 用于查询指定的字段,会得到一个字典数据集
raw(raw_query, params=(), translations=None, using=None) 用于执行原生SQL语句
select_for_update(nowait=False, skip_locked=False, of=(), no_key=False) 用于加排它锁查,例如 User.objects.select_for_update().filter(id=1).first()
查找数据:
User.objects.all() # 查询所有数据
User.objects.filter(id=1).exists() # 查询是否存在数据,如果 QuerySet 包含任何结果,则返回 True,如果不包含,则返回 False。
User.objects.filter(id=1).first() # 得到一个对象,如果没有返回None
User.objects.filter(id=1).last() # 查询最后一个数据
User.objects.get(id=1) # 得到一个对象,如果没有会抛出 Entry.DoesNotExist 异常
User.objects.filter(id__gte=1) # 查询id大等于1的数据 __gt 大于
User.objects.filter(id__lte=1) # 查询id小等于1的数据 __lt 小于
User.objects.filter(id__in=[1,2,3]) # 查询id指定区间的数据
User.objects.exclude(id=10) # 查询id不等于10的数据
User.objects.filter(name='test').exclude(id=10) # 查询name为test但id不等于10的数据,ORM支持链式调用 并且exclude内同样支持范围查询
User.objects.order_by('date') # 对结果集进行排序,使用date字段升序,-date表示倒序
User.objects.count() # 返回数据的总数
User.objects.filter(name__icontains='文') # 查询用户名字里面包含 文
__iexact 精确等于 忽略大小写 ilike 'aaa'
__contains 包含 like '%aaa%'
__icontains 包含 忽略大小写 ilike '%aaa%',但是对于sqlite来说,contains的作用效果等同于icontains
User.objects.filter(username__isnull=True) # 查询用户名为空的用户
User.objects.exclude(username__isnull=True) # 查询用户名不为空的用户
User.objects.filter(name='test', statu='1') # 表示与查询,名称为test且status为1的数据
User.objects.filter(Q(state=0) | Q(state=1)) # 利用django.db.models.Q实现或查询
User.objects.values('id', 'name').filter(id=1).first() # 只查询数据的id、name字段,返回不是模型实例,而是一个类似字典的结构,可以通过key获取数据
User.objects.values('id', 'name').filter(id=1)# 只查询数据的id、name字段,返回一个列表,列表内元素是类似字典的结构
更详细的字段查找参考:https://docs.djangoproject.com/zh-hans/3.2/ref/models/querysets/#field-lookups
更详细的聚合函数参考:https://docs.djangoproject.com/zh-hans/3.2/ref/models/querysets/#aggregation-functions
修改数据:
方法一:
user = User.objects.filter(id=1).first() # 得到一个对象,如果没有返回None
user.name = 'zxc'
user.save()
方法二:
User.objects.filter(id=1).update(name='zxc') # 这个可以批量更新一批数据,但是有个缺点是无法触发signals中的信号,如果需要触发信号需要使用save
官方对QuerySet.update的说法
QuerySet.update() 用于保存更改,所以这比遍历模型列表并对每个模型调用 save() 更有效,但它有一些注意事项:
你不能更新模型的主键。
每个模型的 save() 方法没有被调用,而且 pre_save 和 post_save 信号没有被发送。
如果更新大量行中的大量列,生成的 SQL 可能非常大。通过指定一个合适的 batch_size 来避免这种情况。
更新定义在多表继承祖先上的字段将给每个祖先带来额外的查询。
当单个批处理包含重复项时,该批处理中只有第一个实例会产生更新。
方法三:批量更新
users = User.objects.all()
users[0].name = 'test1'
users[1].name = 'test2'
Entry.objects.bulk_update(users, ['name']) # 比逐个更新性能更好
删除数据:
方法一:
user = User.objects.filter(id=1).first()
user.delete()
方法二:
User.objects.filter(id__gte=1).delete() # 批量删除一批数据
F对象的使用:
class Test(models.Model):
all_num= models.IntegerField(default=0, verbose_name='总数量 ')
have_num= models.IntegerField(default=0, verbose_name='已有数量')
class Meta:
db_table = 'A_Test_Table'
verbose_name = '测试表'
verbose_name_plural = verbose_name
要实现SQL语句:select * from a_test_tablewhere all_num > have_num
from django.db.models import F
test= Test.objects.filter(all_num__gt=F('have_num'))
执行原始SQL语句:
方法一:
Manager.raw(raw_query, params=(), translations=None)
该方法接受一个原生 SQL 查询语句,执行它,并返回一个 django.db.models.query.RawQuerySet 实例。这个 RawQuerySet 能像普通的 QuerySet 一样被迭代获取对象实例。
User.objects.raw("select * from user") 等价于 User.objects.all()
User.objects.raw("select * from user")[0] 等价于 User.objects.first()
users = Person.objects.raw('SELECT *, age(birth_date) AS age FROM user') # 支持别名
for u in users:
print("%s is %s." % (u.first_name, u.age)) # 支持别名
Person.objects.raw('SELECT * FROM user WHERE name = %s', ['lname']) # 支持传递参数
方法二:有时候,甚至 Manager.raw() 都无法满足需求:你可能要执行不明确映射至模型的查询语句,或者就是直接执行 UPDATE, INSERT 或 DELETE 语句。
from django.db import connection
def my_custom_sql(self):
with connection.cursor() as cursor:
cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [self.baz])
cursor.execute("SELECT foo FROM bar WHERE baz = %s", [self.baz])
row = cursor.fetchone()
return row
注意,要避免 SQL 注入,你绝对不能在 SQL 字符串中用引号包裹 %s 占位符。
注意,若要在查询中包含文本的百分号,你需要在传入参数使用两个百分号:
cursor.execute("SELECT foo FROM bar WHERE baz = '30%'")
cursor.execute("SELECT foo FROM bar WHERE baz = '30%%' AND id = %s", [self.id])
显式的使用事务
from django.db import transaction
@transaction.atomic
def viewfunc(request):
# This code executes inside a transaction.
do_stuff()
from django.db import transaction
def viewfunc(request):
# This code executes in autocommit mode (Django's default).
do_stuff()
with transaction.atomic():
# This code executes inside a transaction.
do_more_stuff()
3、序列化器相关
序列化器字段的通用、常用Option
read_only = True # 表示字段只读,默认为False
write_only = True # 表示字段只写,默认为False
required = True # 表示字段必传,会经过序列化器验证,默认为True
default = None # 表示字段的默认值,需要传入一个明确的值或可调用的对象
allow_blank = False # 表示字段允许传入空值,包括None和空字符串,也可不传
label = "描述" # 用于对字段做描述,短文本描述
help_text = "长文本描述" # 和label的区别就是这个常用于长文本描述
error_messages = {
} # 接收一个异常信息的提示,错误消息的错误代码字典。
validators = [
] # 接收一个验证器列表,用对字段做特殊验证
序列化器常用字段类型
BooleanField() # bool类型
CharField() # 字符串类型,存在多个特殊选项
max_length = 255# 用于指定输入字符串的最大长度,可选
min_length = 10 # 用于指定输入字符串的最小长度,可选
trim_whitespace = True # 为真会自动去除字符串首位空格,默认为True
EmailField() # 邮件类型,本质上是字符串类型,使用它的好处是带有邮箱格式验证。同样支持 max_length min_length选项
IntegerField() # int类型,存在两个特殊选项
max_value = 1000 # 验证提供的数字不大于此值。
min_value = 1 # 验证提供的数字不小于此值。
FloatField(max_value=None, min_value=None) # float类型,同样支持 max_value min_value
DecimalField() # 一个固定精度的十进制数,存在多个选项
max_digits # 数字中允许的最大位数。它必须是None或大于或等于 的整数decimal_places。
decimal_places # 与数字一起存储的小数位数。
coerce_to_string # 设置为True是否应为表示返回字符串值,或者False是否Decimal应返回对象。COERCE_DECIMAL_TO_STRING默认为与设置键相同的值,True除非被覆盖。如果Decimal序列化器返回对象,则最终输出格式将由渲染器确定。请注意,设置localize会将值强制为True。
max_value # 验证提供的数字不大于此值。
min_value # 验证提供的数字不小于此值。
localize # 设置为True启用基于当前语言环境的输入和输出本地化。这也将coerce_to_string迫使True。默认为False. 请注意,如果您USE_L10N=True在设置文件中进行了设置,则会启用数据格式。
rounding # 设置量化为配置精度时使用的舍入模式。有效值为decimal模块舍入模式。默认为None.
DateTimeField() # 时间类型,支持多个特殊参数
format = "%Y-%m-%d %H:%:M:%S" # 用于指定输出时间的格式
input_format = ["%Y-%m-%d %H:%:M:%S", ] # 用于指定输入时间的格式字符串列表
DateField() # 日期格式,同样支持 format input_format
ChoiceField() # 选择类型或枚举类型,只能选择一个
choices = ((1, 'A'), (2, 'B')) # 有效值列表或(key, display_name)元组列表。
allow_blank = Fasle # 如果设置为,True则空字符串应被视为有效值。如果设置为,False则空字符串被视为无效并会引发验证错误。默认为False.
MultipleChoiceField() # 多选类型,和选择类型类似,区别在于这个允许选择多个选项。
ListField(child=IntegerField(min_value=0, max_value=100)) # 复合类型,用于验证数组类型数据,并且支持对数组内元素验证
child # 应用于验证列表中的对象的字段实例。如果未提供此参数,则不会验证列表中的对象。
allow_empty # 指定是否允许空列表。
min_length # 验证列表包含不少于此数量的元素。
max_length # 验证列表包含不超过此数量的元素。
DictField(child=<A_FIELD_INSTANCE>, allow_empty=True) DictField(child=CharField()) # 复合类型,用于类似于字典的数据,会验证key和value
child # 应该用于验证字典中的值的字段实例。如果未提供此参数,则不会验证映射中的值。
allow_empty # 指定是否允许空字典。
JSONField() # 复合类型,用于验证json数据,实际中常用的是通过多个序列化器的组合来实现,因为那样更友好、便捷
binary # 如果设置为,True则该字段将输出并验证 JSON 编码字符串,而不是原始数据结构。默认为False.
encoder # 使用此 JSON 编码器序列化输入对象。默认为None.
特殊的字段类型
HiddenField() # 隐藏字段,隐藏后验证器不可见、不可修改,例如 serializers.HiddenField(default=timezone.now) 一般情况会给默认值用于入库的数据初始化
它不根据用户输入获取值,而是从默认值或可调用值中获取值。
通常仅当HiddenField您需要基于某些预先提供的字段值运行一些验证时才需要该类,但您不希望将所有这些字段公开给最终用户。
注意:被HiddenField处理的字段,将会对前端无感,前端在增删改查时都将无感该字段
注意:HiddenField 工作在创建数据时,只有POST时才有效
注意:如果对同一个字段使用HiddenField和read_only_fields,只有HiddenField有效
SerializerMethodField(method_name=None) # 序列化器方法字段,这个字段用于序列化器返回数据时,可以对某些字段进行加工后再返回,可以用来追加返回的字段。
method_name # 指定利用的函数名称,如果不传,则默认为get_<field_name>.
示例 new_name = SerializerMethodField() 此时需要在对应的类中存在一个名为get_new_name的方法,并且返回了合适的值。
注意:SerializerMethodField 只在返回数据时有效,也就是工作在 GET 请求的场景。
基于模型的序列化器元类选项Option
model = User # 用于指定模型
fields = '__all__' or ('id', 'name') # 用于指定哪些字段可以操作,__all__表示所有字段
注意:如果启用了fields,并且存在SerializerMethodField,要保证SerializerMethodField定义的字段包含在fields中
exclude = ('id', ) # 表示禁止序列化的字段,和fields左右是相反的。直接无视该字段的增删改查,一般用于字段被数据库或框架管理,例如是否被删除的标志字段。
注意:模型序列化器中 fields和exclude必须选择其中之一
read_only_fields = ('age', ) # 表示只读的字段,和字段中read_only=True效果一样,好处时可以统一设定只读字段。
注意:已editable=False设置的模型字段,和AutoField字段默认设置为只读,不需要添加到read_only_fields选项中。
注意:只读的字段可以在后头validate中用代码赋值。
depth = 2 # 表示对外键和嵌套关系的处理,例如有外键指向别的模型或别的模型指向自己,可通过depth快速进行层次序列化,缺点是无法指定字段,造成浪费和无法控制
因此建议自行实现管理数据的序列化。后面会给出实例
4、信号
什么是信号:
Django有一个 信号调度器(signal dispatcher),用来帮助解耦的应用获知框架内任何其他地方发生了操作。
简单地说,信号允许某些 发送器 去通知一组 接收器 某些操作发生了。当许多代码段都可能对同一事件感兴趣时,信号特别有用。
Django 提供了 内置信号集 使用户代码能够获得 Django 自身某些操作的通知。其中包括一些有用的通知:
django.db.models.signals.pre_save & django.db.models.signals.post_save
一个模型的 save() 方法被调用之前或之后发出。
django.db.models.signals.pre_delete & django.db.models.signals.post_delete
一个模型的 delete() 方法或查询结果集的 delete() 方法被调用之前或之后发出。
django.db.models.signals.m2m_changed
一个模型的 ManyToManyField 更改后发出。
django.core.signals.request_started & django.core.signals.request_finished
Django 发起或结束一个 HTTP 请求后发出。
详细的信号文档参考:https://docs.djangoproject.com/zh-hans/3.2/topics/signals/
内置信号文档:https://docs.djangoproject.com/zh-hans/3.2/ref/signals/
定义和发送信号:https://docs.djangoproject.com/zh-hans/3.2/topics/signals/#defining-and-sending-signals
5、如何使用自定义缓存
什么是自定义缓存:
本项目实现了一个动态缓存装饰器,可以用来装饰视图函数,并写了一个基于缓存的viewset类,可以实现接口完全基于缓存,获取数据时只有首次是查库,剩余的都是存在缓存中,只有发生变更时才会更新缓存。
实现方案:
1、对所有请求加锁,并且进行读写分离加锁,互斥锁,当有读请求时,会阻塞写操作,同读不阻塞。
2、第一次请求后,会根据请求的参数生成一个指纹,会将这个请求缓存起来,直到发生写操作才将缓存失效,失效后,当新的查询请求结束后会设置上新的缓存。如此往复。
3、对于CRUD操作,可以直接继承内置的MyViewSet来实现。对于自定义的接口,可以通过缓存装饰器来实现。
# 针对自定义缓存的示例:
from extensions.MyCacheViewset import MyModelViewSet
class UserViewSet(MyModelViewSet): # 基于自定义缓存的ViewSet类
'''
partial_update: 更新指定ID的用户,局部更新
create: 创建用户
retrieve: 检索指定ID的用户
update: 更新指定ID的用户
destroy: 删除指定ID的用户
list: 获取用户列表
'''
queryset = User.objects.filter()
serializer_class = UserViewsetSerializer
authentication_classes = (JwtAuthentication, )
permission_classes = (AllowAny, )
throttle_classes = (VisitThrottle, )
pagination_class = Pagination
filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter)
search_fields = ('name', 'desc') # 注意 要针对有索引的字段进行搜索
# filterset_fields = ('status', )
ordering_fields = ('id', 'create_timestamp', 'update_timestamp', 'sort_timestamp')
is_public = True # 针对自定义缓存的一个配置项,如果为真表示这个是公共接口不会根据用户id来设置缓存,否则会根据用户id来设置缓存key
def get_serializer_class(self):
if self.action == 'create':
return CreateUserViewsetSerializer
if self.action in {'update', 'partial_update'}:
return UpdateUserViewsetSerializer
return ReturnUserViewsetSerializer
class GetAllEnumDataView(GenericAPIView):
authentication_classes = (JwtAuthentication, )
permission_classes = (AllowAny, )
throttle_classes = (VisitThrottle, )
@swagger_auto_schema(responses={200: GetAllEnumDataResponse})
@RedisCacheForDecoratorV1('r')
def get(self, request):
'''针对自定义的视图方法使用自定义的缓存装饰器类'''
res = MyJsonResponse(res_data={'msg': gettext_lazy('测试成功')})
pass
return res.data
# 针对序列化器的详细示例:
from rest_framework import serializers
def get_ObjectFlow(type):
'''
返回对象
'''
if type == 0:
approval_flow = TableClass.objects.filter(flow_name='请假审批').first()
elif type == 1:
approval_flow = TableClass.objects.filter(flow_name='出差审批').first()
else:
return None
if approval_flow:
object_flow = TableClass()
return object_flow
else:
return None
class CurrentUser(object):
'''
返回指定的值
使用该方法可以做一些后端数据自动计算的工作
'''
def set_context(self, serializer_field):
self.user_obj = serializer_field.context['request'].user
def __call__(self):
return self.user_obj
class AddUserSerializer(serializers.ModelSerializer):
# 在源头上防止出现重复数据
name = serializers.CharField(label="用户名", help_text="用户名", required=True, allow_blank=False,
validators=[UniqueValidator(queryset=User.objects.all(), message="用户已经存在")])
# 防止出现重复数据的同时,对数据进行验证
mobile = serializers.CharField(label="手机号", help_text="手机号", required=True, allow_blank=False,
validators=[UniqueValidator(queryset=User.all_objects.all(), message="手机号已经存在")],
error_messages={"required": '手机号不能为空', 'blank': '手机号不能为空', 'null': '手机号不能为空'})
# 将一个外键设为只读,防止前端传入值进来修改
object_flow = serializers.PrimaryKeyRelatedField(read_only=True)
# 在执行新增操作的时候,在前端隐藏user的传入,后端默认使用当前用户插入
# 第二种方法 user = serializers.HiddenField(default=serializers.CurrentUserDefault(), label='用户') 其实就是restframwork实现了CurrentUser
user = serializers.HiddenField(default=CurrentUser())
# 指定这个字段在返回给前端之前,也就是序列化时 可以按照指定的函数进行序列化
order_int = serializers.SerializerMethodField(label='订单数')
# 当有模型的外键指向当前模型时,可以通过related_name反向插入所有关联的子数据 接收的子的Serializer
oilcan_datas = DepotUseOilcanDataSerializer(read_only=True, many=True) # 通过related_name和模型的序列化器来返回序列化的反向数据集,也可以通过函数字段自行实现,父找子
# 将对应的外键通过 父Serializer序列化后返回给前端
depart = ReturnDepartSerializer() # 通过外键和外键模型的序列化器来返回序列化的外键数据,也可以通过函数字段自行实现,子找父
# 用于指定日期的输入格式、返回格式,注意可以在settings中统一设置后,可以省略这里的代码
start_date = serializers.DateTimeField(label='开始日期', format='%Y-%m-%d %H:%M:%S', input_formats=['%Y-%m-%d %H:%M:%S', '%Y-%m-%d'], required=False, allow_null=True)
class Meta:
model = User
fields = '__all__' # __all__用于序列化所有字段 or exclude = ('deleted',)指定,某些字段不被序列化返回 or fields = ['company','username',]指定序列化的字段
read_only_fields = ('user', 'object_flow',) # 指定只读的字段,这样就不会被前端修改
validators = [UniqueTogetherValidator(queryset=Auth.objects.all(), fields=['auth_type',], message='该权限已经存在')] # 多字段联合唯一
def validate(self, attrs):
# 重写验证器
now_user = self.context['request'].user
# 查看前端传来的所有数据
print('查看attrs:', attrs)
# 查看前端是否有通过pk检索数据 来做出相应的改变
print('查看pk:', self.context['view'].kwargs.get('pk'))
# 在这里可以获取body参数
print('查看body参数:', self.context['request'].data)
flow_obj = get_ObjectFlow(0)
if not flow_obj:
raise serializers.ValidationError("审批流不存在")
attrs['object_flow'] = flow_obj
return attrs
def validate_name(self, value):
try:
# 用于独立验证name字段
# do something
return value
except Exception as e:
raise serializers.ValidationError(e)
@transaction.atomic
def update(self, instance, validated_data):
# 重写更新方法
obj = super().update(instance, validated_data)
# do something
return obj
@transaction.atomic
def create(self, validated_data):
# 重写创建方法
obj = super().create(validated_data)
# do something
return obj
def get_order_int(self, obj):
# 这种方法只会在返回时被调用
# 在这里可以通过GET参数 做一些返回操作 注意:只有在使用viewset里面才有效
print('查看GET参数:', self.context['request'].query_params)
num = 0
return num
总结:
1、隐藏字段和只读字段的区别
隐藏字段会将字段完全隐藏,可以设置默认值,但是默认值前端不可见,例如在创建数据时 创建者 字段是一个隐藏字段,默认值是当前调用者,此时数据创建成功后,该字段也不会返回给用户。
只读字段会禁止前端传入数据给字段赋值,赋值可以在后台完成,这样的好处是被操作的字段可以在数据创建成功后返回给用户。
隐藏字段只在创建数据时有效,只读字段在数据创建和修改时都有效。
2、前端新增或修改时 即不需要设置也不需要返回 使用 exclude 包含进去
前端新增时 即不需要设置也不需要返回 但需要后端设置值的 使用 HiddenField 因为 HiddenField 只在新增时有效
前端新增或修改时 不需要设置但需要返回的 使用 read_only_fields 包含进去
2、单独验证字段和在validate中统一验证的区别
单独验证字段,可以进行同时验证,被验证的字段会被统一返回验证信息
在validate中统一验证,存在先后顺序,先验证的字段会触发异常,并返回验证信息
# 针对ViewSet的相关示例:
from rest_framework.viewsets import ModelViewSet
class UserViewSet(ModelViewSet):
'''
更新指定ID的用户,局部更新
create: 创建用户
retrieve: 检索指定ID的用户
update: 更新指定ID的用户
destroy: 删除指定ID的用户
list: 获取用户列表
'''
# 指定使用的QuerySet
queryset = User.objects.filter()
# 指定使用的序列化器
serializer_class = UserViewsetSerializer
# 指定使用的认证类
authentication_classes = (JwtAuthentication, )
# 指定使用的权限类
permission_classes = (AllowAny, )
# 指定使用的频率限制类
throttle_classes = (VisitThrottle, )
# 指定使用的分页类
pagination_class = Pagination
# 指定搜索、过滤、排序使用的类
filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter)
# 指定支持搜索的字段
search_fields = ('name', 'desc') # 注意 尽量针对有索引的字段进行搜索
# 指定支持过滤的字段,搜索和过滤的区别:搜索是部分匹配,过滤是全匹配
filterset_fields = ('status', )
# filterset_class = YourFilter # 如果使用了filter类的写法,和filterset_fields只能存在其中一个
# 指定支持排序的字段,注意 尽量使用有索引的字段进行排序
ordering_fields = ('id', 'create_timestamp', 'update_timestamp', 'sort_timestamp')
def get_serializer_class(self):
'''重写返回序列化的方法,可以根据自己的需求动态的返回序列化器类'''
if self.action in ['create']:
return AddUserSerializer
if self.action in ['update', 'partial_update']:
return UpdateUserSerializer
return ReturnUserSerializer
# 针对Swagger可以指定描述和tag,可以提现在Swagger文档中
@swagger_auto_schema(operation_description="创建用户", operation_summary="创建用户")
def create(self, request, *args, **kwargs):
'''重写创建对象的类,可以在这里进行自定义,一般这里需要自定义的逻辑可以转移到序列化中,重写序列化器的创建方法'''
obj = super().create(request, *args, **kwargs)
return obj
def get_queryset(self):
'''重写get_queryset,这样就可以根据不同的情况返回不同的QuerySet'''
if self.request.auth:
return User.objects.filter(id=self.request.user.id)
return None
def list(self, request, *args, **kwargs):
'''重写返回数据方法,可以自定义实现,本例使接口返回的数据是一个对象而不是数组'''
instance = User.objects.filter(id=self.request.user.id).first()
serializer = self.get_serializer(instance)
return Response(serializer.data)
def destroy(self, request, *args, **kwargs):
'''重写删除数据的方法,在这里可以实现自己的逻辑,比如删除数据时校验是否支持删除'''
res = super().destroy(request, *args, **kwargs)
return res
def update(self, request, *args, **kwargs):
'''重写更新数据的方法,一般这里需要自定义的逻辑可以转移到序列化中,重写序列化器的更新方法'''
res = super().update(request, *args, **kwargs)
return res
ModelViewSet本质上就是
from rest_framework import mixins
from rest_framework.viewsets import GenericViewSet
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass
因此如果你的接口可以按需继承不同的类,来实现需要的请求方式。
例如现在需要一个接口通过用户的token返回用户信息,就可以这么写:
class OwnerUserInfoViewset(mixins.ListModelMixin, GenericViewSet):
'''
list: 获取自己的用户信息
'''
serializer_class = OwnerUserViewsetSerializer
authentication_classes = (JwtAuthentication, )
permission_classes = (IsAuthPermission, )
throttle_classes = (VisitThrottle, )
def get_queryset(self):
'''重写get_queryset,用来返回目标用户的数据,因为在token验证那里已经确定了用户是否存在'''
if self.request.auth:
return User.objects.filter(id=self.request.user.id)
return None
def list(self, request, *args, **kwargs):
'''重写list方法,使接口返回的数据是一个对象而不是数组'''
instance = User.objects.filter(id=self.request.user.id).first()
serializer = self.get_serializer(instance)
return Response(serializer.data)
# 针对过滤类的相关示例:
# 详细的官方文档:https://django-filter.readthedocs.io/en/main/
from django_filters.rest_framework import FilterSet, CharFilter
from django.db.models import Q
from django.db import transaction
class MyFilter(FilterSet):
'''
过滤类
'''
my_type = CharFilter(field_name='my_type', method='return_my_type', label='自定义类型过滤')
filter_date = CharFilter(field_name='filter_date', method='return_filter_date', label='自定义时间段过滤')
class Meta:
model = YourModel
fields = ['status', ] # 原本支持的过滤字段
def return_my_type(self, queryset, name, value):
logging.debug(f'查看传递的值:{value}')
if value == '0':
return queryset.filter(Q(my_type=0) | Q(my_type=2) | Q(my_type=4))
elif value == '1':
return queryset.filter(Q(my_type=1) | Q(my_type=3) | Q(my_type=5))
else:
return queryset.all()
def return_filter_date(self, queryset, name, value):
logging.debug(f'查看传递的值:{value}')
if ',' not in value:
return queryset.all()
else:
date_list = value.split(',')
start_date = datetime.strptime(date_list[0], '%Y-%m-%d')
end_date = datetime.strptime(date_list[1] + ' 23:59:59', '%Y-%m-%d %H:%M:%S')
return queryset.filter(update_time__gt=start_date, update_time__lt=end_date)
# 针对signals的相关示例:
import logging
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete
from django.dispatch import receiver
from django.db import transaction
@transaction.atomic
@receiver(pre_save, sender=YourModel)
def pre_save_object(sender, instance=None, **kwargs):
'''接收模型调用save前的信号
args:
sender Model:发送信号的模型类
instance object:实际被保存的实例
'''
logging.info('即将被保存的对象:{}'.format(instance))
@transaction.atomic
@receiver(post_save, sender=YourModel)
def create_update_object(sender, instance=None, created=False, **kwargs):
'''接收模型调用save后的信号
args:
sender Model:发送信号的模型类
instance object:实际被保存的实例
created bool:是否是创建数据
'''
if created:
logging.info('新增时:{}'.format(instance))
else:
logging.info('修改时:{}'.format(instance))
@transaction.atomic
@receiver(pre_delete, sender=YourModel)
def pre_delete_object(sender, instance=None, **kwargs):
'''接收模型调用delete前的信号
args:
sender Model:发送信号的模型类
instance object:实际被保存的实例
'''
logging.info('即将删除的对象:{}'.format(instance))
@transaction.atomic
@receiver(post_delete, sender=YourModel)
def delete_object(sender, instance=None, **kwargs):
'''接收模型调用delete后的信号
args:
sender Model:发送信号的模型类
instance object:实际被保存的实例
'''
logging.info('已经被删除的对象:{}'.format(instance))
# 针对urls的相关示例:
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import YourViewSet, YourView
router = DefaultRouter()
router.register(r'yourpath', YourViewSet, basename="ViewSetPath")
urlpatterns = [
path('', include(router.urls)),
path('yourpath/', YourView.as_view(), name='ViewPath'),
]