Commit 7e5ae3c2 authored by Thom Wiggers's avatar Thom Wiggers 📐
Browse files

Merge branch 'newsletters-documentation' into 'master'

Move the email sending to emails.py and add documentation for the entire package

Closes #578

See merge request !739
parents 3fe4e929 36d4059b
......@@ -25,6 +25,14 @@ newsletters\.apps module
:undoc-members:
:show-inheritance:
newsletters\.emails module
--------------------------
.. automodule:: newsletters.emails
:members:
:undoc-members:
:show-inheritance:
newsletters\.forms module
-------------------------
......
"""Registers admin interfaces for the newsletters module"""
from django.contrib import admin
from django.shortcuts import redirect
......@@ -8,6 +9,7 @@ from .forms import NewsletterEventForm, NewsletterItemForm
class NewsletterItemInline(admin.StackedInline):
"""The inline for the text items in the newsletter"""
form = NewsletterItemForm
model = NewsletterItem
extra = 0
......@@ -22,15 +24,19 @@ class NewsletterItemInline(admin.StackedInline):
class NewsletterEventInline(NewsletterItemInline):
"""The inline for the event items in the newsletter"""
form = NewsletterEventForm
model = NewsletterEvent
@admin.register(Newsletter)
class NewsletterAdmin(TranslatedModelAdmin):
"""Manage the newsletters"""
#: available fields in the admin overview list
list_display = ('title', 'date', 'sent',)
#: available inlines in the admin change form
inlines = (NewsletterItemInline, NewsletterEventInline,)
#: available fieldsets in the admin change form
fieldsets = (
(None, {
'fields': (
......@@ -52,6 +58,10 @@ class NewsletterAdmin(TranslatedModelAdmin):
form.instance.save()
def change_view(self, request, object_id, form_url=''):
"""
Renders the change view
Disallow change access if a newsletter is marked as sent
"""
obj = Newsletter.objects.filter(id=object_id)[0]
if obj is not None and obj.sent is True:
return redirect(obj)
......@@ -59,12 +69,17 @@ class NewsletterAdmin(TranslatedModelAdmin):
request, object_id, form_url, {'newsletter': obj})
def has_delete_permission(self, request, obj=None):
"""
Check if delete permission is granted
Disallow deletion if a newsletter is marked as sent
"""
if obj is not None and obj.sent is True:
return False
return (super(NewsletterAdmin, self)
.has_delete_permission(request, obj=obj))
def get_actions(self, request):
"""Remove the deletion action from the admin"""
actions = super(NewsletterAdmin, self).get_actions(request)
del actions['delete_selected']
return actions
"""Configuration for the newsletters package"""
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class NewslettersConfig(AppConfig):
"""AppConfig for the newsletters package"""
name = 'newsletters'
verbose_name = _('News letters')
"""The emails defined by the newsletters package"""
from django.core.mail import EmailMultiAlternatives
from django.template.loader import get_template
from django.utils import translation
from members.models import Member
from partners.models import Partner
from thaliawebsite.settings import settings
def send_newsletter(request, newsletter):
"""
Sends the newsletter as HTML and plaintext email
:param request: the request object
:param newsletter: the newsletter to be send
"""
partners = Partner.objects.filter(is_main_partner=True)
main_partner = partners[0] if len(partners) > 0 else None
from_email = settings.NEWSLETTER_FROM_ADDRESS
html_template = get_template('newsletters/email.html')
text_template = get_template('newsletters/email.txt')
for language in settings.LANGUAGES:
translation.activate(language[0])
recipients = [member.email for member in
Member.current_members.all().filter(
profile__receive_newsletter=True,
profile__language=language[0])
if member.email]
subject = '[THALIA] ' + newsletter.title
context = {
'newsletter': newsletter,
'agenda_events': (
newsletter.newslettercontent_set
.filter(newsletteritem=None)
.order_by('newsletterevent__start_datetime')
),
'main_partner': main_partner,
'lang_code': language[0],
'request': request
}
html_message = html_template.render(context)
text_message = text_template.render(context)
msg = EmailMultiAlternatives(subject, text_message,
to=[from_email],
bcc=recipients,
from_email=from_email)
msg.attach_alternative(html_message, "text/html")
msg.send()
translation.deactivate()
"""The forms defined by the newsletters package"""
from django import forms
from django.utils.translation import ugettext_lazy as _
......@@ -5,6 +6,7 @@ from .models import NewsletterItem, NewsletterEvent, Newsletter
class NewsletterItemForm(forms.ModelForm):
"""Custom ModelForm for the NewsletterItem model to add the order field"""
order = forms.IntegerField(label=_('order'), initial=0)
def __init__(self, *args, **kwargs):
......@@ -23,6 +25,7 @@ class NewsletterItemForm(forms.ModelForm):
class NewsletterEventForm(NewsletterItemForm):
"""Custom ModelForm for the NewsletterEvent model to add the order field"""
class Meta:
fields = '__all__'
model = NewsletterEvent
"""The models defined by the newsletters package"""
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
......@@ -8,6 +9,8 @@ from utils.translation import ModelTranslateMeta, MultilingualField
class Newsletter(models.Model, metaclass=ModelTranslateMeta):
"""Describes a newsletter"""
title = MultilingualField(
models.CharField,
max_length=150,
......@@ -49,6 +52,8 @@ class Newsletter(models.Model, metaclass=ModelTranslateMeta):
class NewsletterContent(models.Model, metaclass=ModelTranslateMeta):
"""Describes one piece of basic content of a newsletter"""
title = MultilingualField(
models.CharField,
max_length=150,
......@@ -71,10 +76,12 @@ class NewsletterContent(models.Model, metaclass=ModelTranslateMeta):
class NewsletterItem(NewsletterContent):
"""Describes one piece of text content of a newsletter"""
pass
class NewsletterEvent(NewsletterContent):
"""Describes one piece of event content of a newsletter"""
what = MultilingualField(
models.CharField,
max_length=150,
......@@ -129,6 +136,7 @@ class NewsletterEvent(NewsletterContent):
)
def clean(self):
"""Make sure that the event end date is after the start date"""
super().clean()
if (self.end_datetime is not None and
self.start_datetime is not None and
......
"""Defines tests for the newsletters package"""
import doctest
from django.conf import settings
......
"""The routes defined by the newsletters package"""
from django.conf.urls import url
from . import views
......
"""Views provided by the newsletters package"""
from datetime import datetime, timedelta, date
from django.conf import settings
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.decorators import permission_required
from django.core.mail import EmailMultiAlternatives
from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import get_template
from django.utils import translation
from django.utils.translation import activate, get_language_info
from members.models import Member
from newsletters import emails
from newsletters.models import Newsletter
from partners.models import Partner
def preview(request, pk, lang=None):
"""
View that renders the newsletter as HTML
:param request: the request object
:param pk: the newsletter's primary key
:param lang: the language of the render
:return: HttpResponse 200 containing the newsletter HTML
"""
newsletter = get_object_or_404(Newsletter, pk=pk)
partners = Partner.objects.filter(is_main_partner=True)
main_partner = partners[0] if len(partners) > 0 else None
......@@ -39,6 +43,14 @@ def preview(request, pk, lang=None):
def legacy_redirect(request, year, week):
"""
View that redirect you to the right newsletter by
using the previously used URL format of /{year}/{week}
:param request: the request object
:param year: the year of the newsletter
:param week: the week of the newsletter
:return: 302 RedirectResponse
"""
newsletter_date = datetime.strptime(
'%s-%s-1' % (year, week), '%Y-%W-%w')
if date(int(year), 1, 4).isoweekday() > 4:
......@@ -52,54 +64,22 @@ def legacy_redirect(request, year, week):
@staff_member_required
@permission_required('newsletters.send_newsletter')
def admin_send(request, pk):
"""
If this is a GET request this view will render a confirmation
page for the administrator. If it is a POST request the newsletter
will be sent to all recipients
:param request: the request object
:param pk: the newsletter's primary key
:return: 302 RedirectResponse if POST else 200 with the
confirmation page HTML
"""
newsletter = get_object_or_404(Newsletter, pk=pk)
if newsletter.sent:
return redirect(newsletter)
if request.POST:
partners = Partner.objects.filter(is_main_partner=True)
main_partner = partners[0] if len(partners) > 0 else None
from_email = settings.NEWSLETTER_FROM_ADDRESS
html_template = get_template('newsletters/email.html')
text_template = get_template('newsletters/email.txt')
for language in settings.LANGUAGES:
translation.activate(language[0])
recipients = [member.email for member in
Member.current_members.all().filter(
profile__receive_newsletter=True,
profile__language=language[0])
if member.email]
subject = '[THALIA] ' + newsletter.title
context = {
'newsletter': newsletter,
'agenda_events': (
newsletter.newslettercontent_set
.filter(newsletteritem=None)
.order_by('newsletterevent__start_datetime')
),
'main_partner': main_partner,
'lang_code': language[0],
'request': request
}
html_message = html_template.render(context)
text_message = text_template.render(context)
msg = EmailMultiAlternatives(subject, text_message,
to=[from_email],
bcc=recipients,
from_email=from_email)
msg.attach_alternative(html_message, "text/html")
msg.send()
translation.deactivate()
emails.send_newsletter(request, newsletter)
newsletter.sent = True
newsletter.save()
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment