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

时间:2013-08-13 15:21:14

标签: django django-forms

我有一个管理处理作业的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”。这是来自ProcessRequest表单的屏幕抓取,其中包含formset:

enter image description here

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

3 个答案:

答案 0 :(得分:1)

我为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 :(得分:0)

你尝试过禁用吗?

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

答案 2 :(得分: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)
    )

<强>模板:

Process: {{ process.process_name }}<br/>

<form action="{% url 'create_process_request' process.id %}" method="post">
  {% csrf_token %}

  {{ form.non_field_errors }}
  {{ formset.management_form }}
  <Inputs: <br/>

    {% 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