Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New email speakers system #3830

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
278 changes: 278 additions & 0 deletions backend/email_speakers/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
from django.utils import timezone
from django.contrib.admin.options import IS_POPUP_VAR
from django.db import transaction
from typing import Any
from django.contrib import admin, messages

from django.db.models.query import QuerySet
from django.http.request import HttpRequest
from django.http.response import HttpResponse

from users.models import User
from schedule.tasks import (
calculate_recipients_for_email_speaker,
process_email_speaker_for_sending,
)
from email_speakers.models import EmailSpeaker, EmailSpeakerRecipient
from django import forms
from django.utils.safestring import mark_safe


class EmailSpeakerForm(forms.ModelForm):
test_email = forms.EmailField(required=False)

class Meta:
model = EmailSpeaker
exclude = ()


class EmailSpeakerRecipientInline(admin.TabularInline):
model = EmailSpeakerRecipient
extra = 0
readonly_fields = (
"status",
"user",
"sent_at",
)
fields = (
"status",
"user",
"sent_at",
)
show_change_link = True
can_delete = False
verbose_name = "Recipient"
verbose_name_plural = "Recipients"

def has_add_permission(self, request: HttpRequest, obj) -> bool:
return False

def get_queryset(self, request: HttpRequest) -> QuerySet[Any]:
return super().get_queryset(request).filter(is_test=False)


@admin.register(EmailSpeaker)
class EmailSpeakerAdmin(admin.ModelAdmin):
form = EmailSpeakerForm
list_display = ("subject", "status", "conference")
search_fields = ("conference", "subject", "body")
list_filter = (
"status",
"conference",
"sent_at",
)
readonly_fields = (
"created",
"modified",
"status",
"send_test_email",
"send_email_to_speakers",
"sent_at",
"conference",
"show_recipients",
)
inlines = [EmailSpeakerRecipientInline]
date_hierarchy = "created"

def changeform_view(
self,
request: HttpRequest,
object_id: str,
form_url: str = ...,
extra_context: dict[str, bool] | None = ...,
) -> HttpResponse:
extra_context = extra_context or {}
extra_context.update(
{
"show_save_and_add_another": False,
"show_save_and_continue": True,
"show_save": False,
}
)
return super().changeform_view(request, object_id, form_url, extra_context)

def get_inlines(self, request: HttpRequest, obj: Any | None):
return super().get_inlines(request, obj) if obj else []

def get_readonly_fields(
self, request: HttpRequest, obj: Any | None = ...
) -> list[str] | tuple[Any, ...]:
readyonly_fields = super().get_readonly_fields(request, obj)

if not obj:
return set(readyonly_fields) - {
"conference",
}

return readyonly_fields

def has_change_permission(
self, request: HttpRequest, obj: Any | None = None
) -> bool:
if obj and obj.status != EmailSpeaker.Status.draft:
return False
return super().has_change_permission(request, obj)

def response_add(self, request, obj, post_url_continue=None):
if "_addanother" not in request.POST and IS_POPUP_VAR not in request.POST:
request.POST = request.POST.copy()
request.POST["_continue"] = 1
return super().response_add(request, obj, post_url_continue)

def response_change(self, request, obj):
if (
"_send_test_email" in request.POST
or "_send_email_to_speakers" in request.POST
or "_show_recipients" in request.POST
):
request.POST = request.POST.copy()
request.POST["_continue"] = 1

return super().response_change(request, obj)

def save_form(self, request: Any, form: Any, change: Any) -> Any:
instance = super().save_form(request, form, change)

if "_send_test_email" in form.data:
test_email = form.cleaned_data.get("test_email")
if not test_email:
self.message_user(
request,
"No test email specified.",
messages.ERROR,
)
return instance

user = User.objects.filter(email=test_email).first()

if not user:
self.message_user(
request,
f"No user found with email {test_email}.",
messages.ERROR,
)
return instance

transaction.on_commit(
lambda: process_email_speaker_for_sending.delay(
email_speaker_id=instance.id,
recipient_user_id=user.id,
is_test=True,
)
)

