# -*- coding: utf-8 -*- from __future__ import unicode_literals 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.dispatch import receiver from coin.members.models import Member class MaillingList(models.Model): short_name = models.CharField( 'identifiant technique', max_length=50, help_text=( "c'est l'identifiant qui servira à " "communiquer avec le système de mailling-list" "(typiquement, la partie avant le \"@\" dans l'adress )" ) ) email = models.EmailField("adresse mail d'envoi") verbose_name = models.CharField( 'nom complet', max_length=130, help_text="Nom affiché dans l'interface membre" ) description = models.TextField() subscribers = models.ManyToManyField( Member, related_name='subscribed_maillinglists', verbose_name='abonné·e·s') class Meta: verbose_name = 'liste mail' verbose_name_plural = 'listes mail' def __unicode__(self): return '{} ({})'.format(self.verbose_name, self.email) def as_text_listing(self): """ One subscriber email per line """ return '\n'.join( self.subscribers.values_list('email', flat=True)) def sync_to_list_server(self, force_clear=False): if not settings.MAILLIST_SYNC_COMMAND: raise ValueError('You should define MAILLIST_SYNC_COMMAND' ' setting to use maillist module') else: cmd = settings.MAILLIST_SYNC_COMMAND.format( email=self.email, short_name=self.short_name, ) p = subprocess.Popen( cmd, shell=True, stdin=subprocess.PIPE, stderr=subprocess.PIPE) if force_clear: text_listing = '' else: text_listing = self.as_text_listing() out_stdout, out_stderr = p.communicate(text_listing) if p.returncode != 0: raise SystemError( "Erreur à l'appel de la commande : \"{}\"".format( 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() else: return 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) def store_previous_email(sender, instance, *args, **kwargs): """Record the email address for post_save handler update_an_email_address needs the old email address for comparison, but this information is not available at post_save stage. """ member = instance # if not, this is a user creation, nothing to do if member.pk: old_member = Member.objects.get(pk=member.pk) member._previous_email = old_member.email @receiver(post_save, sender=Member) def update_an_email_address(sender, instance, *args, **kwargs): """Check if the member email has changed and sync mail lists if so. We do that at post_save stage because we need the new information to be recorded in database, otherwise, sync_list_to_server() would use the old email. """ member = instance old_email = getattr(member, '_previous_email', None) if old_email and (old_email != member.email): for maillist in member.subscribed_maillinglists.all(): maillist.sync_to_list_server() # Error handling