Add class to form field Django ModelForm - django

I am trying to write a Bootstrap Form with Django ModelForm. I have read the Django Documentation Django Documentation about Forms, so I have this code:
<div class="form-group">
{{ form.subject.errors }}
<label for="{{ form.subject.id_for_label }}">Email subject:</label>
{{ form.subject }}</div>
The {{form.subject}} is rendered by Django, for example in CharField field model, as input tag,
<input type="text"....> etc.
I need add "form-control" class to every input in order to get Bootstrap input appearance (without third-party packages). I found this solution Django add class to form <input ..> field. Is there any way to add a class to every field by default without specifying it in every attribute of the class of Form class?
class ExampleForm(forms.Form):
name = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control'}))
email = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control'}))
address = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control'}))
country = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control'}))
and so on ..

If you can't use a third-party app and want to add a class (e.g., "form-control") to every field in a form in a DRY manner, you can do so in the form class __init__() method like so:
class ExampleForm(forms.Form):
# Your declared form fields here
...
def __init__(self, *args, **kwargs):
super(ExampleForm, self).__init__(*args, **kwargs)
for visible in self.visible_fields():
visible.field.widget.attrs['class'] = 'form-control'
You might need to handle checking for existing classes in attrs too, if for some reason you'll be adding classes both declaratively and within __init__(). The above code doesn't account for that case.
Worth mentioning:
You specified that you don't want to use third-party packages. However, I'll take one second to mention that one of the simplest ways of automatically making forms render in the style of Bootstrap is to use django-crispy-forms, like this:
# settings.py
CRISPY_TEMPLATE_PACK = 'bootstrap3'
# forms.py
from crispy_forms.helper import FormHelper
class ExampleForm(forms.Form):
# Your declared form fields here
...
helper = FormHelper()
# In your template, this renders the form Bootstrap-style:
{% load crispy_forms_tags %}
{% crispy form %}

you can add CSS classes in forms.py
subject = forms.CharField(label='subject',
max_length=100,
widget=forms.TextInput(
attrs={'class': "form-control"}))

Since it took me more hours, than I would like to (django newbie), to figure this out, I will place my outcome here aswell.
Setting widget to each field just to add one class over and over again is against programming rule of repeating and leads to many unneccessary rows. This especially happens when working with bootstrap forms.
Here is my (working) example for adding not only bootstrap classes:
forms.py
class CompanyForm(forms.Form):
name = forms.CharField(label='Jméno')
shortcut = forms.CharField(label='Zkratka')
webpage = forms.URLField(label='Webové stránky')
logo = forms.FileField(label='Logo')
templatetags/custom_tags.py
from django import template
from django.urls import reverse
register = template.Library()
#register.filter('input_type')
def input_type(ob):
'''
Extract form field type
:param ob: form field
:return: string of form field widget type
'''
return ob.field.widget.__class__.__name__
#register.filter(name='add_classes')
def add_classes(value, arg):
'''
Add provided classes to form field
:param value: form field
:param arg: string of classes seperated by ' '
:return: edited field
'''
css_classes = value.field.widget.attrs.get('class', '')
# check if class is set or empty and split its content to list (or init list)
if css_classes:
css_classes = css_classes.split(' ')
else:
css_classes = []
# prepare new classes to list
args = arg.split(' ')
for a in args:
if a not in css_classes:
css_classes.append(a)
# join back to single string
return value.as_widget(attrs={'class': ' '.join(css_classes)})
reusable_form_fields.html (template)
{% load custom_tags %}
{% csrf_token %}
{% for field in form %}
<div class="form-group row">
{% if field|input_type == 'TextInput' %}
<div for="{{ field.label }}" class="col-sm-2 col-form-label">
{{ field.label_tag }}
</div>
<div class="col-sm-10">
{{ field|add_classes:'form-control'}}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
</div>
{% else %}
...
{% endif %}
</div>
{% endfor %}

