Iterate over Django custom Form - python

I am trying to create a dynamic form, with a varying number of CharFields. I want to be able to display them at will in semi-arbitrary places in my form. My approach was to create an iterable function that fielded the right self.fields[INDEX]. However, when I do this, I literally see this:
<django.forms.fields.CharField object at 0x80bae6be0>
<django.forms.fields.CharField object at 0x80bae6f98>
<django.forms.fields.CharField object at 0x80bae6da0>
How do I make a CharField() render as expected?
My code is below:
class ImplementationForm(forms.ModelForm):
"""
Specifies the implementation of a given control.
"""
class Meta:
model = Implementation
fields = ['implemented', 'scope', 'separate']
def __init__(self, *args, **kwargs):
control = kwargs.pop('control')
super(ImplementationForm, self).__init__(*args, **kwargs)
self.fields['separate'].widget.attrs.update({'class': 'separate'})
self.fields['scope'].widget.attrs.update({'class': 'scope'})
for substatement in control.substatement.all():
self.fields['statement_%s'%substatement.pk] = forms.CharField()
def subfield(self):
print("Comes herE")
for index in self.fields:
if index[:10] == 'statement_':
yield self.fields[index]
The template basically does this:
{% for x in myform.subfield %} {{ x }} {% endfor %}

What you are looking for is the form's BoundFields. e.g. {{ form.email }}
You are iterating over the Field instances (not the form's BoundField instances that wraps the Field Instances) e.g. {{ form.field.email }}.
This is why you are getting the
<django.forms.fields.CharField object at 0x80bae6da0>
result from your template. See: https://stackoverflow.com/a/671305/3035260
Also see django's documentation: https://docs.djangoproject.com/en/1.10/ref/forms/api/#django.forms.BoundField
Try this dirty way (See my edit below for a much better solution):
{# Iterate over your list of field instances #}
{% for x in myform.subfield %}
{# Find the matching BoundField instance #}
{% for field in myform %}
{% if field.field == x %}
{# Matching BoudField instance found, display it. #}
{{ field }}
{% endif %}
{% endfor %}
{% endfor %}
Edit:
Just came across a better (less dirty) approach:
A Field has a
get_bound_field(self, form, field_name)
method according to the docs: https://docs.djangoproject.com/en/1.10/_modules/django/forms/fields/#Field.get_bound_field
So, in your subfield method in the last line (The 'yield' line), try this:
yield self.fields[index].get_bound_field(self, index)
Then, your template will remain the same:
{% for x in myform.subfield %} {{ x }} {% endfor %}
and everything should work as you intended.

Related

Django Formset: object has no attribute 'get'

I think I'm almost there, but the last part just doesn't want to work :(. I hope you can help as I'm not seeing it anymore after 2 days.
Within my FormWizard I'm trying to (1) show a Formset based on a Slider Input in a previous set (setting the Extra value) and (2) in each Form within the Formset I want to show a ChoiceField (forms.Select) based on a Text-input in a previous step.
With a lot of searching on stackoverflow I am able to do step 1. Step 2 is almost working, except for the fact that the ChoiceField doesn't update with the new values from the Text-input. This is my code in views.py:
class FormWizardView(LoginRequiredMixin, SessionWizardView):
template_name = 'test/test.html'
def get_form_initial(self, step):
if step == 'formset_step':
form_class = self.form_list[step]
data = self.get_cleaned_data_for_step('slider_step')
if data is not None:
# To set the extra value in formset based on slider input
extra = data['number_slider']
form_class.extra = extra
# To set the ChoiceField value in formset based on text-input
form1_cleaned_data = self.get_cleaned_data_for_step('text_input_step')
formset = form_class().forms
for form in formset:
if form1_cleaned_data:
form.fields['text_input'].choices = [item for item in form1_cleaned_data.items()]
# Print form to test if the form updates
print(form)
return formset
return super(FormWizardView, self).get_form_initial(step)
def done(self, form_list, **kwargs):
do something
return something
I'm trying to return the formset, but I get the error 'TestForm' object has no attribute 'get'. I am probably returning the wrong thing here, but whatever I try to return, it doesn't work. Returning super(FormWizardView, self).get_form_initial(step) just gives me the empty ChoiceField and returning the form gives me the error object of type 'TestForm' has no len().
I also printed out the form in my console, and that seems to work properly. Does anyone know what I should return in order to get the populated ChoiceField?
Many thanks!
EDIT:
Thanks for your answer! When I modify my get_form:
def get_form(self, step=None, data=None, files=None):
if step == 'formset_step':
form_class = self.form_list[step]
data = self.get_cleaned_data_for_step('slider_step')
if data is not None:
# To set the extra value in formset based on slider input
extra = data['number_slider']
form_class.extra = extra
# To set the ChoiceField value in formset based on text-input
form1_cleaned_data = self.get_cleaned_data_for_step('text_input_step')
formset = form_class().forms
for form in formset:
if form1_cleaned_data:
form.fields['text_input'].choices = [item for item in form1_cleaned_data.items()]
# Print form to test if the form updates
print(form)
return super(FormWizardView, self).get_form(step, data, files)
I get the error ['ManagementForm data is missing or has been tampered with']. When browsing through StackOverflow it seems a template problem (and specifically not setting {{ wizard.management_form }}, but I took the plain code from the Django FormTools doc which should normally work. In my template I have this:
{% extends "base.html" %}
{% load i18n %}
{% block head %}
{{ wizard.form.media }}
{% endblock %}
{% block content %}
<p>Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}</p>
<form action="" method="post">{% csrf_token %}
<table>
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{{ form }}
{% endfor %}
{% else %}
{{ wizard.form }}
{% endif %}
</table>
{% if wizard.steps.prev %}
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.first }}">{%
˓→trans "first step" %}</button>
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">{%
˓→trans "prev step" %}</button>
{% endif %}
<input type="submit" value="{% trans "submit" %}"/>
</form>
{% endblock %}
Am I not seeing something in the template or my get_form function not correct? Many thanks for looking at my problem :)
Method get_form_initial from form wizard should return a dictionary with initial data for the form that will be created, not the form itself. If you want to modify whole form, try modifying get_form method instead.

