Good Afternoon,
I have a query when trying to create an edit template, I'm retrieving data via an api call. When I input the data in the django form.Forms the attachment is showing as blank.
This is the Form.py file
class PaymentListForm(forms.Form):
Title = forms.CharField(max_length=50)
Receipt_Amount = forms.DecimalField(max_digits=8, decimal_places=2)
Request_Amount = forms.DecimalField(max_digits=8, decimal_places=2)
Receipt_Attachment = forms.FileField()
def __init__(self, *args, **kwargs):
super(PaymentListForm, self).__init__(*args, **kwargs)
And this is the views.py file
def edit_payments(request, trn_pk):
trns = get_transaction_by_id(trn_pk)
data = trns['data']
if request.method == 'GET':
form = PaymentListForm(initial=data)
return render(request, 'paymentlist/createeditpayments.html', {'form': form})
I can confirm that in the data dictionary there is the link for the attachment but somehow in the forms the attachment is getting lost.
Any help reg this?
Thanks
There is no possibility to set initial value for FileField like in CharField or DecimalField but there is a way to solve this problem. What we need is to hide forms.FileField and handle loading files by a label and show initial data as just text. so first hide forms.FileField:
forms.py:
class PaymentListForm(forms.Form):
Title = forms.CharField(max_length=50)
Receipt_Amount = forms.DecimalField(max_digits=8, decimal_places=2)
Request_Amount = forms.DecimalField(max_digits=8, decimal_places=2)
Receipt_Attachment = forms.FileField(label="", widget=forms.FileInput(
attrs={'style': "display: none",},
), )
def __init__(self, *args, **kwargs):
super(PaymentListForm, self).__init__(*args, **kwargs)
now we need to add tag to handle uploading files instead forms.FileField and tag where we'll display info about attachment url. I'm not sure how html template looks exactly so i've made some generic one - important tag we need to place inside tag.
createeditpayments.html:
<form method="POST" action="" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<span>Receipt attachment: </span>
<label for="id_Receipt_Attachment" class="mt-2 btn btn-primary " style="cursor: pointer;">Choose file...</label>
<span id="receipt_url"> {{ url_of_receipt }}</span>
<br><br>
<button class="btn btn-primary" role="button" type="submit">
Save
</button>
so we should get such output:
of course u can style label, span as u want to. now we need to change a bit the view - specially to add to context dictionary url_of_receipt.
views.py:
def edit_payments(request, trn_pk):
trns = get_transaction_by_id(trn_pk)
data = trns['data']
if request.method == 'GET':
form = PaymentListForm(initial=data)
return render(request, 'paymentlist/createeditpayments.html', {'form': form, 'url_of_receipt': data['Receipt_Attachment']})
i've assumed that data is a dictionary with such structure:
data = {
'Title': 'Example title',
'Receipt_Amount': 21.99,
'Request_Amount': 34,
'Receipt_Attachment': 'media/example.receipt.pdf'
}
so now we've got such output:
and very last thing is to switch on uploading files after clicking the label. think the best way is to use jQuery and short script. so i the bottom of createeditpayments.html place such code:
createeditpayments.html
{% block javascript %}
<script>
// fetch url from hidden FieldField and insert it next to upload button
$("#id_Receipt_Attachment").change(function () {
{#readURL(this);#}
$('#receipt_url').text(' ' + this.files[0].name);
});
</script>
{% endblock %}
and in base.html (or any html which createeditpayments.html extends) place just above closing tag:
base.html:
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js#1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
{% block javascript %}{% endblock %}
</body>
now the label should upload files and change url next to it.
Related
First post, apologies in advance for errors.
My problem:
I initialize my model fields from a proprietary db. These fields are displayed correctly in my template upon first display (I can see the html reflects the values in my proprietary db). I then change the initial selection options (for the MultiSelectField) in my template, and save these changed options to the Django db (verified its saved via the admin function) via post overloading in my views. My success_url is setup in urls.py to redisplay the same view. I have overloaded get_context_data in views to load the form data from the Django database, and to update context data with this loaded data, the idea being that when the templates redisplays (when selecting the submit_button), my html form should now displays the from data loaded from the Django db (not the initial values). What's happening, though, is that the initial values seems to be displayed, as opposed to the changed values I have loaded from the Django db. I would really appreciate help wrt understanding why the template displays values other than what I have loaded from the Django db. The form value that I update in my context before returning it in views.py is not what is displayed in the template.
my models.py:
class LiquidAssetModel(models.Model):
#Get my initial values from an external db
unq_assets = Jnance_base_data_main.get_unq_assets()
init_values = Jnance_base_data_main.get_liq_indexes()
liq_dates = Jnance_base_data_main.get_dates()
#initialize my fields
liquid_choices = MultiSelectField(choices = unq_assets, default = init_values)
from_date = models.CharField(max_length=200, choices=liq_dates, default=liq_dates[0][0])
to_date = models.CharField(max_length=200, choices=liq_dates, default=liq_dates[0][0])
def __str__(self):
return self.from_date
My froms.py:
class LiquidAssetsForm(forms.ModelForm):
# specify the name of model to use
class Meta:
model = LiquidAssetModel
fields = "__all__"
My post overload from views.py
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
#form = forms.LiquidAssetsForm(request.POST)
#form.is_valid goes to the LiquidAssetsForm class and clean(self) overload to check whether the submitted data is fine
if form.is_valid():
liquid_indexes = form.cleaned_data.get('liquid_choices')
from_index = form.cleaned_data.get('from_date')
to_index = form.cleaned_data.get('to_date')
#cleare the database
all_forms = LiquidAssetModel.objects.all()
all_forms.delete()
form.save(commit=False)
form.save(commit=True)
#save our liquid asset choices to db
Jnance_base_data_main.put_liq(liquid_indexes)
#make sure the db is set up, copy the excel files, and then load them into the db
Jnance_base_data_main.gen_liq_graph(liquid_indexes, int(from_index), int(to_index))
return self.form_valid(form, **kwargs)
else:
return self.form_invalid(form, **kwargs)
my get_context_data overload from views.py:
class LiquidView(FormView):
template_name = 'liquid_chart.html'
form_class = forms.LiquidAssetsForm
success_url ="/thanksLiquid/"
liq_file_name = ''
def get_context_data(self, **kwargs):
context = super(LiquidView, self).get_context_data(**kwargs)
#build the string to search for a date_NAV file
search_str = settings.STATIC_ROOT + '/Jnance_Mainapp/images/date_liq*'
search_str = search_str.replace('\\', '/')
#search for a file
the_files = glob.glob(search_str)
#split the string and only use the part after 'static/', since we will use the static template tag in the html
if the_files:
#just use the very first file we found in the list
self.liq_file_name = the_files[0].split('static/')[1]
self.liq_file_name = self.liq_file_name.replace('\\', '/')
else:
print("Jnance_Mainapp: views LiquidView no liquid chart found to display")
#update our context with the file name
context.update({'liquid_file_name': self.liq_file_name})
form_class = self.get_form_class()
form = self.get_form(form_class)
the_obj = LiquidAssetModel.objects.all()
if len(the_obj) == 0:
#no forms saved yet
pass
else:
form.liquid_choices = the_obj.values('liquid_choices')[0]
form.from_date = the_obj.values('from_date')[0]
form.to_date = the_obj.values('to_date')[0]
print (form.liquid_choices)
context.update({'form': form})
return context
my urls.py:
urlpatterns = [
path('', views.HomeView.as_view(), name='home'),
path('NAV_Charts/', views.NAVView.as_view(), name='NAV_Charts'),
path('Asset_Charts/', views.AssetView.as_view(), name='Asset_Charts'),
path('Liquid_Charts/', views.LiquidView.as_view(), name='Liquid_Charts'),
path('Sum_Charts/', views.SumView.as_view(), name='Sum_Charts'),
path('Base_Load/', views.BaseLoadView.as_view(), name='Base_Load'),
path('admin/', admin.site.urls, name= 'admin'),
path('thanksNAV/', views.NAVView.as_view()),
path('thanksAsset/', views.AssetView.as_view()),
path('thanksLiquid/', views.LiquidView.as_view()),
path('thanksBaseLoadForm/', views.BaseLoadView.as_view()),
path('Jnance_Mainapp/', include('Jnance_Mainapp.urls', namespace='Jnance_Mainapp')),]
my template:
{% extends "base.html" %}
{% load static %}
{% load crispy_forms_tags %}
{% block body_block %}
<h1 class="text-center display-1" Bootstrap heading (2.5rem = 40px)>Jnance Liquid Assets Chart Generation</h1>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<table class="row">
<div class="row">
<div class="col-sm-6 text-center align-left">
<img class="img-fluid mb-1" src="{% static liquid_file_name %}" alt="Unable to load chart!"/>
</div>
<br>
<div class="col-sm-3 text-left align-left">
{{ form.from_date|as_crispy_field }}
<br>
{{ form.to_date|as_crispy_field }}
<br>
<br>
<input name="submit_button" type="submit" value="Generate" id="update_button"
class="btn btn-success" enctype="multipart/form-data">
</div>
<div class="col-sm-3 text-left align-left">
{{ form.liquid_choices|as_crispy_field }}
</div>
</div>
</table>
</form>
{% endblock %}
Your setting the initial values to whatever the first object is that gets returned in the_obj = LiquidAssetModel.objects.all() when you set them with the_obj.values('liquid_choices')[0]
I have a checkbox in template and want to get the lit of value from checked checkbox for rendering to pdf in views.py. Do I need to create a form for it? No idea how to combine it.
html:
{% for order in orders %}
<div class="mb-3">
{% for item in order.items.all %}
<input type="checkbox" name="item_checkbox" value="{{ item.item.id }}" >{{ item.item.product_name }}<br>
{% endfor %}
</div>
PDF
{% endfor %}
How do I get the checkbox value and pass it to views.py?
pdf rendering in views.py
def generate_to_pdf(request, id):
order = get_object_or_404(Order, id=id)
time_now = timezone.now()
template = get_template('invoice.html')
context = {
'order': order,
'time_now': time_now
}
template.render(context)
pdf = render_to_pdf('invoice.html', context)
return HttpResponse(pdf, content_type='application/pdf')
Should I do the Form post method in below funcion for get the value from html?
views.py for the above html:
def order_admin(request):
orders = Order.objects.all().order_by('-id')
context = {
'orders': orders,
}
return render(request, 'product_admin.html', context)
You can get the list of selected checkbox values from POST request like
checked_items = request.POST.getlist("item_checkbox")
checked_items will be a list of ids of items which were checked.
Make a form with action="{% url 'generate_pdf' id=order.id %}"
In view function generate_to_pdf
def generate_to_pdf(request, id):
order = get_object_or_404(Order, id=id)
checked_items = request.POST.getlist("item_checkbox") # get checked values
time_now = timezone.now()
template = get_template('invoice.html')
# remaining code
I'm getting this weird error using django-autocomplete-light: "Select a valid choice. That choice is not one of the available choices." However, if I stay on the same page and just push Submit again it submits the form no problem, and the data in the form is inserted in the database like it should.
I have an embedded form with addable inlines from another Model. With these models I can make a BookSubmission having multiple different Books (pointed to another Book Models through ForeignKey) on the same BookSumbission page and for every Book their Quantity specified.
When I don't have the widget activated everything works fine. I can select one or more Books from the expected list.
If activate the widget, everything seems to work fine, even the widget shows the expected Books. However, if I push submit I get the error. Push submit again and the form submits flawlessly.
Does anyone have any idea what is going on here? I thought maybe it has something to do with the load order of the .js files, but playing around with it haven't given me any fruitless results.
models.py
class BookSubmission(models.Model):
SubmissionTitle = models.CharField(
max_length=6,
verbose_name="Submission Title",
unique=True)
SubmissionText = models.TextField(
max_length=1500,
null=True,
blank=True,
verbose_name="Submission Text"
)
class BooksAndQuantity(models.Model):
Submission = models.ForeignKey(
'Submission',
null=True,
blank=True,
verbose_name="BookSubmission"
)
Book = models.ForeignKey(
'Books',
to_field='BookTitle',
db_column='BookTitleID',
null=True,
blank=True,
)
Quantity = models.FloatField(verbose_name="Quantity")
class Books(models.Model):
BookTitle = models.CharField(
max_length=6,
unique=True,
db_column='BookTitleID')
BookText = models.TextField(
max_length=1500,
null=True,
blank=True
)
forms.py
class BookSubmissionForm(forms.ModelForm):
class Meta:
model = BookSubmission
fields = '__all__'
BookAndQuantityFormset = inlineformset_factory(
BookSubmission,
BookAndQuantity,
fields='__all__',
extra=1,
widgets={'Book':
autocomplete.ModelSelect2(url='BookAutocomplete')})
views.py
class BookSubmissionView(generic.CreateView):
template_name = 'BookSubmission.html'
model = BookSubmission
form_class = BookSubmissionForm
success_url = 'success/'
def get(self, request, *args, **kwargs):
"""
Handles GET requests and instantiates blank versions of the form
and its inline formsets.
"""
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
BookAndQuantityForm = BookAndQuantityFormset()
return self.render_to_response(
self.get_context_data(form=form,
BookAndQuantityForm=BookAndQuantityForm))
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance and its inline
formsets with the passed POST variables and then checking them for
validity.
"""
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
BookAndQuantityForm = BookAndQuantityFormset(self.request.POST)
if (form.is_valid() and BookAndQuantityForm.is_valid()):
return self.form_valid(form, BookAndQuantityForm)
else:
return self.form_invalid(form, BookAndQuantityForm)
def form_valid(self, form, BookAndQuantityForm):
"""
Called if all forms are valid. Creates a Recipe instance along with
associated Ingredients and Instructions and then redirects to a
success page.
"""
self.object = form.save()
BookAndQuantityForm.instance = self.object
BookAndQuantityForm.save()
specieandquantityout_form.instance = self.object
specieandquantityout_form.save()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, BookAndQuantityForm):
"""
Called if a form is invalid. Re-renders the context data with the
data-filled forms and errors.
"""
return self.render_to_response(
self.get_context_data(form=form,
BookAndQuantityForm=BookAndQuantityForm))
class BookAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
# if not self.request.user.is_authenticated():
# return Specie.objects.none()
qs = Books.objects.all()
if self.q:
qs = qs.filter(BookTitle__istartswith=self.q)
return qs
With the following html files:
parent.html
<head>
{% load staticfiles %}
<link rel="stylesheet" href="{% static 'personal/css/bootstrap.min.css' %}" type = "text/css"/>
<link rel="stylesheet" href="{% static 'personal/css/navbar-fixed-top.css' %}" >
<link rel="stylesheet" href="{% static 'personal/assets/ie10-viewport-bug-workaround.css' %}" >
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<meta name="viewport" content = "width=device-width, initial-scale=1.0">
<script src="{% static 'personal/assets/ie10-viewport-bug-workaround.js' %}"></script>
<script src="{% static 'personal/js/bootstrap.min.js' %}"></script>
<style type="text/css">
html,
body {
height:100%
}
</style>
</head>
BookSubmission.html
{% extends "parent.html" %}
{% load staticfiles %}
{% block content %}
<div>
<h1>Add BookSubmissionForm</h1>
<form action="" method="post">
{% csrf_token %}
<div>
<p> BookSubmission Title {{ form.SubmissionTitle }} </p>
<p> BookSubmission Text {{ form.SubmissionText }} </p>
</div>
<fieldset>
<legend>Book and Quantity</legend>
{{ bookandquantity_form.management_form }}
{{ bookandquantity_form.non_form_errors }}
{% for form in bookandquantity_form %}
{{ form.id }}
<div class="inline {{ bookandquantity_form.prefix }}">
{{ form.Book.errors }}
{{ form.Book.label_tag }}
{{ form.Book }}
{{ form.Quantity.errors }}
{{ form.Quantity.label_tag }}
{{ form.Quantity }}
</div>
{{ form.media }}
{% endfor %}
</fieldset>
<input type="submit" value="Add BookSubmission" class="submit" />
</form>
</div>
{% endblock %}
{% block footerContent %}
<script src="{% static 'MM/js/jquery.formset.js' %}"></script>
<script type="text/javascript">
$(function() {
$(".inline.{{ bookandquantity_form.prefix }}").formset({
prefix: "{{ bookandquantity_form.prefix }}",
})
})
</script>
{% endblock %}
urls.py
url(r'^BookAutoComplete/$',
views.BookAutoComplete.as_view(),
name='BookAutoComplete'),
edit
What else is weird is the following, the first time that i push "Add another" in the form it looks bugged.
In the following picture the same Formset is inserted twice (now named "Species and Quanity" In & Out).
"Species and Quantity In" is depicted without clicking "Add Another"
In "Species and Quantity Out" I did click on "Add Another", and you can see that the first row becomes buggy, and in the second row a blank useless field is added.
screenshot
Your form validation code is wrong, that's why the form doesn't validate.
Try without the autocomplete widget if in doubt.
See Django forms documentation for details.
I'm trying to use dropzone.js with django.
I'm following the somewhat dated guide here (https://amatellanes.wordpress.com/2013/11/05/dropzonejs-django-how-to-build-a-file-upload-form/)
I strongly suspect My view is at issue.
def test(request):
print "test view has been called"
if request.method == 'POST':
print "test request method is POST"
form = UploadFileForm(request.POST, request.FILES)
print request
print request.FILES
if form.is_valid():
new_file = AttachedFiles(attachedfile=request.FILES['file'])
new_file.save()
id = new_file.pk
print id
print "test form valid"
return HttpResponse(json.dumps({'id': id}), content_type="application/json")
print "test form not valid"
else:
form = UploadFileForm()
data = {'form': form}
return render_to_response('mediamanager/test.html', data, context_instance=RequestContext(request))
I've tested submitting to it with the dropzone code
<!-- IMPORTANT enctype attribute! -->
<form id="my_dropzone" class="dropzone" action="/mediamanager/test/" method="post" enctype="multipart/form-data">
{% csrf_token %}
<button id="submit-all">
Submit all files
</button>
</form>
<script src="{% static 'dropzone/js/dropzone.js' %}"></script>
<script type="text/javascript">
Dropzone.options.myDropzone = {
// Prevents Dropzone from uploading dropped files immediately
autoProcessQueue : true,
init : function() {
var submitButton = document.querySelector("#submit-all")
myDropzone = this;
submitButton.addEventListener("click", function() {
myDropzone.processQueue();
// Tell Dropzone to process all queued files.
});
// You might want to show the submit button only when
// files are dropped here:
this.on("addedfile", function() {
// Show submit button here and/or inform user to click it.
console.log("blah")
});
}
};
</script>
and a basic form
<form action="{% url "test" %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type="file" name="file" />
<input type="submit" value="Submit">
</form>
And the form is never valid.
I'm using a modelform as suggested
class UploadFileForm(forms.ModelForm):
class Meta:
model = AttachedFiles
You can handle Dropzone posts like any other multipart form post.
Here's how I proceed:
#login_required
#usertype_required
def upload_picture(request, uid=None):
"""
Photo upload / dropzone handler
:param request:
:param uid: Optional picture UID when re-uploading a file.
:return:
"""
form = PhotoUploadForm(request.POST, request.FILES or None)
if form.is_valid():
pic = request.FILES['file']
# [...] Process whatever you do with that file there. I resize it, create thumbnails, etc.
# Get an instance of picture model (defined below)
picture = ...
picture.file = pic
picture.save()
return HttpResponse('Image upload succeeded.')
return HttpResponseBadRequest("Image upload form not valid.")
The form is dead simple
class PhotoUploadForm(forms.Form):
# Keep name to 'file' because that's what Dropzone is using
file = forms.ImageField(required=True)
In your model you need the upload_to set:
class Picture(models.Model):
[...]
# Original
file = models.ImageField(upload_to=get_upload_path)
And here's my upload path builder, but you can put anything
def get_upload_path(instance, filename):
""" creates unique-Path & filename for upload """
ext = filename.split('.')[-1]
filename = "%s.%s" % (instance.p_uid, ext)
d = datetime.date.today()
username = instance.author.username
#Create the directory structure
return os.path.join(
'userpics', username, d.strftime('%Y'), d.strftime('%m'), filename
)
Don't forget the csrf_token in the html form itself (I'm using an angularJS directive on top of it so will be different for you)
<form action="{% url 'upload_picture' %}" class="dropzone" drop-zone>
{% csrf_token %}
<div class="fallback">
<h3>Your browser is not supported.</h3>
<strong>
Click here for instructions on how to update it.
</strong>
<p>You can still try to upload your pictures through this form: </p>
<p>
<input name="file" type="file" multiple />
<input type="submit" value="Upload" />
</p>
</div>
</form>
I got dropzone js working by modifying the bootstrap example (I am using bootstrap) here: http://www.dropzonejs.com/bootstrap.html
I do not use any forms. I got a view handling the in coming ajax posts from dropzone. Here is the gist of my view code:
class AjaxUploadView(View):
"""
View for uploading via AJAX.
"""
def post_ajax(self, request, *args, **kwargs):
uploaded_file = request.FILES['file']
# Do stuff with file
# Return appropriate response
Hope it helps.
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.