]> git.0d.be Git - django-panik-newsletter.git/blob - newsletter/models.py
add support for subscribing to mailman3
[django-panik-newsletter.git] / newsletter / models.py
1 import datetime
2 import hashlib
3 import random
4 import smtplib
5 import subprocess
6 from email.mime.multipart import MIMEMultipart
7 from email.mime.text import MIMEText
8
9 import html2text
10 import mechanize
11 import requests
12 from ckeditor.fields import RichTextField
13 from django.conf import settings
14 from django.core.mail import send_mail
15 from django.db import IntegrityError, models
16 from django.template import loader
17 from django.template.loader import render_to_string
18 from django.urls import reverse
19 from django.utils.encoding import force_bytes
20 from django.utils.safestring import mark_safe
21 from django.utils.translation import ugettext
22 from django.utils.translation import ugettext_lazy as _
23
24
25 class Subscriber(models.Model):
26     email = models.EmailField(unique=False)
27     inscription_date = models.DateTimeField(auto_now_add=True)
28     is_validated = models.NullBooleanField()
29     is_registered = models.NullBooleanField()
30     password = models.CharField(max_length=100)
31     bot_check1 = models.BooleanField(default=False)
32     bot_check2 = models.BooleanField(default=False)
33     user_agent = models.CharField(max_length=1000, blank=True)
34     source_ip = models.CharField(max_length=100, blank=True)
35
36     def __unicode__(self):
37         return self.email
38
39     def is_from_bot(self):
40         return bool(self.bot_check1 or self.bot_check2)
41
42     def send_confirmation_email(self):
43         if self.is_from_bot():
44             return
45         self.password = hashlib.sha1(force_bytes(str(random.random()))).hexdigest()
46         confirm_subject = loader.get_template('newsletter/confirmation_email_subject.txt')
47         confirm_body = loader.get_template('newsletter/confirmation_email_body.txt')
48         context = {'token': self.password}
49         send_mail(
50             confirm_subject.render(context).strip(),
51             confirm_body.render(context),
52             settings.NEWSLETTER_SENDER,
53             [self.email],
54         )
55         self.is_validated = False
56         self.save()
57
58     def subscribe(self):
59         newsletter_service = getattr(settings, 'NEWSLETTER_SERVICE', 'mailman')
60         if newsletter_service == 'mailman':
61             return self.subscribe_in_mailman()
62         elif newsletter_service == 'mailman3':
63             return self.subscribe_in_mailman3()
64         elif newsletter_service == 'mailchimp':
65             return self.subscribe_in_mailchimp()
66
67     def subscribe_in_mailman(self):
68         try:
69             subprocess.run(['listadmin', '--add-member', self.email, settings.NEWSLETTER_NAME], check=True)
70         except subprocess.CalledProcessError:
71             # maybe an error because email is already registered?
72             result = subprocess.run(
73                 ['listadmin', '-l', settings.NEWSLETTER_NAME], check=True, capture_output=True, text=True
74             )
75             if self.email in result.stdout.splitlines():
76                 self.is_registered = True
77                 self.save()
78             return
79         self.is_registered = True
80         self.save()
81
82     def subscribe_in_mailman3(self):
83         # emulate browser to login and subscribe user
84         br = mechanize.Browser()
85         br.open(settings.NEWSLETTER_MAILMAN3_LIST_URL)
86         br.follow_link(url_regex=re.compile('/accounts/login'))
87         br.select_form(action=lambda x: '/login/' in x)
88         br['login'] = settings.NEWSLETTER_MAILMAN3_LOGIN
89         br['password'] = settings.NEWSLETTER_MAILMAN3_PASSWORD
90         response = br.submit()
91         assert br.find_link(url_regex=re.compile('/accounts/logout'))
92         br.open(settings.NEWSLETTER_MAILMAN3_LIST_URL + 'mass_subscribe/')
93         br.select_form(action=lambda x: '/mass_subscribe/' in x)
94         br['emails'] = self.email
95         br['pre_confirmed'] = ['on']
96         br['pre_approved'] = ['on']
97         br['pre_verified'] = ['on']
98         br['send_welcome_message'] = ['False']
99         response = br.submit()
100         self.is_registered = True
101         self.save()
102
103     def subscribe_in_mailchimp(self):
104         dc = settings.MAILCHIMP_DC
105         apikey = settings.MAILCHIMP_APIKEY
106         list_id = settings.MAILCHIMP_LIST_ID
107         self.email = self.email.lower()
108
109         email_hash = hashlib.md5(self.email.encode()).hexdigest()
110
111         resp = requests.put(
112             f'https://{dc}.api.mailchimp.com/3.0/lists/{list_id}/members/{email_hash}',
113             auth=('key', apikey),
114             json={
115                 'email_address': self.email,
116                 'status_if_new': 'subscribed',
117             },
118         )
119         if resp.ok and resp.json().get('status') == 'subscribed':
120             self.is_registered = True
121             self.save()
122
123
124 class Newsletter(models.Model):
125     class Meta:
126         verbose_name = _('Newsletter')
127         verbose_name_plural = _('Newsletters')
128         ordering = ['date']
129
130     subject = models.CharField(_('Title'), max_length=50)
131     date = models.DateField(_('Date'))
132     text = RichTextField(_('Text'), null=True, blank=True)
133
134     expedition_datetime = models.DateTimeField(_('Expedition Date/time'), null=True, blank=True)
135
136     def send(self):
137         msg = MIMEMultipart('alternative')
138         msg['Subject'] = self.subject
139         msg['From'] = settings.NEWSLETTER_SENDER
140         msg['To'] = '%s@%s' % (settings.NEWSLETTER_NAME, settings.NEWSLETTER_DOMAIN)
141
142         h2t = html2text.HTML2Text()
143         h2t.unicode_snob = True
144         context = {
145             'text_part': mark_safe(h2t.handle(self.text)),
146             'html_part': mark_safe(self.text),
147         }
148
149         part1 = MIMEText(render_to_string('newsletter/email_body.txt', context), 'plain', _charset='utf-8')
150         part2 = MIMEText(render_to_string('newsletter/email_body.html', context), 'html', _charset='utf-8')
151
152         msg.attach(part1)
153         msg.attach(part2)
154
155         s = smtplib.SMTP('localhost')
156         s.sendmail(msg['From'], msg['To'], msg.as_string())
157         s.quit()
158
159         self.expedition_datetime = datetime.datetime.now()
160         self.save()
161
162     def get_absolute_url(self):
163         return reverse('newsletter-view', kwargs={'pk': self.id})