I have a class with multiple images that can be uploaded. I am trying to use Bootstrap Carousel for a slide show of the images, but I am having a hard time getting it.
Models.py
class Content(models.Model):
title = models.CharField(max_length=250)
content = models.TextField(max_length=1000)
website = models.URLField()
github = models.URLField()
def __str__(self):
return self.title
def get_image_filename(instance, filename):
title = instance.post.title
slug = slugify(title)
return "post_images/%s-%s" % (slug, filename)
class Images(models.Model):
post = models.ForeignKey(Content, on_delete=models.CASCADE)
image = models.ImageField(upload_to=get_image_filename,
verbose_name='Image')
Views.py
def home(request):
content = Content.objects.all()
name = request.POST.get('name')
email = request.POST.get('email')
message = request.POST.get('message')
if request.method == "POST":
if not name and not email and not message:
content = 'name: ' + name + '\n' + 'email: ' + email + '\n' + '\n' + message
send_mail('Email from Portfolio', content, 'email', ['email'], fail_silently=False)
messages.success(request, "Your Email has been sent")
return redirect('home')
else:
return render(request, 'appOne/home.html', {'content':content})
#login_required
def post(request):
ImageFormSet = modelformset_factory(Images,
form=ImageForm, extra=3)
#'extra' means the number of photos that you can upload ^
if request.method == 'POST':
postForm = PostForm(request.POST)
formset = ImageFormSet(request.POST, request.FILES,
queryset=Images.objects.none())
if postForm.is_valid() and formset.is_valid():
post_form = postForm.save(commit=False)
post_form.user = request.user
post_form.save()
for form in formset.cleaned_data:
#this helps to not crash if the user
#do not upload all the photos
if form:
image = form['image']
photo = Images(post=post_form, image=image)
photo.save()
# use django messages framework
messages.success(request,
"Yeeew, check it out on the home page!")
return HttpResponseRedirect("/")
else:
print(postForm.errors, formset.errors)
else:
postForm = PostForm()
formset = ImageFormSet(queryset=Images.objects.none())
return render(request, 'appOne/post.html',
{'postForm': postForm, 'formset': formset})
template:
{% extends 'appOne/base.html' %}
{% load static %}
{%block content%}
{% for obj in content.images_set.all %}
<img src="{{ obj.images.url}}" alt="">
{% endfor %}
{% endblock %}
</html>
Using the template above, when I tried to go home, it does not show anything.
I am in the process of showing the images. The carousel bootstrap is not yet coded on the template.
you are using wrong modelField name. replace {{ obj.images.url}} to {{ obj.image.url}}
example:
{% extends 'appOne/base.html' %}
{% load static %}
{%block content%}
{% for obj in content.images_set.all %}
<img src="{{ obj.image.url}}" alt="">
{% endfor %}
{% endblock %}
</html>
My project is here github Project
I'm getting this error.
ValueError at /salesapp/add/ashton-aged-maduro/
The view salesapp.views.add_CartItem didn't return an HttpResponse object. It returned None instead.
I get this error when I click the 'Add to Cart' button on my singleproduct.html template which calls the ProductAddToCart form. The view is add_CartItem.
I also get the error Field is required when I don't initially set the form values. I'm just stuck now.
This is models.py
class Product(models.Model):
itemid = models.CharField(max_length=128, unique=True)
itemname = models.CharField(max_length=128)
brand = models.CharField(max_length=128)
image = models.ImageField(upload_to='static/images/')
notes = models.CharField(max_length=250)
price = models.IntegerField()
slug = models.SlugField(unique=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.itemname)
super(Product, self).save(*args, **kwargs)
def __str__(self):
return self.itemname
class CartItem(models.Model):
cart_id = models.CharField(max_length=50)
date_added = models.DateTimeField(auto_now_add=True)
quantity = models.IntegerField(default=1)
itemid = models.ForeignKey('Product', unique=False)
class Meta:
db_table = 'cart_items'
ordering = ['date_added']
def name(self):
return self.product.name
My forms.py
class ProductAddToCartForm(forms.ModelForm):
cart_id = forms.CharField(max_length=50)
date_added = forms.DateTimeField()
quantity = forms.IntegerField()
slug = forms.CharField(widget=forms.HiddenInput(), required=False)
#itemid = models.ForeignKey('Product', unique=False)
class Meta:
model = CartItem
fields = ('cart_id', 'date_added', 'quantity', 'slug', 'itemid', )
My views.py
def add_CartItem(request, product_name_slug):
print('In add_CartItem --------------------')
form = ProductAddToCartForm(request.POST)
p = Product.objects.get(slug=product_name_slug)
form = ProductAddToCartForm(initial={'cart_id': 123, 'date_added':date.date.today(), 'quantity': 1, 'slug':p.slug, 'id':p.id, 'itemid':p.itemid})
form.save(commit=False)
print(form)
print(p.slug)
print(p.id)
print(p.itemid)
if form.is_valid():
print(p)
print('In form.is_valid()--------------------------------')
ci = CartItem.objects.create(cart_id=1, date_added=date.date.today(), quantity=1, itemid=p)
form.save(commit=True)
return index(request)
else:
print(form.errors) #return render(request, 'salesapp/errors.html', {'form': form})
My urls.py
from django.conf.urls import url
from salesapp import views
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'about/', views.about, name='about'),
url(r'customers/', views.customers, name='customers'),
url(r'products/', views.products, name='products'),
url(r'^add_product/$', views.add_product, name='add_product'),
url(r'^add_customer/$', views.add_customer, name='add_customer'),
url(r'items/(?P<product_name_slug>[\w\-]+)/$', views.show_product, name='show_product'),
url(r'^add/(?P<product_name_slug>[\w\-]+)/$', views.add_CartItem, name='add_CartItem'),
#url(r'^cart/$', views.show_cart, name='show_cart'),
#url(r'^register/$', views.register, name='register'),
#url(r'^login/$', views.user_login, name='login'),
#url(r'^logout/$', views.user_logout, name='logout'),
#url(r'^restricted/', views.restricted, name='restricted'),
]
and my template where I want to display the ProductAddToCartForm but add a product to the CartItem table.
<!DOCTYPE html>
{% extends 'salesapp/base.html' %}
{% load staticfiles %}
{% block title_block %}
{{ product.itemname }}
{% endblock %}
{% block body_block %}
<div>
<div>
<ul style="list-style:none; text-align:center;">
<li style="float:left; width:25%; margin:20px;">
<img src="/{{ product.image }}"/>
<div>
<b>{{ product.itemname }}</b><br/>
Price per cigar:<br/>
<b>${{ product.price }}</b>
<p>{{ product.notes }}</p>
</div>
<form method="post" action="/salesapp/add/{{ product.slug }}/" class="cart">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in form.visible_fields %}
{{ field.errors }}
{{ field.help_text }}
{{ field }}
{% endfor %}
<br />
<input type="submit" value="Add To Cart" name="submit" alt="Add To Cart" />
</form>
<div class="cb"></div>
</li>
</ul>
</div>
<!-- More code -->
</div>
{% endblock %}
A view function in django should either return a json or a dict or can return a Webpage
You can either do one of following
1) return a json or dict
return {"value1":"value","value2":"value"}
2)redirect to a web page
return redirect('/some/url/')
3)return Http response
return HttpResponse(status=<status code>,content="some content")
4)render a template
t = loader.get_template('myapp/index.html')
c = {'foo': 'bar'}
return HttpResponse(t.render(c, request),
content_type='application/xhtml+xml')
Because you did not return any response to the view,
According to the docs
A view function is simply a Python function that
takes a Web request and returns a Web response.
You need to return to use render method for initial rendering of form and for redirection to another view you can use redirect method of Django.
A view function must return an HttpResponse. For example, if the process was successfull and you dont want to return anything, you can return HttpResponse(status=200)
When a view handles forms, you have to split GET and POST requests. In the GET part you need to instantiate the form without data. In the POST part you fill the form with request.POST data. And this data must have ALL mandatory fields. A typical view function scheme to handle a form is the folowing:
def view(request):
if request.method == "GET":
form = MyForm()
return ...
if request.method == "POST":
form = MyForm(request.POST)
form.save()
return ...
In your template, you have to show all form fields. Then, all form fields will be passed with the request. If you dont, you need to fill the form fields in the view.
first delete the prints, almost when you make a question, are useless in Django
def add_CartItem(request, product_name_slug):
form = ProductAddToCartForm(request.POST)
if request.method == 'POST':
if form.is_valid():
ci = CartItem.objects.create(cart_id=1, date_added=date.date.today(), quantity=1, itemid=p)
ci.save()#you save your model changes, not your form
return HttpResponseRedirect(reverse('your:url'))#your return the success url or the same
else:
#here error, if form isn't valid
else:
form = ProductAddToCartForm(request.POST)
return render(request, 'your/template.html', {'form': form})
That is the correct way to work with forms in Django, first you must make a if statement asking to the browser if is a post request or a normal request, if is post request, take the data from the forms and are adding to the database, if not, Django return a empty template form.
Let me know if you problem solve
In the django docs, there's an example of using inlineformset_factory to edit already created objects
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#using-an-inline-formset-in-a-view
I changed the example to be this way:
def manage_books(request):
author = Author()
BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',))
if request.method == "POST":
formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
if formset.is_valid():
formset.save()
return HttpResponseRedirect(author.get_absolute_url())
else:
formset = BookInlineFormSet(instance=author)
return render_to_response("manage_books.html", {
"formset": formset,
})
With the above, it renders only the inline model without the parent model.
To create a new object, say Author, with multiple Books associated to, using inlineformset_factory, what's the approach?
An example using the above Author Book model from django docs will be helpful. The django docs only provided example of how to edit already created object using inlineformset_factory but not to create new one
I've done that using Django Class-Based Views.
Here's my approach:
models.py
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length=100)
forms.py
from django.forms import ModelForm
from django.forms.models import inlineformset_factory
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Fieldset
from .models import Author, Book
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ('name', )
#property
def helper(self):
helper = FormHelper()
helper.form_tag = False # This is crucial.
helper.layout = Layout(
Fieldset('Create new author', 'name'),
)
return helper
class BookFormHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(BookFormHelper, self).__init__(*args, **kwargs)
self.form_tag = False
self.layout = Layout(
Fieldset("Add author's book", 'title'),
)
BookFormset = inlineformset_factory(
Author,
Book,
fields=('title', ),
extra=2,
can_delete=False,
)
views.py
from django.views.generic import CreateView
from django.http import HttpResponseRedirect
from .forms import AuthorForm, BookFormset, BookFormHelper
from .models import Book, Author
class AuthorCreateView(CreateView):
form_class = AuthorForm
template_name = 'library/manage_books.html'
model = Author
success_url = '/'
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
book_form = BookFormset()
book_formhelper = BookFormHelper()
return self.render_to_response(
self.get_context_data(form=form, book_form=book_form)
)
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
book_form = BookFormset(self.request.POST)
if (form.is_valid() and book_form.is_valid()):
return self.form_valid(form, book_form)
return self.form_invalid(form, book_form)
def form_valid(self, form, book_form):
"""
Called if all forms are valid. Creates a Author instance along
with associated books and then redirects to a success page.
"""
self.object = form.save()
book_form.instance = self.object
book_form.save()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, book_form):
"""
Called if whether 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, book_form=book_form)
)
def get_context_data(self, **kwargs):
""" Add formset and formhelper to the context_data. """
ctx = super(AuthorCreateView, self).get_context_data(**kwargs)
book_formhelper = BookFormHelper()
if self.request.POST:
ctx['form'] = AuthorForm(self.request.POST)
ctx['book_form'] = BookFormset(self.request.POST)
ctx['book_formhelper'] = book_formhelper
else:
ctx['form'] = AuthorForm()
ctx['book_form'] = BookFormset()
ctx['book_formhelper'] = book_formhelper
return ctx
urls.py
from django.conf.urls import patterns, url
from django.views.generic import TemplateView
from library.views import AuthorCreateView
urlpatterns = patterns('',
url(r'^author/manage$', AuthorCreateView.as_view(), name='handle-books'),
url(r'^$', TemplateView.as_view(template_name='home.html'), name='home'),
)
manage_books.html
{% load crispy_forms_tags %}
<head>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
</head>
<div class='container'>
<form method='post'>
{% crispy form %}
{{ book_form.management_form }}
{{ book_form.non_form_errors }}
{% crispy book_form book_formhelper %}
<input class='btn btn-primary' type='submit' value='Save'>
</form>
<div>
Notice:
This is a simple runable example that use the inlineformset_factory
feature and Django generic Class-Based Views
I'm assumming django-crispy-forms is installed, and it's properly
configured.
Code repository is hosted at: https://bitbucket.org/slackmart/library_example
I know it's more code that the showed solutions, but start to using Django Class-Based Views is great.
I didn't read your question properly at first. You need to also render the the form for the parent model. I haven't tested this, I'm going off what I've done before and the previously linked answer, but it should work.
UPDATE
If you're using the view to both and edit, you should check for an Author ID first. If there's no ID, it'll render both forms as a new instance, whereas with an ID it'll, fill them with the existing data. Then you can check if there was a POST request.
def manage_books(request, id):
if id:
author = Author.objects.get(pk=author_id) # if this is an edit form, replace the author instance with the existing one
else:
author = Author()
author_form = AuthorModelForm(instance=author) # setup a form for the parent
BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',))
formset = BookInlineFormSet(instance=author)
if request.method == "POST":
author_form = AuthorModelForm(request.POST)
if id:
author_form = AuthorModelForm(request.POST, instance=author)
formset = BookInlineFormSet(request.POST, request.FILES)
if author_form.is_valid():
created_author = author_form.save(commit=False)
formset = BookInlineFormSet(request.POST, request.FILES, instance=created_author)
if formset.is_valid():
created_author.save()
formset.save()
return HttpResponseRedirect(created_author.get_absolute_url())
return render_to_response("manage_books.html", {
"author_form": author_form,
"formset": formset,
})
I am posting my final solutions, as per extensive assistant given by Onyeka.
Below I post the Add and Edit solutions of using inlineformset_factory of Django using the Author and Book example found in the Django Docs.
First, the Adding of Author object, with 3 extras of Book object to be appended.
Obviously, this goes into your views.py
def add_author(request):
'''This function creates a brand new Author object with related Book objects using inlineformset_factory'''
author = Author()
author_form = AuthorModelForm(instance=author) # setup a form for the parent
BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',))
if request.method == "POST":
author_form = AuthorModelForm(request.POST)
formset = BookInlineFormSet(request.POST, request.FILES)
if author_form.is_valid():
created_author = author_form.save(commit=False)
formset = BookInlineFormSet(request.POST, request.FILES, instance=created_author)
if formset.is_valid():
created_author.save()
formset.save()
return HttpResponseRedirect(created_author.get_absolute_url())
else:
author_form = AuthorModelForm(instance=author)
formset = BookInlineFormSet()
return render(request, "add_author.html", {
"author_form": author_form,
"formset": formset,
})
def edit_author(request, author_id):
'''This function edits an Author object and its related Book objects using inlineformset_factory'''
if id:
author = Author.objects.get(pk=author_id) # if this is an edit form, replace the author instance with the existing one
else:
author = Author()
author_form = AuthorModelForm(instance=author) # setup a form for the parent
BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',))
formset = BookInlineFormSet(instance=author)
if request.method == "POST":
author_form = AuthorModelForm(request.POST)
if id:
author_form = AuthorModelForm(request.POST, instance=author)
formset = BookInlineFormSet(request.POST, request.FILES)
if author_form.is_valid():
created_author = author_form.save(commit=False)
formset = BookInlineFormSet(request.POST, request.FILES, instance=created_author)
if formset.is_valid():
created_author.save()
formset.save()
return HttpResponseRedirect(created_author.get_absolute_url())
return render(request, "edit_author.html", {
"author_id": author_id, # This author_id is referenced
# in template for constructing the posting url via {% url %} tag
"author_form": author_form,
"formset": formset,
})
This part goes into your urls.py, assuming views have been imported, and urlpatterns constructed already.
...
url(r'^add/book/$', views.add_author, name='add_author'),
url(r'^edit/(?P<author_id>[\d]+)$', views.edit_author, name='edit_author'),
...
Now to the templates part. The edit Author object template (edit_author.html) looks like this (no styling applied)
<form action="{% url 'edit_book' author_id %}" method="POST" >
<!-- See above: We're using the author_id that was passed to template via views render of the edit_author(...) function -->
{% csrf_token %} <!-- You're dealing with forms. csrf_token must come -->
{{ author_form.as_p }}
{{ formset.as_p }}
<input type="submit" value="submit">
</form>
To add a brand new Author object via template (add_author.html):
<form action="." method="POST" >{% csrf_token %}
{{ author_form.as_p }}
{{ formset.as_p }}
<input type="submit" value="submit">
</form>
NOTE:
Using the action='.' might appear to be a cheap way of constructing the url, whereby the form posts the form data to the same page. With this example, using the action='.' for the edit_author.html template always got the form posted to /edit/ instead of /edit/1 or /edit/2
Constructing the url using the {% url 'edit_author' author_id %} ensures the form always posts to the right url. Failing to do use the {% url %} cost me lots of hours and trouble.
Big thanks to Onyeka.
i did exactly what you are trying :
https://github.com/yakoub/django_training/tree/master/article
you need to create a separate form using the prefix attribute .
then when you save you need to iterate over all books and associate them with the author you just created .
This is my first django inline_formset view for create a invoice with list of invoice_item_set.
In models.py there are three models
Customer
it has customer data like name, mobile_no, his_address etc..
Invoice
it has invoice data like customer_primary_key(required), delivery_address, billed_date etc..
total of the invoice item can be achieved by getting all "invoiceitem_set.all()" as items and from that sum of all add(items.item_subtotal)
InvoiceItem
it has invoiceitem data like invoice_primary_key(required), item_name, quantity, price etc..
the total is calculated before the model is saves
models.py
class Customer(models.Model):
pass
class Invoice(models.Model):
customer_id = models.ForeignKey(Customer, on_delete=models.PROTECT) # many - to - on relationship
invoice_id = models.CharField(....)
bill_note = models.TextField(....)
cash_pay = models.DecimalField(....)
upi_pay = models.DecimalField(....)
#property
def total_amount(self):
bill_total = 0
items = self.invoiceitem_set.all()
for item in items:
bill_total += item.item_subtotal
return bill_total
class InvoiceItem(models.Model):
invoice = models.ForeignKey(Invoice) # many - to - one relationship
item_name = models.CharField(....)
item_quantity = models.DecimalField(....)
item_price = models.DecimalField(....)
item_subtotal = models.DecimalField(....)
def save(self, *args, **kwargs):
self.item_subtotal = self.item_quantity * self.item_price
super(InvoiceItem, self).save(*args, **kwargs)
views.py (CreateView)
from django.db import transaction
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponse, HttpResponseRedirect
class InvoiceCreateView(LoginRequiredMixin, CreateView):
model = Invoice
form_class = InvoiceForm
template_name = 'billingsite/create_invoice.html'
def get_context_data(self, **kwargs):
context = super(InvoiceCreateView, self).get_context_data(**kwargs)
context['custom_title'] = "New Invoice"
temp = dict()
temp['customer_id'] = 0
if self.request.POST:
customer_id = int(self.request.POST.get('customer_id')) or False # custom clean method.
if customer_id:
customer_object = Customer.objects.get(pk=customer_id)
invoice_object = Invoice.objects.filter(customer_id=customer_object).order_by('-created_time').first()
temp = {
"customer_id": customer_id, "mobile_no": customer_object.mobile_no,
"searched_mobile_no": customer_object.raw_mobile_no,
"customer_name": customer_object.name, "gst_no": customer_object.goods_tax_id,
"pre_bal": customer_object.pending_balance, "purchased_date": "No Bills",
"created_date": customer_object.created_date.strftime(CUSTOM_DATE_FORMAT)
}
context['formset'] = InvoiceFormSet(self.request.POST)
else:
context['formset'] = InvoiceFormSet()
context['temp'] = temp
return context
def post(self, request, *args, **kwargs):
self.object = None
context = self.get_context_data()
customer_id = int(self.request.POST.get('customer_id')) # custom clean method.
if customer_id and customer_id != 0:
customer_object = Customer.objects.get(pk=customer_id)
form_class = self.get_form_class()
form = self.get_form(form_class)
formsets = context['formset']
with transaction.atomic():
form.instance.customer_id = customer_object
form.save(commit=False)
if form.is_valid() and formsets.is_valid():
self.object = form.save()
messages.success(self.request, f'Invoice is Submitted.')
return self.form_valid(form, formsets)
else:
return self.form_invalid(form, formsets)
return reverse_lazy('InvoiceList')
return self.render_to_response(context)
def form_valid(self, form, formsets):
formsets = formsets.save(commit=False)
for formset in formsets:
formset.invoice = self.object
formset.save()
return HttpResponseRedirect(self.get_success_url(self.object.pk))
def form_invalid(self, form, formsets):
return self.render_to_response(
self.get_context_data(form=form, formset=formsets))
def get_success_url(self, pk):
return reverse_lazy('ViewInvoice', kwargs={'pk': pk})
urls.py
urlpatterns = [
path('invoice/create/', views.InvoiceCreateView.as_view(), name='AddInvoice'),
]
forms.py
class InvoiceItemForm(forms.ModelForm):
item_name = forms.CharField(label=_('Product Name'))
item_subtotal = forms.IntegerField(required=False, label=_('Sub Total'))
class Meta:
model = InvoiceItem
fields = ['item_name', 'item_quantity', 'item_price', 'item_subtotal']
exclude = ()
widgets = {
"item_quantity": widgets.NumberInput(attrs={'step': '0.25'}),
"item_price": widgets.NumberInput(attrs={'step': '0.25'})
}
def __init__(self, *args, **kwargs):
super(InvoiceItemForm, self).__init__(*args, **kwargs)
self.fields['item_name'].widget.attrs['placeholder'] = 'Enter the food name'
self.fields['item_quantity'].widget.attrs['placeholder'] = 'Pieces'
self.fields['item_price'].widget.attrs['placeholder'] = 'in ₹'
self.fields['item_subtotal'].widget.attrs['readonly'] = True
self.fields['item_subtotal'].widget.attrs['tabindex'] = -1
for field in self.fields.values():
field.widget.attrs['class'] = 'form-control'
class InlineFormSet(forms.BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(InlineFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False
InvoiceFormSet = forms.inlineformset_factory(
Invoice, InvoiceItem, fields=('__all__'),
form=InvoiceItemForm, formset = InlineFormSet,
extra=0, min_num=1, can_delete=True
)
create_invoice.html
<fieldset>
<div class="text-dark py-4 table-responsive">
<div class="inline-formset inline-group" id="{{ formset.prefix }}-group" data-inline-type="tabular"
data-inline-formset="{
"name": "#{{ formset.prefix }}",
"options": {
"prefix": "{{ formset.prefix }}",
"addText": "Add+",
"deleteText": "<i class='bi bi-x'></i>",
"formCssClass": "dynamic-{{ formset.prefix }}",
}
}">
{% csrf_token %}
<div class="tabular inline-related">
{{ formset.management_form }}
<table id="invoice-table" class="as-table table table-xl table-hover caption">
<div class="d-block invalid-feedback">{{ formset.non_form_errors }}</div>
<caption>Add list of items.</caption>
{% for form in formset.forms %}
{% if forloop.first %}
<thead class="text-light">
<tr class="text-center">
<th scope="col">#</th>
{% for field in form.visible_fields %}
<th scope="col">{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% endif %}
<tr scope="row" class="form-row" id="{{ formset.prefix }}-{{ forloop.counter0 }}">
<th class="original">
<div class="index">{{ forloop.counter1 }}</div>
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
</th>
{% for field in form.visible_fields %}
<td class="field-{{ field.name }}">
{% if field.name != "DELETE" %}
{% if field.errors %}
{{ field|addCls:"is-invalid" }}
{% else %}
{{ field }}
{% endif %}
{% if field.errors %}
<div class="invalid-feedback">{{ field.errors }}</div>
{% endif %}
{% else %}
{{ field }}
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
<tr scope="row" class="form-row empty-row" id="{{ formset.prefix }}-empty">
<th class="original">
<div class="index">__prefix__</div>
{% for field in formset.empty_form.hidden_fields %}
{{ field }}
{% endfor %}
</th>
{% for field in formset.empty_form.visible_fields %}
<td class="field-{{ field.name }}">
{% if field.name != "DELETE" %}
{{ field }}
{% endif %}
</td>
{% endfor %}
</tr>
</tbody>
</table>
</div>
</div>
</div>
</fieldset>
Last week I faced a task related to listView and Forms in Django, I was wondering what is the best way (+Pythonic) to implement a search form inside a ListView, after I read 1 and 2 I got a main idea so I implemented a first solution and I would like to receive your Feedback. The goal here is to perform query by code field and keep the queryset in order to synchronize it with the pagination.
forms.py
class InscriptionQueryForm(forms.Form):
query_inscription = forms.CharField(label=_('Code'), required=False)
models.py
class Inscription(models.Model):
code = models.CharField(max_length=10, unique=True)
start_on = models.DateField()
finish_on = models.DateField()
active = models.BooleanField(default=False)
views.py
class InscriptionListView(ListView, FormMixin):
model = Inscription
paginate_by = 4
context_object_name = 'inscriptions'
form_class = InscriptionQueryForm
form = None
object_list = None
search = False
def get_queryset(self):
form = self.form_class(self.request.POST)
if form.is_valid() and self.request.method == 'POST':
self.request.session['query_inscription'] = \
form.cleaned_data['query_inscription']
return self.model.objects.filter(
code__icontains=form.cleaned_data['query_inscription']).\
order_by('-active')
if self.request.method == 'GET' and \
'query_inscription' in self.request.session:
return self.model.objects.filter(
code__icontains=self.request.session.get(
'query_inscription', '')).order_by('-active')
return self.model.objects.all().order_by('-active')
def get(self, request, *args, **kwargs):
# From ProcessFormMixin
self.form = self.get_form(self.form_class)
# From BaseListView
if self.request.GET.get('page', False) or self.search:
self.object_list = self.get_queryset()
else:
self.search = False
self.object_list = self.model.objects.all().order_by('-active')
if 'query_inscription' in self.request.session:
del self.request.session['query_inscription']
context = self.get_context_data(
object_list=self.object_list, form=self.form)
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
self.search = True
return self.get(request, *args, **kwargs)
What do you think guys?, I'm sure there are many others better ways.
Last week I faced the similar problem. My model was a common django User. However the same approach can be used here.
I guess you want to search through your Inscriptions using search field and paginating your results. And when you go through the pages you expect to see the results of your search query.
Your models.py stays as it is. We are going to modify forms.py as it will be able to initialize the search request
class InscriptionQueryForm(forms.Form):
query_inscription = forms.CharField(label='Code', required=False)
def __init__(self, query_inscription):
super(InscriptionQueryForm, self).__init__()
self.fields['query_inscription'].initial = query_inscription
Now let's look at the views.py. You don't need to store your request value in the session as schillingt mentioned in his comment. All you need is to init your form with the search request.
class InscriptionListView(ListView):
model = Inscription
paginate_by = 4
context_object_name = 'inscriptions'
query_inscription = ''
def get_context_data(self, **kwargs):
context = super(InscriptionListView, self).get_context_data(**kwargs)
context['form'] = InscriptionQueryForm(self.query_inscription)
# this parameter goes for the right pagination
context['search_request'] = ('query_inscription=' +
unicode(self.query_inscription))
return context
def get(self, request, *args, **kwargs):
self.query_inscription = request.GET.get('query_inscription', '')
return super(InscriptionListView, self).get(request, *args, **kwargs)
def get_queryset(self):
if not self.query_inscription:
inscription_list = Inscription.objects.all()
else:
inscription_list = Inscription.objects.filter(
code__icontains=self.query_inscription)
return inscription_list.order_by('-active')
And the last thing to mention is the pagination
inscription_list.html
<form action="">
{{ form.as_p }}
<input type="submit" value="search"/>
</form>
<hr/>
{% if inscriptions %}
{% for inscription in inscriptions %}
{{ inscription.code }} {{ inscription.start_on }} {{ inscription.finish_on }}
<hr/>
{% endfor %}
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
previous
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
next
{% endif %}
</span>
</div>
{% endif %}
{% endif %}
That's it!