From 10808b5340ebbaad211c1c3f7045ab4cd378f0be Mon Sep 17 00:00:00 2001 From: Marlan McInnes-Taylor Date: Sun, 18 Aug 2024 19:42:37 -0500 Subject: [PATCH 01/16] refactored core app views to class based format --- src/core/urls.py | 9 ++- src/core/views.py | 174 ++++++++++++++++++++++++---------------------- 2 files changed, 95 insertions(+), 88 deletions(-) diff --git a/src/core/urls.py b/src/core/urls.py index cf102a5f..4beb086a 100644 --- a/src/core/urls.py +++ b/src/core/urls.py @@ -3,9 +3,8 @@ from . import views urlpatterns = [ - # contest suite homepage - path('', views.index, name='index'), - path('contact/', views.contact, name='contact'), - path('faq/', views.faq, name='faq'), - path('teams/', views.teams, name='teams'), + path('', views.IndexTemplateView.as_view(), name='index'), + path('contact/', views.ContactTemplateView.as_view(), name='contact'), + path('faq/', views.FaqTemplateView.as_view(), name='faq'), + path('teams/', views.TeamsTemplateView.as_view(), name='teams'), ] diff --git a/src/core/views.py b/src/core/views.py index 503690d7..6398ffb0 100644 --- a/src/core/views.py +++ b/src/core/views.py @@ -1,5 +1,5 @@ -from django.shortcuts import render from django.core.cache import cache +from django.views.generic.base import TemplateView import requests as req @@ -12,114 +12,122 @@ # Create your views here. -def index(request): +class IndexTemplateView(TemplateView): """ View to display site index(home) page. Displays announcements, DOMjudge server status, and information on extra credit courses, participation, teams, and looking for group participants. """ - context = {} + template_name = 'core/index.html' - # Get cached DOMjudge server status or ping server - if cache.get('domjudge_status'): - context['domjudge_status'] = cache.get('domjudge_status') - else: - try: - r = req.head(DOMJUDGE_URL) - except req.ConnectionError: - context['domjudge_status'] = None + def get_context_data(self, **kwargs): + # Call the base implementation first to get a context + context = super().get_context_data(**kwargs) + + # Get cached DOMjudge server status or ping server + if cache.get('domjudge_status'): + context['domjudge_status'] = cache.get('domjudge_status') else: - context['domjudge_status'] = r.status_code - cache.set('domjudge_status', r.status_code, CACHE_TIMEOUT) - - # Get contest object or set to None - context['contest'] = cache.get_or_set( - 'contest_model', Contest.objects.first(), CACHE_TIMEOUT) - - # Get published announcements - context['announcements'] = (Announcement.objects.filter(status=1)) - # Get all courses - context['courses'] = Course.objects.all() - - if context['contest'] and context['contest'].lfg_active: - # Get Looking For Group profile totals - context['lfg_profiles_upper'] = LFGProfile.objects.filter(active=True).filter(division=1).count() - context['lfg_profiles_lower'] = LFGProfile.objects.filter(active=True).filter(division=2).count() - - ### Teams ### - - teams_set = Team.objects.all() - participants_set = Profile.objects.all() - - # Aggregate upper division team and participant info - upper_teams_set = teams_set.filter(division=1).filter( - faculty=False).exclude(num_members=0) - context['num_upper_teams'] = upper_teams_set.count() - context['num_upper_participants'] = participants_set.filter( - team__division=1).count() - - # Aggregate lower division team and participant info - lower_teams_set = teams_set.filter(division=2).filter( - faculty=False).exclude(num_members=0) - context['num_lower_teams'] = lower_teams_set.count() - context['num_lower_participants'] = participants_set.filter( - team__division=2).count() - - # Aggregate faculty team and participant info - faculty_teams_set = teams_set.filter(faculty=True).exclude(num_members=0) - context['num_faculty_teams'] = faculty_teams_set.count() - context['num_faculty_participants'] = participants_set.filter( - team__faculty=True).count() - - return render(request, 'core/index.html', context) - - -def contact(request): + try: + r = req.head(DOMJUDGE_URL) + except req.ConnectionError: + context['domjudge_status'] = None + else: + context['domjudge_status'] = r.status_code + cache.set('domjudge_status', r.status_code, CACHE_TIMEOUT) + + # Get contest object or set to None + context['contest'] = cache.get_or_set( + 'contest_model', Contest.objects.first(), CACHE_TIMEOUT) + + # Get published announcements + context['announcements'] = (Announcement.objects.filter(status=1)) + # Get all courses + context['courses'] = Course.objects.all() + + if context['contest'] and context['contest'].lfg_active: + # Get Looking For Group profile totals + context['lfg_profiles_upper'] = LFGProfile.objects.filter(active=True).filter(division=1).count() + context['lfg_profiles_lower'] = LFGProfile.objects.filter(active=True).filter(division=2).count() + + ### Teams ### + + teams_set = Team.objects.all() + participants_set = Profile.objects.all() + + # Aggregate upper division team and participant info + upper_teams_set = teams_set.filter(division=1).filter( + faculty=False).exclude(num_members=0) + context['num_upper_teams'] = upper_teams_set.count() + context['num_upper_participants'] = participants_set.filter( + team__division=1).count() + + # Aggregate lower division team and participant info + lower_teams_set = teams_set.filter(division=2).filter( + faculty=False).exclude(num_members=0) + context['num_lower_teams'] = lower_teams_set.count() + context['num_lower_participants'] = participants_set.filter( + team__division=2).count() + + # Aggregate faculty team and participant info + faculty_teams_set = teams_set.filter(faculty=True).exclude(num_members=0) + context['num_faculty_teams'] = faculty_teams_set.count() + context['num_faculty_participants'] = participants_set.filter( + team__faculty=True).count() + + return context + + +class ContactTemplateView(TemplateView): """ View to display contact us page. """ - return render(request, 'core/contact.html') + template_name = 'core/contact.html' -def faq(request): +class FaqTemplateView(TemplateView): """ View to display faq page. """ - return render(request, 'core/faq.html') + template_name = 'core/faq.html' -def teams(request): +class TeamsTemplateView(TemplateView): """ View to display teams page. """ - context = {} + template_name = 'core/teams.html' + + def get_context_data(self, **kwargs): + # Call the base implementation first to get a context + context = super().get_context_data(**kwargs) - # Get contest object or set to None - context['contest'] = cache.get_or_set( - 'contest_model', Contest.objects.first(), CACHE_TIMEOUT) + # Get contest object or set to None + context['contest'] = cache.get_or_set( + 'contest_model', Contest.objects.first(), CACHE_TIMEOUT) - teams_set = Team.objects.all() - participants_set = Profile.objects.all() + teams_set = Team.objects.all() + participants_set = Profile.objects.all() - # Aggregate upper division team and participant info - upper_teams_set = teams_set.filter(division=1).filter(faculty=False).exclude(num_members=0) - context['upper_teams'] = upper_teams_set.order_by('-questions_answered', 'score', 'name') - context['num_upper_teams'] = upper_teams_set.count() - context['num_upper_participants'] = participants_set.filter(team__division=1).count() + # Aggregate upper division team and participant info + upper_teams_set = teams_set.filter(division=1).filter(faculty=False).exclude(num_members=0) + context['upper_teams'] = upper_teams_set.order_by('-questions_answered', 'score', 'name') + context['num_upper_teams'] = upper_teams_set.count() + context['num_upper_participants'] = participants_set.filter(team__division=1).count() - # Aggregate lower division team and participant info - lower_teams_set = teams_set.filter(division=2).filter(faculty=False).exclude(num_members=0) - context['lower_teams'] = lower_teams_set.order_by('-questions_answered', 'score', 'name') - context['num_lower_teams'] = lower_teams_set.count() - context['num_lower_participants'] = participants_set.filter(team__division=2).count() + # Aggregate lower division team and participant info + lower_teams_set = teams_set.filter(division=2).filter(faculty=False).exclude(num_members=0) + context['lower_teams'] = lower_teams_set.order_by('-questions_answered', 'score', 'name') + context['num_lower_teams'] = lower_teams_set.count() + context['num_lower_participants'] = participants_set.filter(team__division=2).count() - # Aggregate faculty team and participant info - faculty_teams_set = teams_set.filter(faculty=True).exclude(num_members=0) - context['faculty_teams'] = faculty_teams_set.order_by('-questions_answered', 'score', 'name') - context['num_faculty_teams'] = faculty_teams_set.count() - context['num_faculty_participants'] = participants_set.filter(team__faculty=True).count() + # Aggregate faculty team and participant info + faculty_teams_set = teams_set.filter(faculty=True).exclude(num_members=0) + context['faculty_teams'] = faculty_teams_set.order_by('-questions_answered', 'score', 'name') + context['num_faculty_teams'] = faculty_teams_set.count() + context['num_faculty_participants'] = participants_set.filter(team__faculty=True).count() - return render(request, 'core/teams.html', context) + return context From f25e4bb0a83e5e78ae154338e084ada2628b639e Mon Sep 17 00:00:00 2001 From: Marlan McInnes-Taylor Date: Mon, 19 Aug 2024 14:49:24 -0500 Subject: [PATCH 02/16] Implemented contestadmin_auth test as a mixin - Supports class based views --- src/contestadmin/utils.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/contestadmin/utils.py b/src/contestadmin/utils.py index 704432c8..640cfadd 100644 --- a/src/contestadmin/utils.py +++ b/src/contestadmin/utils.py @@ -1,7 +1,19 @@ +from django.contrib.auth.mixins import UserPassesTestMixin + """ Functions useable by @user_passes_test view decorator. Each function accepts a User object as its only parameter. """ def contestadmin_auth(user): - return user.profile.role == 5 or user.is_superuser + return user.profile.role == 5 or user.is_superuser + + +class ContestAdminAuthMixin(UserPassesTestMixin): + """ + Mixin which integrates the contestadmin_auth test into a UserPassesTestMixin. + - Enables class based view support of the test. + """ + + def test_func(self): + return contestadmin_auth(self.request.user) From 40725a4bf15e07b7c89e38e16dfeff7701b10dfd Mon Sep 17 00:00:00 2001 From: Marlan McInnes-Taylor Date: Mon, 19 Aug 2024 15:18:14 -0500 Subject: [PATCH 03/16] Added task to generate team csvs --- src/contestadmin/tasks.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/contestadmin/tasks.py b/src/contestadmin/tasks.py index d9e06e16..4f26a85d 100644 --- a/src/contestadmin/tasks.py +++ b/src/contestadmin/tasks.py @@ -266,6 +266,38 @@ def generate_ec_reports(): f'Processed extra credit files for {num_courses} courses') +@shared_task +def generate_team_csvs(): + """ + Celery task which creates CSV files containing team data per division. + """ + + for division in Team.DIVISION: + if division[0] == 1: # Upper + team_file = f"{MEDIA_ROOT}/team_files/upper.csv" + else: # Lower + team_file = f"{MEDIA_ROOT}/team_files/lower.csv" + + with open(team_file, 'w', newline='') as team_csv: + writer = csv.writer( + team_csv, delimiter=',', quoting=csv.QUOTE_MINIMAL) + + # File header + writer.writerow(['team_division', 'team_name', 'questions_answered', 'domjudge_id', 'team_active', 'team_members']) + + # Team data + teams = Team.objects.filter(division=division[0]) + for team in teams: + writer.writerow([ + team.get_division_code(), + team.name, + team.questions_answered, + team.contest_id, + 'T' if team.is_active() else 'F', + '_'.join(team.get_members()) + ]) + + @shared_task def email_faculty(domain): """ From 1b4f7083bc329d7f1474fd7c04f1048be13b752d Mon Sep 17 00:00:00 2001 From: Marlan McInnes-Taylor Date: Mon, 19 Aug 2024 15:27:57 -0500 Subject: [PATCH 04/16] Added view to generate and serve team csvs --- src/contestadmin/views.py | 55 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/src/contestadmin/views.py b/src/contestadmin/views.py index adc5384a..58584fb0 100644 --- a/src/contestadmin/views.py +++ b/src/contestadmin/views.py @@ -1,8 +1,9 @@ import os from django.contrib import messages -from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required, user_passes_test +from django.contrib.auth.mixins import LoginRequiredMixin +from django.contrib.auth.models import User from django.db import transaction from django.http import HttpResponse from django.utils.encoding import force_str @@ -15,7 +16,7 @@ from . import forms from . import tasks -from .utils import contestadmin_auth +from .utils import contestadmin_auth, ContestAdminAuthMixin from contestadmin.models import Contest from contestsuite.settings import MEDIA_ROOT from lfg.models import LFGProfile @@ -192,6 +193,53 @@ def get(self, request): return redirect('admin_dashboard') +class ExportTeamData(LoginRequiredMixin, ContestAdminAuthMixin, View): + """ + View which creates and serves a zip file containing contest team data per division. + """ + + def get(self, request): + """ + Schedules generation of CSV files. + """ + + tasks.generate_team_csvs.delay() + messages.info(request, 'Team data CSVs generation scheduled.', fail_silently=True) + + return redirect('admin_dashboard') + + def download(self): + """ + Serves a ZIP file containing all team data CSV files. + """ + + fpath = f"{MEDIA_ROOT}/team_files/" + + # Initialize zip file + in_memory = BytesIO() + zip = ZipFile(in_memory, 'a') + + # Add team csvs to zip file + for fname in os.listdir(fpath): + zip.write(fpath+fname, fname) + + # fix for Linux zip files read in Windows + for file in zip.filelist: + file.create_system = 0 + + zip.close() + + # Initialize response + response = HttpResponse(content_type='application/zip') + response['Content-Disposition'] = 'attachment; filename=team_data_csvs.zip' + + # Write zip file to response + in_memory.seek(0) + response.write(in_memory.read()) + + return response + + @login_required @user_passes_test(contestadmin_auth, login_url='/', redirect_field_name=None) @transaction.atomic @@ -324,6 +372,9 @@ def dashboard(request): context['dj_files_available'] = True else: context['dj_files_available'] = False + + # Determine if team CSVs have been generated + context['team_csvs_available'] = True if len(os.listdir(f"{MEDIA_ROOT}/team_files/")) > 0 else False # Volunteer card data context['volunteers'] = [user for user in Profile.objects.order_by('role').all() if user.is_volunteer()] From ec7d22e80fc5932d909b02b484233f3fe7461ede Mon Sep 17 00:00:00 2001 From: Marlan McInnes-Taylor Date: Mon, 19 Aug 2024 15:30:14 -0500 Subject: [PATCH 05/16] Added buttons to generate and download team csvs --- src/contestadmin/templates/contestadmin/dashboard.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/contestadmin/templates/contestadmin/dashboard.html b/src/contestadmin/templates/contestadmin/dashboard.html index 287b9f2e..231723c9 100644 --- a/src/contestadmin/templates/contestadmin/dashboard.html +++ b/src/contestadmin/templates/contestadmin/dashboard.html @@ -104,6 +104,14 @@

Contest Dashboard

Contest Tools
+
+ Generate Team CSVs + {% if team_csvs_available %} + + {% else %} + + {% endif %} +
From 1a1f1cdd37702e0358486ede4e4e7a0ac13618c0 Mon Sep 17 00:00:00 2001 From: Marlan McInnes-Taylor Date: Mon, 19 Aug 2024 15:31:04 -0500 Subject: [PATCH 06/16] Added paths to generate and download team csvs --- src/contestadmin/urls.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/contestadmin/urls.py b/src/contestadmin/urls.py index d2a16b8b..e8fa359c 100644 --- a/src/contestadmin/urls.py +++ b/src/contestadmin/urls.py @@ -15,5 +15,7 @@ path('ec_files/generate', login_required((user_passes_test(contestadmin_auth, login_url='/', redirect_field_name=None))(views.GenerateExtraCreditReports.as_view())), name='gen_ec_reports'), path('faculty//', views.FacultyDashboard.as_view(), name='fac_ec_dashboard'), path('faculty//download', views.FacultyDashboard.download, name='fac_ec_files_dl'), - path('statistics/', views.contest_statistics, name='contest_stats') + path('statistics/', views.contest_statistics, name='contest_stats'), + path('team_csvs/generate', views.ExportTeamData.as_view(), name='generate_team_csvs'), + path('team_csvs/download', views.ExportTeamData.download, name='download_team_csvs') ] From 4b25ae0a1a9fb9af15673f89e412bc27b5c6fe49 Mon Sep 17 00:00:00 2001 From: Marlan McInnes-Taylor Date: Mon, 19 Aug 2024 15:32:24 -0500 Subject: [PATCH 07/16] Added install of team_files directory --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 6c4c443f..1f6b9135 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,6 +30,7 @@ RUN pip install --no-cache-dir -r /tmp/requirements.txt \ && install -d -m 0755 -o app_user -g app_user /app/media \ && install -d -m 0755 -o app_user -g app_user /app/media/contest_files \ && install -d -m 0755 -o app_user -g app_user /app/media/ec_files \ + && install -d -m 0755 -o app_user -g app_user /app/media/team_files \ && install -d -m 0755 -o app_user -g app_user /app/media/uploads # Code and User Setup From 22e1c5d2d3d4b524a734e4d70ba02cefde539d5b Mon Sep 17 00:00:00 2001 From: Marlan McInnes-Taylor Date: Mon, 19 Aug 2024 15:42:56 -0500 Subject: [PATCH 08/16] Updated env key value mapping syntax --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1f6b9135..f85ce25a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ FROM python:3.10-slim LABEL maintainer="ACM at FSU " -ENV PYTHONUNBUFFERED 1 -ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED=1 +ENV PYTHONDONTWRITEBYTECODE=1 ARG REQUIREMENTS=requirements.txt From 3aaa64822e7901e5f0a09612383b78a2b31df6ad Mon Sep 17 00:00:00 2001 From: Marlan McInnes-Taylor Date: Tue, 20 Aug 2024 19:24:39 -0500 Subject: [PATCH 09/16] Created password update form --- src/contestadmin/forms.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/contestadmin/forms.py b/src/contestadmin/forms.py index 492664b2..9cbdb377 100644 --- a/src/contestadmin/forms.py +++ b/src/contestadmin/forms.py @@ -56,3 +56,11 @@ class DesignateFacultyTeamForm(forms.Form): max_length=30, label='Team name', help_text="Name of faculty team.") + + +class UpdatePasswordForm(forms.Form): + username = forms.CharField( + max_length=150, + label='Username', + help_text="Person's account username.") + password = forms.CharField() From a33b58c18731c474b382c69c78181eaee02b91b1 Mon Sep 17 00:00:00 2001 From: Marlan McInnes-Taylor Date: Tue, 20 Aug 2024 19:30:49 -0500 Subject: [PATCH 10/16] Added password update to contest dash view --- src/contestadmin/views.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/contestadmin/views.py b/src/contestadmin/views.py index 58584fb0..f72e694b 100644 --- a/src/contestadmin/views.py +++ b/src/contestadmin/views.py @@ -4,6 +4,7 @@ from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import User +from django.contrib.auth.password_validation import validate_password from django.db import transaction from django.http import HttpResponse from django.utils.encoding import force_str @@ -256,10 +257,10 @@ def dashboard(request): file_form = forms.ResultsForm(request.POST, request.FILES) checkin_form = forms.CheckinUsersForm(request.POST) channel_form = forms.ClearChannelForm(request.POST) + update_password_form = forms.UpdatePasswordForm(request.POST) profile_role_form = forms.UpdateProfileRoleForm(request.POST) activate_account_form = forms.ActivateAccountForm(request.POST) - faculty_team_form = forms.DesignateFacultyTeamForm( - request.POST) + faculty_team_form = forms.DesignateFacultyTeamForm(request.POST) # Process walk-in team creation form if walkin_form.is_valid(): @@ -277,6 +278,25 @@ def dashboard(request): channel_form.cleaned_data['channel_id']) messages.info(request, 'Clear channel task scheduled.', fail_silently=True) + # Process password update form + elif update_password_form.is_valid(): + try: + user = User.objects.get(username=update_password_form.cleaned_data['username']) + except: + messages.error(request, 'User not found.', fail_silently=True) + else: + try: + validate_password(update_password_form.cleaned_data['password'], user) + except: + messages.error(request, 'Please try a different password.', fail_silently=True) + else: + try: + user.set_password(update_password_form.cleaned_data['password']) + user.save() + except: + messages.error(request, 'Password save failed.', fail_silently=True) + else: + messages.success(request, 'Password updated.', fail_silently=True) # Process profile role change form elif profile_role_form.is_valid(): try: @@ -350,6 +370,7 @@ def dashboard(request): file_form = forms.ResultsForm() checkin_form = forms.CheckinUsersForm() channel_form = forms.ClearChannelForm() + update_password_form = forms.UpdatePasswordForm() profile_role_form = forms.UpdateProfileRoleForm() activate_account_form = forms.ActivateAccountForm() faculty_team_form = forms.DesignateFacultyTeamForm() @@ -384,6 +405,7 @@ def dashboard(request): context['file_form'] = file_form context['gen_walkin_form'] = walkin_form context['channel_form'] = channel_form + context['update_password_form'] = update_password_form context['profile_role_form'] = profile_role_form context['activate_account_form'] = activate_account_form context["faculty_team_form"] = faculty_team_form From b8435bbd2e42b36f58ea291998ca14d41a1b6df6 Mon Sep 17 00:00:00 2001 From: Marlan McInnes-Taylor Date: Tue, 20 Aug 2024 19:31:48 -0500 Subject: [PATCH 11/16] Added password update form --- .../templates/contestadmin/dashboard.html | 65 ++++++++++++------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/src/contestadmin/templates/contestadmin/dashboard.html b/src/contestadmin/templates/contestadmin/dashboard.html index 231723c9..e7b5cd26 100644 --- a/src/contestadmin/templates/contestadmin/dashboard.html +++ b/src/contestadmin/templates/contestadmin/dashboard.html @@ -101,23 +101,37 @@

