From ab4b7c0e8aafe75036f6924be228021ead2e79e7 Mon Sep 17 00:00:00 2001 From: Stefan Dworschak Date: Fri, 6 Jan 2023 16:25:01 +0000 Subject: [PATCH] #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 --- Dockerfile | 2 +- accounts/admin.py | 7 +++- accounts/migrations/0019_emailtemplate.py | 26 +++++++++++++ .../migrations/0020_auto_20230104_1655.py | 22 +++++++++++ accounts/models.py | 17 +++++++++ docker-compose.yml | 14 +++++++ hackathon/tasks.py | 36 ++++++++++++++++++ .../templates/hackathon/enrolment_email.txt | 36 ++++++++++++++++++ hackathon/views.py | 5 +++ main/__init__.py | 3 ++ main/celery.py | 11 ++++++ main/settings.py | 11 +++++- requirements.txt | 4 ++ static/img/hackathon_header.png | Bin 0 -> 28344 bytes 14 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 accounts/migrations/0019_emailtemplate.py create mode 100644 accounts/migrations/0020_auto_20230104_1655.py create mode 100644 hackathon/tasks.py create mode 100644 hackathon/templates/hackathon/enrolment_email.txt create mode 100644 main/celery.py create mode 100644 static/img/hackathon_header.png diff --git a/Dockerfile b/Dockerfile index b82c4fcd..8dbfdc43 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM ubuntu:20.04 RUN apt-get update -y -RUN apt-get install python3 python3-pip libmysqlclient-dev mysql-client vim -y +RUN apt-get install python3 python3-pip libmysqlclient-dev mysql-client vim sqlite3 -y WORKDIR /hackathon-app COPY ./requirements.txt /hackathon-app/requirements.txt diff --git a/accounts/admin.py b/accounts/admin.py index 574d169f..59bd3dde 100644 --- a/accounts/admin.py +++ b/accounts/admin.py @@ -3,7 +3,7 @@ from django.contrib.auth.forms import UserChangeForm, UserCreationForm from django.contrib.auth.decorators import login_required -from .models import CustomUser, Organisation +from .models import CustomUser, Organisation, EmailTemplate from accounts.models import SlackSiteSettings @@ -49,8 +49,13 @@ class CustomUserAdmin(BaseUserAdmin): readonly_fields = ('last_login', 'date_joined', 'user_type') +class EmailTemplateAdmin(admin.ModelAdmin): + list_display = ('display_name', 'subject', 'template_name', 'is_active', ) + + # sign-in via allauth required before accessing the admin panel admin.site.login = login_required(admin.site.login) admin.site.register(CustomUser, CustomUserAdmin) admin.site.register(Organisation) admin.site.register(SlackSiteSettings) +admin.site.register(EmailTemplate, EmailTemplateAdmin) diff --git a/accounts/migrations/0019_emailtemplate.py b/accounts/migrations/0019_emailtemplate.py new file mode 100644 index 00000000..7620335c --- /dev/null +++ b/accounts/migrations/0019_emailtemplate.py @@ -0,0 +1,26 @@ +# Generated by Django 3.1.13 on 2023-01-04 15:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0018_slacksitesettings'), + ] + + operations = [ + migrations.CreateModel( + name='EmailTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('display_name', models.CharField(max_length=255)), + ('description', models.TextField(blank=True, null=True)), + ('template_name', models.CharField(max_length=255)), + ('subject', models.CharField(max_length=1048)), + ('plain_text_message', models.TextField()), + ('html_message', models.TextField(blank=True, null=True)), + ('is_active', models.BooleanField(default=True)), + ], + ), + ] diff --git a/accounts/migrations/0020_auto_20230104_1655.py b/accounts/migrations/0020_auto_20230104_1655.py new file mode 100644 index 00000000..cd5e70c7 --- /dev/null +++ b/accounts/migrations/0020_auto_20230104_1655.py @@ -0,0 +1,22 @@ +# Generated by Django 3.1.13 on 2023-01-04 16:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0019_emailtemplate'), + ] + + operations = [ + migrations.AlterModelOptions( + name='emailtemplate', + options={'verbose_name': 'Email Template', 'verbose_name_plural': 'Email Templates'}, + ), + migrations.AlterField( + model_name='emailtemplate', + name='template_name', + field=models.CharField(max_length=255, unique=True), + ), + ] diff --git a/accounts/models.py b/accounts/models.py index 4735770b..970e3f4f 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -210,3 +210,20 @@ def __str__(self): class Meta: verbose_name = 'Slack Site Settings' verbose_name_plural = 'Slack Site Settings' + + +class EmailTemplate(models.Model): + display_name = models.CharField(max_length=255) + description = models.TextField(null=True, blank=True) + template_name = models.CharField(max_length=255, unique=True) + subject = models.CharField(max_length=1048) + plain_text_message = models.TextField() + html_message = models.TextField(null=True, blank=True) + is_active = models.BooleanField(default=True) + + class Meta: + verbose_name = 'Email Template' + verbose_name_plural = 'Email Templates' + + def __str__(self): + return self.display_name diff --git a/docker-compose.yml b/docker-compose.yml index 24f7aea0..e8b5d687 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,6 +33,16 @@ services: - "8000:8000" tty: true stdin_open: true + + hackathon-worker: + image: hackathon-app + environment: + - ENV_FILE=/hackathon-app/.env + - DEVELOPMENT=1 + entrypoint: ["celery", "-A", "main", "worker", "-l", "info"] + volumes: + - ./data/:/hackathon-app/data/ + - ./.env:/hackathon-app/.env mysql: image: docker.io/mysql:5.6.36 @@ -45,8 +55,12 @@ services: MYSQL_PASSWORD: gummyball volumes: - ./data/mysql:/var/lib/mysql + - ./hackathon/:/hackathon-app/hackathon/ smtp: image: mailhog/mailhog:v1.0.1 ports: - "8026:8025" + + redis: + image: redis diff --git a/hackathon/tasks.py b/hackathon/tasks.py new file mode 100644 index 00000000..600d1ea2 --- /dev/null +++ b/hackathon/tasks.py @@ -0,0 +1,36 @@ +import logging +import os + +from celery import shared_task +from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist +from django.core.mail import send_mail +from smtplib import SMTPException + +from accounts.models import EmailTemplate, SlackSiteSettings + +logger = logging.getLogger(__name__) + + +@shared_task +def send_email_from_template(user_email, user_name, hackathon_display_name, template_name): + try: + template = EmailTemplate.objects.get(template_name=template_name, is_active=True) + user_name = user_name or user_email + slack_settings = SlackSiteSettings.objects.first() + if slack_settings and slack_settings.enable_welcome_emails: + send_mail( + subject=template.subject.format(hackathon=hackathon_display_name), + message=template.plain_text_message.format(student=user_name, hackathon=hackathon_display_name), + html_message=template.html_message.format(student=user_name, hackathon=hackathon_display_name), + from_email=settings.DEFAULT_FROM_EMAIL, + recipient_list=[user_email], + fail_silently=False, + ) + logger.info("Email {template_name} sucessfully sent to user {user.id}.") + except ObjectDoesNotExist: + logger.exception( + (f"There is no template with the name {template_name}." + "Please create it on the Django Admin Panel")) + except SMTPException: + logger.exception("There was an issue sending the email.") diff --git a/hackathon/templates/hackathon/enrolment_email.txt b/hackathon/templates/hackathon/enrolment_email.txt new file mode 100644 index 00000000..6ec62ba7 --- /dev/null +++ b/hackathon/templates/hackathon/enrolment_email.txt @@ -0,0 +1,36 @@ +

