查看: 144|回复: 3

Django模型字段类型与选项:ForeignKey on_delete

[复制链接]
发表于 4 小时前 | 显示全部楼层 |阅读模式
在Django中,模型是数据描述的准确载体,每个模型映射一张数据库表,模型类继承自 django.db.models.Model,其属性对应数据库字段。Django 通过这些字段类自动生成数据库访问 API,并指定数据库数据类型,提供表单渲染和基础验证。

一、常用字段类型
AutoField:自增整数字段,根据可用 ID 自动递增。BigAutoField:64位自增整数,范围 1 到 9223372036854775807。BooleanField:布尔字段,默认表单部件为 CheckboxInput(null=True 时为 NullBooleanSelect),默认值为 None。CharField:字符串字段,适用于中小型文本,需指定 max_length(必填)和可选的 db_collation。DateField:日期字段,对应 datetime.date 实例,支持 auto_now(每次保存时更新为当前时间)和 auto_now_add(创建时设为当前时间)参数。

二、字段选项
• null:设为 True 时,空字段在数据库中存为 NULL,默认 False。
• blank:设为 True 时,字段允许为空,默认 False。
• choices:二元组列表,用于提供选项,例如 CHOICES = [('FR', 'Freshman'), ('SO', 'Sophomore')]。
• default:字段默认值。
• help_text:表单控件显示的帮助文本。
• primary_key:设为 True 时该字段成为主键。
• unique:值为 True 时字段在整个表中唯一。
除 ForeignKey、ManyToManyField、OneToOneField 外,任何字段均可接受 verbose_name 参数,未指定时自动将属性名下划线替换为空格。

三、关联关系
Django 支持三种数据库关联:多对一(ForeignKey)、多对多(ManyToManyField)、一对一(OneToOneField)。
Foreignkey 需要两个位置参数:关联的模型类(或 'self' 表示自关联)和 on_delete 参数。若关联尚未定义的模型,可使用模型名称字符串。

示例:Car 与 Manufacturer 的多对一关系
  1. from django.db import models
  2. class Manufacturer(models.Model):
  3.     # ...
  4.     pass
  5. class Car(models.Model):
  6.     manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
  7.     # ...
复制代码

四、on_delete 参数详解
当被引用的对象被删除时,on_delete 决定 ForeignKey 的行为,可选值在 django.db.models 中:

• CASCADE:级联删除,被引用的对象删除时,同时删除包含 ForeignKey 的对象。
• PROTECT:通过引发 ProtectedError(IntegrityError 子类)阻止删除被引用的对象。
• RESTRICT(Django 3.1+):通过引发 RestrictedError 防止删除,但若被引用对象在同一操作中被 CASCADE 关联删除则允许。
• SET_NULL:将 ForeignKey 设为 NULL,需同时指定 null=True。
• SET_DEFAULT:设为默认值,需为 ForeignKey 设置 default。
• SET():设为指定值或可调用对象的返回值。
• DO_NOTHING:不采取措施,由数据库自身约束处理。

示例:
  1. manufacturer = models.ForeignKey(Manufacturer, on_delete=models.SET_NULL, null=True)
  2. manufacturer = models.ForeignKey(Manufacturer, on_delete=models.SET_DEFAULT, default=1)
  3. def get_manufacturer():
  4.     return 1
  5. manufacturer = models.ForeignKey(Manufacturer, on_delete=models.SET(get_manufacturer))
复制代码

五、地区自关联实战
以地区表自关联为例,模型中使用自引用 ForeignKey:
  1. class District(models.Model):
  2.     parent = models.ForeignKey(
  3.         "self",
  4.         null=True,
  5.         blank=True,
  6.         on_delete=models.CASCADE,
  7.         related_name="child",
  8.         verbose_name="父级地区",
  9.     )
  10.     name = models.CharField(max_length=128, help_text="地区名称", verbose_name="地区名称")
  11.     level = models.IntegerField(default=0, help_text="地区级别", verbose_name="地区级别")
  12.     full_name = models.CharField(max_length=256, help_text="地区全名", verbose_name="地区全名")
  13.     def __str__(self):
  14.         return self.full_name
  15.     class Meta:
  16.         managed = True
  17.         verbose_name = "地区信息"
  18.         verbose_name_plural = verbose_name
复制代码

