grimboite/articles/dev/2013-07-26-regroup_by.draft

140 lines
5.0 KiB
Plaintext

Django regroup_by
=================
Un truc hyper sympa en Django, c'est de pouvoir construire un dictionnaire à la volée dans un template. A partir du modèle de l'application, on peut facilement regrouper un ensemble d'éléments sur base d'un champ particulier lors de l'affichage d'un template.
Pour l'exemple ci-dessous, je définis plusieurs modules à exécuter et une ou plusieurs planifications pour chacun d'entre eux. Ces planifications peuvent être programmées une fois par mois (au début, au milieu ou à la fin du mois), à un moment de la semaine ou quotidiennement. Pour l'affichage, une manière simple de faire serait la suivante:
```django
{% for mod in modules %}
<h2>{{ mod }}</h2>
<p class="description info">{{ mod.description }}</p>
<h2>Planifications</h2>
<ul class="ulbox">
{% for plan in mod.planification_set.all %}
<li>Planifié à {{ p.time }} ({{ p.frequency }})</li>
{% endfor %}
</ul>
{% endfor %}
```
On aurait tous les modules qui s'afficheraient les uns après les autres (ça tombe bien, c'est ce qu'on veut), mais les planifications ne seraient pas regroupées. On aura vite le cas suivant:
```
Module 1
Planifié à 18h (quotidiennement)
Planifié à 18h (lundi)
Planifié à 10h (au début du mois)
Planifié à 10h (quotidiennement)
```
Pour l'affichage, j'aimerais en fait que toutes les planifications soient regroupées par fréquence, puis par moment d'exécution, pour avoir la visualisation suivante:
```
Module 1
Quotidiennement
Planifié à 10h
Planifié à 17h
Lundi
Planifié à 18h
Au début du mois
Planifié à 10h
```
C'est là que la méthode `regroup` de Django peut nous aider.
Supposons que nous ayons le modèle suivant :
```python
class Module(models.Model):
label = models.CharField(max_length=255, editable=False, db_index=True)
description = models.CharField(max_length=255, blank=True, editable=False)
def __unicode__(self):
return unicode(self.label)
class Planification(models.Model):
FREQUENCY = (
('1x par jour', ((0, 'Quotidiennement'),)),
('1x par semaine', (
(1, 'Lundi'),
(2, 'Mardi'),
(3, 'Mercredi'),
(4, 'Jeudi'),
(5, 'Vendredi'),
(6, 'Samedi'),
(7, 'Dimanche')
)),
('1x par mois', (
(-1, 'Au début du mois'),
(-15, 'Au milieu du mois'),
(-30, 'A la fin du mois')
)),)
MONTH_LENGTH=[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
module = models.ForeignKey('Module')
time = models.TimeField(default='18:00:00')
frequency = models.IntegerField(choices=FREQUENCY, verbose_name="Fréquence de planification")
class Meta:
""" Le petit paramètre qui permet de trier directement nos planifications par fréquence, puis par moment d'exécution """
ordering = ['frequency', 'time',]
def __unicode__(self):
return u'[%s] %s, %s' % (self.module, self.get_frequency_display(), self.time)
def __self__(self):
return '[%s] %s, %s' % (self.module, self.get_frequency_display(), self.time)
def __repr__(self):
return '[%s] %s, %s' % (self.module, self.get_frequency_display(), self.time)
```
Ce n'est pas trop compliqué, il y a juste une `ForeignKey` planquée quelque part entre la classe `Module` et la classe de `Planification` et qui permet de faire le lien entre ces deux classes. Dans le template:
1. on va d'abord boucler sur tous les modules présents,
2. puis on va appeler le groupement sur les planifications qui y sont associées.
3. On va ensuite boucler non seulement sur les différentes clés (la méthode `regroup` construit un **dictionnaire** et pas une simple liste), puis sur chacune des valeurs proposées pour cette clé:
```django
{% for mod in modules %}
<h2>{{ mod }}</h2>
<p class="description info">{{ mod.description }}</p>
{% regroup mod.planification_set.all by get_frequency_display as plans_by_freq %}
{% if plans_by_freq %}
<h2>Planifications</h2>
<ul class="ulbox">
{% for plan in plans_by_freq %}
<li><b>{{ plan.grouper }}</b>
<ul>
{% for p in plan.list %}
<li>{{ p.time }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
{% endif %}
{% endfor %}
```
Explications dans l'ordre:
* La méthode `regroup <bidule> by <chose>` permet de construire le dictionnaire sur base du champ souhaité
* Le `{% if plans_by_freq %}` n'est pas obligatoire, il est juste là pour ne pas afficher le titre s'il n'y a aucune planification associée
* `{% for plan in plans_by_freq %}` boucle sur toutes les clés/valeurs
* `{{ plan.grouper }}` permet d'afficher la valeur de la clé de tri
* `{% for p in plan.list %}` permet de boucler sur toutes les valeurs associées à la clé courante.
Django va alors trier ce dictionnaire avec les paramètres par défaut définis au niveau de la classe. C'est pour cette raison que j'avais ajouté un tri dans la classe `Planification`:
```python
class Meta:
ordering = ['frequency', 'time']
```
Petite astuce en plus: si on veut utiliser l'affichage d'un champ `CHOICE` plutôt que la valeur de l'option, il suffit d'utiliser la méthode `get_*field*_display` dans le template. Bref, c'est magique.