django listview order by model property - python

I have a model called Lap with a property calculated using data from the table
class Lap(models.Model):
team=models.ForeignKey('Team', models.DO_NOTHING)
timestamp=models.IntegerField()
num=models.IntegerField()
#property
def laptime(self):
endtime=self.timestamp
starttime=Lap.objects.get(team=self.team, num=self.num-1).timestamp)
return time.timedelta(seconds=endtime-starttime)
I am trying to create a listview for the model
class FastestLap(ListView):
model=Lap
template_name='fastestlaps.html'
context_object_name='laps'
I want to order the list view by the laptime property.
sorting by a column can be done using the ordering variable or by creating a get_queryset method and doing queryset.order_by(fieldname) in that method but I cant find a way to order by the property. How do I order by laptime?

Use Window, F and Lag functions and annotate so:
from django.db.models import Window, F
from django.db.models.functions import Lag
# from .models import Lap
# from django.views.generic import ListView
class FastestLap(ListView):
queryset = Lap.objects.annotate(
starttime=Window(
expression=Lag('timestamp', default=0),
partition_by=['team'],
order_by=F('num').asc(),
)
).annotate(
laptime=F('timestamp') - F('starttime')
).order_by('laptime')
template_name = 'fastestlaps.html'
context_object_name = 'laps'
References:
https://docs.djangoproject.com/en/4.1/ref/models/expressions/
https://docs.djangoproject.com/en/4.1/ref/models/expressions/#f-expressions
https://docs.djangoproject.com/en/4.1/ref/models/database-functions/#lag

Related

Apply sort using python(not QuerySet) to models using ListView pagination

Apply sort using python(not QuerySet) to models using ListView pagination
I use Django ListView with pagination feature to display models.
Since I need to sort list of models using method field(need to send param to method thus it must be a method) return value, I cannot complete sorting using get_query_set method.
Since get_context_data is called for each page, I cannot add sort to there.
class SpotRankingView(ListView):
model = Spot
template_name = 'places/spot_list.html'
paginate_by = 5
def get_context_data(self, **kwargs):
context = super(SpotRankingView, self).get_context_data(**kwargs)
# This is called for every page
# So I cannot add sort here
return context
def get_queryset(self):
# I need method return value to sort models
#
# ex. I need something like blow to sort models
# method category as blow is a calculated property on model.
# spot.category(category_name = 'Relaxing').points
#
# Those cannot be done by QuerySet so I caanot add that
# sorting here
return Spot.objects.all()
How can I sort models using python list sorting & make them paginate?
Not sure what kind of sorting you need. There are a lot of sorting methods like by date, by id, by reverse date. Since you are using Class Based Views you can use ArchiveIndexView like below.
from django.views.generic.dates import ArchiveIndexView
class SpotRankingView(ArchiveIndexView):
model = Spot
template_name = 'places/spot_list.html'
paginate_by = 5
date_field = "date_created"
You need to set allow_future to True if you want to show objects with date in future. If you still don't understand then follow the reference.
You also can do this for your list view like:
class SpotRankingView(ListView):
model = Spot
template_name = 'places/spot_list.html'
paginate_by = 5
def get_ordering(self):
ordering = self.request.GET.get('ordering', '-date_created')

Override imported class variables - django/python

