Django自定义Widget在选择1以上时失败(输入整数)



我想要一个多状态点击框。所以我花了一些空闲时间在一个很好的Django解决方案上,它就是:

class MultiStateChoiceInput(forms.widgets.ChoiceInput):
    input_type = 'radio'
    def __init__(self, name, value, attrs, choice, index, label_id):
        # Override to use the label_id which is upped with 1
        if 'id' in attrs:
            self.label_id = attrs['id']+ "_%d" % label_id
        super(MultiStateChoiceInput, self).__init__(name, value, attrs, choice, index)
        self.value = force_text(self.value)

    @property
    def id_for_label(self):
        return self.label_id
    def render(self, name=None, value=None, attrs=None, choices=()):
        if self.id_for_label:           
            label_for = format_html(' for="{}"', self.id_for_label)
        else:
            label_for = ''
        attrs = dict(self.attrs, **attrs) if attrs else self.attrs
        return format_html(
            '{} <label{}>{}</label>',  self.tag(attrs), label_for, self.choice_label
        )

class MultiStateRenderer(forms.widgets.ChoiceFieldRenderer):
    choice_input_class = MultiStateChoiceInput
    outer_html = '<span class="cyclestate">{content}</span>'
    inner_html = '{choice_value}{sub_widgets}'
    def render(self):
        """
        Outputs a <ul> for this set of choice fields.
        If an id was given to the field, it is applied to the <ul> (each
        item in the list will get an id of `$id_$i`).
        # upgraded with the label_id
        """
        id_ = self.attrs.get('id')
        output = []
        for i, choice in enumerate(self.choices):
            choice_value, choice_label = choice
            if isinstance(choice_label, (tuple, list)):
                attrs_plus = self.attrs.copy()
                if id_:
                    attrs_plus['id'] += '_{}'.format(i)
                sub_ul_renderer = self.__class__(
                    name=self.name,
                    value=self.value,
                    attrs=attrs_plus,
                    choices=choice_label,
                    label_id = (i+1) % (len(self.choices))  # label_id is next one
                )
                sub_ul_renderer.choice_input_class = self.choice_input_class
                output.append(format_html(self.inner_html, choice_value=choice_value,
                                          sub_widgets=sub_ul_renderer.render()))
            else:
                w = self.choice_input_class(self.name, self.value,
                                            self.attrs.copy(), choice, i,  label_id = (i+1) % (len(self.choices)))  # label_id is next one
                output.append(format_html(self.inner_html,
                                          choice_value=force_text(w), sub_widgets=''))
        return format_html(self.outer_html,
                           id_attr=format_html(' id="{}"', id_) if id_ else '',
                           content=mark_safe('n'.join(output)))

class MultiStateSelectWidget(forms.widgets.RendererMixin, forms.widgets.Select):
    ''' This widget enables multistate clickable toggles 
    Requires some css as well  (see .cyclestate)
    '''
    renderer = MultiStateRenderer

这创建了一个表单,如下所述https://stackoverflow.com/a/33455783/3849359点击切换下一个状态,直到达到和,然后在开始时继续。

在我看来,该表格被称为:

SomeFormSet= modelformset_factory(myModel, form=myModelForm, extra=0)    
SomeFormSet.form = staticmethod(curry(myModelForm, somevariable=somevariable))
formset = SomeFormSet(request.POST or None, queryset=somequeryset)

forms.py是:

class myModelForm(forms.ModelForm):
    CHOICES = (
       (0, _('a')),
       (1, _('b')),
       (2, _('c')),
       (3, _('d')),
       )

    field = forms.IntegerField(widget=MultiStateSelectWidget(choices=CHOICES))

    class Meta:
        model = MyModal
        fields = ('field',)
        widgets = {'id': forms.HiddenInput(),
                 }
    def __init__(self, *args, **kwargs):
        self.variable= kwargs.pop('variable')
        super(myModelForm, self).__init__(*args, **kwargs)
        for field in myModelForm.fields:
            if self.instance.pk:
                if not getattr(self.instance, field):
                    self.initial[field]= 0
                else:
                    self.initial[field]= 1
                if anothercondition:
                    self.initial[field] = 3
                else:
                    self.initial[field] = 2

我觉得效果很好。点击并保存确实很好(我有一个自定义的保存方法)。除非表单字段的值为2或3,否则它会突然失败,并显示错误消息:"字段"应为整数。

如果有人能帮忙那就太好了,因为我没有主意了!

编辑:以防万一。。。我已经检查了POST,它很棒。唯一的问题是,如果值是2,Django在解析POST时会完全丢失该值(它变成None),我不知道为什么。

第二版:Django ModelForm似乎也做模型验证。这个模型是一个BooleanField,这就是它失败的原因。如果有人知道一个好的方法来覆盖它,那就太好了!

@edgarzamora你的评论不是答案,但很接近!

我从Form类Meta中删除了"字段",所以它看起来像:

class Meta:
    model = MyModal
    fields = ('',)
    widgets = {'id': forms.HiddenInput(),
             }

现在一切都正常了,因为我有自己的自定义保存方法。。。太愚蠢了,花了我几个小时!谢谢

最新更新