Skip to content

Commit ab4b7c0

Browse files
#M20. Add automated emails to signup and hackathon registration (#277)
* adding basic celery setting * Adding celery and model to store Email Templates * Fixing grammer in template
1 parent 310ac81 commit ab4b7c0

14 files changed

Lines changed: 190 additions & 4 deletions

File tree

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
FROM ubuntu:20.04
22

33
RUN apt-get update -y
4-
RUN apt-get install python3 python3-pip libmysqlclient-dev mysql-client vim -y
4+
RUN apt-get install python3 python3-pip libmysqlclient-dev mysql-client vim sqlite3 -y
55

66
WORKDIR /hackathon-app
77
COPY ./requirements.txt /hackathon-app/requirements.txt

accounts/admin.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
44
from django.contrib.auth.decorators import login_required
55

6-
from .models import CustomUser, Organisation
6+
from .models import CustomUser, Organisation, EmailTemplate
77
from accounts.models import SlackSiteSettings
88

99

@@ -49,8 +49,13 @@ class CustomUserAdmin(BaseUserAdmin):
4949
readonly_fields = ('last_login', 'date_joined', 'user_type')
5050

5151

52+
class EmailTemplateAdmin(admin.ModelAdmin):
53+
list_display = ('display_name', 'subject', 'template_name', 'is_active', )
54+
55+
5256
# sign-in via allauth required before accessing the admin panel
5357
admin.site.login = login_required(admin.site.login)
5458
admin.site.register(CustomUser, CustomUserAdmin)
5559
admin.site.register(Organisation)
5660
admin.site.register(SlackSiteSettings)
61+
admin.site.register(EmailTemplate, EmailTemplateAdmin)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Generated by Django 3.1.13 on 2023-01-04 15:38
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('accounts', '0018_slacksitesettings'),
10+
]
11+
12+
operations = [
13+
migrations.CreateModel(
14+
name='EmailTemplate',
15+
fields=[
16+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
17+
('display_name', models.CharField(max_length=255)),
18+
('description', models.TextField(blank=True, null=True)),
19+
('template_name', models.CharField(max_length=255)),
20+
('subject', models.CharField(max_length=1048)),
21+
('plain_text_message', models.TextField()),
22+
('html_message', models.TextField(blank=True, null=True)),
23+
('is_active', models.BooleanField(default=True)),
24+
],
25+
),
26+
]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Generated by Django 3.1.13 on 2023-01-04 16:55
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('accounts', '0019_emailtemplate'),
10+
]
11+
12+
operations = [
13+
migrations.AlterModelOptions(
14+
name='emailtemplate',
15+
options={'verbose_name': 'Email Template', 'verbose_name_plural': 'Email Templates'},
16+
),
17+
migrations.AlterField(
18+
model_name='emailtemplate',
19+
name='template_name',
20+
field=models.CharField(max_length=255, unique=True),
21+
),
22+
]

accounts/models.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,20 @@ def __str__(self):
210210
class Meta:
211211
verbose_name = 'Slack Site Settings'
212212
verbose_name_plural = 'Slack Site Settings'
213+
214+
215+
class EmailTemplate(models.Model):
216+
display_name = models.CharField(max_length=255)
217+
description = models.TextField(null=True, blank=True)
218+
template_name = models.CharField(max_length=255, unique=True)
219+
subject = models.CharField(max_length=1048)
220+
plain_text_message = models.TextField()
221+
html_message = models.TextField(null=True, blank=True)
222+
is_active = models.BooleanField(default=True)
223+
224+
class Meta:
225+
verbose_name = 'Email Template'
226+
verbose_name_plural = 'Email Templates'
227+
228+
def __str__(self):
229+
return self.display_name

docker-compose.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ services:
3333
- "8000:8000"
3434
tty: true
3535
stdin_open: true
36+
37+
hackathon-worker:
38+
image: hackathon-app
39+
environment:
40+
- ENV_FILE=/hackathon-app/.env
41+
- DEVELOPMENT=1
42+
entrypoint: ["celery", "-A", "main", "worker", "-l", "info"]
43+
volumes:
44+
- ./data/:/hackathon-app/data/
45+
- ./.env:/hackathon-app/.env
3646

3747
mysql:
3848
image: docker.io/mysql:5.6.36
@@ -45,8 +55,12 @@ services:
4555
MYSQL_PASSWORD: gummyball
4656
volumes:
4757
- ./data/mysql:/var/lib/mysql
58+
- ./hackathon/:/hackathon-app/hackathon/
4859

4960
smtp:
5061
image: mailhog/mailhog:v1.0.1
5162
ports:
5263
- "8026:8025"
64+
65+
redis:
66+
image: redis

