How to paginate multiple queries in a single function-based-view or class-based-view in django? - django

I have a search function that queries multiple models. I have the expected results displayed in a html template and so far all is fine. The problem is that I want to paginate the results using django's built in Pagination class. Pagination with multiple models is where I'm now stuck. I have other class based views working well paginating single models.
def search_results(request):
if request.method == 'POST':
searched = request.POST['searched']
books = Book.objects.filter(description__icontains=searched,
title__icontains=searched).order_by('-id')
sermons = Sermon.objects.filter(description__icontains=searched,
title__icontains=searched).order_by('-id')
other_sermons = SermonsByOtherFathers.objects.filter(description__icontains=searched,
title__icontains=searched).order_by('id')
other_books = BooksByOtherFathers.objects.filter(description__icontains=searched,
title__icontains=searched).order_by('-id')
context = {
'searched': searched,
'sermons': sermons,
'other_sermons': other_sermons,
'books': books,
'other_books': other_books,
}
if searched == "":
return HttpResponse('Please type something in the search input.')
return render(request, "search_results.html", context)
This is a simplified version of my html template.
{% for book in books %}
<tr>
<td>
{{ book.title }}
<p> {{book.author}} </p>
<p> {{ book.category }} </p>
<p> {{ book.description }} </p>
</td>
</tr>
{% endfor %}
<-- ...and the same loops goes for the rest of the other querysets. -->
{% for book in other_books %}
<-- code here -->
{% endfor %}
{% for sermon in sermons %}
<-- code here -->
{% endfor %}
{% for sermon in other_sermons %}
<-- code here -->
{% endfor %}
Any help with the django multiple-model pagination part will be appreciated.

Related

Get Data Problem with Foreignkey Models QuerySet (Django)