配合 DRF 视图实现父级下二级地区查询,并接入缓存(Redis 等):
  1. class CityCountyView(APIView):
  2.     authentication_classes = [BasicAuthentication]
  3.     permission_classes = (AllowAny,)
  4.     def get(self, request, pk):
  5.         # 缓存键
  6.         cache_key = 'sub_data_%s' % pk
  7.         sub_data = cache.get(cache_key)
  8.         if not sub_data:
  9.             try:
  10.                 parent_obj = District.objects.get(id=pk)
  11.                 sub_objs = parent_obj.child.all()
  12.                 subs = []
  13.                 for sub in sub_objs:
  14.                     subs.append({'id': sub.id, 'name': sub.name})
  15.                 sub_data = {'id': pk, 'name': parent_obj.name, 'subs': subs}
  16.             except Exception as e:
  17.                 print(e)
  18.                 return Response({'code': 400, 'errmsg': '查询失败'})
  19.             # 缓存一天
  20.             cache.set(cache_key, sub_data, 3600 * 24)
  21.         return Response({'code': 0, 'errmsg': 'ok', 'sub_data': sub_data})
复制代码

六、动态生成模型类
有时需要根据不同参数动态创建模型。以下两种方式均可实现:

方式一:函数返回内部类
  1. def getTaskDetailModel(table_name):
  2.     class MyClass(models.Model):
  3.         task = models.ForeignKey(Task, on_delete=models.CASCADE, related_name='details', verbose_name='任务')
  4.         user_id = models.IntegerField(verbose_name='用户ID')
  5.         created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
  6.         is_complet = models.BooleanField(default=False, verbose_name='是否完成')
  7.         class Meta:
  8.             db_table = table_name
  9.     return MyClass
复制代码

方式二:使用 type() 动态创建
  1. def create_task_detail_model(password_code):
  2.     classname = f'TaskDetail_{password_code}'
  3.     class Meta:
  4.         db_table = classname
  5.     return type(classname, (models.Model,), {
  6.         'task': models.ForeignKey(Task, on_delete=models.CASCADE, related_name='details', verbose_name='任务'),
  7.         'user_id': models.IntegerField(verbose_name='用户ID'),
  8.         'created_time': models.DateTimeField(auto_now_add=True, verbose_name='创建时间'),
  9.         'is_complet': models.BooleanField(default=False, verbose_name='是否完成'),
  10.         '__str__': lambda self: f"{self.task.password_code} - {self.user_id}",
  11.         'Meta': Meta,
  12.         '__module__': __name__,
  13.     })
复制代码

通过以上内容,可以系统地掌握 Django 模型字段类型的选择、字段选项配置、ForeignKey 关联及 on_delete 参数的应用,并学会自关联和动态模型生成的实战技巧。
回复

使用道具 举报

发表于 3 小时前 | 显示全部楼层

Re: Django模型字段类型与选项:ForeignKey on_delete

您好!感谢分享这么详细的技术内容,把 Django 模型字段和 ForeignKey 的 `on_delete` 参数讲得很透彻。特别是对 CASCADE、PROTECT、RESTRICT、SET_NULL 等选项的对比,以及自关联地区的实战示例,非常实用。之前我对 RESTRICT 和 PROTECT 的区别理解得不够深,看了您的解释清楚多了。期待更多这样的干货!
回复 支持 反对

使用道具 举报

发表于 3 小时前 | 显示全部楼层

Re: Django模型字段类型与选项:ForeignKey on_delete

感谢楼主分享关于Django模型字段和ForeignKey on_delete参数的详细说明,特别是地区自关联的实战示例,很有参考价值。对于on_delete的各个选项,你讲解得很清晰,尤其是RESTRICT在3.1+版本中的行为变化,这个细节容易被忽略。请问在实际项目中,你更倾向于使用CASCADE还是SET_NULL?另外,自关联中使用related_name='child'来反向查询子级地区,配合缓存实现二级查询,这个设计很实用。
回复 支持 反对

使用道具 举报

发表于 3 小时前 | 显示全部楼层

Re: Django模型字段类型与选项:ForeignKey on_delete

楼主的整理很详尽,对Django新手来说是很清晰的入门指引。我特别想补充一点关于 `on_delete` 选择时的实际考虑: - **CASCADE** 适合强关联数据,比如订单明细和订单,订单删除明细自然该删; - **PROTECT** 和 **RESTRICT** 建议在业务逻辑中要保护好关键数据时使用,比如省市区数据被引用时不让误删; - **SET_NULL** 和 **SET_DEFAULT** 适合弱关联场景,比如文章的作者被删除后,文章保留但作者变空或变成默认值。 地区自关联的例子很实用,配合DRF和缓存做树形结构接口是经典做法。另外提一个小建议:自关联里 `related_name='child'` 后,用 `parent.child.all()` 就能拿子级,查询起来非常方便,值得新朋友留意这个命名习惯。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-7-17 20:00 , Processed in 0.031892 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部