Django field display in template by group - django

I would like to display 2 groups of fields. The group is defined based on the kind of field.
I added 2 methods in my form:
def get_group_a(self):
return [obj for obj in self.fields.values() if isinstance(obj, GroupAField)]
def get_group_b(self):
return [obj for obj in self.fields.values() if isinstance(obj, GroupBField)]
Then in the template I tried to display the form:
<h1>Group A:</h1>
{% for f in form.get_group_a %}
<div class="fieldWrapper">
{{ f.errors }}
<label> {{ f.label }}:</label>
{{ f }}
</div>
{% endfor %}
<h1>Group B:</h1>
{% for f in form.get_group_b %}
<div class="fieldWrapper">
{{ f.error }}
<label> {{ f.label }}:</label>
{{ f }}
</div>
{% endfor %}
This is working partially. I have the good label of the field but I don't have the text input displayed.
How can I get the good field object?

Don't iterate through self.fields in your get_group methods, but through self directly. self.fields contains the raw field instances: for display, Django creates BoundField instances which wrap those fields, and which you access directly via self['fieldname'].

For representing form fields in template Django uses BoundField. BoundField used to display HTML or access attributes for a single field of a Form instance. So in your case, you should wrap grouped fields with BoundField, like this:
def get_group_a(self):
return [BoundField(self, field, name) for name, field in self.fields.items() if isinstance(field, GroupAField)]
def get_group_b(self):
return [BoundField(self, field, name) for name, field in self.fields.items() if isinstance(field, GroupbField)]

Related

Django: how to display my own dynamically-made fields

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 }} :{{ field }} <br>
{% endfor %}
{% for field in form.fields_alone %}
{{ field.label }} :{{ 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?
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 }} :{{ 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).
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
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]

How to access field values of an object from a ModelMultipleChoiceField, from within a template?

