Separate form fields to "parts"; render part with loop, render part with specific design, render part with loop again

huangapple go评论61阅读模式
英文:

Separate form fields to "parts"; render part with loop, render part with specific design, render part with loop again

问题

# forms.py
class HappyIndexForm(forms.Form):
    pizza_eaten = forms.IntegerField(label="Pizzas eaten")
    # 5 more fields
    minutes_outside = forms.IntegerField(label="Minutes outside")

class TherapyNeededForm(HappyIndexForm):
    had_therapy_before = forms.BooleanField()
    # about 20 more fields

class CanMotivateOthers(HappyIndexForm):
    has_hobbies = forms.BooleanField()
    # about 20 more fields
<!-- template.html -->
{% for field in first_ten_fields_therapy_needed %}
{{ field }}
{% endfor %}

{% include happyindexform.html %}

{% for field in second_ten_fields_therapy_needed %}
{{ field }}
{% endfor %}
任务:

循环遍历 TherapyNeededForm 的前十个字段,然后显示我为来自 HappyIndexForm 的字段编写的特定模板,称为 'happyindexform.html',然后循环遍历 TherapyNeededForm 的后十个字段。

我的问题:

如果我循环遍历字段并包含我的 'happyindexform.html',那么从继承中获得的字段将显示两次。我当然也可以为 TherapyNeededForm 的所有20个字段编写特定模板,但这很重复,与DRY不符。

注意:我正在使用Django的基于类的视图。这就是为什么我觉得使用2个表单比继承方法更加紧张的原因。

我觉得我不知道如何将表单分成块并将它们分别发送到模板。

英文:

I want to render part of the form via a loop in template and a part with specific "design".

# forms.py
class HappyIndexForm(forms.Form):
    pizza_eaten = forms.IntegerField(label=&quot;Pizzas eaten&quot;)
    # 5 more fields
    minutes_outside = forms.IntegerField(label=&quot;Minutes outside&quot;)

class TherapyNeededForm(HappyIndexForm):
    had_therapy_before = forms.BooleanField()
    # about 20 more fields

class CanMotivateOthers(HappyIndexForm):
    has_hobbies = forms.BooleanField()
    # about 20 more fields

The only purpose of HappyIndexForm is to pass the 7 fields to other forms that need to work with the "HappyIndex" (it is a made up example).

I have designed a very nice and complex template for the HappyIndexFormfields. The fields of TherapyNeededForm exclusive of the HappyIndexFormfields I want to simply loop over.

&lt;!-- template.html --&gt;
{% for field in first_ten_fields_therapy_needed %}
{{ field }}
{% endfor %}

{% include happyindexform.html %}

{% for field in second_ten_fields_therapy_needed %}
{{ field }}
{% endfor %}
Task:

Loop over first ten fields of TherapyNeededForm, then display the specific template I wrote for the fields coming from HappyIndexForm called 'happyindexform.html', then loop over second ten fields of TherapyNeededForm.

My problem:

If I loop over the fields and include my 'happyindexform.html' the fields coming from inheritance get displayed twice. I for sure can also write the specific template for all the 20 fields of TherapyNeededForm but that is very repetitive and not in any way aligned to DRY.

Note: I am using django's class-based views. That's why I feel using 2 forms would be more stressful than the inheritance approach.

I feel like I am somehow missing how I can devide my form to blocks and ship them to the template separately.

答案1

得分: 2

我会翻译代码部分,以下是代码的翻译:

from itertools import islice

class TherapyNeededView(FormView):
    template_name = 'template.html'
    form_class = TherapyNeededForm

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        form = context['form']

        # 获取所有字段名
        all_fields = list(form.fields.keys())

        # 获取前10个字段名
        first_ten_fields = list(islice(all_fields, 10))

        # 获取从HappyIndexForm继承的字段名
        happy_index_fields = [name for name in all_fields if name in HappyIndexForm.base_fields]

        # 获取剩余的字段名
        remaining_fields = list(islice(all_fields, len(happy_index_fields) + len(first_ten_fields), None))

        # 更新上下文
        context.update({
            'first_ten_fields_therapy_needed': first_ten_fields,
            'happy_index_fields': happy_index_fields,
            'second_ten_fields_therapy_needed': remaining_fields,
        })

        return context

在模板中,你可以使用这些新变量:

<!-- template.html -->
{% for field_name in first_ten_fields_therapy_needed %}
{{ form[field_name] }}
{% endfor %}

{% include happyindexform.html %}

{% for field_name in second_ten_fields_therapy_needed %}
{{ form[field_name] }}
{% endfor %}

在你的'happyindexform.html'中,你可以遍历字段:

