Rapports: trie des rapports en WIP
authorJean-Philippe Caissy <jean-philippe.caissy@auf.org>
Thu, 26 Jan 2012 21:43:26 +0000 (15:43 -0600)
committerJean-Philippe Caissy <jean-philippe.caissy@auf.org>
Thu, 26 Jan 2012 21:43:26 +0000 (15:43 -0600)
project/lib.py
project/rh/templates/admin/table_header.html [new file with mode: 0644]
project/rh/templates/rh/rapports/postes.html
project/rh/templatetags/rapports.py
project/rh/views.py

index e73b3a1..d1b1809 100644 (file)
@@ -2,6 +2,7 @@
 
 import datamaster_modeles.models as ref
 import rh.models as rh
+from operator import itemgetter
 
 def get_employe_from_user(user):
     """
@@ -44,3 +45,17 @@ def safe_create_groupe(name=None, id=None):
     except:
         return None
     return grp
+
+def multikeysort(items, columns):
+    """ Trie un une liste de tableau à la Django (-column pour descandant, column pour ascendant)
+    http://stackoverflow.com/questions/1143671/python-sorting-list-of-dictionaries-by-multiple-keys
+    """
+    comparers = [ ((itemgetter(col[1:].strip()), -1) if col.startswith('-') else (itemgetter(col.strip()), 1)) for col in columns]  
+    def comparer(left, right):
+        for fn, mult in comparers:
+            result = cmp(fn(left), fn(right))
+            if result:
+                return mult * result
+        else:
+            return 0
+    return sorted(items, cmp=comparer)
diff --git a/project/rh/templates/admin/table_header.html b/project/rh/templates/admin/table_header.html
new file mode 100644 (file)
index 0000000..dad0517
--- /dev/null
@@ -0,0 +1,5 @@
+{% for header in headers %}<th class="{{ header.class_attr }}">
+{% if header.sortable %}<a href="{{ header.url|escape }}">{% endif %}
+{{ header.text }}
+  {% if header.sortable %}</a>{% endif %}
+</th>{% endfor %}
index 45c5c48..7670c74 100644 (file)
 <table id="result_list">
 <thead>
 <tr>
-       <th># du poste</th>
-       <th>Nom du poste</th>
-       <th>Implantation</th>
-       <th># de l'employé</th>
-       <th>Nom</th>
-       <th>Prénom</th>
+    {% table_header headers %}
 </tr>
 </thead>
 {% spaceless %}{% for poste in postes %}
index 1244a99..016d184 100644 (file)
@@ -121,6 +121,9 @@ def filter_implantation_remun(context):
     return {'title': u"implantation",
             'choices': prepare_choices(Implantation.objects.values_list('id', 'nom'), 'dossiers__poste__implantation', context)}
 
+@register.inclusion_tag('admin/table_header.html', takes_context=True)
+def table_header(context, headers):
+        return {'headers': headers}
 
 def get_query_string(request, new_params=None, remove=None):
     if new_params is None: new_params = {}
@@ -166,3 +169,127 @@ def prepare_choices_date(field_name, context, links, remove=[]):
                        'query_string': get_query_string(request, param_dict, [field_generic]),
                        'display': title})
     return result
+
+ORDER_VAR = 'o'
+ORDER_TYPE_VAR = 'ot'
+
+class SortHeaders:
+    """
+    Handles generation of an argument for the Django ORM's
+    ``order_by`` method and generation of table headers which reflect
+    the currently selected sort, based on defined table headers with
+    matching sort criteria.
+
+    Based in part on the Django Admin application's ``ChangeList``
+    functionality.
+
+    http://djangosnippets.org/snippets/308/
+    """
+    def __init__(self, request, headers, default_order_field=None,
+            default_order_type='asc', additional_params=None):
+        """
+        request
+            The request currently being processed - the current sort
+            order field and type are determined based on GET
+            parameters.
+
+        headers
+            A list of two-tuples of header text and matching ordering
+            criteria for use with the Django ORM's ``order_by``
+            method. A criterion of ``None`` indicates that a header
+            is not sortable.
+
+        default_order_field
+            The index of the header definition to be used for default
+            ordering and when an invalid or non-sortable header is
+            specified in GET parameters. If not specified, the index
+            of the first sortable header will be used.
+
+        default_order_type
+            The default type of ordering used - must be one of
+            ``'asc`` or ``'desc'``.
+
+        additional_params:
+            Query parameters which should always appear in sort links,
+            specified as a dictionary mapping parameter names to
+            values. For example, this might contain the current page
+            number if you're sorting a paginated list of items.
+        """
+        if default_order_field is None:
+            for i, (header, query_lookup) in enumerate(headers):
+                if query_lookup is not None:
+                    default_order_field = i
+                    break
+        if default_order_field is None:
+            raise AttributeError('No default_order_field was specified and none of the header definitions given were sortable.')
+        if default_order_type not in ('asc', 'desc'):
+            raise AttributeError('If given, default_order_type must be one of \'asc\' or \'desc\'.')
+        if additional_params is None: additional_params = {}
+
+        self.header_defs = headers
+        self.additional_params = additional_params
+        self.order_field, self.order_type = default_order_field, default_order_type
+
+        # Determine order field and order type for the current request
+        params = dict(request.GET.items())
+        if ORDER_VAR in params:
+            try:
+                new_order_field = int(params[ORDER_VAR])
+                if headers[new_order_field][1] is not None:
+                    import pdb
+                    pdb.set_trace()
+                    self.order_field = params[ORDER_VAR]
+            except (IndexError, ValueError):
+                pass # Use the default
+        if ORDER_TYPE_VAR in params and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
+            self.order_type = params[ORDER_TYPE_VAR]
+        try:
+            del params[ORDER_VAR]
+        except KeyError:
+            pass
+        try:
+            del params[ORDER_TYPE_VAR]
+        except KeyError:
+            pass
+        self.additional_params = params
+
+    def headers(self):
+        """
+        Generates dicts containing header and sort link details for
+        all defined headers.
+        """
+        for i, (header, order_criterion) in enumerate(self.header_defs):
+            th_classes = []
+            new_order_type = 'asc'
+            if i == self.order_field:
+                th_classes.append('sorted %sending' % self.order_type)
+                new_order_type = {'asc': 'desc', 'desc': 'asc'}[self.order_type]
+            yield {
+                'text': header,
+                'sortable': order_criterion is not None,
+                'url': self.get_query_string({ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}),
+                'class_attr': (th_classes and ' '.join(th_classes) or ''),
+            }
+
+    def get_query_string(self, params):
+        """
+        Creates a query string from the given dictionary of
+        parameters, including any additonal parameters which should
+        always be present.
+        """
+        params.update(self.additional_params)
+        if self.order_type == params[ORDER_TYPE_VAR] and self.order_field == params[ORDER_VAR]:
+            params[ORDER_TYPE_VAR] = 'asc' if params[ORDER_TYPE_VAR] == "desc" else "desc"
+        return '?%s' % '&'.join(['%s=%s' % (param, value) \
+                                     for param, value in params.items()])
+
+    def get_order_by(self):
+        """
+        Creates an ordering criterion based on the current order
+        field and order type, for use with the Django ORM's
+        ``order_by`` method.
+        """
+        return '%s%s' % (
+            self.order_type == 'desc' and '-' or '',
+            self.header_defs[self.order_field][1],
+        )
index eb0b234..c97e2de 100644 (file)
@@ -6,6 +6,7 @@ from django.contrib.auth.decorators import login_required
 from django.utils.encoding import smart_str
 from django.shortcuts import redirect, render_to_response, get_object_or_404
 from django.template import RequestContext
+from django.http import Http404
 from sendfile import sendfile
 
 from datamaster_modeles import models as ref
@@ -14,6 +15,8 @@ from project.lib import get_employe_from_id
 from rh import models as rh
 from rh.lib import calc_remun
 from rh.decorators import drh_or_admin_required
+from rh.templatetags.rapports import SortHeaders
+from project.lib import multikeysort
 
 # pas de reference a DAE devrait etre refactorisé
 from dae.utils import get_employe_from_user
@@ -90,6 +93,9 @@ def rapports_poste(request):
     comble = 'all'
 
     for key, value in lookup_params.items():
+        if key == 'o' or key == 'ot':
+            del lookup_params[key]
+            continue
         if not isinstance(key, str):
             # 'key' will be used as a keyword argument later, so Python
             # requires it to be a string.
@@ -130,11 +136,29 @@ def rapports_poste(request):
             line['employe_id'] = employe.id
             line['employe_nom'] = employe.nom
             line['employe_prenom'] = employe.prenom
+        else:
+            line['employe_id'] = None
+            line['employe_nom'] = None
+            line['employe_prenom'] = None
+
+    if 'o' in request.GET:
+        try:
+            out = multikeysort(out, [request.GET['o']])
+        except KeyError:
+            raise Http404
 
     c = {
         'title': 'Rapport des postes',
         'postes': out,
         'count': len(out),
+        'headers': list(SortHeaders(request, (
+                (u"# de l'employé", "id"),
+                (u"Nom", "nom"),
+                (u"Implantation", "implantation"),
+                (u"# de l'employé", "employe_id"),
+                (u"Nom", "employe_nom"),
+                (u"Prénom", "employe_prenom"),
+            )).headers())
     }
 
     return render_to_response('rh/rapports/postes.html', c, RequestContext(request))