Ordering a filter result in Django - django

I made a Search results function in view.py:
def search_results(request):
book_list = Book.objects.all()
book_filter = BookFilter(request.GET, queryset=book_list)
book_list = book_filter.qs.order_by('title')
return render(request, 'catalog/search_results.html', {'filter': book_list})
But it seems that the line
book_list = book_filter.qs.order_by('title')
doesn't have any effect on the result. Could someone help?
The BookFilter class looks like:
class BookFilter(django_filters.FilterSet):
Cim = django_filters.CharFilter(field_name="title",
lookup_expr='icontains',
label="Book's title")
Author = django_filters.CharFilter(field_name="Author__first_name",
lookup_expr='icontains',
label="Book's author")
Status = django_filters.CharFilter(field_name="Status",
lookup_expr='icontains',
label="Book's status")
class Meta:
model = Book
fields = '__all__'
The html containing the results looks like this:
{% for book in filter.qs %}
<tr style="background-color:{{ book.BackColor }}">
<td><a style="color: {{ book.ForeColor }}" href="{{ book.get_absolute_url }}">{{ book.title}}</a></td>
<td style="color: {{ book.ForeColor }}">{{ book.Author.last_name }} {{ book.Author.first_name }}</td>
</tr>
{% empty %}
<tr>
<td style="color: #aeb189" colspan="5">No books</td>
</tr>
{% endfor %}

Use OrderingFilter. Add order_by = OrderingFilter(fields=("title",)) to your filter class.

Related

Why can't I display my data filtered to active user?

