Browse Source

Use an intermediate model to manage mailling list subscriptions.

SimonBoulier 6 years ago
parent
commit
2b59d86285

+ 35 - 11
maillists/admin.py

@@ -8,7 +8,18 @@ from django.contrib import admin
 from django.contrib import messages
 from django.http import HttpResponseRedirect
 
-from .models import MaillingList, SyncCommandError
+from .models import MaillingList, MaillingListSubscription, SyncCommandError
+import coin.members.admin
+
+
+class MaillingListSubscriptionInline(admin.TabularInline):
+    model = MaillingListSubscription
+    extra = 0
+
+    form = autocomplete_light.modelform_factory(
+        MaillingListSubscription,
+        fields='__all__',
+    )
 
 
 class MaillingListAdmin(admin.ModelAdmin):
@@ -32,29 +43,42 @@ class MaillingListAdmin(admin.ModelAdmin):
     sync_to_server.short_description = (
         'Synchroniser les listes sélectionnées vers le serveur')
 
-    form = autocomplete_light.modelform_factory(
-        MaillingList,
-        fields='__all__',
-    )
+    inlines = [MaillingListSubscriptionInline,]
 
-    def change_view(self, request, object_id, form_url='', extra_context=None):
+    def change_view(self, request, object_id, *args, **kwargs):
         try:
             return super(MaillingListAdmin, self).change_view(
-                request, object_id, form_url, extra_context)
-
+                request, object_id, *args, **kwargs)
         except SyncCommandError as e:
             try:
                 ml = MaillingList.objects.get(pk=object_id)
                 ml_name = "La liste mail « {} »".format(ml.short_name)
             except MaillingList.DoesNotExist:
                 ml_name = "La nouvelle liste mail"
-
             messages.error(
                 request,
                 "{} n'a pas pu être synchronisée".format(ml_name) +
-                " vers le serveur de listes : « {} ». Vous pouvez".format(e) +
-                " relancer la synchro depuis la liste des listes mail.")
+                " vers le serveur de listes : « {} ».".format(e))
             return HttpResponseRedirect(request.path)
 
 
 admin.site.register(MaillingList, MaillingListAdmin)
+
+
+class MemberAdmin(coin.members.admin.MemberAdmin):
+    inlines = coin.members.admin.MemberAdmin.inlines + [MaillingListSubscriptionInline]
+
+    def change_view(self, request, *args, **kwargs):
+        try:
+            return super(MemberAdmin, self).change_view(
+                request, *args, **kwargs)
+        except SyncCommandError as e:
+            messages.error(
+                request,
+                "Les listes mails n'ont pas pu être synchronisées" +
+                " vers le serveur de listes : « {} ».".format(e))
+            return HttpResponseRedirect(request.path)
+
+
+admin.site.unregister(coin.members.admin.Member)
+admin.site.register(coin.members.admin.Member, MemberAdmin)

+ 20 - 4
maillists/migrations/0001_initial.py

@@ -16,15 +16,31 @@ class Migration(migrations.Migration):
             name='MaillingList',
             fields=[
                 ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
-                ('short_name', models.CharField(help_text=b'c\'est l\'identifiant qui servira \xc3\xa0 communiquer avec le syst\xc3\xa8me de mailling-list(typiquement, la partie avant le "@" dans l\'adress )', max_length=50, verbose_name=b'identifiant technique')),
-                ('email', models.EmailField(max_length=254, verbose_name=b"adresse mail d'envoi")),
-                ('verbose_name', models.CharField(help_text=b"Nom affich\xc3\xa9 dans l'interface membre", max_length=130, verbose_name=b'nom complet')),
+                ('short_name', models.CharField(help_text='c\'est l\'identifiant qui servira \xe0 communiquer avec le syst\xe8me de mailling-list(typiquement, la partie avant le "@" dans l\'adress )', max_length=50, verbose_name='identifiant technique')),
+                ('email', models.EmailField(max_length=254, verbose_name="adresse mail d'envoi")),
+                ('verbose_name', models.CharField(help_text="Nom affich\xe9 dans l'interface membre", max_length=130, verbose_name='nom complet')),
                 ('description', models.TextField()),
-                ('subscribers', models.ManyToManyField(related_name='subscribed_maillinglists', verbose_name=b'abonn\xc3\xa9\xc2\xb7e\xc2\xb7s', to=settings.AUTH_USER_MODEL)),
             ],
             options={
                 'verbose_name': 'liste mail',
                 'verbose_name_plural': 'listes mail',
             },
         ),
+        migrations.CreateModel(
+            name='MaillingListSubscription',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('maillinglist', models.ForeignKey(to='maillists.MaillingList')),
+                ('member', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'verbose_name': 'abonnement \xe0 une liste mail',
+                'verbose_name_plural': 'abonnements \xe0 des listes mail',
+            },
+        ),
+        migrations.AddField(
+            model_name='maillinglist',
+            name='subscribers',
+            field=models.ManyToManyField(related_name='subscribed_maillinglists', verbose_name='abonn\xe9\xb7e\xb7s', to=settings.AUTH_USER_MODEL, through='maillists.MaillingListSubscription', blank=True),
+        ),
     ]

