Django [SOLVED]: Django newbie, struggling to understand how to implement a custom queryset
January 2, 2018 at 4:22 am #246528
So I’m pretty new to Django, I started playing yesterday and have been playing with the standard polls tutorial.
I’d like to be able to filter the active questions based on the results of a custom method (in this case it is the
Question.is_open()method (fig1 below).
The problem as I understand it
When I try and access only the active questions using a filter like
questions.objects.filter(is_open=true)it fails. If I understand correctly this relies on a queryset exposed via a model manager which can only filter based on records within the sql database.
1) Am I approaching this problem in most pythonic/django/dry way ? Should I be exposing these methods by subclassing the models.Manager and generating a custom queryset ? (that appears to be the consensus online).
2) If I should be using a manager subclass with a custom queryset, i’m not sure what the code would look like. For example, should I be using sql via a cursor.execute (as per the documentation here, which seems very low level) ? Or is there a better, higher level way of achieving this in django itself ?
I’d appreciate any insights into how to approach this.
class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField('date published',default=timezone.now()) start_date = models.DateTimeField('poll start date',default=timezone.now()) closed_date = models.DateTimeField('poll close date', default=timezone.now() + datetime.timedelta(days=1)) def time_now(self): return timezone.now() def was_published_recently(self): return self.pub_date >= timezone.now() - datetime.timedelta(days=1) def is_open(self): return ((timezone.now() > self.start_date) and (timezone.now() < self.closed_date)) def was_opened_recently(self): return self.start_date >= timezone.now() - datetime.timedelta(days=1) and self.is_open() def was_closed_recently(self): return self.closed_date >= timezone.now() - datetime.timedelta(days=1) and not self.is_open() def is_opening_soon(self): return self.start_date <= timezone.now() - datetime.timedelta(days=1) def closing_soon(self): return self.closed_date <= timezone.now() - datetime.timedelta(days=1)
Just as a follow-up. I’ve subclassed the default manager with a hardcoded SQL string (just for testing), however, it fails as it’s not an attribute
class QuestionManager(models.Manager): def get_queryset(self): return super().get_queryset() def get_expired(self): from django.db import connection with connection.cursor() as cursor: cursor.execute(""" select id, question_text, closed_date, start_date, pub_date from polls_question where ( polls_question.start_date < '2017-12-24 00:08') and (polls_question.closed_date > '2017-12-25 00:01') order by pub_date;""") result_list =  for row in cursor.fetchall(): p = self.model(id=row, question=row, closed_date=row, start_date=row, pub_date=row) result_list.append(p) return result_list
I’m calling the method with
active_poll_list = Question.objects.get_expired()
but I get the exception
Exception Value: 'Manager' object has no attribute 'get_expired'
I’m really not sure I understand why this doesn’t work. It must be my misunderstanding of how I should invoke a method that returns a queryset from the manager.
Any suggestions would be much appreciated.
ThanksJanuary 2, 2018 at 4:22 am #246529
There are so many things in your question and I’ll try to cover as many as possible.
When you’re trying to get a queryset for a model, you can use only the field attributes as lookups. That means in your example that you can do:
Question.objects.filter(question_text='What's the question?')
But you can’t query a method:
There is no field
is_open. It is a method of the model class and it can’t be used when filtering a queryset.
The methods you have declared in the class
Questionmight be better decorated as properties (
@property) or as cached properties. For the later import this:
from django.utils.functional import cached_property
and decorate the methods like this:
@cached_property def is_open(self): # ...
This will make the calculated value avaiable as property, not as method:
question = Question.objects.get(pk=1) print(questin.is_open)
When you specify default value for time fields you very probably want this:
pub_date = models.DateTimeField('date published', default=timezone.now)
Pay attention – it is just
timezone.now! The callable should be called when an entry is created. Otherwise the method
timezone.now()will be called the first time the django app starts and all entries will have that time saved.
If you want to add extra methods to the manager, you have to assign your custom manager to the
class Question(models.Model): # the fields ... objects = QuestionManager()
After that the method
get_expiredwill be available:
I hope this helps you to understand some things that went wrong in your code.
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
You must be logged in to reply to this topic.