I am making django practising. I found a repo and edited to myself. When i came to the get data from models with foreignkey, i get query instead of data. I think my function is a little bit spagetti or my template file.
Full repo is here https://github.com/eseymenler/demo2
Here is the output of my codes. First red square is query, but i want second red square datas.
Hasta Adı: {{ patient.name }}
Hastanın Sağlık Problemi: {{ prob }}
Hastanın Boyu: {{ vital }}
Hastanın Sigara Kullanım Durumu: {{ social }}
First data {{ patient.name }} is very good for me. Thats what i want.
But when i get write {{ prob.problem }} it gives me nothing. So where is my fault.
But also when i make for loop like this, i get data which i want. So how can i fix this.
{% for y in prob %}
{{ y.problem }}
{% endfor %}
And my views.py
#login_required()
def patienttumbilgiListView(request, id):
patient = Patient.objects.get(aadhaarId = id)
prob = ProblemList.objects.filter(patient = patient)
vital = VitalSign.objects.filter(patient = patient)
social = SocialHistory.objects.filter(patient = patient)
return render(
request, 'patient_records/patient-problem-tum.html',
context={'prob': prob, 'vital': vital, 'social': social, 'patient': patient })
and my template file
{% extends 'base.html' %}
{% load static %}
{% load bootstrap4 %}
{% load i18n %}
{% block title %}problem-list-view{% endblock title %}
{% block content %}
<div class="container">
Hasta Adı: {{ patient.name }}</br>
Hastanın Sağlık Problemi: {{ prob }}<br>
Hastanın Boyu: {{ vital }}<br>
Hastanın Sigara Kullanım Durumu: {{ social }} <br></div>
<br>
<br>
<div class="container">
<style type="text/css">
.tg {border-collapse:collapse;border-spacing:0;}
.tg td{border-color:black;border-style:solid;border-width:1px;font-family:Arial, sans-serif;font-size:14px;
overflow:hidden;padding:10px 5px;word-break:normal;}
.tg th{border-color:black;border-style:solid;border-width:1px;font-family:Arial, sans-serif;font-size:14px;
font-weight:normal;overflow:hidden;padding:10px 5px;word-break:normal;}
.tg .tg-0lax{text-align:left;vertical-align:top}
</style>
<table class="tg">
<thead>
<tr>
<th class="tg-0lax">Hasta Adı:</th>
<th class="tg-0lax">{{ patient.name }}</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tg-0lax">Hastanın Sağlık Problemi:</td>
<td class="tg-0lax">{% for y in prob %}<br> <br> {{ y.problem }}<br> <br> <br>{% endfor %}</td>
</tr>
<tr>
<td class="tg-0lax">Hastanın Boyu:</td>
<td class="tg-0lax">{% for x in vital %}<br> <br> {{ x.height }}<br> <br> <br>{% endfor %}</td>
</tr>
<tr>
<td class="tg-0lax">Sigara Kullanımı:</td>
<td class="tg-0lax">{% for item in social %}<br> <br><br> {{ item.tobacco_smoking_status }}<br> <br> <br>{% endfor %}</td>
</tr>
</tbody>
</table>
</div>
<br>
<br>
{% endblock %}
Roughly What You Had
So your code is:
#login_required()
def patienttumbilgiListView(request, id):
patient = Patient.objects.get(aadhaarId = id)
prob = ProblemList.objects.filter(patient = patient)
vital = VitalSign.objects.filter(patient = patient)
social = SocialHistory.objects.filter(patient = patient)
return render(
request, 'patient_records/patient-problem-tum.html',
context={'prob': prob, 'vital': vital, 'social': social, 'patient': patient })
{% for y in prob %}
{{y.problem}}
{% endfor %}
{% for x in vital %}
{{x.height}}
{% endfor %}
{% for item in social %}
{{item.tobacco_smoking_status}}
{% endfor %}
Some Friendly Suggestions
"id" is a reserved word / function - I suggest changing id to maybe pid in your code.
Return an error to your view - You don't necessarily have to make this change, but I think its a good idea, and can even help with troubleshooting. I mean you should probably be returning an error or something to this chart if you can't find these objects with those ids in your database.
"get()" throws a DoesNotExist error if the id isn't found - You should catch the error and return it to the console you are making or do something like that.
views.py
#login_required()
def patienttumbilgiListView(request, pid):
context = {}
try:
patient = Patient.objects.get(aadhaarId = pid)
except DoesNotExist:
context['error'] = f"Could not find patient with id '{pid}'."
else:
context['prob'] = ProblemList.objects.filter(patient=patient)
context['vital'] = VitalSign.objects.filter(patient=patient)
context['social'] = SocialHistory.objects.filter(patient=patient)
context['patient'] = patient
return render(request, 'patient_records/patient-problem-tum.html', context=context)
template.html
{% if error %}
{{error}}
{% endif %}
{% for y in prob %}
{{y.problem}}
{% empty %}
Could not find problems.
{% endfor %}
{% for x in vital %}
{{x.height}}
{% empty %}
Could not find vitals.
{% endfor %}
{% for item in social %}
{{item.tobacco_smoking_status}}
{% empty %}
Could not find socials.
{% endfor %}
What Your Problem Is And How To Solve It
I am making django practising. I found a repo and edited to myself. When i came to the get data from models with foreignkey, i get query instead of data.
Based on this quote, I am assuming you want to know why the data is being returned as a queryset instead of as a singular result. From the docs:
filter() will always give you a QuerySet, even if only a single object matches the query - in this case, it will be a QuerySet containing a single element. If you know there is only one object that matches your query, you can use the get() method on a Manager which returns the object directly: one_entry = Entry.objects.get(pk=1).
Which is why you should use the for loop in your template.html. If you don't want to use that, then you should preprocess this data either by using get() instead of filter() or simply, in the context, send the [0] element to the template from your view.
So, to clarify, if you want to use {{prob.problem}} in your template, you need to change your view to either do prob = ProblemList.objects.filter(patient = patient)[0] or prob = ProblemList.objects.get(patient = patient). If you choose the first, you should check the length (prob = prob[0] if len(prob) == 1 else None), otherwise, if you choose the latter, you should wrap it with a try, except, else clause like this:
try:
prob = ProblemList.objects.get(patient=patient)
except DoesNotExist:
context['error'] = "Could not find ProblemList."
print("Does Not Exist")
else:
context['prob'] = prob
or you could just use the for loop that you have in place, I don't know how many entries you are expecting the query set to have per patient.
If I need to further clarify anything, please let me know.

Iterate context variables in Django templates based on user's attributes

