I am trying to pass extra parameters (not provided by the Forms) in customize my template
In this case I am passing an icon font.
class AddForm(forms.Form):
def __init__(self, *args, **kwargs):
*passing some args*
self.fields[field_key] = forms.CharField(**field_args)
self.fields[field_key].icon = 'fa-font'
template.html
{% for field in form%}
{{field.icon}}
{{field}}
{% endfor %}
When I render the form to to the view, no icon is printed out.
How can I pass extra parameters to a form field?
Yeah sure bcs when you use constructor init in form you can change only values of your field like:
just_value = models.ModelChoiceField(
queryset=YourModel.objects.none())
And then you can pass only .queryest in init
self.fields['just_value'].queryset = YourModel.objects.filter(something_there)
Im not sure about icons but you can style your field by add widget attrs:
just_value = models.ModelChoiceField(
queryset=YourModel.objects.none(),
widget=attrs{'class':'form-class')
I would like to recommend you package 'django-widget-tweaks` and render every of your fieild with {% render_field %}
To pass extra parameters to a form field in Django, you can create a custom widget for that field and add the extra parameter to the widget
from django.forms.widgets import TextInput
class IconTextInput(TextInput):
def __init__(self, icon, *args, **kwargs):
self.icon = icon
super().__init__(*args, **kwargs)
Then Modify the __init__ method of your form to use the custom widget
class AddForm(forms.Form):
def __init__(self, *args, **kwargs):
# passing some args
field_args['widget'] = IconTextInput(icon='fa-font')
self.fields[field_key] = forms.CharField(**field_args)
Finally, modify the template to use the icon parameter:
{% for field in form %}
<i class="fa {{ field.field.widget.icon }}"></i>
{{ field }}
{% endfor %}
Related
I want to use bootstrap on django form. To do so I need to add .form-control class to each field. My workaround is:
def __init__(self, *args, **kwargs):
super(SomeForm, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].widget.attrs = {
'class': 'form-control'
}
{% for field in form %}
<div class="form-group">
{{ field.label_tag }}
{{ field }}
</div>
{% endfor %}
But I believe it's wrong to put CSS in python code (if I would want to use another CSS framework, I would have to change form class). How can I move the class to template? I would not like to use crispy not to be bound to bootstrap.
You can use a custom template tag for adding a class, see more information here
You will have to create a new module inside the templatetags directory:
myapp/
__init__.py
models.py
templatetags/
__init__.py
extra_filters.py <-- Name it as you prefer
views.py
forms.py
The content of your extra_filters.py module can be something like this:
from django import template
register = template.Library()
#register.filter(name='addclass')
def addclass(field, myclass):
return field.as_widget(attrs={"class": myclass})
Now in your template, load the new filter and use it as follows:
{% load extra_filters %}
{% for field in form %}
<div class="form-group">
{{ field.label_tag }}
{{ field|addclass:"form-control" }}
</div>
{% endfor %}
Add form-control css class to the widgets.
The form class is the access point of the input tags through widgets. By changing the widget attr from the constructor method in form.Form
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.visible_fields():
field.field.widget.attrs.update({'class': 'form-control'})
This is how my form looks:
class TestForm(forms.ModelForm):
class Meta:
model = Restaurant
fields = [
'title',
'content',
]
def clean(self, *args, **kwargs):
title = self.cleaned_data.get("title")
content = self.cleaned_data.get("content")
error_dict = {}
if len(title) < 3:
error_dict['title'] = ValidationError("testerror1")
if len(content) < 3:
error_dict['content'] = ValidationError('testerror2')
if error_dict:
raise ValidationError(error_dict)
If I try to submit the form with empty title and content it shows two error messages (testerror1, testerror2), they appear above each field label and looks like this:
<ul class="errorlist">
<li>test2</li>
</ul>
But I want to hide each of them if client click on input, so I tried with Jquery:
$("#my_form_id").find('input, textarea').click(function() {
$(this).closest('ul').hide();
})
Without success (it doesn't found any <ul> element.
My question is, is there a way to set different id for each error? So that I can manage each one separately.
You can put a unique identifier on each error message, but it is a bit fiddly and I'm not sure how 'safe' I would consider it.
What I think is a better alternative for what you want is iterating over the fields in the form, and rendering the field and the error messages one at a time. The end of this post describes how to do that. If what you really want is to put identifiers on the error messages without looping through the fields in the template... well, read on.
The Hard Way
In order to get more than a simple text message rendered for each error message (without breaking out the form in the template), you need to provide an alternative ErrorList class to the ModelForm. The ErrorList class is what performs the rendering of the errors in HTML, so by creating and using a subclass you can change what gets rendered - including adding a special code from the ValidationError itself.
from django.forms.utils import ErrorList
from django.utils.html import format_html, format_html_join
# This overrides the ErrorList class to provide the additional rendering
# features you want - in this example it only overrides it for the `ul` output
class ErrorListDerivative(ErrorList):
def as_ul(self):
if not self.data:
return ''
# Key part 1: the UL is now being rendered with a class for
# each of the errors which includes the error code from the
# ValidationError. You can then locate the UL by looking for that class.
return format_html(
'<ul class="{{}} {}">{{}}</ul>'.format(' '.join(('errorcode{}'.format(e.code) for e in self.data))),
self.error_class,
# Key Part 2: This adds the code from the validation error to the individual LIs
format_html_join('', '<li class="errorforcode{}">{}</li>', ((e.code, e.message) for e in self.data))
)
Now having created an ErrorList that renders things the way you want, it needs to be used by the TestForm.
class TestForm(forms.ModelForm):
# This __init__ is what makes the ModelForm use the custom ErrorList class you've created.
# The BaseForm from which ModelForm is derived (a few layers of inheritence deep) has an `error_class` argument to receive the class used to render errors. This just injects your custom class.
def __init__(self, *args, **kwargs):
kwargs_new = {'error_class': ErrorListDerivative}
kwargs_new.update(kwargs)
super().__init__(*args, **kwargs_new)
class Meta:
model = Restaurant
fields = [
'title',
'content',
]
Then, inside your TestForm clean function, you pass the additional code to the ValidationErrors
def clean(self, *args, **kwargs):
title = self.cleaned_data.get("title")
content = self.cleaned_data.get("content")
error_dict = {}
# Key Part 3: Here we're including the custom error code in the
# ValidationError, which will be rendered out
if len(title) < 3:
error_dict['title'] = ValidationError("testerror1", code='title')
if len(content) < 3:
error_dict['content'] = ValidationError('testerror2', code='content')
if error_dict:
# Must admit, not sure if adding a code here will do anything at all
raise ValidationError(error_dict, code='3')
Once you have done that, the HTML output should look something like:
<form id="my_form_id" method="post" novalidate="">
<label for="id_title">Title:</label>
<ul class="errorlist errorcodetitle">
<li class="errorforcodetitle">testerror1</li>
</ul><input type="text" name="title" value="ao" maxlength="100" required="" id="id_title">
<label for="id_content">Content:</label>
<ul class="errorlist errorcodecontent">
<li class="errorforcodecontent">testerror2</li>
</ul><input type="text" name="content" value="ao" maxlength="100" required="" id="id_content">
<button type="submit">Submit</button>
</form>
With the class now on those ULs, you can use the name of the field to locate the relevant UL and hide it.
$("#my_form_id").find('input, textarea').click(function(evt) {
$('.errorcode' + this.name).hide();
})
The Idiomatic Way
If you don't want to go down that rabbit hole, an alternative is to do something more like the example in the django docs for 'looping over the form's fields' (https://docs.djangoproject.com/en/3.0/topics/forms/#looping-over-the-form-s-fields)
It doesn't give you the custom classes (or ids, whatever you end up adding) on the error messages, but it is much more idiomatic.
Something like the following...
{% for field in form %}
<div class="fieldWrapper">
<div class="errorcode{{field.html_name}}">
{{ field.errors }}
</div>
{{ field.label_tag }} {{ field }}
{% if field.help_text %}
<p class="help">{{ field.help_text|safe }}</p>
{% endif %}
</div>
{% endfor %}
Then you can use the same jquery as was described above:
$("#my_form_id").find('input, textarea').click(function(evt) {
$('.errorcode' + this.name).hide();
})
class form(forms.ModelForm):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
u = User.objects.get(user=user)
super(form, self).__init__(*args, **kwargs)
self.fields['phone'].queryset = Phone.objects.filter(user=u)
class Meta:
model = MyModel
fields = ["name", "description", , "phone"]
This form pre-populates the field phones with phones that belong to the currently logged in user. I got the template to display using {{ form.as_p }}, but I dont know how to manually display the fields with the pre-populated phone names to be chosen, in an html template.
I tried the snippet below as part of the bigger form, but it did not work.
<select multiple="multiple" id="id_phone" name="phone" required>
{% for p in phone %}
<option value="{{ p.id }}">{{ p.name }}</option>
{% endfor %}
</select>
Also, how can I allow a user to choose multiple phones at once with this pre-population method. I tried to use ModelMultipleChoiceField but it did not work.
There are two things that you need in this situation
Set an initial value for the field
Set the field widget to be able to select multiple values
Luckily, Django already has a widget for that!
The following code will achieve what you want:
class form(forms.ModelForm):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
u = User.objects.get(user=user)
kwargs["initial"].update({
"phone": Phone.objects.filter(user=u)
})
super(form, self).__init__(*args, **kwargs)
class Meta:
model = MyModel
widgets = {
"phone": SelectMultiple
}
fields = ["name", "description", "phone"]
With these two pieces in place, the template becomes easy!
{{ form.phone.errors }} {# shows on page any errors during clean #}
{{ form.phone }} {# renders the widget #}
If you do not want to use the built in widget that Django provides, look into creating your own
For other ways to set initial values checkout this post
I've been struggling for nearly a day on a simple rendering of a form field. That would be great if you could help me on this one.
I'm using Flask-WTF, python 2.7.
I'm trying to render a SelectField using a custom ListWidget. The field basically must be rendered within a UL HTML tag, rather than a SELECT html tag, and this seems to be what I'm struggling with.
This is how my custom widget class is:
widget.py
class CustomListWidget(ListWidget):
def __init__(self, *args, **kwargs):
super(CustomListWidget, self).__init__(*args, **kwargs)
def __call__(self, field, **kwargs):
kwargs.setdefault('id', field.id)
html = ['<{} {}>'.format(self.html_tag, html_params(**kwargs))]
for subfield in field:
html.append('<li><span>{}</span></li>'.format(subfield()))
html.append('</{}>'.format(self.html_tag))
return HTMLString(''.join(html))
This is how my form looks, and the category field is the one I'm struggling with.
form.py
from widget import CustomListWidget
from wtforms import SelectField, SubmitField
widget = CustomListWidget(html_tag='ul')
class MyForm(Form):
category = SelectField('category', [DataRequired()], widget=widget, default='1', choices=[
('1', 'whatever'),
('2', 'whatever2')
])
submit = SubmitField('Search')
view.py
from form import MyForm
from flask import render_template
#app.route('/formtest', methods=['GET', 'POST'])
def formtest():
form = MyForm()
if request.method == 'GET':
render_template('form.html', form=form)
if form.validate_on_submit():
return redirect(url_for('whatever', data=-form.data))
return 'form not validated'
form.html
<div class="search-input with-dropdown">
<div class="dropdown">
{{ form.category(class='dropdown-content hide') }} }}
</div>
</div>
With this code I'm able to get the display expected, but no value are being passed from the field. Whichever value I select, after I submit, only the default value is there.
I've done something similar recently with a loop to iterate over all the choices.
Basically, I've created a SelectMultipleField copy to have my own html for it as we needed specific stuff.
As you can see in the snipped in bellow, I iterate over the field.iter_choices().
{%- if field.type in ['SelectMultipleField'] %}
<ul>
{% for key, value, checked in field.iter_choices() %}
<li>{{ value }}</li>
{% endfor %}
</ul>
{%- endif %}
You can read more about custom widgets in http://wtforms.simplecodes.com/docs/0.6/widgets.html
In Django admin , does anyone know how can i get the chosen values from FilteredSelectMultiple widget in, when the form is saved?
class ControllerForm(forms.ModelForm):
terminal = forms.ModelMultipleChoiceField(queryset=[])
def __init__(self, *args, **kwargs):
super(ControllerForm, self).__init__(*args, **kwargs)
self.fields['terminal'].widget = widgets.FilteredSelectMultiple('terminals', False)
self.fields['terminal'].help_text = "Select the terminals which are to be added to the group."
self.fields['terminal'].required = False
self.fields['terminal'].label = "Select terminal(s)"
self.fields['terminal'].choices = [(t.id, str(t)) for t in Terminal.objects.filter(associated=False)]
class Meta:
model = Controller
class ControllerAdmin(admin.ModelAdmin):
"""
Controller admin form customization.
"""
list_display = ('name', 'group',)
form = ControllerForm
admin.site.register(Controller, ControllerAdmin)
EDIT:
I think i can access the values in the save_model method. (https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_model)
I've reached a solution. Using the save_model method available in the ModelAdmin one can access the chosen objects in the form.cleaned_data dictionary.
def save_model(self, request, obj, form, change):
for terminal in form.cleaned_data['terminal']:
...
obj.save()
Checkout https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_model and https://docs.djangoproject.com/en/dev/topics/forms/?from=olddocs for more details on this method.
Thanks
forms.py
class SampleWidget(forms.Form):
date=forms.CharField(widget=AdminDateWidget,max_length=100)
users = forms.ModelMultipleChoiceField(queryset=User.objects.all(),widget=FilteredSelectMultiple(("Users"), False))
mytemple.html
<form action="." method="POST">
{{ form.as_p }}
{{ form.media }}
{% csrf_token %}
<p><input type="submit" value="Submit"></p>
</form>
The widget should post the correct values selected without issue if you have your templates and forms setup like so.
Refer to this one:
This is only example using filteredselectmultiplte widget
http://jayapal-d.blogspot.com/2009/08/reuse-django-admin-filteredselectmultip.html