diff --git a/dump.rdb b/dump.rdb new file mode 100644 index 00000000..0958dab0 Binary files /dev/null and b/dump.rdb differ diff --git a/hiss/Procfile b/hiss/Procfile new file mode 100644 index 00000000..d48c4bff --- /dev/null +++ b/hiss/Procfile @@ -0,0 +1,2 @@ +web: gunicorn hiss.wsgi --log-file - +worker: celery -A hiss worker --loglevel=info \ No newline at end of file diff --git a/hiss/application/admin.py b/hiss/application/admin.py index ebca4f0a..76506b6b 100644 --- a/hiss/application/admin.py +++ b/hiss/application/admin.py @@ -59,6 +59,7 @@ def build_approval_email( "event_year": settings.EVENT_YEAR, "confirmation_deadline": confirmation_deadline, "organizer_email": settings.ORGANIZER_EMAIL, + "event_date_text": settings.EVENT_DATE_TEXT, } html_message = render_to_string("application/emails/approved.html", context) message = strip_tags(html_message) @@ -78,6 +79,7 @@ def build_rejection_email(application: Application) -> Tuple[str, str, None, Lis "organizer_name": settings.ORGANIZER_NAME, "event_year": settings.EVENT_YEAR, "organizer_email": settings.ORGANIZER_EMAIL, + "event_date_text": settings.EVENT_DATE_TEXT, } html_message = render_to_string("application/emails/rejected.html", context) message = strip_tags(html_message) @@ -102,7 +104,7 @@ def approve(_modeladmin, _request: HttpRequest, queryset: QuerySet) -> None: print(f"approval email built for {approval_email[-1:]}") email_tuples.append(approval_email) application.save() - send_mass_html_mail(email_tuples) + send_mass_html_mail.delay(email_tuples) def reject(_modeladmin, _request: HttpRequest, queryset: QuerySet) -> None: @@ -115,7 +117,7 @@ def reject(_modeladmin, _request: HttpRequest, queryset: QuerySet) -> None: application.status = STATUS_REJECTED email_tuples.append(build_rejection_email(application)) application.save() - send_mass_html_mail(email_tuples) + send_mass_html_mail.delay(email_tuples) def resend_confirmation(_modeladmin, _request: HttpRequest, queryset: QuerySet) -> None: diff --git a/hiss/application/emails.py b/hiss/application/emails.py index 92ddf1df..052c8dcb 100644 --- a/hiss/application/emails.py +++ b/hiss/application/emails.py @@ -1,44 +1,14 @@ +from celery import shared_task import json from io import BytesIO - import pyqrcode -from django.conf import settings from django.core import mail from django.template.loader import render_to_string from django.utils import html - from application.models import Application from application.apple_wallet import get_apple_wallet_pass_url - -import threading -from django.core.mail import EmailMessage - -#create separate threading class for confirmation email since it has a QR code - -class EmailQRThread(threading.Thread): - def __init__(self, subject, msg, html_msg, recipient_email, qr_stream): - self.subject = subject - self.msg = msg - self.html_msg = html_msg - self.recipient_email = recipient_email - self.qr_stream = qr_stream - threading.Thread.__init__(self) - - def run(self): - qr_code = pyqrcode.create(self.qr_content) - qr_stream = BytesIO() - qr_code.png(qr_stream, scale=5) - - email = mail.EmailMultiAlternatives( - self.subject, self.msg, from_email=None, to=[self.recipient_email] - ) - email.attach_alternative(self.html_msg, "text/html") - email.attach("code.png", self.qr_stream.getvalue(), "text/png") - - # if above code is defined directly in function, it will run synchronously - # therefore need to directly define in threading class to run asynchronously - - email.send() +from django.conf import settings +from django.core.mail import EmailMultiAlternatives def send_creation_email(app: Application) -> None: """ @@ -59,37 +29,56 @@ def send_creation_email(app: Application) -> None: # send_html_email is threaded from the User class # see user/models.py - app.user.send_html_email(template_name, context, subject) + app.user.send_html_email.delay(template_name, context, subject) - -def send_confirmation_email(app: Application) -> None: +@shared_task +def send_confirmation_email(app_id: int) -> None: """ Sends a confirmation email to a user, which contains their QR code as well as additional event information. - :param app: The user's application - :type app: Application + :param app_id: The ID of the user's application + :type app_id: int :return: None """ - subject = f"HowdyHack Waitlist: Important Day-Of Information" - email_template = "application/emails/confirmed.html" - context = { - "first_name": app.first_name, - "event_name": settings.EVENT_NAME, - "organizer_name": settings.ORGANIZER_NAME, - "event_year": settings.EVENT_YEAR, - "organizer_email": settings.ORGANIZER_EMAIL, - "apple_wallet_url": get_apple_wallet_pass_url(app.user.email), - } - html_msg = render_to_string(email_template, context) - plain_msg = html.strip_tags(html_msg) - - qr_content = json.dumps( - { + try: + app = Application.objects.get(id=app_id) + subject = f"TAMUhack: Important Day-Of Information" + email_template = "application/emails/confirmed.html" + + if app.status == "E": + subject = f"TAMUhack Waitlist: Important Day-of Information!" + email_template = "application/emails/confirmed-waitlist.html" + + context = { "first_name": app.first_name, - "last_name": app.last_name, - "email": app.user.email, - "university": app.school.name, + "event_name": settings.EVENT_NAME, + "organizer_name": settings.ORGANIZER_NAME, + "event_year": settings.EVENT_YEAR, + "organizer_email": settings.ORGANIZER_EMAIL, + "apple_wallet_url": get_apple_wallet_pass_url(app.user.email), + "event_date_text": settings.EVENT_DATE_TEXT, } - ) - - email_thread = EmailQRThread(subject, plain_msg, html_msg, app.user.email, qr_content) - email_thread.start() + + html_msg = render_to_string(email_template, context) + plain_msg = html.strip_tags(html_msg) + email = mail.EmailMultiAlternatives( + subject, plain_msg, from_email=None, to=[app.user.email] + ) + + email.attach_alternative(html_msg, "text/html") + qr_content = json.dumps( + { + "first_name": app.first_name, + "last_name": app.last_name, + "email": app.user.email, + "university": app.school.name, + } + ) + + qr_code = pyqrcode.create(qr_content) + qr_stream = BytesIO() + qr_code.png(qr_stream, scale=5) + email.attach("code.png", qr_stream.getvalue(), "image/png") + print(f"sending confirmation email to {app.user.email}") + email.send() + except Exception as e: + print(f"Error sending confirmation email: {e}") \ No newline at end of file diff --git a/hiss/application/fixtures/schools.json b/hiss/application/fixtures/schools.json index 8a5fad80..38c267dd 100644 --- a/hiss/application/fixtures/schools.json +++ b/hiss/application/fixtures/schools.json @@ -10984,7 +10984,7 @@ }, { "model": "application.school", - "pk": 2075, + "pk": 2074, "fields": { "name": "Texas A&M University - San Antonio" } @@ -14519,7 +14519,7 @@ }, { "model": "application.school", - "pk": 2074, + "pk": 2075, "fields": { "name": "Other" } diff --git a/hiss/application/meal_groups.py b/hiss/application/meal_groups.py new file mode 100644 index 00000000..ff946ecd --- /dev/null +++ b/hiss/application/meal_groups.py @@ -0,0 +1,59 @@ +from django.db import transaction +from models import Application + +NUM_GROUPS = 4 +RESTRICTED_FRONTLOAD_FACTOR = 1.3 + +def assign_food_groups(): + veg_apps = [] + nobeef_apps = [] + nopork_apps = [] + allergy_apps = [] + othernonveg_apps = [] + + applicants = Application.objects.filter(status__in=['A', 'E', 'C']) + + for app in applicants: + if "Vegetarian" in app.dietary_restrictions or "Vegan" in app.dietary_restrictions: + veg_apps.append(app) + elif "No-Beef" in app.dietary_restrictions: + nobeef_apps.append(app) + elif "No-Pork" in app.dietary_restrictions: + nopork_apps.append(app) + elif "Food-Allergy" in app.dietary_restrictions: + allergy_apps.append(app) + else: + othernonveg_apps.append(app) + + restricted_apps = veg_apps + nobeef_apps + nopork_apps + allergy_apps + num_apps = len(restricted_apps) + len(othernonveg_apps) + + group_size = num_apps // NUM_GROUPS + restricted_percent = len(restricted_apps) / num_apps + restricted_target = restricted_percent * RESTRICTED_FRONTLOAD_FACTOR + restricted_per_group = restricted_target * group_size + + groups = [[] for _ in range(NUM_GROUPS)] + group_restricted_count = [0] * NUM_GROUPS + + # Assign restricted applicants + for i in range(NUM_GROUPS): + groups[i] = restricted_apps[:int(restricted_per_group)] + restricted_apps = restricted_apps[int(restricted_per_group):] + group_restricted_count[i] = len(groups[i]) + + # Assign unrestricted applicants + for i in range(NUM_GROUPS): + groups[i] += othernonveg_apps[:group_size - group_restricted_count[i]] + othernonveg_apps = othernonveg_apps[group_size - group_restricted_count[i]:] + groups[-1] += othernonveg_apps + + # Update database with meal groups + with transaction.atomic(): + for i, group in enumerate(groups): + group_letter = chr(65 + i) + for app in group: + app.meal_group = group_letter + app.save() + + return {f"Group {chr(65 + i)}": len(group) for i, group in enumerate(groups)} diff --git a/hiss/application/models.py b/hiss/application/models.py index 9e2144c8..b5ad624b 100644 --- a/hiss/application/models.py +++ b/hiss/application/models.py @@ -405,7 +405,92 @@ class Application(models.Model): user = models.ForeignKey("user.User", on_delete=models.CASCADE, null=False) status = models.CharField( choices=STATUS_OPTIONS, max_length=1, default=STATUS_PENDING - ) + ) + + def get_next_meal_group(self): + """ + Determines the next meal group considering frontloading for restricted groups + using the RESTRICTED_FRONTLOAD_FACTOR. + """ + RESTRICTED_FRONTLOAD_FACTOR = 1.3 + + meal_groups = ['A', 'B', 'C', 'D'] + group_distribution = {group: 0 for group in meal_groups} + restricted_distribution = {group: 0 for group in meal_groups} + total_confirmed = Application.objects.filter(status='C').exclude(meal_group__isnull=True) + + for group in meal_groups: + group_distribution[group] = total_confirmed.filter(meal_group=group).count() + restricted_distribution[group] = total_confirmed.filter( + meal_group=group, + dietary_restrictions__icontains="Vegetarian" + ).count() + total_confirmed.filter( + meal_group=group, + dietary_restrictions__icontains="No-Beef" + ).count() + total_confirmed.filter( + meal_group=group, + dietary_restrictions__icontains="No-Pork" + ).count() + total_confirmed.filter( + meal_group=group, + dietary_restrictions__icontains="Food-Allergy" + ).count() + + total_apps = sum(group_distribution.values()) + total_restricted = sum(restricted_distribution.values()) + if total_apps == 0: + return meal_groups[0] + + base_restricted_percent = total_restricted / total_apps if total_apps else 0 + target_restricted_percent = { + group: base_restricted_percent * (RESTRICTED_FRONTLOAD_FACTOR if i < len(meal_groups) // 2 else 1.0) + for i, group in enumerate(meal_groups) + } + target_restricted_count = { + group: target_restricted_percent[group] * group_distribution[group] if group_distribution[group] > 0 else 0 + for group in meal_groups + } + + restricted_gap = { + group: target_restricted_count[group] - restricted_distribution[group] + for group in meal_groups + } + prioritized_group = max(restricted_gap, key=restricted_gap.get) + + last_assigned_group = ( + total_confirmed.order_by('-datetime_submitted') + .values_list('meal_group', flat=True) + .first() + ) + if last_assigned_group: + next_index = (meal_groups.index(last_assigned_group) + 1) % len(meal_groups) + else: + next_index = 0 + + # use frontloaded group if it aligns with round-robin or is significantly better + if prioritized_group == meal_groups[next_index]: + return meal_groups[next_index] + elif restricted_gap[prioritized_group] > restricted_gap[meal_groups[next_index]]: + return prioritized_group + else: + return meal_groups[next_index] + + def assign_meal_group(self): + """ + Assigns a meal group based on the current status and dietary restrictions. + """ + if self.status == 'C': # Confirmed + self.meal_group = self.get_next_meal_group() + elif self.status == 'E': # Waitlisted + self.meal_group = 'E' + else: + self.meal_group = None + + def save(self, *args, **kwargs): + """ + Overrides save to ensure meal group assignment logic is applied. + """ + self.assign_meal_group() + super().save(*args, **kwargs) # ABOUT YOU first_name = models.CharField( diff --git a/hiss/application/views.py b/hiss/application/views.py index 128959de..88a116c7 100644 --- a/hiss/application/views.py +++ b/hiss/application/views.py @@ -6,7 +6,7 @@ from django.shortcuts import redirect from django.urls import reverse_lazy from django.views import generic - +from django.db import transaction from application.emails import send_confirmation_email, send_creation_email from application.forms import ApplicationModelForm from application.models import ( @@ -103,9 +103,10 @@ def post(self, request: HttpRequest, *args, **kwargs): raise PermissionDenied( "You can't confirm your application if it hasn't been approved." ) - app.status = STATUS_CONFIRMED - app.save() - send_confirmation_email(app) + with transaction.atomic(): + app.status = STATUS_CONFIRMED + app.save() + send_confirmation_email(app) return redirect(reverse_lazy("status")) diff --git a/hiss/customauth/views.py b/hiss/customauth/views.py index 12b67b69..34e3a6ec 100644 --- a/hiss/customauth/views.py +++ b/hiss/customauth/views.py @@ -7,7 +7,7 @@ from django.http import HttpResponse from django.shortcuts import redirect, render, get_object_or_404 from django.urls import reverse_lazy -from django.utils.encoding import force_bytes, force_text +from django.utils.encoding import force_bytes, force_str from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode from django.views import generic @@ -26,7 +26,7 @@ def send_confirmation_email(curr_domain: RequestSite, user: User) -> None: "event_name": settings.EVENT_NAME, "organizer_name": settings.ORGANIZER_NAME, } - user.send_html_email(template_name, context, subject) + user.send_html_email.delay(template_name, context, subject) # Create your views here. @@ -58,7 +58,7 @@ class ActivateView(views.View): def get(self, request, *_args, **kwargs): user = None try: - uid = force_text(urlsafe_base64_decode(kwargs["uidb64"])) + uid = force_str(urlsafe_base64_decode(kwargs["uidb64"])) user = get_user_model().objects.get(id=int(uid)) except ( TypeError, diff --git a/hiss/hiss/__init__.py b/hiss/hiss/__init__.py index e69de29b..e0b971ee 100644 --- a/hiss/hiss/__init__.py +++ b/hiss/hiss/__init__.py @@ -0,0 +1,4 @@ +from __future__ import absolute_import, unicode_literals +from .celery import app as celery_app + +__all__ = ('celery_app',) \ No newline at end of file diff --git a/hiss/hiss/celery.py b/hiss/hiss/celery.py new file mode 100644 index 00000000..56a45d05 --- /dev/null +++ b/hiss/hiss/celery.py @@ -0,0 +1,22 @@ +from __future__ import absolute_import, unicode_literals +import os +from celery import Celery + +# Set the default Django settings module for the 'celery' program. +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hiss.settings.dev') + +app = Celery('hiss') + +# Using a string here so the worker doesn't have to serialize the object to child processes. +app.config_from_object('django.conf:settings', namespace='CELERY') + +# Load task modules from all registered Django app configs. +app.autodiscover_tasks() + +# Celery configuration for Redis as the broker and result backend +app.conf.broker_url = os.environ.get('REDIS_URL', 'redis://localhost:6379/0') # Redis as broker +app.conf.result_backend = os.environ.get('REDIS_URL', 'redis://localhost:6379/0') # Redis as result backend + +@app.task(bind=True) +def debug_task(self): + print(f'Request: {self.request!r}') diff --git a/hiss/hiss/settings/base.py b/hiss/hiss/settings/base.py index 61cc89fe..cec92994 100644 --- a/hiss/hiss/settings/base.py +++ b/hiss/hiss/settings/base.py @@ -116,7 +116,7 @@ AWS_REGION = "us-east-2" AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID") AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY") -AWS_S3_BUCKET_NAME = "2024-hh-resumes" +AWS_S3_BUCKET_NAME = "2025-th-resumes" AWS_S3_KEY_PREFIX = "prod" STATIC_URL = "/" + BASE_PATHNAME + "static/" diff --git a/hiss/hiss/settings/customization.py b/hiss/hiss/settings/customization.py index 188bebf3..13da5c92 100644 --- a/hiss/hiss/settings/customization.py +++ b/hiss/hiss/settings/customization.py @@ -1,12 +1,13 @@ from django.utils import timezone MAX_YEARS_ADMISSION = 6 -EVENT_NAME = "HowdyHack" -EVENT_YEAR = "2024" +EVENT_NAME = "TAMUhack" +EVENT_YEAR = "2025" ORGANIZER_NAME = "TAMUhack" ORGANIZER_EMAIL = "hello@tamuhack.com" -EVENT_START_DATETIME = timezone.datetime(2024, 9, 28, hour=9, minute=0, second=0) -EVENT_END_DATETIME = timezone.datetime(2024, 9, 29, hour=12, minute=0, second=0) +EVENT_START_DATETIME = timezone.datetime(2025, 1, 25, hour=9, minute=0, second=0) +EVENT_END_DATETIME = timezone.datetime(2025, 1, 26, hour=12, minute=0, second=0) +EVENT_DATE_TEXT = "January 25-26, 2025" MAX_MEMBERS_PER_TEAM = 4 APPLE_WALLET_S3_BUCKET_URL = "https://hh24-apple-wallet-passes.s3.amazonaws.com" diff --git a/hiss/hiss/settings/dev.py b/hiss/hiss/settings/dev.py index 2d3fd32a..829bfe3c 100644 --- a/hiss/hiss/settings/dev.py +++ b/hiss/hiss/settings/dev.py @@ -4,6 +4,8 @@ # noinspection PyUnresolvedReferences from .customization import * +import os + SECRET_KEY = "development" DEBUG = True # Database @@ -25,3 +27,11 @@ MEDIA_ROOT = "resumes" AWS_S3_KEY_PREFIX = "dev-resumes" + +CELERY_ACCEPT_CONTENT = ['json'] +CELERY_TASK_SERIALIZER = 'json' +CELERY_RESULT_SERIALIZER = 'json' +CELERY_TIMEZONE = 'UTC' +CELERY_ENABLE_UTC = True +CELERY_BROKER_URL = os.environ.get('REDIS_URL') +CELERY_RESULT_BACKEND = os.environ.get('REDIS_URL') \ No newline at end of file diff --git a/hiss/requirements.txt b/hiss/requirements.txt index ed3d495d..650e2c59 100644 --- a/hiss/requirements.txt +++ b/hiss/requirements.txt @@ -54,3 +54,5 @@ typed-ast==1.4.0 urllib3==1.25.6 whitenoise==5.2.0 wrapt==1.11.2 +celery==4.4.7 +redis \ No newline at end of file diff --git a/hiss/shared/admin_functions.py b/hiss/shared/admin_functions.py index 85f1de6e..34c4b6c7 100644 --- a/hiss/shared/admin_functions.py +++ b/hiss/shared/admin_functions.py @@ -1,46 +1,19 @@ +from celery import shared_task from django.core.mail import get_connection, EmailMultiAlternatives -import threading -#create separate Threading class for mass emails -class MassEmailThread(threading.Thread): - def __init__(self, subject, text_content, html_content, from_email, recipient_list, connection): - threading.Thread.__init__(self) - self.subject = subject - self.text_content = text_content - self.html_content = html_content - self.from_email = from_email - self.recipient_list = recipient_list - self.connection = connection - self.result = 0 - - def run(self): - email = EmailMultiAlternatives(self.subject, self.text_content, self.from_email, self.recipient_list) - email.attach_alternative(self.html_content, "text/html") - try: - self.result = email.send(fail_silently=False, connection=self.connection) - except Exception as e: - print("Error sending email: ", e) - self.result = 0 - -def send_mass_html_mail(datatuple, fail_silently=False, user=None, password=None, connection=None): +@shared_task +def send_mass_html_mail_task(datatuple, fail_silently=False, user=None, password=None): """ - Sends each message in datatuple (subject, text_content, html_content, from_email, recipient_list). - Returns the number of emails sent. + Celery task to send multiple HTML emails given a datatuple of + (subject, text_content, html_content, from_email, recipient_list). """ - connection = connection or get_connection( + connection = get_connection( username=user, password=password, fail_silently=fail_silently ) - - threads = [] - + messages = [] for subject, text, html, from_email, recipient in datatuple: - email_thread = MassEmailThread(subject, text, html, from_email, recipient, connection) - email_thread.start() - threads.append(email_thread) - - for thread in threads: - thread.join() - - total_sent = sum(thread.result for thread in threads if thread.result) - - return total_sent \ No newline at end of file + message = EmailMultiAlternatives(subject, text, from_email, recipient) + message.attach_alternative(html, "text/html") + messages.append(message) + + return connection.send_messages(messages) diff --git a/hiss/static/style.css b/hiss/static/style.css index fcdbf4f7..d11804e2 100644 --- a/hiss/static/style.css +++ b/hiss/static/style.css @@ -1,3 +1,5 @@ +@import url('//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css'); + html, body { overflow-x: hidden; } @@ -725,8 +727,6 @@ button { color: white; } -@import url('//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css'); - .errorlist > li { color: #721c24; font-size: 14px; diff --git a/hiss/templates/400.html b/hiss/templates/400.html index 11c530e9..0f9eda64 100644 --- a/hiss/templates/400.html +++ b/hiss/templates/400.html @@ -6,7 +6,7 @@