+ 0 - 30
maillists/migrations/0002_auto_20181106_1148.py

@@ -1,30 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-from django.conf import settings
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('maillists', '0001_initial'),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name='maillinglist',
-            name='short_name',
-            field=models.CharField(help_text='c\'est l\'identifiant qui servira \xe0 communiquer avec le syst\xe8me de mailling-list(typiquement, la partie avant le "@" dans l\'adress )', max_length=50, verbose_name='identifiant technique'),
-        ),
-        migrations.AlterField(
-            model_name='maillinglist',
-            name='subscribers',
-            field=models.ManyToManyField(related_name='subscribed_maillinglists', verbose_name='abonn\xe9\xb7e\xb7s', to=settings.AUTH_USER_MODEL, blank=True),
-        ),
-        migrations.AlterField(
-            model_name='maillinglist',
-            name='verbose_name',
-            field=models.CharField(help_text="Nom affich\xe9 dans l'interface membre", max_length=130, verbose_name='nom complet'),
-        ),
-    ]

+ 21 - 23
maillists/models.py

@@ -6,7 +6,7 @@ import subprocess
 
 from django.conf import settings
 from django.db import models
-from django.db.models.signals import pre_save, post_save, m2m_changed
+from django.db.models.signals import pre_save, post_save, post_delete
 from django.dispatch import receiver
 
 from coin.members.models import Member
@@ -15,6 +15,16 @@ from coin.members.models import Member
 class SyncCommandError(SystemError):
     pass
 
+class MaillingListSubscription(models.Model):
+    member = models.ForeignKey(Member, verbose_name='membre')
+    maillinglist = models.ForeignKey('MaillingList', verbose_name='liste mail')
+
+    class Meta:
+        verbose_name = 'abonnement à une liste mail'
+        verbose_name_plural = 'abonnements à des listes mail'
+        unique_together = ('member', 'maillinglist')
+
+
 class MaillingList(models.Model):
     short_name = models.CharField(
         'identifiant technique', max_length=50,
@@ -32,6 +42,7 @@ class MaillingList(models.Model):
     description = models.TextField()
     subscribers = models.ManyToManyField(
         Member, related_name='subscribed_maillinglists',
+        through=MaillingListSubscription,
         verbose_name='abonné·e·s', blank=True)
 
     class Meta:
@@ -71,30 +82,17 @@ class MaillingList(models.Model):
                         out_stderr.decode('utf-8')))
 
 
-@receiver(m2m_changed, sender=MaillingList.subscribers.through)
-def push_updated_list(sender, instance, action, reverse, model, pk_set, **kwargs):
-    if action in ('post_add', 'post_remove'):
-        if reverse:
-            impacted_mls = MaillingList.objects.filter(pk__in=pk_set)
-        else:
-            impacted_mls = [instance]
-    elif action == 'post_clear' and not reverse:
-        impacted_mls = [instance]
-
-    # cannot be handled at post_clear (we would have lost the information on
-    # what has been removed)
-    elif action == 'pre_clear' and reverse:
-        impacted_mls = instance.subscribed_maillinglists.all()
-
+@receiver(post_save, sender=MaillingListSubscription)
+def push_new_subscription(sender, instance, created, raw, *args, **kwargs):
+    if raw:
+        print("The synchronization of mailling list with Coin was not performed, please launch it by hand in the admin interface.")
     else:
-        return
+        instance.maillinglist.sync_to_list_server()
+
+@receiver(post_delete, sender=MaillingListSubscription)
+def push_remove_subscription(sender, instance, *args, **kwargs):
+    instance.maillinglist.sync_to_list_server()
 
-    for ml in impacted_mls:
-        if action == 'pre_clear':
-            # We have to force it because it has not yet been reflected in DB.
-            ml.sync_to_list_server(force_clear=True)
-        else:
-            ml.sync_to_list_server()
 
 
 @receiver(pre_save, sender=Member)

+ 6 - 5
maillists/views.py

@@ -7,7 +7,7 @@ from django import forms
 from django.forms import formset_factory
 from django.shortcuts import render, redirect
 
-from .models import MaillingList, SyncCommandError
+from .models import MaillingList, MaillingListSubscription, SyncCommandError
 from coin.members.models import Member
 
 
@@ -51,11 +51,12 @@ def lists_list(request):
             )
             old_subscriptions = set(request.user.subscribed_maillinglists.all())
             try:
+                # add
                 for mail_list in new_subscriptions - old_subscriptions:
-                    request.user.subscribed_maillinglists.add(mail_list)
-
-                for mail_list in old_subscriptions - new_subscriptions:
-                    request.user.subscribed_maillinglists.remove(mail_list)
+                    MaillingListSubscription.objects.create(member=request.user, maillinglist=mail_list)
+                # remove
+                to_remove = old_subscriptions - new_subscriptions
+                MaillingListSubscription.objects.filter(member=request.user, maillinglist__in=to_remove).delete()
             except SyncCommandError as e:
                 messages.error(
                     request,