Crispy forms are the way to go . Tips for Bootstrap 4. Adding to #Christian Abbott's answer, For forms , bootstrap says, use form-group and form-control .
This is how it worked for me .
My forms.py
class BlogPostForm(forms.ModelForm):
class Meta:
model = models.Post
fields = ['title', 'text', 'tags', 'author', 'slug']
helper = FormHelper()
helper.form_class = 'form-group'
helper.layout = Layout(
Field('title', css_class='form-control mt-2 mb-3'),
Field('text', rows="3", css_class='form-control mb-3'),
Field('author', css_class='form-control mb-3'),
Field('tags', css_class='form-control mb-3'),
Field('slug', css_class='form-control'),
)
My post_create.html
{% extends 'blog/new_blog_base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<div class="container">
<form method='POST' enctype="multipart/form-data">
{% csrf_token %}
{{ form.media }}
{% crispy form %}
<hr>
<input type="submit" name="Save" value="Save" class='btn btn-primary'> <a href="{% url 'home' %}" class='btn btn-danger'>Cancel</a>
</form>
</div>
{% endblock %}
Note : If you are using CK Editor RichTextField() for your model field , then that field wont be affected . If anyone knows about it , do update this .

You can also explicity mention the field that you want to apply the class to
class ProfileForm(ModelForm):
class Meta:
model = Profile
fields = ['avatar','company']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['avatar'].widget.attrs.update({'class': 'form-control'})
self.fields['company'].widget.attrs.update({'class':'form-control'})

I found it easier to identify the element via css and add the styling there. With django forms you get a unique id for each form field (user form prefixes if you display the form multiple times in your template).
# views.py
def my_view_function(request):
form_a = MyForm(prefix="a")
form_b = MyForm(prefix="b")
context = {
"form_a": form_a,
"form_b": form_b
}
return render(request, "template/file.html", context)
style
// file.css
form input#by_id {
width: 100%;
}

This is a answer complemeting #Christian Abbott correct answer.
If you use a lot of forms, a option for not having to override init every single time may be to create your own form class:
class MyBaseForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for visible in self.visible_fields():
visible.field.widget.attrs['class'] = 'form-control'
Then you can inherit from this class and it is going to automatically make the styles for you.
class ExampleForm(MyBaseForm):
# Your declared form fields here
...
Same thing can be done with ModelForm by simply creating a MyBaseModelForm that inherits from ModelForm.

This is very practical:
class CreateSomethingForm(forms.ModelForm):
class Meta:
model = Something
exclude = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs['class'] = 'form-control'
In this way you don't have to go field by field.

One way is to create base form class and manually update the field's attribute inside __init__ method.
Another is by using already existing libraries like this one:
https://github.com/dyve/django-bootstrap3
There are plenty of these libraries around github. Look around.

Ok some time has passed but i had the same issues. I came to this solution:
class FormCssAttrsMixin():
cssAttrs = {}
def inject_css_attrs(self):
# iterate through fields
for field in self.fields:
widget = self.fields[field].widget
widgetClassName = widget.__class__.__name__
# found widget which should be manipulated?
if widgetClassName in self.cssAttrs.keys():
# inject attributes
attrs = self.cssAttrs[widgetClassName]
for attr in attrs:
if attr in widget.attrs: # attribute already existing
widget.attrs.update[attr] = widget[attr] + " " + attrs[attr] # append
else: # create attribute since its not existing yet
widget.attrs[attr] = attrs[attr]
class MyForm(FormCssAttrsMixin, forms.Form):
# add class attribute to all django textinputs widgets
cssAttrs = {"TextInput": {"class": "form-control"}}
name = forms.CharField()
email = forms.CharField()
address = forms.CharField()
country = forms.CharField()
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.inject_css_attrs()
With this Mixin class you can manipulate the attributes of form widgets in a generic way. Simply add a dictionary as class variable which contains the desired attributes and values per widget.
This way you can add your css classes at the same location where you define your fields. Only downside is, that you have to call the "inject_css_attrs" method somewhere but i think that is ok.

A generalized version of #christian-abbott response:
class ExampleForm(forms.Form):
_HTML_CLASSES = ('form-control', 'something-else')
# Your declared form fields here
...
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for visible in self.visible_fields():
missing_classes = list(self._HTML_CLASSES)
if 'class' in visible.field.widget.attrs:
current_classes = visible.field.widget.attrs['class'].split(' ')
for current_class in current_classes:
if current_class in missing_classes:
missing_classes.remove(current_class)
else:
current_classes = []
visible.field.widget.attrs['class'] = ' '.join(current_classes + missing_classes)