I need to override variables (or pass dynamic data) to imported class.
filters.py
import django_filters
from .models import Gate, Tram, OperationArea, Bogie
from distutils.util import strtobool
from django import forms
class GateFilter(django_filters.FilterSet):
# Prepare dynamic lists with choices
tram_list = [(id, number) for id, number in Tram.objects.all().values_list('id', 'number')]
bogie_list = [(id, number) for id, number in Bogie.objects.all().values_list('id', 'number')]
area_list = [(id, area) for id, area in OperationArea.objects.all().values_list('id', 'area')]
# Generate fields
tram = django_filters.MultipleChoiceFilter(choices=tram_list, label=u'Tramwaj')
car = django_filters.MultipleChoiceFilter(choices=Gate.CAR_SYMBOLS, label=u'Człon')
bogie = django_filters.MultipleChoiceFilter(choices=bogie_list, label=u'Wózek')
bogie_type = django_filters.MultipleChoiceFilter(choices=Gate.BOGIE_TYPES, label=u'Typ wózka')
area = django_filters.MultipleChoiceFilter(choices=area_list, label=u'Obszar')
operation_no = django_filters.CharFilter(label=u'Numer operacji', widget=forms.TextInput(attrs={'size': '16px'}))
status = django_filters.MultipleChoiceFilter(choices=Gate.GATE_STATUSES, label=u'Status')
rating = django_filters.MultipleChoiceFilter(choices=Gate.GATE_GRADES, label=u'Ocena')
class Meta:
pass
views.py
from .filters import GateFilter
class GateListView(generic.ListView):
queryset = None
gate_type = None
template_name = 'qapp/gate/list.html'
context_object_name = 'gate_list'
paginate_by = 20
def get_queryset(self):
# Type is stored in database as big-letter word, so 'bjc' != 'BJC'.
if self.gate_type.upper() == 'BJW':
ordering = ['bogie', 'bogie_type']
else:
ordering = ['tram', 'car']
queryset = Gate.objects.filter(type=self.gate_type.upper()).order_by(*ordering)
self.gate_list = GateFilter(self.request.GET, queryset=queryset)
return self.gate_list.qs.distinct()
def get_context_data(self, **kwargs):
context = super(GateListView, self).get_context_data(**kwargs)
# Return Gate.type to template.
context['gate_type'] = self.gate_type
# Return object (for generating form) to template.
context['gate_list_filter'] = self.gate_list
return context
As you can see, in the filters.py, the data for variables tram_list, bogie_list and area_list are dynamic (fetched from database).
But during importing this class to views.py, this data becomes static.
I tried to override this values:
using #classmethod decorator in class GateFilter, and calling it
before setting self.gate_list object,
in views.py using GateFilter.tram_list (and the rest) notation,
No luck.
I can't use reload() function, due to import type (from .filters import GateFilter).
Currently for update lists in filters.py I need to rerun whole app.
This is unacceptable for business logic of my app.
This is the wrong approach. Rather, you should be using the filters that are aware of querysets and that evaluate them when required: ModelChoiceFilter and ModelMultipleChoiceFilter.
class GateFilter(django_filters.FilterSet):
team = django_filters.ModelMultipleChoiceFilter(queryset=Tram.objects.all())

Add django-filter to class-based-view to allow user filter results on frontend

Unfortunatelly Django doesn't have super-magic Drupal's analog for Views module https://www.drupal.org/project/views (by the way other cms also doesn't have it) so we all need write views in code and add content filters like everyone see in Django Admin by hand.
I need to add filters with dropdowns for Charfield and datepopup widget for DateTime field in my class-based-view, i found django-filter for this http://django-filter.readthedocs.org/en/latest/usage.html
But in docs no example how to setup it with CBW, only with function views.
views.py:
class VkwallpostListView(ListView):
model = Vkwallpost
context_object_name = "vk_list"
def get_template_names(self):
return ["vk_list.html"]
def get_context_data(self, **kwargs):
articles = Vkwallpost.objects.order_by("-date_created")[:5]
videos = Fbpagepost.objects.order_by("-date_created")[:5]
items = list(articles) + list(videos)
items.sort(key=lambda i: i.date_created, reverse=True)
return {"vk_fb_list": items[:5]}
def get_queryset(self):
wallposts = Vkwallpost.objects
if 'all_posts' not in self.request.GET:
pass
elif 'all' in self.request.GET:
pass
else:
success = False
criteria = {}
if 'sentiment' in self.request.GET:
criteria['sentiment'] = self.request.GET['sentiment']
print(criteria)
wallposts = wallposts.filter(**criteria)
return wallposts
And i want to easily add this filters:
import django_filters
class VkwallpostFilter(django_filters.FilterSet):
class Meta:
model = Vkwallpost
fields = ['sentiment', 'date_created']
How to achieve this?
Try to use Django Form with ModelChoiceField or ModelMultipleChoiceField.
Its all that you need.

How to sort by many custom methods in Django Admin

