Howdy, hackers!
-You are waitlisted for HowdyHack 2024 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.
+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 18961f93..80cfb7e8 100644
--- a/hiss/templates/application/emails/confirmed.html
+++ b/hiss/templates/application/emails/confirmed.html
@@ -436,7 +436,7 @@
- Get ready for HowdyHack 2024!
+ Get ready for TAMUhack 2025!
Important Reminders
@@ -474,7 +474,7 @@ Howdy, hackers!
-We're so excited to see you at HowdyHack 2024 on {{ event_date_text }}.
+We're so excited to see you at TAMUhack 2025 on {{ event_date_text }}.
Check-in
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.
From cdeeea615265db8f87ce3c0f389b6cf8cf943cd9 Mon Sep 17 00:00:00 2001
From: skandrigi
Date: Mon, 25 Nov 2024 19:16:19 -0600
Subject: [PATCH 7/8] update sandeep-dev
---
dump.rdb | Bin 0 -> 88 bytes
hiss/Procfile | 2 +
hiss/application/admin.py | 4 +-
hiss/application/emails.py | 102 +++++++++++--------------
hiss/application/fixtures/schools.json | 4 +-
hiss/application/meal_groups.py | 59 ++++++++++++++
hiss/application/models.py | 87 ++++++++++++++++++++-
hiss/application/views.py | 9 ++-
hiss/customauth/views.py | 6 +-
hiss/hiss/__init__.py | 4 +
hiss/hiss/celery.py | 22 ++++++
hiss/hiss/settings/dev.py | 10 +++
hiss/requirements.txt | 2 +
hiss/shared/admin_functions.py | 51 +++----------
hiss/user/models.py | 39 ++++------
15 files changed, 270 insertions(+), 131 deletions(-)
create mode 100644 dump.rdb
create mode 100644 hiss/Procfile
create mode 100644 hiss/application/meal_groups.py
create mode 100644 hiss/hiss/celery.py
diff --git a/dump.rdb b/dump.rdb
new file mode 100644
index 0000000000000000000000000000000000000000..0958dab0b78a552c5daedea626ad86c5c7011362
GIT binary patch
literal 88
zcmWG?b@2=~FfcUy#aWb^l3A=H&uT<_tS!vj4nl1OUE5BkBME
literal 0
HcmV?d00001
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..7876a426 100644
--- a/hiss/application/admin.py
+++ b/hiss/application/admin.py
@@ -102,7 +102,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 +115,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..8f1189c7 100644
--- a/hiss/application/emails.py
+++ b/hiss/application/emails.py
@@ -1,45 +1,15 @@
+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
-
+from django.conf import settings
#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()
-
def send_creation_email(app: Application) -> None:
"""
Sends an email to the user informing them of their newly-created app.
@@ -59,37 +29,55 @@ 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)
+ try:
+ app = Application.objects.get(id=app_id)
- qr_content = json.dumps(
- {
+ subject = f"HowdyHack: Important Day-of Information!"
+ email_template = "application/emails/confirmed.html"
+
+ if app.status == "E":
+ subject = f"HowdyHack 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),
+ "meal_group": app.meal_group,
}
- )
+ html_msg = render_to_string(email_template, context)
+ msg = html.strip_tags(html_msg)
+ email = mail.EmailMultiAlternatives(
+ subject, msg, from_email=None, to=[app.user.email]
+ )
+ email.attach_alternative(html_msg, "text/html")
- email_thread = EmailQRThread(subject, plain_msg, html_msg, app.user.email, qr_content)
- email_thread.start()
+ 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(), "text/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 df165475..bb8b9840 100644
--- a/hiss/application/models.py
+++ b/hiss/application/models.py
@@ -401,7 +401,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/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/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)
From fc6265ca3035da5a6abd89dd5c550b271db563a5 Mon Sep 17 00:00:00 2001
From: Sandeep Kandrigi <108851203+skandrigi@users.noreply.github.com>
Date: Thu, 28 Nov 2024 17:30:21 -0600
Subject: [PATCH 8/8] update MIME type to image/png instead of text/png, png
not a subcategory of text
---
hiss/application/emails.py | 48 ++++++++++++++++++++++----------------
1 file changed, 28 insertions(+), 20 deletions(-)
diff --git a/hiss/application/emails.py b/hiss/application/emails.py
index 50af5e50..052c8dcb 100644
--- a/hiss/application/emails.py
+++ b/hiss/application/emails.py
@@ -8,7 +8,7 @@
from application.models import Application
from application.apple_wallet import get_apple_wallet_pass_url
from django.conf import settings
-#create separate threading class for confirmation email since it has a QR code
+from django.core.mail import EmailMultiAlternatives
def send_creation_email(app: Application) -> None:
"""
@@ -39,25 +39,32 @@ def send_confirmation_email(app_id: int) -> None:
:type app_id: int
:return: None
"""
- 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"HowdyHack Waitlist: Important Day-of Information!"
- email_template = "application/emails/confirmed-waitlist.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),
- "event_date_text": settings.EVENT_DATE_TEXT,
- }
- html_msg = render_to_string(email_template, context)
- plain_msg = html.strip_tags(html_msg)
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,
+ "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,
+ }
+
+ 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,
@@ -66,10 +73,11 @@ def send_confirmation_email(app_id: int) -> None:
"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(), "text/png")
+ email.attach("code.png", qr_stream.getvalue(), "image/png")
print(f"sending confirmation email to {app.user.email}")
email.send()
except Exception as e:
|