If you just need to change the class for bootstrap purposes, you can just add a script to the template.
<script>
const elementsInputs = document.querySelectorAll('input[id^="id_"]');
elementsInputs.forEach(element => {
element.classList.add("form-control");
});
const elementsLabels = document.querySelectorAll('label[for^="id_"]');
elementsLabels.forEach(element => {
element.classList.add("form-label");
});
</script>
then the form fields in the template should be something like:
<div class="fieldWrapper">
{{ form.subject.errors }}
{{ form.subject.label_tag }}
{{ form.subject }}
</div>
as described in Django.

You can add classes in your forms.py inside the Meta class:
class Form(forms.ModelForm):
class Meta:
model = ModelForm
fields = "__all__"
widgets = {
'name': forms.TextInput(attrs={'class':'form-control'})
}

I understood "no third-party libs", but this one django-widget-tweaks
really WORTH MENTIONING
is simple, DRY and powerfull.
give you full control over the widget rendering doesnt matter which css framework you are using ... still simple
you manage many html attributes you want on HTML not Django forms.
User template "filters" not template tags (as a "normal" form var)
You control the input and labels
django-widget-tweaks
-> https://github.com/jazzband/django-widget-tweaks
Sample ...
{{form.hours|attr:"class:form-control form-control-sm"}}

You can do it without any external libraries or code changes, right in the template. Like this:
{% for field in form %}
<div class="input_item">
<p class="title">{{ field.label }}:</p>
<div class="form-group">
<{{ field|cut:"<"|cut:">" }} class="form-control">
</div>
</div>
{% endfor %}
However, it is not the best solution. If you can create templatetag - go for it.

you can use row-cols-5
<div class="row row-cols-5">
<div class="col">1</div>
<div class="col">2</div>
<div class="col">3</div>
<div class="col">4</div>
<div class="col">5</div>
</div>

I know that author asked about Bootstrap for own Form, but there is an additional way to include Bootstrap class tag in Django form for authentication, password reset etc.
If we create template with standard form:
<form action="" method="post">
{% csrf_token %}
{{ form }}
</form>
then in browser source code we can see all the form fields with the tags:
<form action="" method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="xxx">
<tr><th><label for="id_old_password">Old password:</label></th><td><input type="password" name="old_password" autofocus required id="id_old_password"></td></tr>
<tr><th><label for="id_new_password1">New password:</label></th><td><input type="password" name="new_password1" required id="id_new_password1"></td></tr>
<tr><th><label for="id_new_password2">New password confirmation:</label></th><td><input type="password" name="new_password2" required id="id_new_password2"></td></tr>
</form>
Variable {{ form }} in our template now can be replaced with this code and Bootstrap classes we needed:
<div class="fieldWrapper form-group" aria-required="true">
<label for="id_old_password">Old password:</label><span class="required">*</span>
<input type="password" **class="form-control"** name="old_password" autofocus required id="id_old_password">
</div>
Maybe it could be useful for redesign built-in static forms.

Related

Display a form field i.e., a choice field of form in the template django

