1

I have a SurveyForm where I create dynamically my fields. Here's the most basic code I could do and still have my problem:

class SurveyForm(forms.Form):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.fields = []
        self.fields_alone = []
        new_field = forms.CharField(label=f'1 - 2: This is the question',
                                    widget=widgets.Input(attrs={}))
        self.fields.append(('id_1', new_field))
        self.fields_alone.append(new_field)
        self.fields = OrderedDict(self.fields)

In my template, when I do a "classical" loop I can display the fields and it works, but the second loop, which is supposed to access to the same fields, doesn't work:

<form action="" method="post">
    {% for field in form %}
        {{ field.label }}&nbsp;:{{ field }} <br>
    {% endfor %}

    {% for field in form.fields_alone %}
        {{ field.label }}&nbsp;:{{ field }} <br>
    {% endfor %}
</form>

The second loop display the field as a string like <django.forms.fields.CharField object at 0x0000012C00EF60D0>

What am I missing to display is like the "classical" loop?

Olivier Pons
  • 15,363
  • 26
  • 117
  • 213

3 Answers3

1

I found the problem. It's in the source code of Django.

Django can only convert fields to HTML if they are BoundField. The problem is that you can access to the BoundField "version" of the fields of your form through iteration on the form itself.

In the django/forms/forms.py:

def __getitem__(self, name):
    # blabla code
    if name not in self._bound_fields_cache:
        self._bound_fields_cache[name] = field.get_bound_field(self, name)

So you can get HTML code only through an iteration on the form or direct access to a field via form['myfield'].

So in my form I did:

class SurveyForm(forms.Form):
    def field_by_id(self, field_id):
        return self[field_id]

Then I've made a template tag in my application which is:

@register.filter(name='field_by_id')
def field_by_id(arg1, arg2):
    """
    Returns the result of field_by_id() (= method has to exist in arg1!)
    Usage: {{ form|field_by_id:XXX }} (XXX = field id string)

    :param arg1: object of class Form
    :param arg2: field id string
    :returns corresponding field
    """
    return arg1.field_by_id(arg2)

and then in my template, I use it like this:

    {% for question_group, questions in form.question_groups.items %}
        {% for question, answers in questions.items %}
            {% for answer in answers %}
                {% with form|field_by_id:answer as field %}
                    {{ field.label }}&nbsp;:{{ field }} <br>
                {% endwith %}
            {% endfor %}
        {% endfor %}
    {% endfor %}

And it works. Tricky solution, but I have many sub-groups (I could have used FormSet's for a single sub-group).

Olivier Pons
  • 15,363
  • 26
  • 117
  • 213
0

The fields list is meant to store the form fields. Whereas fields_alone isn't.

That is also the reason why you are able to loop over the form itself and not over form.fields.

Since I cant figure out why you need to have this other list I suggest that you add all the fields to the actual fields list.

Comment if there are any further questions to this problem

Tunic
  • 79
  • 1
  • 9
  • I need to create and then display a form 100% dynamically constructed with fields that are "answers" grouped by "questions" and "questions" grouped by "group of questions and "group of questions" grouped by my own "form" model. To make this, I'm trying to make my code based on this: https://stackoverflow.com/questions/10366745/django-form-field-grouping – Olivier Pons Jan 23 '20 at 17:32
  • ...and to be able to make this, I dont know how to make "recursive" groups of groups of groups (and so on) of fields – Olivier Pons Jan 23 '20 at 17:34
0

Here:

self.fields = []

You're overwriting the form's own fields ordereddict. You just want to add to it:

self.fields["whatevernameyouwant"] = new_field

(works on django 1.11, not tested on 2.x / 3.x)

EDIT:

the problem is when I try to use the fields through another property

You mean the form.fields_alone thing ? Well, you can always make it a property instead:

class YourForm(...):
    def __init__(...)
        # ....
        self._fields_alone = []
        self.fields["whatevernameyouwant"] = new_field
        self.fields_alone.append("whatevernameyouwant")

    @property
    def fields_alone(self):
        for name in self._fields_alone:
            yield self[name]

But it won't remove this field from the "normal" form iterator (for field in form) so you'll have the field twice, so you'd also need to redefine __iter__ too:

def __iter__(self):
    for name in self.fields:
        if name not in self._fields_alone:
            yield self[name]
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • Hi. Ive done this à lot of times it works flawlessly the problem is when I try to use the fields through *another* property. – Olivier Pons Jan 23 '20 at 17:03
  • Hi, your solution doesn't work either... but in the template I've reproduced the problem more easily, if this can help you to.. help me: `{% for field in form.fields.values %} {{ field.label }} :{{ field }}
    {% endfor %}` --> this will display things like `` instead of the actual field
    – Olivier Pons Jan 23 '20 at 21:25
  • What really disturbs me is that `{{ field.label }}` *works properly in both loops*. It's the `{{ field }}` "alone" which doesn't work. – Olivier Pons Jan 23 '20 at 21:32