Is it possible to iterate over a list of three context variables shown below so that depending on the user's attribute (in this instance grade, Grade 10, Grade 11, Grade 12). If current user's grade attribute is Grade 10 then they only get: context['grade10'] from below.
Current view:
class SumListView(ListView):
model = Summaries
template_name = 'exam/summary.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['grade10'] = Summary.objects.filter(grade='Grade 10')
context['grade11'] = Summary.objects.filter(grade='Grade 11')
context['grade12'] = Summary.objects.filter(grade='Grade 12')
return context'''
Current html template block:
{% block content %}
{% for summary in grade10 %}
<div class="container>
{{ summary.content }}
</div>
{% endfor %}
{% endblock content %}
I tried this but it breaks the code since for loops and iterations are mixing:
{% block content %}
{% if grade10 %}
{% for summary in grade10 %}
<div class="container>
{{ summary.content }}
</div>
{% endfor %}
{% elif grade11 %}
{% for summary in grade10 %}
<div class="container>
{{ summary.content }}
</div>
{% endfor %}
{% else grade12 %}
{% for summary in grade10 %}
<div class="container>
{{ summary.content }}
</div>
{% endfor %}
{% enif %}
{% endblock content %}
What is the best way to go about this?
I know I can write different urls for each context which in turn renders a different template but that does not seem efficient and I hope there is a better way to do that. Any help highly appreciated, including documentation pointers.
You can perform this logic in your view, you can use a single context variable but change it's contents based on your logic
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user_grade = self.request.user.get_grade() # Replace with how you access the user's grade
context['grades'] = Summary.objects.filter(grade=user_grade)
return context
Then loop over grades in your template

Django nested QuerySet yielding nothing

I am using Django 1.8 with Postgres 9.2 on a Windows 8 machine.
I have two pieces of nearly identical code from two of my projects. One works and the other doesn't.
Here's the code that works:
# views.py
from django.shortcuts import render
from models import Artist, Track
def music(request):
artists = Artist.objects.all().order_by('orderName')
artistTrackCollections = []
for artist in artists:
artist.tracks = Track.objects.filter(artist=artist).order_by('order')
artistTrackCollections.append(artist)
return render(request, 'music.html', {'artistTrackCollections': artistTrackCollections,})
And the relevant template code:
{% for artist in artistTrackCollections %}
<dl>
<dt>
{% if artist.website %}
<h2>{{ artist.name }}</h2>
{% else %}
<h2>{{ artist.name }}</h2>
{% endif %}
</dt>
<dd>
<ul>
{% for track in artist.tracks %}
<li>“{{ track.title }}”
<i>({{ track.album.title }})</i>
{% endfor %}
</ul>
</dd>
</dl>
{% endfor %}
Now here's pretty much the exact same code from a different project of mine that doesn't work anymore:
def index(request):
productList = PartModel.objects.filter(isBuild=True, isActive=True).order_by('name')
productCollection = []
for product in productList:
product.part_list = product.buildpart.all().order_by('family__type')[:5]
productCollection.append(product)
return render(request, 'index.html', { 'productCollection': productCollection, })
and its corresponding template:
{% for product in productCollection %}
<p>{{ product.name }}
<p>${{ product.price }}
<ul>
{% for part in product.part_list %}
<li>{{ part.family.type.name }} | {{ part.name }}
{% endfor %}
</ul>
{% endfor %}
This code used to work but now it doesn't. In the code that works I succeed in going through each artist and attaching that artist's tracks. In the code that fails I try to go through each PartModel instance that is a build and get that PartModel instance's corresponding parts. They are identical as far as I can tell. productCollection gets populated but the part_list data for some reason is blank. This leads me to believe the problem is with this line:
product.part_list = product.buildpart.all().order_by('family__type')[:5]
But I cannot discern the difference from this line:
artist.tracks = Track.objects.filter(artist=artist).order_by('order')
Thanks in advance for any help!

Form to display checkbox with title without the ability to edit title

I've been playing around with Django forms and it's a bit of a headache. I came to a point where I can display my form properly but I only want to edit part of it's data, not all of them.
Specifically: I have a Product model that has a title and a featured bool field. I want a form that displays all the products names with a tickbox next to it. The user is able to edit the featured property of each Product on the same form, but only that! My problem is that with the current setup, the displayed title is also an input field, not a simple text. If I change the template to display {{ productForm.title.value }} instead of {{ productForm.title }} it displays it as a text but the form failes the validation (It requires the title to have a value, since it cannot be null).
Model is plain simple, title and featured fields with CharField and BooleanField types. Validation branch is also not taken care of in the sample but "is_valid()" returns false.
views.py
def featured_product(request):
FeaturedProductFormSet = modelformset_factory(Product, fields=('title', 'featured'))
if request.method == "POST":
productForms = FeaturedProductFormSet(request.POST)
if productForms.is_valid():
productForms.save()
return redirect('/admin/')
else:
productForms = FeaturedProductFormSet()
return render_to_response(
'vetco_app/featured_products.html',
{
'productForms': productForms,
},
context_instance=RequestContext(request)
)
featured_products.html
<form action="" method="post">
<table>
<thead>
<th>
Termék neve
</th>
<th>
Kiemelt
</th>
</thead>
<tbody>
{% for productForm in productForms %}
{% if not forloop.last %}
<tr>
<td>
{{ productForm.id }}
{{ productForm.title }}
</td>
<td>
{{ productForm.featured }}
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
<p>
{{ productForms.management_form }}
{% csrf_token %}
<button type="submit" name="action" value="Save">Save</button>
</p>
How to make it display only the text of the title without the need to validate it?
Simply provide context:
from django.template import RequestContext
context = RequestContext(request, {"your_model":your_model")
now in templates just write:
{{ your_model.some_attribute }}

Better way to re-order Django form errors?

Is there a better, more "Django-istic", way to put Django form error messages in a particular order than the technique shown below? I have a form with ten fields in it. If the user doesn't fill out a required field or if they enter invalid data, I iterate through the form's error fields which I've put in a custom error list by displaying a red "x" and the error message(s) at the top and a red "x" next to the invalid field(s) below:
{# template.html #}
<form method="post" action=".">
{% csrf_token %}
{# Non-field errors removed for clarity #}
{# Iterate through error messages in custom ordered error list #}
{% for error_message in ordered_error_list %}
<img src='/static/img/red_x.png' alt='Error'></img>
{{ error_message }}<br>
{% endfor %}
<br>
{% for field in form.visible_fields %}
<div>
{{ field.label_tag }}<br />
{{ field }}
{% if field.errors %}
<img src='/static/img/red_x.png' alt='Error'></img>
{% endif %}
</div>
{% endfor %}
{# Hidden fields removed for clarity #}
<p><input type="submit" value="Continue" /></p>
</form>
Here's my view and helper function:
# views.py
def create_member_profile(request):
if request.method == "POST":
form = CreateMemberProfileForm(request.POST)
if form.is_valid():
# Process form data and redirect to next page...
return HttpResponseRedirect(reverse('review-member-profile'))
# Before I re-POST the form and display the errors, I'll put the errors in order
else:
ordered_error_list = put_member_profile_errors_in_order(form)
else:
form = CreateMemberProfileForm()
return render_to_response(template, locals(), context_instance=RequestContext(request))
def put_member_profile_errors_in_order(form):
errors_in_order = [] # List
for error in form['field_1'].errors:
errors_in_order.append(error)
for error in form['field_2'].errors:
errors_in_order.append(error)
for error in form['field_3'].errors:
errors_in_order.append(error)
# ...
for error in form['field_10'].errors:
errors_in_order.append(error)
return errors_in_order
The reason this all seems necessary is that form.errors is a dictionary and Python dictionaries, by definition, are unordered. However, as I said, I want any error messages at the top to be displayed in the same order as the form fields they refer to. I couldn't find any form.errors attributes that would allow me to re-order the form errors. I don't like the "for error in form[]" blocks but they seem to be required if I want to strip the HTML tag from the beginning of each error. Also note that I'm not checking for errors by putting each "for" loop inside an "if form['field'].errors" block because omitting this doesn't make a difference and I want this code to run as fast as possible.
Is there a better way to do this? Thanks!
You could do something like this. Same idea s you are using (two for loops).
<!-- near the top -->
<tr>
{% for field in form %}
<td>{{ field.label_tag }}: {{ field.errors }}</td>
{% endfor %}
</tr>
<!-- your form -->
{% for field in form %}
<tr>
<td>{{ field.label_tag }}:</td>
<td>
{{ field }}
{{ field.help_text }}
</td>
</tr>
{% endfor %}
Format this as you wish, but I think that kind of gives you an idea. You can also customize the error messages in your form clean:
from django import forms
YourForm(forms.Form):
....
def field_clean(self): # or use clean(self) for multiple fields
....
raise forms.ValidationError(u'Your custom error')
....
More on form field validation from Django docs.