Using HTML in Crispy Forms formset - django

I have the following FormHelper that I'm using to render a formset in Django Crispy Forms. My understanding of the documentation indicates that I should end up with a formset with two HTML fields (name, user name) and two input fields, but this doesn't seem to be the case. In fact, I only have the two form fields.
I'm unclear as to how I would go about adding HTML to a formset, given that the code below doesn't seem to do the trick.
class ProposalFormSetHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(ProposalFormSetHelper, self).__init__(*args, **kwargs)
self.form_method = 'post'
self.layout = Layout(
HTML('{{ form.instance.proposal.name }}'),
HTML('{{ form.instance.user.get_full_name }}'),
Field('accepted', css_class='input-mini'),
Field('rating', css_class='input-mini')
)
self.template = 'bootstrap/table_inline_formset.html'
self.add_input(Submit('submit', 'Update'))
I should note that I've also tried to, alternatively, keep the FormHelper in the form itself and attach it to the {% crispy %} tag by doing: {% crispy formset formset.form.helper %}, but this had pretty much the same affect.

I'm the lead developer of crispy-forms
The problem here is that you are using bootstrap/table_inline_formset.html which currently doesn't support specifying a layout.
It's stated clearly in the docs: http://django-crispy-forms.readthedocs.org/en/latest/crispy_tag_formsets.html#custom-templates-and-table-inline-formsets
This template doesn’t currently take into account any layout you have specified and only works with bootstrap template pack.
I'm aware this is not the expected behavior and there is an open issue about it. I'm hopefully working this week on solving it somehow.

Related

Django 1.11 CreateView adding datepicker for datefields

So I am trying to change my form's model Datefield output to the Datepicker similar to DatepickerWidget in CreateView
The forms are generated using a html template:
{% for field in form %}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<span class="text-danger small">{{ field.error }}</span>
</div>
<label class="control-label col-sm-2">{{field.label_tag}}</label>
<div class="col-sm-10">{{field}}</div>
</div>
{% endfor %}
Here is the Views with what I tried:
class newenv_form(generic.CreateView):
model = Environment
fields =['name', 'description', 'creation_date', 'status','status_update_date']
template_name = 'catalogue/new_env.html'
#Does not work
def get_form(self, form):
form = super(newenv_form, self)
form.fields['creation_date','status_update_date'].widget = forms.DateInput(attrs={'class':'datepicker'})
return form
Here is what worked but it is a dropdown datepicker that is limited in choices
def get_form(self):
'''add date picker in forms'''
from django.forms.extras.widgets import SelectDateWidget
form = super(EnvironmentCreateView, self).get_form()
form.fields['creation_date'].widget = SelectDateWidget()
return form
Note that I remove form_class which was causing problems
UPDATE: On Django 3.1, you can find SelectDateWidget within django.forms.widgets
Try to change the following line in the method get_form:
form = super(newenv_form, self)
to:
form = super(newenv_form, self).get_form(form)
And please follow the conventions and use PascalCase for class names in python.
You could call this class EnvironmentCreateView. Further generic view classes could be called for example EnvironmentListView, EnvironmentDetailView, EnvironmentUpdateView, EnvironmentDeleteView.
Using the same pattern for all your model classes will produce comprehensible code.
EDIT (2017-10-24):
Regarding your comment here is a further explanation. Although it is hard to give a correct remote diagnosis, I'd suggest the following changes:
class EnvironmentCreateView(generic.CreateView):
# class attributes ...
def get_form(self, form_class=None):
form = super(EnvironmentCreateView, self).get_form(form_class)
# further code ...
The essential changes are in bold. The class name is changed to meet the conventions. Also the parameter form is changed to form_class to meet the convetions, too. I emphasise conventions in particular, because it makes the code very comprehensible to other people familiar with the framework.
The important change is that form_class has the initial value None.
That should solve the problem with the error.
In the body of the method you call the parent method with super and write after that your custom code.
Please check the documentation for generic.CreateView. It inherits, among others, from generic.FormMixin. That is the class with the method get_form.

Formatting django form.non_field_errors in a template