grouping fields in django form

I want to render form grouping fields. Form actually is created dynamically according to incoming dictionary
for f in settings.FORM_BIG_FIELDS:
self.fields[f['id']] = eval(f['type'])(label=f['label'], required=f.get('required', True))
self.fields[f['id']].groupp = f.get('group', 1)
groupp attribute means appropriate group, then I try to render it like
{% regroup form.fields.values by groupp as field_group %}
{% for group in field_group %}
<div class="group_{{ group.grouper }}">
{% for field in group.list %}
<p>
{{ field.all }}
{{ field }}
</p>
{% endfor %}
</div>
{% endfor %}
But as output I get the following
<django.forms.fields.CharField object at 0xb527388c>
<django.forms.fields.IntegerField object at 0xb52738ec>
<django.forms.fields.ChoiceField object at 0xb527394c>
I have read that these are not the same as BoundField object. How to render fields or is there any other better approaches to group fields?
If you do not want use any additional libraries, then the most easy solution is to render them manually, i would say. Otherwise you will just spend alot of time repeating the functionality of the library i copied as comment to your post.
There is always the case that things should be DRY. But we build websites for the users and user cares little about how the form rendering in template is done. For this reason we have often created form templates manually like this:
<div class="something">
{{ form.fieldname.label_tag }}{{ form.fieldname }}
</div>
Easyest way to organise it saving you some time. And in my opinion it is not that bad either, since this is not very common when you need fields organised by fieldsets.
I know this question is rather old, but I am sure there are still people who can benefit from a simple solution:
Say you have a group name and list of members. You can define a self.fieldset in your form's init to be a dictionary of {'group_1': ['member_1', 'member_2', ... ], ... }. Once you attach this to the form, you can pass it to views and from there to the template:
In forms.py:
class MyForm:
def __init__(self, current_user, *args, **kwargs):
super(YourForm, self).__init__(*args, **kwargs)
self.field['group'].queryset = Group.objects.filter(user = current_user)
...
In views.py:
form = self.Form(current_user)
the_fieldsets = form.fieldset
c = {'form': search_form,
'fieldsets': the_fieldsets }
In your template:
{% for field in form %}
<tr>
<td>{{field.label_tag}}</td>
{% if field.name == 'group' %}
<td>
<select id='{{field.id}}' name='{{field.name}}'>
{% for k,v in fieldsets.items %}
<optgroup label = {{k.name}}>
{% for val in v %}
<option name='{{val}} value = {{val.id}}> {{val.name}} </option> # Note that the select needs to return 'id', so value has to be {{val.id}}
{% endfor %}
</optgroup>
{% endfor %}
</select>
</td>
{% else %}
<td>{{field}}</td>
{% endif %}
<td>{{field.help_text}}</td>
<td>{{field.errors}}</td>
</tr>
{% endfor %}

