Browse Source

Add `import_mailling_list` command to bulk import a mailling list

Jocelyn Delalande 6 years ago
parent
commit
eac24dbb41

+ 17 - 8
doc/admin/maillists.md

@@ -63,14 +63,8 @@ des inscriptions sont faites par d'autres moyens (mail, interface du serveur de
 liste de discussions… etc), ces modifications risquent d'être écrasées par la
 liste de discussions… etc), ces modifications risquent d'être écrasées par la
 gestion d'abonnements de Coin.
 gestion d'abonnements de Coin.
 
 
-- La commande de synchro est lancée à chaque abonnement/désabonnement. Si vous
-  inscrivez 100 membres d'un coup, ça pourrait être un peu long si ça passe
-  par SSH. Astuce quand vous initialisez vos listes pour la première fois donc :
-
-    1. mettre une commande de synchro bidon,
-    2. faire toutes vos inscriptions
-    3. mettre la vraie commande de synchro
-    4. lancer une synchro manuelle de chaque liste
+- La commande de synchro est lancée à chaque abonnement/désabonnement. Il y a
+  un outil d'import « en masse » : [import_mailling_list](#méthode-b-importer-des-abonnements-en-masse).
 
 
 Mise en place
 Mise en place
 -------------
 -------------
@@ -118,8 +112,23 @@ envisageables en recourant à un petit script sur mesure.
 
 
 ### 3. Ajouter des listes
 ### 3. Ajouter des listes
 
 
+Deux méthodes, selon que vous voulez initialiser la liste avec une vide ou
+pré-remplie avec une liste d'abonnés.
+
+#### Méthode A : créer une liste vide
 
 
 Se rendre dans l'admin de coin et dans la nouvelle catégorie « Listes mail »,
 Se rendre dans l'admin de coin et dans la nouvelle catégorie « Listes mail »,
 renseigner les listes mail que l'on souhaite voir gérées par Coin.
 renseigner les listes mail que l'on souhaite voir gérées par Coin.
 
 
+#### Méthode B : importer des abonnements « en masse »
+
+Pour créer une liste et faire un import initial de tou·te·s ses abonné·e·s d'un
+coup, vous pouvez utiliser la commande `./manage.py import_mailling_list` qui
+permet de créer une liste à partir de son adresse, son nom et d'un fichier
+texte contenant les adresses à abonner (qui doivent correspondre à des membres
+renseignés dans coin).
+
+Pour plus d'infos : `./manage.py import_mailling_list --help`
 
 
+*NB : Il vous faudra ensuite aller renseigner, via l'interface d'admin de coin,
+la description complète de la liste (celle que verront les membres).*

+ 1 - 0
maillists/management/__init__.py

@@ -0,0 +1 @@
+

+ 0 - 0
maillists/management/commands/__init__.py


+ 113 - 0
maillists/management/commands/import_mailling_list.py

@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import sys
+
+from django.core.management.base import BaseCommand, CommandError
+from django.db import transaction
+
+from coin.members.models import Member
+from maillists.models import (
+    MaillingList,
+    MaillingListSubscription,
+    skip_maillist_sync,
+)
+
+"""Import a text file of email addresses into mailling list subscription"
+
+Create a new mailling-list subscribing the provided addresses. The script will
+try to map email addresses to members, and stop if some addresses do not belong
+to any member.
+
+This command takes care to avoid triggering a sync per single subscription.
+"""
+
+
+class Command(BaseCommand):
+    help = __doc__
+
+    def add_arguments(self, parser):
+        parser.add_argument(
+            'subscribers_file',
+            help="The text file with the subscribed email addresses, one per line",
+        )
+        parser.add_argument(
+            '--email',
+            help='Mail address of the list',
+            required=True,
+        )
+        parser.add_argument(
+            '--verbose-name',
+            help='The full human-targeted name of the list',
+            required=True,
+        )
+
+        parser.add_argument(
+            '--force',
+            help='Import email adresses skipping those who do not belong to any member',
+            action='store_true',
+            default=False
+        )
+
+        parser.add_argument(
+            '--dry-run',
+            help='Do not write anything to database, just parse the file and show unknown addresses',
+            action='store_true',
+            default=False
+        )
+
+    @staticmethod
+    def _iter_emails(filename):
+        with open(filename) as f:
+            for l in f.readlines():
+                yield l.strip()
+
+    @staticmethod
+    def _get_unknown_email(emails):
+        for email in emails:
+            try:
+                Member.objects.get(email=email)
+            except Member.DoesNotExist:
+                yield email
+
+    @transaction.atomic
+    def handle(self, subscribers_file, email, verbose_name, force, dry_run, *args, **kwargs):
+        ml = MaillingList.objects.create(
+            short_name=email.split('@')[0],
+            email=email,
+            description='À RENSEIGNER',
+            verbose_name=verbose_name,
+        )
+        unknown_emails = []
+        with skip_maillist_sync():
+            for email in self._iter_emails(subscribers_file):
+                try:
+                    member = Member.objects.get(email=email)
+                except Member.DoesNotExist:
+                    unknown_emails.append(email)
+                else:
+                    mls = MaillingListSubscription(
+                        member=member,
+                        maillinglist=ml,
+                    )
+                    mls.skip_sync = True
+                    mls.save()
+
+        # Do it once… (db will be rollback if it fails)
+        sys.stdout.write('Pousse la liste sur le serveur… ',)
+        ml.sync_to_list_server()
+        print('OK')
+
+        if (len(unknown_emails) > 0) and not force:
+            print('ERREUR : Ces adresses ne correspondent à aucun membre')
+            for email in unknown_emails:
+                print(email)
+
+            raise CommandError(
+                "Rien n'a été créé en base, utiliser --force au besoin.")
+
+        elif force or len(unknown_emails) == 0:
+            if dry_run:
+                # exception triggers rollback
+                raise CommandError(
+                    "--dry-run est utilisée, rien n'a été écrit en base")