Contest Dashboard

- Contest Tools + Tools
-
- Generate Team CSVs - {% if team_csvs_available %} - - {% else %} - - {% endif %} -
-
- +
+
+
+ Generate Team CSVs + {% if team_csvs_available %} + + {% else %} + + {% endif %} +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
-
- -
+
@@ -150,17 +164,22 @@

Contest Dashboard

- Account Tools + Change User Password
-
-
- +
+
+ {% csrf_token %} +
+ {{ update_password_form.username | placeholder:"Username" }} +
+
+ {{ update_password_form.password | placeholder:"New password" }} +
-
- + -
+
@@ -351,7 +370,7 @@
From 3e1a0e0675c2e5401d347ffdff2386ca95bf2380 Mon Sep 17 00:00:00 2001 From: Marlan McInnes-Taylor Date: Wed, 2 Oct 2024 15:04:43 -0500 Subject: [PATCH 15/16] Updated account dashboard default notifications Removed team and contestant check-in related messages for users with volunteer status --- src/manager/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/manager/views.py b/src/manager/views.py index ff9938aa..1ee860d3 100644 --- a/src/manager/views.py +++ b/src/manager/views.py @@ -38,7 +38,7 @@ def dashboard(request): context['team_members'] = User.objects.filter(profile__team=request.user.profile.team).exclude(username=request.user.username) # Generate account some useful account notifications - if not request.user.profile.has_team(): + if not request.user.profile.has_team() and not request.user.profile.is_volunteer(): messages.warning( request, 'You are not a member of a registered team. You must be a team member in order to compete. Check out the FAQ for more information.') if not request.user.profile.has_courses() and context['total_num_courses'] > 0: @@ -47,7 +47,7 @@ def dashboard(request): if request.user.profile.fsu_id is None or request.user.profile.fsu_id == '': messages.info( request, 'Your FSU ID is blank. You must add it to your profile in order to receive extra credit. Check out the FAQ for more information.') - if request.user.profile.fsu_num is None or request.user.profile.fsu_num == '': + if (request.user.profile.fsu_num is None or request.user.profile.fsu_num == '') and not request.user.profile.is_volunteer(): messages.info( request, 'Your FSU number is blank. You must add it to your profile in order to swipe check in using your FSUCard on contest day. Check out the FAQ for more information.') From e7ac4a73d8c520f13c317d1e0118ca7a161fa112 Mon Sep 17 00:00:00 2001 From: Marlan McInnes-Taylor Date: Wed, 2 Oct 2024 15:24:01 -0500 Subject: [PATCH 16/16] Refactored admin account activation and updated contest dashboard ui - Generalized account activation process to include deactivation - Moved walk-in team button to pre-contest section --- src/contestadmin/forms.py | 8 +++- .../templates/contestadmin/dashboard.html | 36 ++++++++--------- src/contestadmin/views.py | 40 +++++++++++++------ 3 files changed, 51 insertions(+), 33 deletions(-) diff --git a/src/contestadmin/forms.py b/src/contestadmin/forms.py index 9cbdb377..912505b5 100644 --- a/src/contestadmin/forms.py +++ b/src/contestadmin/forms.py @@ -43,12 +43,18 @@ class Meta: model = Profile fields = ["role"] + +class AccountStatusForm(forms.Form): + STATUS = ( + (0, 'Activate'), + (1, 'Deactivate') + ) -class ActivateAccountForm(forms.Form): username = forms.CharField( max_length=150, label='Username', help_text="Person's account username.") + status = forms.ChoiceField(choices=STATUS) class DesignateFacultyTeamForm(forms.Form): diff --git a/src/contestadmin/templates/contestadmin/dashboard.html b/src/contestadmin/templates/contestadmin/dashboard.html index e7b5cd26..efd2e98a 100644 --- a/src/contestadmin/templates/contestadmin/dashboard.html +++ b/src/contestadmin/templates/contestadmin/dashboard.html @@ -25,6 +25,9 @@

Contest Dashboard

Pre-Contest
+
+ +
Generate DOMjudge TSVs {% if dj_files_available %} @@ -73,7 +76,6 @@

Contest Dashboard

- {% if dj_results_processed %} Generate Reports @@ -106,6 +108,9 @@

Contest Dashboard

+
+ +
Generate Team CSVs {% if team_csvs_available %} @@ -114,17 +119,11 @@

Contest Dashboard

{% endif %}
-
- -
-
- -
- +
@@ -284,7 +283,6 @@

Contest Dashboard

- - - - -