I've have forms.py file in that i have a choice field which i've to display it in the template.html
forms.py
Choices = [('Yelp',)]
class UtilitiesForm(forms.Form):
api_select = forms.MultipleChoiceField(widget=forms.Select(),
choices=Choices)
text_url = forms.CharField()
template.html
{% block body_content %}
<form action="/utilities/fetch-data/" method="post" id="utitliy__form">
<div class="form-row">
<label>Select an API</label>
{{ form.api_select }}
</div>
</form>
{% endblock body_content %}
i'm getting Value error can you guys help me how to write the choice field in template.html
You need not to do anything with choice field in forms.py you can directly use it in your template.
template
<form action="" method="post">
{% csrf_token %}
<label for="order_no">Order No.:</label>
<input type="text" class='form-control' value="{{Order}}" name="order_no" readonly><br>
<label for="isbn">ISBN No.:</label>
<input type="text" class='form-control' value="{{ISBN}}" name="isbn" readonly><br>
<label for="rate">Rate:</label>{{forms.rate}}(Rate us 10 is the Highest and 1 is the lowest)<br><br>
<label for="comment">Comments</label>{{forms.comment}}<br><br>
<input type="submit" class="btn btn-success">
</form>
Models.py
RATING=( (1,1), (2,2), (3,3), (4,4), (5,5), (6,6), (7,7), (8,8), (9,9), (10,10) )
class returnbook(models.Model):
order_no=models.IntegerField(blank=True,null=True)
isbn=models.CharField(max_length=15)
rate=models.IntegerField(choices=RATING,default='1')
comment=models.TextField(max_length=20000,blank=True)
user=models.CharField(max_length=50)
class Meta:
unique_together = ('order_no', 'isbn')
def __unicode__(self):
return unicode(self.rate)
The choices [Django-doc] should be:
Either an iterable (e.g., a list or tuple) of 2-tuples to use as
choices for this field, or a callable that returns such an iterable.
This argument accepts the same formats as the choices argument to a
model field. See the model field reference documentation on choices
for more details. If the argument is a callable, it is evaluated each
time the field’s form is initialized. Defaults to an empty list.
Here you provide it an iterable of 1-tuples. The tuple specify the value, and the textual representation, for example:
API_SELECT_CHOICES = [('yelp', 'Yelp')]
class UtilitiesForm(forms.Form):
api_select = forms.ChoiceField(
widget=forms.Select,
choices=API_SELECT_CHOICES
)
text_url = forms.CharField()
It is also strange that you used a MultipleChoiceField with a Select widget. Normally a Select widget is used to select exactly one element, whereas a SelectMultiple widget is used to select zero, one, or more elements. So I here changed the field to ChoiceField. A ChoiceField with a Select widget makes sense, and a MultipleChoiceField with a SelectMultiple makes sense, but the other combinations do not make much sense.
Add the choice in form
forms.py
from django.contrib.auth.models import User
from django import forms
class UserForm(forms.ModelForm):
status = models.IntegerField(choices=((1, _("Unread")),(2, _("Read"))),default=1)
class Meta:
model = User
fields = ['first_name', 'last_name', 'status']
in views.py
from django.contrib.auth.models import User
from .forms import UserForm # Add form which is written in your forms.py
def index(request):
context = {}
form = UserForm() #Initiate
return render(request, 'index.html', {'form': form})
index.html you will get choice in html
<form action="{% url 'about' %}" method="POST" role="form">
{% csrf_token %}
{{ form }}
</form>
for more details please refer this link

Django - Forms - autofill & hide foreign key field

I have models.py, and forms.py that looks like this:
class BHA_overall(models.Model):
bha_number = models.ForeignKey(BHA_List, 'CASCADE', related_name='bha_overall')
drill_str_name = models.CharField(max_length=111)
depth_in = models.CharField(max_length=111)
depth_out = models.CharField(max_length=111)
class BHA_overall_Form(forms.ModelForm):
class Meta():
model = BHA_overall
fields = '__all__'
In my template, if I just use:
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<button name='action' value='login' type="submit">Sign in</button>
</form>
the foreign key field bha_number is displayed as a combo box where I can select the specific bha_number model instance it belongs to, like this:
Here, I want to remove Bha number field from the user side, and just let my code auto fill that field for the user, and hide it. So from the user side, there will be only 3 fields displayed. How can I do this?
Currently I am implementing this html code:
<form id="demo-form" data-parsley-validate="" novalidate="" method="POST">
<div class="row">
{% csrf_token %}
{% for field in form %}
<div class="col-lg-2 col-md-2 col-sm-4 col-xs-6" style="margin-bottom: 5px">
<label class="input-upper-title">{{ field.name }}</label>
<input type="text" id="" class="form-control input-field-height-vertical" name="" data-parsley-trigger="" required="">
</div>
{% endfor %}
<input type="submit" class='btn btn-primary' value="Submit">
</div>
</form>
And it renders this:
I want the first field, bha_number to disappear from the user side, but the system still needs to get that information to save to a correct model instance. So I'm looking for an way to auto fill this ForeignKey field at forms.py or views.py level.
Here is my views.py:
class BHA_UpdateView(UpdateView):
model = BHA_List
success_url = reverse_lazy('well_list') # this is wrong
form_class = BHA_overall_Form
def post(self, request, **kwargs):
api = get_well_api(self.request)
current_bha = BHA_List.objects.filter(id=get_current_bha_id(self.request))[0]
form = BHA_overall_Form(request.POST, instance=BHA_overall.objects.filter(bha_number__well__api=api, bha_number=current_bha)[0])
if form.is_valid():
form.save()
return super().post(request, **kwargs)
You can use exclude to hide the field from form
Class BHA_overall_Form(forms.ModelForm):
class Meta():
model = BHA_overall
fields = '__all__'
exclude = ('bha_number',)
To auto fill after checking if form is valid, just clean the data using form = form.cleaned_data and store it in any variable. It's nothing but a dictionary. You can assign value to this like form['bha_number'] = your value and save it to database by using form.save().
Or you can use object = form.save(commit=False) because this method will return an object. Then you can do object.bha_number = your number
And finally object. Save in next line. That's all. Choose whatever solution you like.
Why don't you keep it as is on forms.py but exclude it from your HTML? That way the user would not see it as an option but the value would still be sent with the form.

