33"""
44
55from operator import attrgetter
6+ from typing import Any , Literal , Sequence
67
7- from django .db .models import F
8- from django .http import Http404
8+ from django .db .models import F , QuerySet
9+ from django .http import Http404 , HttpRequest
910
1011from . import settings
1112
1213
13- def render_sort_anchor (request , field_name , title , default_direction ):
14+ def render_sort_anchor (
15+ request : HttpRequest ,
16+ field_name : str ,
17+ title : str ,
18+ default_direction : Literal ["asc" , "desc" ],
19+ ) -> str :
20+ """Render an HTML anchor tag for sorting a column."""
1421 get_params = request .GET .copy ()
1522 sort_by = get_params .get ("sort" , None )
1623 if sort_by == field_name :
@@ -41,9 +48,9 @@ def render_sort_anchor(request, field_name, title, default_direction):
4148 return f'<a href="{ request .path } { url_append } " title="{ title } ">{ title } { icon } </a>'
4249
4350
44- def get_order_by_from_request (request ) :
51+ def get_order_by_from_request (request : HttpRequest ) -> str :
4552 """
46- Retrieve field used for sorting a queryset
53+ Retrieve field used for sorting a queryset.
4754
4855 :param request: HTTP request
4956 :return: the sorted field name, prefixed with "-" if ordering is descending
@@ -54,7 +61,8 @@ def get_order_by_from_request(request):
5461 return f"{ sort_sign } { field_name } "
5562
5663
57- def need_python_sorting (queryset , order_by ):
64+ def need_python_sorting (queryset : QuerySet [Any ], order_by : str ) -> bool :
65+ """Check if Python sorting is needed (for non-database fields)."""
5866 if order_by .find ("__" ) >= 0 :
5967 # Python can't sort order_by with '__'
6068 return False
@@ -65,9 +73,19 @@ def need_python_sorting(queryset, order_by):
6573 return field not in field_names
6674
6775
68- def sort_queryset (queryset , order_by , null_ordering ):
69- """order_by is an Django ORM order_by argument"""
76+ def sort_queryset (
77+ queryset : QuerySet [Any ],
78+ order_by : str ,
79+ null_ordering : dict [str , bool ],
80+ ) -> QuerySet [Any ] | Sequence [Any ]:
81+ """
82+ Sort a queryset by the given field.
7083
84+ :param queryset: The queryset to sort
85+ :param order_by: Django ORM order_by argument (e.g., "name" or "-name")
86+ :param null_ordering: Dict with nulls_first or nulls_last setting
87+ :return: Sorted queryset or list (if Python sorting is used)
88+ """
7189 if not order_by :
7290 # In this case the queryset can't be ordered (no field name specified)
7391 # even though nulls ordering is set
@@ -79,7 +97,7 @@ def sort_queryset(queryset, order_by, null_ordering):
7997 if order_by [0 ] == "-" :
8098 if len (order_by ) == 1 :
8199 # Prefix without field name
82- raise ValueError
100+ raise ValueError ( "Order by prefix without field name" )
83101
84102 reverse = True
85103 name = order_by [1 :]
@@ -91,13 +109,23 @@ def sort_queryset(queryset, order_by, null_ordering):
91109 # Fallback on pure Python sorting (much slower on large data)
92110 if hasattr (queryset [0 ], name ):
93111 return sorted (queryset , key = attrgetter (name ), reverse = reverse )
94- raise AttributeError
112+ raise AttributeError ( f"Object has no attribute ' { name } '" )
95113
96114 ordering_exp = (F (name ).desc if reverse else F (name ).asc )(** null_ordering )
97115 return queryset .order_by (ordering_exp )
98116
99117
100- def get_null_ordering (request , default_template_ordering = None ):
118+ def get_null_ordering (
119+ request : HttpRequest ,
120+ default_template_ordering : str | None = None ,
121+ ) -> dict [str , bool ]:
122+ """
123+ Get null ordering configuration from request or template default.
124+
125+ :param request: HTTP request
126+ :param default_template_ordering: Default ordering from template ("first" or "last")
127+ :return: Dict with nulls_first or nulls_last setting, or empty dict
128+ """
101129 # Prioritize changes in URL parameter over the default template variable
102130 nulls_value = request .GET .get ("nulls" , default_template_ordering )
103131 if nulls_value :
0 commit comments