The Objective
The objective is to conditionally order a QuerySet by one of three different date fields in the view based on another field in the model. Since conditional ordering cannot be accomplished with Class Meta I am exploring accomplishing this objective in the view.
Here is the relevant excerpt from models.py
:
READING_PROGRESS = [
('---', '---'),
('1) On Reading List', '1) On Reading List'),
('2) Reading In Progress', '2) Reading In Progress'),
('3) Completed Reading', '3) Completed Reading'),
]
class ReadingProgress(models.Model):
record = models.ForeignKey(
LibraryRecord,
related_name='record_in_reading_progress',
on_delete=models.CASCADE,
null=True,
blank=True,
verbose_name='Library record'
)
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
null=True,
blank=True
)
reading_progress = models.CharField(
max_length=30,
choices=READING_PROGRESS,
default='---'
)
date_added = models.DateField(
auto_now=False,
auto_now_add=False,
null=True,
blank=True,
)
date_started = models.DateField(
auto_now=False,
auto_now_add=False,
null=True,
blank=True,
)
date_completed = models.DateField(
auto_now=False,
auto_now_add=False,
null=True,
blank=True,
)
class Meta:
ordering = [
'reading_progress',
]
verbose_name_plural = 'Reading Progress'
unique_together = ('record', 'user',)
# Record metadata
date_created = models.DateTimeField(
auto_now_add=True
)
def __str__(self):
return f'{self.record.title} - {self.reading_progress}'
The relevant fields in the model are:
reading_progress
date_added
date_started
date_completed
Each date field corresponds to a status value. I want to be able to order_by the QuerySet in the view by the field reading_progress
:
- When
reading_progress
== '1) On Reading List' then order bydate_added
- When
reading_progress
== '2) Reading In Progress' then order bydate_started
- When
reading_progress
== '3) Completed Reading' then order bydate_completed
Research This Far
I did some research and found a useful looking QuerySet API called, annotate()
. This looks to be way to go (Django docs).
However, my implementation is not working. Here's what I currently have in views.py
:
reading_progress = ReadingProgress.objects.filter(user__username=self.request.user)\
.annotate(
date_to_display=Case(
When(reading_progress='1) On Reading List', then=F('date_added')),
When(reading_progress='2) Reading In Progress', then=F('date_started')),
When(reading_progress='3) Completed Reading', then=F('date_completed')),
output_field=DateField(),
)
).order_by(
'date_to_display'
)
The Django docs appeared to suggest that:
- I could filter
- and then annotate on the filtered QuerySet
- With additional research I concluded that I could use
F()
as a way of implementing the query within theannotate()
API
However, my implementation is not quite working. This is the Traceback reporting a NameError:
Traceback (most recent call last):
File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner
response = get_response(request)
File "/usr/local/lib/python3.10/site-packages/django/core/handlers/base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python3.10/site-packages/django/views/generic/base.py", line 84, in view
return self.dispatch(request, *args, **kwargs)
File "/usr/local/lib/python3.10/site-packages/django/contrib/auth/mixins.py", line 73, in dispatch
return super().dispatch(request, *args, **kwargs)
File "/usr/local/lib/python3.10/site-packages/django/views/generic/base.py", line 119, in dispatch
return handler(request, *args, **kwargs)
File "/usr/local/lib/python3.10/site-packages/django/views/generic/list.py", line 174, in get
context = self.get_context_data()
File "/app/web/library/views.py", line 1280, in get_context_data
reading_progress = ReadingProgress.objects.filter(user__username=self.request.user).annotate(date_to_display=Case(When(reading_progress='1) On Reading List', then=F('date_added')),When(reading_progress='2) Reading In Progress', then=F('date_started')),When(reading_progress='3) Completed Reading', then=F('date_completed')),output_field=DateField(),)).order_by('date_to_display')
Exception Type: NameError at /reading_list/
Exception Value: name 'Case' is not defined
I feel like I am close but am currently scratching my head. The example snippets I have seen make use of Case
but for some reason I'm getting an error saying I've not defined Case
. This leads me to believe that maybe I'm approaching this the wrong way.
Any insights or suggestions are welcome.
The Answer
From the accepted answer, views.py
works with the following. I also removed output_field
as all three fields being annotated are the same data type:
from django.db.models import Case, F, Q, Value, When
reading_progress = ReadingProgress.objects.filter(user__username=self.request.user)\
.annotate(
date_to_display=Case(
When(reading_progress='1) On Reading List', then=F('date_added')),
When(reading_progress='2) Reading In Progress', then=F('date_started')),
When(reading_progress='3) Completed Reading', then=F('date_completed')),
)
).order_by(
'reading_progress',
'date_to_display',
)
CodePudding user response:
You have to import the relevant parts within views.py
from django.db.models import Case, F, Q, Value, When