I want to be able to sort by several custom methods in Django Admin. This question provides solution for one method only.
I tried to modify it:
from django.db import models
class CustomerAdmin(admin.ModelAdmin):
list_display = ('number_of_orders','number_of_somevalue') # added field
def queryset(self, request):
qs = super(CustomerAdmin, self).queryset(request)
qs = qs.annotate(models.Count('order'))
qs = qs.annotate(models.Count('somevalue')) # added line
return qs
def number_of_orders(self, obj):
return obj.order__count
number_of_orders.admin_order_field = 'order__count'
def number_of_somevalue(self, obj): # added method
return obj.somevalue__count
number_of_somevalue.admin_order_field = 'somevalue__count'
and it works incorrectly. It seems that it multiplies the count values instead of counting them separately.
Example:
I have 2 orders and 2 somevalues, but in the panel I see 4 orders and 4 somevalues.
Adding another method with yet another value makes it 8 (2*2*2).
How can I fix it?
You can try this to sort by many custom methods (Tested):
from django.db.models import Count
class CustomerAdmin(admin.ModelAdmin):
# The list display must contain the functions that calculate values
list_display = ('number_of_orders','number_of_somevalue') # added field
# Overwrite queryset in model admin
def queryset(self, request):
qs = super(CustomerAdmin, self).queryset(request)
# The query have to return multiple annotation, for this use distinct=True in the Count function
qs = qs.annotate(number_orders = Count('order', distinct=True)).annotate(number_somevalue = Count('somevalue',distinct=True))
return qs
# This function return the new field calculated in queryset (number_orders)
def number_of_orders(self, obj):
return obj.number_orders
number_of_orders.admin_order_field = 'numberorders' # sortable new column
# And this one will return the another field calculated (number_somevalue)
def number_of_somevalue(self, obj): # added method
return obj.number_somevalue
number_of_somevalue.admin_order_field = 'number_somevalue'# sortable new column

Passing of modeladmin as a parameter in Custom List Filtering in Django Admin

I would like to do something if one modeladmin is passed and do another thing if another modeladmin is passed. But it seems that the modeladmin doesn't get passed as a parameter in list_filter while it does get passed in actions in django admin. why is this so?
from datetime import date
from django.utils.translation import ugettext_lazy as _
from django.contrib.admin import SimpleListFilter
class DecadeBornListFilter(SimpleListFilter):
# Human-readable title which will be displayed in the
# right admin sidebar just above the filter options.
title = _('decade born')
# Parameter for the filter that will be used in the URL query.
parameter_name = 'decade'
def lookups(self, request, model_admin):
"""
Returns a list of tuples. The first element in each
tuple is the coded value for the option that will
appear in the URL query. The second element is the
human-readable name for the option that will appear
in the right sidebar.
"""
return (
('80s', _('in the eighties')),
('90s', _('in the nineties')),
)
def queryset(self, request, queryset):
"""
Returns the filtered queryset based on the value
provided in the query string and retrievable via
`self.value()`.
"""
# Compare the requested value (either '80s' or '90s')
# to decide how to filter the queryset.
if self.value() == '80s':
return queryset.filter(birthday__gte=date(1980, 1, 1),
birthday__lte=date(1989, 12, 31))
if self.value() == '90s':
return queryset.filter(birthday__gte=date(1990, 1, 1),
birthday__lte=date(1999, 12, 31))
For example, in the example above, i would like to do something different if it is a student which is to check the birthday is between 90's or 2000's. But it is the parent, i would like to check if the birthday is between 70's or 80's? Just assume that different modeladmin will be passed. How do i include the modeladmin as parameter to do these changes? Need some guidance on this...
You could set self.model_admin:
class DecadeBornListFilter(SimpleListFilter):
#[...]
def lookups(self, request, model_admin):
self.model_admin = model_admin
# ...
def queryset(self, request, queryset):
# here you can use self.model_admin
Or, use inheritance:
class BaseDecadeBornListFilter(SimpleListFilter):
# [...]
class DecadeBornListFilter1(BaseDecadeBornListFilter):
# [...]
class DecadeBornListFilter2(BaseDecadeBornListFilter):
# [...]
class StudentModelAdmin1(admin.ModelAdmin):
list_filter = (DecadeBornListFilter1,)
class StudentModelAdmin2(admin.ModelAdmin):
list_filter = (DecadeBornListFilter2,)

Categories

Resources