400

Bad Request

Whoops! The request you made isn't valid.

-

If you think this is a mistake, email us at {{ organizer_email }}

+

If you think this is a mistake, email us at {{ ORGANIZER_EMAIL }}

Take Me Back diff --git a/hiss/templates/403.html b/hiss/templates/403.html index fef716ae..ab3ffbef 100644 --- a/hiss/templates/403.html +++ b/hiss/templates/403.html @@ -6,7 +6,7 @@

403

Page Forbidden

Whoops! It looks like you do not have access to this page.

-

If you think this is a mistake, email us at {{ organizer_email }}

+

If you think this is a mistake, email us at {{ ORGANIZER_EMAIL }}

Take Me Back diff --git a/hiss/templates/404.html b/hiss/templates/404.html index 34d929cd..e45a4b48 100644 --- a/hiss/templates/404.html +++ b/hiss/templates/404.html @@ -6,7 +6,7 @@

404

Page Not Found

Whoops! It looks like the page you are looking for doesn't exist.

-

If you think this is a mistake, email us at {{ organizer_email }}

+

If you think this is a mistake, email us at {{ ORGANIZER_EMAIL }}

Take Me Back diff --git a/hiss/templates/500.html b/hiss/templates/500.html index 98d74379..56f90f39 100644 --- a/hiss/templates/500.html +++ b/hiss/templates/500.html @@ -6,7 +6,7 @@

