Django Order By Date, but have “None” at end?

后端 未结 5 1297
半阙折子戏
半阙折子戏 2020-12-05 01:32

I have a model of work orders, with a field for when the work order is required by. To get a list of work orders, with those that are required early, I do this:

5条回答
  •  一个人的身影
    2020-12-05 02:23

    Requirements: Python 3.4, Django 10.2, PostgreSQL 9.5.4

    Variant 1

    Solution:

    class IsNull(models.Func):
    
        template = "%(expressions)s IS NULL"
    

    Usage (None always latest):

    In [1]: a = User.polls_manager.users_as_voters()
    
    In [4]: from django.db import models
    
    In [5]: class IsNull(models.Func):
       ...:     template = "%(expressions)s IS NULL"
       ...:     
    
    In [7]: a = a.annotate(date_latest_voting_isnull=IsNull('date_latest_voting'))
    
    In [9]: for i in a.order_by('date_latest_voting_isnull', 'date_latest_voting'):
       ...:     print(i.date_latest_voting)
       ...:     
    2016-07-30 01:48:11.872911+00:00
    2016-08-31 13:13:47.240085+00:00
    2016-09-16 00:04:23.042142+00:00
    2016-09-18 19:45:54.958573+00:00
    2016-09-26 07:27:34.301295+00:00
    2016-10-03 14:01:08.377417+00:00
    2016-10-21 16:07:42.881526+00:00
    2016-10-23 11:10:02.342791+00:00
    2016-10-31 04:09:03.726765+00:00
    None
    
    In [10]: for i in a.order_by('date_latest_voting_isnull', '-date_latest_voting'):
        ...:     print(i.date_latest_voting)
        ...:     
    2016-10-31 04:09:03.726765+00:00
    2016-10-23 11:10:02.342791+00:00
    2016-10-21 16:07:42.881526+00:00
    2016-10-03 14:01:08.377417+00:00
    2016-09-26 07:27:34.301295+00:00
    2016-09-18 19:45:54.958573+00:00
    2016-09-16 00:04:23.042142+00:00
    2016-08-31 13:13:47.240085+00:00
    2016-07-30 01:48:11.872911+00:00
    None
    

    Notes

    1. Based on https://www.isotoma.com/blog/2015/11/23/sorting-querysets-with-nulls-in-django/
    2. Drawback: unnecessary buffer field, overhead for ordering

    Variant 2

    Solution:

    from django.db import models
    from django.db import connections
    from django.db.models.sql.compiler import SQLCompiler
    
    
    class NullsLastCompiler(SQLCompiler):
    
        # source code https://github.com/django/django/blob/master/django/db/models/sql/compiler.py
    
        def get_order_by(self):
    
            result = super(NullsLastCompiler, self).get_order_by()
    
            # if result exists and backend is PostgreSQl
            if result and self.connection.vendor == 'postgresql':
    
                # modified raw SQL code to ending on NULLS LAST after ORDER BY
                # more info https://www.postgresql.org/docs/9.5/static/queries-order.html
                result = [
                    (expression, (sql + ' NULLS LAST', params, is_ref))
                    for expression, (sql, params, is_ref) in result
                ]
    
            return result
    
    
    class NullsLastQuery(models.sql.Query):
    
        # source code https://github.com/django/django/blob/master/django/db/models/sql/query.py
        def get_compiler(self, using=None, connection=None):
            if using is None and connection is None:
                raise ValueError("Need either using or connection")
            if using:
                connection = connections[using]
    
            # return own compiler
            return NullsLastCompiler(self, connection, using)
    
    
    class NullsLastQuerySet(models.QuerySet):
    
        # source code https://github.com/django/django/blob/master/django/db/models/query.py
        def __init__(self, model=None, query=None, using=None, hints=None):
    
            super(NullsLastQuerySet, self).__init__(model, query, using, hints)
    
            # replace on own Query
            self.query = query or NullsLastQuery(model)
    

    Usage:

    # instead of models.QuerySet use NullsLastQuerySet
    class UserQuestionQuerySet(NullsLastQuerySet):
    
        def users_with_date_latest_question(self):
    
            return self.annotate(date_latest_question=models.Max('questions__created'))
    
    
    #connect to a model as a manager
    class User(AbstractBaseUser, PermissionsMixin):
        .....
    
        questions_manager = UserQuestionQuerySet().as_manager()
    

    Results (None always latest):

    In [2]: qs = User.questions_manager.users_with_date_latest_question()
    
    In [3]: for i in qs:
       ...:     print(i.date_latest_question)
       ...:     
    None
    None
    None
    2016-10-28 20:48:49.005593+00:00
    2016-10-04 19:01:38.820993+00:00
    2016-09-26 00:35:07.839646+00:00
    None
    2016-07-27 04:33:58.508083+00:00
    2016-09-14 10:40:44.660677+00:00
    None
    
    In [4]: for i in qs.order_by('date_latest_question'):
       ...:     print(i.date_latest_question)
       ...:     
    2016-07-27 04:33:58.508083+00:00
    2016-09-14 10:40:44.660677+00:00
    2016-09-26 00:35:07.839646+00:00
    2016-10-04 19:01:38.820993+00:00
    2016-10-28 20:48:49.005593+00:00
    None
    None
    None
    None
    None
    
    In [5]: for i in qs.order_by('-date_latest_question'):
       ...:     print(i.date_latest_question)
       ...:     
    2016-10-28 20:48:49.005593+00:00
    2016-10-04 19:01:38.820993+00:00
    2016-09-26 00:35:07.839646+00:00
    2016-09-14 10:40:44.660677+00:00
    2016-07-27 04:33:58.508083+00:00
    None
    None
    None
    None
    None
    

    Notes:

    1. Based on the Django: Adding "NULLS LAST" to query and the Django`s source code

    2. Global on all fields of a model (it is advantage and disadvantage simultaneously)

    3. No a unnecessary field

    4. A drawback - tested only on the PostgreSQL

提交回复
热议问题