models.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from contextlib import contextmanager
  4. import subprocess
  5. from django.conf import settings
  6. from django.db import models
  7. from django.db.models.signals import pre_save, post_save, post_delete
  8. from django.dispatch import receiver
  9. from coin.members.models import Member
  10. class SyncCommandError(SystemError):
  11. pass
  12. class MaillingListSubscription(models.Model):
  13. member = models.ForeignKey(Member, verbose_name='membre')
  14. maillinglist = models.ForeignKey('MaillingList', verbose_name='liste mail')
  15. class Meta:
  16. verbose_name = 'abonnement à une liste mail'
  17. verbose_name_plural = 'abonnements aux listes mail'
  18. unique_together = ('member', 'maillinglist')
  19. def __str__(self):
  20. return str(self.maillinglist)
  21. class MaillingList(models.Model):
  22. short_name = models.CharField(
  23. 'identifiant technique', max_length=50, unique=True,
  24. help_text=(
  25. "c'est l'identifiant qui servira à "
  26. "communiquer avec le serveur de liste mail "
  27. "(typiquement, la partie avant le \"@\" dans l'adress )"
  28. )
  29. )
  30. email = models.EmailField("adresse mail d'envoi", unique=True)
  31. verbose_name = models.CharField(
  32. 'nom complet', max_length=130,
  33. help_text="Nom affiché dans l'interface membre"
  34. )
  35. description = models.TextField()
  36. subscribers = models.ManyToManyField(
  37. Member, related_name='subscribed_maillinglists',
  38. through=MaillingListSubscription,
  39. verbose_name='abonné·e·s', blank=True)
  40. class Meta:
  41. verbose_name = 'liste mail'
  42. verbose_name_plural = 'listes mail'
  43. def __unicode__(self):
  44. return '{} ({})'.format(self.verbose_name, self.email)
  45. def as_text_listing(self):
  46. """ One subscriber email per line
  47. """
  48. return '\n'.join(
  49. self.subscribers.values_list('email', flat=True))
  50. def sync_to_list_server(self, force_clear=False):
  51. if not settings.MAILLIST_SYNC_COMMAND:
  52. raise ValueError('You should define MAILLIST_SYNC_COMMAND'
  53. ' setting to use maillist module')
  54. else:
  55. cmd = settings.MAILLIST_SYNC_COMMAND.format(
  56. email=self.email,
  57. short_name=self.short_name,
  58. )
  59. p = subprocess.Popen(
  60. cmd, shell=True,
  61. stdin=subprocess.PIPE, stderr=subprocess.PIPE)
  62. if force_clear:
  63. text_listing = ''
  64. else:
  65. text_listing = self.as_text_listing()
  66. out_stdout, out_stderr = p.communicate(text_listing)
  67. if p.returncode != 0:
  68. raise SyncCommandError(
  69. "Erreur à l'appel de la commande : \"{}\"".format(
  70. out_stderr.decode('utf-8')))
  71. def push_new_subscription(sender, instance, created, raw, *args, **kwargs):
  72. if raw:
  73. print("The synchronization of mailling list with Coin was not performed, please launch it by hand in the admin interface.")
  74. elif not getattr(sender, 'skip_sync', False):
  75. instance.maillinglist.sync_to_list_server()
  76. def push_remove_subscription(sender, instance, *args, **kwargs):
  77. if not getattr(sender, 'skip_sync', False):
  78. instance.maillinglist.sync_to_list_server()
  79. def store_previous_email(sender, instance, *args, **kwargs):
  80. """Record the email address for post_save handler
  81. update_an_email_address needs the old email address for comparison, but
  82. this information is not available at post_save stage.
  83. """
  84. member = instance
  85. # if not, this is a user creation, nothing to do
  86. if member.pk:
  87. old_member = Member.objects.get(pk=member.pk)
  88. member._previous_email = old_member.email
  89. @receiver(post_save, sender=Member)
  90. def update_an_email_address(sender, instance, *args, **kwargs):
  91. """Check if the member email has changed and sync mail lists if so.
  92. We do that at post_save stage because we need the new information to be
  93. recorded in database, otherwise, sync_list_to_server() would use the old
  94. email.
  95. """
  96. member = instance
  97. old_email = getattr(member, '_previous_email', None)
  98. if old_email and (old_email != member.email):
  99. for maillist in member.subscribed_maillinglists.all():
  100. maillist.sync_to_list_server()
  101. # Error handling ???
  102. # try:
  103. # except SyncCommandError as e:
  104. # print("error", e)
  105. # we cannot send a message because we don't have the request
  106. SIGNALS = [
  107. (Member, pre_save, store_previous_email),
  108. (Member, post_save, update_an_email_address),
  109. (MaillingListSubscription, post_save, push_new_subscription),
  110. (MaillingListSubscription, post_delete, push_remove_subscription),
  111. ]
  112. def connect_signals():
  113. for sender, signal, receiver in SIGNALS:
  114. signal.connect(sender=sender, receiver=receiver)
  115. def disconnect_signals():
  116. for sender, signal, receiver in SIGNALS:
  117. signal.disconnect(sender=sender, receiver=receiver)
  118. # Do it once
  119. connect_signals()
  120. @contextmanager
  121. def skip_maillist_sync():
  122. """ Allows to skip temporary signals
  123. """
  124. disconnect_signals()
  125. try:
  126. yield
  127. finally:
  128. connect_signals()