I have a database of television channels that I am displaying using a Django form with a ModelMultipleChoiceField and a CheckboxSelectMultiple widget.
The template renders fine, but how do I access fields of the individual objects that are part of the ModelMultipleChoiceField field of the form?
Channel model
class Channel(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
This is the form
class ChannelForm(forms.Form):
channels_list = forms.ModelMultipleChoiceField(queryset=Channel.objects.all().order_by('name'),widget=forms.CheckboxSelectMultiple)
This is how I was trying to access the 'name' field of each channel in the template by doing {{channel.name}}.
{% for channel in form.channels_list %}
{{ channel }} accessing: {{ channel.name }}<br>
{% endfor %}
When it renders it simply says "accessing: " without the channel name
Here is my view.py as requested:
def generage_recommendation(request):
if request.method == 'POST':
# POST code ...
else:
form = RecommendationRequestForm()
return render(request, 'recommendation_home.html', {'form':form})
in your views.py,
use ChannelForm instead of RecommendationRequestForm.
in your template, you can access field values of ModelMultipleChoiceField like this:
{% for key, value in form.channels_list.field.choices %}
Channel ID = {{ key }}
Channel Name = {{ value }}
{% endfor %}
(notice .field. code)
Can you post your views.py file.
You don't have to use
form.channels in the template.
Try using this instead.
{% for channel in channels_list %}
{{ channel }} accessing: {{ channel.name }}<br>
{% endfor %}

Check for errors and other values at the widget level - maybe using custom form field

How can I access if a field has)errors at the level of widget?
Using default I tried:
{% if widget.attributes.has_errors %} or {% if widget.has_errors %}
but are not working.
I use custom widget templates, I'm thinking to use a custom form Field and overwrite the default field.
I know clean method exist but I don't know how to push to the widget the dynamic(non default) data/attributes I want.
I tried:
class AWidget(forms.Widget):
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
has_errors = context['widget']['attrs'].pop('has_errors', None)
context['widget']['has_errors'] = has_errors
It works for errors but I don't know if is the best option plus I want to pass other values/attributes from Form Field,and I think will be better to try to overwrite the Form Field but I don't know exactly how.
Also accessing individual attributes using:
{{ widget.attrs.maxlength }} or {{ widget.attrs.items.maxlength }}
even if accedes in a for loop works
I know I can add a parent div with a class of error:
<div class="{% if form.field.errors %}pass_error{% endif %}">
{{ form.field }}
</div>
but, that implies big changes at the css level.
I already overwrite all Django widgets with custom widgets, on error I don't need just to change a border color, but to show or not different elements of the widget template and the position of some of them change.
I already modify the based widget to add errors, but I'm looking to do it in a more elegant way at the field level by passing from the field to the widget, parameters depending on error type.
So my question is what I need to overwrite to pass from field to widget errors and other variables ?
Not sure whether this could help in your specific use case ... but just in case, please note that when you build your form in the view, you can add extra parameters as needed, then pass them down to your custom widget.
Working example:
file "forms.py"
from django import forms
def build_ingredient_form(unit):
"""
Ingredient form factory
Here we build the form class dynamically, in order to acces 'unit' via closure.
References:
http://stackoverflow.com/questions/622982/django-passing-custom-form-parameters-to-formset#623030
"""
class IngredientForm(forms.Form):
#quantity = forms.DecimalField(max_digits=10)
quantity = UnitField(unit, required=False)
...
return IngredientForm
file "fields.py"
from django import forms
from .fields import UnitField
class UnitField(forms.CharField):
"""
Custom field to support UnitWidget
References:
- http://tothinkornottothink.com/post/10815277049/django-forms-i-custom-fields-and-widgets-in
"""
def __init__(self, unit, *args, **kwargs):
self.unit = unit
super(UnitField, self).__init__(*args, **kwargs)
self.widget = UnitWidget(unit)
...
file "widgets.py"
from django import forms
from .models import Unit
class UnitWidget(forms.TextInput):
def __init__(self, unit, attrs=None):
if unit is None:
self.unit = Unit()
else:
self.unit = unit
...
Well a widget is how you will render the field's data/value into the HTML rendered template, that's the only function of widgets, look the following example taken from the docs:
>>> name = forms.TextInput(attrs={'required': True})
>>> name.render('name', 'A name')
'<input name="name" type="text" value="A name" required>'
>>>
>>> name = forms.TextInput(attrs={'required': False})
>>> name.render('name', 'A name')
'<input name="name" type="text" value="A name">'
So, widgets are not aware of the data is valid(has errors) or not and should remain that way.
Is not a good idea to handle any data error/validation at the widget level, you want, I can ensure that, that if you change how your field looks like (the widget), your validations keeps working.
Said that ...
How can I access field errors?
When you are rendering a form you can do it field by field lets take this form by example:
class LoginForm(forms.Form):
username = forms.CharField(max_length=255)
password = forms.CharField(widget=forms.PasswordInput)
you can write to temlate:
<form action="." method="get">
<p>{{ loginform.username.label }}: {{ loginform.username }}</p>
<p>{{ loginform.password.label }}: {{ loginform.password}}</p>
<button type="submit">submit</button>
</form>
And this will render something like the following:
Now, suppose your form won't admit passwords with less than 8 characters:
class LoginForm(forms.Form):
username = forms.CharField(max_length=255)
password = forms.CharField(widget=forms.PasswordInput)
def clean_password(self):
password = self.cleaned_data['password']
if len(password) < 8:
raise forms.ValidationError(
"Password must have at least 8 characters, it has only %(password_length)s",
code='invalid password',
params={'password_length': len(password)}
)
return password
You can access the password errors like this:
<form action="." method="get">
{% csrf_token %}
<p>{{ form.username.label }}: {{ form.username }}</p>
<p>{{ form.password.label }}: {{ form.password}}</p>
<ul>
{% for error in form.password.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
<button type="submit">submit</button>
</form>
And now if you type a short password ...
I want the control to look different if there are errors.
You can add some style if there are errors just use {% if ... %} in your template code:
<p>
{{ form.password.label }}:
<span class="{% if form.password.errors %}pass_error{% endif %}">
{{ form.password }}
</span>
</p>
With CSS:
<style>
.pass_error input {
border-color: red;
}
</style>
And this is the result:
Conlusion.
Validate and handle data errors in the form or using validators, use widgets for display the data, of course, you can customize how the data is presented since you can specify a custom template for your widget.
I also recommend django-widget-twaeks if you want to add attributes to your widget in template code. This apps allows you to write code like (example from the app docs):
{% load widget_tweaks %}
<!-- change input type (e.g. to HTML5) -->
{% render_field form.search_query type="search" %}
<!-- add/change several attributes -->
{% render_field form.text rows="20" cols="20" title="Hello, world!" %}
<!-- append to an attribute -->
{% render_field form.title class+="css_class_1 css_class_2" %}
<!-- template variables can be used as attribute values -->
{% render_field form.text placeholder=form.text.label %}

Why is not there a reusable template for Django's DetailView?

Displaying forms in a template is rather easy in Django:
<form action="" method="post">{% csrf_token %}
{{ form }}
<input type="submit" value="Update" />
</form>
It is basically just one word - display the {{ form }}. It is so simple that you can use the same template for different forms.
You can limit the fields to be shown on the form using the fields = [] list if you are using CBV's such as CreateView or UpdateView.
Drawing parallel to this, one expects to have a similar workflow for showing the models as well (as opposed to editing) such as in DetailView. But, there is no such thing.. You have to write a custom template for every DetailView that you use. Such as:
<h3>User: {{ user }}</h3>
<label>First Name</label>: {{ user.first_name }} <br />
<label>Last Name</label>: {{ user.last_name }} <br />
<label>Username</label>: {{ user.username }} <br />
<label>School</label>: {{ user.person.school.name }} <br />
This is very similar to what the {{ form }} would generate, except for the field values printed here, as opposed toinputs being printed there.
So, I wonder, why isn't there a reusable generic template for DetailView's? Is there a technical limitation for this, or is it just not as reusable as I imagine?
I have created and have been using gladly for about a year now my own generic templates. So, I wanted to share, here it is:
Creating a view is as simple as this:
class PersonDetail(DetailViewParent):
model=Person
DetailViewParent used above (override fields and exclude as needed; default is to include all):
class DetailViewParent(DetailView):
fields=[]
exclude=[]
template_name='common/modal_detail.html'
def get_context_data(self, **kwargs):
context=super(DetailViewParent, self).get_context_data(**kwargs)
context['exclude']=self.exclude
context['fields']=self.fields
return context
Relevant part of the template:
{% fields %}
{% for name, label, value, is_link in fields %}
<tr>
<td><strong>{{ label|capfirst }}</strong></td>
<td>
{% if value.get_absolute_url and request.is_ajax %}
<a class="modal-loader" href="{{ value.get_absolute_url }}">{{ value }}</a>
{% elif value.get_absolute_url %}
{{ value }}
{% else %}
{% if is_link and request.is_ajax %}
<a class="modal-loader" href="{{ value }}">{{ value }}</a>
{% elif is_link %}
{{ value }}
{% else %}
{{ value }}
{% endif %}
{% endif %}
</td>
</tr>
{% endfor %}
And the template tags:
#register.tag(name="fields")
def generate_fields(parser, token):
"""
{% fields %} - loads field name, label, value, is_link to the context
"""
args=token.contents.split()
object_name='object'
if len(args) == 2:
object_name=args[1]
return FieldsNode(object_name)
class FieldsNode(template.Node):
"""
called by generate_fields above
"""
def __init__(self, object_name):
self.object_name=object_name
def render(self, context):
# Get the data necessary for rendering the thing, and add it to the context.
try:
obj=template.Variable(self.object_name).resolve(context)
except template.VariableDoesNotExist:
return ''
include_fields=context.get("fields", None)
exclude_fields=context.get("exclude", None)
fields=[]
for field in obj._meta.fields:
name=field.name
if exclude_fields and name in exclude_fields:
continue
if include_fields and name not in include_fields:
continue
label=field.verbose_name
value=getattr(obj, field.name)
is_link=(type(field).__name__ in ('URLField',))
if isinstance(value, bool):
value=get_bool_check_mark(value)
elif value is None:
value=''
fields.append((
name, label, value, is_link,
))
# If include_fields was defined, then sort by the order.
if include_fields:
fields=sorted(fields, key=lambda field_: include_fields.index(field_[0]))
context['fields']=fields
return ''
The template might be customized to your needs and liking. But I would like to note two things:
1) get_absolute_url: if this (standard django) model method is defined, the field value is shown as url.
2) modal-loader class: this triggers js on the client side to show the detail view in a bootstrap 3 modal. Furthermore, if clicked on a link as mentioned in 1) that is loaded onto the same modal, thus making it easier to browse detail views. It has also a "back" button to go back to the previous model's view. I am not including that here because it is a lot of code, and beyond the scope of this question.
I think it is not as reusable as you imagine.
It might conceivably be possible to define "standard" ways to render simple model properties like CharField - this quickly becomes impossible when you get into more complex relational fields like ManyToManyField, ForeignKey, OneToOneField. You would end up overriding any default representation very quickly for anything but the simplest of models.
Secondly Django is not - and should not be - opinionated about what your models are for, and therefore it makes sense that it doesn't try to assume how you want to render them.
This is different from forms where the structure of individual form fields is defined in Django and in HTML, and there is a strong correlation between the two.

