Commit 2cf9e993 authored by Sébastiaan Versteeg's avatar Sébastiaan Versteeg
Browse files

Merge branch 'feature/24-newsletter' into 'master'

Newsletter integration

Closes #24. Also fixes #66

See merge request !60
parents 4e5ec886 a1d4f185
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-09-04 20:32
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0006_auto_20160824_2041'),
]
operations = [
migrations.AddField(
model_name='member',
name='receive_newsletter',
field=models.BooleanField(default=True, help_text='Receive the Thalia Newsletter', verbose_name='Receive newsletter'),
),
]
......@@ -260,6 +260,12 @@ class Member(models.Model):
default=True,
)
receive_newsletter = models.BooleanField(
verbose_name=_('Receive newsletter'),
help_text=_("Receive the Thalia Newsletter"),
default=True,
)
# --- Direct debit information ----
direct_debit_authorized = models.BooleanField(
......
from django.contrib import admin
from django.shortcuts import redirect
from utils.translation import TranslatedModelAdmin
from newsletters.models import (
Newsletter,
NewsletterEvent,
NewsletterItem
)
class NewsletterItemInline(admin.StackedInline):
model = NewsletterItem
class NewsletterEventInline(admin.StackedInline):
model = NewsletterEvent
@admin.register(Newsletter)
class NewsletterAdmin(TranslatedModelAdmin):
list_display = ('title', 'date', 'sent',)
inlines = (NewsletterItemInline, NewsletterEventInline,)
fieldsets = (
(None, {
'fields': (
'title', 'date', 'description'
)
}),
)
def change_view(self, request, object_id, form_url=''):
obj = Newsletter.objects.filter(id=object_id)[0]
if obj is not None and obj.sent is True:
return redirect(obj)
return super(NewsletterAdmin, self).change_view(
request, object_id, form_url, {'newsletter': obj})
def has_delete_permission(self, request, obj=None):
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):
actions = super(NewsletterAdmin, self).get_actions(request)
del actions['delete_selected']
return actions
from django.apps import AppConfig
class NewslettersConfig(AppConfig):
name = 'newsletters'
# Thalia Website Newsletter translations
# Copyright (C) 2016
# This file is distributed under the same license as the thaliawebsite package.
msgid ""
msgstr ""
"Project-Id-Version: Thaliawebsite: nieuwsbrieven\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-09-05 14:27+0200\n"
"PO-Revision-Date: 2016-09-05 14:30+0100\n"
"Last-Translator: Thom Wiggers <thom@thomwiggers.nl>\n"
"Language-Team: Technicie <www@thalia.nu>\n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: newsletters/models.py:13 newsletters/models.py:49
msgid "Title"
msgstr "Titel"
#: newsletters/models.py:14
msgid "The title is used for the email subject."
msgstr "De titel wordt gebruikt als onderwerp van de e-mail."
#: newsletters/models.py:19
msgid "Date"
msgstr "Datum"
#: newsletters/models.py:20
msgid ""
"This date is used to extract the week of this newsletter, best scenario:"
"always use the monday of the week the newsletter is for. If you leave it "
"empty no week is shown."
msgstr ""
"Deze datum wordt gebruikt om de week van de nieuwsbrief te bepalen, het "
"makkelijkste is dus om gewoon de maandag van de week in te vullen waar deze "
"nieuwsbrief voor is."
#: newsletters/models.py:30
msgid "Introduction"
msgstr "Introductie"
#: newsletters/models.py:31
msgid ""
"This is the text that starts the newsletter. It always begins with \"Dear "
"members\" and you can append whatever you want."
msgstr ""
"Dit is de tekst waarmee de nieuwsbrief start. Er wordt altijd begonnen met "
"\"Beste Thalianen\" en je kunt daarna alles schrijven wat je wilt."
#: newsletters/models.py:56
msgid "Description"
msgstr "Beschrijving"
#: newsletters/models.py:75 newsletters/templates/newsletters/email.html:127
#: newsletters/templates/newsletters/email.txt:20
msgid "What"
msgstr "Wat"
#: newsletters/models.py:83 newsletters/templates/newsletters/email.html:137
#: newsletters/templates/newsletters/email.txt:21
msgid "Where"
msgstr "Waar"
#: newsletters/models.py:89
msgid "Start date and time"
msgstr "Startdatum en -tijd"
#: newsletters/models.py:95
msgid "End date and time"
msgstr "Einddatum en -tijd"
#: newsletters/models.py:101
msgid "Price (in Euro)"
msgstr "Prijs (in Euro)"
#: newsletters/models.py:108
msgid "Costs (in Euro)"
msgstr "Kosten (in Euro)"
#: newsletters/models.py:112
msgid "This is the price that a member has to pay when he/she did not show up."
msgstr ""
"Dit is de prijs die een lid moet betalen als hij/zij niet aanwezig was."
#: newsletters/models.py:120
msgid "Can't have an event travel back in time"
msgstr "Een evenement kan niet terug in de tijd reizen"
#: newsletters/templates/admin/newsletters/change_form.html:14
msgid "Send newsletter to members"
msgstr "Verstuur nieuwsbrief naar leden"
#: newsletters/templates/admin/newsletters/change_form.html:15
msgid "Show preview"
msgstr "Toon voorbeeld"
#: newsletters/templates/newsletters/admin/send_confirm.html:4
msgid "Thalia site admin"
msgstr "Thalia site admin"
#: newsletters/templates/newsletters/admin/send_confirm.html:7
msgid "Thalia administration"
msgstr "Thalia administratie"
#: newsletters/templates/newsletters/admin/send_confirm.html:12
msgid "home"
msgstr "home"
#: newsletters/templates/newsletters/admin/send_confirm.html:13
msgid "newsletters"
msgstr "nieuwsbrieven"
#: newsletters/templates/newsletters/admin/send_confirm.html:15
msgid "Send newsletter"
msgstr "Verstuur nieuwsbrief"
#: newsletters/templates/newsletters/admin/send_confirm.html:19
#, python-format
msgid "Send newsletter: %(newsletter)s"
msgstr "Verstuur nieuwsbrief: %(newsletter)s"
#: newsletters/templates/newsletters/admin/send_confirm.html:22
#, python-format
msgid "Are you sure you want to send the newsletter '%(newsletter)s'?"
msgstr "Weet je zeker dat je de nieuwsbrief '%(newsletter)s' wilt verzenden?"
#: newsletters/templates/newsletters/admin/send_confirm.html:30
msgid "No, take me back"
msgstr "Nee, ga terug"
#: newsletters/templates/newsletters/email.html:26
#: newsletters/templates/newsletters/email.txt:32
msgid "view this email in your browser"
msgstr "bekijk deze e-mail in je browser"
#: newsletters/templates/newsletters/email.html:55
#: newsletters/templates/newsletters/email.txt:3
msgid "dear members"
msgstr "beste thalianen"
#: newsletters/templates/newsletters/email.html:66
#: newsletters/templates/newsletters/email.txt:8
msgid "agenda"
msgstr "agenda"
#: newsletters/templates/newsletters/email.html:113
#: newsletters/templates/newsletters/email.txt:18
msgid "Attention"
msgstr "Let op"
#: newsletters/templates/newsletters/email.html:113
#: newsletters/templates/newsletters/email.txt:18
msgid "Registration deadline = unregistration deadline"
msgstr "Aanmelddeadline = Afmelddeadline"
#: newsletters/templates/newsletters/email.html:114
#: newsletters/templates/newsletters/email.txt:18
msgid "Thalia will recover the costs on you if you do not unregister on time"
msgstr ""
"Niet of niet op tijd afmelden betekent de door Thalia per persoon gemaakte "
"kosten betalen"
#: newsletters/templates/newsletters/email.html:116
#: newsletters/templates/newsletters/email.txt:18
msgid "These costs are"
msgstr "Deze bedragen"
#: newsletters/templates/newsletters/email.html:147
#: newsletters/templates/newsletters/email.txt:22
msgid "When"
msgstr "Wanneer"
#: newsletters/templates/newsletters/email.html:163
#: newsletters/templates/newsletters/email.txt:23
msgid "Price"
msgstr "Prijs"
#: newsletters/templates/newsletters/email.html:168
#: newsletters/templates/newsletters/email.txt:23
msgid "Free"
msgstr "Gratis"
#: newsletters/templates/newsletters/email.html:206
#: newsletters/templates/newsletters/email.txt:30
msgid "our main partner"
msgstr "onze hoofdpartner"
#: newsletters/templates/newsletters/email.txt:1
msgid "newsletter"
msgstr "nieuwsbrief"
#: newsletters/templates/newsletters/email.txt:26
msgid "room"
msgstr "kamer"
#: newsletters/templates/newsletters/email.txt:27
msgid "website"
msgstr "website"
#: newsletters/templates/newsletters/email.txt:28
msgid "email"
msgstr "e-mailadres"
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-09-04 20:27
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import tinymce.models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Newsletter',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(blank=True, help_text='This date is used to extract the week of this newsletter, best scenario:always use the monday of the week the newsletter is for. If you leave it empty no week is shown.', null=True, verbose_name='Date')),
('sent', models.BooleanField(default=False)),
('title_en', models.CharField(help_text='The title is used for the email subject.', max_length=150, verbose_name='Title (EN)')),
('title_nl', models.CharField(help_text='The title is used for the email subject.', max_length=150, verbose_name='Title (NL)')),
('description_en', tinymce.models.HTMLField(help_text='This is the text that starts the newsletter. It always begins with "Dear members" and you can append whatever you want.', verbose_name='Introduction (EN)')),
('description_nl', tinymce.models.HTMLField(help_text='This is the text that starts the newsletter. It always begins with "Dear members" and you can append whatever you want.', verbose_name='Introduction (NL)')),
],
),
migrations.CreateModel(
name='NewsletterEvent',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title_en', models.CharField(max_length=150, verbose_name='Title (EN)')),
('title_nl', models.CharField(max_length=150, verbose_name='Title (NL)')),
('description_en', tinymce.models.HTMLField(verbose_name='Description (EN)')),
('description_nl', tinymce.models.HTMLField(verbose_name='Description (NL)')),
('start_datetime', models.DateTimeField(verbose_name='Start date and time')),
('end_datetime', models.DateTimeField(verbose_name='End date and time')),
('price', models.DecimalField(decimal_places=2, default=0, max_digits=5, verbose_name='Price (in Euro)')),
('penalty_costs', models.DecimalField(decimal_places=2, default=0, help_text='This is the price that a member has to pay when he/she did not show up.', max_digits=5, verbose_name='Costs (in Euro)')),
('where_en', models.CharField(max_length=150, verbose_name='Where (EN)')),
('where_nl', models.CharField(max_length=150, verbose_name='Where (NL)')),
('what_en', models.CharField(max_length=150, verbose_name='What (EN)')),
('what_nl', models.CharField(max_length=150, verbose_name='What (NL)')),
('newsletter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='newsletters.Newsletter')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='NewsletterItem',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title_en', models.CharField(max_length=150, verbose_name='Title (EN)')),
('title_nl', models.CharField(max_length=150, verbose_name='Title (NL)')),
('description_en', tinymce.models.HTMLField(verbose_name='Description (EN)')),
('description_nl', tinymce.models.HTMLField(verbose_name='Description (NL)')),
('newsletter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='newsletters.Newsletter')),
],
options={
'abstract': False,
},
),
]
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from tinymce.models import HTMLField
from utils.translation import MultilingualField, ModelTranslateMeta
class Newsletter(models.Model, metaclass=ModelTranslateMeta):
title = MultilingualField(
models.CharField,
max_length=150,
verbose_name=_('Title'),
help_text=_('The title is used for the email subject.'),
blank=False,
)
date = models.DateField(
verbose_name=_('Date'),
help_text=_('This date is used to extract the week of this '
'newsletter, best scenario:'
'always use the monday of the week the newsletter is '
'for. If you leave it empty no week is shown.'),
blank=True,
null=True
)
description = MultilingualField(
HTMLField,
verbose_name=_('Introduction'),
help_text=_('This is the text that starts the newsletter. It always '
'begins with "Dear members" and you can append '
'whatever you want.'),
blank=False,
)
sent = models.BooleanField(
default=False
)
def get_absolute_url(self):
return reverse('newsletters:preview', args=(self.pk,))
class NewsletterContent(models.Model, metaclass=ModelTranslateMeta):
title = MultilingualField(
models.CharField,
max_length=150,
verbose_name=_('Title'),
blank=False,
null=False,
)
description = MultilingualField(
HTMLField,
verbose_name=_('Description'),
blank=False,
null=False,
)
newsletter = models.ForeignKey(Newsletter, on_delete=models.CASCADE)
class Meta:
abstract = True
class NewsletterItem(NewsletterContent):
pass
class NewsletterEvent(NewsletterContent):
what = MultilingualField(
models.CharField,
max_length=150,
verbose_name=_('What'),
blank=False,
null=False,
)
where = MultilingualField(
models.CharField,
max_length=150,
verbose_name=_('Where'),
blank=False,
null=False,
)
start_datetime = models.DateTimeField(
verbose_name=_('Start date and time'),
blank=False,
null=False,
)
end_datetime = models.DateTimeField(
verbose_name=_('End date and time'),
blank=False,
null=False,
)
price = models.DecimalField(
verbose_name=_('Price (in Euro)'),
max_digits=5,
decimal_places=2,
default=0,
)
penalty_costs = models.DecimalField(
verbose_name=_('Costs (in Euro)'),
max_digits=5,
decimal_places=2,
default=0,
help_text=_('This is the price that a member has to '
'pay when he/she did not show up.'),
)
def clean(self):
super().clean()
if self.end_datetime < self.start_datetime:
raise ValidationError({
'end': _("Can't have an event travel back in time")
})
.submit-row a.default {
text-transform: uppercase;
}
.submit-row a.button {
float: right;
padding: 10px 15px;
line-height: 15px;
margin: 0 0 0 8px;
}
.newsletters-row a.button.default {
background-color: #e62272;
}
.newsletters-row a.button.default:hover, .newsletters-row a.button.default:active {
background-color: #cd2167;
}
.newsletters-row a.button {
background-color: #e65c95;
}
.newsletters-row a.button:hover, .newsletters-row a.button:active {
background-color: #d25389;
}
\ No newline at end of file
{% extends "admin/change_form.html" %}
{% load i18n admin_urls static compress %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'admin/newsletters/css/forms.css' %}" />
{% endblock %}
{% block submit_buttons_bottom %}
{{ block.super }}
{% if newsletter %}
<div class="submit-row newsletters-row">
<a href="{% url 'newsletters:admin-send' pk=newsletter.pk %}" class="default button">{% trans "Send newsletter to members" %}</a>
<a target="_blank" href="{{ newsletter.get_absolute_url }}" class="button">{% trans "Show preview" %}</a>
</div>
{% endif %}
{% endblock %}
\ No newline at end of file
{% extends "admin/delete_selected_confirmation.html" %}
{% load i18n admin_urls static admin_modify %}
{% block title %}{{ newsletter.title }} | {{ site_title|default:_('Thalia site admin') }}{% endblock %}
{% block branding %}
<h1 id="site-name"><a href="{% url 'admin:index' %}">{{ site_header|default:_('Thalia administration') }}</a></h1>
{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'home'|capfirst %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label='newsletters' %}">{% trans 'newsletters'|capfirst %}</a>
&rsaquo; <a href="{% url 'admin:newsletters_newsletter_change' newsletter.pk %}">{{ newsletter.title }}</a>
&rsaquo; {% trans 'Send newsletter' %}
</div>
{% endblock %}
{% block content_title %}<h1>{% blocktrans with newsletter=newsletter.title %}Send newsletter: {{ newsletter }}{% endblocktrans %}</h1>{% endblock %}
{% block content %}
<p>{% blocktrans with newsletter=newsletter.title %}Are you sure you want to send the newsletter '{{ newsletter }}'?{% endblocktrans %}</p>
<form method="post">
{% csrf_token %}
<div>
<input type="hidden" name="post" value="yes">
<input type="submit" value="Yes, I'm sure">
<a href="{% url 'admin:newsletters_newsletter_change' newsletter.pk %}" class="button cancel-link">{% trans "No, take me back" %}</a>
</div>
</form>
<br class="clear">
{% endblock %}
\ No newline at end of file
{% load i18n %}
{% load listutil %}
{% load baseurl %}
{% load static from staticfiles %}
{% with primary_color="#EE227A" item_h2_style="color:#EE227A;font-family:Calibri, 'Trebuchet MS', sans-serif; font-size: 18px; margin-bottom: 10px; margin-top: 30px;" item_tr_style="padding-left: 30px; padding-right: 30px; padding-bottom: 10px; margin: 0px; vertical-align: top;" %}
<html style="background-color: #F0F0F0;">
<head>
<title>{{ newsletter.title }}</title>
<style type="text/css">
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
</style>
</head>
<body>
<div style="background-color: #F0F0F0; padding: 20px;">