Render form errors with the label rather than field name

I would like to list all form errors together using {{ form.errors }} in the template. This produces a list of form fields and nested lists of the errors for each field. However, the literal name of the field is used. The generated html with an error in a particular field might look like this.
<ul class="errorlist">
<li>
target_date_mdcy
<ul class="errorlist">
<li>This field is required.</li>
</ul>
</li>
</ul>
I would like use the errorlist feature, as it's nice and easy. However, I want to use the label ("Target Date", say) rather than the field name. Actually, I can't think of a case in which you would want the field name displaying for the user of a webpage. Is there way to use the rendered error list with the field label?
I don't see a simple way to do this.
The errors attribute of the form actually returns an ErrorDict, a class defined in django.forms.utils - it's a subclass of dict that knows to produce that ul rendering of itself as its unicode representation. But the keys are actually the field names, and that's important to maintain for other behavior. So it provides no easy access to the field labels.
You could define a custom template tag that accepts the form to produce the rendering you prefer, since in Python code it's easy to get the field label given the form and the field name. Or you could construct an error list by label in the view, add it to your context, and use that instead.
edit
Alternately again, you can iterate over the fields and check their individual errors, remembering to display non_field_errors as well. Something like:
<ul class="errorlist">
{% if form.non_field_errors %}
<li>{{ form.non_field_errors }}</li>
{% endif %}
{% for field in form %}
{% if field.errors %}
<li>
{{ field.label }}
<ul class="errorlist">
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</li>
{% endif %}
{% endfor %}
</ul>
You might want to wrap non_field_errors in a list as well, depending.
I know this has already been answered but, I ran across the same scenario and found there is a simple way to use the label:
{% if form.errors %}
<ul class="user-msg error">
{% for field in form %}
{% for error in field.errors %}
<li>
{% if field != '__all__' %}
<strong>{{ field.label }}:</strong>
{% endif %}
{{ error }}
</li>
{% endfor %}
{% endfor %}
</ul>
{% endif %}
I solved this in a custom form class which all my forms inherit instead of django.forms.Form. There I change the way form.errors works by returning a custom ErrorDict whose as_ul method takes labels into account. Thus you don't need to change your templates, but you need to have your forms inherit CustomBaseForm.
class CustomErrorDict(ErrorDict):
def __init__(self, form, iterable=None, **kwargs):
self.form = form
super(CustomErrorDict, self).__init__(iterable, **kwargs)
def as_ul(self):
if not self:
return u''
def humanify(field_name):
try:
return self.form.fields[field_name].label or field_name
except:
return field_name
# main logic is copied from the original ErrorDict:
return mark_safe(u'<ul class="errorlist">%s</ul>'
% ''.join([u'<li>%s%s</li>' % (humanify(k), force_unicode(v))
for k, v in self.items()]))
class CustomBaseForm(forms.Form):
#property
def errors(self):
return CustomErrorDict(self, super(forms.Form, self).errors)
... rest of CustomBaseForm ...
from django import forms
def my_clean(self):
self.my_errors = ''
for x in self.visible_fields():
if x.errors:
self.my_errors += "<p>%s: %s</p>" % (x.label, x.errors)
class SetPwdForm(forms.Form):
pwd= forms.CharField(label='password', required=True, min_length=6)
def clean(self):
...
my_clean(self)
use myform.my_errors in views.
Just in case anyone is looking do something like this using the django.contrib.messages framework in, for example, a FormView:
def form_invalid(self, form):
for field, errors in form.errors.items():
for error in errors:
messages.error(
self.request,
form.fields[field].label + ": " + error
)
Note this is just a basic template, you'll have to take care of non-field errors et cetera in your code in the case where form.fields[field] doesn't make sense.
The following approach shows verbose_name instead of the field name.
It can be used in get_context_data() too but personally, I prefer this way:
from django.core.exceptions import FieldDoesNotExist
class ShowVerboseNameInFormsMixin:
def add_error(self, field, error):
super(ShowVerboseNameInFormsMixin, self).add_error(field, error)
for field, message in self._errors.copy().items():
try:
verbose_name = self._meta.model._meta.get_field(field).verbose_name
del self._errors[field]
self._errors[verbose_name] = self.error_class()
self._errors[verbose_name].extend(message)
except FieldDoesNotExist:
pass
and then use it like this:
from django import forms
class FooForm(ShowVerboseNameInFormsMixin, forms.ModelForm):
class Meta:
model = Foo
fields = ['foo', 'bar', 'baz']
with a little extra code, It can show the __all__ to all or any other intended string.
Here's the filter I used to render an error list with the field label, following Peter's suggestion.
from django.utils.safestring import mark_safe
from django.template import Library, Context, loader
register = Library()
#register.filter
def form_error_list(form):
result = ""
if form.errors:
fields = form.fields
error_items = []
for error_field in form.errors:
label = fields[error_field].label
if not label:
label = ' '.join([word.capitalize() for word in error_field.split('_')])
errors = form.errors[error_field]
error_li = ''.join(['<li>{0}</li>'.format(error) for error in errors])
error_items.append({'label': label, 'error_li': error_li})
inner = ''.join(['<li>{0}<ul class="errorlist">{1}</ul></li>'.format(item['label'], item['error_li']) for item in error_items])
result = mark_safe('<ul class="errorlist">{0}</ul>'.format(inner))
return result