500

Internal Server Error

Whoops! There's an issue on our end. We're working to fix it!

-

If you think this is a mistake, email us at {{ organizer_email }}

+

If you think this is a mistake, email us at {{ ORGANIZER_EMAIL }}

Take Me Back diff --git a/hiss/templates/application/emails/approved.html b/hiss/templates/application/emails/approved.html index b4431719..adff81cf 100644 --- a/hiss/templates/application/emails/approved.html +++ b/hiss/templates/application/emails/approved.html @@ -53,9 +53,9 @@

margin-bottom: 20px; " > - You have been accepted to {{event_name}} {{event_year}}! We were impressed by your application and we’re excited to see you + You have been accepted to {{EVENT_NAME}} {{EVENT_YEAR}}! We were impressed by your application and we're excited to see you September 28-29, 2024. + >{{ event_date_text }}.

- The {{ organizer_name }} Team + The {{ ORGANIZER_NAME }} Team diff --git a/hiss/templates/application/emails/confirmed-hh.html b/hiss/templates/application/emails/confirmed-hh.html index 8ab56a1e..bacb2a75 100644 --- a/hiss/templates/application/emails/confirmed-hh.html +++ b/hiss/templates/application/emails/confirmed-hh.html @@ -515,7 +515,7 @@

Special Accommodations
-If you require any special accommodations during HowdyHack, please email us at hello@tamuhack.com and we’d be happy to work with you.
+If you require any special accommodations during {{event_name}}, please email us at hello@tamuhack.com and we’d be happy to work with you.


