Django [SOLVED]: Django newbie, struggling to understand how to implement a custom queryset

Django [SOLVED]: Django newbie, struggling to understand how to implement a custom queryset

Home Forums Frameworks Django Tutorials Django [SOLVED]: Django newbie, struggling to understand how to implement a custom queryset

Viewing 2 posts - 1 through 2 (of 2 total)
  • Author
    Posts
  • #246528

    Cloudy Point
    Keymaster

    QuestionQuestion

    So I’m pretty new to Django, I started playing yesterday and have been playing with the standard polls tutorial.

    Context

    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.

    My questions

    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.

    Thanks

    Matt

    My models.py

    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)
    

    [Update]

    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[0], question=row[1], closed_date=row[2], start_date=row[3], pub_date=row[4])
                    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.

    Thanks

    #246529

    Cloudy Point
    Keymaster

    Accepted AnswerAnswer

    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?')
    

    or:

    Question.objects.filter(question_text__icontains='what')
    

    But you can’t query a method:

    Question.objects.filter(is_open=True)
    

    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 Question might 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 objects:

    class Question(models.Model):
        # the fields ...
        objects = QuestionManager()
    

    After that the method get_expired will be available:

    Question.objects.get_expired()
    

    I hope this helps you to understand some things that went wrong in your code.

    Source: https://stackoverflow.com/questions/47968581/django-newbie-struggling-to-understand-how-to-implement-a-custom-queryset
    Author: cezar
    Creative Commons License
    This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Viewing 2 posts - 1 through 2 (of 2 total)

You must be logged in to reply to this topic.