Django模型设可为空ForeignKey,为何管理后台仍要求必填?
Django后台可为空ForeignKey字段必填错误的解决方法
问题场景
我使用Django与Python为ContentProgress模型定制管理后台,该模型的custom_user_content字段是设置了null=True的ForeignKey。但在Django管理后台中,若不为该字段赋值就保存或编辑ContentProgress实例,会触发“Custom user Content此字段为必填项”的错误。相关代码如下:
Admin类代码
class ContentProgressAdmin(admin.ModelAdmin, ExportCsvMixin): list_display = ['user', 'lesson', 'in_percent', 'in_seconds', 'done', 'created_at', 'updated_at'] fields = ['user', 'lesson', 'in_percent', 'in_seconds', 'done', 'real_seconds_listened', 'custom_user_content'] search_fields = ['user__username', 'lesson__name', 'user__email'] list_filter = ['done', ('custom_user_content', RelatedDropdownFilter), ('user__company', RelatedDropdownFilter)] autocomplete_fields = ['user', 'lesson', 'custom_user_content'] actions = ['export_as_csv'] admin.site.register(ContentProgress, ContentProgressAdmin)
Model代码
class ContentProgress(Progress): lesson = models.ForeignKey(MediaFile, on_delete=models.CASCADE) custom_user_content = models.ForeignKey(CustomUserContent, null=True, on_delete=models.CASCADE) def __str__(self): return f'{self.lesson.name} - {int(self.in_percent * 100)}%'
原因分析
Django中null=True仅控制数据库层面是否允许字段存储NULL值,而表单(包括后台管理表单)的必填验证由blank=True参数决定。同时,当字段被加入autocomplete_fields后,后台默认会将该字段的表单控件设为必填状态,即使模型中设置了null=True。
解决方法
方法一:修改模型字段(推荐)
在模型的custom_user_content字段上同时添加blank=True,让表单层面允许空值:
class ContentProgress(Progress): lesson = models.ForeignKey(MediaFile, on_delete=models.CASCADE) # 同时设置null=True和blank=True custom_user_content = models.ForeignKey(CustomUserContent, null=True, blank=True, on_delete=models.CASCADE) def __str__(self): return f'{self.lesson.name} - {int(self.in_percent * 100)}%'
方法二:在Admin类中强制设置字段非必填(无需修改模型)
如果不想修改模型,可以重写Admin类的formfield_for_foreignkey方法,将该字段设为非必填:
class ContentProgressAdmin(admin.ModelAdmin, ExportCsvMixin): list_display = ['user', 'lesson', 'in_percent', 'in_seconds', 'done', 'created_at', 'updated_at'] fields = ['user', 'lesson', 'in_percent', 'in_seconds', 'done', 'real_seconds_listened', 'custom_user_content'] search_fields = ['user__username', 'lesson__name', 'user__email'] list_filter = ['done', ('custom_user_content', RelatedDropdownFilter), ('user__company', RelatedDropdownFilter)] autocomplete_fields = ['user', 'lesson', 'custom_user_content'] actions = ['export_as_csv'] def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == 'custom_user_content': kwargs['required'] = False return super().formfield_for_foreignkey(db_field, request, **kwargs) admin.site.register(ContentProgress, ContentProgressAdmin)
方法三:自定义表单类
创建自定义表单,在表单中设置custom_user_content字段为非必填,然后在Admin类中指定该表单:
from django import forms from .models import ContentProgress class ContentProgressForm(forms.ModelForm): class Meta: model = ContentProgress fields = '__all__' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['custom_user_content'].required = False class ContentProgressAdmin(admin.ModelAdmin, ExportCsvMixin): form = ContentProgressForm list_display = ['user', 'lesson', 'in_percent', 'in_seconds', 'done', 'created_at', 'updated_at'] fields = ['user', 'lesson', 'in_percent', 'in_seconds', 'done', 'real_seconds_listened', 'custom_user_content'] search_fields = ['user__username', 'lesson__name', 'user__email'] list_filter = ['done', ('custom_user_content', RelatedDropdownFilter), ('user__company', RelatedDropdownFilter)] autocomplete_fields = ['user', 'lesson', 'custom_user_content'] actions = ['export_as_csv'] admin.site.register(ContentProgress, ContentProgressAdmin)
内容的提问来源于stack exchange,提问作者Chau Loi
相关产品推荐
相关产品推荐