Why is Django widgets for TimeInput not showing

I'm trying to create a TimeInput field in a form and noticed that the widget isn't showing correctly. But when I check the localhost:8000/admin, I see the widget showing up correctly.
My code is as follows. For models.py,
class TimeLimit(models.Model):
before = models.TimeField(blank=True, default=time(7, 0)) # 7AM
after = models.TimeField(blank=True, default=time(23, 0)) # 11PM
For views.py,
class UpdateTimeLimitView(LoginRequiredMixin, FormView):
model = TimeLimit
template_name = 'accounts/update_time_limit.html'
form_class = UpdateTimeLimitForm
def get_success_url(self):
return reverse_lazy('accounts:user_profile') + '?username=' + self.request.GET['username']
def get_context_data(self, **kwargs):
data = super(UpdateTimeLimitView, self).get_context_data(**kwargs)
data['username'] = self.request.GET['username']
return data
For forms.py,
class UpdateTimeLimitForm(forms.Form):
time_error = {'required': 'This field is required.',
'invalid': 'Please enter valid Hour:Minute values.'}
before = forms.TimeField(widget=forms.TimeInput(format='%H:%M'))
after = forms.TimeField(widget=TimeInput(format='%H:%M'))
class Meta:
model = TimeLimit
Finally, the relevant part for fields in update_time_limit.html,
<div class="container">
<form method="post">
{% csrf_token %}
<p>
{% for field in form %}
{{ field.errors }}
<label for="{{ field.id_for_label }}">{{ field.label }}({{ field.help_text }}):</label>
<br />
{{ field }}<br /><br /> and
{% endfor %}
</p>
<input class="btn btn-primary done-btn" type="submit" value="Update Time Limit">
</form>
</div>
Is there anything that I'm missing or doing wrong? Thank you.
The Django admin uses AdminTimeWidget to display time fields, not the TimeInput widget that you are using in your code.
There isn't a documented way to reuse the AdminTimeWidget outside of the Django admin. Getting it to work is very hacky (see the answer on this question, which is probably out of date), so it's probably better to use a different widget.
convert datetime.time(7, 0) to string work for me.
data['before'] = data['before'].strftime('%H:%M:%S')

Read-only form in template, django