Access form field attributes in templated Django

I've been doing some custom forms with django but I don't get how to access attributes that a specific form field has attached via the forms.py.
def putErrorInTitle (cls):
init = cls.__init__
def __init__ (self, *args, **kwargs):
init(self, *args, **kwargs)
if self.errors:
for field_error in self.errors:
self.fields[field_error].widget.attrs['title'] = self.errors[field_error][0]
self.fields[field_error].widget.attrs['class'] = "help_text error_field"
cls.__init__ = __init__
return cls
That's how I attached the attibutes to the field.
<dl class="clearfix two">
<dd>
<label for="id_diagnosis">Diagnostico:</label>
<select class="{{form.id_diagnosis.class}}" id="id_equipment_activity-{{ forloop.counter0 }}-id_diagnosis" name="equipment_activity-{{ forloop.counter0 }}-id_diagnosis">
{% for x,y in form.fields.id_diagnosis.choices %}
<option value="{{ x }}" {% ifequal form.id_diagnosis.data|floatformat x|floatformat %}selected="selected"{% endifequal %}>{{ y }}</option>
{% endfor %}
<option value="1000" {% ifequal form.id_diagnosis.data|floatformat '1000'|floatformat %}selected="selected"{% endifequal %}>Otro</option>
</select>
</dd>
<dd class="vertical_center" id="optional_diagnosis"><label for="optional_diagnosis">Diagnostico opcional:</label>{{ form.optional_diagnosis }}</dd>
</dl>
I've been trying to access its attributes:
class="{{form.id_diagnosis.class}}", class="{{form.id_diagnosis.widget.class}}"
And I don't seem to find clear documentation about what's accessible and what's not. Really I would rather have old fashion documentation than django "friendly" one
In other cases it can can be useful to set and get field attributes.
Setting in form's init function:
self.fields['some_field'].widget.attrs['readonly'] = True
... and accessing it in a template:
{{ form.some_field.field.widget.attrs.readonly }}
It looks like you just want to display form errors for each field.
After the form is cleaned or validated in the view, the fields should contain
the error messages. So that you can display them in the template like so:
<form action='.' method='post'>
...
<div class='a-field'>
{{ form.field_1.errors|join:", " }}
{{ form.field_1.label_tag }}
{{ form.field_1 }}
</div>
...
</form>
If however you really want to display the form field attributes then you
can try something like:
{{ form.field_1.field.widget.attrs.maxlength }}
The above answers are correct, however, I'd like to add a note for those who are accessing form fields in a loop.
If you're doing this in a loop like this
{% for field in form %}
{{ field.field.widget.attrs.placeholder }} # field.field is the key here
{% endfor %}