1
1
# -*- coding: utf-8 -*-
2
+ from __future__ import absolute_import , unicode_literals
2
3
from django .core .urlresolvers import reverse
3
4
from django .db .models .fields import FieldDoesNotExist
4
5
from django .template import RequestContext , Context , Template
10
11
from itertools import ifilter , islice
11
12
import warnings
12
13
from .templatetags .django_tables2 import title
13
- from .utils import A , AttributeDict , Attrs , OrderBy , Sequence
14
+ from .utils import A , AttributeDict , Attrs , OrderBy , OrderByTuple , Sequence
14
15
15
16
16
17
class Column (object ):
@@ -27,7 +28,7 @@ class Column(object):
27
28
:type accessor: :class:`basestring` or :class:`~.utils.Accessor`
28
29
:param accessor: An accessor that describes how to extract values for
29
30
this column from the :term:`table data`.
30
- :parameter default: The default value for the column. This can be a value
31
+ :param default: The default value for the column. This can be a value
31
32
or a callable object [1]_. If an object in the data
32
33
provides :const:`None` for a column, the default will
33
34
be used instead.
@@ -40,15 +41,18 @@ class Column(object):
40
41
41
42
.. [1] The provided callable object must not expect to
42
43
receive any arguments.
44
+ :param order_by: Allows one or more accessors to be used for ordering
45
+ rather than ``accessor``.
46
+ :type order_by: :class:`unicode`, :class:`tuple`, :class:`~utils.Accessor`
43
47
:type visible: :class:`bool`
44
48
:param visible: If :const:`False`, this column will not be in HTML from
45
49
output generators (e.g. :meth:`as_html` or
46
50
``{% render_table %}``).
47
51
48
52
When a field is not visible, it is removed from the
49
53
table's :attr:`~Column.columns` iterable.
50
- :type sortable : :class:`bool`
51
- :param sortable : If :const:`False`, this column will not be allowed to
54
+ :type orderable : :class:`bool`
55
+ :param orderable : If :const:`False`, this column will not be allowed to
52
56
influence row ordering/sorting.
53
57
:type attrs: :class:`Attrs` object
54
58
:param attrs: HTML attributes to be added to components in the column
@@ -63,25 +67,34 @@ class Column(object):
63
67
creation_counter = 0
64
68
65
69
def __init__ (self , verbose_name = None , accessor = None , default = None ,
66
- visible = True , sortable = None , attrs = None ):
70
+ visible = True , orderable = None , attrs = None , order_by = None ,
71
+ sortable = None ):
67
72
if not (accessor is None or isinstance (accessor , basestring ) or
68
73
callable (accessor )):
69
- raise TypeError (u 'accessor must be a string or callable, not %s' %
74
+ raise TypeError ('accessor must be a string or callable, not %s' %
70
75
accessor .__class__ .__name__ )
71
76
if callable (accessor ) and default is not None :
72
- raise TypeError ('accessor must be string when default is used, not'
73
- ' callable' )
77
+ raise TypeError ('accessor must be string when default is used, not callable' )
74
78
self .accessor = A (accessor ) if accessor else None
75
79
self ._default = default
76
- self .sortable = sortable
77
80
self .verbose_name = verbose_name
78
81
self .visible = visible
82
+ if sortable is not None :
83
+ warnings .warn ('`sortable` is deprecated, use `orderable` instead.' ,
84
+ DeprecationWarning )
85
+ # if orderable hasn't been specified, we'll use sortable's value
86
+ if orderable is None :
87
+ orderable = sortable
88
+ self .orderable = orderable
79
89
attrs = attrs or {}
80
90
if not isinstance (attrs , Attrs ):
81
91
warnings .warn ('attrs must be Attrs object, not %s'
82
92
% attrs .__class__ .__name__ , DeprecationWarning )
83
93
attrs = Attrs (attrs )
84
94
self .attrs = attrs
95
+ # massage order_by into an OrderByTuple or None
96
+ order_by = (order_by , ) if isinstance (order_by , basestring ) else order_by
97
+ self .order_by = OrderByTuple (order_by ) if order_by is not None else None
85
98
86
99
self .creation_counter = Column .creation_counter
87
100
Column .creation_counter += 1
@@ -131,6 +144,15 @@ def render(self, value):
131
144
"""
132
145
return value
133
146
147
+ @property
148
+ def sortable (self ):
149
+ """
150
+ *deprecated* -- use `orderable` instead.
151
+ """
152
+ warnings .warn ('`sortable` is deprecated, use `orderable` instead.' ,
153
+ DeprecationWarning )
154
+ return self .orderable
155
+
134
156
135
157
class CheckBoxColumn (Column ):
136
158
"""
@@ -146,9 +168,9 @@ class CheckBoxColumn(Column):
146
168
147
169
This class implements some sensible defaults:
148
170
149
- - The ``name`` attribute of the input is the :term:`column name` (can
150
- override via ``attrs`` argument).
151
- - The ``sortable`` parameter defaults to :const:`False`.
171
+ - HTML input's ``name`` attribute is the :term:`column name` (can override
172
+ via ``attrs`` argument).
173
+ - ``orderable`` defaults to :const:`False`.
152
174
153
175
.. note::
154
176
@@ -179,7 +201,7 @@ def __init__(self, attrs=None, **extra):
179
201
if header_attrs :
180
202
attrs .setdefault ('th__input' , header_attrs )
181
203
182
- kwargs = {'sortable ' : False , 'attrs' : attrs }
204
+ kwargs = {'orderable ' : False , 'attrs' : attrs }
183
205
kwargs .update (extra )
184
206
super (CheckBoxColumn , self ).__init__ (** kwargs )
185
207
@@ -418,11 +440,11 @@ def attrs(self):
418
440
th_class = set ((c for c in th .get ("class" , "" ).split (" " ) if c ))
419
441
td_class = set ((c for c in td .get ("class" , "" ).split (" " ) if c ))
420
442
# add classes for ordering
421
- if self .sortable :
422
- th_class .add ("sortable " )
423
- order_by = self . order_by
424
- if order_by :
425
- th_class .add ("desc" if order_by .is_descending else "asc" )
443
+ if self .orderable :
444
+ th_class .add ("orderable " )
445
+ th_class . add ( "sortable" ) # backwards compatible
446
+ if self . is_ordered :
447
+ th_class .add ("desc" if self . order_by_alias .is_descending else "asc" )
426
448
# Always add the column name as a class
427
449
th_class .add (self .name )
428
450
td_class .add (self .name )
@@ -472,22 +494,94 @@ def name(self):
472
494
@property
473
495
def order_by (self ):
474
496
"""
475
- If this column is sorted, return the associated :class:`.OrderBy`
476
- instance, otherwise ``None``.
497
+ Returns an :class:`OrderByTuple` of appropriately prefixed data source
498
+ keys used to sort this column.
499
+
500
+ See :meth:`.order_by_alias` for details.
477
501
"""
478
- try :
479
- return (self .table .order_by or {})[self .name ]
480
- except KeyError :
481
- return None
502
+ if self .column .order_by is not None :
503
+ order_by = self .column .order_by
504
+ else :
505
+ # default to using column name as data source sort key
506
+ order_by = OrderByTuple ((self .name , ))
507
+ return order_by .opposite if self .order_by_alias .is_descending else order_by
508
+
509
+ @property
510
+ def order_by_alias (self ):
511
+ """
512
+ Returns an :class:`OrderBy` describing the current state of ordering
513
+ for this column.
514
+
515
+ The following attempts to explain the difference between ``order_by``
516
+ and ``order_by_alias``.
517
+
518
+ ``order_by_alias`` returns and ``OrderBy`` instance that's based on
519
+ the *name* of the column, rather than the keys used to order the table
520
+ data. Understanding the difference is essential.
521
+
522
+ Having an alias *and* a normal version is necessary because an N-tuple
523
+ (of data source keys) can be used by the column to order the data, and
524
+ it's ambiguous when mapping from N-tuple to column (since multiple
525
+ columns could use the same N-tuple).
526
+
527
+ The solution is to use order by *aliases* (which are really just
528
+ prefixed column names) that describe the ordering *state* of the
529
+ column, rather than the specific keys in the data source should be
530
+ ordered.
531
+
532
+ e.g.::
533
+
534
+ >>> class SimpleTable(tables.Table):
535
+ ... name = tables.Column(order_by=("firstname", "last_name"))
536
+ ...
537
+ >>> table = SimpleTable([], order_by=("-name", ))
538
+ >>> table.columns["name"].order_by_alias
539
+ "-name"
540
+ >>> table.columns["name"].order_by
541
+ ("-first_name", "-last_name")
542
+
543
+ The ``OrderBy`` returned has been patched to include an extra attribute
544
+ ``next``, which returns a version of the alias that would be
545
+ transitioned to if the user toggles sorting on this column, e.g.::
546
+
547
+ not sorted -> ascending
548
+ ascending -> descending
549
+ descending -> ascending
550
+
551
+ This is useful otherwise in templates you'd need something like:
552
+
553
+ {% if column.is_ordered %}
554
+ {% querystring table.prefixed_order_by_field=column.order_by_alias.opposite %}
555
+ {% else %}
556
+ {% querystring table.prefixed_order_by_field=column.order_by_alias %}
557
+ {% endif %}
558
+
559
+ """
560
+ order_by = OrderBy ((self .table .order_by or {}).get (self .name , self .name ))
561
+ order_by .next = order_by .opposite if self .is_ordered else order_by
562
+ return order_by
563
+
564
+ @property
565
+ def is_ordered (self ):
566
+ return self .name in (self .table .order_by or ())
482
567
483
568
@property
484
569
def sortable (self ):
485
570
"""
486
- Return a ``bool`` depending on whether this column is sortable .
571
+ *deprecated* -- use `orderable` instead .
487
572
"""
488
- if self .column .sortable is not None :
489
- return self .column .sortable
490
- return self .table .sortable
573
+ warnings .warn ('`%s.sortable` is deprecated, use `orderable`'
574
+ % type (self ).__name__ , DeprecationWarning )
575
+ return self .orderable
576
+
577
+ @property
578
+ def orderable (self ):
579
+ """
580
+ Return a ``bool`` depending on whether this column supports ordering.
581
+ """
582
+ if self .column .orderable is not None :
583
+ return self .column .orderable
584
+ return self .table .orderable
491
585
492
586
@property
493
587
def table (self ):
@@ -611,24 +705,33 @@ def iteritems(self):
611
705
def items (self ):
612
706
return list (self .iteritems ())
613
707
614
- def itersortable (self ):
708
+ def iterorderable (self ):
615
709
"""
616
- Same as :meth:`.BoundColumns.all` but only returns sortable
617
- :class:`.BoundColumn` objects.
710
+ Same as :meth:`.BoundColumns.all` but only returns orderable columns.
618
711
619
712
This is useful in templates, where iterating over the full
620
713
set and checking ``{% if column.sortable %}`` can be problematic in
621
714
conjunction with e.g. ``{{ forloop.last }}`` (the last column might not
622
715
be the actual last that is rendered).
623
716
"""
624
- return ifilter (lambda x : x .sortable , self .iterall ())
717
+ return ifilter (lambda x : x .orderable , self .iterall ())
718
+
719
+ def itersortable (self ):
720
+ warnings .warn ('`itersortable` is deprecated, use `iterorderable` instead.' ,
721
+ DeprecationWarning )
722
+ return self .iterorderable ()
723
+
724
+ def orderable (self ):
725
+ return list (self .iterorderable ())
625
726
626
727
def sortable (self ):
627
- return list (self .itersortable ())
728
+ warnings .warn ("`sortable` is deprecated, use `orderable` instead." ,
729
+ DeprecationWarning )
730
+ return self .orderable
628
731
629
732
def itervisible (self ):
630
733
"""
631
- Same as :meth:`.sortable ` but only returns visible
734
+ Same as :meth:`.iterorderable ` but only returns visible
632
735
:class:`.BoundColumn` objects.
633
736
634
737
This is geared towards table rendering.
0 commit comments