How can i make a input read-only on a template, not in a model or view?
For example in
{% for form in formset %}
{{ form.id }}
<tr>
<th>{{ form.project_name }}</th>
I need to make {{ form.project_name }} readonly(still as form), via html if it's possible(it has a default value in model.py)
Edit. I need it to be browser read only, not a form readonly.
If you want make all fields read only at a template level, use HTML <fieldset> disabled attribute:
<form action="{% url "app.views.fonction" %}" method="post">{% csrf_token %}
<h2>{% trans "Any title" %}</h2>
<fieldset disabled="disabled">
{{ form.as_ul }}
</fieldset>
<input type="submit" class="any" value="any" />
</form>
If you want to make input fields readonly in HTML, you'll have to write all the required HTML yourself.
HTML
<input type="text" value="{{ object.field_1 }}" readonly />
<input type="text" value="{{ object.field_2 }}" readonly />
<!-- And so on -->
There is a very nice answer here :
You can add the necessary html to the form field via the widget’s attributes property:
myform.fields['thefield'].widget.attrs['readonly'] = True
This way you can still write in your template
{{myform.thefield.label_tag}}
{{myform.thefield}}
and Django will put readonly="True" in the <input ...>.
The cleanest way is to create a new base form class that has a readonlyfields meta option, and does the rest of the work for you.
You shouldn't have any of that logic in the template, but rather validate data in the view, and put let django render readonly input as a span widget.
I use this in production with great success.
class SpanWidget(forms.Widget):
'''Renders a value wrapped in a <span> tag.
Requires use of specific form support. (see ReadonlyForm
or ReadonlyModelForm)
'''
def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs, name=name)
return mark_safe(u'<span%s >%s</span>' % (
forms.util.flatatt(final_attrs), self.display_value))
def value_from_datadict(self, data, files, name):
return self.original_value
class SpanField(forms.Field):
'''A field which renders a value wrapped in a <span> tag.
Requires use of specific form support. (see ReadonlyForm
or ReadonlyModelForm)
'''
def __init__(self, *args, **kwargs):
kwargs['widget'] = kwargs.get('widget', SpanWidget)
super(SpanField, self).__init__(*args, **kwargs)
class Readonly(object):
'''Base class for ReadonlyForm and ReadonlyModelForm which provides
the meat of the features described in the docstings for those classes.
'''
class NewMeta:
readonly = tuple()
def __init__(self, *args, **kwargs):
super(Readonly, self).__init__(*args, **kwargs)
readonly = self.NewMeta.readonly
if not readonly:
return
for name, field in self.fields.items():
if name in readonly:
field.widget = SpanWidget()
elif not isinstance(field, SpanField):
continue
model_field = self.instance._meta.get_field_by_name(name)[0]
field.widget.original_value = model_field.value_from_object(self.instance)
field.widget.display_value = unicode(getattr(self.instance, name))
class ReadonlyForm(Readonly, forms.Form):
'''A form which provides the ability to specify certain fields as
readonly, meaning that they will display their value as text wrapped
with a <span> tag. The user is unable to edit them, and they are
protected from POST data insertion attacks.
The recommended usage is to place a NewMeta inner class on the
form, with a readonly attribute which is a list or tuple of fields,
similar to the fields and exclude attributes on the Meta inner class.
class MyForm(ReadonlyForm):
foo = forms.TextField()
class NewMeta:
readonly = ('foo',)
'''
pass
class ReadonlyModelForm(Readonly, forms.ModelForm):
'''A ModelForm which provides the ability to specify certain fields as
readonly, meaning that they will display their value as text wrapped
with a <span> tag. The user is unable to edit them, and they are
protected from POST data insertion attacks.
The recommended usage is to place a NewMeta inner class on the
form, with a readonly attribute which is a list or tuple of fields,
similar to the fields and exclude attributes on the Meta inner class.
class Foo(models.Model):
bar = models.CharField(max_length=24)
class MyForm(ReadonlyModelForm):
class Meta:
model = Foo
class NewMeta:
readonly = ('bar',)
'''
pass
This is code I use in production:
class MembershipForm(ReadonlyModelForm):
class Meta:
model = Membership
fields = ('user','board', 'privileged', 'alumni')
class NewMeta:
readonly = ('user')
def email(self):
return self.instance.user.email
I am using this simple code (part of a project using bootstrap) to render a read only form in a template (there is no submit button). This allows further template customization.
<ul class="list-group">
{% for field in form %}
<li class="list-group-item">
{{ field.label_tag }} {{ field.value }}
</li>
{% endfor %}
</ul>

Django: How do I add arbitrary html attributes to input fields on a form?

