Render form errors with the label rather than field name - python

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

Related

How to update multiple objects at once in Django?

As the image presents I have to update multiple objects at once, e.g. to update the status of each object. If the status is active, the object will display on another page and vice versa. The goal is to change the status of one object (or all) with one button.
At the moment if I click on 'Update_all' I got only one value.
Django's Admin Page to take action on multiple objects would be a nice solution, but I have no idea, how this template is constructed although I considered the html code of the template.
Another try I attempted - similiar to the image above - was this one:
My template
<div class="container">
{% if forms.items %}
{% for key,value in forms.items %}
<form class="custom-form-manage-habis" method="post" action="">
{% csrf_token %}
<div class="container custom-div-manage-habits">
{{key}}
 
{{value}}
</div>
<hr>
{% endfor %}
{% else %}
<p>There are no habits to manage.</p>
{% endif %}
<input type="submit" value="Apply changes" {% if not forms.items
%}style="display:none"{% endif %} ></input>
</form>
</div>
...and in my view:
def post(self, request):
print('postFuction')
print(request.POST)
form_collection = {}
habit_update_list = request.POST.getlist('is_active')
print(habit_update_list)
habits = Habit.objects.filter(created_by=request.user.userprofile)
i = 0
for habit in habits:
print('I: ' + str(i))
form = HabitToManageForm(request.POST, instance=habit)
if form.is_valid():
habit = form.save(commit=False)
habit.is_active = habit_update_list[i]
print(habit)
habit.save()
else:
print('Error while check if form is valid')
i += 1
return redirect('manage_habits')
The problem here that I get indeed values of the objects, but unsorted and therefore it may be true that I get a value of object2, which will saved in object4.
So, is there a common practice or best way to handle this problem? Or may someone has any hints how I adopted the django admin template approach "select an object, then change it".
EDIT
Form:
class HabitToManageForm(forms.ModelForm):
class Meta:
model = Habit
fields = ('is_active',)

Iterate over Django custom Form

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.

simple loop in rendered template django cms plugin

I want to loop data taken from database in rendered template. Using cms plugin.
I dont have problem looping data in html template. But if i use CMSPlugin to insert new app in placeholder, nothing shows.
If i run url localhost:port/test.html.I got input what i want. But rendered template doesnt loop data.
{% for post in posts %}
{{ post.firstoption }}
{% endfor %}
if I use code below, nothing shows in my rendered template. Values are passed in rendered template. Because, if i try {{instance.firstoption}} i get value shown in template. Problem is i cant loop data with tag instance.
{% for post in instance.posts %}
{{ post.firstoption }}
{% endfor %}
I also tried {% for post in instance.posts_set.all %}
and {% for post in instance.posts.all %}
cms_plugins.py
class pricing(CMSPluginBase):
model = mymodel
name = _("myplugin")
render_template = "template.html"
cache = False
def render(self, context, instance, placeholder):
context.update({'instance': instance})
return context
models.py
class mymodel(CMSPlugin):
firstoption = models.CharField(max_length=200)
def __str__(self):
return self.firstoption
It is probably because you need to call all on your posts
{% for post in instance.posts.all %}
{{ post.firstoption }}
{% endfor }

Django Templates: Is there a way to query a specific object based on its property (instead of iterating over all)?

Let's say I have an object 'users' coming in as a context, which I know contains many user objects. Now, in my template, I want to access a specific user which I know has a specific property value (let's say id of 10), and then display another property associated with that user. I know that I can do:
{% for user in users %}
{% if user.id == 10 %}
{{ user.age }}
{% endif %}
{% endfor %}
I know that I could extract that user in my view and pass just that one object (?) but in this case it makes sense to have all of them.
I feel that there ought to be an easier way to do this but I'm a Django/templating newbie. Tried searching but wasn't sure how to phrase it..
this is really a job for template tags
templatetags/my_tags.py
...
#register.filter
def where_id(users,user_id):
return filter(lambda u:u.pk==user_id,users)
...
sometemplate.html
{%load my_tags %}
...
{% for user in users|where_id:10 %}
....
If you are working with unique fields, it might be better to use get, rather than filter. Here's a generic way to do that, inspired by Joran's answer and this answer.
my_template.html:
{%load my_tags %}
...
This is the user with pk=1: {{ users|get:"pk, 1" }}
....
templatetags/my_tags.py:
from django.template import Library
from django.core.exceptions import ObjectDoesNotExist
register = Library()
#register.filter
def get(models, argstring):
args = argstring.split(',')
if len(args) != 2:
raise ValueError("Exactly two arguments required, separated by comma")
field, value = args
try:
return models.get(**{field: value})
except ObjectDoesNotExist:
return None

Django Formsets - form.is_valid() is False preventing formset validation