We can't wait to meet all you! Get ready for a fun-filled weekend!
diff --git a/hiss/templates/application/emails/confirmed-waitlist.html b/hiss/templates/application/emails/confirmed-waitlist.html index 5a2bc301..9c3a8fd2 100644 --- a/hiss/templates/application/emails/confirmed-waitlist.html +++ b/hiss/templates/application/emails/confirmed-waitlist.html @@ -473,7 +473,7 @@

Howdy, hackers!

-You are waitlisted for HowdyHack 2024 on September 28 - 29. We will have a separate line for students on the waitlist. After 10AM, we will admit people from the waitlist line until the MSC capacity has been reached. Please come early to ensure you have a higher chance of being admitted to the event as it is first come, first serve. +You are waitlisted for {{event_name}} on {{ event_date_text }}. We will have a separate line for students on the waitlist. After 10AM, we will admit people from the waitlist line until the MSC capacity has been reached. Please come early to ensure you have a higher chance of being admitted to the event as it is first come, first serve.

Check-in
diff --git a/hiss/templates/application/emails/confirmed.html b/hiss/templates/application/emails/confirmed.html index 8c8c28b8..b3f11358 100644 --- a/hiss/templates/application/emails/confirmed.html +++ b/hiss/templates/application/emails/confirmed.html @@ -438,7 +438,7 @@ -

