From 874f5214c94782037dafe49c1ae04e89e233b02e Mon Sep 17 00:00:00 2001 From: Stefan Dworschak Date: Wed, 4 Jan 2023 11:51:35 +0000 Subject: [PATCH 1/3] adding basic celery setting --- hackathon/tasks.py | 35 ++++++++++++++++++ .../templates/hackathon/enrolment_email.txt | 36 +++++++++++++++++++ main/celery.py | 11 ++++++ 3 files changed, 82 insertions(+) create mode 100644 hackathon/tasks.py create mode 100644 hackathon/templates/hackathon/enrolment_email.txt create mode 100644 main/celery.py diff --git a/hackathon/tasks.py b/hackathon/tasks.py new file mode 100644 index 00000000..3c42ff57 --- /dev/null +++ b/hackathon/tasks.py @@ -0,0 +1,35 @@ +import logging +import os + +from celery import shared_task +from django.conf import settings +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, hackathon, template_name): + try: + template = EmailTemplate.objects.get(template_name=template_name) + user_name = user.first_name or user.email + slack_settings = SlackSiteSettings.objects.first() + if slack_settings and slack_settings.enable_welcome_emails: + send_mail( + subject=template.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=slack_settings.from_email, + recipient_list=[user.email], + fail_silently=False, + ) + logger.info("Email {template_name} sucessfully sent to user {user.id}.") + except template.DoesNotExist: + 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..153f3217 --- /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 a person short. +

+ +

+ !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/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() From 451d74d32fc9db21573028fb6837c01613ca242a Mon Sep 17 00:00:00 2001 From: Stefan Dworschak Date: Wed, 4 Jan 2023 17:20:07 +0000 Subject: [PATCH 2/3] Adding celery and model to store Email Templates --- 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 | 19 +++++++------ hackathon/views.py | 5 ++++ main/__init__.py | 3 ++ main/settings.py | 11 ++++++-- requirements.txt | 4 +++ static/img/hackathon_header.png | Bin 0 -> 28344 bytes 12 files changed, 117 insertions(+), 13 deletions(-) create mode 100644 accounts/migrations/0019_emailtemplate.py create mode 100644 accounts/migrations/0020_auto_20230104_1655.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 index 3c42ff57..600d1ea2 100644 --- a/hackathon/tasks.py +++ b/hackathon/tasks.py @@ -3,6 +3,7 @@ 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 @@ -12,22 +13,22 @@ @shared_task -def send_email_from_template(user, hackathon, template_name): +def send_email_from_template(user_email, user_name, hackathon_display_name, template_name): try: - template = EmailTemplate.objects.get(template_name=template_name) - user_name = user.first_name or user.email + 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.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=slack_settings.from_email, - recipient_list=[user.email], + 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 template.DoesNotExist: + except ObjectDoesNotExist: logger.exception( (f"There is no template with the name {template_name}." "Please create it on the Django Admin Panel")) 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/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 From 63a54d345b31efce847a998da13b1ae1c5ad87c4 Mon Sep 17 00:00:00 2001 From: Stefan Dworschak Date: Fri, 6 Jan 2023 12:48:35 +0000 Subject: [PATCH 3/3] Fixing grammer in template --- hackathon/templates/hackathon/enrolment_email.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/hackathon/templates/hackathon/enrolment_email.txt b/hackathon/templates/hackathon/enrolment_email.txt index 153f3217..6ec62ba7 100644 --- a/hackathon/templates/hackathon/enrolment_email.txt +++ b/hackathon/templates/hackathon/enrolment_email.txt @@ -1,6 +1,6 @@

Hi {student},

-

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

+

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

What does participation involve?
@@ -10,25 +10,25 @@

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 a person short. + 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. + 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. + 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. + 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. +

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!