hackathon/tasks.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import logging
2+
import os
3+
4+
from celery import shared_task
5+
from django.conf import settings
6+
from django.core.exceptions import ObjectDoesNotExist
7+
from django.core.mail import send_mail
8+
from smtplib import SMTPException
9+
10+
from accounts.models import EmailTemplate, SlackSiteSettings
11+
12+
logger = logging.getLogger(__name__)
13+
14+
15+
@shared_task
16+
def send_email_from_template(user_email, user_name, hackathon_display_name, template_name):
17+
try:
18+
template = EmailTemplate.objects.get(template_name=template_name, is_active=True)
19+
user_name = user_name or user_email
20+
slack_settings = SlackSiteSettings.objects.first()
21+
if slack_settings and slack_settings.enable_welcome_emails:
22+
send_mail(
23+
subject=template.subject.format(hackathon=hackathon_display_name),
24+
message=template.plain_text_message.format(student=user_name, hackathon=hackathon_display_name),
25+
html_message=template.html_message.format(student=user_name, hackathon=hackathon_display_name),
26+
from_email=settings.DEFAULT_FROM_EMAIL,
27+
recipient_list=[user_email],
28+
fail_silently=False,
29+
)
30+
logger.info("Email {template_name} sucessfully sent to user {user.id}.")
31+
except ObjectDoesNotExist:
32+
logger.exception(
33+
(f"There is no template with the name {template_name}."
34+
"Please create it on the Django Admin Panel"))
35+
except SMTPException:
36+
logger.exception("There was an issue sending the email.")
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<p>Hi {student},</p>
2+
3+
<p>Thank you so much for registering for the {hackathon}!</p>
4+
5+
<p>
6+
<strong>What does participation involve?</strong><br>
7+
You'll be assigned to a team, and work together building a project based on the assigned theme in a limited number of days. Don't worry if you have limited coding experience, all levels are welcome, and we encourage alumni to participate!
8+
</p>
9+
10+
<p>
11+
<strong>What am I committing to?</strong><br>
12+
We recommend at bare minimum you dedicate a minimum of 8-10 hours over the duration of the Hackathon. You will be expected to actively contribute to your team, not just observe.
13+
Please check your calendar and confirm that you are available before registering, as dropping out really lets your team down, and the team will be one person fewer.
14+
</p>
15+
16+
<p>
17+
<strong>IMPORTANT!</strong><br>
18+
Please ensure your Profile is up to date, especially the 'Latest Module' entry. This is vital for the team selection process. We try our best to balance the teams fairly, so it really helps you and your team to be accurate with your profile.
19+
</p>
20+
21+
<p>
22+
<strong>Register for the Intro Webinar!</strong><br>
23+
Please check the <a href="https://code-institute-room.slack.com/archives/CDAFARB71" target="_blank">#hackathon</a> channel for details on how to register for the intro and project presentations webinar.
24+
</p>
25+
26+
<p>
27+
<strong>Need Help?</strong><br>
28+
Please ask any questions in the <a href="https://code-institute-room.slack.com/archives/CDAFARB71" target="_blank">#hackathon</a> channel, the HackTeam are ready and happy to help out. You can ask them a question by using the @hackteam tag on slack.
29+
</p>
30+
31+
<p>Thanks again for signing up, we are excited to see what you and your team will create! Remember, hackathons are about team-building, learning and most importantly having fun.
32+
33+
<p>
34+
Happy Hacking!<br>
35+
The Code Institute Community Team
36+
</p>

hackathon/views.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
HackAwardForm, HackTeamForm
1818
from .lists import AWARD_CATEGORIES
1919
from .helpers import format_date, query_scores, create_judges_scores_table
20+
from .tasks import send_email_from_template
2021

2122
from accounts.models import UserType
2223
from accounts.decorators import can_access, has_access_to_hackathon
@@ -416,14 +417,17 @@ def enroll_toggle(request):
416417
id=request.POST.get("hackathon-id"))
417418
if request.user in hackathon.judges.all():
418419
hackathon.judges.remove(request.user)
420+
send_email_from_template.apply_async(args=[request.user.email, request.user.first_name, hackathon.display_name, 'withdraw_judge'])
419421
messages.success(request, "You have withdrawn from judging.")
420422
elif request.user in hackathon.participants.all():
421423
hackathon.participants.remove(request.user)
424+
send_email_from_template.apply_async(args=[request.user.email, request.user.first_name, hackathon.display_name, 'withdraw_participant'])
422425
messages.success(request,
423426
"You have withdrawn from this Hackaton.")
424427
elif (request.POST.get('enrollment-type') == 'judge'
425428
and request.user.user_type in judge_user_types):
426429
hackathon.judges.add(request.user)
430+
send_email_from_template.apply_async(args=[request.user.email, request.user.first_name, hackathon.display_name, 'enroll_judge'])
427431
messages.success(request, "You have enrolled as a facilitator/judge.") # noqa: E501
428432
else:
429433
if hackathon.max_participants_reached():
@@ -432,6 +436,7 @@ def enroll_toggle(request):
432436
return redirect(reverse('hackathon:view_hackathon', kwargs={
433437
'hackathon_id': request.POST.get("hackathon-id")}))
434438
hackathon.participants.add(request.user)
439+
send_email_from_template.apply_async(args=[request.user.email, request.user.first_name, hackathon.display_name, 'enroll_participant'])
435440
messages.success(request, "You have enrolled successfully.")
436441

437442
return redirect(reverse(

main/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .celery import app as celery_app
2+
3+
__all__ = ['celery_app']

0 commit comments

Comments
 (0)