Hi {student},

+ +

Thank you so much for registering for the {hackathon}!

+ +

+ What does participation involve?
+ 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! +

+ +

+ What am I committing to?
+ 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. + 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. +

+ +

+ IMPORTANT!
+ 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. +

+ +

+ Register for the Intro Webinar!
+ Please check the #hackathon channel for details on how to register for the intro and project presentations webinar. +

+ +

+ Need Help?
+ Please ask any questions in the #hackathon channel, the HackTeam are ready and happy to help out. You can ask them a question by using the @hackteam tag on slack. +

+ +

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. + +

+ Happy Hacking!
+ The Code Institute Community Team +

diff --git a/hackathon/views.py b/hackathon/views.py index ca7712cf..b82f6284 100644 --- a/hackathon/views.py +++ b/hackathon/views.py @@ -17,6 +17,7 @@ HackAwardForm, HackTeamForm from .lists import AWARD_CATEGORIES from .helpers import format_date, query_scores, create_judges_scores_table +from .tasks import send_email_from_template from accounts.models import UserType from accounts.decorators import can_access, has_access_to_hackathon @@ -416,14 +417,17 @@ def enroll_toggle(request): id=request.POST.get("hackathon-id")) if request.user in hackathon.judges.all(): hackathon.judges.remove(request.user) + send_email_from_template.apply_async(args=[request.user.email, request.user.first_name, hackathon.display_name, 'withdraw_judge']) messages.success(request, "You have withdrawn from judging.") elif request.user in hackathon.participants.all(): hackathon.participants.remove(request.user) + send_email_from_template.apply_async(args=[request.user.email, request.user.first_name, hackathon.display_name, 'withdraw_participant']) messages.success(request, "You have withdrawn from this Hackaton.") elif (request.POST.get('enrollment-type') == 'judge' and request.user.user_type in judge_user_types): hackathon.judges.add(request.user) + send_email_from_template.apply_async(args=[request.user.email, request.user.first_name, hackathon.display_name, 'enroll_judge']) messages.success(request, "You have enrolled as a facilitator/judge.") # noqa: E501 else: if hackathon.max_participants_reached(): @@ -432,6 +436,7 @@ def enroll_toggle(request): return redirect(reverse('hackathon:view_hackathon', kwargs={ 'hackathon_id': request.POST.get("hackathon-id")})) hackathon.participants.add(request.user) + send_email_from_template.apply_async(args=[request.user.email, request.user.first_name, hackathon.display_name, 'enroll_participant']) messages.success(request, "You have enrolled successfully.") return redirect(reverse( diff --git a/main/__init__.py b/main/__init__.py index e69de29b..cd042640 100644 --- a/main/__init__.py +++ b/main/__init__.py @@ -0,0 +1,3 @@ +from .celery import app as celery_app + +__all__ = ['celery_app'] diff --git a/main/celery.py b/main/celery.py new file mode 100644 index 00000000..2898ee47 --- /dev/null +++ b/main/celery.py @@ -0,0 +1,11 @@ +import os + +from celery import Celery + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'main.settings') + +app = Celery('main') + +app.config_from_object('django.conf:settings', namespace='CELERY') + +app.autodiscover_tasks() diff --git a/main/settings.py b/main/settings.py index 25a36ee5..4ff6399f 100644 --- a/main/settings.py +++ b/main/settings.py @@ -36,6 +36,7 @@ "allauth.account", "allauth.socialaccount", "crispy_forms", + "django_celery_results", # custom apps "accounts", @@ -100,14 +101,14 @@ EMAIL_BACKEND = os.environ.get( 'EMAIL_BACKEND', 'django.core.mail.backends.console.EmailBackend') +DEFAULT_FROM_EMAIL = (os.environ.get('DEFAULT_FROM_EMAIL') + or os.environ.get("SUPPORT_EMAIL")) if EMAIL_BACKEND == 'django.core.mail.backends.smtp.EmailBackend': EMAIL_USE_TLS = os.environ.get('EMAIL_USE_TLS', 'False') == 'True' EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 25)) EMAIL_HOST = os.environ.get('EMAIL_HOST') EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER') EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD') - DEFAULT_FROM_EMAIL = (os.environ.get('DEFAULT_FROM_EMAIL') - or os.environ.get("SUPPORT_EMAIL")) AUTH_USER_MODEL = "accounts.CustomUser" ACCOUNT_SIGNUP_FORM_CLASS = "accounts.forms.SignupForm" @@ -163,6 +164,12 @@ }, ] +# Celery +CELERY_BROKER_URL = os.environ.get('CELERY_BROKER', 'redis://redis:6379') +CELERY_RESULT_BACKEND = os.environ.get('CELERY_RESULT_BACKEND', 'redis://redis:6379') # noqa: E501 +CELERY_ACCEPT_CONTENT = os.environ.get('CELERY_ACCEPT_CONTENT', 'application/json').split(',') # noqa: E501 +CELERY_TASK_SERIALIZER = os.environ.get('CELERY_TASK_SERIALIZER', 'json') +CELERY_RESULT_SERIALIZER = os.environ.get('CELERY_RESULT_SERIALIZER', 'json') LANGUAGE_CODE = "en-us" TIME_ZONE = "UTC" diff --git a/requirements.txt b/requirements.txt index 9753b318..241ec1a2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ appdirs==1.4.3 asgiref==3.2.10 backcall==0.2.0 CacheControl==0.12.6 +celery==5.2.3 certifi==2020.6.20 chardet==3.0.4 colorama==0.4.3 @@ -13,6 +14,8 @@ distro==1.4.0 dj-database-url==0.5.0 Django==3.1.13 django-allauth==0.42.0 +django-celery-beat==2.2.1 +django-celery-results==2.2.0 django-crispy-forms==1.9.2 django-extensions==3.1.0 graphviz==0.16 @@ -45,6 +48,7 @@ python-dotenv==0.14.0 python3-openid==3.2.0 pytoml==0.1.21 pytz==2020.1 +redis==4.1.1 requests==2.24.0 requests-oauthlib==1.3.0 retrying==1.3.3 diff --git a/static/img/hackathon_header.png b/static/img/hackathon_header.png new file mode 100644 index 0000000000000000000000000000000000000000..01c8497aa433cca2ea6f94b09d81b474f90c28e8 GIT binary patch literal 28344 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 literal 0 HcmV?d00001