How do I format django form.non_field_errors.as_text in a template without them being either an unordered list or having an * appended to the front?
{{ form.non_field_errors.as_text }} displays the errors with an * in front of the text.
This django ticket was also helpful in explaining why the * will not be removed, but that doesn't help me. I do not want the *.
Both {{ form.non_field_errors }} and {{ form.non_field_errors.as_ul }} display as an unordered list, and I do not want an unordered list.
{% for error in form.non_field_errors %}
{{error}}
{% endfor %}
When you call the list of errors as text it will try to display it as a list. Simply loop through the list to get the error by itself so you can apply your own styling.
More info about it on the django project website
Well, by default Django forms use ErrorList as error_class (proof link). As you can see, its as_text method formats list by prepending asterisks to values.
So, you can create some custom error_class with own as_text method and pass it to your form in a suitable manner.
Iterating through form.non_field_errors is not always the best approach, for instance in my case I wanted to display a tooltip like in this screenshot next to the field in question using Django Widget Tweaks.
project/app/templates/template.html
{% render_field field class="form-control is-invalid" data-toggle="tooltip" title=field.errors.as_text %}
Instead of messing around with template code to pass the HTML title attribute in one piece I followed the tip from #oblalex and wrote my own modified ErrorList class with a as_text method without asterisks.
project/app/utils.py
from django.forms.utils import ErrorList
class MyErrorList(ErrorList):
"""
Modified version of the original Django ErrorList class.
ErrorList.as_text() does not print asterisks anymore.
"""
def as_text(self):
return '\n'.join(self)
Now that the as_text() function is overwritten, you can pass your class MyErrorList as the error_class argument of your Form or ModelForm or like in my case some ModelForm formset:
project/app/views.py
from django.forms import formset_factory
from .forms import InputForm
from .utils import MyErrorList
def yourView(request):
InputFormSet = formset_factory(InputForm)
formset = InputFormSet(error_class=MyErrorList)
context = {'formset': formset}
return render(request, 'template.html', context)
And now the tooltip looks like this without the asterisk.
Instead of passing the error_class every time you instantiate a form in your views, you can just add this one-liner to your form class (see this answer from #Daniel):
project/app/forms.py
from django import forms
from .utils import MyErrorList
class InputForm(forms.ModelForm):
def __init__(self, *args, row, **kwargs):
super(InputForm, self).__init__(*args, **kwargs)
self.error_class = MyErrorList

Conditional field in form

I need to make a Form class that may or not have a ReCaptcha field depending on whether the user is logged in or not.
Because this is a CommentForm, I have no access to the request object on form creation/definition, so I can't rely on that.
For the POST request the solution is easy: I've got this:
class ReCaptchaCommentForm(CommentForm):
def __init__(self, data=None, *args, **kwargs):
super(ReCaptchaCommentForm, self).__init__(data, *args, **kwargs)
if data and 'recaptcha_challenge_field' in data:
self.fields['captcha'] = ReCaptchaField()
Having done this, form validation should work as intended. The problem now is on the template side. I need the template to be like this:
<form action={% comment_form_target %} method="post">
{# usual form stuff #}
{% if not user.is_authenticated %}
<script type="text/javascript"
src="http://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>
<div id="recaptcha-div"></div>
<script type="text/javascript">
Recaptcha.create({{ public_key }}, "recaptcha-div",
{ theme: 'white',
callback: Recaptcha.focus_response_field });
</script>
{% endif %}
</form>
But I'd like not to have to repeat that code on every comments/*/form.html template. I gather there should be some way of adding equivalent code from a widget's render method and Media definition.
Can anyone think of a nice way to do this?
I assume that you instatiate your form in a view, so you could just pass the user from request to the form (just like in auth app SetPassword form):
def __init__(self, user, data=None, *args, **kwargs):
super(ReCaptchaCommentForm, self).__init__(data, *args, **kwargs)
if user.is_authenticated():
self.fields['captcha'] = ReCaptchaField()
Use crispy-forms!
You can include html elements in the form layout that would allow you to exclude/include a field based on the views request context. Extremely useful features outside of that as well.
Here's the relevant doc section.
What I am doing about conditional fields is having a base class (which inherits from Form) and other subclasses with the extra conditional fields.
Then in my view, depending on the condition I choose the required subclassed form. I know that it involves some duplicated code, but it seems easier than other approaches.
Well, it's unfortunate that django-floppyforms doesn't give access to the request. It would have been nice to know it was an option, as I've recently started using django-floppyforms in my own project.
Short of that, the best thing I can think of is to simply rely on template inheritance. You can create a comments/form.html file and then have each comments/*/form.html extend that. Put the Recaptcha code as you have it in the base form.html and you're good to go.

Django: How to check if there are field errors from within custom widget definition?

I'd like to create widgets that add specific classes to element markup when the associated field has errors.
I'm having a hard time finding information on how to check whether a field has errors associated with it, from within widget definition code.
At the moment I have the following stub widget code (the final widget will use more complex markup).
from django import forms
from django.utils.safestring import mark_safe
class CustomTextWidget(forms.Widget):
def render(self, name, value, attrs):
field_has_errors=False # change to dynamically reflect field errors, somehow
if field_has_errors:
error_class_string="error"
else:
error_class_string=""
return mark_safe(
"<input type=\"text\" class=\"%s\" value=\"%s\" id=\"id_%s\" name=\"%s\">" % (error_class_string, value, name, name)
)
Can anyone shed light on a sensible way to populate the field_has_errors Boolean here? (or perhaps suggest a better way to accomplish what I'm trying to do). Thanks in advance.
As Jason says, the widget has no access to the field itself. I think a better solution though is to use the cascading nature of CSS.
{% for field in form %}
<div class="field{% if field.errors %} field_error{% endif %}">
{{ field }}
</div>
{% endfor %}
Now in your CSS you can do:
div.field_error input { color: red }
or whatever you need.
The widget has no knowledge of the field to which it is being applied. It is the field that maintains information about errors. You can check for error_messages in the init method of your form, and inject an error class to your widget accordingly:
class YourForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(YourForm, self).__init__(*args, **kwargs)
attrs = {}
if self.fields['your_field'].error_messages is not None:
attrs['class'] = 'errors'
self.fields['your_field'].widget = YourWidget(attrs=attrs)

Django and fieldsets on ModelForm

I know you can specify fieldsets in django for Admin helpers. However, I cannot find anything useful for ModelForms. Just some patches which I cannot use. Am I missing something? Is there a way I could achieve something like fieldsets without manually writing out each field on my template in the appropriate tag.
I would ideally like to iterate through a set of BoundFields. However, doing something like this at the end of my ModelForm:
fieldsets = []
fieldsets.append(('Personal Information',
[username,password,password2,first_name,last_name,email]),) # add a 2 element tuple of string and list of fields
fieldsets.append(('Terms & Conditions',
[acceptterms,acceptprivacy]),) # add a 2 element tuple of string and list of fields
fails as the items contained in my data structure are the raw fields, not the BoundFields. t looks like BoundFields are generated on the fly... this makes me sad. Could I create my own subclass of forms.Form which contains a concept of fieldsets (even a rough one that is not backward compatible... this is just for my own project) and if so, can you give any pointer? I do not want to mess with the django code.
I think this snippet does exactly what you want. It gives you a Form subclass that allows you to declaratively subdivide your form into fieldsets and iterate through them in your template.
Update: that snippet has since become part of django-form-utils
Fieldsets in modelforms are still in "design" stage. There's a ticket in Django trac with low activity.
It's something I've been interested in researching myself in the near future, but since I haven't done it yet the best I can offer are these snippets:
Form splitting/Fieldset templatetag
Sectioned Form
Forms splitted in fieldsets
Edit: I just noticed this question again and I realize it needs an edit to point out Carl's project django-form-utils which contains a BetterForm class which can contain fieldsets. If you like this project give him a +1 for his answer below :)
One thing you can do is break your logical fieldsets into separate model form classes.
class PersonalInfoForm (forms.ModelForm):
class Meta:
model=MyModel
fields=('field1', 'field2', ...)
class TermsForm (forms.ModelForm):
class Meta:
model=MyModel
fields=('fieldX', 'fieldY', ...)
Pass them to your template in different variables and break up the formsets:
<form ...>
<fieldset><legend>Personal Information</legend>
{{ personal_info_form }}
</fieldset>
<fieldset><legend>Terms and Conditions</legend>
{{ terms_form }}
</fieldset>
</form>
In that sense each of your form classes is just a fragment of the actual HTML form.
It introduces a touch of complexity when you call save on the form. You'll probably want to pass commit=False and then merge the resultant objects. Or just avoid using ModelForm.save altogether and populate your model object by hand with 'cleaned_data'
Daniel Greenfelds django-uni-form solves this with a the Layout helper class. I'm trying it out right now and it looks pretty clean to me.
Uniform helpers can use layout objects. A layout can consist of fieldsets, rows, columns, HTML and fields.
I originally picked Django-uni-form because it complies with section 508.
You can use this package: https://pypi.org/project/django-forms-fieldset/
pip install django-forms-fieldset
Add forms_fieldset to your INSTALLED_APPS setting like this:
INSTALLED_APPS = [
...
'forms_fieldset',
]
Add fieldsets in your form
from django.forms import ModelForm
from .models import Student
class StudentForm(ModelForm):
fieldsets = [
("Student Information", {'fields': [
('first_name', 'last_name'),
('email', 'adress'),
]}),
("Parent Information", {'fields': [
'mother_name',
'father_name',
]}),
]
class Meta:
model = Student
fields = '__all__'
In your views
def home(request):
form = StudentForm()
if request.method == 'POST':
form = Form(request.POST, request.FILES)
#save...
context = {
'form': form,
}
return render(request, 'home.html', context)
in your template
{% load forms_fieldset static %}
<link rel="stylesheet" type="text/css" href="{% static 'forms_fieldset/css/main.css' %}">
<form>
{{ form|fieldset:'#42945c' }}
</form>
This was the code that I developed in order to understand custom tags (with links). I applied it to create a fieldset.
Disclaimer: I encourage the use of any of the above answers, this was just for the sake of learning.
templatetags/myextras.py:
from django import template
from django.template import Context
register = template.Library()
class FieldsetNode(template.Node):
""" Fieldset renderer for 'fieldset' tag """
def __init__(self, nodelist, fieldset_name):
""" Initialize renderer class
https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#writing-the-renderer
:param nodelist: a list of the template nodes inside a block of 'fieldset'
:param fieldset_name: the name of the fieldset
:return: None
"""
self.nodelist = nodelist
self.fieldset_name = fieldset_name
def render(self, context):
""" Render the inside of a fieldset block based on template file
https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#auto-escaping-considerations
:param context: the previous template context
:return: HTML string
"""
t = context.template.engine.get_template('myapp/fieldset.html')
return t.render(Context({
'var': self.nodelist.render(context),
'name': self.fieldset_name,
}, autoescape=context.autoescape))
#register.tag
def fieldset(parser, token):
""" Compilation function for fieldset block tag
Render a form fieldset
https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#writing-the-compilation-function
https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#parsing-until-another-block-tag
:param parser: template parser
:param token: tag name and variables
:return: HTML string
"""
try:
tag_name, fieldset_name = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError("%r tag requires a single argument" % token.contents.split()[0])
if not (fieldset_name[0] == fieldset_name[-1] and fieldset_name[0] in ('"', "'")):
raise template.TemplateSyntaxError("%r tag's argument should be in quotes" % tag_name)
nodelist = parser.parse(('endfieldset',))
parser.delete_first_token()
return FieldsetNode(nodelist, fieldset_name[1:-1])
templates/myapp/fieldset.html:
<div class="fieldset panel panel-default">
<div class="panel-heading">{{ name }}</div>
<div class="panel-body">{{ var }}</div>
</div>
templates/myapp/myform.html:
<form action="{% url 'myapp:myurl' %}" method="post">
{% csrf_token %}
{% fieldset 'General' %}
{{form.myfield1 }}
{% endfieldset %}
{# my submit button #}
</form>