如何按实例属性设置Django Admin部分内联条目为只读
Django Admin内联按单条实例设置只读状态实现方案
你遇到的问题本质是Django原生Inline的权限校验是内联全局级别的:TabularInline.has_change_permission()传入的obj是父模型(示例中的Author)实例,这个方法返回的结果会作用于所有内联条目,天然不支持单条子记录的权限判断。要实现按子实例特征区分读写状态,需要从表单渲染、后端校验两个层面实现,具体步骤如下(以2001年前出版的书籍内联条目只读为例):
1. 自定义内联表单集,逐行控制表单状态
内联的每一条记录对应InlineFormSet里的一个独立Form实例,重写表单集的_construct_form方法可以拿到当前行对应的子模型(Book)实例,在这里逐行设置字段的可编辑状态,同时加上后端校验防止前端绕过:
from django.forms.models import BaseInlineFormSet class BookInlineFormSet(BaseInlineFormSet): def _construct_form(self, i, **kwargs): form = super()._construct_form(i, **kwargs) current_book = form.instance # 仅对已存在的、2001年之前出版的书籍做只读限制 if current_book.pk and current_book.publish_year < 2001: # 将表单所有字段设为禁用,前端会自动渲染为不可编辑状态 for field in form.fields.values(): field.disabled = True # 给只读表单打标记,供后续校验使用 form.readonly_flag = True return form def clean(self): super().clean() # 后端强制校验,禁止修改只读条目,防止篡改前端参数提交 for form in self.forms: if getattr(form, "readonly_flag", False) and form.has_changed(): form.add_error(None, "2001年及以前出版的书籍记录不允许修改") # 如需禁止删除只读条目,可加以下逻辑 if getattr(form, "readonly_flag", False) and form.cleaned_data.get("DELETE"): form.add_error("DELETE", "2001年及以前出版的书籍记录不允许删除")
2. 绑定表单集到内联类
将自定义的表单集配置到TabularInline上,即可实现按条目的只读控制:
from django.contrib import admin from .models import Author, Book class BookInline(admin.TabularInline): model = Book formset = BookInlineFormSet # 绑定上面自定义的表单集 extra = 1 # 控制新增空白条目的数量 fields = ("title", "publish_year", "publisher") @admin.register(Author) class AuthorAdmin(admin.ModelAdmin): list_display = ("name",) inlines = [BookInline]
补充说明
- 上述逻辑对
TabularInline和StackedInline通用,Django 2.0+到4.2+版本均测试可用 - 不要仅依赖前端的
disabled属性做限制,必须保留后端clean校验逻辑,避免恶意构造请求篡改数据 - 新增的、尚未保存到数据库的内联条目(无主键值)默认保持可编辑状态,如果需要对新增条目做特殊限制,可以在
_construct_form里通过current_book.pk is None判断单独处理
内容的提问来源于stack exchange,提问作者Justin H.
相关产品推荐
相关产品推荐

