Skip to content

Codebase cleanup + new contest admin tools #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
FROM python:3.10-slim
LABEL maintainer="ACM at FSU <contact@fsu.acm.org>"

ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1

ARG REQUIREMENTS=requirements.txt

Expand Down Expand Up @@ -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
Expand Down
16 changes: 15 additions & 1 deletion src/contestadmin/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,30 @@ 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):
teamname = forms.CharField(
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()
32 changes: 32 additions & 0 deletions src/contestadmin/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down
75 changes: 50 additions & 25 deletions src/contestadmin/templates/contestadmin/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ <h1 class="text-center">Contest Dashboard</h1>
<i class="fa-solid fa-clock-rotate-left fa-fw"></i> Pre-Contest
</div>
<div class="card-body overflow-auto">
<div class="row justify-content-center">
<button type="button" class="btn btn-primary btn-sm my-1" data-toggle="modal" data-target="#walkinModal"> <i class="fa-solid fa-person-walking fa-fw"></i> Create Walk-in teams</button>
</div>
<div class="row justify-content-center">
<a class="btn btn-primary btn-sm my-1" href="{% url 'gen_dj_files' %}" onclick="return confirm('Are you certain you want to generate the DOMjudge files?');"><i class="fa-solid fa-file-circle-plus fa-fw"></i> Generate DOMjudge TSVs</a>
{% if dj_files_available %}
Expand Down Expand Up @@ -73,7 +76,6 @@ <h1 class="text-center">Contest Dashboard</h1>
</div>
<div class="card-body overflow-auto">
<div class="row justify-content-center">

{% if dj_results_processed %}
<!--<button type="button" class="btn btn-primary btn-sm my-1" data-toggle="modal" data-target="#generateEcReportsModal">Generate Reports</button>-->
<a class="btn btn-primary btn-sm my-1" href="{% url 'gen_ec_reports' %}" onclick="return confirm('Are you certain you want to generate the extra credit files?');"><i class="fa-solid fa-file-circle-plus fa-fw"></i> Generate Reports</a>
Expand Down Expand Up @@ -101,15 +103,34 @@ <h1 class="text-center">Contest Dashboard</h1>
<!-- Contest tools card -->
<div class="card mt-4 border-secondary">
<div class="card-header font-weight-bold bg-secondary text-white">
<i class="fa-solid fa-wrench fa-fw"></i> Contest Tools
<i class="fa-solid fa-wrench fa-fw"></i> Tools
</div>
<div class="card-body overflow-auto">
<div class="row justify-content-center">
<button type="button" class="btn btn-primary btn-sm my-1" data-toggle="modal" data-target="#walkinModal"> <i class="fa-solid fa-person-walking fa-fw"></i> Create Walk-in teams</button>
<div class="row">
<div class="col-lg-6">
<div class="row justify-content-center">
<button type="button" class="btn btn-primary btn-sm my-1" data-toggle="modal" data-target="#accountStatusModal"><i class="fa-solid fa-user-gear fa-fw"></i> Update Account Status</button>
</div>
<div class="row justify-content-center">
<a class="btn btn-primary btn-sm my-1" href="{% url 'generate_team_csvs' %}" onclick="return confirm('Are you certain you want to generate the team data CSVs?');"><i class="fa-solid fa-file-circle-plus fa-fw"></i> Generate Team CSVs</a>
{% if team_csvs_available %}
<a class="btn text-dark" href="{% url 'download_team_csvs' %}" role="button"><i class="fa fa-download fa-md" aria-hidden="true"></i></a>
{% else %}
<a class="btn text-secondary disabled" href="#" role="button"><i class="fa fa-download fa-md" aria-hidden="true"></i></a>
{% endif %}
</div>
</div>
<div class="col-lg-6">
<div class="row justify-content-center">
<button type="button" class="btn btn-primary btn-sm my-1" data-toggle="modal" data-target="#facultyTeamModal"><i
class="fa-solid fa-chalkboard-user fa-fw"></i> Designate Faculty Team</button>
</div>
<div class="row justify-content-center">
<button type="button" class="btn btn-primary btn-sm my-1" data-toggle="modal" data-target="#checkinModal"><i class="fa-solid fa-robot fa-fw"></i> Check in/out Users</button>
</div>
</div>
</div>
<div class="row justify-content-center">
<button type="button" class="btn btn-primary btn-sm my-1" data-toggle="modal" data-target="#checkinModal"><i class="fa-solid fa-robot fa-fw"></i> Check in/out Users</button>
</div>

</div>
</div>
<!-- Contest tools card end -->
Expand Down Expand Up @@ -142,17 +163,22 @@ <h1 class="text-center">Contest Dashboard</h1>
<!-- Account tools card -->
<div class="card mt-4 border-secondary">
<div class="card-header font-weight-bold bg-secondary text-white">
<i class="fa-solid fa-wrench fa-fw"></i> Account Tools
<i class="fa-solid fa-user-shield fa-fw"></i> Change User Password
</div>
<div class="card-body overflow-auto">
<div class="row justify-content-center">
<button type="button" class="btn btn-primary btn-sm my-1" data-toggle="modal" data-target="#activateAccountModal"><i class="fa-solid fa-user-check fa-fw"></i> Activate User Account</button>
<form method="post" onSubmit="return confirm('Are you certain you want to update this user password?');">
<div class="card-body overflow-auto">
{% csrf_token %}
<div class="form-group">
{{ update_password_form.username | placeholder:"Username" }}
</div>
<div class="form-group">
{{ update_password_form.password | placeholder:"New password" }}
</div>
</div>
<div class="row justify-content-center">
<button type="button" class="btn btn-primary btn-sm my-1" data-toggle="modal" data-target="#facultyTeamModal"><i
class="fa-solid fa-chalkboard-user fa-fw"></i> Designate Faculty Team</button>
<div class="card-footer">
<button class="btn btn-primary btn-sm" type="submit">Save</button>
</div>
</div>
</form>
</div>
<!-- Account tools card end -->
</div>
Expand Down Expand Up @@ -257,7 +283,6 @@ <h1 class="text-center">Contest Dashboard</h1>
</div>
<!-- Volunteer Summary card end -->


<!-- Create Walk-in Teams Modal -->
<div class="modal fade" id="walkinModal" tabindex="-1" role="dialog"
aria-labelledby="confirmClearModalCenterTitle" aria-hidden="true">
Expand Down Expand Up @@ -294,7 +319,6 @@ <h5 class="mb-0">{{ field.label_tag }}</h5>
</div>
<!-- End Create Walk-in Teams Modal -->


<!-- Checkin/Checkout Users Modal -->
<div class="modal fade" id="checkinModal" tabindex="-1" role="dialog" aria-labelledby="confirmClearModalCenterTitle"
aria-hidden="true">
Expand Down Expand Up @@ -330,20 +354,22 @@ <h5 class="modal-title" id="confirmClearModalLongTitle">Check in/out Users</h5>
</div>
<!-- End Create Walk-in Teams Modal -->


<!-- Activate Account Modal -->
<div class="modal fade" id="activateAccountModal" tabindex="-1" role="dialog"
<!-- Account Status Modal -->
<div class="modal fade" id="accountStatusModal" tabindex="-1" role="dialog"
aria-labelledby="confirmClearModalCenterTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="confirmClearModalLongTitle">Activate User Account</h5>
<h5 class="modal-title" id="confirmClearModalLongTitle">Update Account Status</h5>
</div>
<form method="post" onSubmit="return confirm('Are you certain you want to activate this account?');">
<form method="post" onSubmit="return confirm('Are you certain you want to update this account?');">
<div class="modal-body overflow-auto">
{% csrf_token %}
<div class="form-group">
{{ activate_account_form.username | placeholder:"Username"}}
{{ account_status_form.username | placeholder:"Username" }}
</div>
<div class="form-group">
{{ account_status_form.status }}
</div>
</div>
<div class="card-footer">
Expand All @@ -354,8 +380,7 @@ <h5 class="modal-title" id="confirmClearModalLongTitle">Activate User Account</h
</div>
</div>
</div>
<!-- End Activate Account Modal -->

<!-- End Account Status Modal -->

<!-- Designate Faculty Team Modal -->
<div class="modal fade" id="facultyTeamModal" tabindex="-1" role="dialog"
Expand Down
4 changes: 3 additions & 1 deletion src/contestadmin/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/<uidb64>/', views.FacultyDashboard.as_view(), name='fac_ec_dashboard'),
path('faculty/<uidb64>/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')
]
14 changes: 13 additions & 1 deletion src/contestadmin/utils.py
Original file line number Diff line number Diff line change
@@ -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)
Loading