Show parent_model in django template

I have a variable:
m2m_links = mymodel._meta.many_to_many
This has all of the fields that are m2m in a particular model. I want the template to display the names of the models that are linked. Within my view, I can list the tables like so:
for f in m2m_links:
print f.related.parent_model
But in my template, if I try
{% for table in m2m_links %}
{{ table.related.parent_model }}<br>
{% endfor %}
I get an error: "Caught DoesNotExist while rendering"
How do I get the names of the tables to render in the template? And a further question, how do I get just the name, not something like
<class 'myapp.models.ModelName'>
which is what I have showing in my terminal from the "print" statement.
There's no reason, based on the template code you've provided that you should be getting that error. Most likely, there's something else going on in your template that is causing that.
As for your second question, the way to get a class' name is:
some_class.__name__
However, the Django template engine will not allow you to use underscored properties in the template, so your best bet is to prepare a proper list in your view:
linked_models = [m2m.related.parent_model.__name__ for m2m in mymodel._meta.many_to_many]
Then, just loop through that in your template and the all the work is already done.
UPDATE (based on comment)
You do it mostly the same way, though you have a couple of choices.
You can do a list of tuples and unpack it in the template:
linked_models = [(m2m.related.parent_model.__name__, m2m.related.parent_model._meta.verbose_name) for m2m in mymodel._meta.many_to_many]
Then, in your template:
{% for class_name, verbose_name in linked_models %}
{{ class_name }} {{ verbose_name }}
{% endfor %}
Create a list of dictionaries and reference the keys in the template:
linked_models = [{'class_name': m2m.related.parent_model.__name__, 'verbose_name': m2m.related.parent_model._meta.verbose_name} for m2m in mymodel._meta.many_to_many]
Them, in your template:
{% for model in linked_models %}
{{ model.class_name }} {{ model.verbose_name }}
{% endfor %}

Inlines Python/Django technique for objects

I am reading the source code of the Django application blog at git://github.com/nathanborror/django-basic-apps.git.
How do you read the following Django code?
{% tags_for_object object as tag_list %}
My attempt: Make the variable object of the type tags_for_object and rename the variable to tag_list.
The object apparently is based on the file blog/templates/inlines/default.html:
{% if object %}
{{ object }}
{% else %}
{% for object in object_list %}
{{ object }}
{% endfor %}
{% endif %}
What is the befefit of putting the logic to two-step procedure: run single object, else loop through a list of objects?
It looks like tags_for_object is the template tag from the django-tagging application.
From the django-tagging documentation:
tags_for_object:
Retrieves a list of Tag objects
associated with an object and stores
them in a context variable.
Usage:
{% tags_for_object [object] as [varname] %}
Example:
{% tags_for_object foo_object as tag_list %}
You can then loop through the tag_list variable in the template to display the tags.
{% tags_for_object foo_object as tag_list %}
<ul>
{% for tag in tag_list %}
<li>{{ tag }}</li>
{% endfor %}
</ul>
For the second part of your question, you understand the code correctly. If the variable object exists in the context (and doesn't evaluate to False), it is displayed. If it does not exist in the context (or if it evaluates to False), then the code loops through the objects in object_list, and displays them.
As for why you would want to do this, you would have to look at the code that uses inlines/default.html to work out what the designer had in mind.

Categories