Browse Source

feat(base): étoffe les capacités de filtrage du mailling; présente la liste des destinataires; fix #46; fix #44

pull/52/head
François Poulain 1 year ago committed by Cliss XXI - dev
parent
commit
b8444992e9
  1. 9
      docs/source/administration/index.rst
  2. 48
      gvot/base/forms.py
  3. 65
      gvot/base/views.py
  4. 36
      gvot/templates/mailing/confirm.html
  5. 5
      gvot/templates/mailing/index.html

9
docs/source/administration/index.rst

@ -133,7 +133,8 @@ Les colonnes « nom », « prenom » et « collectif » ne peuvent être v @@ -133,7 +133,8 @@ Les colonnes « nom », « prenom » et « collectif » ne peuvent être v
à la fois. Dit autrement le pouvoir doit au moins désigner un nom, un prénom
ou un nom de collectif.
La colonne « courriel » ne peut être vide.
La colonne « courriel » ne peut être vide. Plusieurs colonnes « courriel »
peuvent être présentes.
Une pondération absente sera interprétée à la valeur « 1 ».
@ -262,8 +263,12 @@ Expédition d'un mailing @@ -262,8 +263,12 @@ Expédition d'un mailing
Vous pouvez démarrer un mailing d'annonce, directement depuis le panel
« Pouvoirs ».
Il est possible de filtrer les destinataires concernés selon plusieurs
critères : ayant voté ou non, ayant tel attribut défini ou non,
à quelle valeur, etc.
Une fois les modalités d'envoi définies, une confirmation avec
prévisualisation du mailing vous sera présentée.
prévisualisation du mailing et de ses destinataires vous sera présentée.
.. note::

48
gvot/base/forms.py

@ -29,8 +29,30 @@ class MaillingForm(forms.Form): @@ -29,8 +29,30 @@ class MaillingForm(forms.Form):
filter_key = forms.ChoiceField(
choices=(),
required=False,
help_text="Filtre optionnellement les pouvoirs dont "
"le champ personnalisé désigné est égal à :",
)
filter_ope = forms.ChoiceField(
choices=(
(None, "Choississez une opération de filtrage"),
('icontains', "Contient"),
('istartswith', "Commence par"),
('iendswith', "Termine par"),
('iexact', "Est"),
('not_isempty', "Est défini"),
('not_icontains', "Est défini et ne contient pas"),
('not_istartswith', "Est défini et ne commence pas par"),
('not_iendswith', "Est défini et ne termine pas par"),
('not_iexact', "Est défini et est différent de"),
('isempty', "N'est pas défini"),
('empty_not_icontains', "N'est pas défini ou ne contient pas"),
(
'empty_not_istartswith',
"N'est pas défini ou ne commence pas par",
),
('empty_not_iendswith', "N'est pas défini ou ne termine pas par"),
('empty_not_iexact', "N'est pas défini ou est différent de"),
),
required=False,
)
filter_val = forms.CharField(
@ -38,6 +60,28 @@ class MaillingForm(forms.Form): @@ -38,6 +60,28 @@ class MaillingForm(forms.Form):
max_length=255,
)
def clean(self):
cleaned_data = super().clean()
if cleaned_data.get('filter_key') and not cleaned_data.get(
'filter_ope'
):
self.add_error(
'filter_ope',
ValidationError(
"Veuillez définir l'opération de filtrage à appliquer"
),
)
if cleaned_data.get('filter_ope') and not cleaned_data.get(
'filter_key'
):
self.add_error(
'filter_key',
ValidationError(
"Veuillez définir à quel champ s'applique le filtre"
),
)
return cleaned_data
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

65
gvot/base/views.py

