首页 > 解决方案 > Django ModelMultipleChoiceField 1:N 初始

问题描述

我有两个模型,机器和设备

class Machine(models.Model):
    pass

class Device(models.Model):
    machine = models.ForeignKey(Machine, related_name='devices')

现在在 Django Admin 中,在机器更改页面中,我希望能够添加 1-N 设备引用,它工作得很好,并且与 ManyToMany 关系开箱即用,这是它在管理员中的外观(期望) 在此处输入图像描述

我正在尝试forms.ModelMultipleChoiceField用于 1:N 选择。我已经想出了保存这种关系的方法,但是输入初始值似乎不起作用。

我如何尝试提供初始值:

class MachineForm(forms.ModelForm):
    class Meta:
        model = Machine
        fields = '__all__'

    devices = forms.ModelMultipleChoiceField(queryset=Device.objects.filter(machine=None).all(), required=False) 

    def __init__(self, *args, **kwargs):
        super(MachineForm, self).__init__(*args, **kwargs)
        if self.instance:
            self.fields['devices'].initial = self.instance.devices.all()

    def save(self, *args, **kwargs):
        instance = super(MachineForm, self).save(commit=False)
        self.fields['devices'].initial.update(machine=None)
        instance.save()
        self.cleaned_data['devices'].update(machine=instance)
        return instance

在调试器中,我可以清楚地知道初始查询集是非空的:

initial <QuerySet [<Device: 10126>, <Device: 10127>]> 但是 django admin 中的字段仍然是空的。

在此处输入图像描述

有谁知道为什么?

编辑:

我已经试过了

self.fields['devices'].initial = self.instance.devices.all().values_list('id', flat=True)

没有运气。

编辑2:

调试日志来自self.fields['devices'].__dict__

[api-857c7fc84d-rh42v api-app] empty_label -> None
[api-857c7fc84d-rh42v api-app] required -> False
[api-857c7fc84d-rh42v api-app] label -> None
[api-857c7fc84d-rh42v api-app] initial -> <QuerySet [<Device: 10126>, <Device: 10127>]>
[api-857c7fc84d-rh42v api-app] show_hidden_initial -> False
[api-857c7fc84d-rh42v api-app] help_text -> 
[api-857c7fc84d-rh42v api-app] disabled -> False
[api-857c7fc84d-rh42v api-app] label_suffix -> None
[api-857c7fc84d-rh42v api-app] localize -> False
[api-857c7fc84d-rh42v api-app] widget -> <django.forms.widgets.SelectMultiple object at 0x7f7d749597f0>
[api-857c7fc84d-rh42v api-app] error_messages -> {'required': 'This field is required.', 'invalid_choice': 'Select a valid choice. %(value)s is not one of the available choices.', 'list': 'Enter a list of values.', 'invalid_pk_value': '"%(pk)s" is not a valid value.'}
[api-857c7fc84d-rh42v api-app] validators -> []
[api-857c7fc84d-rh42v api-app] _queryset -> <QuerySet [<Device: 10036>, <Device: 10135>, <Device: 10062>, <Device: 10069>, <Device: 10101>, <Device: 10139>, <Device: 10022>, <Device: 10149>, <Device: 10103>, <Device: 10146>, <Device: 10020>, <Device: 10040>, <Device: 10075>, <Device: 10123>, <Device: 10059>, <Device: 10001>, <Device: 10142>, <Device: 10148>, <Device: 10097>, <Device: 10118>, '...(remaining elements truncated)...']>

小部件作为字典:

[api-66c9c5d85c-qtnrq api-app] attrs -> {}
[api-66c9c5d85c-qtnrq api-app] choices -> <django.forms.models.ModelChoiceIterator object at 0x7f6c73ad4ac8>
[api-66c9c5d85c-qtnrq api-app] is_required -> False

标签: pythondjango

解决方案


我设法解决了这个问题。在查找Django文档数小时后,我在ModelMultipleChoiceField源代码中找到了这个。

https://docs.djangoproject.com/en/2.1/_modules/django/forms/models/#ModelMultipleChoiceField

def _check_values(self, value):
        """
        Given a list of possible PK values, return a QuerySet of the
        corresponding objects. Raise a ValidationError if a given value is
        invalid (not a valid PK, not in the queryset, etc.) <- NOT IN THE QUERYSET?!
        """
        key = self.to_field_name or 'pk'
        # deduplicate given values to avoid creating many querysets or
        # requiring the database backend deduplicate efficiently.
        try:
            value = frozenset(value)
        except TypeError:
            # list of lists isn't hashable, for example
            raise ValidationError(
                self.error_messages['list'],
                code='list',
            )
        for pk in value:
            try:
                self.queryset.filter(**{key: pk}) # <------
            except (ValueError, TypeError):
                raise ValidationError(
                    self.error_messages['invalid_pk_value'],
                    code='invalid_pk_value',
                    params={'pk': pk},
                )
        qs = self.queryset.filter(**{'%s__in' % key: value})
        pks = {str(getattr(o, key)) for o in qs}
        for val in value:
            if str(val) not in pks:
                raise ValidationError(
                    self.error_messages['invalid_choice'],
                    code='invalid_choice',
                    params={'value': val},
                )
        return qs

问题是我的初始和我的查询集处于分离状态,因此它在某处默默地失败了。

所以改变

devices = forms.ModelMultipleChoiceField(queryset=Device.objects.filter(machine=None).all(), required=False)

devices = forms.ModelMultipleChoiceField(queryset=Device.objects.all(), required=False)

解决了这个问题。

我想知道这是否是一个错误Django?由于查询集中总是会丢失初始值,至少在我的情况下,因为我的查询集是未分配设备的集合。


推荐阅读