<!-- happyindexform.html -->
{% for field_name in happy_index_fields %}
{{ form[field_name] }}
{% endfor %}

这样,来自'HappyIndexForm'的字段将只显示一次。

英文:

I would assume the HappyIndexForm is used as a base form class for other form classes. Here, you have two other form classes - TherapyNeededForm and CanMotivateOthersForm - that inherit from HappyIndexForm, thereby inheriting its fields.

When you instantiate TherapyNeededForm or CanMotivateOthersForm, they will have all the fields from HappyIndexForm along with their own additional fields.

The goal would be therefore to separate out the fields inherited from HappyIndexForm from the rest of the fields in TherapyNeededForm and CanMotivateOthersForm, which allows you to treat them differently in your templates.

In Django, form fields are stored in a dictionary-like object, which ensures that the fields are always iterated over in the order (by default) they were defined in the form class (see "Notes on field ordering").
You could take advantage of this to separate the fields into three parts:

  • the first 10,
  • the inherited fields from HappyIndexForm,
  • and the remaining fields.

To do this, you can use Python's built-in itertools library, specifically the []islice function](https://docs.python.org/3/library/itertools.html#itertools.islice), which returns an iterator that returns selected elements from the iterable.

In your view, you can slice the fields into two parts, the first ten and the remaining fields.
Then, in the template, you can include the 'happyindexform.html' after the first loop.


However, the key here is to remember that Django form fields need to be accessed in a [bound state](https://docs.djangoproject.com/en/4.2/ref/forms/api/#bound-and-unbound-forms] in order to be rendered properly in a template.

When you iterate over a form object directly, you get bound fields, which are the field objects bound to the form and its data. These bound fields can be directly included in templates, and Django will render the appropriate HTML.

That means a code like this one would be incorrect:

// Incorrect
first_ten_fields = list(islice(form, 10))
remaining_fields = list(islice(form, 10, None))

This is incorrect, because slicing the form object gives us the fields themselves, not the bound fields, which are needed for rendering.

You need first to get a list of all field names, and then to slice this list of names:

// Correct
all_fields = list(form.fields.keys())
first_ten_fields = list(islice(all_fields, 10))
second_ten_fields = list(islice(all_fields, 10, None))

And you need to be careful with the template themselves:

&lt;!-- Incorrect --&gt;
{% for field in first_ten_fields %}
{{ field }}
{% endfor %}

That does not work because field is not a bound field, it's just the field itself: you need to use the field names (form[field_name]) to access the corresponding bound fields:

&lt;!-- Correct --&gt;
{% for field_name in first_ten_fields %}
{{ form[field_name] }}
{% endfor %}

The code would be:

from itertools import islice

class TherapyNeededView(FormView):
    template_name = &#39;template.html&#39;
    form_class = TherapyNeededForm

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        form = context[&#39;form&#39;]

        # Get all field names
        all_fields = list(form.fields.keys())

        # Get the first 10 field names
        first_ten_fields = list(islice(all_fields, 10))

        # Get the field names inherited from HappyIndexForm
        happy_index_fields = [name for name in all_fields if name in HappyIndexForm.base_fields]

        # Get the remaining field names
        remaining_fields = list(islice(all_fields, len(happy_index_fields) + len(first_ten_fields), None))

        # Update the context
        context.update({
            &#39;first_ten_fields_therapy_needed&#39;: first_ten_fields,
            &#39;happy_index_fields&#39;: happy_index_fields,
            &#39;second_ten_fields_therapy_needed&#39;: remaining_fields,
        })

        return context

In your template, you can use these new variables:

&lt;!-- template.html --&gt;
{% for field_name in first_ten_fields_therapy_needed %}
{{ form[field_name] }}
{% endfor %}

{% include happyindexform.html %}

{% for field_name in second_ten_fields_therapy_needed %}
{{ form[field_name] }}
{% endfor %}

And in your 'happyindexform.html', you can loop over the fields:

&lt;!-- happyindexform.html --&gt;
{% for field_name in happy_index_fields %}
{{ form[field_name] }}
{% endfor %}

This way, the fields from the HappyIndexForm will only be displayed once.


The line in the view:

happy_index_fields = [name for name in all_fields if name in HappyIndexForm.base_fields]

This line is going through the names of the fields of the form in the view (either TherapyNeededForm or CanMotivateOthersForm), and picking out the ones that are part of HappyIndexForm.
These field names are then passed to the template separately from the other field names.

In the template, we use these names to access the corresponding bound fields from the form. Django will then render these bound fields correctly.

This approach allows you to use your special template for the HappyIndexForm fields while avoiding the issue of displaying these fields twice.

huangapple
  • 本文由 发表于 2023年5月28日 23:16:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76352175.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定