@ -2,6 +2,7 @@ import csv @@ -2,6 +2,7 @@ import csv
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db.models import Q
from django.shortcuts import redirect
from django.urls import reverse, reverse_lazy
from django.views.generic import FormView, RedirectView, detail
@ -137,6 +138,7 @@ class MaillingIndex(FormInvalidMixin, FormView): @@ -137,6 +138,7 @@ class MaillingIndex(FormInvalidMixin, FormView):
self.request.session['dests'] = form.cleaned_data['dests']
self.request.session['template_id'] = form.cleaned_data['template'].id
self.request.session['filter_key'] = form.cleaned_data['filter_key']
self.request.session['filter_ope'] = form.cleaned_data['filter_ope']
self.request.session['filter_val'] = form.cleaned_data['filter_val']
return super().form_valid(form)
@ -154,6 +156,7 @@ class MaillingConfirm(FormInvalidMixin, FormView): @@ -154,6 +156,7 @@ class MaillingConfirm(FormInvalidMixin, FormView):
self.dests = self.request.session.get('dests', None)
self.template_id = self.request.session.get('template_id', None)
self.filter_key = self.request.session.get('filter_key', None)
self.filter_ope = self.request.session.get('filter_ope', None)
self.filter_val = self.request.session.get('filter_val', None)
def dispatch(self, request, *args, **kwargs):
@ -174,10 +177,51 @@ class MaillingConfirm(FormInvalidMixin, FormView): @@ -174,10 +177,51 @@ class MaillingConfirm(FormInvalidMixin, FormView):
self.qs = pouvoirs.filter(vote__isnull=True)
if self.filter_key:
self.qs = pouvoirs.filter(
champ_perso__intitule=self.filter_key,
champ_perso__contenu=self.filter_val,
).distinct()
if self.filter_ope in [
'icontains',
'iendswith',
'iexact',
'istartswith',
]:
filtre = Q(champ_perso__intitule=self.filter_key) & Q(
**{
'champ_perso__contenu__'
+ self.filter_ope: self.filter_val
}
)
elif self.filter_ope in [
'not_icontains',
'not_iendswith',
'not_iexact',
'not_istartswith',
]:
filtre = Q(champ_perso__intitule=self.filter_key) & ~Q(
**{
'champ_perso__contenu__'
+ self.filter_ope[4:]: self.filter_val
}
)
elif self.filter_ope in [
'empty_not_icontains',
'empty_not_iendswith',
'empty_not_iexact',
'empty_not_istartswith',
]:
filtre = ~Q(champ_perso__intitule=self.filter_key) | (
Q(champ_perso__intitule=self.filter_key)
& ~Q(
**{
'champ_perso__contenu__'
+ self.filter_ope[10:]: self.filter_val
}
)
)
elif self.filter_ope == 'isempty':
filtre = ~Q(champ_perso__intitule=self.filter_key)
elif self.filter_ope == 'not_isempty':
filtre = Q(champ_perso__intitule=self.filter_key)
self.qs = pouvoirs.filter(filtre).distinct()
return super().dispatch(request, *args, **kwargs)
@ -189,13 +233,19 @@ class MaillingConfirm(FormInvalidMixin, FormView): @@ -189,13 +233,19 @@ class MaillingConfirm(FormInvalidMixin, FormView):
# drop now obsolete session data
self.request.session.pop('dests', False)
self.request.session.pop('template_id', False)
self.request.session.pop('filter_key', False)
self.request.session.pop('filter_ope', False)
self.request.session.pop('filter_val', False)
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['scrutin'] = self.template.scrutin
context['nb'] = self.qs.values_list('courriels__courriel').count()
context['qs'] = self.qs
context['nb_dests'] = self.qs.values_list(
'courriels__courriel'
).count()
if self.dests == 'tous':
context['dests'] = "tous les participants"
elif self.dests == 'exprimes':
@ -203,6 +253,11 @@ class MaillingConfirm(FormInvalidMixin, FormView): @@ -203,6 +253,11 @@ class MaillingConfirm(FormInvalidMixin, FormView):
elif self.dests == 'abstenus':
context['dests'] = "tous les participants n'ayant pas encore voté"
context['filter_key'] = self.filter_key
context['filter_ope'] = (
dict(forms.MaillingForm.declared_fields['filter_ope'].choices)
.get(self.filter_ope)
.lower()
)
context['filter_val'] = self.filter_val
context['preview'] = dict(
zip(

36
gvot/templates/mailing/confirm.html

@ -7,20 +7,19 @@ @@ -7,20 +7,19 @@
{% include "wagtailadmin/shared/header.html" with title="Démarrer un mailing" icon="mail" subtitle=" Analyse du mailing avant validation" %}
<div class="nice-padding">
<div class="overflow">
<section>
<h2>Résumé</h2>
<p>
Vous êtes sur le point d'envoyer un mailing à <strong>{{ dests }}</strong> au scrutin « {{ scrutin.title }} »{% if filter_key %}
dont le champ « <i>{{ filter_key }}</i> » est égal à « <i>{{ filter_val }}</i> »{% endif %}.
dont le champ « <strong>{{ filter_key }}</strong> » {{ filter_ope }}{% if "défini" not in filter_ope %} « <strong>{{ filter_val }}</strong> »{% endif %}{% endif %}.
</p>
<div class="help-block {% if nb %}help-info{% else %}help-critical{% endif %}">
Cette action va engendrer une file de <strong>{{ nb }}</strong> courriel{{ nb | pluralize }}.
Cette action va engendrer une file de <strong>{{ qs.count }}</strong> courriel{{ qs.count | pluralize }} à <strong>{{ nb_dests }}</strong> destinataire{{ nb_dests | pluralize }}.
</div>
<hr>
<h2>Prévisualisation de l'email</h2>
<h3>Sujet : {{ preview.subject }}</h3>
<div class="row row-flush">
{% if preview.html %}
<div class="col6">
<h3 style="background: #e6e6e6; padding: 1em; margin-bottom: 1em;">Version texte</h3>
@ -34,8 +33,31 @@ @@ -34,8 +33,31 @@
</div>
{% endif %}
</ul>
</div>
</section>
</div>
<section>
<hr>
<h2>Prévisualisation de la liste des destinataires</h2>
<ul class="listing">
{% for pouvoir in qs %}
<li>
<div class="row row-flush">
<div class="col4 title">
<a href="{% url "base_pouvoir_modeladmin_edit" pouvoir.pk %}">
{{ pouvoir.nom | default_if_none:'' }}
{{ pouvoir.prenom | default_if_none:'' }}
{% if pouvoir.nom or pouvoir.prenom %}{% if pouvoir.collectif %}&ndash;{% endif %}{% endif %}
{{ pouvoir.collectif }}
</a>
</div>
<div class="col8">
{{ pouvoir.courriels_list | join:", " | default_if_none:'-' | truncatechars:100 }}
</div>
</div>
</li>
{% endfor %}
</ul>
</section>
</div>
<form action="" method="POST" novalidate>
@ -45,8 +67,8 @@ @@ -45,8 +67,8 @@
<ul>
<li class="actions">
<div class="dropdown dropup dropdown-button match-width">
<button type="submit" class="button action-save button-longrunning{% if import_ko %} disabled{% endif %}" data-clicked-text="Envoyer le mailing">
<span class="icon icon-spinner"></span><em>{% if import_ko %}Impossible d'{% endif %}Envoyer le mailing</em>
<button type="submit" class="button action-save button-longrunning" data-clicked-text="Envoyer le mailing">
<span class="icon icon-spinner"></span><em>Envoyer le mailing</em>
</button>
</div>
</li>

5
gvot/templates/mailing/index.html

@ -42,13 +42,16 @@ @@ -42,13 +42,16 @@
<li class="object file_field">
<div class="title-wrapper">
<label for="id_destinataires">
Filtrage supplémentaire
Filtrage supplémentaire optionnel
</label>
</div>
<div class="object-layout">
<div class="object-layout_big-part">
<fieldset>
{% include "wagtailadmin/shared/field_as_li.html" with field=form.filter_key show_label=False %}
<p class="help">Filtre optionnellement les pouvoirs dont le champ personnalisé désigné :</p>
{% include "wagtailadmin/shared/field_as_li.html" with field=form.filter_ope show_label=False %}
<p class="help">la valeur :</p>
{% include "wagtailadmin/shared/field_as_li.html" with field=form.filter_val show_label=False %}
</fieldset>
</div>

Loading…
Cancel
Save
Map all the world