Skip to content
Snippets Groups Projects
Commit 8ee25ffb authored by Tom Teichler's avatar Tom Teichler :beers:
Browse files

Add frontend for info mailings

parent a580d981
No related branches found
No related tags found
1 merge request!9Resolve "Info mailing"
Pipeline #57577 failed
Showing with 267 additions and 12 deletions
......@@ -11,7 +11,7 @@ from phonenumber_field.formfields import PhoneNumberField
from aleksis.core.mixins import ExtensibleForm
from aleksis.core.models import Group, Person
from .models import Event, EventRegistration, Terms, Voucher
from .models import Event, EventRegistration, InfoMailing, Terms, Voucher
COMMENT_CHOICES = [
("first", _("Only first name")),
......@@ -49,6 +49,7 @@ class EditEventForm(ExtensibleForm):
Fieldset(_("Date data"), Row("date_event", "date_registration", "date_retraction")),
Fieldset(_("Event details"), Row("cost", "max_participants"), "information"),
Fieldset(_("Terms"), "terms"),
Fieldset(_("Info mailings"), "info_mailings"),
),
)
......@@ -68,6 +69,7 @@ class EditEventForm(ExtensibleForm):
"max_participants",
"terms",
"information",
"info_mailings",
]
widgets = {
"linked_group": ModelSelect2Widget(
......@@ -78,6 +80,10 @@ class EditEventForm(ExtensibleForm):
search_fields=["aspect__icontains"],
attrs={"data-minimum-input-length": 0, "class": "browser-default"},
),
"info_mailings": ModelSelect2MultipleWidget(
search_fields=["subject__icontains"],
attrs={"data-minimum-input-length": 0, "class": "browser-default"},
),
}
......@@ -456,3 +462,17 @@ class RegisterEventAccount(SignupForm, ExtensibleForm):
"The username must only contain lower case letters and numbers, "
"and must begin with a letter."
)
class EditInfoMailingForm(forms.ModelForm):
layout = Layout(
Row("sender", "reply_to", "active"),
Row("send_to_person", "send_to_guardians"),
Row("subject"),
Row("text"),
)
class Meta:
model = InfoMailing
exclude = ["sent_to"]
......@@ -44,6 +44,17 @@ MENUS = {
)
],
},
{
"name": _("Info mailings"),
"url": "info_mailings",
"icon": "info",
"validators": [
(
"aleksis.core.util.predicates.permission_validator",
"paweljong.view_info_mailings_rule",
)
],
},
{
"name": _("Generate participant list"),
"url": "generate_lists",
......
# Generated by Django 3.2.12 on 2022-03-01 15:19
import ckeditor.fields
import django.contrib.sites.managers
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('core', '0037_alter_personinvitation_id'),
('sites', '0002_alter_domain_unique'),
('paweljong', '0012_event_slug'),
]
operations = [
migrations.AlterField(
model_name='eventregistration',
name='event',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='paweljong.event', verbose_name='Event'),
),
migrations.CreateModel(
name='InfoMailing',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('extended_data', models.JSONField(default=dict, editable=False)),
('subject', models.CharField(max_length=255, verbose_name='subject')),
('text', ckeditor.fields.RichTextField(verbose_name='Text')),
('reply_to', models.EmailField(blank=True, max_length=254, verbose_name='Request replies to')),
('active', models.BooleanField(default=False, verbose_name='Mailing is active')),
('sender', models.EmailField(blank=True, max_length=254, verbose_name='Sender')),
('send_to_person', models.BooleanField(default=True, verbose_name='Send to registered person')),
('send_to_guardians', models.BooleanField(default=False, verbose_name='Send to guardians')),
('sent_to', models.ManyToManyField(blank=True, editable=False, related_name='received_info_mailings', to='core.Person', verbose_name='Sent to persons')),
('site', models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.site')),
],
options={
'abstract': False,
},
managers=[
('objects', django.contrib.sites.managers.CurrentSiteManager()),
],
),
migrations.AddField(
model_name='event',
name='info_mailings',
field=models.ManyToManyField(blank=True, related_name='events', to='paweljong.InfoMailing', verbose_name='Info mailings'),
),
]
......@@ -8,11 +8,11 @@ from django.utils.translation import gettext_lazy as _
from ckeditor.fields import RichTextField
from django_iban.fields import IBANField
from templated_email import send_templated_mail
from aleksis.core.mixins import ExtensibleModel
from aleksis.core.models import Group, Person
from aleksis.core.util.core_helpers import generate_random_code, get_site_preferences
from aleksis.core.util.email import send_email
class Terms(ExtensibleModel):
......@@ -31,10 +31,17 @@ class InfoMailing(ExtensibleModel):
active = models.BooleanField(verbose_name=_("Mailing is active"), default=False)
sender = models.EmailField(verbose_name=_("Sender"), blank=True)
send_to_person = models.BooleanField(verbose_name=_("Send to registered person"), default=True)
send_to_guardians = models.BooleanField(verbose_name=_("Send to guardians"), default=False)
sent_to = models.ManyToManyField(Person, verbose_name=_("Sent to persons"), related_name="received_info_mailings", editable=False, blank=True)
sent_to = models.ManyToManyField(
Person,
verbose_name=_("Sent to persons"),
related_name="received_info_mailings",
editable=False,
blank=True,
)
def __str__(self) -> str:
return self.subject
......@@ -47,12 +54,16 @@ class InfoMailing(ExtensibleModel):
sent_to = self.sent_to.all()
for event in self.events.all():
for registration in event.registrations:
for registration in event.registrations.all():
if registration.person in sent_to:
continue
subject = self.subject.format(event=event, registration=registration, person=registration.person)
body = self.text.format(event=event, registration=registration, person=registration.person)
subject = self.subject.format(
event=event, registration=registration, person=registration.person
)
body = self.text.format(
event=event, registration=registration, person=registration.person
)
if self.send_to_person:
to = [registration.person.email]
......@@ -68,9 +79,18 @@ class InfoMailing(ExtensibleModel):
reply_to = self.reply_to or sender
context = {"subject": subject, "body": body}
send_templated_email(template_name="info_mailing", context=context, from_email=sender, recipient_list=to, cc=cc)
send_email(
template_name="info_mailing",
context=context,
from_email=sender,
recipient_list=to,
cc=cc,
headers={
"Reply-To": reply_to,
},
)
self.sent_to.add(self.registration.person)
self.sent_to.add(registration.person)
class Event(ExtensibleModel):
......@@ -94,7 +114,9 @@ class Event(ExtensibleModel):
max_participants = models.PositiveSmallIntegerField(verbose_name=_("Maximum participants"))
information = RichTextField(verbose_name=_("Information about the event"))
terms = models.ManyToManyField(Terms, verbose_name=_("Terms"), related_name="event", blank=True)
info_mailings = models.ManyToManyField(InfoMailing, verbose_name=_("Info mailings"), related_name="events", blank=True)
info_mailings = models.ManyToManyField(
InfoMailing, verbose_name=_("Info mailings"), related_name="events", blank=True
)
def save(self, *args, **kwargs):
if not self.slug:
......@@ -193,7 +215,9 @@ class Voucher(ExtensibleModel):
class EventRegistration(ExtensibleModel):
event = models.ForeignKey(Event, on_delete=models.CASCADE, verbose_name=_("Event"), related_name="registrations")
event = models.ForeignKey(
Event, on_delete=models.CASCADE, verbose_name=_("Event"), related_name="registrations"
)
person = models.ForeignKey(Person, on_delete=models.CASCADE, verbose_name=_("Person"))
date_registered = models.DateTimeField(auto_now_add=True, verbose_name=_("Registration date"))
......
......@@ -109,3 +109,11 @@ view_terms_predicate = has_person & (
has_global_perm("paweljong.view_term") | has_any_object("paweljong.view_term", Terms)
)
rules.add_perm("paweljong.view_terms_rule", view_terms_predicate)
# View info_mailings
view_info_mailings_predicate = has_person & (
has_global_perm("paweljong.view_info_mailing")
| has_any_object("paweljong.view_info_mailing", Terms)
)
rules.add_perm("paweljong.view_info_mailings_rule", view_info_mailings_predicate)
......@@ -76,3 +76,23 @@ class TermsTable(tables.Table):
verbose_name=_("Edit"),
text=_("Edit"),
)
class InfoMailingsTable(tables.Table):
class Meta:
attrs = {"class": "responsive-table highlight"}
subject = tables.Column()
edit = tables.LinkColumn(
"edit_info_mailing_by_pk",
args=[A("id")],
verbose_name=_("Edit"),
text=_("Edit"),
)
delete = tables.LinkColumn(
"delete_info_mailing_by_pk",
args=[A("id")],
verbose_name=_("Delete"),
text=_("Delete"),
)
......@@ -6,5 +6,6 @@ from aleksis.core.celery import app
@app.task(run_every=timedelta(hours=1))
def send_info_mailings() -> None:
from .models import InfoMailing # noqa
for mailing in InfoMailing.get_active_mailings():
mailing.send()
{% extends "core/base.html" %}
{% load material_form i18n %}
{% block page_title %}{% blocktrans %}Create info mailing{% endblocktrans %}{% endblock %}
{% block browser_title %}{% blocktrans %}Create info mailing{% endblocktrans %}{% endblock %}
{% block extra_head %}
{{ form.media.css }}
{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
{% form form=form %}{% endform %}
{% include "core/partials/save_button.html" %}
</form>
{{ form.media.js }}
{% endblock %}
{% extends "core/base.html" %}
{% load material_form i18n %}
{% block page_title %}{% blocktrans %}Edit info mailing{% endblocktrans %}{% endblock %}
{% block browser_title %}{% blocktrans %}Edit info mailing{% endblocktrans %}{% endblock %}
{% block extra_head %}
{{ form.media.css }}
{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
{% form form=form %}{% endform %}
{% include "core/partials/save_button.html" %}
</form>
{{ form.media.js }}
{% endblock %}
{% extends "core/base.html" %}
{% load material_form i18n %}
{% load render_table from django_tables2 %}
{% block page_title %}{% blocktrans %}Info mailings{% endblocktrans %}{% endblock %}
{% block browser_title %}{% blocktrans %}Info mailings{% endblocktrans %}{% endblock %}
{% block content %}
<a class="btn colour-primary waves-effect waves-light" href="{% url 'create_info_mailing' %}">{% trans "Create info mailing" %}</a>
{% render_table table %}
{% endblock %}
......@@ -102,4 +102,24 @@ urlpatterns = [
views.TermEditView.as_view(),
name="edit_term_by_pk",
),
path(
"event/info_mailings/list",
views.InfoMailingListView.as_view(),
name="info_mailings",
),
path(
"event/info_mailings/create",
views.InfoMailingCreateView.as_view(),
name="create_info_mailing",
),
path(
"event/info_mailings/<int:pk>/edit",
views.InfoMailingEditView.as_view(),
name="edit_info_mailing_by_pk",
),
path(
"event/info_mailings/<int:pk>/delete",
views.InfoMailingDeleteView.as_view(),
name="delete_info_mailing_by_pk",
),
]
......@@ -32,12 +32,19 @@ from .filters import EventFilter, EventRegistrationFilter, VoucherFilter
from .forms import (
EditEventForm,
EditEventRegistrationForm,
EditInfoMailingForm,
EditTermForm,
EditVoucherForm,
GenerateListForm,
)
from .models import Event, EventRegistration, Terms, Voucher
from .tables import EventRegistrationsTable, ManageEventsTable, TermsTable, VouchersTable
from .models import Event, EventRegistration, InfoMailing, Terms, Voucher
from .tables import (
EventRegistrationsTable,
InfoMailingsTable,
ManageEventsTable,
TermsTable,
VouchersTable,
)
User = get_user_model()
......@@ -769,3 +776,46 @@ class UpcomingEventsRSSFeed(Feed):
class AccountRegisterStart(TemplateView):
template_name = "paweljong/register_start.html"
class InfoMailingListView(PermissionRequiredMixin, SingleTableView):
"""Table of all info mailings."""
model = InfoMailing
table_class = InfoMailingsTable
permission_required = "paweljong.view_info_mailing"
template_name = "paweljong/info_mailing/list.html"
@method_decorator(never_cache, name="dispatch")
class InfoMailingCreateView(PermissionRequiredMixin, AdvancedCreateView):
"""Create view for info mailings."""
model = InfoMailing
form_class = EditInfoMailingForm
permission_required = "paweljong.add_info_mailing"
template_name = "paweljong/info_mailing/create.html"
success_url = reverse_lazy("info_mailings")
success_message = _("The info mailing has been created.")
@method_decorator(never_cache, name="dispatch")
class InfoMailingEditView(PermissionRequiredMixin, AdvancedEditView):
"""Edit view for info mailings."""
model = InfoMailing
form_class = EditInfoMailingForm
permission_required = "paweljong.edit_info_mailing"
template_name = "paweljong/info_mailing/edit.html"
success_url = reverse_lazy("info_mailings")
success_message = _("The info mailing has been saved.")
class InfoMailingDeleteView(PermissionRequiredMixin, AdvancedDeleteView):
"""Delete view for info mailings."""
model = InfoMailing
permission_required = "paweljong.delete_info_mailing"
template_name = "core/pages/delete.html"
success_url = reverse_lazy("info_mailings")
success_message = _("The info mailing has been deleted.")
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment