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 ...@@ -11,7 +11,7 @@ from phonenumber_field.formfields import PhoneNumberField
from aleksis.core.mixins import ExtensibleForm from aleksis.core.mixins import ExtensibleForm
from aleksis.core.models import Group, Person from aleksis.core.models import Group, Person
from .models import Event, EventRegistration, Terms, Voucher from .models import Event, EventRegistration, InfoMailing, Terms, Voucher
COMMENT_CHOICES = [ COMMENT_CHOICES = [
("first", _("Only first name")), ("first", _("Only first name")),
...@@ -49,6 +49,7 @@ class EditEventForm(ExtensibleForm): ...@@ -49,6 +49,7 @@ class EditEventForm(ExtensibleForm):
Fieldset(_("Date data"), Row("date_event", "date_registration", "date_retraction")), Fieldset(_("Date data"), Row("date_event", "date_registration", "date_retraction")),
Fieldset(_("Event details"), Row("cost", "max_participants"), "information"), Fieldset(_("Event details"), Row("cost", "max_participants"), "information"),
Fieldset(_("Terms"), "terms"), Fieldset(_("Terms"), "terms"),
Fieldset(_("Info mailings"), "info_mailings"),
), ),
) )
...@@ -68,6 +69,7 @@ class EditEventForm(ExtensibleForm): ...@@ -68,6 +69,7 @@ class EditEventForm(ExtensibleForm):
"max_participants", "max_participants",
"terms", "terms",
"information", "information",
"info_mailings",
] ]
widgets = { widgets = {
"linked_group": ModelSelect2Widget( "linked_group": ModelSelect2Widget(
...@@ -78,6 +80,10 @@ class EditEventForm(ExtensibleForm): ...@@ -78,6 +80,10 @@ class EditEventForm(ExtensibleForm):
search_fields=["aspect__icontains"], search_fields=["aspect__icontains"],
attrs={"data-minimum-input-length": 0, "class": "browser-default"}, 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): ...@@ -456,3 +462,17 @@ class RegisterEventAccount(SignupForm, ExtensibleForm):
"The username must only contain lower case letters and numbers, " "The username must only contain lower case letters and numbers, "
"and must begin with a letter." "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 = { ...@@ -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"), "name": _("Generate participant list"),
"url": "generate_lists", "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 _ ...@@ -8,11 +8,11 @@ from django.utils.translation import gettext_lazy as _
from ckeditor.fields import RichTextField from ckeditor.fields import RichTextField
from django_iban.fields import IBANField from django_iban.fields import IBANField
from templated_email import send_templated_mail
from aleksis.core.mixins import ExtensibleModel from aleksis.core.mixins import ExtensibleModel
from aleksis.core.models import Group, Person from aleksis.core.models import Group, Person
from aleksis.core.util.core_helpers import generate_random_code, get_site_preferences from aleksis.core.util.core_helpers import generate_random_code, get_site_preferences
from aleksis.core.util.email import send_email
class Terms(ExtensibleModel): class Terms(ExtensibleModel):
...@@ -31,10 +31,17 @@ class InfoMailing(ExtensibleModel): ...@@ -31,10 +31,17 @@ class InfoMailing(ExtensibleModel):
active = models.BooleanField(verbose_name=_("Mailing is active"), default=False) 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_person = models.BooleanField(verbose_name=_("Send to registered person"), default=True)
send_to_guardians = models.BooleanField(verbose_name=_("Send to guardians"), default=False) 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: def __str__(self) -> str:
return self.subject return self.subject
...@@ -47,12 +54,16 @@ class InfoMailing(ExtensibleModel): ...@@ -47,12 +54,16 @@ class InfoMailing(ExtensibleModel):
sent_to = self.sent_to.all() sent_to = self.sent_to.all()
for event in self.events.all(): for event in self.events.all():
for registration in event.registrations: for registration in event.registrations.all():
if registration.person in sent_to: if registration.person in sent_to:
continue continue
subject = self.subject.format(event=event, registration=registration, person=registration.person) subject = self.subject.format(
body = self.text.format(event=event, registration=registration, person=registration.person) event=event, registration=registration, person=registration.person
)
body = self.text.format(
event=event, registration=registration, person=registration.person
)
if self.send_to_person: if self.send_to_person:
to = [registration.person.email] to = [registration.person.email]
...@@ -68,9 +79,18 @@ class InfoMailing(ExtensibleModel): ...@@ -68,9 +79,18 @@ class InfoMailing(ExtensibleModel):
reply_to = self.reply_to or sender reply_to = self.reply_to or sender
context = {"subject": subject, "body": body} 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): class Event(ExtensibleModel):
...@@ -94,7 +114,9 @@ class Event(ExtensibleModel): ...@@ -94,7 +114,9 @@ class Event(ExtensibleModel):
max_participants = models.PositiveSmallIntegerField(verbose_name=_("Maximum participants")) max_participants = models.PositiveSmallIntegerField(verbose_name=_("Maximum participants"))
information = RichTextField(verbose_name=_("Information about the event")) information = RichTextField(verbose_name=_("Information about the event"))
terms = models.ManyToManyField(Terms, verbose_name=_("Terms"), related_name="event", blank=True) 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): def save(self, *args, **kwargs):
if not self.slug: if not self.slug:
...@@ -193,7 +215,9 @@ class Voucher(ExtensibleModel): ...@@ -193,7 +215,9 @@ class Voucher(ExtensibleModel):
class EventRegistration(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")) person = models.ForeignKey(Person, on_delete=models.CASCADE, verbose_name=_("Person"))
date_registered = models.DateTimeField(auto_now_add=True, verbose_name=_("Registration date")) date_registered = models.DateTimeField(auto_now_add=True, verbose_name=_("Registration date"))
......
...@@ -109,3 +109,11 @@ view_terms_predicate = has_person & ( ...@@ -109,3 +109,11 @@ view_terms_predicate = has_person & (
has_global_perm("paweljong.view_term") | has_any_object("paweljong.view_term", Terms) has_global_perm("paweljong.view_term") | has_any_object("paweljong.view_term", Terms)
) )
rules.add_perm("paweljong.view_terms_rule", view_terms_predicate) 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): ...@@ -76,3 +76,23 @@ class TermsTable(tables.Table):
verbose_name=_("Edit"), verbose_name=_("Edit"),
text=_("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 ...@@ -6,5 +6,6 @@ from aleksis.core.celery import app
@app.task(run_every=timedelta(hours=1)) @app.task(run_every=timedelta(hours=1))
def send_info_mailings() -> None: def send_info_mailings() -> None:
from .models import InfoMailing # noqa from .models import InfoMailing # noqa
for mailing in InfoMailing.get_active_mailings(): for mailing in InfoMailing.get_active_mailings():
mailing.send() 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 = [ ...@@ -102,4 +102,24 @@ urlpatterns = [
views.TermEditView.as_view(), views.TermEditView.as_view(),
name="edit_term_by_pk", 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 ...@@ -32,12 +32,19 @@ from .filters import EventFilter, EventRegistrationFilter, VoucherFilter
from .forms import ( from .forms import (
EditEventForm, EditEventForm,
EditEventRegistrationForm, EditEventRegistrationForm,
EditInfoMailingForm,
EditTermForm, EditTermForm,
EditVoucherForm, EditVoucherForm,
GenerateListForm, GenerateListForm,
) )
from .models import Event, EventRegistration, Terms, Voucher from .models import Event, EventRegistration, InfoMailing, Terms, Voucher
from .tables import EventRegistrationsTable, ManageEventsTable, TermsTable, VouchersTable from .tables import (
EventRegistrationsTable,
InfoMailingsTable,
ManageEventsTable,
TermsTable,
VouchersTable,
)
User = get_user_model() User = get_user_model()
...@@ -769,3 +776,46 @@ class UpcomingEventsRSSFeed(Feed): ...@@ -769,3 +776,46 @@ class UpcomingEventsRSSFeed(Feed):
class AccountRegisterStart(TemplateView): class AccountRegisterStart(TemplateView):
template_name = "paweljong/register_start.html" 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