diff --git a/Dockerfile b/Dockerfile index 12d97acc..48b3ab91 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,4 +8,4 @@ RUN python3 -m pip install -r requirements.txt RUN python3 manage.py collectstatic --no-input -CMD gunicorn -b :$PORT hiss.wsgi:application --capture-output +CMD gunicorn -b :$PORT hiss.wsgi:application --log-file=- --capture-output --log-level=debug diff --git a/hiss/application/admin.py b/hiss/application/admin.py index 653f124f..ebca4f0a 100644 --- a/hiss/application/admin.py +++ b/hiss/application/admin.py @@ -180,7 +180,7 @@ class ApplicationAdmin(admin.ModelAdmin): readonly_fields = [ "datetime_submitted", "user", - "is_adult", + # "is_adult", "gender", "race", "major", @@ -203,13 +203,15 @@ class ApplicationAdmin(admin.ModelAdmin): ("status", ChoiceDropdownFilter), ("classification", ChoiceDropdownFilter), ("gender", ChoiceDropdownFilter), + ("major", ChoiceDropdownFilter), ("grad_year", ChoiceDropdownFilter), ("num_hackathons_attended", ChoiceDropdownFilter), - ("wares", ChoiceDropdownFilter), + # ("wares", ChoiceDropdownFilter), # ("technology_experience", ChoiceDropdownFilter), # ("dietary_restrictions", ChoiceDropdownFilter), ("shirt_size", ChoiceDropdownFilter), ("datetime_submitted", DateRangeFilter), + ("accessibility_requirements", ChoiceDropdownFilter), RaceFilter, ) list_display = ( @@ -232,6 +234,7 @@ class ApplicationAdmin(admin.ModelAdmin): "fields": [ "first_name", "last_name", + "tamu_email", "age", "phone_number", "country", @@ -250,6 +253,7 @@ class ApplicationAdmin(admin.ModelAdmin): "school", "school_other", "major", + "major_other", "classification", "gender", "gender_other", @@ -266,9 +270,10 @@ class ApplicationAdmin(admin.ModelAdmin): "Logistical Information", { "fields": [ - "wares", + # "wares", "shirt_size", "dietary_restrictions", + "meal_group", "additional_accommodations", # "address", "emergency_contact_name", @@ -279,7 +284,7 @@ class ApplicationAdmin(admin.ModelAdmin): }, ), ("Confirmation Deadline", {"fields": ["confirmation_deadline"]}), - ("Miscellaneous", {"fields": ["notes"]}), + ("Miscellaneous", {"fields": ["notes", "is_adult", "accessibility_requirements"]}), ] formfield_overrides = { AddressField: {"widget": AddressWidget(attrs={"style": "width: 300px;"})} diff --git a/hiss/application/emails.py b/hiss/application/emails.py index 4ec31d79..92ddf1df 100644 --- a/hiss/application/emails.py +++ b/hiss/application/emails.py @@ -10,6 +10,35 @@ 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() def send_creation_email(app: Application) -> None: """ @@ -27,8 +56,11 @@ def send_creation_email(app: Application) -> None: "organizer_email": settings.ORGANIZER_EMAIL, } - app.user.send_html_email(template_name, context, subject) + # send_html_email is threaded from the User class + # see user/models.py + app.user.send_html_email(template_name, context, subject) + def send_confirmation_email(app: Application) -> None: """ @@ -37,7 +69,7 @@ def send_confirmation_email(app: Application) -> None: :type app: Application :return: None """ - subject = f"TAMUhack Waitlist: Important Day-Of Information" + subject = f"HowdyHack Waitlist: Important Day-Of Information" email_template = "application/emails/confirmed.html" context = { "first_name": app.first_name, @@ -48,11 +80,7 @@ def send_confirmation_email(app: Application) -> None: "apple_wallet_url": get_apple_wallet_pass_url(app.user.email), } 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") + plain_msg = html.strip_tags(html_msg) qr_content = json.dumps( { @@ -62,9 +90,6 @@ def send_confirmation_email(app: Application) -> 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") - print(f"sending confirmation email to {app.user.email}") - email.send() + + email_thread = EmailQRThread(subject, plain_msg, html_msg, app.user.email, qr_content) + email_thread.start() diff --git a/hiss/application/fixtures/schools.json b/hiss/application/fixtures/schools.json index 585a17f8..8a5fad80 100644 --- a/hiss/application/fixtures/schools.json +++ b/hiss/application/fixtures/schools.json @@ -10982,6 +10982,13 @@ "name": "Texas A&M University - Kingsville" } }, + { + "model": "application.school", + "pk": 2075, + "fields": { + "name": "Texas A&M University - San Antonio" + } + }, { "model": "application.school", "pk": 1570, diff --git a/hiss/application/forms.py b/hiss/application/forms.py index 56f9b4bc..a5a5d1c7 100644 --- a/hiss/application/forms.py +++ b/hiss/application/forms.py @@ -23,6 +23,10 @@ class ApplicationModelForm(forms.ModelForm): label='If you chose "Prefer to self-describe", please elaborate.', required=False, ) + major_other = forms.CharField( + label='If you chose "Other", please specify your major.', + required=False, + ) school = forms.ModelChoiceField( School.objects.all(), label="What school do you go to?", @@ -31,6 +35,10 @@ class ApplicationModelForm(forms.ModelForm): label='If you chose "Other", please enter your school\'s name here.', required=False, ) + tamu_email = forms.CharField( + label="TAMU Email if you are a Texas A&M student", + required=False, + ) # Languages PYTHON = "Python" @@ -178,7 +186,7 @@ class ApplicationModelForm(forms.ModelForm): (TENSORFLOW, "Tensorflow"), (PYTORCH, "PyTorch"), (FLUTTER, "Flutter"), - (REACT_NATIVE, "React Native") + (REACT_NATIVE, "React Native"), ) # SKILLS technology_experience = forms.MultipleChoiceField( @@ -207,9 +215,9 @@ class ApplicationModelForm(forms.ModelForm): (KOSHER, "Kosher"), (GLUTEN_FREE, "Gluten-Free"), (FOOD_ALLERGY, "Food Allergy"), - (OTHER_DIETARY_RESTRICTION, "Other") + (OTHER_DIETARY_RESTRICTION, "Other"), ) - + dietary_restrictions = forms.MultipleChoiceField( label="Do you have any dietary restrictions?", help_text="Select all that apply", @@ -234,6 +242,13 @@ def __init__(self, *args, **kwargs): } super().__init__(*args, **kwargs) + + photo_agreement = "Do you grant permission for TAMUhack to use your name, likeness, voice, and any photographs, video recordings, or audio recordings taken during the event 'TAMUhack 2025' for promotional and media purposes, including but not limited to publications, websites, social media, and press releases?" + accessibilities = "Please check this box if you require any accommodations to ensure accessibility during this event. Our team will follow up to discuss your needs." + + self.fields["agree_to_photos"].label = mark_safe(photo_agreement) + self.fields["accessibility_requirements"].label = mark_safe(accessibilities) + self.fields["agree_to_coc"].label = mark_safe( 'I agree to the MLH Code of Conduct' ) @@ -247,10 +262,7 @@ def __init__(self, *args, **kwargs): ' and the MLH Privacy Policy' ) - mlh_newsletter = ( - "I authorize MLH to send me occasional emails about relevant events, career opportunities, and community announcements." - ) - + mlh_newsletter = "I authorize MLH to send me occasional emails about relevant events, career opportunities, and community announcements." self.fields["agree_to_mlh_stuff"].label = mark_safe(mlh_stuff) self.fields["signup_to_mlh_newsletter"].label = mark_safe(mlh_newsletter) @@ -287,6 +299,14 @@ def clean(self): "Please fill out this field with the appropriate information." ) self.add_error("race_other", msg) + major = self.cleaned_data.get("major") + if major: + major_other = self.cleaned_data.get("major_other") + if major == "Other" and not major_other: + msg = forms.ValidationError( + 'Please fill out this field or choose "Other".' + ) + self.add_error("major_other", msg) return self.cleaned_data class Meta: @@ -296,7 +316,10 @@ class Meta: "agree_to_coc": forms.CheckboxInput, "agree_to_mlh_stuff": forms.CheckboxInput, "signup_to_mlh_newsletter": forms.CheckboxInput, + "agree_to_photos": forms.CheckboxInput, + "accessibility_requirements": forms.CheckboxInput, "travel_reimbursement": forms.CheckboxInput, + "tamu_email": forms.EmailInput(attrs={"placeholder": "netid@tamu.edu"}), "extra_links": forms.TextInput( attrs={ "placeholder": "ex. GitHub, Devpost, personal website, LinkedIn, etc." @@ -312,7 +335,9 @@ class Meta: "country", "school", "school_other", + "tamu_email", "major", + "major_other", "classification", "grad_year", "level_of_study", @@ -322,7 +347,7 @@ class Meta: "race_other", "num_hackathons_attended", "technology_experience", - "wares", + # "wares", "dietary_restrictions", "has_team", "wants_team", @@ -339,6 +364,8 @@ class Meta: "emergency_contact_phone", "emergency_contact_email", "notes", + "agree_to_photos", + "accessibility_requirements", "agree_to_coc", "agree_to_mlh_stuff", "signup_to_mlh_newsletter", diff --git a/hiss/application/migrations/0021_auto_20240620_1047.py b/hiss/application/migrations/0021_auto_20240620_1047.py new file mode 100644 index 00000000..0afd1d18 --- /dev/null +++ b/hiss/application/migrations/0021_auto_20240620_1047.py @@ -0,0 +1,38 @@ +# Generated by Django 2.2.13 on 2024-06-20 15:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('application', '0020_auto_20231214_2259'), + ] + + operations = [ + migrations.AddField( + model_name='application', + name='major_other', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Other'), + ), + migrations.AddField( + model_name='application', + name='tamu_email', + field=models.EmailField(blank=True, max_length=75, null=True, verbose_name='TAMU Email if you are a Texas A&M student'), + ), + migrations.AlterField( + model_name='application', + name='grad_year', + field=models.IntegerField(choices=[(2024, 2024), (2025, 2025), (2026, 2026), (2027, 2027), (2028, 2028), (2029, 2029)], verbose_name='What is your anticipated graduation year?'), + ), + migrations.AlterField( + model_name='application', + name='major', + field=models.CharField(choices=[('Computer Science', 'Computer Science'), ('Software Engineering', 'Software Engineering'), ('Computer Engineering', 'Computer Engineering'), ('Electrical Engineering', 'Electrical Engineering'), ('Information Technology', 'Information Technology'), ('Data Science', 'Data Science'), ('major_other', 'Other')], default='NA', max_length=100, verbose_name="What's your major?"), + ), + migrations.AlterField( + model_name='application', + name='shirt_size', + field=models.CharField(choices=[('XXS', 'XXS'), ('XS', 'XS'), ('S', 'S'), ('M', 'M'), ('L', 'L'), ('XL', 'XL'), ('XXL', 'XXL')], max_length=4, verbose_name='What size shirt do you wear?'), + ), + ] diff --git a/hiss/application/migrations/0022_auto_20240627_1606.py b/hiss/application/migrations/0022_auto_20240627_1606.py new file mode 100644 index 00000000..ff5ebb6a --- /dev/null +++ b/hiss/application/migrations/0022_auto_20240627_1606.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.13 on 2024-06-27 21:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('application', '0021_auto_20240620_1047'), + ] + + operations = [ + migrations.AlterField( + model_name='application', + name='major', + field=models.CharField(choices=[('Computer Science', 'Computer Science'), ('Software Engineering', 'Software Engineering'), ('Computer Engineering', 'Computer Engineering'), ('Electrical Engineering', 'Electrical Engineering'), ('Information Technology', 'Information Technology'), ('Data Science', 'Data Science'), ('Other', 'Other')], default='NA', max_length=100, verbose_name="What's your major?"), + ), + ] diff --git a/hiss/application/migrations/0023_auto_20240810_1843.py b/hiss/application/migrations/0023_auto_20240810_1843.py new file mode 100644 index 00000000..e002b5b6 --- /dev/null +++ b/hiss/application/migrations/0023_auto_20240810_1843.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.13 on 2024-08-10 23:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('application', '0022_auto_20240627_1606'), + ] + + operations = [ + migrations.AlterField( + model_name='application', + name='wants_team', + field=models.CharField(choices=[('Friend', 'From a friend'), ('Tabling', 'Tabling outside Zachry'), ('Howdy Week', 'From Howdy Week'), ('Yard Sign', 'Yard sign'), ('Social Media', 'Social media'), ('Student Orgs', 'Though another student org'), ('TH Organizer', 'From a TAMUhack organizer'), ('ENGR Newsletter', 'From the TAMU Engineering Newsletter'), ('MLH', 'Major League Hacking (MLH)'), ('Attended Before', "I've attended TAMUhack before")], max_length=16, verbose_name='How did you hear about TAMUhack?'), + ), + ] diff --git a/hiss/application/migrations/0024_auto_20240823_1038.py b/hiss/application/migrations/0024_auto_20240823_1038.py new file mode 100644 index 00000000..a0d18300 --- /dev/null +++ b/hiss/application/migrations/0024_auto_20240823_1038.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.13 on 2024-08-23 15:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('application', '0023_auto_20240810_1843'), + ] + + operations = [ + migrations.AddField( + model_name='application', + name='meal_group', + field=models.CharField(blank=True, default=None, max_length=255, null=True), + ), + migrations.AlterField( + model_name='application', + name='status', + field=models.CharField(choices=[('P', 'Under Review'), ('R', 'Rejected'), ('A', 'Admitted'), ('C', 'Confirmed'), ('X', 'Declined'), ('I', 'Checked in'), ('E', 'Waitlisted (Expired, internally)')], default='P', max_length=1), + ), + ] diff --git a/hiss/application/migrations/0025_auto_20241024_1250.py b/hiss/application/migrations/0025_auto_20241024_1250.py new file mode 100644 index 00000000..4aa479d0 --- /dev/null +++ b/hiss/application/migrations/0025_auto_20241024_1250.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.13 on 2024-10-24 17:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('application', '0024_auto_20240823_1038'), + ] + + operations = [ + migrations.AlterField( + model_name='application', + name='major', + field=models.CharField(choices=[('Computer Science', 'Computer Science'), ('Computer Engineering', 'Computer Engineering'), ('Computing', 'Computing'), ('Electrical Engineering', 'Electrical Engineering'), ('Management Information Systems', 'Management Information Systems'), ('Data Science/Engineering', 'Data Science/Engineering'), ('General Engineering', 'General Engineering'), ('Biomedical Engineering', 'Biomedical Engineering'), ('Chemical Engineering', 'Chemical Engineering'), ('Civil Engineering', 'Civil Engineering'), ('Industrial Engineering', 'Industrial Engineering'), ('Mechanical Engineering', 'Mechanical Engineering'), ('Aerospace Engineering', 'Aerospace Engineering'), ('Electronic Systems Engineering Technology (ESET)', 'Electronic Systems Engineering Technology (ESET)'), ('Mathematics', 'Mathematics'), ('Physics', 'Physics'), ('Statistics', 'Statistics'), ('Biology', 'Biology'), ('Chemistry', 'Chemistry'), ('Other', 'Other')], default='NA', max_length=100, verbose_name="What's your major?"), + ), + migrations.AlterField( + model_name='application', + name='wants_team', + field=models.CharField(choices=[('Friend', 'From a friend'), ('Tabling', 'Tabling outside Zachry'), ('Howdy Week', 'From Howdy Week'), ('Yard Sign', 'Yard sign'), ('Social Media', 'Social media'), ('Student Orgs', 'Though another student org'), ('TH Organizer', 'From a TAMUhack organizer'), ('ENGR Newsletter', 'From the TAMU Engineering Newsletter'), ('MLH', 'Major League Hacking (MLH)'), ('Attended Before', "I've attended HowdyHack before")], max_length=16, verbose_name='How did you hear about HowdyHack?'), + ), + migrations.AlterField( + model_name='application', + name='wares', + field=models.CharField(blank=True, choices=[('SW', 'Software'), ('HW', 'Hardware')], default='NA', max_length=8, verbose_name='TAMUhack will be partnering with IEEE to offer a dedicated hardware track and prizes. Participants can choose to compete in this track or in the general software tracks. Would you like to compete in the software or hardware track'), + ), + ] diff --git a/hiss/application/migrations/0026_auto_20241119_1945.py b/hiss/application/migrations/0026_auto_20241119_1945.py new file mode 100644 index 00000000..406d451a --- /dev/null +++ b/hiss/application/migrations/0026_auto_20241119_1945.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.13 on 2024-11-20 01:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('application', '0025_auto_20241024_1250'), + ] + + operations = [ + migrations.AddField( + model_name='application', + name='accessibility_requirements', + field=models.BooleanField(blank=True, choices=[(True, 'Agree'), (False, 'Disagree')], default=None, null=True), + ), + migrations.AddField( + model_name='application', + name='agree_to_photos', + field=models.BooleanField(choices=[(True, 'Agree')], default=None, null=True), + ), + ] diff --git a/hiss/application/models.py b/hiss/application/models.py index 27bab959..9e2144c8 100644 --- a/hiss/application/models.py +++ b/hiss/application/models.py @@ -1,6 +1,7 @@ # pylint: disable=C0330 import uuid from typing import List, Optional, Tuple +import re from django.conf import settings from django.core import exceptions @@ -240,13 +241,6 @@ class DietaryRestriction(models.Model): # QUESTION2_TEXT = "What is the one thing you'd build if you had unlimited resources?" # QUESTION3_TEXT = "What's your hidden talent?" -WOMENS_XXS = "WXXS" -WOMENS_XS = "WXS" -WOMENS_S = "WS" -WOMENS_M = "WM" -WOMENS_L = "WL" -WOMENS_XL = "WXL" -WOMENS_XXL = "WXXL" UNISEX_XXS = "XXS" UNISEX_XS = "XS" UNISEX_S = "S" @@ -256,20 +250,13 @@ class DietaryRestriction(models.Model): UNISEX_XXL = "XXL" SHIRT_SIZES = [ - (WOMENS_XXS, "Women's XXS"), - (WOMENS_XS, "Women's XS"), - (WOMENS_S, "Women's S"), - (WOMENS_M, "Women's M"), - (WOMENS_L, "Women's L"), - (WOMENS_XL, "Women's XL"), - (WOMENS_XXL, "Women's XXL"), - (UNISEX_XXS, "Unisex XXS"), - (UNISEX_XS, "Unisex XS"), - (UNISEX_S, "Unisex S"), - (UNISEX_M, "Unisex M"), - (UNISEX_L, "Unisex L"), - (UNISEX_XL, "Unisex XL"), - (UNISEX_XXL, "Unisex XXL"), + (UNISEX_XXS, "XXS"), + (UNISEX_XS, "XS"), + (UNISEX_S, "S"), + (UNISEX_M, "M"), + (UNISEX_L, "L"), + (UNISEX_XL, "XL"), + (UNISEX_XXL, "XXL"), ] STATUS_PENDING = "P" @@ -300,7 +287,7 @@ class DietaryRestriction(models.Model): (STATUS_CONFIRMED, "Confirmed"), (STATUS_DECLINED, "Declined"), (STATUS_CHECKED_IN, "Checked in"), - (STATUS_EXPIRED, "Expired"), + (STATUS_EXPIRED, "Waitlisted (Expired, internally)"), ] HAS_TEAM = "HT" @@ -311,9 +298,54 @@ class DietaryRestriction(models.Model): (HAS_NO_TEAM, "I do not have a team"), ] +CS = "Computer Science" +CE = "Computer Engineering" +COMP = "Computing" +EE = "Electrical Engineering" +MIS = "Management Information Systems" +DS = "Data Science/Engineering" +GENE = "General Engineering" +BMEN = "Biomedical Engineering" +CHEM = "Chemical Engineering" +CIVIL = "Civil Engineering" +INDU = "Industrial Engineering" +MECH = "Mechanical Engineering" +AERO = "Aerospace Engineering" +ESET = "Electronic Systems Engineering Technology (ESET)" +MATH = "Mathematics" +PHYS = "Physics" +STAT = "Statistics" +BIO = "Biology" +CHEMISTRY = "Chemistry" +MAJOR_OTHER = "Other" + +MAJORS = [ + (CS, "Computer Science"), + (CE, "Computer Engineering"), + (COMP, "Computing"), + (EE, "Electrical Engineering"), + (MIS, "Management Information Systems"), + (DS, "Data Science/Engineering"), + (GENE, "General Engineering"), + (BMEN, "Biomedical Engineering"), + (CHEM, "Chemical Engineering"), + (CIVIL, "Civil Engineering"), + (INDU, "Industrial Engineering"), + (MECH, "Mechanical Engineering"), + (AERO, "Aerospace Engineering"), + (ESET, "Electronic Systems Engineering Technology (ESET)"), + (MATH, "Mathematics"), + (PHYS, "Physics"), + (STAT, "Statistics"), + (BIO, "Biology"), + (CHEMISTRY, "Chemistry"), + (MAJOR_OTHER, "Other"), +] + WANTS_TEAM_OPTIONS = [ ("Friend", "From a friend"), ("Tabling", "Tabling outside Zachry"), + ("Howdy Week", "From Howdy Week"), ("Yard Sign", "Yard sign"), ("Social Media", "Social media"), ("Student Orgs", "Though another student org"), @@ -417,8 +449,18 @@ class Application(models.Model): on_delete=models.SET_NULL, verbose_name="What school do you go to?", ) - school_other = models.CharField(null=True, blank=True, max_length=255) - major = models.CharField("What's your major?", max_length=255) + school_other = models.CharField( + null=True, blank=True, max_length=255 + ) + tamu_email = models.EmailField( + "TAMU Email if you are a Texas A&M student", null=True, blank=True, max_length = 75 + ) + major = models.CharField( + "What's your major?", default=NO_ANSWER, choices= MAJORS, max_length = 100 + ) + major_other = models.CharField( + "Other", max_length=255, null=True, blank=True + ) classification = models.CharField( "What classification are you?", choices=CLASSIFICATIONS, max_length=3 ) @@ -444,7 +486,7 @@ class Application(models.Model): "How many hackathons have you attended?", max_length=22, choices=HACKATHON_TIMES ) wares = models.CharField( - "TAMUhack will be partnering with IEEE to offer a dedicated hardware track and prizes. Participants can choose to compete in this track or in the general software tracks. Would you like to compete in the software or hardware track", choices=WARECHOICE, max_length=8, default=NO_ANSWER, blank=False + "TAMUhack will be partnering with IEEE to offer a dedicated hardware track and prizes. Participants can choose to compete in this track or in the general software tracks. Would you like to compete in the software or hardware track", choices=WARECHOICE, max_length=8, default=NO_ANSWER, blank=True ) # LEGAL INFO agree_to_coc = models.BooleanField(choices=AGREE, default=None) @@ -461,6 +503,13 @@ class Application(models.Model): help_text="Please note that freshmen under 18 must be accompanied by an adult or prove that they go to Texas " "A&M.", ) + + agree_to_photos = models.BooleanField( + choices=AGREE, null=True, default=None + ) + accessibility_requirements = models.BooleanField( + choices=AGREE_DISAGREE, null=True, default=None, blank=True + ) # LOGISTICAL INFO shirt_size = models.CharField( @@ -488,6 +537,7 @@ class Application(models.Model): ) dietary_restrictions = models.CharField(max_length=5000, default=None) + meal_group = models.CharField(max_length=255, null=True, blank=True, default=None) technology_experience = models.CharField(max_length=5000, default=None) @@ -520,12 +570,28 @@ def get_absolute_url(self): def clean(self): super().clean() - if not self.is_adult: + + def is_valid_name(name): + pattern = r"^(?=.{1,40}$)[a-zA-Z]+(?:[-' ][a-zA-Z]+)*$" + + match = re.match(pattern, name) + + return bool(match) + + + if not self.age.isnumeric(): + raise exceptions.ValidationError("Age must be a number.") + if not self.is_adult and int(self.age) > 18 or self.is_adult and int(self.age) < 18: + raise exceptions.ValidationError( + "Age and adult status do not match. Please confirm you are 18 or older." + ) + #Fixes the obos admin panel bug, idk why the checkbox doesn't show up + if not int(self.age) >= 18 or not self.is_adult: raise exceptions.ValidationError( "Unfortunately, we cannot accept hackers under the age of 18. Have additional questions? Email " f"us at {settings.ORGANIZER_EMAIL}. " ) - if not self.first_name.isalpha(): - raise exceptions.ValidationError("First name can only contain letters.") - if not self.last_name.isalpha(): - raise exceptions.ValidationError("Last name can only contain letters.") + if not is_valid_name(self.first_name): + raise exceptions.ValidationError("First name can only contain letters, spaces, hyphens, and apostrophes.") + if not is_valid_name(self.last_name): + raise exceptions.ValidationError("Last name can only contain letters, spaces, hyphens, and apostrophes.") diff --git a/hiss/application/views.py b/hiss/application/views.py index ad7d8ec8..128959de 100644 --- a/hiss/application/views.py +++ b/hiss/application/views.py @@ -29,19 +29,30 @@ class CreateApplicationView(mixins.LoginRequiredMixin, generic.CreateView): success_url = reverse_lazy("status") def get_context_data(self, **kwargs): + print("Calling get_context_data") context = super().get_context_data(**kwargs) context["active_wave"] = Wave.objects.active_wave() return context def form_valid(self, form: ApplicationModelForm): - if Application.objects.filter(user=self.request.user).exists(): - form.add_error(None, "You can only submit one application to this event.") - return self.form_invalid(form) - application: Application = form.save(commit=False) - application.user = self.request.user - application.wave = Wave.objects.active_wave() - application.save() - send_creation_email(application) + print("Appliction submitted: calling form_valid") + try: + if Application.objects.filter(user=self.request.user).exists(): + form.add_error(None, "You can only submit one application to this event.") + return self.form_invalid(form) + print("calling form.save") + application: Application = form.save(commit=False) + application.user = self.request.user + application.wave = Wave.objects.active_wave() + print("calling application.save") + application.save() + print("calling send_creation_email") + send_creation_email(application) + print("done with form_valid; sending redirect", flush=True) + + except Exception as e: + print(f"Exception: {e}", flush=True) + raise e return redirect(self.success_url) diff --git a/hiss/create_application_application.sql b/hiss/create_application_application.sql index 359c348a..e02b1992 100644 --- a/hiss/create_application_application.sql +++ b/hiss/create_application_application.sql @@ -251,3 +251,14 @@ ALTER TABLE "application_application" ADD COLUMN "wares" varchar(255) DEFAULT '' ALTER TABLE "application_application" ALTER COLUMN "wares" DROP DEFAULT; COMMIT; +-- 0020 +BEGIN; +-- +-- Alter field agree_to_photography on application +-- +-- Alter field accessibility_requirements on application +-- +ALTER TABLE "application_application" ADD COLUMN "agree_to_photography" BOOLEAN NOT NULL DEFAULT FALSE; +ALTER TABLE "application_application" ADD COLUMN "accessibility_requirements" BOOLEAN NOT NULL DEFAULT FALSE; +COMMIT; + diff --git a/hiss/customauth/views.py b/hiss/customauth/views.py index 688ccc22..12b67b69 100644 --- a/hiss/customauth/views.py +++ b/hiss/customauth/views.py @@ -15,7 +15,6 @@ from customauth.tokens import email_confirmation_generator from user.models import User - def send_confirmation_email(curr_domain: RequestSite, user: User) -> None: subject = "Confirm your email address!" template_name = "registration/emails/activate.html" diff --git a/hiss/hiss/settings/base.py b/hiss/hiss/settings/base.py index fae50431..61cc89fe 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-th-resumes" +AWS_S3_BUCKET_NAME = "2024-hh-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 0eb82359..188bebf3 100644 --- a/hiss/hiss/settings/customization.py +++ b/hiss/hiss/settings/customization.py @@ -1,12 +1,12 @@ from django.utils import timezone MAX_YEARS_ADMISSION = 6 -EVENT_NAME = "TAMUhack" -EVENT_YEAR = "X" +EVENT_NAME = "HowdyHack" +EVENT_YEAR = "2024" ORGANIZER_NAME = "TAMUhack" ORGANIZER_EMAIL = "hello@tamuhack.com" -EVENT_START_DATETIME = timezone.datetime(2024, 1, 27, hour=9, minute=0, second=0) -EVENT_END_DATETIME = timezone.datetime(2024, 1, 28, hour=12, minute=0, second=0) +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) MAX_MEMBERS_PER_TEAM = 4 -APPLE_WALLET_S3_BUCKET_URL = "https://thx-apple-wallet-passes.s3.amazonaws.com" +APPLE_WALLET_S3_BUCKET_URL = "https://hh24-apple-wallet-passes.s3.amazonaws.com" diff --git a/hiss/shared/admin_functions.py b/hiss/shared/admin_functions.py index bc843d7e..85f1de6e 100644 --- a/hiss/shared/admin_functions.py +++ b/hiss/shared/admin_functions.py @@ -1,27 +1,46 @@ 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 send_mass_html_mail( - datatuple, fail_silently=False, user=None, password=None, connection=None -): - """ - Given a datatuple of (subject, text_content, html_content, from_email, - recipient_list), sends each message to each recipient list. Returns the - number of emails sent. - - If from_email is None, the DEFAULT_FROM_EMAIL setting is used. - If auth_user and auth_password are set, they're used to log in. - If auth_user is None, the EMAIL_HOST_USER setting is used. - If auth_password is None, the EMAIL_HOST_PASSWORD setting is used. + 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 - (from this StackOverflow answer +def send_mass_html_mail(datatuple, fail_silently=False, user=None, password=None, connection=None): + """ + Sends each message in datatuple (subject, text_content, html_content, from_email, recipient_list). + Returns the number of emails sent. """ connection = connection or get_connection( username=user, password=password, fail_silently=fail_silently ) - messages = [] + + threads = [] + for subject, text, html, from_email, recipient in datatuple: - message = EmailMultiAlternatives(subject, text, from_email, recipient) - message.attach_alternative(html, "text/html") - messages.append(message) - return connection.send_messages(messages) + 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 diff --git a/hiss/static/hh24_parking.jpg b/hiss/static/hh24_parking.jpg new file mode 100644 index 00000000..1cf51fc7 Binary files /dev/null and b/hiss/static/hh24_parking.jpg differ diff --git a/hiss/static/hh24emailheader.jpg b/hiss/static/hh24emailheader.jpg new file mode 100644 index 00000000..cd1d77c9 Binary files /dev/null and b/hiss/static/hh24emailheader.jpg differ diff --git a/hiss/static/main.js b/hiss/static/main.js index 77dfa2da..88a0162e 100644 --- a/hiss/static/main.js +++ b/hiss/static/main.js @@ -2,7 +2,8 @@ $(document).ready(function() { const optionalQuestionsStyle = "color:#4286f3; padding: 2em 0em 1em 0em;"; const optionalQuestionsText = "The following questions are optional. Scroll down to submit your application, or continue to help us improve the event!" const optionalQuestionsNode = `
${optionalQuestionsText}
` - if (!$('#id_race input[value="O"]')[0].checked) { + + if ($('#id_race input[value="O"]').length > 0 && !$('#id_race input[value="O"]')[0].checked) { $('#id_race_other').parent().hide(); } $('#id_race input[value="O"]').click(function() { @@ -26,17 +27,38 @@ $(document).ready(function() { $('#id_gender_other').parent().hide(); } }); - + + if ($('#id_major').val() !== "Other"){ + $('#id_major_other').parent().hide(); + } + $('#id_major').on('change', function(){ + let selection = $('#id_major').val(); + if (selection === "Other"){ + $('#id_major_other').parent().show(); + } + else{ + $('#id_major_other').parent().hide(); + } + }); + if ($('#id_school option:selected').text() !== "Other"){ $('#id_school_other').parent().hide(); } + if ($('#id_school option:selected').text() !== "Texas A&M University"){ + $('#id_tamu_email').parent().hide(); + } $('#id_school').on('change', function(){ let selection = $('#id_school option:selected').text(); - if (selection === "Other"){ + if (selection == "Texas A&M University" ) { + $("#id_tamu_email").parent().show(); + } + else if (selection === "Other"){ $('#id_school_other').parent().show(); + $('#id_tamu_email').parent().hide(); } else{ $('#id_school_other').parent().hide(); + $('#id_tamu_email').parent().hide(); } }); // Custom styling for multi-select inputs. diff --git a/hiss/static/style.css b/hiss/static/style.css index 8c3ea235..fcdbf4f7 100644 --- a/hiss/static/style.css +++ b/hiss/static/style.css @@ -662,6 +662,8 @@ label[for="id_agree_to_mlh_stuff"], label[for="id_signup_to_mlh_newsletter"], label[for="id_is_adult"], label[for="id_travel_reimbursement"], +label[for="id_agree_to_photos"], +label[for="id_accessibility_requirements"], #id_travel_reimbursement ~ .helptext { margin-left: 45px; } @@ -670,12 +672,15 @@ label[for=id_is_adult] { margin-top: -20px; } -#id_agree_to_mlh_stuff { +#id_agree_to_mlh_stuff, #id_agree_to_photos { top: -100px; } +#id_signup_to_mlh_newsletter, #id_accessibility_requirements { + top: -50px; +} + #id_agree_to_coc, -#id_signup_to_mlh_newsletter, #id_is_adult, #id_travel_reimbursement { top: -30px; diff --git a/hiss/templates/application/emails/approved.html b/hiss/templates/application/emails/approved.html index b4ed9963..b4431719 100644 --- a/hiss/templates/application/emails/approved.html +++ b/hiss/templates/application/emails/approved.html @@ -55,7 +55,7 @@

> You have been accepted to {{event_name}} {{event_year}}! We were impressed by your application and we’re excited to see you January 27 - 28, 2024. + >September 28-29, 2024.

--> - diff --git a/hiss/templates/application/emails/confirmed-waitlist.html b/hiss/templates/application/emails/confirmed-waitlist.html new file mode 100644 index 00000000..5a2bc301 --- /dev/null +++ b/hiss/templates/application/emails/confirmed-waitlist.html @@ -0,0 +1,891 @@ + + + + + + + + + {{ event_name }} {{ event_year }} Day Of Information + + + + + + +

+ + + + + + + + + + +
+ + + + + +
+ + + +
+ +
+ +
+ + + + + +
+ + + + + +
+ + + + + + + + +
+ +

Get ready for HowdyHack 2024!

+ +

Waitlist: Important Reminders

+ +
+ + + +
+ + + + + +
+ + + + + + + + +
+ +

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. +
+
+Check-in
+The event will take place in the Gates Ballroom at the Memorial Student Center (275 Joe Routt Blvd, College Station, TX 77843). Check-in for waitlisted hackers will begin at 10AM Saturday, September 28th outside the ballroom at MSC 2400.
+
+

To check in, you will need:

+
    +
  1. +The QR code** attached to your "{{ event_name }} Waitlist: Important Day-of Information!" email +
  2. +
  3. + Your student ID +
  4. +
+ + + +
+** Your QR Code may not be visible if you are using Apple Mail. 
+
+ + Add to Apple Wallet + +
+

You are in meal group:

+

{{ meal_group }}

+
+Communication and Info
+Discord
+You can join our Discord here
+
+Our Discord will be used for communicating announcments during the event, and will also let you get in contact with organizers if you have any questions.  
+
+
+Live Site & Schedule
+Check out our live site here to find our prizes and our schedule to make sure you don't miss out on any of our fun events!
+
+ + +
+Special Accommodations
+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.
+
+
+ +Parking
+There are three parking lots available for parking during the event. Please read the information below for the details of each parking lot. You can click on each address to view the location on Google Maps. 

+ +

Lot 100T
+133-155 John Kimbrough Blvd, College Station, TX 77840
+*Free in unmarked spaces

+ +

Lot 74
+730 Olsen Blvd, College Station, TX 77845
+*Free in unmarked spaces

+ +

Lot 97
+420 John Kimbrough Blvd, College Station, TX 77845
+*Free in unmarked spaces

+ + +Map of available parking lots + +

(Click to view full-resolution image)

+ +


+ +
+We can't wait to meet all of you! Get ready for a fun-filled weekend!
+
+- The {{ organizer_name }} Team +

+ + + +
+ + + + + +
+ + + + +
+ +
+ +
+ + + + + +
+ + + + +
+ +
+ +
+ +
+ + + + + +
+ + + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + + + + + + +
+ + + + +
+ + + + + + + +
+ Facebook +
+
+
+ + + + + + + + + + +
+ + + + +
+ + + + + + + +
+ Twitter +
+
+
+ + + + + + + + + + +
+ + + + +
+ + + + + + + +
+ Link +
+
+
+ + + + + + + + + + +
+ + + + +
+ + + + + + + +
+ Website +
+
+
+ + + + +
+
+
+ +
+ + + + + +
+ + + + +
+ +
+ +
+ + + + + +
+ + + + + + + + +
+ + Copyright © 2024 TAMUhack, All rights reserved.
+
+Want to change how you receive these emails?
+You can update your preferences or unsubscribe from this list.
+  +
+ + + +
+ +
+ + + + +
+
+
+
+
+
+
+
+ + + + +
+ + + + +
+ This email was sent to *|EMAIL|* +
+ why did I get this?    unsubscribe from this list    update subscription preferences +
+ *|LIST:ADDRESSLINE|* +
+
+ *|REWARDS|* +
+
+ +
+ diff --git a/hiss/templates/application/emails/confirmed.html b/hiss/templates/application/emails/confirmed.html index 52e6d2b2..8c8c28b8 100644 --- a/hiss/templates/application/emails/confirmed.html +++ b/hiss/templates/application/emails/confirmed.html @@ -390,11 +390,8 @@ -<<<<<<< HEAD - @@ -444,7 +438,7 @@
-======= - ->>>>>>> origin + +> -<<<<<<< HEAD
-======= - ->>>>>>> origin +
-

Get ready for TAMUhack X!

+

Get ready for HowdyHack 2024!

Important Reminders

@@ -481,15 +475,25 @@

Howdy, hackers!

-You are waitlisted for TAMUhack X. We will have a separate line for students on the waitlist. After 11AM, we will admit people from the waitlist line until the MSC capacity has been reached. +We're so excited to see you at HowdyHack 2024 on September 28 - 29.

-Check-in
-The event will take place in the Bethancourt Ballroom at the Memorial Student Center (275 Joe Routt Blvd, College Station, TX 77843). Check-in will begin at 9AM Saturday, January 27th outside the ballroom at MSC 2300*.
+Check-in
+The event will take place in the Gates Ballroom at the Memorial Student Center (275 Joe Routt Blvd, College Station, TX 77843). Check-in will begin at 9AM Saturday, September 28th outside the ballroom at MSC 2400.

-You will need the QR code** attached to your "{{ event_name }} Waitlist: Important Day-of Information" email and your student ID to check-in at the venue.
+

We will have separate lines for students accepted, and students on the waitlist. After 10AM, we will admit people from the waitlist line until the MSC capacity has been reached.

+

Because of this, admission to the hackathon is not guaranteed if you arrive after 10AM. Please show up early to ensure you secure your spot at the event.

+


-* We will have a separate line for students on the waitlist. After 11AM, we will admit people from the waitlist line until the MSC capacity has been reached.
+

To check in, you will need:

+
    +
  1. +The QR code** attached to your "{{ event_name }}: Important Day-of Information!" email +
  2. +
  3. + Your student ID +
  4. +

** Your QR Code may not be visible if you are using Apple Mail. 

@@ -497,35 +501,24 @@


+

You are in meal group:

+

{{ meal_group }}

+
-Parking
-There are two parking lots available for parking during the event. Please read the information below for the details of each parking lot. You can click on each address to view the location on Google Maps. 

- -

Lot 98
-301 Adriance Lab Rd, College Station, TX 77845
-*Free in unmarked spaces

- -

Lot 114
-264-272 Adriance Lab Rd, College Station, TX 77845
-*Free in unmarked spaces

- - -Map of available parking lots - -

(Click to view full-resolution image)


-Communication and Info
+Communication and Info
Discord
-You can join our Discord here
+You can join our Discord here

-Our Discord be used for communicating announcments during the event, and will also let you get in contact with organizers if you have any questions.  
+Our Discord will be used for communicating announcments during the event, and will also let you get in contact with organizers if you have any questions.  


-Live Site
-Check out our live site here to find our prizes and our schedule to make sure you don't miss out on any of our fun events!
+Live Site & Schedule
+Check out our live site here to find our prizes and our schedule to make sure you don't miss out on any of our fun events!

+
-Special Accommodations
+Special Accommodations
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.
+
+
+ +Parking
+There are three parking lots available for parking during the event. Please read the information below for the details of each parking lot. You can click on each address to view the location on Google Maps. 

+ +

Lot 100T
+133-155 John Kimbrough Blvd, College Station, TX 77840
+*Free in unmarked spaces

+ +

Lot 74
+730 Olsen Blvd, College Station, TX 77845
+*Free in unmarked spaces

+ +

Lot 97
+420 John Kimbrough Blvd, College Station, TX 77845
+*Free in unmarked spaces

+ + +Map of available parking lots + +

(Click to view full-resolution image)

+ +

We can't wait to meet all of you! Get ready for a fun-filled weekend!
diff --git a/hiss/templates/application/emails/created.html b/hiss/templates/application/emails/created.html index 0842fcd0..afeae49a 100644 --- a/hiss/templates/application/emails/created.html +++ b/hiss/templates/application/emails/created.html @@ -1,7 +1,4 @@ -<<<<<<< HEAD -
-
tamuhack banner @@ -18,16 +15,6 @@ margin: 20px auto; margin-top: 0px">

Hi {{ first_name }}!

-======= -
- - ->>>>>>> origin -
Hi {{ first_nam {{EVENT_NAME}} {{EVENT_YEAR}} We'll be carefully reviewing your application and will get back to you soon!

- -<<<<<<< HEAD The {{ORGANIZER_NAME}} Team @@ -56,21 +41,4 @@

Hi {{ first_nam

-======= -

- Thanks! -

- - - The {{ORGANIZER_NAME}} Team - - -
-
->>>>>>> origin \ No newline at end of file diff --git a/hiss/templates/application/emails/rejected.html b/hiss/templates/application/emails/rejected.html index acd5ce90..e35f2e5d 100644 --- a/hiss/templates/application/emails/rejected.html +++ b/hiss/templates/application/emails/rejected.html @@ -44,11 +44,24 @@

Dear {{ first_n 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 - interests and skills with us. Unfortunately, due to space limitations of the MSC, we are only able to accept 750 hackers, and we are unable to grant you an acceptance at this time. -

However, we will have a waitlist line and we are extending an invitation for you to join our waitlist. Starting at 11AM, we will admit people from the waitlist line until the MSC capacity has been reached, since not all accepted people will show up to the event. - Please come early to ensure you have a higher chance of being admitted to the event as it is first come, first serve. + 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! +

+ + + +