I'm trying to display only data for the logged in user in my table. I can display everything using objects.all() but when I filter to the active user, it doesn't work. I have tried changing my context to refer to the queryset as a whole but I get an error saying that I can't perform get on a tuple.
If I have the context as is, I get an error saying 'QuerySet object has no attribute 'user'
Models.py:
class HealthStats(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
date = models.DateField(auto_now=True)
weight = models.DecimalField(max_digits=5, decimal_places=2)
run_distance = models.IntegerField(default=5)
run_time = models.TimeField()
class Meta:
db_table = 'health_stats'
ordering = ['-date']
def __str__(self):
return f"{self.user} | {self.date}"
Views.py:
def health_history(request):
queryset = HealthStats.objects.filter(user=request.user).values()
print(queryset)
print(type(queryset))
context = {
"user": queryset.user_id,
"weight": queryset.weight,
"date": queryset.date,
"run_distance": queryset.run_distance,
"run_time": queryset.run_time,
}
return (request, 'health_hub_history.html', context)
health_hub_history.html:
{% extends 'base.html' %}
{% load static %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12 text-center">
<h1>My Health History</h1>
</div>
</div>
</div>
<div class="container-fluid">
<div class="row justify-content-center">
<div class="col-auto text-center p-3">
<table class="table table-striped table-hover table-bordered">
<tr>
<td>User:</td>
<td>Weight (lbs):</td>
<td>Date:</td>
<td>Run Distance (km):</td>
<td>Run Time (HH:MM:SS):</td>
</tr>
{% for stat in queryset %}
<tr>
<td>{{ stat.user }}</td>
<td>{{ stat.weight }} </td>
<td>{{ stat.date }}</td>
<td>{{ stat.run_distance }}</td>
<td>{{ stat.run_time }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
{% endblock content %}
Ignore the print statements- just trying to figure out the issue, and will remove them once solved. Also aware that the .values() at the end of the queryset declaration is probably wrong- it's there for the same reasons.
I have been going around in circles for a few hours so any help would be appreciated!
HealthStats.objects.filter(user=request.user).values()
The above code gives you a QuerySet object, not your HealthStats object. Since User is a ForeignKey of HealthStats, this means that each User object can have any number of HealthStats objects associated to it (it can have 0, 1, 2, or more stats). This means the function health_history should be giving your front end a list of HealthStats objects.
def health_history(request):
serialized_stats = []
# `filter` also returns a `QuerySet` object which is already iterable.
for stats in HealthStats.objects.filter(user=request.user):
serialized_stats.append({
"user": stats.user_id,
"weight": stats.weight,
"date": stats.date,
"run_distance": stats.run_distance,
"run_time": stats.run_time,
})
context = {
"stats": serialized_stats
}
return (request, 'health_hub_history.html', context)
You'd probably also need to change your html logic to properly handle this.
It's because of your return statement. Try this:
return (request, 'health_hub_history.html', {'queryset': queryset})

Use Q object and Paginator together in Django

I've created View which filters data by search query given in textbox. As well as I used Paginator to show data divided into pages.
My problem is, when I filter data with Q object then and try to paginate by clicking the next button, all data is refreshed.
When I search text by Q object the URL becomes http://127.0.0.1:8000/mael/parties/?q=keyword
And from clicking the next button the URL becomes http://127.0.0.1:8000/mael/parties/?page=2
When I manually change URL http://127.0.0.1:8000/mael/parties/?q=keyword&page=2, then it works. But I don't know how to do this in code.
Is it possible to use Q object search and pagination together?
My View
from mael.models import PartyTotalBillsView
from django.views.generic import ListView
from django.db.models import Q
from django.http import HttpResponseRedirect
class PartyListView(ListView):
paginate_by = 2
model = PartyTotalBillsView
def parties(request):
# Show all records or searched query record
search_text = request.GET.get('q','')
try:
if search_text:
queryset = (Q(party_name__icontains=search_text))
party_list = PartyTotalBillsView.objects.filter(queryset).order_by('party_name')
else:
# Show all data if empty keyword is entered
party_list = PartyTotalBillsView.objects.order_by('party_name')
except PartyTotalBillsView.DoesNotExist:
party_list = None
paginator = Paginator(party_list, 2) # Show 2 rows per page: for Test
page_number = request.GET.get('page')
party_list = paginator.get_page(page_number)
return render(request, 'mael/parties.html', {'party_list': party_list})
Template file
<form id="search-form" method="get" action="/mael/parties/">
<input id="search-text" type="text" name="q" placeholder="Enter search keyword">
<input class="btn-search-party" type="submit" value="Search" />
</form>
<br/>
<table class="show-data">
<thead>
<tr>
<th>ID</th>
<th>Party Name</th>
<th>Total Bill Amount</th>
<th>Phone</th>
<th>Address</th>
<th></th>
</tr>
</thead>
{% if party_list %}
<tbody>
{% for party in party_list %}
<tr>
<td class="party-id">{{ party.party_id }}</td>
<td class="party-name">{{ party.party_name }}</td>
<td>{{ party.total_bills }}</td>
<td class="party-phone">{{ party.party_phone }}</td>
<td class="party-address">{{ party.party_address }}</td>
<td>
<button class="btn-modify" data-partyid="{{party.party_id}}" type="buttton">
Modify
</button>
</td>
</tr>
{% endfor %}
</tbody>
{% endif %}
</table>
<div class="pagination">
<span class="step-links">
{% if party_list.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ party_list.number }} of {{ party_list.paginator.num_pages }}
</span>
{% if party_list.has_next %}
next
last »
{% endif %}
</span>
</div>
Please do not use two views. A ListView can perform filtering as well:
class PartyListView(ListView):
paginate_by = 2
model = PartyTotalBillsView
template_name = 'mael/parties.html'
context_object_name = 'party_list'
def querystring(self):
qs = self.request.GET.copy()
qs.pop(self.page_kwarg, None)
return qs.urlencode()
def get_queryset(self):
qs = super().get_queryset()
if 'q' in self.request.GET:
qs = qs.filter(party_name__icontains=self.request.GET['q'])
return qs.order_by('party_name')
In the links for the previous and next pages, you then append the querystring of the view:
<span class="step-links">
{% if party_list.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% if party_list.has_next %}
next
last »
{% endif %}
</span>
Pagination & CBV
If you are using django generic ListView with paginate_by attribute, you don't need to build paginator instance. Either you use CBV (Class Based View) or Function Views but not both.
For HTML display create a _django_pager.html page to include in your list pages.
{% comment %}
https://getbootstrap.com/docs/4.1/components/pagination/
{% endcomment %}
{% if is_paginated %}
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item"><a class="page-link" href="?{% url_replace page=page_obj.previous_page_number %}">«</a></li>
{% else %}
<li class="page-item disabled">«</li>
{% endif %}
{% for i in page_obj.paginator.page_range %}
{% if page_obj.number == i %}
<li class="page-item active">{{ i }}<span class="sr-only">(current)</span></li>
{% else %}
<li class="page-item"><a class="page-link" href="?{% url_replace page=i %}">{{ i }}</a></li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item"><a class="page-link" href="?{% url_replace page=page_obj.next_page_number %}">»</a></li>
{% else %}
<li class="page-item disabled">»</li>
{% endif %}
</ul>
{% endif %}
Filtering
Q object is powerfull for building complex query but you have to specify DB field under which Q object applies.
Instead of hardcoding the form in HTML I recommand to use a Form Class. So in forms.py create a PartySearchForm
class PartySearchForm(forms.Form):
"""
Search in party
"""
search_text = forms.CharField(max_length=100,
required=False,
widget=forms.TextInput(attrs={
"class": "form-control",
"placeholder": "Search"
})
)
Option 1: filter queryset in view
class PartyListView(ListView):
model = PartyTotalBillsView
form = PartySearchForm
paginate_by = 100
def build_where(self):
where = Q(pk__gt=0)
if self.request.GET.get("search_text"):
search_list = self.request.GET.get("search_text", None).split()
for search_item in search_list:
where &= (
Q(party_name__icontains=search_item)
)
return where
def get_queryset(self):
qs = self.model.objects.all()
qswhere = qs.filter(self.build_where())
# first param must be request.GET or None (essential for the first load and initial values)
# https://www.peterbe.com/plog/initial-values-bound-django-form-rendered
self.form = PartySearchForm(self.request.GET or None)
return qswhere
In the build_where function you can add as many search field as you want. You can search on other DB field than party_name by adding the fields to the where variable.
where &= (
Q(party_name__icontains=search_item)
| Q(party_location__icontains=search_item)
)
You can also add other search fields than search_text in your form and add Q search on the where variable.
if self.request.GET.get("my_new_field"):
where &= Q(supplier=self.request.GET.get("my_new_field", ""))
Key point here is the get_queryset method where the displayed queryset is defined, ie: fetched, filtered and sorted (which could also be a method). .order_by('party_name') is not useful if you add a class Meta in models.py
class Meta:
verbose_name = "Let's go party"
ordering = ['party_name']
One other way to do would be to pass the queryset to the form and perform the search
Option 2: filter queryset in form
Looks even cleaner with the search logic in the SearchForm only!
PartyListView.get_queryset become
def get_queryset(self):
qs1 = self.model.objects.all()
self.form = PartySearchForm(self.request.GET, queryset=qs1)
qs = self.form.get_queryset(self.request.GET)
return qs
PartySearchForm become
class PartySearchForm(forms.Form):
"""
Search in party
"""
search_text = forms.CharField(max_length=100,
required=False,
widget=forms.TextInput(attrs={
"class": "form-control",
"placeholder": "Search"
})
)
def __init__(self, *args, **kwargs):
"""
Takes an option named argument ``queryset`` as the base queryset used in
the ``get_queryset`` method.
"""
self.queryset = kwargs.pop("queryset", None)
super().__init__(*args, **kwargs)
def get_queryset(self, request):
where = Q(pk__gt=0)
# is_valid() check is important to get access to cleaned_data
if not self.is_valid():
return self.queryset
search_text = self.cleaned_data.get("search_text").strip()
if search_text:
search_list = search_text.split()
for search_item in search_list:
where &= (
Q(party_name__icontains=search_item)
)
qs = self.queryset.filter(where)
return qs.distinct()
Eventually, if you are using Postgres DB and want to go deeper with Text Search you can implement Django full text search. Pros & cons can be gained by reading this.

How to Query DB using .object in Django

I have few categories. Say Electronics and Toy. and i have mutiple shops in a mall. A shop is saved with a foreign key(category). Now in the navigation bar.. i want to list stores based on their categories. Thanks in anticipation
models.py
class ShopCategories(models.Model):
category = models.CharField(max_length=50, unique=True,)
def __str__(self):
return self.category
class NewShop(models.Model):
category = models.ForeignKey(ShopCategories)
name = models.CharField(max_length=100, unique=True)
tagline = models.CharField(max_length=50, default='Enter tagline here2')
description = models.TextField(default='enter shop description')
def __str__(self):
return self.name
views.py
def basefile(request):
shop_cat = NewShop.objects.filter(category_id=1)
shop_name = NewShop.objects.filter(name=shop_cat)
return render_to_response('base.html', {'Shopname':shop_name, 'Shopcat':shop_cat})
base.html
{% for category_id in Shopcat %}
<li>{{ Shopname }}</l>
{% endfor %}
To get all store use below query.
shopcat = NewShop.objects.filter(category__category="category name")
base.html
{% for shop in shopcat %}
<li>{{ shop.name }}</l>
{% endfor %}
Try do this:
urls.py
urlpatterns = [
...
url(r'^get_shops_by_category/(?P<id_category>\d+)/$', views.get_shops_by_category, name = "get_shops_by_category"),
]
views.py
def basefile(request):
categories = ShopCategories.objects.all()
shop_name = NewShop.objects.filter(name=shop_cat)
return render_to_response('base.html', {'Shopname':shop_name, 'categories': categories})
def get_shops_by_category(request, **kwargs):
categories = ShopCategories.objects.all()
current_category = ShopCategories.objects.get(id = kwargs['id_category']
shops = NewShop.objects.filter(category = current_category)
return render_to_response('base.html', {'shops': shops, 'categories': categories, 'current_category' current_category})
base.html
...
<select>
{% for category in categories %}
{% if current_category %}
<option value="{{ category.id }}">{{ category.category }}</option>
{% else %}
<option value="{{ category.id }}" onclick="location.href = '{% url 'your_app:your_url' category.id %}'">{{ category.category }}</option>
{% endfor %}
</select>
{% if shops %}
<table>
<thead>
<tr>
<th>Shop name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for shop in shops %}
<tr>
<td>{{ shop.name }}</td>
<td><!-- Buttons or links, ect ... --></td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
...
Thanks Guys. I know this isn't the best method but i was able to fix it this way
models.py
class ShopCategories(models.Model):
category = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.category
class NewShop(models.Model):
category = models.ForeignKey(ShopCategories)
name = models.CharField(max_length=100, unique=True)
tagline = models.CharField(max_length=50, default='Enter tagline here2')
description = models.TextField(default='enter shop description')
def __str__(self):
return self.name
views.py
def basefile(request):
cat1 = NewShop.objects.filter(category_id=1)
cat2 = NewShop.objects.filter(category_id=2)
cat3 = NewShop.objects.filter(category_id=3)
cat4 = NewShop.objects.filter(category_id=4)
shop_name1 = ShopCategories.objects.filter(id=1)
shop_name2 = ShopCategories.objects.filter(id=2)
shop_name3 = ShopCategories.objects.filter(id=3)
shop_name4 = ShopCategories.objects.filter(id=4)
return render_to_response('base.html', {'Shop_cat1':cat1, 'Shop_cat2':cat2, 'Shop_cat3':cat3,
'Shop_cat4':cat4,'shop_name1':shop_name1, 'shop_name2':shop_name2,
'shop_name3':shop_name3, 'shop_name4':shop_name4})
base.html
{% for shop_name1 in shop_name1 %}
<li>
<h3> {{ shop_name1 }}</h3>
</li>
{% endfor %}
{% for Shop_cat1 in Shop_cat1 %}
<li>{{ Shop_cat1 }}</li>
{% endfor %}

Django model cross reference in templatre

Ok So my mind is going to mush...
I have 2 models. One is a card location and the other holds card types. I've got a view and template which display's the cards in a specific chassis. What I can't seem to get to work is the foreign key reference. I want to display the CardType.sName in the template.
I'm certain i've just done something stupid ...
Models.py:
class CardLocation(models.Model):
chassis = models.ForeignKey(Chassis)
slot = models.CharField(max_length=20)
slot_sub = models.CharField(max_length=20)
CardType = models.ForeignKey(CardType)
PartNum = models.CharField(max_length=200)
ProdID = models.CharField(max_length=200)
versID = models.CharField(max_length=200)
serialNum = models.CharField(max_length=200)
cleiCode = models.CharField(max_length=200)
lastSeen = models.DateTimeField()
isActive = models.BooleanField(default=0)
def __unicode__(self):
return self.slot
class CardType(models.Model):
sName = models.CharField(max_length=5)
lName = models.CharField(max_length=200)
description = models.CharField(max_length=200)
def __unicode__(self):
return self.sName
views.py
class DetailView(generic.ListView):
model = CardLocation
template_name = 'chassis/detail.html'
context_object_name = 'cardLoc'
def get_queryset(self):
#chassis_id = get_object_or_404(CardLocation, chassis_id__iexact=self.args[0])
chassis_id = self.args[0]
return CardLocation.objects.filter(chassis_id=chassis_id)
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(DetailView, self).get_context_data(**kwargs)
# Add in the
context['chassisQ'] = Chassis.objects.get(id=self.args[0])
#context['CardType'] = CardType.objects.order_by()
return context
detail.html
{% load staticfiles %}
<h2>
<table>
<tr><td>Name:</td><td>{{ chassisQ.name }}<td></tr>
<tr><td>Owner:</td><td>{{ chassisQ.owner }}<td></tr>
<tr><td>ip Adress:</td><td>{{ chassisQ.ipAddr }}<td></tr>
<tr><td>Last updated:</td><td>{{ chassisQ.lastSeen }}<td></tr>
</table>
</h2>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
{% if cardLoc %}
<table border=1>
<tr><td>slot</td><td>Type</td><td>Part Number</td><td>Product ID/</td><td>Version ID</td><td>Serial Num</td><td>CLEI code</td></tr>
{% for card in cardLoc %}
<tr>
<td align="right">{{ card.slot }}</td>
<td>Type {{ card.cardtype__set.all.sName }} </td> <!-- DISPLAY sName HERE -->
<td>{{ card.PartNum }}</td>
<td align="right">{{ card.ProdID }}/</td>
<td align="left">{{ card.versID }}</td>
<td>{{ card.serialNum }}</td>
<td>{{ card.cleiCode }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<p>No cards are available...</p>
{% endif %}
A Card has numerous CardTypes (as it's a ForeignKey relationship), not just one. You have:
<td>Type {{ card.cardtype__set.all.sName }} </td>
You need to loop through all the CardTypes related to the Card:
{% for card in cardLoc %}
...
{% for cardtype in card.cardtype__set.all %}
<td>Type {{ cardtype.sName }}</td>
{% endfor %}
...
{% endfor %}

generate and calling boolean field forms in django template

I want to generate django boolean form(checkbox) by for loop(of django Templates) and call it(to views) to delete checked data.
I writed some codes:
(but it don't work at if request.POST['id_checkbox{}'.format(b.id)]: in views)
my codes:
Template
<form role="form" method="post">
{% csrf_token %}
{% render_field form.action %}
<button type="submit" class="btn btn-default">Submit</button>
<table class="table table-striped text-right nimargin">
<tr>
<th class="text-right"> </th>
<th class="text-right">row</th>
<th class="text-right">title</th>
<th class="text-right">publication_date</th>
</tr>
{% for b in obj %}
<tr>
<td><input type="checkbox" name="id_checkbox_{{ b.id }}"></td>
<td>{{ b.id }}</td>
<td>{{ b.title }}</td>
<td>{{ b.publication_date }}</td>
</tr>
{% endfor %}
</table>
</form>
Views
class book_showForm(forms.Form):
action = forms.ChoiceField(label='go:', choices=(('1', '----'), ('2', 'delete'), ))
selection = forms.BooleanField(required=False, )
def libra_book(request):
if request.method == 'POST':
sbform = book_showForm(request.POST)
if sbform.is_valid():
for b in Book.objects.all():
if request.POST['id_checkbox_{}'.format(b.id)]:
Book.objects.filter(id=b.id).delete()
return HttpResponseRedirect('/libra/book/')
else:
sbform = book_showForm()
return render(request, 'libra_book.html', {'obj': Book.objects.all(), 'form': sbform})
Model
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.CharField(max_length=20)
publication_date = models.DateField()
how can i use request.POST to understand that what is value of the checkbox(True or False)?
try to change your checkbox to this
<input type="checkbox" name="checks[]" value="{{ b.id }}">
then on your view,something like this
list_of_checks = request.POST.getlist('checks') # output should be list
for check_book_id in list_of_checks: # loop it
b = Book.objects.get(id=check_book_id) # get the object then
b.delete() # delete it
I find the answer that i must use request.POST.get('id_checkbox_{}'.format(b.id), default=False) Instead of request.POST['id_checkbox_{}'.format(b.id)]
because request.POST['id_checkbox_{}'.format(b.id)] [or request.POST.__getitem__('id_checkbox_{}'.format(b.id))] Raises django.utils.datastructures.MultiValueDictKeyError if the key does not exist.
and must set defout request.POST.get('id_checkbox_{}'.format(b.id), default=False)
see HttpRequest.POST here
and see QueryDict.get(key, default=None) and QueryDict.__getitem__(key) QueryDict.get(key, default=None)