django input element error css class - python

I'd like to know how can I add .error class to input elements (to registration app) when the form validation fails.

If you want to place your error CSS class to form input widgets (not their containers), you can derive your form class from the following one:
class StyledErrorForm(forms.Form):
def is_valid(self):
result = super().is_valid()
# loop on *all* fields if key '__all__' found else only on errors:
for x in (self.fields if '__all__' in self.errors else self.errors):
attrs = self.fields[x].widget.attrs
attrs.update({'class': attrs.get('class', '') + ' is-invalid'})
return result

It's now easy -- new feature in Django 1.2
Just add an attribute on the form class & you're good to go. This feature is mentioned in the docs under a "new in 1.2" note, but you can find the magic at django.forms.forms.BoundField.css_classes Here's the API reference, and an example:
class MyForm(forms.Form):
required_css_class = "required"
error_css_class = "error"

This can be done completely through your template.
You build the form template for each form field that you want to test you can use the following example construct
<input type="text" class="reg-txt{% if form.fieldname.errors %} errors{% endif %}"/>
This lets you provide the interface you want without modifying the view & django form code.

Using a Custom Template...
Personally never had much luck using the built in Django error classing solutions, and besides, I like to use the built in 'striptags' template filter on the errors, to get rid of all the html list stuff which I cant figure out how to render nicely anyway.
I use the following custom template to class them as 'error_id'.
#register.filter(is_safe=True)
#stringfilter
def error_id(value):
if value=='':
return ''
else:
return r'<span class="error_id">'+value+'</span>'
Render the individual errors in your template using:
{{ form.my_field.errors|striptags|error_id}}
Or render the whole form using something like:
<table border="1" cellpadding="5px" align="center">
{% for field in form.visible_fields %}
<tr>
<td> {{ field.label_tag }}: </td>
<td> {{ field }} </td>
<td> {{ field.errors|striptags|error_id }} </td>
</tr>
{% endfor %}
</table>

(Better late than never)
You should be able to do this with Django Uni Form

Related

Blending the rendering of DataTableView and a standard view in Django