self.message_user(
request,
f"Test email sent to {user.email}.",
messages.SUCCESS,
)

if "_send_email_to_speakers" in form.data:
instance.sent_at = timezone.now()
instance.status = EmailSpeaker.Status.in_progress
instance.save(update_fields=["sent_at", "status"])

transaction.on_commit(
lambda: process_email_speaker_for_sending.delay(
email_speaker_id=instance.id, is_test=False
)
)

self.message_user(
request,
"Sending the email.",
messages.SUCCESS,
)

if "_show_recipients" in form.data:
transaction.on_commit(
lambda: calculate_recipients_for_email_speaker.delay(
email_speaker_id=instance.id,
status=EmailSpeakerRecipient.Status.draft,
)
)
self.message_user(
request,
"Calculating recipients in background.",
messages.INFO,
)

return instance

def send_email_to_speakers(self, obj):
return mark_safe(
"""
<input type="submit" name="_send_email_to_speakers" value="Send email to speakers!" style="background-color: var(--delete-button-bg)" />
"""
)

def send_test_email(self, obj):
return mark_safe(
"""
<input type="submit" name="_send_test_email" value="Send test email" />
"""
)

def show_recipients(self, obj):
if not obj.is_draft:
return None
return mark_safe(
"""
<input type="submit" name="_show_recipients" value="Show recipients" />
"""
)

def get_fieldsets(self, request, obj=None):
fieldsets = super().get_fieldsets(request, obj)

if not obj:
return [
(
None,
{
"fields": (
"conference",
"subject",
)
},
),
]

fieldsets = [
(
"Email",
{
"fields": ("status", "conference", "subject", "body"),
},
),
(
"Test email",
{
"fields": ("test_email", "send_test_email"),
},
),
(
"Recipients",
{"fields": ("send_only_to_speakers_without_ticket", "show_recipients")},
),
(
"Send",
{
"fields": ("send_email_to_speakers",),
},
),
(
"Dates",
{
"fields": ("sent_at", "created", "modified"),
"classes": ("collapse",),
},
),
]

if not obj.is_draft:
fieldsets.pop(1)
fieldsets.pop(2)
fieldsets[-1][1]["classes"] = ()

return fieldsets
6 changes: 6 additions & 0 deletions backend/email_speakers/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class EmailSpeakersConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "email_speakers"
32 changes: 32 additions & 0 deletions backend/email_speakers/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Generated by Django 4.2.7 on 2024-05-02 19:06

from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import model_utils.fields


class Migration(migrations.Migration):

initial = True

dependencies = [
('conferences', '0042_conference_video_description_template_and_more'),
]

operations = [
migrations.CreateModel(
name='EmailSpeaker',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('subject', models.CharField(max_length=988)),
('body', models.TextField()),
('conference', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='email_speakers', to='conferences.conference', verbose_name='conference')),
],
options={
'verbose_name_plural': 'Emails to speakers',
},
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.7 on 2024-05-02 19:17

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('email_speakers', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='emailspeaker',
name='send_only_to_speakers_without_ticket',
field=models.BooleanField(default=False),
),
]
18 changes: 18 additions & 0 deletions backend/email_speakers/migrations/0003_emailspeaker_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.7 on 2024-05-02 19:18

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('email_speakers', '0002_emailspeaker_send_only_to_speakers_without_ticket'),
]

operations = [
migrations.AddField(
model_name='emailspeaker',
name='status',
field=models.CharField(choices=[('draft', 'Draft'), ('sent', 'Sent')], default='draft', max_length=20, verbose_name='status'),
),
]
18 changes: 18 additions & 0 deletions backend/email_speakers/migrations/0004_emailspeaker_sent_at.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.7 on 2024-05-02 19:34

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('email_speakers', '0003_emailspeaker_status'),
]

operations = [
migrations.AddField(
model_name='emailspeaker',
name='sent_at',
field=models.DateTimeField(blank=True, null=True),
),
]
Loading
Loading