【问题标题】:How do I programmatically set fields in ModelForm in Django?如何以编程方式在 Django 的 ModelForm 中设置字段?
【发布时间】:2012-05-18 01:47:05
【问题描述】:

this question,我想将我的表单从常规Form 转换为ModelForm,这样我就可以利用instance 中的instance 参数ModelForm

这是我当前的表单代码:

class OrderDetailForm(forms.Form):
    def __init__(
        self,
        user,
        can_edit_work_type=None,
        can_edit_vendor=None,
        can_edit_note=None,
        *args,
        **kwargs
    ):
        super(OrderDetailForm, self).__init__(*args, **kwargs)

        if can_edit_work_type:
            self.fields['work_type'] = forms.ChoiceField(choices=Order.WORK_TYPE_CHOICES)
        if can_edit_vendor:
            self.fields['vendor'] = forms.ModelChoiceField(
                queryset=Vendor.objects.all(),
                empty_label="Choose a vendor",
            )
        if can_edit_note:
            self.fields['note'] = forms.CharField(widget=forms.Textarea)

    def clean(self):
        super(OrderDetailForm, self).clean()

        if 'note' in self.cleaned_data:
            if len(self.cleaned_data['note']) < 50:
                self._errors['note'] = self.error_class([u"Please enter a longer note."])

                del self.cleaned_data['note']

        return self.cleaned_data

如您所见,我有一些if 语句可以确定字段是否在表单中显示(从逻辑上讲,这意味着某些用户只能编辑字段的某些部分)。

我将如何在ModelForm 中做到这一点?我理解fields 是一个元组,所以不能像我在Form 中那样附加它。所以我想做类似的事情

class OrderDetailForm(forms.ModelForm):
    class Meta:
        model = Order
        # fields = ('work_type', 'vendor', 'note') I can't do that since I need to be able to control it. See below.

        # Can I control widgets even if that field doesn't exist?
        widgets = {
            'note': forms.Textarea(),
        }

    def __init__(
        self,
        user,
        can_edit_work_type=None,
        can_edit_vendor=None,
        can_edit_note=None,
        *args,
        **kwargs
    ):
        super(OrderDetailForm, self).__init__(*args, **kwargs)

        fields = []

        if can_edit_work_type:
            fields.append('work_type')
        if can_edit_vendor:
            fields.append('vendor')
        if can_edit_note:
            fields.append('note')

        self.Meta.fields = tuple(fields) # Does this work?

    def clean(self):
        super(OrderDetailForm, self).clean()

        if 'note' in self.cleaned_data:
            if len(self.cleaned_data['note']) < 50:
                self._errors['note'] = self.error_class([u"Please enter a longer note."])

                del self.cleaned_data['note']

        return self.cleaned_data

这可能吗? ModelForm中的字段如何控制?

【问题讨论】:

    标签: python django-forms


    【解决方案1】:

    另一种可能的方式是在视图中生成一个内联表单类,以根据请求排除字段,例如为Order模型定义一个普通模型表单,称为OrderDetailForm:

    class OrderDetailForm(forms.ModelForm):
        class Meta:
            model = Order
            fields = ('work_type', 'vendor', 'note') 
            widgets = {
                'note': forms.Textarea(),
            }
    

    在视图中,例如编辑订单,根据OrderDetailForm创建自定义表单:

    def edit(request, order_id):
        order = Order.objects.get(pk=order_id)
        can_edit_work_type = bool(request.REQUEST.get('can_edit_work_type', False))
        can_edit_vender = bool(request.REQUEST.get('can_edit_vender', False))
        can_edit_note = bool(request.REQUEST.get('can_edit_note', False))
    
        exclude_fields = []
    
        if not can_edit_work_type:
            exclude_fields.append('work_type')
    
        if not can_edit_vender:
            exclude_fields.append('vender')
    
        if not can_edit_note:
            exclude_fields.append('note')
    
        class CustomizedOrderForm(OrderDetailForm):
            class Meta:
                model = Order
                exclude = tuple(exclude_fields)
    
        if request.method == 'POST':
            form = CustomizedOrderForm(instance=order, data=request.POST)
            if form.is_valid():
                form.save()
        else:
            form = CustomizedOrderForm(instance=order)
        return render(request, 'order_form.html', {'form': form})
    

    【讨论】:

    • 我喜欢尽可能地保留我的请求处理程序并将逻辑放在formsmodels 和其他地方而不是处理程序本身中。
    • 这不会在有效帖子中返回 HttpResponse。
    • 这不会向表单添加字段。 ...还是这样做...嗯...这有点'很棒,但是处理程序中的嵌套类完全错误;)
    • 嗯,我不确定通过GET 控制字段,但我确实喜欢逐个排除字段的想法。
    【解决方案2】:

    ModelForm api 与常规的Form 非常相似。优点是除了默认小部件、实例 kwarg 和保存方法等便利之外,您现在还可以获得 model validation

    fields attr 仍然是 dict 类似。您可以看到由元类 here 构建的字段。然后,去throughinheritance 并在BaseModelForm.__init__ 中调用super(),我们到达declared fields 中的deepcopyoriginallySortedDict。这对FormModelForm 很常见,它们都是BaseForm 的子类。

    将字段放在exclude 中,并按照您在原始__init__ 中的方式添加它们。

    以同样的方式清洁它们。

    然后,您可以重写save 方法:您可以调用super() 来取回对象并根据需要处理cleaned_data 中的数据。

    class OrderDetailForm(forms.ModelForm):
        # regular fields, not based on bools
        # ...
    
        class Meta:
            model = Order
            exclude = ('work_type', 'vendor', 'note')
            # or fields = (...other fields )
    
    
        def __init__(
            self,
            user,
            can_edit_work_type=None,
            can_edit_vendor=None,
            can_edit_note=None,
            *args,
            **kwargs,
        ):
            super(OrderDetailForm, self).__init__(*args, **kwargs)
    
            if can_edit_work_type:
                self.fields['work_type'] = forms.ChoiceField(
                                           choices=Order.WORK_TYPE_CHOICES)
            if can_edit_vendor:
                self.fields['vendor'] = forms.ModelChoiceField(
                    queryset=Vendor.objects.all(),
                    empty_label="Choose a vendor",
                )
            if can_edit_note:
                self.fields['note'] = forms.CharField(widget=forms.Textarea)
    
        def clean(self):
            # I never call super() in clean .. do I? .. hmmm
            # maybe I should or is sth magic going on?
            # alternately,
            # data = self.cleaned_data
            # let's call super though
            data = super(OrderDetailForm, self).clean()
    
            if 'note' in data:
                if len(data['note']) < 50:
                    # I raise a validation error so .is_valid() comes back False
                    # form.errors happens magically ...
                    raise forms.ValidationError("Not long enough ...")
    
            return data
    
        def save(self, *args, **kwargs):
            data = self.cleaned_data
            # maybe do some stuff here
            # ...
    
            # commit=True or commit=False could be important
            order = super(OrderDetailForm, self).save(*args, **kwargs)
    
            if 'note' in data:
                order.note = data['note']
    
            # ... do other stuff
    
            # probably ...
            order.save()
    
            # respect how model forms work.
            return order
    

    【讨论】:

    • 那么如何在__init__ 下的fields 中添加一个模型字段?我可以做类似self.fields.append('note') 的事情吗?看起来fields 仍然是一个元组,而不是字典。
    • 在常规的Form 中,您不是附加到字段 attr 而是为其键分配值。
    猜你喜欢
    • 1970-01-01
    • 2014-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-03
    相关资源
    最近更新 更多