Django2.2升级至4.2后AccessPolicy多对多字段报错求助
问题
将遗留项目从Django 2.2升级到Django 4.2后,仅AccessPolicy模型(包含ManyToMany字段)功能异常,报错信息:
'AccessPolicy' instance needs to have a primary key value before this relationship can be used.
相关代码片段:
模型代码(models.py)
class AccessPolicy(TimeStampedModel): name = models.CharField(max_length=255, null=False, blank=False) external_id = models.CharField(max_length=255, unique=True, null=True) version_hash = models.CharField(max_length=255, null=False, blank=False) read_only = models.BooleanField(default=False, help_text=('Some text')) users = models.ManyToManyField(User, blank=True) class Meta: verbose_name = 'Access Policy' verbose_name_plural = 'Access Policies' def __str__(self): return f'{self._meta.object_name} Object ({self.pk})'
表单代码(forms.py)
class AccessPolicyForm(forms.ModelForm): external_id = forms.CharField(disabled=True, required=False) rules = forms.CharField(widget=QueryBuilderWidget) class Meta: model = AccessPolicy fields = 'name', 'external_id', 'read_only', 'rules', 'users', def clean_external_id(self): external_id = self.cleaned_data.get('external_id') if not external_id: return md5(f"{str(self.instance.created)} {self.cleaned_data.get('name')} {self.data.get('rules')}") return external_id def clean_rules(self) -> CleanedRules: raw_rules: RawRules = json.loads(self.cleaned_data.get('rules')) conditional_serializer = RulesSerializer(data=raw_rules) if not conditional_serializer.is_valid(): raise ValidationError(conditional_serializer.errors) return conditional_serializer.validated_data def save(self, commit=True): self.instance.version_hash = hash_dict(self.cleaned_data.get('rules')) return super().save(commit) def _save_m2m(self): self.instance.rules.all().delete() self._save_condition() self._save_users() def _save_users(self): self.instance.users.clear() self.instance.users.add(*self.cleaned_data.get('users')) def _save_condition(self): root_condition = RulesSerializer().create(self.cleaned_data.get('rules')) root_condition.access_policy = self.instance root_condition.save()
Admin代码(admin.py)
class AccessPolicyAdmin(admin.ModelAdmin): form = AccessPolicyForm list_display = 'name', search_fields = 'name', formfield_overrides = { models.ManyToManyField: { 'widget': FilteredSelectMultiple(attrs={'size': 20}, verbose_name='linked users', is_stacked=False) }, } def get_form(self, request, obj: AccessPolicy = None, change=False, **kwargs): form = super().get_form(request, obj, change, **kwargs) data = self._get_form_data(obj or AccessPolicy()) form.declared_fields[AUTHORIZATION_CONDITION_CHILDREN].initial = json.dumps(data) return form def _get_form_data(self, access_policy: AccessPolicy): return pipe(access_policy, AccessRulesGenerator.get_root_rule, RulesSerializer, extract_data)
已知需先保存模型实例再操作ManyToMany字段,但修改save()和_save_m2m()后问题仍存在,需明确修改位置。
解决方案
问题根源是Django 4.2对模型实例保存与关联关系绑定的校验更严格,且自定义的_save_m2m和save方法逻辑冲突,具体修改如下:
1. 重构save方法,确保新建实例先获取主键
创建新AccessPolicy时,必须先将实例保存到数据库拿到主键,再处理关联关系:
def save(self, commit=True): self.instance.version_hash = hash_dict(self.cleaned_data.get('rules')) # 新建对象场景:先保存实例获取主键,再处理关联 if not self.instance.pk and commit: # 先保存实例(不触发m2m) super().save(commit=False) self.instance.save() # 手动触发关联关系保存 self._save_m2m() return self.instance # 编辑对象场景:正常执行父类逻辑 return super().save(commit)
2. 修改_save_m2m方法,移除重复的ManyToMany处理
Django的ModelForm会自动处理users字段的保存,自定义的_save_users方法重复操作会导致冲突,同时确保_save_condition执行时实例已有主键:
def _save_m2m(self): # 先清理并重新保存rules关联 self.instance.rules.all().delete() self._save_condition() # 移除自定义的users处理,交给Django自动完成 # self._save_users() # 删除此行
3. 给_save_condition添加主键校验(可选)
增加容错性,避免因实例未保存导致的报错:
def _save_condition(self): if not self.instance.pk: raise ValueError("AccessPolicy实例必须先保存才能创建规则关联") root_condition = RulesSerializer().create(self.cleaned_data.get('rules')) root_condition.access_policy = self.instance root_condition.save()
4. Admin代码无需修改
get_form中创建的未保存实例仅用于初始化表单数据,不会触发关联操作,保持原代码即可。
内容的提问来源于stack exchange,提问作者zacniewski
相关产品推荐
相关产品推荐