I'm am utilizing a formset to enable users subscribe to multiple feeds. I require a) Users chose a subscription by selecting a boolean field, and are also required to tag the subscription and b) a user must subscribe to an specified number of subscriptions.
Currently the below code is capable of a) ensuring the users tags a subscription, however some of my forms is_valid() are False and thus preventing my validation of the full formset. [edit] Also, the relevant formset error message fails to display.
Below is the code:
from django import forms
from django.forms.formsets import BaseFormSet
from tagging.forms import TagField
from rss.feeder.models import Feed
class FeedForm(forms.Form):
subscribe = forms.BooleanField(required=False, initial=False)
tags = TagField(required=False, initial='')
def __init__(self, *args, **kwargs):
feed = kwargs.pop("feed")
super(FeedForm, self).__init__(*args, **kwargs)
self.title = feed.title
self.description = feed.description
def clean(self):
"""apply our custom validation rules"""
data = self.cleaned_data
feed = data.get("subscribe")
tags = data.get("tags")
tag_len = len(tags.split())
self._errors = {}
if feed == True and tag_len < 1:
raise forms.ValidationError("No tags specified for feed")
return data
class FeedFormSet(BaseFormSet):
def __init__(self, *args, **kwargs):
self.feeds = list(kwargs.pop("feeds"))
self.req_subs = 3 # TODO: convert to kwargs arguement
self.extra = len(self.feeds)
super(FeedFormSet, self).__init__(*args, **kwargs)
# WARNING! Using undocumented. see for details...
def _construct_form(self, i, **kwargs):
kwargs["feed"] = self.feeds[i]
return super(FeedFormSet, self)._construct_form(i, **kwargs)
def clean(self):
"""Checks that only a required number of Feed subscriptions are present"""
if any(self.errors):
# Do nothing, don't bother doing anything unless all the FeedForms are valid
return
total_subs = 0
for i in range(0, self.extra):
form = self.forms[i]
feed = form.cleaned_data
subs = feed.get("subscribe")
if subs == True:
total_subs += 1
if total_subs != self.req_subs:
raise forms.ValidationError("More subscriptions...") # TODO more informative
return form.cleaned_data
As requested, the view code:
from django.forms import formsets
from django.http import Http404
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from rss.feeder.forms import FeedForm
from rss.feeder.forms import FeedFormSet
from rss.feeder.models import Feed
FeedSet = formsets.formset_factory(FeedForm, FeedFormSet)
def feeds(request):
if request.method == "POST":
formset = create_feed_formset(request.POST)
if formset.is_valid():
# submit the results
return HttpResponseRedirect('/feeder/thanks/')
else:
formset = create_feed_formset()
return render_to_response('feeder/register_step_two.html', {'formset': formset})
def create_feed_formset(data=None):
"""Create and populate a feed formset"""
feeds = Feed.objects.order_by('id')
if not feeds:
# No feeds found, we should have created them
raise Http404('Invalid Step')
return FeedSet(data, feeds=feeds) # return the instance of the formset
Any help would be appreciated.
Ps. For full disclosure, this code is based on http://google.com/search?q=cache:rVtlfQ3QAjwJ:https://www.pointy-stick.com/blog/2009/01/23/advanced-formset-usage-django/+django+formset
[Solved] See solution below.
Solved. Below is a quick run through of the solution.
Reporting the error required manipulating and formating a special error message. In the source code for formsets I found the errors that apply to a whole form are known as non_form_errors and produced a custom error based on this. [note: I couldn't find any authoritive documentation on this, so someone might know a better way]. The code is below:
def append_non_form_error(self, message):
errors = super(FeedFormSet, self).non_form_errors()
errors.append(message)
raise forms.ValidationError(errors)
The formsets clean method also needed a few tweaks. Basically it checks the if the forms is bound (empty ones aren't, hence is_valid is false in the question) and if so accesses checks there subscribe value.
def clean(self):
"""Checks that only a required number of Feed subscriptions are present"""
count = 0
for form in self.forms:
if form.is_bound:
if form['subscribe'].data:
count += 1
if count > 0 and count != self.required:
self.append_non_form_error("not enough subs")
Some might wonder why I choose to access the value using the form['field_name'].data format. This allows us to retrieve the raw value and always get a count on subscriptions, allowing me to return all relevant messages for the entire formset, i.e. specific problems with individual forms and higher level problems (like number of subscriptions), meaning that the user won't have to resubmit the form over and over to work through the list of errors.
Finally, I was missing one crucial aspect of my template, the {{ formset.non_form_errors }} tag. Below is the updated template:
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<form action="." method="post">
{{ formset.management_form }}
{{ formset.non_form_errors }}
<ol>
{% for form in formset.forms %}
<li><p>{{ form.title }}</p>
<p>{{ form.description }}</p>
{{ form.as_p }}
</li>
{% endfor %}
</ol>
<input type="submit">
</form>
{% endblock %}
I made attempt to circumvent my problem...it is not a good solution, it's very much so a hack. It allows people to proceed if they subscribe to the required number of feeds (in the case below more than 1), however if less than the required number of feeds, it fails to show the error message raised.
def clean(self):
count = 0
for i in range(0, self.extra):
form = self.forms[i]
try:
if form.cleaned_data:
count += 1
except AttributeError:
pass
if count > 1:
raise forms.ValidationError('not enough subscriptions')
return form.cleaned_data
I do use {{ formset.management_form }} in my template so as far as I know the error should display. Below my template in case I'm misguided.
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<form action="." method="post">
{{ formset.management_form }}
<ol>
{% for form in formset.forms %}
{{ form.as_p }}
</li>
{% endfor %}
</ol>
<input type="submit">
</form>
{% endblock %}

Categories