Get ready for HowdyHack 2024!

+

Get ready for TAMUhack 2025!

Important Reminders

@@ -465,6 +465,7 @@

@@ -475,7 +476,7 @@

Howdy, hackers!

-We're so excited to see you at HowdyHack 2024 on September 28 - 29. +We're so excited to see you at TAMUhack 2025 on {{ event_date_text }}.

Check-in
diff --git a/hiss/templates/application/emails/rejected.html b/hiss/templates/application/emails/rejected.html index e35f2e5d..35a02846 100644 --- a/hiss/templates/application/emails/rejected.html +++ b/hiss/templates/application/emails/rejected.html @@ -43,7 +43,7 @@

Dear {{ first_n font-weight: normal; color: #777777; margin-bottom: 20px;"> - Thank you so much for applying to {{ event_name }} {{ event_year }}. We really appreciate the time you took to share your + Thank you so much for applying to {{ EVENT_NAME }} {{ EVENT_YEAR }}. We really appreciate the time you took to share your interests and skills with us. Unfortunately, due to space limitations of the MSC, we are only able to accept a limited number hackers, and we are unable to grant you an acceptance. If you're still interested in hacking with us, our spring hackathon, TAMUhack, has a bigger capacity and more awesome prizes to win! We hope to see you there!

@@ -91,7 +91,7 @@

Dear {{ first_n ======= - The {{ organizer_name }} Team + The {{ ORGANIZER_NAME }} Team >>>>>>> origin diff --git a/hiss/templates/status/status.html b/hiss/templates/status/status.html index 412afbfb..a5c135d6 100644 --- a/hiss/templates/status/status.html +++ b/hiss/templates/status/status.html @@ -107,7 +107,7 @@
ACCEPTED

- Please RSVP below by September 27th, 2024, at 11:59PM or you risk losing your spot. + Please RSVP below by January 24th, 2025, at 11:59PM or you risk losing your spot.

diff --git a/hiss/user/models.py b/hiss/user/models.py index c0a040fd..a1d0aefd 100644 --- a/hiss/user/models.py +++ b/hiss/user/models.py @@ -7,7 +7,11 @@ from django.template.loader import render_to_string from django.utils import html from rest_framework.authtoken.models import Token -import threading + +from celery import shared_task +from django.core.mail import send_mail +from django.template.loader import render_to_string +from django.utils.html import strip_tags class EmailUserManager(auth_models.UserManager): """ @@ -36,23 +40,6 @@ def create_superuser( extra_fields.setdefault("is_active", True) return self._create_user(email, password, **extra_fields) -class EmailThread(threading.Thread): - def __init__(self, subject, plain_message, recipient_email, html_message): - self.subject = subject - self.plain_message = plain_message - self.recipient_email = recipient_email - self.html_message = html_message - threading.Thread.__init__(self) - - def run(self): - mail.send_mail( - subject=self.subject, - message=self.plain_message, - from_email= settings.ORGANIZER_EMAIL, - recipient_list=[self.recipient_email], - html_message=self.html_message, - ) - class User(auth_models.AbstractUser): """ A representation of a user within the registration system. Users are uniquely identified by their email, @@ -87,12 +74,18 @@ class User(auth_models.AbstractUser): USERNAME_FIELD = "email" REQUIRED_FIELDS = [] - def send_html_email(self, template_name, context, subject): - """Send an HTML email to the user.""" + @shared_task + def send_html_email(template_name, context, subject, recipient_email): + """Celery task to send an HTML email.""" html_msg = render_to_string(template_name, context) - plain_msg = html.strip_tags(html_msg) - email_thread = EmailThread(subject, plain_msg, self.email, html_msg) - email_thread.start() + plain_msg = strip_tags(html_msg) + send_mail( + subject=subject, + message=plain_msg, + from_email=settings.ORGANIZER_EMAIL, + recipient_list=[recipient_email], + html_message=html_msg, + ) @receiver(post_save, sender=settings.AUTH_USER_MODEL)