File: ordering.rst

package info (click to toggle)
django-tables 2.7.5-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,752 kB
  • sloc: python: 7,120; makefile: 132; sh: 74
file content (148 lines) | stat: -rw-r--r-- 4,751 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
Alternative column ordering
===========================

When using QuerySet data, one might want to show a computed value which is not
in the database. In this case, attempting to order the column will cause an
exception::

    # models.py
    class Person(models.Model):
        first_name = models.CharField(max_length=200)
        family_name = models.CharField(max_length=200)

        @property
        def name(self):
            return f"{self.first_name} {self.family_name}"

    # tables.py
    class PersonTable(tables.Table):
        name = tables.Column()

::

    >>> table = PersonTable(Person.objects.all())
    >>> table.order_by = "name"
    >>>
    >>> # will result in:
    FieldError: Cannot resolve keyword 'name' into field. Choices are: first_name, family_name

To prevent this, django-tables2 allows two ways to specify custom ordering:
accessors and :meth:`~.order_FOO` methods.

.. _order-by-accessors:

Ordering by accessors
---------------------

You can supply an ``order_by`` argument containing a name or a tuple of the
names of the columns the database should use to sort it::

    class PersonTable(tables.Table):
        name = tables.Column(order_by=("first_name", "family_name"))

`~.Accessor` syntax can be used as well, as long as they point to a model field.

If ordering does not make sense for a particular column, it can be disabled via
the ``orderable`` argument::

    class SimpleTable(tables.Table):
        name = tables.Column()
        actions = tables.Column(orderable=False)


.. _table.order_foo:

:meth:`table.order_FOO` methods
--------------------------------

Another solution for alternative ordering is being able to chain functions on to
the original QuerySet. This method allows more complex functionality giving the
ability to use all of Django's QuerySet API.

Adding a `Table.order_FOO` method (where `FOO` is the name of the column),
gives you the ability to chain to, or modify, the original QuerySet when that
column is selected to be ordered.

The method takes two arguments: `QuerySet`, and `is_descending`. The return
must be a tuple of two elements. The first being the QuerySet and the second
being a boolean; note that modified QuerySet will only be used if the boolean is
`True`.

For example, let's say instead of ordering alphabetically, ordering by
amount of characters in the first_name is desired.
The implementation would look like this:
::

    # tables.py
    from django.db.models.functions import Length

    class PersonTable(tables.Table):
        name = tables.Column()

        def order_name(self, queryset, is_descending):
            queryset = queryset.annotate(
                length=Length("first_name")
            ).order_by(("-" if is_descending else "") + "length")
            return (queryset, True)



As another example, presume the situation calls for being able to order by a
mathematical expression. In this scenario, the table needs to be able to be
ordered by the sum of both the shirts and the pants. The custom column will
have its value rendered using :ref:`table.render_FOO`.

This can be achieved like this::

    # models.py
    class Person(models.Model):
        first_name = models.CharField(max_length=200)
        family_name = models.CharField(max_length=200)
        shirts = models.IntegerField()
        pants = models.IntegerField()


    # tables.py
    from django.db.models import F

    class PersonTable(tables.Table):
        clothing = tables.Column()

        class Meta:
            model = Person

        def render_clothing(self, record):
            return str(record.shirts + record.pants)

        def order_clothing(self, queryset, is_descending):
            queryset = queryset.annotate(
                amount=F("shirts") + F("pants")
            ).order_by(("-" if is_descending else "") + "amount")
            return (queryset, True)


Using :meth:`Column.order` on custom columns
--------------------------------------------

If you created a custom column, which also requires custom ordering like
explained above, you can add the body of your ``order_foo`` method to the
order method on your custom column, to allow easier reuse.

For example, the `PersonTable` from above could also be defined like this::

    class ClothingColumn(tables.Column):
        def render(self, record):
            return str(record.shirts + record.pants)

        def order(self, queryset, is_descending):
            queryset = queryset.annotate(
                amount=F("shirts") + F("pants")
            ).order_by(("-" if is_descending else "") + "amount")
            return (queryset, True)


    class PersonTable(tables.Table):
        clothing = ClothingColumn()

        class Meta:
            model = Person