diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index 6c1630e..3ea9b5a 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -1,9 +1,13 @@
name: Checks
on:
- pull_request_target:
+ pull_request:
branches:
- '**'
+ # Allow the Deploy workflow to run this exact same suite as a required gate
+ # before it builds and ships (see deploy.yml). Defining the checks once, here,
+ # keeps the pull-request run and the pre-deploy gate from drifting apart.
+ workflow_call:
jobs:
backend-checks:
@@ -14,9 +18,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- with:
- ref: ${{github.event.pull_request.head.ref}}
- repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
@@ -31,7 +32,11 @@ jobs:
black --check .
- name: Tests
env:
- SECRET_KEY: ${{ secrets.CI_SECRET_KEY }}
+ # The CI settings only need *a* valid Django key to boot — it signs
+ # nothing that outlives the test run — so we use a throwaway value
+ # rather than the production SECRET_KEY. This keeps the real key out
+ # of CI and lets these checks run on pull requests from forks.
+ SECRET_KEY: django-insecure-ci-not-a-real-secret
DEBUG: 0
run: python manage.py test --settings=hackathon_site.settings.ci
@@ -43,15 +48,14 @@ jobs:
steps:
- uses: actions/checkout@v4
- with:
- ref: ${{github.event.pull_request.head.ref}}
- repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Use Node.js 16.x
uses: actions/setup-node@v4
with:
node-version: '16.x'
- name: Install dependencies
run: yarn install
+ - name: Formatting check
+ run: yarn run prettier-check
dashboard-checks:
runs-on: ubuntu-latest
@@ -61,9 +65,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- with:
- ref: ${{github.event.pull_request.head.ref}}
- repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Use Node.js 16.x
uses: actions/setup-node@v4
with:
@@ -76,3 +77,5 @@ jobs:
run: yarn run tsc
- name: Tests
run: yarn test --watchAll=false
+ - name: Build frontend
+ run: yarn run build
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 82fad7b..3062e5a 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -11,8 +11,17 @@ env:
STACK_NAME: makeuoft
jobs:
+ # Required gate: run the full suite (Black, Django tests, prettier, tsc, jest,
+ # frontend build) before anything is built or shipped. build/deploy depend on
+ # this job, so a red commit on develop never reaches production — even one
+ # pushed directly, bypassing PR review. The checks are defined in checks.yml so
+ # this gate and the pull-request run can never drift apart.
+ checks:
+ uses: ./.github/workflows/checks.yml
+
build:
runs-on: ubuntu-latest
+ needs: [ checks ]
outputs:
GITHUB_SHA_SHORT: ${{ steps.sha7.outputs.GITHUB_SHA_SHORT }}
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
deleted file mode 100644
index e6863f3..0000000
--- a/.github/workflows/main.yml
+++ /dev/null
@@ -1,82 +0,0 @@
-name: CI/CD
-
-on:
- pull_request_target:
- branches:
- - '**'
-
-jobs:
- backend-checks:
- runs-on: ubuntu-latest
- defaults:
- run:
- working-directory: hackathon_site
-
- steps:
- - uses: actions/checkout@v4
- with:
- ref: ${{github.event.pull_request.head.ref}}
- repository: ${{github.event.pull_request.head.repo.full_name}}
- - name: Set up Python 3.10
- uses: actions/setup-python@v5
- with:
- python-version: '3.10'
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install -r requirements.txt
- - name: Check formatting with Black
- run: |
- # Stop the build if there are any formatting issues picked up by Black
- black --check .
- - name: Tests
- env:
- SECRET_KEY: ${{ secrets.DJANGO_SECRET_KEY }}
- DEBUG: 0
- run: python manage.py test --settings=hackathon_site.settings.ci
-
- template-checks:
- runs-on: ubuntu-latest
- defaults:
- run:
- working-directory: hackathon_site
-
- steps:
- - uses: actions/checkout@v4
- with:
- ref: ${{github.event.pull_request.head.ref}}
- repository: ${{github.event.pull_request.head.repo.full_name}}
- - name: Use Node.js 16.x
- uses: actions/setup-node@v4
- with:
- node-version: '16.x'
- - name: Install dependencies
- run: yarn install
- - name: Formatting check
- run: yarn run prettier-check
-
- dashboard-checks:
- runs-on: ubuntu-latest
- defaults:
- run:
- working-directory: hackathon_site/dashboard/frontend
-
- steps:
- - uses: actions/checkout@v4
- with:
- ref: ${{github.event.pull_request.head.ref}}
- repository: ${{github.event.pull_request.head.repo.full_name}}
- - name: Use Node.js 16.x
- uses: actions/setup-node@v4
- with:
- node-version: '16.x'
- - name: Install dependencies
- run: yarn install
- - name: Formatting check
- run: yarn prettier --check 'src/**/*.(js|ts|tsx|scss)'
- - name: Typescript check
- run: yarn run tsc
- - name: Tests
- run: yarn test --watchAll=false
- - name: Build frontend
- run: yarn run build
diff --git a/hackathon_site/event/api_filters.py b/hackathon_site/event/api_filters.py
index 7803723..41ee6da 100644
--- a/hackathon_site/event/api_filters.py
+++ b/hackathon_site/event/api_filters.py
@@ -25,5 +25,7 @@ class TeamFilter(filters.FilterSet):
)
team_code = filters.CharFilter(
- field_name="team_code", label="Team code", help_text="Team code",
+ field_name="team_code",
+ label="Team code",
+ help_text="Team code",
)
diff --git a/hackathon_site/event/api_views.py b/hackathon_site/event/api_views.py
index 1384b93..99f86ae 100644
--- a/hackathon_site/event/api_views.py
+++ b/hackathon_site/event/api_views.py
@@ -163,7 +163,10 @@ def post(self, request, *args, **kwargs):
# Construct response data
response_serializer = TeamSerializer(profile.team)
response_data = response_serializer.data
- return Response(data=response_data, status=status.HTTP_201_CREATED,)
+ return Response(
+ data=response_data,
+ status=status.HTTP_201_CREATED,
+ )
class JoinTeamView(generics.GenericAPIView, mixins.RetrieveModelMixin):
@@ -200,7 +203,10 @@ def post(self, request, *args, **kwargs):
current_team.delete()
response_serializer = TeamSerializer(profile.team)
response_data = response_serializer.data
- return Response(data=response_data, status=status.HTTP_200_OK,)
+ return Response(
+ data=response_data,
+ status=status.HTTP_200_OK,
+ )
class TeamIncidentListView(
@@ -295,7 +301,9 @@ def get(self, request, *args, **kwargs):
def delete(self, request, *args, **kwargs):
team = self.get_object()
active_orders = Order.objects.filter(
- Q(team=team), ~Q(status="Cancelled"), ~Q(status="Returned"),
+ Q(team=team),
+ ~Q(status="Cancelled"),
+ ~Q(status="Returned"),
)
if active_orders.exists():
raise ValidationError(
diff --git a/hackathon_site/event/forms.py b/hackathon_site/event/forms.py
index d6240d0..e32c9d0 100644
--- a/hackathon_site/event/forms.py
+++ b/hackathon_site/event/forms.py
@@ -79,9 +79,7 @@ def __init__(self, *args, **kwargs):
def save(self, commit=True):
self.instance = super().save(commit=False)
- self.instance.phone_number = re.sub(
- "[^0-9]", "", self.instance.phone_number
- )
+ self.instance.phone_number = re.sub("[^0-9]", "", self.instance.phone_number)
if commit:
self.instance.save()
return self.instance
diff --git a/hackathon_site/event/migrations/0003_remove_profile_status.py b/hackathon_site/event/migrations/0003_remove_profile_status.py
index b1289c0..67405bc 100644
--- a/hackathon_site/event/migrations/0003_remove_profile_status.py
+++ b/hackathon_site/event/migrations/0003_remove_profile_status.py
@@ -10,5 +10,8 @@ class Migration(migrations.Migration):
]
operations = [
- migrations.RemoveField(model_name="profile", name="status",),
+ migrations.RemoveField(
+ model_name="profile",
+ name="status",
+ ),
]
diff --git a/hackathon_site/event/migrations/0006_profile_phone_number.py b/hackathon_site/event/migrations/0006_profile_phone_number.py
index dc878cb..b7ee54e 100644
--- a/hackathon_site/event/migrations/0006_profile_phone_number.py
+++ b/hackathon_site/event/migrations/0006_profile_phone_number.py
@@ -13,7 +13,10 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="profile",
name="phone_number",
- field=models.CharField(default="8022818076", max_length=20,),
+ field=models.CharField(
+ default="8022818076",
+ max_length=20,
+ ),
preserve_default=False,
),
]
diff --git a/hackathon_site/event/migrations/0010_team_credits.py b/hackathon_site/event/migrations/0010_team_credits.py
index 85d563f..ab84a46 100644
--- a/hackathon_site/event/migrations/0010_team_credits.py
+++ b/hackathon_site/event/migrations/0010_team_credits.py
@@ -11,6 +11,8 @@ class Migration(migrations.Migration):
operations = [
migrations.AddField(
- model_name="team", name="credits", field=models.IntegerField(default=300),
+ model_name="team",
+ name="credits",
+ field=models.IntegerField(default=300),
),
]
diff --git a/hackathon_site/event/migrations/0011_interestsubmission.py b/hackathon_site/event/migrations/0011_interestsubmission.py
index 294df88..1c3de3b 100644
--- a/hackathon_site/event/migrations/0011_interestsubmission.py
+++ b/hackathon_site/event/migrations/0011_interestsubmission.py
@@ -7,26 +7,310 @@
class Migration(migrations.Migration):
dependencies = [
- ('event', '0010_team_credits'),
+ ("event", "0010_team_credits"),
]
operations = [
migrations.CreateModel(
- name='InterestSubmission',
+ name="InterestSubmission",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('first_name', models.CharField(max_length=30)),
- ('last_name', models.CharField(max_length=30)),
- ('email', models.EmailField(max_length=254)),
- ('age', models.PositiveIntegerField(choices=[(None, ''), (17, '17'), (18, '18'), (19, '19'), (20, '20'), (21, '21'), (22, '22'), (23, '22+')])),
- ('phone_number', models.CharField(max_length=20, validators=[django.core.validators.RegexValidator('^\\+?\\d[\\d\\s()-]{7,}$', message='Enter a valid phone number.')])),
- ('school', models.CharField(max_length=255)),
- ('study_level', models.CharField(choices=[(None, ''), ('post-secondary-2-years', '2 year Undergraduate University or community college program'), ('post-secondary-3-or-more-years', '3+ year Undergraduate University program'), ('graduate', 'Graduate University (Masters, Professional, Doctoral, etc)'), ('other', 'Other')], help_text='Current level of study', max_length=50)),
- ('country', models.CharField(choices=[(None, ''), ('Afghanistan', 'Afghanistan'), ('Albania', 'Albania'), ('Algeria', 'Algeria'), ('Andorra', 'Andorra'), ('Angola', 'Angola'), ('Antigua and Barbuda', 'Antigua and Barbuda'), ('Argentina', 'Argentina'), ('Armenia', 'Armenia'), ('Australia', 'Australia'), ('Austria', 'Austria'), ('Azerbaijan', 'Azerbaijan'), ('Bahamas', 'Bahamas'), ('Bahrain', 'Bahrain'), ('Bangladesh', 'Bangladesh'), ('Barbados', 'Barbados'), ('Belarus', 'Belarus'), ('Belgium', 'Belgium'), ('Belize', 'Belize'), ('Benin', 'Benin'), ('Bhutan', 'Bhutan'), ('Bolivia', 'Bolivia'), ('Bosnia and Herzegovina', 'Bosnia and Herzegovina'), ('Botswana', 'Botswana'), ('Brazil', 'Brazil'), ('Brunei', 'Brunei'), ('Bulgaria', 'Bulgaria'), ('Burkina Faso', 'Burkina Faso'), ('Burundi', 'Burundi'), ('Cabo Verde', 'Cabo Verde'), ('Cambodia', 'Cambodia'), ('Cameroon', 'Cameroon'), ('Canada', 'Canada'), ('Central African Republic', 'Central African Republic'), ('Chad', 'Chad'), ('Chile', 'Chile'), ('China', 'China'), ('Colombia', 'Colombia'), ('Comoros', 'Comoros'), ('Congo (Brazzaville)', 'Congo (Brazzaville)'), ('Congo (Kinshasa)', 'Congo (Kinshasa)'), ('Costa Rica', 'Costa Rica'), ("Côte d'Ivoire", "Côte d'Ivoire"), ('Croatia', 'Croatia'), ('Cuba', 'Cuba'), ('Cyprus', 'Cyprus'), ('Czechia', 'Czechia'), ('Denmark', 'Denmark'), ('Djibouti', 'Djibouti'), ('Dominica', 'Dominica'), ('Dominican Republic', 'Dominican Republic'), ('Ecuador', 'Ecuador'), ('Egypt', 'Egypt'), ('El Salvador', 'El Salvador'), ('Equatorial Guinea', 'Equatorial Guinea'), ('Eritrea', 'Eritrea'), ('Estonia', 'Estonia'), ('Eswatini', 'Eswatini'), ('Ethiopia', 'Ethiopia'), ('Fiji', 'Fiji'), ('Finland', 'Finland'), ('France', 'France'), ('Gabon', 'Gabon'), ('Gambia', 'Gambia'), ('Georgia', 'Georgia'), ('Germany', 'Germany'), ('Ghana', 'Ghana'), ('Greece', 'Greece'), ('Grenada', 'Grenada'), ('Guatemala', 'Guatemala'), ('Guinea', 'Guinea'), ('Guinea-Bissau', 'Guinea-Bissau'), ('Guyana', 'Guyana'), ('Haiti', 'Haiti'), ('Honduras', 'Honduras'), ('Hungary', 'Hungary'), ('Iceland', 'Iceland'), ('India', 'India'), ('Indonesia', 'Indonesia'), ('Iran', 'Iran'), ('Iraq', 'Iraq'), ('Ireland', 'Ireland'), ('Israel', 'Israel'), ('Italy', 'Italy'), ('Jamaica', 'Jamaica'), ('Japan', 'Japan'), ('Jordan', 'Jordan'), ('Kazakhstan', 'Kazakhstan'), ('Kenya', 'Kenya'), ('Kiribati', 'Kiribati'), ('Kosovo', 'Kosovo'), ('Kuwait', 'Kuwait'), ('Kyrgyzstan', 'Kyrgyzstan'), ('Laos', 'Laos'), ('Latvia', 'Latvia'), ('Lebanon', 'Lebanon'), ('Lesotho', 'Lesotho'), ('Liberia', 'Liberia'), ('Libya', 'Libya'), ('Liechtenstein', 'Liechtenstein'), ('Lithuania', 'Lithuania'), ('Luxembourg', 'Luxembourg'), ('Madagascar', 'Madagascar'), ('Malawi', 'Malawi'), ('Malaysia', 'Malaysia'), ('Maldives', 'Maldives'), ('Mali', 'Mali'), ('Malta', 'Malta'), ('Marshall Islands', 'Marshall Islands'), ('Mauritania', 'Mauritania'), ('Mauritius', 'Mauritius'), ('Mexico', 'Mexico'), ('Micronesia', 'Micronesia'), ('Moldova', 'Moldova'), ('Monaco', 'Monaco'), ('Mongolia', 'Mongolia'), ('Montenegro', 'Montenegro'), ('Morocco', 'Morocco'), ('Mozambique', 'Mozambique'), ('Myanmar', 'Myanmar'), ('Namibia', 'Namibia'), ('Nauru', 'Nauru'), ('Nepal', 'Nepal'), ('Netherlands', 'Netherlands'), ('New Zealand', 'New Zealand'), ('Nicaragua', 'Nicaragua'), ('Niger', 'Niger'), ('Nigeria', 'Nigeria'), ('North Korea', 'North Korea'), ('North Macedonia', 'North Macedonia'), ('Norway', 'Norway'), ('Oman', 'Oman'), ('Pakistan', 'Pakistan'), ('Palau', 'Palau'), ('Palestine', 'Palestine'), ('Panama', 'Panama'), ('Papua New Guinea', 'Papua New Guinea'), ('Paraguay', 'Paraguay'), ('Peru', 'Peru'), ('Philippines', 'Philippines'), ('Poland', 'Poland'), ('Portugal', 'Portugal'), ('Qatar', 'Qatar'), ('Romania', 'Romania'), ('Russia', 'Russia'), ('Rwanda', 'Rwanda'), ('Saint Kitts and Nevis', 'Saint Kitts and Nevis'), ('Saint Lucia', 'Saint Lucia'), ('Saint Vincent and the Grenadines', 'Saint Vincent and the Grenadines'), ('Samoa', 'Samoa'), ('San Marino', 'San Marino'), ('Sao Tome and Principe', 'Sao Tome and Principe'), ('Saudi Arabia', 'Saudi Arabia'), ('Senegal', 'Senegal'), ('Serbia', 'Serbia'), ('Seychelles', 'Seychelles'), ('Sierra Leone', 'Sierra Leone'), ('Singapore', 'Singapore'), ('Slovakia', 'Slovakia'), ('Slovenia', 'Slovenia'), ('Solomon Islands', 'Solomon Islands'), ('Somalia', 'Somalia'), ('South Africa', 'South Africa'), ('South Korea', 'South Korea'), ('South Sudan', 'South Sudan'), ('Spain', 'Spain'), ('Sri Lanka', 'Sri Lanka'), ('Sudan', 'Sudan'), ('Suriname', 'Suriname'), ('Sweden', 'Sweden'), ('Switzerland', 'Switzerland'), ('Syria', 'Syria'), ('Taiwan', 'Taiwan'), ('Tajikistan', 'Tajikistan'), ('Tanzania', 'Tanzania'), ('Thailand', 'Thailand'), ('Timor-Leste', 'Timor-Leste'), ('Togo', 'Togo'), ('Tonga', 'Tonga'), ('Trinidad and Tobago', 'Trinidad and Tobago'), ('Tunisia', 'Tunisia'), ('Turkey', 'Turkey'), ('Turkmenistan', 'Turkmenistan'), ('Tuvalu', 'Tuvalu'), ('Uganda', 'Uganda'), ('Ukraine', 'Ukraine'), ('United Arab Emirates', 'United Arab Emirates'), ('United Kingdom', 'United Kingdom'), ('United States', 'United States'), ('Uruguay', 'Uruguay'), ('Uzbekistan', 'Uzbekistan'), ('Vanuatu', 'Vanuatu'), ('Vatican City', 'Vatican City'), ('Venezuela', 'Venezuela'), ('Vietnam', 'Vietnam'), ('Yemen', 'Yemen'), ('Zambia', 'Zambia'), ('Zimbabwe', 'Zimbabwe'), ('Other', 'Other')], max_length=255)),
- ('conduct_agree', models.BooleanField(default=False, help_text='I have read and agree to the MLH code of conduct.')),
- ('logistics_agree', models.BooleanField(default=False, help_text='I authorize you to share my application/registration information with Major League Hacking for event administration, ranking, and MLH administration in-line with the MLH Privacy Policy. I further agree to the terms of both the MLH Contest Terms and Conditions and the MLH Privacy Policy.')),
- ('email_agree', models.BooleanField(blank=True, default=False, help_text='I authorize MLH to send me occasional emails about relevant events, career opportunities, and community announcements.', null=True)),
- ('created_at', models.DateTimeField(auto_now_add=True)),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("first_name", models.CharField(max_length=30)),
+ ("last_name", models.CharField(max_length=30)),
+ ("email", models.EmailField(max_length=254)),
+ (
+ "age",
+ models.PositiveIntegerField(
+ choices=[
+ (None, ""),
+ (17, "17"),
+ (18, "18"),
+ (19, "19"),
+ (20, "20"),
+ (21, "21"),
+ (22, "22"),
+ (23, "22+"),
+ ]
+ ),
+ ),
+ (
+ "phone_number",
+ models.CharField(
+ max_length=20,
+ validators=[
+ django.core.validators.RegexValidator(
+ "^\\+?\\d[\\d\\s()-]{7,}$",
+ message="Enter a valid phone number.",
+ )
+ ],
+ ),
+ ),
+ ("school", models.CharField(max_length=255)),
+ (
+ "study_level",
+ models.CharField(
+ choices=[
+ (None, ""),
+ (
+ "post-secondary-2-years",
+ "2 year Undergraduate University or community college program",
+ ),
+ (
+ "post-secondary-3-or-more-years",
+ "3+ year Undergraduate University program",
+ ),
+ (
+ "graduate",
+ "Graduate University (Masters, Professional, Doctoral, etc)",
+ ),
+ ("other", "Other"),
+ ],
+ help_text="Current level of study",
+ max_length=50,
+ ),
+ ),
+ (
+ "country",
+ models.CharField(
+ choices=[
+ (None, ""),
+ ("Afghanistan", "Afghanistan"),
+ ("Albania", "Albania"),
+ ("Algeria", "Algeria"),
+ ("Andorra", "Andorra"),
+ ("Angola", "Angola"),
+ ("Antigua and Barbuda", "Antigua and Barbuda"),
+ ("Argentina", "Argentina"),
+ ("Armenia", "Armenia"),
+ ("Australia", "Australia"),
+ ("Austria", "Austria"),
+ ("Azerbaijan", "Azerbaijan"),
+ ("Bahamas", "Bahamas"),
+ ("Bahrain", "Bahrain"),
+ ("Bangladesh", "Bangladesh"),
+ ("Barbados", "Barbados"),
+ ("Belarus", "Belarus"),
+ ("Belgium", "Belgium"),
+ ("Belize", "Belize"),
+ ("Benin", "Benin"),
+ ("Bhutan", "Bhutan"),
+ ("Bolivia", "Bolivia"),
+ ("Bosnia and Herzegovina", "Bosnia and Herzegovina"),
+ ("Botswana", "Botswana"),
+ ("Brazil", "Brazil"),
+ ("Brunei", "Brunei"),
+ ("Bulgaria", "Bulgaria"),
+ ("Burkina Faso", "Burkina Faso"),
+ ("Burundi", "Burundi"),
+ ("Cabo Verde", "Cabo Verde"),
+ ("Cambodia", "Cambodia"),
+ ("Cameroon", "Cameroon"),
+ ("Canada", "Canada"),
+ ("Central African Republic", "Central African Republic"),
+ ("Chad", "Chad"),
+ ("Chile", "Chile"),
+ ("China", "China"),
+ ("Colombia", "Colombia"),
+ ("Comoros", "Comoros"),
+ ("Congo (Brazzaville)", "Congo (Brazzaville)"),
+ ("Congo (Kinshasa)", "Congo (Kinshasa)"),
+ ("Costa Rica", "Costa Rica"),
+ ("Côte d'Ivoire", "Côte d'Ivoire"),
+ ("Croatia", "Croatia"),
+ ("Cuba", "Cuba"),
+ ("Cyprus", "Cyprus"),
+ ("Czechia", "Czechia"),
+ ("Denmark", "Denmark"),
+ ("Djibouti", "Djibouti"),
+ ("Dominica", "Dominica"),
+ ("Dominican Republic", "Dominican Republic"),
+ ("Ecuador", "Ecuador"),
+ ("Egypt", "Egypt"),
+ ("El Salvador", "El Salvador"),
+ ("Equatorial Guinea", "Equatorial Guinea"),
+ ("Eritrea", "Eritrea"),
+ ("Estonia", "Estonia"),
+ ("Eswatini", "Eswatini"),
+ ("Ethiopia", "Ethiopia"),
+ ("Fiji", "Fiji"),
+ ("Finland", "Finland"),
+ ("France", "France"),
+ ("Gabon", "Gabon"),
+ ("Gambia", "Gambia"),
+ ("Georgia", "Georgia"),
+ ("Germany", "Germany"),
+ ("Ghana", "Ghana"),
+ ("Greece", "Greece"),
+ ("Grenada", "Grenada"),
+ ("Guatemala", "Guatemala"),
+ ("Guinea", "Guinea"),
+ ("Guinea-Bissau", "Guinea-Bissau"),
+ ("Guyana", "Guyana"),
+ ("Haiti", "Haiti"),
+ ("Honduras", "Honduras"),
+ ("Hungary", "Hungary"),
+ ("Iceland", "Iceland"),
+ ("India", "India"),
+ ("Indonesia", "Indonesia"),
+ ("Iran", "Iran"),
+ ("Iraq", "Iraq"),
+ ("Ireland", "Ireland"),
+ ("Israel", "Israel"),
+ ("Italy", "Italy"),
+ ("Jamaica", "Jamaica"),
+ ("Japan", "Japan"),
+ ("Jordan", "Jordan"),
+ ("Kazakhstan", "Kazakhstan"),
+ ("Kenya", "Kenya"),
+ ("Kiribati", "Kiribati"),
+ ("Kosovo", "Kosovo"),
+ ("Kuwait", "Kuwait"),
+ ("Kyrgyzstan", "Kyrgyzstan"),
+ ("Laos", "Laos"),
+ ("Latvia", "Latvia"),
+ ("Lebanon", "Lebanon"),
+ ("Lesotho", "Lesotho"),
+ ("Liberia", "Liberia"),
+ ("Libya", "Libya"),
+ ("Liechtenstein", "Liechtenstein"),
+ ("Lithuania", "Lithuania"),
+ ("Luxembourg", "Luxembourg"),
+ ("Madagascar", "Madagascar"),
+ ("Malawi", "Malawi"),
+ ("Malaysia", "Malaysia"),
+ ("Maldives", "Maldives"),
+ ("Mali", "Mali"),
+ ("Malta", "Malta"),
+ ("Marshall Islands", "Marshall Islands"),
+ ("Mauritania", "Mauritania"),
+ ("Mauritius", "Mauritius"),
+ ("Mexico", "Mexico"),
+ ("Micronesia", "Micronesia"),
+ ("Moldova", "Moldova"),
+ ("Monaco", "Monaco"),
+ ("Mongolia", "Mongolia"),
+ ("Montenegro", "Montenegro"),
+ ("Morocco", "Morocco"),
+ ("Mozambique", "Mozambique"),
+ ("Myanmar", "Myanmar"),
+ ("Namibia", "Namibia"),
+ ("Nauru", "Nauru"),
+ ("Nepal", "Nepal"),
+ ("Netherlands", "Netherlands"),
+ ("New Zealand", "New Zealand"),
+ ("Nicaragua", "Nicaragua"),
+ ("Niger", "Niger"),
+ ("Nigeria", "Nigeria"),
+ ("North Korea", "North Korea"),
+ ("North Macedonia", "North Macedonia"),
+ ("Norway", "Norway"),
+ ("Oman", "Oman"),
+ ("Pakistan", "Pakistan"),
+ ("Palau", "Palau"),
+ ("Palestine", "Palestine"),
+ ("Panama", "Panama"),
+ ("Papua New Guinea", "Papua New Guinea"),
+ ("Paraguay", "Paraguay"),
+ ("Peru", "Peru"),
+ ("Philippines", "Philippines"),
+ ("Poland", "Poland"),
+ ("Portugal", "Portugal"),
+ ("Qatar", "Qatar"),
+ ("Romania", "Romania"),
+ ("Russia", "Russia"),
+ ("Rwanda", "Rwanda"),
+ ("Saint Kitts and Nevis", "Saint Kitts and Nevis"),
+ ("Saint Lucia", "Saint Lucia"),
+ (
+ "Saint Vincent and the Grenadines",
+ "Saint Vincent and the Grenadines",
+ ),
+ ("Samoa", "Samoa"),
+ ("San Marino", "San Marino"),
+ ("Sao Tome and Principe", "Sao Tome and Principe"),
+ ("Saudi Arabia", "Saudi Arabia"),
+ ("Senegal", "Senegal"),
+ ("Serbia", "Serbia"),
+ ("Seychelles", "Seychelles"),
+ ("Sierra Leone", "Sierra Leone"),
+ ("Singapore", "Singapore"),
+ ("Slovakia", "Slovakia"),
+ ("Slovenia", "Slovenia"),
+ ("Solomon Islands", "Solomon Islands"),
+ ("Somalia", "Somalia"),
+ ("South Africa", "South Africa"),
+ ("South Korea", "South Korea"),
+ ("South Sudan", "South Sudan"),
+ ("Spain", "Spain"),
+ ("Sri Lanka", "Sri Lanka"),
+ ("Sudan", "Sudan"),
+ ("Suriname", "Suriname"),
+ ("Sweden", "Sweden"),
+ ("Switzerland", "Switzerland"),
+ ("Syria", "Syria"),
+ ("Taiwan", "Taiwan"),
+ ("Tajikistan", "Tajikistan"),
+ ("Tanzania", "Tanzania"),
+ ("Thailand", "Thailand"),
+ ("Timor-Leste", "Timor-Leste"),
+ ("Togo", "Togo"),
+ ("Tonga", "Tonga"),
+ ("Trinidad and Tobago", "Trinidad and Tobago"),
+ ("Tunisia", "Tunisia"),
+ ("Turkey", "Turkey"),
+ ("Turkmenistan", "Turkmenistan"),
+ ("Tuvalu", "Tuvalu"),
+ ("Uganda", "Uganda"),
+ ("Ukraine", "Ukraine"),
+ ("United Arab Emirates", "United Arab Emirates"),
+ ("United Kingdom", "United Kingdom"),
+ ("United States", "United States"),
+ ("Uruguay", "Uruguay"),
+ ("Uzbekistan", "Uzbekistan"),
+ ("Vanuatu", "Vanuatu"),
+ ("Vatican City", "Vatican City"),
+ ("Venezuela", "Venezuela"),
+ ("Vietnam", "Vietnam"),
+ ("Yemen", "Yemen"),
+ ("Zambia", "Zambia"),
+ ("Zimbabwe", "Zimbabwe"),
+ ("Other", "Other"),
+ ],
+ max_length=255,
+ ),
+ ),
+ (
+ "conduct_agree",
+ models.BooleanField(
+ default=False,
+ help_text='I have read and agree to the MLH code of conduct.',
+ ),
+ ),
+ (
+ "logistics_agree",
+ models.BooleanField(
+ default=False,
+ help_text='I authorize you to share my application/registration information with Major League Hacking for event administration, ranking, and MLH administration in-line with the MLH Privacy Policy. I further agree to the terms of both the MLH Contest Terms and Conditions and the MLH Privacy Policy.',
+ ),
+ ),
+ (
+ "email_agree",
+ models.BooleanField(
+ blank=True,
+ default=False,
+ help_text="I authorize MLH to send me occasional emails about relevant events, career opportunities, and community announcements.",
+ null=True,
+ ),
+ ),
+ ("created_at", models.DateTimeField(auto_now_add=True)),
],
),
]
diff --git a/hackathon_site/event/models.py b/hackathon_site/event/models.py
index 12bac21..53af302 100644
--- a/hackathon_site/event/models.py
+++ b/hackathon_site/event/models.py
@@ -32,7 +32,10 @@ class Profile(models.Model):
team = models.ForeignKey(
Team, related_name="profiles", on_delete=models.CASCADE, null=False
)
- phone_number = models.CharField(max_length=20, null=False,)
+ phone_number = models.CharField(
+ max_length=20,
+ null=False,
+ )
id_provided = models.BooleanField(default=False, null=False)
attended = models.BooleanField(default=False, null=False)
acknowledge_rules = models.BooleanField(default=False, null=False)
@@ -93,44 +96,204 @@ class UserActivity(models.Model):
# ISO 3166-1 list of countries (Country of Residence), per MLH's required format.
_COUNTRY_NAMES = [
- "Afghanistan", "Albania", "Algeria", "Andorra", "Angola",
- "Antigua and Barbuda", "Argentina", "Armenia", "Australia", "Austria",
- "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus",
- "Belgium", "Belize", "Benin", "Bhutan", "Bolivia",
- "Bosnia and Herzegovina", "Botswana", "Brazil", "Brunei", "Bulgaria",
- "Burkina Faso", "Burundi", "Cabo Verde", "Cambodia", "Cameroon", "Canada",
- "Central African Republic", "Chad", "Chile", "China", "Colombia",
- "Comoros", "Congo (Brazzaville)", "Congo (Kinshasa)", "Costa Rica",
- "Côte d'Ivoire", "Croatia", "Cuba", "Cyprus", "Czechia", "Denmark",
- "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt",
- "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Eswatini",
- "Ethiopia", "Fiji", "Finland", "France", "Gabon", "Gambia", "Georgia",
- "Germany", "Ghana", "Greece", "Grenada", "Guatemala", "Guinea",
- "Guinea-Bissau", "Guyana", "Haiti", "Honduras", "Hungary", "Iceland",
- "India", "Indonesia", "Iran", "Iraq", "Ireland", "Israel", "Italy",
- "Jamaica", "Japan", "Jordan", "Kazakhstan", "Kenya", "Kiribati",
- "Kosovo", "Kuwait", "Kyrgyzstan", "Laos", "Latvia", "Lebanon", "Lesotho",
- "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg",
- "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta",
- "Marshall Islands", "Mauritania", "Mauritius", "Mexico", "Micronesia",
- "Moldova", "Monaco", "Mongolia", "Montenegro", "Morocco", "Mozambique",
- "Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands", "New Zealand",
- "Nicaragua", "Niger", "Nigeria", "North Korea", "North Macedonia",
- "Norway", "Oman", "Pakistan", "Palau", "Palestine", "Panama",
- "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Poland",
- "Portugal", "Qatar", "Romania", "Russia", "Rwanda",
- "Saint Kitts and Nevis", "Saint Lucia",
- "Saint Vincent and the Grenadines", "Samoa", "San Marino",
- "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia",
- "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia",
- "Solomon Islands", "Somalia", "South Africa", "South Korea",
- "South Sudan", "Spain", "Sri Lanka", "Sudan", "Suriname", "Sweden",
- "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand",
- "Timor-Leste", "Togo", "Tonga", "Trinidad and Tobago", "Tunisia",
- "Turkey", "Turkmenistan", "Tuvalu", "Uganda", "Ukraine",
- "United Arab Emirates", "United Kingdom", "United States", "Uruguay",
- "Uzbekistan", "Vanuatu", "Vatican City", "Venezuela", "Vietnam",
- "Yemen", "Zambia", "Zimbabwe", "Other",
+ "Afghanistan",
+ "Albania",
+ "Algeria",
+ "Andorra",
+ "Angola",
+ "Antigua and Barbuda",
+ "Argentina",
+ "Armenia",
+ "Australia",
+ "Austria",
+ "Azerbaijan",
+ "Bahamas",
+ "Bahrain",
+ "Bangladesh",
+ "Barbados",
+ "Belarus",
+ "Belgium",
+ "Belize",
+ "Benin",
+ "Bhutan",
+ "Bolivia",
+ "Bosnia and Herzegovina",
+ "Botswana",
+ "Brazil",
+ "Brunei",
+ "Bulgaria",
+ "Burkina Faso",
+ "Burundi",
+ "Cabo Verde",
+ "Cambodia",
+ "Cameroon",
+ "Canada",
+ "Central African Republic",
+ "Chad",
+ "Chile",
+ "China",
+ "Colombia",
+ "Comoros",
+ "Congo (Brazzaville)",
+ "Congo (Kinshasa)",
+ "Costa Rica",
+ "Côte d'Ivoire",
+ "Croatia",
+ "Cuba",
+ "Cyprus",
+ "Czechia",
+ "Denmark",
+ "Djibouti",
+ "Dominica",
+ "Dominican Republic",
+ "Ecuador",
+ "Egypt",
+ "El Salvador",
+ "Equatorial Guinea",
+ "Eritrea",
+ "Estonia",
+ "Eswatini",
+ "Ethiopia",
+ "Fiji",
+ "Finland",
+ "France",
+ "Gabon",
+ "Gambia",
+ "Georgia",
+ "Germany",
+ "Ghana",
+ "Greece",
+ "Grenada",
+ "Guatemala",
+ "Guinea",
+ "Guinea-Bissau",
+ "Guyana",
+ "Haiti",
+ "Honduras",
+ "Hungary",
+ "Iceland",
+ "India",
+ "Indonesia",
+ "Iran",
+ "Iraq",
+ "Ireland",
+ "Israel",
+ "Italy",
+ "Jamaica",
+ "Japan",
+ "Jordan",
+ "Kazakhstan",
+ "Kenya",
+ "Kiribati",
+ "Kosovo",
+ "Kuwait",
+ "Kyrgyzstan",
+ "Laos",
+ "Latvia",
+ "Lebanon",
+ "Lesotho",
+ "Liberia",
+ "Libya",
+ "Liechtenstein",
+ "Lithuania",
+ "Luxembourg",
+ "Madagascar",
+ "Malawi",
+ "Malaysia",
+ "Maldives",
+ "Mali",
+ "Malta",
+ "Marshall Islands",
+ "Mauritania",
+ "Mauritius",
+ "Mexico",
+ "Micronesia",
+ "Moldova",
+ "Monaco",
+ "Mongolia",
+ "Montenegro",
+ "Morocco",
+ "Mozambique",
+ "Myanmar",
+ "Namibia",
+ "Nauru",
+ "Nepal",
+ "Netherlands",
+ "New Zealand",
+ "Nicaragua",
+ "Niger",
+ "Nigeria",
+ "North Korea",
+ "North Macedonia",
+ "Norway",
+ "Oman",
+ "Pakistan",
+ "Palau",
+ "Palestine",
+ "Panama",
+ "Papua New Guinea",
+ "Paraguay",
+ "Peru",
+ "Philippines",
+ "Poland",
+ "Portugal",
+ "Qatar",
+ "Romania",
+ "Russia",
+ "Rwanda",
+ "Saint Kitts and Nevis",
+ "Saint Lucia",
+ "Saint Vincent and the Grenadines",
+ "Samoa",
+ "San Marino",
+ "Sao Tome and Principe",
+ "Saudi Arabia",
+ "Senegal",
+ "Serbia",
+ "Seychelles",
+ "Sierra Leone",
+ "Singapore",
+ "Slovakia",
+ "Slovenia",
+ "Solomon Islands",
+ "Somalia",
+ "South Africa",
+ "South Korea",
+ "South Sudan",
+ "Spain",
+ "Sri Lanka",
+ "Sudan",
+ "Suriname",
+ "Sweden",
+ "Switzerland",
+ "Syria",
+ "Taiwan",
+ "Tajikistan",
+ "Tanzania",
+ "Thailand",
+ "Timor-Leste",
+ "Togo",
+ "Tonga",
+ "Trinidad and Tobago",
+ "Tunisia",
+ "Turkey",
+ "Turkmenistan",
+ "Tuvalu",
+ "Uganda",
+ "Ukraine",
+ "United Arab Emirates",
+ "United Kingdom",
+ "United States",
+ "Uruguay",
+ "Uzbekistan",
+ "Vanuatu",
+ "Vatican City",
+ "Venezuela",
+ "Vietnam",
+ "Yemen",
+ "Zambia",
+ "Zimbabwe",
+ "Other",
]
INTEREST_COUNTRY_CHOICES = [(None, "")] + [(name, name) for name in _COUNTRY_NAMES]
diff --git a/hackathon_site/event/serializers.py b/hackathon_site/event/serializers.py
index c9ef8b4..4f25d21 100644
--- a/hackathon_site/event/serializers.py
+++ b/hackathon_site/event/serializers.py
@@ -145,9 +145,11 @@ def create(self, validated_data):
"id_provided": False,
"acknowledge_rules": acknowledge_rules,
"e_signature": e_signature,
- "phone_number": "6471437544"
- if is_test_user
- else Application.objects.get(user=current_user).phone_number,
+ "phone_number": (
+ "6471437544"
+ if is_test_user
+ else Application.objects.get(user=current_user).phone_number
+ ),
}
profile = Profile.objects.create(**{**response_data, "user": current_user})
diff --git a/hackathon_site/event/static/event/js/sponsors-component.js b/hackathon_site/event/static/event/js/sponsors-component.js
index bfed4b2..4decf4b 100644
--- a/hackathon_site/event/static/event/js/sponsors-component.js
+++ b/hackathon_site/event/static/event/js/sponsors-component.js
@@ -3,45 +3,47 @@ const { useState } = React;
const SponsorCard = ({ sponsor, index }) => {
const [imageError, setImageError] = useState(false);
-
+
const handleClick = () => {
if (sponsor.website) {
- window.open(sponsor.website, '_blank', 'noopener,noreferrer');
+ window.open(sponsor.website, "_blank", "noopener,noreferrer");
}
};
// Build the full logo URL using the static URL prefix
const logoUrl = window.STATIC_URL ? window.STATIC_URL + sponsor.logo : sponsor.logo;
- return (
- React.createElement('div', {
- className: 'sponsor-card',
+ return React.createElement(
+ "div",
+ {
+ className: "sponsor-card",
onClick: handleClick,
- style: { cursor: sponsor.website ? 'pointer' : 'default' }
+ style: { cursor: sponsor.website ? "pointer" : "default" },
},
- !imageError ? (
- React.createElement('img', {
- src: logoUrl,
- alt: sponsor.name,
- onError: () => setImageError(true)
- })
- ) : (
- React.createElement('div', { className: 'sponsor-placeholder' },
- React.createElement('span', null, sponsor.name)
- )
- )
- )
+ !imageError
+ ? React.createElement("img", {
+ src: logoUrl,
+ alt: sponsor.name,
+ onError: () => setImageError(true),
+ })
+ : React.createElement(
+ "div",
+ { className: "sponsor-placeholder" },
+ React.createElement("span", null, sponsor.name)
+ )
);
};
const SponsorsGrid = ({ sponsors }) => {
// Render all sponsors dynamically - grid will adjust automatically
- return React.createElement('div', { className: 'sponsors-grid' },
+ return React.createElement(
+ "div",
+ { className: "sponsors-grid" },
sponsors.map((sponsor, index) =>
React.createElement(SponsorCard, {
key: `${sponsor.name}-${index}`,
sponsor: sponsor,
- index: index
+ index: index,
})
)
);
@@ -49,45 +51,50 @@ const SponsorsGrid = ({ sponsors }) => {
// Initialize the component when ready
function initSponsors() {
- const sponsorsContainer = document.getElementById('sponsors-react-container');
-
+ const sponsorsContainer = document.getElementById("sponsors-react-container");
+
if (!sponsorsContainer) {
return;
}
-
+
if (!window.SPONSORS_DATA || window.SPONSORS_DATA.length === 0) {
return;
}
-
- if (typeof React === 'undefined' || typeof ReactDOM === 'undefined') {
+
+ if (typeof React === "undefined" || typeof ReactDOM === "undefined") {
return;
}
-
+
try {
const root = ReactDOM.createRoot(sponsorsContainer);
- root.render(React.createElement(SponsorsGrid, { sponsors: window.SPONSORS_DATA }));
+ root.render(
+ React.createElement(SponsorsGrid, { sponsors: window.SPONSORS_DATA })
+ );
} catch (error) {
- console.error('Error rendering sponsors component:', error);
+ console.error("Error rendering sponsors component:", error);
}
}
// Wait for dependencies and DOM, then initialize
function waitForDependencies(callback) {
- if (typeof React !== 'undefined' &&
- typeof ReactDOM !== 'undefined' &&
- window.SPONSORS_DATA) {
+ if (
+ typeof React !== "undefined" &&
+ typeof ReactDOM !== "undefined" &&
+ window.SPONSORS_DATA
+ ) {
callback();
} else {
- setTimeout(function() { waitForDependencies(callback); }, 50);
+ setTimeout(function () {
+ waitForDependencies(callback);
+ }, 50);
}
}
// Initialize when dependencies are ready
-waitForDependencies(function() {
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', initSponsors);
+waitForDependencies(function () {
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", initSponsors);
} else {
initSponsors();
}
});
-
diff --git a/hackathon_site/event/static/event/js/sponsors-data.js b/hackathon_site/event/static/event/js/sponsors-data.js
index 8bc510f..47adf2b 100644
--- a/hackathon_site/event/static/event/js/sponsors-data.js
+++ b/hackathon_site/event/static/event/js/sponsors-data.js
@@ -6,27 +6,27 @@ window.SPONSORS_DATA = [
{
name: "Qualcomm",
logo: "event/images/sponsors/qualcomm.png",
- website: "https://www.qualcomm.com"
+ website: "https://www.qualcomm.com",
},
{
name: "Alphawave Semi",
logo: "event/images/sponsors/alphawave.png",
- website: "https://awavesemi.com"
+ website: "https://awavesemi.com",
},
{
name: "Marvell",
logo: "event/images/sponsors/marvell.png",
- website: "https://www.marvell.com"
+ website: "https://www.marvell.com",
},
{
name: "MLH",
logo: "event/images/sponsors/mlh.png",
- website: "https://mlh.io"
+ website: "https://mlh.io",
},
{
name: "Titan Haptics",
logo: "event/images/sponsors/titan.png",
- website: "https://titanhaptics.com"
+ website: "https://titanhaptics.com",
},
/***
@@ -54,17 +54,17 @@ window.SPONSORS_DATA = [
{
name: "Altera",
logo: "event/images/sponsors/altera.png",
- website: "https://www.altera.com"
+ website: "https://www.altera.com",
},
{
name: "University of Toronto ECE Department",
logo: "event/images/sponsors/ece.png",
- website: "https://www.ece.utoronto.ca"
+ website: "https://www.ece.utoronto.ca",
},
{
name: "Edge Impulse",
logo: "event/images/sponsors/edge_impulse.png",
- website: "https://www.edgeimpulse.com"
+ website: "https://www.edgeimpulse.com",
},
// Add more sponsors here:
// {
@@ -73,4 +73,3 @@ window.SPONSORS_DATA = [
// website: "https://www.company.com"
// },
];
-
diff --git a/hackathon_site/event/test_api.py b/hackathon_site/event/test_api.py
index 394d2d2..c6ec731 100644
--- a/hackathon_site/event/test_api.py
+++ b/hackathon_site/event/test_api.py
@@ -579,16 +579,30 @@ def test_user_can_have_profile(self):
def test_not_including_required_fields(self):
self._review(application=self._apply_as_user(self.user, rsvp=True))
self._login()
- response = self.client.post(self.view, {"e_signature": "user signature",})
+ response = self.client.post(
+ self.view,
+ {
+ "e_signature": "user signature",
+ },
+ )
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
- response = self.client.post(self.view, {"acknowledge_rules": True,})
+ response = self.client.post(
+ self.view,
+ {
+ "acknowledge_rules": True,
+ },
+ )
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_acknowledge_rules_is_false(self):
self._review(application=self._apply_as_user(self.user, rsvp=True))
self._login()
response = self.client.post(
- self.view, {"e_signature": "user signature", "acknowledge_rules": False,},
+ self.view,
+ {
+ "e_signature": "user signature",
+ "acknowledge_rules": False,
+ },
)
data = response.json()
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
@@ -600,7 +614,11 @@ def test_e_signature_is_empty(self):
self._review(application=self._apply_as_user(self.user, rsvp=True))
self._login()
response = self.client.post(
- self.view, {"e_signature": "", "acknowledge_rules": True,},
+ self.view,
+ {
+ "e_signature": "",
+ "acknowledge_rules": True,
+ },
)
data = response.json()
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
@@ -703,10 +721,12 @@ def setUp(self):
picture="/picture/location/other",
)
OrderItem.objects.create(
- order=self.order, hardware=self.hardware,
+ order=self.order,
+ hardware=self.hardware,
)
OrderItem.objects.create(
- order=self.order, hardware=self.other_hardware,
+ order=self.order,
+ hardware=self.other_hardware,
)
# making extra data to test if team data is being filtered
@@ -715,10 +735,12 @@ def setUp(self):
status="Submitted", team=self.team2, request={"hardware": []}
)
OrderItem.objects.create(
- order=self.order_2, hardware=self.hardware,
+ order=self.order_2,
+ hardware=self.hardware,
)
OrderItem.objects.create(
- order=self.order_2, hardware=self.other_hardware,
+ order=self.order_2,
+ hardware=self.other_hardware,
)
self.view = reverse("api:event:team-orders")
@@ -768,7 +790,8 @@ def setUp(self):
picture="/picture/location",
)
self.order_item = OrderItem.objects.create(
- order=self.order, hardware=self.hardware,
+ order=self.order,
+ hardware=self.hardware,
)
self.request_data = {
@@ -806,7 +829,8 @@ def test_post_another_team_incident(self):
request={"hardware": [{"id": 1, "quantity": 2}]},
)
self.order_item2 = OrderItem.objects.create(
- order=self.order2, hardware=self.other_hardware,
+ order=self.order2,
+ hardware=self.other_hardware,
)
request_data = {
@@ -1050,7 +1074,8 @@ def test_failed_beginning_status(self):
)
response = self.client.patch(self._build_view(order.id), self.request_data)
self.assertEqual(
- response.json(), {"status": ["Cannot change the status for this order."]},
+ response.json(),
+ {"status": ["Cannot change the status for this order."]},
)
self.assertFalse(
self.request_data["status"] == Order.objects.get(id=self.pk).status
@@ -1066,7 +1091,8 @@ def test_cannot_change_other_team_order(self):
)
response = self.client.patch(self._build_view(order.id), self.request_data)
self.assertEqual(
- response.json(), {"detail": "Can only change the status of your orders."},
+ response.json(),
+ {"detail": "Can only change the status of your orders."},
)
self.assertFalse(
self.request_data["status"] == Order.objects.get(id=self.pk).status
diff --git a/hackathon_site/event/tests.py b/hackathon_site/event/tests.py
index 1098a02..95f0e49 100644
--- a/hackathon_site/event/tests.py
+++ b/hackathon_site/event/tests.py
@@ -823,7 +823,8 @@ def test_serializer(self):
user.groups.add(group)
Profile.objects.create(
- user=user, team=team,
+ user=user,
+ team=team,
)
user_serialized = UserSerializer(user).data
@@ -849,7 +850,8 @@ def test_serializer(self):
user = User.objects.create()
Profile.objects.create(
- user=user, team=team,
+ user=user,
+ team=team,
)
user_serialized = UserInProfileSerializer(user).data
@@ -937,7 +939,6 @@ def setUp(self):
"school": "UofT",
"study_level": "other",
"graduation_year": 2020,
- "what_hackathon_experience": "hi",
"why_participate": "there",
"what_technical_experience": "foo",
"conduct_agree": True,
diff --git a/hackathon_site/event/urls.py b/hackathon_site/event/urls.py
index 9b1f7ed..2a439f2 100644
--- a/hackathon_site/event/urls.py
+++ b/hackathon_site/event/urls.py
@@ -19,7 +19,11 @@
),
name="login",
),
- path("accounts/logout/", auth_views.LogoutView.as_view(), name="logout",),
+ path(
+ "accounts/logout/",
+ auth_views.LogoutView.as_view(),
+ name="logout",
+ ),
path("dashboard/", DashboardView.as_view(), name="dashboard"),
path("dashboard/qrscan/", QRScannerView.as_view(), name="qr-scanner"),
path(
diff --git a/hackathon_site/hackathon_site/settings/ci.py b/hackathon_site/hackathon_site/settings/ci.py
index 1aa26a8..f3598f2 100644
--- a/hackathon_site/hackathon_site/settings/ci.py
+++ b/hackathon_site/hackathon_site/settings/ci.py
@@ -18,6 +18,7 @@
caught during testing. These should be caught by a staging environment,
and by running your code before you merge it.
"""
+
from hackathon_site.settings import *
# Convenient for some methods to test, since DEBUG=0 in testing
diff --git a/hackathon_site/hackathon_site/tests.py b/hackathon_site/hackathon_site/tests.py
index 2574dc9..d59f4de 100644
--- a/hackathon_site/hackathon_site/tests.py
+++ b/hackathon_site/hackathon_site/tests.py
@@ -56,7 +56,6 @@ def _apply_as_user(user, team=None, **kwargs):
"school": "UofT",
"study_level": "other",
"graduation_year": 2020,
- "what_hackathon_experience": "hi",
"why_participate": "there",
"what_technical_experience": "foo",
"conduct_agree": True,
@@ -147,7 +146,10 @@ def _make_event_team(self, team=None, self_users=True, num_users=4):
return team
def _review(
- self, application=None, reviewer=None, **kwargs,
+ self,
+ application=None,
+ reviewer=None,
+ **kwargs,
):
if application is None:
application = self.user.application
diff --git a/hackathon_site/hackathon_site/urls.py b/hackathon_site/hackathon_site/urls.py
index 400c397..c03dec1 100644
--- a/hackathon_site/hackathon_site/urls.py
+++ b/hackathon_site/hackathon_site/urls.py
@@ -4,6 +4,7 @@
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.0/topics/http/urls/
"""
+
from django.contrib import admin
from django.urls import path, include # re_path is imported later only for DEBUG block
from django.conf import settings
@@ -19,12 +20,14 @@
urlpatterns = [
path("admin/", admin.site.urls),
path("api/", include("api.urls", namespace="api")),
-
# OpenAPI schema & docs (drf-spectacular)
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
- path("api/docs/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"),
+ path(
+ "api/docs/",
+ SpectacularSwaggerView.as_view(url_name="schema"),
+ name="swagger-ui",
+ ),
path("api/redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
-
path("registration/", include("registration.urls", namespace="registration")),
]
@@ -62,4 +65,4 @@
]
# Catchall for event urls at the end of the url routes
-urlpatterns += [path("", include("event.urls", namespace="event"))]
\ No newline at end of file
+urlpatterns += [path("", include("event.urls", namespace="event"))]
diff --git a/hackathon_site/hardware/admin.py b/hackathon_site/hardware/admin.py
index e97e6a9..d2f7e68 100644
--- a/hackathon_site/hardware/admin.py
+++ b/hackathon_site/hardware/admin.py
@@ -13,7 +13,14 @@
from import_export.widgets import ManyToManyWidget
from import_export.fields import Field
-from hardware.models import Hardware, Category, Order, Incident, OrderItem, OrderLockConfig
+from hardware.models import (
+ Hardware,
+ Category,
+ Order,
+ Incident,
+ OrderItem,
+ OrderLockConfig,
+)
class OrderInline(admin.TabularInline):
@@ -337,23 +344,21 @@ class OrderLockConfigAdmin(admin.ModelAdmin):
list_display = ("orders_locked", "locked_by", "locked_at", "updated_at")
readonly_fields = ("locked_by", "locked_at", "created_at", "updated_at")
fieldsets = (
- (None, {
- "fields": ("orders_locked", "reason")
- }),
- ("Lock Information", {
- "fields": ("locked_by", "locked_at"),
- "classes": ("collapse",)
- }),
- ("Timestamps", {
- "fields": ("created_at", "updated_at"),
- "classes": ("collapse",)
- }),
+ (None, {"fields": ("orders_locked", "reason")}),
+ (
+ "Lock Information",
+ {"fields": ("locked_by", "locked_at"), "classes": ("collapse",)},
+ ),
+ (
+ "Timestamps",
+ {"fields": ("created_at", "updated_at"), "classes": ("collapse",)},
+ ),
)
-
+
def has_add_permission(self, request):
# Only allow one instance (singleton pattern)
return not OrderLockConfig.objects.exists()
-
+
def has_delete_permission(self, request, obj=None):
# Don't allow deletion of the singleton instance
return False
diff --git a/hackathon_site/hardware/api_urls.py b/hackathon_site/hardware/api_urls.py
index 3d673e9..c4bb7c8 100644
--- a/hackathon_site/hardware/api_urls.py
+++ b/hackathon_site/hardware/api_urls.py
@@ -21,6 +21,14 @@
views.HardwareDetailView.as_view(),
name="hardware-detail",
),
- path("orders//", views.OrderDetailView.as_view(), name="order-detail",),
- path("order_items/", views.OrderItemListView.as_view(), name="order-item-list",),
+ path(
+ "orders//",
+ views.OrderDetailView.as_view(),
+ name="order-detail",
+ ),
+ path(
+ "order_items/",
+ views.OrderItemListView.as_view(),
+ name="order-item-list",
+ ),
]
diff --git a/hackathon_site/hardware/migrations/0001_initial.py b/hackathon_site/hardware/migrations/0001_initial.py
index c66cfa2..0a1432e 100644
--- a/hackathon_site/hardware/migrations/0001_initial.py
+++ b/hackathon_site/hardware/migrations/0001_initial.py
@@ -30,7 +30,9 @@ class Migration(migrations.Migration):
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
],
- options={"verbose_name_plural": "categories",},
+ options={
+ "verbose_name_plural": "categories",
+ },
),
migrations.CreateModel(
name="Hardware",
@@ -56,7 +58,9 @@ class Migration(migrations.Migration):
("updated_at", models.DateTimeField(auto_now=True)),
("categories", models.ManyToManyField(to="hardware.Category")),
],
- options={"verbose_name_plural": "hardware",},
+ options={
+ "verbose_name_plural": "hardware",
+ },
),
migrations.CreateModel(
name="Order",
diff --git a/hackathon_site/hardware/migrations/0002_auto_20200624_2137.py b/hackathon_site/hardware/migrations/0002_auto_20200624_2137.py
index 70e2226..f0dc9ea 100644
--- a/hackathon_site/hardware/migrations/0002_auto_20200624_2137.py
+++ b/hackathon_site/hardware/migrations/0002_auto_20200624_2137.py
@@ -11,9 +11,18 @@ class Migration(migrations.Migration):
]
operations = [
- migrations.RemoveField(model_name="incident", name="order",),
- migrations.RemoveField(model_name="order", name="hardware",),
- migrations.RemoveField(model_name="order", name="part_returned_health",),
+ migrations.RemoveField(
+ model_name="incident",
+ name="order",
+ ),
+ migrations.RemoveField(
+ model_name="order",
+ name="hardware",
+ ),
+ migrations.RemoveField(
+ model_name="order",
+ name="part_returned_health",
+ ),
migrations.AlterField(
model_name="order",
name="status",
diff --git a/hackathon_site/hardware/migrations/0006_auto_20210918_1440.py b/hackathon_site/hardware/migrations/0006_auto_20210918_1440.py
index e91b701..3d94c6f 100644
--- a/hackathon_site/hardware/migrations/0006_auto_20210918_1440.py
+++ b/hackathon_site/hardware/migrations/0006_auto_20210918_1440.py
@@ -11,6 +11,8 @@ class Migration(migrations.Migration):
operations = [
migrations.RenameField(
- model_name="order", old_name="hardware_set", new_name="hardware",
+ model_name="order",
+ old_name="hardware_set",
+ new_name="hardware",
),
]
diff --git a/hackathon_site/hardware/migrations/0013_hardware_credits.py b/hackathon_site/hardware/migrations/0013_hardware_credits.py
index cb56d33..02b3ea4 100644
--- a/hackathon_site/hardware/migrations/0013_hardware_credits.py
+++ b/hackathon_site/hardware/migrations/0013_hardware_credits.py
@@ -11,6 +11,8 @@ class Migration(migrations.Migration):
operations = [
migrations.AddField(
- model_name="hardware", name="credits", field=models.IntegerField(default=0),
+ model_name="hardware",
+ name="credits",
+ field=models.IntegerField(default=0),
),
]
diff --git a/hackathon_site/hardware/migrations/0015_order_in_progress_status.py b/hackathon_site/hardware/migrations/0015_order_in_progress_status.py
index 6000124..793b985 100644
--- a/hackathon_site/hardware/migrations/0015_order_in_progress_status.py
+++ b/hackathon_site/hardware/migrations/0015_order_in_progress_status.py
@@ -9,38 +9,37 @@ class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ('hardware', '0014_set_default_max_item_count'),
+ ("hardware", "0014_set_default_max_item_count"),
]
operations = [
# add new "In Progress" status option to existing status choices
migrations.AlterField(
- model_name='order',
- name='status',
+ model_name="order",
+ name="status",
field=models.CharField(
choices=[
- ('Submitted', 'Submitted'),
- ('In Progress', 'In Progress'),
- ('Ready for Pickup', 'Ready for Pickup'),
- ('Picked Up', 'Picked Up'),
- ('Cancelled', 'Cancelled'),
- ('Returned', 'Returned')
+ ("Submitted", "Submitted"),
+ ("In Progress", "In Progress"),
+ ("Ready for Pickup", "Ready for Pickup"),
+ ("Picked Up", "Picked Up"),
+ ("Cancelled", "Cancelled"),
+ ("Returned", "Returned"),
],
- default='Submitted',
- max_length=64
+ default="Submitted",
+ max_length=64,
),
),
# add packing_admin field to track who is currently packing the order
migrations.AddField(
- model_name='order',
- name='packing_admin',
+ model_name="order",
+ name="packing_admin",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
- related_name='packing_orders',
- to=settings.AUTH_USER_MODEL
+ related_name="packing_orders",
+ to=settings.AUTH_USER_MODEL,
),
),
]
-
diff --git a/hackathon_site/hardware/migrations/0016_orderlockconfig.py b/hackathon_site/hardware/migrations/0016_orderlockconfig.py
index 199e325..4a87a72 100644
--- a/hackathon_site/hardware/migrations/0016_orderlockconfig.py
+++ b/hackathon_site/hardware/migrations/0016_orderlockconfig.py
@@ -10,25 +10,41 @@ class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ('hardware', '0015_order_in_progress_status'),
+ ("hardware", "0015_order_in_progress_status"),
]
operations = [
migrations.CreateModel(
- name='OrderLockConfig',
+ name="OrderLockConfig",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('orders_locked', models.BooleanField(default=False)),
- ('locked_at', models.DateTimeField(blank=True, null=True)),
- ('reason', models.TextField(blank=True, default='')),
- ('created_at', models.DateTimeField(auto_now_add=True)),
- ('updated_at', models.DateTimeField(auto_now=True)),
- ('locked_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='order_locks', to=settings.AUTH_USER_MODEL)),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("orders_locked", models.BooleanField(default=False)),
+ ("locked_at", models.DateTimeField(blank=True, null=True)),
+ ("reason", models.TextField(blank=True, default="")),
+ ("created_at", models.DateTimeField(auto_now_add=True)),
+ ("updated_at", models.DateTimeField(auto_now=True)),
+ (
+ "locked_by",
+ models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="order_locks",
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
],
options={
- 'verbose_name': 'Order Lock Configuration',
- 'verbose_name_plural': 'Order Lock Configuration',
+ "verbose_name": "Order Lock Configuration",
+ "verbose_name_plural": "Order Lock Configuration",
},
),
]
-
diff --git a/hackathon_site/hardware/models.py b/hackathon_site/hardware/models.py
index 54c9c2e..9375755 100644
--- a/hackathon_site/hardware/models.py
+++ b/hackathon_site/hardware/models.py
@@ -127,7 +127,11 @@ class Order(models.Model):
request = models.JSONField(null=False)
# track which admin is currently packing this order to prevent double-packing
packing_admin = models.ForeignKey(
- User, on_delete=models.SET_NULL, null=True, blank=True, related_name="packing_orders"
+ User,
+ on_delete=models.SET_NULL,
+ null=True,
+ blank=True,
+ related_name="packing_orders",
)
created_at = models.DateTimeField(auto_now_add=True, null=False)
@@ -182,9 +186,14 @@ class OrderLockConfig(models.Model):
Admins can toggle this to prevent/allow order submissions without redeployment.
Superusers can bypass the lock for emergency situations.
"""
+
orders_locked = models.BooleanField(default=False)
locked_by = models.ForeignKey(
- User, null=True, blank=True, on_delete=models.SET_NULL, related_name="order_locks"
+ User,
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ related_name="order_locks",
)
locked_at = models.DateTimeField(null=True, blank=True)
reason = models.TextField(blank=True, default="")
@@ -207,4 +216,4 @@ def get_lock_status(cls):
def __str__(self):
status = "Locked" if self.orders_locked else "Unlocked"
- return f"Order Submissions: {status}"
\ No newline at end of file
+ return f"Order Submissions: {status}"
diff --git a/hackathon_site/hardware/serializers.py b/hackathon_site/hardware/serializers.py
index ee7ebb3..cc4ba13 100644
--- a/hackathon_site/hardware/serializers.py
+++ b/hackathon_site/hardware/serializers.py
@@ -8,7 +8,14 @@
from rest_framework import serializers
from event.models import Profile
-from hardware.models import Hardware, Category, OrderItem, Order, Incident, OrderLockConfig
+from hardware.models import (
+ Hardware,
+ Category,
+ OrderItem,
+ Order,
+ Incident,
+ OrderLockConfig,
+)
class HardwareSerializer(serializers.ModelSerializer):
@@ -142,8 +149,12 @@ class OrderListSerializer(serializers.ModelSerializer):
items = OrderItemInOrderSerializer(many=True, read_only=True)
team_code = serializers.SerializerMethodField()
total_credits = serializers.SerializerMethodField() # Add total_credits field
- packing_admin_id = serializers.SerializerMethodField() # track who is packing this order
- packing_admin_name = serializers.SerializerMethodField() # display admin name for ui
+ packing_admin_id = (
+ serializers.SerializerMethodField()
+ ) # track who is packing this order
+ packing_admin_name = (
+ serializers.SerializerMethodField()
+ ) # display admin name for ui
class Meta:
model = Order
@@ -168,15 +179,18 @@ def get_team_code(obj: Order):
def get_total_credits(self, obj):
# Directly use the model method
return obj.get_total_credits()
-
+
def get_packing_admin_id(self, obj):
# return the id of the admin currently packing this order
return obj.packing_admin.id if obj.packing_admin else None
-
+
def get_packing_admin_name(self, obj):
# return full name of admin packing the order for display purposes
if obj.packing_admin:
- return f"{obj.packing_admin.first_name} {obj.packing_admin.last_name}".strip() or obj.packing_admin.username
+ return (
+ f"{obj.packing_admin.first_name} {obj.packing_admin.last_name}".strip()
+ or obj.packing_admin.username
+ )
return None
@@ -230,7 +244,7 @@ def update(self, instance: Order, validated_data):
if status is not None:
instance.status = status
-
+
# automatically manage packing_admin based on status changes
if status == "In Progress" and instance.packing_admin is None:
# when starting to pack an order, assign current user as packing admin
@@ -240,7 +254,7 @@ def update(self, instance: Order, validated_data):
elif status in ["Ready for Pickup", "Cancelled", "Submitted"]:
# clear packing admin when order is no longer being packed
instance.packing_admin = None
-
+
if request_field is not None:
for item in request_field:
items_in_order = list(
@@ -320,7 +334,7 @@ def validate(self, data):
user = self.context["request"].user
is_test_user = user.groups.filter(name=settings.TEST_USER_GROUP).exists()
is_superuser = user.is_superuser
-
+
# Allow test users and superusers to bypass all restrictions
if not is_test_user and not is_superuser:
# Check lock status first
@@ -330,7 +344,7 @@ def validate(self, data):
"Order submissions are currently locked by administrators. "
"Please contact the hardware team for assistance."
)
-
+
# time restrictions
if datetime.now(settings.TZ_INFO) < settings.HARDWARE_SIGN_OUT_START_DATE:
raise serializers.ValidationError(
@@ -405,7 +419,7 @@ def validate(self, data):
f"Projected credits after order: {remaining_credits - new_order_credits}."
)
- for (hardware, requested_quantity) in requested_hardware.items():
+ for hardware, requested_quantity in requested_hardware.items():
team_hardware = team_unreturned_orders.get(id=hardware.id)
team_hardware_count = getattr(team_hardware, "past_order_count", 0)
if hardware.quantity_remaining - requested_quantity < 0:
@@ -424,7 +438,7 @@ def validate(self, data):
+ team_hardware_count
+ requested_quantity
)
- for (category, count) in category_counts.items():
+ for category, count in category_counts.items():
if count > category.max_per_team:
error_messages.append(
"Maximum number of items for the Category {} is reached (limit of {} items per team)".format(
@@ -446,13 +460,13 @@ def create(self, validated_data):
# The reason why doing this is because the id field stores the hardware object, django cannot translate hardware object into JSON. Therefore, loop has been used to get the hardware id and quantity requested
serialized_requested_hardware = []
- for (hardware, requested_quantity) in requested_hardware.items():
+ for hardware, requested_quantity in requested_hardware.items():
serialized_requested_hardware.append(
{"id": hardware.id, "requested_quantity": requested_quantity}
)
order_items = []
- for (hardware, requested_quantity) in requested_hardware.items():
+ for hardware, requested_quantity in requested_hardware.items():
num_order_items = min(hardware.quantity_remaining, requested_quantity)
if num_order_items <= 0:
response_data["hardware"].append(
@@ -484,7 +498,9 @@ def create(self, validated_data):
{
"hardware_id": hardware.id,
"message": "Only {} of {} {}(s) were available".format(
- num_order_items, requested_quantity, hardware.name,
+ num_order_items,
+ requested_quantity,
+ hardware.name,
),
}
)
@@ -666,9 +682,9 @@ def create(self, validated_data):
)
for quantity_idx in range(max_available_quantity):
- order_items_with_hardware[
- quantity_idx
- ].part_returned_health = hardware_item["part_returned_health"]
+ order_items_with_hardware[quantity_idx].part_returned_health = (
+ hardware_item["part_returned_health"]
+ )
order_items_with_hardware[quantity_idx].save()
if max_available_quantity > 0:
diff --git a/hackathon_site/hardware/test_api.py b/hackathon_site/hardware/test_api.py
index 0df0fae..42c27c0 100644
--- a/hackathon_site/hardware/test_api.py
+++ b/hackathon_site/hardware/test_api.py
@@ -10,7 +10,14 @@
from rest_framework.test import APITestCase
from event.models import Team, User, Profile
-from hardware.models import Hardware, Category, Order, OrderItem, Incident, OrderLockConfig
+from hardware.models import (
+ Hardware,
+ Category,
+ Order,
+ OrderItem,
+ Incident,
+ OrderLockConfig,
+)
from hardware.serializers import (
HardwareSerializer,
CategorySerializer,
@@ -269,6 +276,7 @@ def test_hardware_get_success(self):
expected_response = {
"id": self.hardware.id,
"name": "name",
+ "credits": 0,
"categories": [category.id for category in self.hardware.categories.all()],
"model_number": "model",
"manufacturer": "manufacturer",
@@ -371,11 +379,13 @@ def setUp(self):
picture="/picture/location/other",
)
self.order_item = OrderItem.objects.create(
- order=self.order, hardware=self.hardware,
+ order=self.order,
+ hardware=self.hardware,
)
self.order_item2 = OrderItem.objects.create(
- order=self.order, hardware=self.other_hardware,
+ order=self.order,
+ hardware=self.other_hardware,
)
self.incident = Incident.objects.create(
@@ -492,10 +502,12 @@ def setUp(self):
picture="/picture/location/other",
)
OrderItem.objects.create(
- order=self.order, hardware=self.hardware,
+ order=self.order,
+ hardware=self.hardware,
)
OrderItem.objects.create(
- order=self.order, hardware=self.other_hardware,
+ order=self.order,
+ hardware=self.other_hardware,
)
self.order_2 = Order.objects.create(
status="Submitted",
@@ -503,10 +515,12 @@ def setUp(self):
request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]},
)
OrderItem.objects.create(
- order=self.order_2, hardware=self.hardware,
+ order=self.order_2,
+ hardware=self.hardware,
)
OrderItem.objects.create(
- order=self.order_2, hardware=self.other_hardware,
+ order=self.order_2,
+ hardware=self.other_hardware,
)
self.order_3 = Order.objects.create(
status="Cancelled",
@@ -514,7 +528,8 @@ def setUp(self):
request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]},
)
OrderItem.objects.create(
- order=self.order_3, hardware=self.hardware,
+ order=self.order_3,
+ hardware=self.hardware,
)
self.order_4 = Order.objects.create(
status="Submitted",
@@ -522,7 +537,8 @@ def setUp(self):
request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]},
)
OrderItem.objects.create(
- order=self.order_4, hardware=self.hardware,
+ order=self.order_4,
+ hardware=self.hardware,
)
self.view_permissions = Permission.objects.filter(
content_type__app_label="hardware", codename="view_order"
@@ -683,10 +699,12 @@ def setUp(self):
picture="/picture/location/other",
)
self.order_item_1 = OrderItem.objects.create(
- order=self.order, hardware=self.hardware,
+ order=self.order,
+ hardware=self.hardware,
)
self.order_item_2 = OrderItem.objects.create(
- order=self.order, hardware=self.other_hardware,
+ order=self.order,
+ hardware=self.other_hardware,
)
self.order_2 = Order.objects.create(
status="Submitted",
@@ -694,10 +712,12 @@ def setUp(self):
request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]},
)
self.order_item_3 = OrderItem.objects.create(
- order=self.order_2, hardware=self.hardware,
+ order=self.order_2,
+ hardware=self.hardware,
)
self.order_item_4 = OrderItem.objects.create(
- order=self.order_2, hardware=self.other_hardware,
+ order=self.order_2,
+ hardware=self.other_hardware,
)
self.order_3 = Order.objects.create(
status="Cancelled",
@@ -705,7 +725,8 @@ def setUp(self):
request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]},
)
self.order_item_5 = OrderItem.objects.create(
- order=self.order_3, hardware=self.hardware,
+ order=self.order_3,
+ hardware=self.hardware,
)
self.order_4 = Order.objects.create(
status="Submitted",
@@ -713,7 +734,8 @@ def setUp(self):
request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]},
)
self.order_item_6 = OrderItem.objects.create(
- order=self.order_4, hardware=self.hardware,
+ order=self.order_4,
+ hardware=self.hardware,
)
self.view_permissions = Permission.objects.filter(
content_type__app_label="hardware", codename="view_orderitem"
@@ -830,7 +852,8 @@ def setUp(self):
)
self.order_item = OrderItem.objects.create(
- order=self.order, hardware=self.hardware,
+ order=self.order,
+ hardware=self.hardware,
)
self.request_data = {
@@ -888,7 +911,8 @@ def setUp(self):
)
self.order_item = OrderItem.objects.create(
- order=self.order, hardware=self.hardware,
+ order=self.order,
+ hardware=self.hardware,
)
self.incident = Incident.objects.create(
@@ -955,7 +979,8 @@ def setUp(self):
)
self.order_item = OrderItem.objects.create(
- order=self.order, hardware=self.hardware,
+ order=self.order,
+ hardware=self.hardware,
)
self.incident = Incident.objects.create(
@@ -1018,6 +1043,9 @@ def test_fail_change_non_existing_field(self):
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+@override_settings(
+ HARDWARE_SIGN_OUT_END_DATE=datetime.now(settings.TZ_INFO) + relativedelta(days=1)
+)
class OrderListViewPostTestCase(SetupUserMixin, APITestCase):
def setUp(self):
super().setUp()
@@ -1083,7 +1111,11 @@ def create_order(self):
self.order = Order.objects.create(
status="Cart",
team=self.team,
- request={"hardware": [{"id": 1, "quantity": 2},]},
+ request={
+ "hardware": [
+ {"id": 1, "quantity": 2},
+ ]
+ },
)
self.category1 = Category.objects.create(name="category1", max_per_team=4)
@@ -1174,7 +1206,13 @@ def test_submitting_order_as_test_user_before_start_date_success(self):
expected_response = {
"order_id": 1,
- "hardware": [{"hardware_id": simple_hardware.id, "quantity_fulfilled": 1}],
+ "hardware": [
+ {
+ "hardware_id": simple_hardware.id,
+ "hardware_name": "name",
+ "quantity_fulfilled": 1,
+ }
+ ],
"errors": [],
}
@@ -1207,7 +1245,13 @@ def test_create_simple_order(self):
expected_response = {
"order_id": 1,
- "hardware": [{"hardware_id": simple_hardware.id, "quantity_fulfilled": 1}],
+ "hardware": [
+ {
+ "hardware_id": simple_hardware.id,
+ "hardware_name": "name",
+ "quantity_fulfilled": 1,
+ }
+ ],
"errors": [],
}
@@ -1348,7 +1392,13 @@ def test_hardware_limit_returned_orders(self):
expected_response = {
"order_id": 2,
- "hardware": [{"hardware_id": hardware.id, "quantity_fulfilled": 4}],
+ "hardware": [
+ {
+ "hardware_id": hardware.id,
+ "hardware_name": "name",
+ "quantity_fulfilled": 4,
+ }
+ ],
"errors": [],
}
@@ -1389,7 +1439,13 @@ def test_hardware_limit_cancelled_orders(self):
expected_response = {
"order_id": 2,
- "hardware": [{"hardware_id": hardware.id, "quantity_fulfilled": 1}],
+ "hardware": [
+ {
+ "hardware_id": hardware.id,
+ "hardware_name": "name",
+ "quantity_fulfilled": 1,
+ }
+ ],
"errors": [],
}
@@ -1530,7 +1586,13 @@ def test_category_limit_returned_orders(self):
expected_response = {
"order_id": 2,
- "hardware": [{"hardware_id": hardware.id, "quantity_fulfilled": 4}],
+ "hardware": [
+ {
+ "hardware_id": hardware.id,
+ "hardware_name": "name",
+ "quantity_fulfilled": 4,
+ }
+ ],
"errors": [],
}
@@ -1571,7 +1633,13 @@ def test_category_limit_cancelled_orders(self):
expected_response = {
"order_id": 2,
- "hardware": [{"hardware_id": hardware.id, "quantity_fulfilled": 1}],
+ "hardware": [
+ {
+ "hardware_id": hardware.id,
+ "hardware_name": "name",
+ "quantity_fulfilled": 1,
+ }
+ ],
"errors": [],
}
@@ -1682,10 +1750,12 @@ def test_multiple_hardware_success(self):
self.assertEqual(len(response_hardware), 2)
expected_response_hardware_1 = {
"hardware_id": hardware_1.id,
+ "hardware_name": "name",
"quantity_fulfilled": num_hardware_1_requested,
}
expected_response_hardware_2 = {
"hardware_id": hardware_2.id,
+ "hardware_name": "name",
"quantity_fulfilled": num_hardware_2_requested,
}
self.assertCountEqual(
@@ -1738,6 +1808,7 @@ def test_repeated_hardware_input_ids(self):
"hardware": [
{
"hardware_id": hardware.id,
+ "hardware_name": "name",
"quantity_fulfilled": num_hardware_requested,
}
],
@@ -1935,7 +2006,7 @@ def test_user_lack_perms(self):
def test_successful_status_change(self):
self._login(self.change_permissions)
- request_data = {"status": "Ready for Pickup"}
+ request_data = {"status": "In Progress"}
response = self.client.patch(self._build_view(self.pk), request_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(request_data["status"], Order.objects.get(id=self.pk).status)
@@ -2002,7 +2073,8 @@ def setUp(self):
)
self.order_item = OrderItem.objects.create(
- order=self.order, hardware=self.hardware,
+ order=self.order,
+ hardware=self.hardware,
)
self.request_data = {
@@ -2052,7 +2124,8 @@ def setUp(self):
super().setUp()
self.view = reverse("api:hardware:order-lock")
self.admin_permissions = Permission.objects.filter(
- content_type__app_label="hardware", codename__in=["view_order", "change_order"]
+ content_type__app_label="hardware",
+ codename__in=["view_order", "change_order"],
)
# Ensure lock config starts in unlocked state
lock_config = OrderLockConfig.get_lock_status()
@@ -2085,37 +2158,48 @@ def test_toggle_lock_success(self):
admin_group = Group.objects.get(name="Hardware Site Admins")
self.user.groups.add(admin_group)
self._login()
-
+
# Lock orders
request_data = {"orders_locked": True, "reason": "Test lock"}
response = self.client.post(self.view, request_data, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = response.json()
self.assertTrue(data["orders_locked"])
-
+
# Verify in database
lock_config = OrderLockConfig.get_lock_status()
self.assertTrue(lock_config.orders_locked)
self.assertEqual(lock_config.locked_by, self.user)
self.assertIsNotNone(lock_config.locked_at)
-
+
# Unlock orders
request_data = {"orders_locked": False}
response = self.client.post(self.view, request_data, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = response.json()
self.assertFalse(data["orders_locked"])
-
+
# Verify in database
lock_config.refresh_from_db()
self.assertFalse(lock_config.orders_locked)
self.assertIsNone(lock_config.locked_by)
+@override_settings(
+ HARDWARE_SIGN_OUT_END_DATE=datetime.now(settings.TZ_INFO) + relativedelta(days=1)
+)
class OrderSubmissionWithLockTestCase(SetupUserMixin, APITestCase):
def setUp(self):
super().setUp()
self.view = reverse("api:hardware:order-list")
+ self.team = Team.objects.create()
+ self.user2 = User.objects.create_user(
+ username="frank@johnston.com",
+ password="hellothere31415",
+ email="frank@johnston.com",
+ first_name="Frank",
+ last_name="Johnston",
+ )
self.hardware = Hardware.objects.create(
name="Test Hardware",
model_number="model",
@@ -2130,40 +2214,48 @@ def setUp(self):
lock_config.orders_locked = False
lock_config.save()
+ def create_min_number_of_profiles(self):
+ Profile.objects.create(user=self.user, team=self.team)
+ Profile.objects.create(user=self.user2, team=self.team)
+
def test_order_submission_when_locked(self):
"""Test that regular users cannot submit orders when locked"""
self._login()
self.create_min_number_of_profiles()
-
+
# Lock orders
lock_config = OrderLockConfig.get_lock_status()
lock_config.orders_locked = True
lock_config.save()
-
+
request_data = {"hardware": [{"id": self.hardware.id, "quantity": 1}]}
response = self.client.post(self.view, request_data, format="json")
-
+
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn("non_field_errors", response.json())
- self.assertIn("locked by administrators", response.json()["non_field_errors"][0])
+ self.assertIn(
+ "locked by administrators", response.json()["non_field_errors"][0]
+ )
@override_settings(
- HARDWARE_SIGN_OUT_START_DATE=datetime.now(settings.TZ_INFO) - relativedelta(days=1),
- HARDWARE_SIGN_OUT_END_DATE=datetime.now(settings.TZ_INFO) + relativedelta(days=1),
+ HARDWARE_SIGN_OUT_START_DATE=datetime.now(settings.TZ_INFO)
+ - relativedelta(days=1),
+ HARDWARE_SIGN_OUT_END_DATE=datetime.now(settings.TZ_INFO)
+ + relativedelta(days=1),
)
def test_order_submission_when_unlocked(self):
"""Test that users can submit orders when unlocked"""
self._login()
self.create_min_number_of_profiles()
-
+
# Ensure unlocked
lock_config = OrderLockConfig.get_lock_status()
lock_config.orders_locked = False
lock_config.save()
-
+
request_data = {"hardware": [{"id": self.hardware.id, "quantity": 1}]}
response = self.client.post(self.view, request_data, format="json")
-
+
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_superuser_can_bypass_lock(self):
@@ -2172,13 +2264,13 @@ def test_superuser_can_bypass_lock(self):
self.user.is_superuser = True
self.user.save()
self.create_min_number_of_profiles()
-
+
# Lock orders
lock_config = OrderLockConfig.get_lock_status()
lock_config.orders_locked = True
lock_config.save()
-
+
request_data = {"hardware": [{"id": self.hardware.id, "quantity": 1}]}
response = self.client.post(self.view, request_data, format="json")
-
+
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
diff --git a/hackathon_site/hardware/tests.py b/hackathon_site/hardware/tests.py
index bdf798e..3b86146 100644
--- a/hackathon_site/hardware/tests.py
+++ b/hackathon_site/hardware/tests.py
@@ -35,6 +35,7 @@ def test_base_case_no_order_items(self):
expected_response = {
"id": 1,
"name": "name",
+ "credits": 0,
"categories": [self.category1.id, self.category2.id],
"model_number": "model",
"manufacturer": "manufacturer",
@@ -57,7 +58,10 @@ def test_some_items_cancelled(self):
team=team,
request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]},
)
- order_item_1 = OrderItem.objects.create(order=order, hardware=self.hardware,)
+ order_item_1 = OrderItem.objects.create(
+ order=order,
+ hardware=self.hardware,
+ )
self.hardware.refresh_from_db()
hardware_serializer = HardwareSerializer(self.hardware)
@@ -65,6 +69,7 @@ def test_some_items_cancelled(self):
expected_response = {
"id": 1,
"name": "name",
+ "credits": 0,
"categories": [self.category1.id, self.category2.id],
"model_number": "model",
"manufacturer": "manufacturer",
@@ -89,13 +94,17 @@ def test_some_items_returned(self):
order_item_1 = OrderItem.objects.create(
order=order, hardware=self.hardware, part_returned_health="Healthy"
)
- order_item_2 = OrderItem.objects.create(order=order, hardware=self.hardware,)
+ order_item_2 = OrderItem.objects.create(
+ order=order,
+ hardware=self.hardware,
+ )
self.hardware.refresh_from_db()
hardware_serializer = HardwareSerializer(self.hardware)
expected_response = {
"id": 1,
"name": "name",
+ "credits": 0,
"categories": [self.category1.id, self.category2.id],
"model_number": "model",
"manufacturer": "manufacturer",
@@ -136,8 +145,14 @@ def test_some_items_none_returned(self):
team=team,
request={"hardware": [{"id": 1, "quantity": 2}]},
)
- order_item_1 = OrderItem.objects.create(order=order, hardware=self.hardware,)
- order_item_2 = OrderItem.objects.create(order=order, hardware=self.hardware,)
+ order_item_1 = OrderItem.objects.create(
+ order=order,
+ hardware=self.hardware,
+ )
+ order_item_2 = OrderItem.objects.create(
+ order=order,
+ hardware=self.hardware,
+ )
self.hardware.refresh_from_db()
hardware_serializer = HardwareSerializer(self.hardware)
self.assertEqual(hardware_serializer.data["quantity_remaining"], 2)
@@ -152,7 +167,10 @@ def test_some_items_returned_healthy(self):
order_item_1 = OrderItem.objects.create(
order=order, hardware=self.hardware, part_returned_health="Healthy"
)
- order_item_2 = OrderItem.objects.create(order=order, hardware=self.hardware,)
+ order_item_2 = OrderItem.objects.create(
+ order=order,
+ hardware=self.hardware,
+ )
self.hardware.refresh_from_db()
hardware_serializer = HardwareSerializer(self.hardware)
self.assertEqual(hardware_serializer.data["quantity_remaining"], 3)
@@ -168,7 +186,8 @@ def test_some_items_returned_not_healthy(self):
order=order, hardware=self.hardware, part_returned_health="Broken"
)
OrderItem.objects.create(
- order=order, hardware=self.hardware,
+ order=order,
+ hardware=self.hardware,
)
self.hardware.refresh_from_db()
hardware_serializer = HardwareSerializer(self.hardware)
@@ -181,8 +200,14 @@ def test_some_items_cancelled(self):
team=team,
request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]},
)
- order_item_1 = OrderItem.objects.create(order=order, hardware=self.hardware,)
- order_item_2 = OrderItem.objects.create(order=order, hardware=self.hardware,)
+ order_item_1 = OrderItem.objects.create(
+ order=order,
+ hardware=self.hardware,
+ )
+ order_item_2 = OrderItem.objects.create(
+ order=order,
+ hardware=self.hardware,
+ )
self.hardware.refresh_from_db()
hardware_serializer = HardwareSerializer(self.hardware)
self.assertEqual(hardware_serializer.data["quantity_remaining"], 4)
@@ -326,6 +351,9 @@ def test_empty_order(self):
"team_id": self.team.id,
"team_code": self.team.team_code,
"status": "Cart",
+ "total_credits": 0,
+ "packing_admin_id": None,
+ "packing_admin_name": None,
"items": [],
"created_at": serializers.DateTimeField().to_representation(
order.created_at
@@ -349,7 +377,10 @@ def test_hardware_set(self):
item_1 = OrderItem.objects.create(
order=order, hardware=self.hardware, part_returned_health="Healthy"
)
- item_2 = OrderItem.objects.create(order=order, hardware=self.other_hardware,)
+ item_2 = OrderItem.objects.create(
+ order=order,
+ hardware=self.other_hardware,
+ )
self.hardware.refresh_from_db()
self.other_hardware.refresh_from_db()
order_serializer = OrderListSerializer(order).data
@@ -358,6 +389,9 @@ def test_hardware_set(self):
"team_id": self.team.id,
"team_code": self.team.team_code,
"status": "Cart",
+ "total_credits": 0,
+ "packing_admin_id": None,
+ "packing_admin_name": None,
"items": [
{
"id": item_1.id,
diff --git a/hackathon_site/hardware/views.py b/hackathon_site/hardware/views.py
index 1506a6a..d4a59e8 100644
--- a/hackathon_site/hardware/views.py
+++ b/hackathon_site/hardware/views.py
@@ -22,7 +22,14 @@
IncidentFilter,
OrderItemFilter,
)
-from hardware.models import Hardware, Category, Order, Incident, OrderItem, OrderLockConfig
+from hardware.models import (
+ Hardware,
+ Category,
+ Order,
+ Incident,
+ OrderItem,
+ OrderLockConfig,
+)
from hardware.serializers import (
CategorySerializer,
@@ -302,9 +309,9 @@ def patch(self, request, *args, **kwargs):
# Add the cancellation message to the context if one was provided.
if cancellation_message:
- render_to_string_context[
- "cancellation_message"
- ] = cancellation_message
+ render_to_string_context["cancellation_message"] = (
+ cancellation_message
+ )
send_mail(
subject=render_to_string(
@@ -332,13 +339,16 @@ def patch(self, request, *args, **kwargs):
}
profile.user.email_user(
subject=render_to_string(
- self.update_order_email_subject_template, context,
+ self.update_order_email_subject_template,
+ context,
),
message=render_to_string(
- self.update_order_email_template_participant, context,
+ self.update_order_email_template_participant,
+ context,
),
html_message=render_to_string(
- self.update_order_email_template_participant, context,
+ self.update_order_email_template_participant,
+ context,
),
from_email=settings.DEFAULT_FROM_EMAIL,
connection=connection,
@@ -442,6 +452,7 @@ class OrderLockView(generics.GenericAPIView):
GET: Returns current lock status (accessible to all authenticated users)
POST: Toggles lock status (admin only)
"""
+
def get_permissions(self):
if self.request.method == "POST":
return [UserIsAdmin()]
@@ -450,29 +461,37 @@ def get_permissions(self):
def get(self, request, *args, **kwargs):
"""Get current lock status"""
lock_config = OrderLockConfig.get_lock_status()
- return Response({
- "orders_locked": lock_config.orders_locked,
- "locked_by": lock_config.locked_by.email if lock_config.locked_by else None,
- "locked_at": lock_config.locked_at,
- "reason": lock_config.reason,
- })
+ return Response(
+ {
+ "orders_locked": lock_config.orders_locked,
+ "locked_by": (
+ lock_config.locked_by.email if lock_config.locked_by else None
+ ),
+ "locked_at": lock_config.locked_at,
+ "reason": lock_config.reason,
+ }
+ )
def post(self, request, *args, **kwargs):
"""Toggle lock status (admin only)"""
from django.utils import timezone
-
+
lock_config = OrderLockConfig.get_lock_status()
new_lock_state = request.data.get("orders_locked", False)
-
+
lock_config.orders_locked = new_lock_state
lock_config.locked_by = request.user if new_lock_state else None
lock_config.locked_at = timezone.now() if new_lock_state else None
lock_config.reason = request.data.get("reason", "")
lock_config.save()
-
- return Response({
- "orders_locked": lock_config.orders_locked,
- "locked_by": lock_config.locked_by.email if lock_config.locked_by else None,
- "locked_at": lock_config.locked_at,
- "reason": lock_config.reason,
- })
+
+ return Response(
+ {
+ "orders_locked": lock_config.orders_locked,
+ "locked_by": (
+ lock_config.locked_by.email if lock_config.locked_by else None
+ ),
+ "locked_at": lock_config.locked_at,
+ "reason": lock_config.reason,
+ }
+ )
diff --git a/hackathon_site/registration/forms.py b/hackathon_site/registration/forms.py
index 312e04b..f0ab331 100644
--- a/hackathon_site/registration/forms.py
+++ b/hackathon_site/registration/forms.py
@@ -247,7 +247,9 @@ def clean(self):
return cleaned_data
def clean_age(self):
- user_age = self.cleaned_data["age"]
+ user_age = self.cleaned_data.get("age")
+ if user_age is None:
+ return user_age
# Check if the age is "22+"
if user_age == "22+":
return user_age
@@ -259,8 +261,8 @@ def clean_age(self):
return user_age
def handle_free_response_pronouns(self):
- user_pronouns = self.cleaned_data["pronouns"]
- user_free_response_pronouns = self.cleaned_data["free_response_pronouns"]
+ user_pronouns = self.cleaned_data.get("pronouns")
+ user_free_response_pronouns = self.cleaned_data.get("free_response_pronouns")
if user_pronouns == "other" and not user_free_response_pronouns:
raise forms.ValidationError(
_(
@@ -270,8 +272,8 @@ def handle_free_response_pronouns(self):
)
def handle_free_response_gender(self):
- user_gender = self.cleaned_data["gender"]
- user_free_response_gender = self.cleaned_data["free_response_gender"]
+ user_gender = self.cleaned_data.get("gender")
+ user_free_response_gender = self.cleaned_data.get("free_response_gender")
if user_gender == "prefer-to-self-describe" and not user_free_response_gender:
raise forms.ValidationError(
_(
@@ -281,10 +283,10 @@ def handle_free_response_gender(self):
)
def handle_free_response_dietary_restrictions(self):
- user_dietary_restrictions = self.cleaned_data["dietary_restrictions"]
- user_free_response_dietary_restrictions = self.cleaned_data[
+ user_dietary_restrictions = self.cleaned_data.get("dietary_restrictions")
+ user_free_response_dietary_restrictions = self.cleaned_data.get(
"free_response_dietary_restrictions"
- ]
+ )
if (
user_dietary_restrictions == "allergies"
or user_dietary_restrictions == "other"
@@ -295,10 +297,10 @@ def handle_free_response_dietary_restrictions(self):
)
def handle_free_response_sexual_identity(self):
- user_sexual_identity = self.cleaned_data["sexual_identity"]
- user_free_response_sexual_identity = self.cleaned_data[
+ user_sexual_identity = self.cleaned_data.get("sexual_identity")
+ user_free_response_sexual_identity = self.cleaned_data.get(
"free_response_sexual_identity"
- ]
+ )
if (
user_sexual_identity == "different-identity"
and not user_free_response_sexual_identity
@@ -311,10 +313,12 @@ def handle_free_response_sexual_identity(self):
)
def handle_highest_formal_education(self):
- user_highest_formal_education = self.cleaned_data["highest_formal_education"]
- user_free_response_highest_formal_education = self.cleaned_data[
+ user_highest_formal_education = self.cleaned_data.get(
+ "highest_formal_education"
+ )
+ user_free_response_highest_formal_education = self.cleaned_data.get(
"free_response_highest_formal_education"
- ]
+ )
if (
user_highest_formal_education == "other"
and not user_free_response_highest_formal_education
diff --git a/hackathon_site/registration/migrations/0002_auto_20200910_1606.py b/hackathon_site/registration/migrations/0002_auto_20200910_1606.py
index 0d7192f..3cf48e7 100644
--- a/hackathon_site/registration/migrations/0002_auto_20200910_1606.py
+++ b/hackathon_site/registration/migrations/0002_auto_20200910_1606.py
@@ -15,9 +15,18 @@ class Migration(migrations.Migration):
]
operations = [
- migrations.RemoveField(model_name="application", name="mlh_conduct_agree",),
- migrations.RemoveField(model_name="application", name="mlh_data_agree",),
- migrations.RemoveField(model_name="application", name="preferred_name",),
+ migrations.RemoveField(
+ model_name="application",
+ name="mlh_conduct_agree",
+ ),
+ migrations.RemoveField(
+ model_name="application",
+ name="mlh_data_agree",
+ ),
+ migrations.RemoveField(
+ model_name="application",
+ name="preferred_name",
+ ),
migrations.AddField(
model_name="application",
name="conduct_agree",
diff --git a/hackathon_site/registration/migrations/0004_application_rsvp.py b/hackathon_site/registration/migrations/0004_application_rsvp.py
index 0006de9..d82b512 100644
--- a/hackathon_site/registration/migrations/0004_application_rsvp.py
+++ b/hackathon_site/registration/migrations/0004_application_rsvp.py
@@ -11,6 +11,8 @@ class Migration(migrations.Migration):
operations = [
migrations.AddField(
- model_name="application", name="rsvp", field=models.BooleanField(null=True),
+ model_name="application",
+ name="rsvp",
+ field=models.BooleanField(null=True),
),
]
diff --git a/hackathon_site/registration/migrations/0005_auto_20240103_2009.py b/hackathon_site/registration/migrations/0005_auto_20240103_2009.py
index bd632ab..bcff489 100644
--- a/hackathon_site/registration/migrations/0005_auto_20240103_2009.py
+++ b/hackathon_site/registration/migrations/0005_auto_20240103_2009.py
@@ -10,11 +10,26 @@ class Migration(migrations.Migration):
]
operations = [
- migrations.RemoveField(model_name="application", name="birthday",),
- migrations.RemoveField(model_name="application", name="data_agree",),
- migrations.RemoveField(model_name="application", name="q1",),
- migrations.RemoveField(model_name="application", name="q2",),
- migrations.RemoveField(model_name="application", name="q3",),
+ migrations.RemoveField(
+ model_name="application",
+ name="birthday",
+ ),
+ migrations.RemoveField(
+ model_name="application",
+ name="data_agree",
+ ),
+ migrations.RemoveField(
+ model_name="application",
+ name="q1",
+ ),
+ migrations.RemoveField(
+ model_name="application",
+ name="q2",
+ ),
+ migrations.RemoveField(
+ model_name="application",
+ name="q3",
+ ),
migrations.AddField(
model_name="application",
name="age",
diff --git a/hackathon_site/registration/migrations/0013_auto_20251128_1027.py b/hackathon_site/registration/migrations/0013_auto_20251128_1027.py
index 81442ac..ce7a55a 100644
--- a/hackathon_site/registration/migrations/0013_auto_20251128_1027.py
+++ b/hackathon_site/registration/migrations/0013_auto_20251128_1027.py
@@ -7,37 +7,59 @@
class Migration(migrations.Migration):
dependencies = [
- ('registration', '0012_auto_20250125_2136'),
+ ("registration", "0012_auto_20250125_2136"),
]
operations = [
migrations.RemoveField(
- model_name='application',
- name='what_role_in_team_setting',
+ model_name="application",
+ name="what_role_in_team_setting",
),
migrations.AddField(
- model_name='application',
- name='one_million_dollars_investment',
- field=models.TextField(default='', help_text='If you had a million dollars to invest in one technology brand, what would it be and why?', max_length=1000),
+ model_name="application",
+ name="one_million_dollars_investment",
+ field=models.TextField(
+ default="",
+ help_text="If you had a million dollars to invest in one technology brand, what would it be and why?",
+ max_length=1000,
+ ),
),
migrations.AlterField(
- model_name='application',
- name='conduct_agree',
- field=models.BooleanField(default=False, help_text='I have read and agree to the MLH code of conduct.'),
+ model_name="application",
+ name="conduct_agree",
+ field=models.BooleanField(
+ default=False,
+ help_text='I have read and agree to the MLH code of conduct.',
+ ),
),
migrations.AlterField(
- model_name='application',
- name='logistics_agree',
- field=models.BooleanField(default=False, help_text='I authorize you to share my application/registration information with Major League Hacking for event administration, ranking, and MLH administration in-line with the MLH Privacy Policy. I further agree to the terms of both the MLH Contest Terms and Conditions and the MLH Privacy Policy.'),
+ model_name="application",
+ name="logistics_agree",
+ field=models.BooleanField(
+ default=False,
+ help_text='I authorize you to share my application/registration information with Major League Hacking for event administration, ranking, and MLH administration in-line with the MLH Privacy Policy. I further agree to the terms of both the MLH Contest Terms and Conditions and the MLH Privacy Policy.',
+ ),
),
migrations.AlterField(
- model_name='application',
- name='phone_number',
- field=models.CharField(max_length=20, validators=[django.core.validators.RegexValidator('^\\+?\\d[\\d\\s()-]{7,}$', message='Enter a valid phone number.')]),
+ model_name="application",
+ name="phone_number",
+ field=models.CharField(
+ max_length=20,
+ validators=[
+ django.core.validators.RegexValidator(
+ "^\\+?\\d[\\d\\s()-]{7,}$",
+ message="Enter a valid phone number.",
+ )
+ ],
+ ),
),
migrations.AlterField(
- model_name='application',
- name='what_past_experience',
- field=models.TextField(default='', help_text='Describe the most significant challenges you faced in a project/hackathon. What did you learn from them?', max_length=1000),
+ model_name="application",
+ name="what_past_experience",
+ field=models.TextField(
+ default="",
+ help_text="Describe the most significant challenges you faced in a project/hackathon. What did you learn from them?",
+ max_length=1000,
+ ),
),
]
diff --git a/hackathon_site/registration/models.py b/hackathon_site/registration/models.py
index 1db6f04..cbc0c97 100644
--- a/hackathon_site/registration/models.py
+++ b/hackathon_site/registration/models.py
@@ -202,7 +202,7 @@ class Application(models.Model):
null=False,
validators=[
validators.RegexValidator(
- r"^\+?\d[\d\s()-]{7,}$",
+ r"^\+?(?:[\s()-]*\d){10,13}[\s()-]*$",
message="Enter a valid phone number.",
)
],
diff --git a/hackathon_site/registration/test_forms.py b/hackathon_site/registration/test_forms.py
index e8d0b97..0d3b321 100644
--- a/hackathon_site/registration/test_forms.py
+++ b/hackathon_site/registration/test_forms.py
@@ -86,23 +86,28 @@ def setUp(self):
self.data = {
"age": "18",
"pronouns": "he-him",
- "gender": "male",
- "ethnicity": "chinese",
+ "gender": "man",
"phone_number": "1234567890",
+ "city": "Toronto",
"country": "canada",
"dietary_restrictions": "halal",
"tshirt_size": "L",
- "underrepresented_community": "no",
- "sexual_orientation": "straight",
+ "under_represented_group": "no",
+ "sexual_identity": "heterosexual-or-straight",
+ "highest_formal_education": "secondary-or-high-school",
"school": "UofT",
- "study_level": "gradschool",
+ "study_level": "graduate",
"graduation_year": "2025",
"program": "computer science",
"how_many_hackathons": "2",
- "what_hackathon_experience": "foo",
+ "past_hackathon_info": "n/a",
+ "what_past_experience": "foo",
"why_participate": "foo",
"what_technical_experience": "foo",
+ "one_million_dollars_investment": "foo",
"discovery_method": "instagram",
+ "conduct_agree": True,
+ "logistics_agree": True,
}
self.files = self._build_files()
diff --git a/hackathon_site/registration/test_views.py b/hackathon_site/registration/test_views.py
index 1ef66f6..eaa1c32 100644
--- a/hackathon_site/registration/test_views.py
+++ b/hackathon_site/registration/test_views.py
@@ -174,23 +174,28 @@ def setUp(self):
self.data = {
"age": "18",
"pronouns": "he-him",
- "gender": "male",
- "ethnicity": "chinese",
+ "gender": "man",
"phone_number": "1234567890",
+ "city": "Toronto",
"country": "canada",
"dietary_restrictions": "halal",
"tshirt_size": "L",
- "underrepresented_community": "no",
- "sexual_orientation": "straight",
+ "under_represented_group": "no",
+ "sexual_identity": "heterosexual-or-straight",
+ "highest_formal_education": "secondary-or-high-school",
"school": "UofT",
- "study_level": "gradschool",
+ "study_level": "graduate",
"graduation_year": "2025",
"program": "computer science",
"how_many_hackathons": "2",
- "what_hackathon_experience": "foo",
+ "past_hackathon_info": "n/a",
+ "what_past_experience": "foo",
"why_participate": "foo",
"what_technical_experience": "foo",
+ "one_million_dollars_investment": "foo",
"discovery_method": "instagram",
+ "conduct_agree": True,
+ "logistics_agree": True,
}
self.team = Team.objects.create()
diff --git a/hackathon_site/registration/urls.py b/hackathon_site/registration/urls.py
index 2be8472..f0fdd85 100644
--- a/hackathon_site/registration/urls.py
+++ b/hackathon_site/registration/urls.py
@@ -16,7 +16,11 @@
),
name="signup_complete",
),
- path("signup/closed/", views.SignUpClosedView.as_view(), name="signup_closed",),
+ path(
+ "signup/closed/",
+ views.SignUpClosedView.as_view(),
+ name="signup_closed",
+ ),
path(
"activate//",
views.ActivationView.as_view(),
diff --git a/hackathon_site/review/admin.py b/hackathon_site/review/admin.py
index 5acb39a..be934aa 100644
--- a/hackathon_site/review/admin.py
+++ b/hackathon_site/review/admin.py
@@ -386,7 +386,9 @@ def build_reviewer_assignment_cache_key(user_id):
# Update the active reviewers cache set, 20 minute TTL
cache.set(
- active_reviewers_set_cache_key, active_reviewers, timeout=60 * 20,
+ active_reviewers_set_cache_key,
+ active_reviewers,
+ timeout=60 * 20,
)
team_review_page = reverse(
diff --git a/hackathon_site/review/tests.py b/hackathon_site/review/tests.py
index bb090a5..5c1e640 100644
--- a/hackathon_site/review/tests.py
+++ b/hackathon_site/review/tests.py
@@ -72,7 +72,9 @@ def _create_teams_and_reviews_for_mail_tests(
MagicMock(return_value=older_updated_date),
):
self._review(
- application, status="Waitlisted", decision_sent_date=sent_date,
+ application,
+ status="Waitlisted",
+ decision_sent_date=sent_date,
)
elif i == 3:
self._review(
@@ -213,7 +215,7 @@ def test_correct_text_in_accepted_email(self):
response = self.client.post(self.view, data=self.form_data)
clean = re.compile("<.*?>")
- clean_mail_body = re.sub(clean, "", mail.outbox[0].body)
+ clean_mail_body = re.sub(r"\s+", " ", re.sub(clean, "", mail.outbox[0].body))
link = f"http://testserver{reverse('event:dashboard')}"
@@ -249,21 +251,18 @@ def test_correct_text_in_waitlisted_email(self):
self.client.post(self.view, data=self.form_data)
clean = re.compile("<.*?>")
- clean_mail_body = re.sub(clean, "", mail.outbox[0].body)
+ clean_mail_body = re.sub(r"\s+", " ", re.sub(clean, "", mail.outbox[0].body))
link = f"http://testserver{reverse('event:dashboard')}"
self.assertIn(link, mail.outbox[0].body)
self.assertIn(settings.HACKATHON_NAME, mail.outbox[0].body)
- self.assertIn(
- settings.FINAL_REVIEW_RESPONSE_DATE.strftime("%B %-d, %Y"),
- mail.outbox[0].body,
- )
+ self.assertIn("You will hear back from us by", clean_mail_body)
self.assertIn(
f"{ settings.HACKATHON_NAME } Application Decision", mail.outbox[0].subject
)
self.assertIn(
- f"we have decided to defer your application to the next round",
+ "we have decided to place your application on the waitlist",
clean_mail_body,
)
@@ -276,7 +275,7 @@ def test_correct_text_in_rejected_email(self):
self.client.post(self.view, data=self.form_data)
clean = re.compile("<.*?>")
- clean_mail_body = re.sub(clean, "", mail.outbox[0].body)
+ clean_mail_body = re.sub(r"\s+", " ", re.sub(clean, "", mail.outbox[0].body))
self.assertIn(settings.HACKATHON_NAME, mail.outbox[0].body)
self.assertIn(