ValueError:'RightPolicy'示例需要有一个主键值才能使用此关系(Django从2.2升级到4.2后)

ndh0cuux  于 2023-10-21  发布在  Go
关注(0)|答案(1)|浏览(88)

我正在将遗留项目从Django 2.2升级到Django 4.2。除了使用ManyToMany字段的模型之外,其他一切都正常。
型号:

# 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)

这段代码在Django 2.2下运行良好,没有错误,但在Django 4.2下会出现错误:

'AccessPolicy' instance needs to have a primary key value before this relationship can be used.

我知道在添加任何m2m字段之前需要保存模型的示例,并且必须使用.add()方法添加m2m字段。我尝试了对save()_save_m2m()方法进行一些修改,但没有成功。我应该在哪里做?

bgibtngc

bgibtngc1#

我决定创建AccessPolicy的临时示例,那么缺少pk的问题不再存在。

# admin.py

company_name = 'Temporary policy to delete, because we only need its pk for saving users.'
temporary_policy = AccessPolicy.objects.create(name=company_name)
data = self._get_form_data(obj or temporary_policy)
all_temp_policies = AccessPolicy.objects.filter(name=company_name)

# More than one policy with given name is generated, so I'm deleting them in a loop
for tp in all_temp_policies:
    tp.delete()

在我看来,这个解决方案不是最好的,因为我生成了几个同名的策略,然后我必须删除它们。但是主键的问题消失了。

相关问题