I have an input field that is rendered with a template like so:
<div class="field">
{{ form.city }}
</div>
Which is rendered as:
<div class="field">
<input id="id_city" type="text" name="city" maxlength="100" />
</div>
Now suppose I want to add an autocomplete="off" attribute to the input element that is rendered, how would I do that? Or onclick="xyz()" or class="my-special-css-class"?
Check this page
city = forms.CharField(widget=forms.TextInput(attrs={'autocomplete':'off'}))
Sorry for advertisment, but I've recently released an app (https://github.com/kmike/django-widget-tweaks) that makes such tasks even less painful so designers can do that without touching python code:
{% load widget_tweaks %}
...
<div class="field">
{{ form.city|attr:"autocomplete:off"|add_class:"my_css_class" }}
</div>
or, alternatively,
{% load widget_tweaks %}
...
<div class="field">
{% render_field form.city autocomplete="off" class+="my_css_class" %}
</div>
If you are using "ModelForm":
class YourModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(YourModelForm, self).__init__(*args, **kwargs)
self.fields['city'].widget.attrs.update({
'autocomplete': 'off'
})
If you are using ModelForm, apart from the possibility of using __init__ as #Artificioo provided in his answer, there is a widgets dictionary in Meta for that matter:
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ('name', 'title', 'birth_date')
widgets = {
'name': Textarea(attrs={'cols': 80, 'rows': 20}),
}
Relevant documentation
I did't want to use an entire app for this thing.
Instead I found the following code here https://blog.joeymasip.com/how-to-add-attributes-to-form-widgets-in-django-templates/
# utils.py
from django.template import Library
register = Library()
#register.filter(name='add_attr')
def add_attr(field, css):
attrs = {}
definition = css.split(',')
for d in definition:
if ':' not in d:
attrs['class'] = d
else:
key, val = d.split(':')
attrs[key] = val
return field.as_widget(attrs=attrs)
use the tag in the html file
{% load utils %}
{{ form.field_1|add_attr:"class:my_class1 my_class2" }}
{{ form.field_2|add_attr:"class:my_class1 my_class2,autocomplete:off" }}
I have spent quite a few days trying to create re-usable form templates to create and update models in Django forms. Note that am using ModelForm to change or create object. Am using also bootstrap to style my forms.
I used django_form_tweaks for some forms in past, but I needed some customization without a lot of template dependency. Since I already have jQuery in my Project I decided to leverage its properties to style my forms.
Here is the code, and can work with any form.
#forms.py
from django import forms
from user.models import User, UserProfile
from .models import Task, Transaction
class AddTransactionForm(forms.ModelForm):
class Meta:
model = Transaction
exclude = ['ref_number',]
required_css_class = 'required'
Views.py
#method_decorator(login_required, name='dispatch')
class TransactionView(View):
def get(self, *args, **kwargs):
transactions = Transaction.objects.all()
form = AddTransactionForm
template = 'pages/transaction.html'
context = {
'active': 'transaction',
'transactions': transactions,
'form': form
}
return render(self.request, template, context)
def post(self, *args, **kwargs):
form = AddTransactionForm(self.request.POST or None)
if form.is_valid():
form.save()
messages.success(self.request, 'New Transaction recorded succesfully')
return redirect('dashboard:transaction')
messages.error(self.request, 'Fill the form')
return redirect('dashboard:transaction')
HTML Code
Note: Am using bootstrap4 modal to remove the hassle of creating many views. Maybe it is better to use generic CreateView or UpdateView.
Link Bootstrap and jqQery
<div class="modal-body">
<form method="post" class="md-form" action="." enctype="multipart/form-data">
{% csrf_token %}
{% for field in form %}
<div class="row">
<div class="col-md-12">
<div class="form-group row">
<label for="" class="col-sm-4 col-form-label {% if field.field.required %}
required font-weight-bolder text-danger{%endif %}">{{field.label}}</label>
<div class="col-sm-8">
{{field}}
</div>
</div>
</div>
</div>
{% endfor %}
<input type="submit" value="Add Transaction" class="btn btn-primary">
</form>
</div>
Javascript Code remember to load this in $(document).ready(function() { /* ... */}); function.
var $list = $("#django_form :input[type='text']");
$list.each(function () {
$(this).addClass('form-control')
});
var $select = $("#django_form select");
$select.each(function () {
$(this).addClass('custom-select w-90')
});
var $list = $("#django_form :input[type='number']");
$list.each(function () {
$(this).addClass('form-control')
});
var $list = $("form :input[type='text']");
$list.each(function () {
$(this).addClass('form-control')
});
var $select = $("form select");
$select.each(function () {
$(this).addClass('custom-select w-90')
});
var $list = $("form :input[type='number']");
$list.each(function () {
$(this).addClass('form-control')
});