首页 文章

Django inlineformset_factory,预定义的modelchoice字段呈现为文本

提问于
浏览
0

我有一个管理处理作业的Django应用程序,我正在尝试创建一个用户友好的处理请求表单 . 基本上,有一个Process模型定义了一个进程,并有一个相关的ProcessInput模型来定义输入参数 . 镜像这两个模型以创建Process的“实例”是ProcessRequest和ProcessRequestInputValue模型 .

这里的要求是对于新的ProcessRequest,必须有一整套与所有ProcessInput匹配的ProcessRequestInputValues . 目前,我正在使用inlineformset_factory创建一个表单来提交流程请求,允许同时输入所有输入值 . 我还提供了初始数据来预先填充输入选项 . 这是有效的,但用户可以更改输入值类别,因为它是ModelChoiceField . 我想“修复”这个值并将其显示为文本,本质上是一个只读的文本小部件 .

这是相关的代码 . 一,型号:

class Process(models.Model):
    process_name = models.CharField(unique=True)

class ProcessInput(models.Model):
    process = models.ForeignKey(Process)
    input_name = models.CharField()
    default_value = models.CharField(null=True, blank=True, max_length=1024)

    class Meta:
        unique_together = (('process', 'input_name'),)

class ProcessRequest(models.Model):
    process = models.ForeignKey(Process)
    request_user = models.ForeignKey(User, editable=False)

class ProcessRequestInputValue(models.Model):
    process_request = models.ForeignKey(ProcessRequest)
    process_input = models.ForeignKey(ProcessInput)
    value = models.CharField(null=False, blank=False, max_length=1024)

    class Meta:
        unique_together = (('process_request', 'process_input'),)

表格很简单:

class ProcessRequestForm(ModelForm):
    class Meta:
        model = ProcessRequest
        exclude = ('process', 'request_user')

class ProcessRequestInputValueForm(ModelForm):
    class Meta:
        model = ProcessRequestInputValue
        exclude = ('process_request',)

最后是formset视图中的代码:

PRInputValueFormSet = inlineformset_factory(
    ProcessRequest,
    ProcessRequestInputValue,
    form=ProcessRequestInputValueForm,
    extra=process.processinput_set.count(),
    can_delete=False,
)

form = ProcessRequestForm(instance=process_request)

initial_data = list()

for process_input in process.processinput_set.all():
    initial_data.append({'process_input': process_input})

formset = PRInputValueFormSet(
    instance=process_request,
    initial=initial_data)

这是有效的,它保留了有关表单错误的所有填写信息 . 但是,就像我上面所说的那样,表单将进程输入显示为下拉列表,因为它是一个ModelChoiceField .

例如,假设我们有一个名为“Add Numbers”的进程,并有2个输入“NumberA”和“NumberB” . 这是使用formset从ProcessRequest表单中获取的屏幕:

enter image description here

我想基本上将选项显示为值的标签 . 我尝试了几种方法,但没有找到任何效果很好的方法 . 有任何想法吗?

3 回答

  • 0

    我为inline_formsets做的方法是简单地排除你不希望在表单中显示的字段,这里是process_input以及process_request

    class ProcessRequestInputValueForm(ModelForm):
        class Meta:
            model = ProcessRequestInputValue
            exclude = ('process_request','process_input')
    

    现在在底层模型(ProcessRequestInputValue)上设置一个属性值来回答你想要的东西,在你的情况下我正在猜测ProcessInput上的Process对象:

    @property
    def my_process(self):
        return self.process_input.process
    

    然后在你的模板中,直接从表单循环内部调用该一元方法:

    <span>Process Input: {{form.instance.my_process}}</span>
    

    它将在Process对象上调用unicode方法

    这避免了必须制作任何复杂的ReadOnlyWidgets等 .

  • 1

    你试过禁用吗?

    some_field = forms.ChoiceField(choices=my_choices, 
                                   widget=forms.Select(attrs={'disabled':'disabled'}))
    
  • 0

    嗯,这似乎是一个黑客攻击,但到目前为止我找到的唯一解决方案是创建一个虚拟表单字段来存储标签值 . 下面是我到目前为止的代码 .

    我对此并不十分满意,我很乐意看到任何其他解决方案 .

    forms.py:

    class ProcessRequestForm(ModelForm):
        class Meta:
            model = ProcessRequest
            exclude = ('process', 'request_user')
    
    
    class ProcessRequestInputValueForm(ModelForm):
        value_label = forms.CharField(widget=forms.HiddenInput())
        process_input = forms.ModelChoiceField(
            queryset=ProcessInput.objects.all(),
            widget=forms.HiddenInput())
    
        class Meta:
            model = ProcessRequestInputValue
            exclude = ('process_request',)
    

    views.py:

    def create_process_request(request, process_id):
        process = get_object_or_404(Process, pk=process_id)
        process_request = ProcessRequest(process=process, request_user=request.user)
    
        PRInputValueFormSet = inlineformset_factory(
            ProcessRequest,
            ProcessRequestInputValue,
            form=ProcessRequestInputValueForm,
            extra=process.processinput_set.count(),
            can_delete=False,
        )
    
        if request.method == 'POST':
            # validate the form / formset and save
        else:
            form = ProcessRequestForm(instance=process_request)
            initial_data = list()
    
            for process_input in process.processinput_set.all():
                # note 'value_label' is used for the 'value' field's label
                initial_data.append(
                    {
                        'process_input': process_input,
                        'value_label': process_input.input_name
                    })
    
            formset = PRInputValueFormSet(instance=process_request, initial=initial_data)
    
        return render_to_response(
            'create_process_request.html',
            {
                'process': process,
                'form': form,
                'formset': formset,
            },
            context_instance=RequestContext(request)
        )
    

    template:

    Process: {{ process.process_name }}
    <form action="{% url 'create_process_request' process.id %}" method="post"> {% csrf_token %} {{ form.non_field_errors }} {{ formset.management_form }} <Inputs:
    {% for input_form in formset %} {% for hidden_field in input_form.hidden_fields %} {% if hidden_field.name == 'process_input' %} {{ hidden_field.as_hidden }} {% elif hidden_field.name == 'value_label' %} {# Needed if form submission has errors, else our custom label goes away #} {# Could be used to "hack" our custom label on POST %} {{ hidden_field.as_hidden }} {# Our custom dynamic label #} {{ hidden_field.value }} {% endif %} {% endfor %} {{ input_form.value }} {% if input_form.value.errors %} {{ input_form.value.errors|striptags }} {% endif %} <hr/> {% endfor %} <button type="submit">Save</button> </div> </div> </form>

    还有一个更实用的形式:

    enter image description here

相关问题