I'm pretty new to django and have been experimenting with some of the code
I want to build a form that starts with a parent record, lists the children of that record, and then when I click on a child (or a button in the row of that child) shows the children under that in a datatableview object. phew It should look a little like this:
So the dataset is the primary object into the view, and the tables are a datatables filtered by the dataset id, which all works fine... but how do I get the {{ datatable }} to render in context?
The current view code is pretty basic - this is initially all just for display.
def datasetview(request, datasetid):
dataset = get_object_or_404(DataSet, pk=datasetid)
context = {
'dataset': dataset,
}
return render(request, 'data/dataset_view.html', context)
within the html template, I render the table list with:
{% for datatable in dataset.datatables.all %}
{% if not datatable.deleted %}
<tr>
<td class="p-1 align-middle">{{ datatable.tablename }}</td>
<td class="p-1 align-middle"><button type="button" class="btn btn-outline-primary" onclick="fill_attribute_table({{ datatable.datatableid }})">Edit</button></td>
</tr>
{% endif %}
{% endfor %}
I've been able to render the dataviewtable as a generic page using the demo code provided at pypi.org/project/django-datatable-view (that's how I produced the hacky screen image above) but have no idea how to blend the results here together or pass the datatableid that I can easily attach to the row of tables (the edit button currently throws up an alert with the relevant id...

Saving class-based view formset items with a new "virtual" column

I have a table inside a form, generated by a formset.
In this case, my problem is to save all the items after one of them is modified, adding a new "virtual" column as the sum of other two (that is only generated when displaying the table, not saved).
I tried different ways, but no one is working.
Issues:
This save is not working at all. It worked when it was only one form, but not for the formset
I tried to generate the column amount as a Sum of box_one and box_two without success. I tried generating the form this way too, but this is not working:
formset = modelformset_factory(
Item, form=ItemForm)(queryset=Item.objects.order_by(
'code__name').annotate(amount=Sum('box_one') + Sum('box_two')))
This issue is related to this previous one, but this new one is simpler:
Pre-populate HTML form table from database using Django
Previous related issues at StackOverflow are very old and not working for me.
I'm using Django 2.0.2
Any help would be appreciated. Thanks in advance.
Current code:
models.py
class Code(models.Model):
name = models.CharField(max_length=6)
description = models.CharField(max_length=100)
def __str__(self):
return self.name
class Item(models.Model):
code = models.ForeignKey(Code, on_delete=models.DO_NOTHING)
box_one = models.IntegerField(default=0)
box_two = models.IntegerField(default=0)
class Meta:
ordering = ["code"]
views.py
class ItemForm(ModelForm):
description = CharField()
class Meta:
model = Item
fields = ['code', 'box_one', 'box_two']
def save(self, commit=True):
item = super(ItemForm, self).save(commit=commit)
item.box_one = self.cleaned_data['box_one']
item.box_two = self.cleaned_data['box_two']
item.code.save()
def get_initial_for_field(self, field, field_name):
if field_name == 'description' and hasattr(self.instance, 'code'):
return self.instance.code.description
else:
return super(ItemForm, self).get_initial_for_field(
field, field_name)
class ItemListView(ListView):
model = Item
def get_context_data(self, **kwargs):
data = super(ItemListView, self).get_context_data()
formset = modelformset_factory(Item, form=ItemForm)()
data['formset'] = formset
return data
urls.py
app_name = 'inventory'
urlpatterns = [
path('', views.ItemListView.as_view(), name='index'),
item_list.html
...
<div>
<form action="" method="post"></form>
<table>
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<thead>
<tr>
{% if forloop.first %}
<th>{{ form.code.label_tag }} </th>
<th>{{ form.description.label_tag }} </th>
<th> <label>Amount:</label> </th>
<th>{{ form.box_one.label_tag }} </th>
<th>{{ form.box_two.label_tag }} </th>
{% endif %}
</tr>
</thead>
<tbody>
<tr>
<td>{{ form.code }}</td>
<td>{{ form.description }}</td>
<td>{{ form.amount }}</td>
<td>{{ form.box_one }}</td>
<td>{{ form.box_two }}</td>
</tr>
</tbody>
{% endfor %}
<input type="submit" value="Update" />
</table>
</form>
</div>
...
Annotating query with virtual column
Sum is an aggregate expression and is not how you want to be annotating this query in this case. Instead, you should use an F exrepssion to add the value of two numeric fields
qs.annotate(virtual_col=F('field_one') + F('field_two'))
So your corrected queryset would be
Item.objects.order_by('code__name').annotate(amount=F('box_one') + F('box_two'))
The answer provided by cezar works great if intend to use the property only for 'row-level' operations. However, if you intend to make a query based on amount, you need to annotate the query.
Saving the formset
You have not provided a post method in your view class. You'll need to provide one yourself since you're not inheriting from a generic view that provides one for you. See the docs on Handling forms with class-based views. You should also consider inheriting from a generic view that handles forms. For example ListView does not implement a post method, but FormView does.
Note that your template is also not rendering form errors. Since you're rendering the formset manually, you should consider adding the field errors (e.g. {{ form.field.errors}}) so problems with validation will be presented in the HTML. See the docs on rendering fields manually.
Additionally, you can log/print the errors in your post method. For example:
def post(self, request, *args, **kwargs):
formset = MyFormSet(request.POST)
if formset.is_valid():
formset.save()
return SomeResponse
else:
print(formset.errors)
return super().post(request, *args, **kwargs)
Then if the form does not validate you should see the errors in your console/logs.
You're already on the right path. So you say you need a virtual column. You could define a virtual property in your model class, which won't be stored in the database table, nevertheless it will be accessible as any other property of the model class.
This is the code you should add to your model class Item:
class Item(models.Model):
# existing code
#property
def amount(self):
return self.box_one + self.box_one
Now you could do something like:
item = Item.objects.get(pk=1)
print(item.box_one) # return for example 1
print(item.box_two) # return for example 2
print(item.amount) # it will return 3 (1 + 2 = 3)
EDIT:
Through the ModelForm we have access to the model instance and thus to all of its properties. When rendering a model form in a template we can access the properties like this:
{{ form.instance.amount }}
The idea behind the virtual property amount is to place the business logic in the model and follow the approach fat models - thin controllers. The amount as sum of box_one and box_two can be thus reused in different places without code duplication.

Django - accessing modelform field value in template

I have a form with multiple fields, one of which I would like to be able to replace the field's HTML control with just the plain text of the field's value. What do I do in the template, in models.py, or in my forms.py to do this? I have nothing in my forms.py related to that field yet, just other fields.
#models.py
class RoomEvent(models.Model):
room = models.ForeignKey('Room')
description = models.CharField(max_length=250)
-
#template
<form method='POST' action=''>
<table>
<tr>
<td><label for="id_room">Room:</label></td>
<td>{{ form.room.room.name }}</td>
</tr>
...
I have tried this:
#fields.py:
class RoomEventForm(ModelForm):
room = forms.ModelChoiceField(queryset=Room.objects.select_related())
...then replacing {{form.room}} with {{form.room.room.name}} in the template, to no avail. Just gives me a blank.
{{ form.room.name }} gives me "room"..?
{{ form.room.value }} gives me the ID, not the actual name.
Why is this so difficult???
I wound up using {{ roomevent.room.name }} where roomevent is the ModelForm's model. I guess that's the "generally accepted" way of doing this.

context variable blank when passed to template

The context variable is empty when displayed in the template. I have tested the query in the Django Shell and the query is returning the data in the Shell.
views.py (https://github.com/josieh/playground-finder/blob/master/playgroundApp/views.py#L11-L26)
def Playground_List(request):
context = {
'playgrounds': Playground.object.all()
}
return render (request, "home.html", context)
Template home.html (https://github.com/josieh/playground-finder/blob/master/playgroundApp/templates/playgroundApp/home.html#L48-L54)
{% for playground in playgrounds %}
{# Each "player" is a player model object. #}
<tr>
<td> {{ playground.Name }}</td>
<td> {{ playground.Address }}</td>
</tr>
{% endfor %}
Can you please help explain why the context variable of playgrounds would be empty?
Thanks for your help!
One problem is that it should be Playground.objects.all(). I imagine that's likely a transcription error in the question text, because that should throw an AttributeError if it was actually what you wrote in your program.
Are you certain your attributes of the Playground model are called Name and Address as opposed to name and address? It is against Python style convention to capitalize the names of attributes. If you have mistakenly capitalized these names then this would indeed cause your output to appear blank.
Injecting print statements/functions (depending on Python 2 or 3) into your view to print the context and/or the QuerySet might help you debug the issue.

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

Categories