Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
789a575
Change all links to direct to FSU-ACM repository
hoangvu5 Oct 29, 2025
7c674b7
Bump Django's version to 4.2.26
hoangvu5 Nov 19, 2025
1048ef2
Merge pull request #62 from FSU-ACM/django-update
hoangvu5 Feb 17, 2026
bd6b006
Fixed typo
the-sam-sepiol Feb 18, 2026
a3ca713
added group/ to url paths
the-sam-sepiol Feb 18, 2026
93c63f4
Fixed the group registration error. Added in email valdiation requst.
the-sam-sepiol Feb 18, 2026
100365b
Merge pull request #64 from FSU-ACM/group-registration-fix
hoangvu5 Feb 19, 2026
41f5d02
added passed_cop3330 boolean field to Profile model
ramonicv Feb 20, 2026
384b777
added PASSED_COP3330_CHOICES tuple to ProfileForm
ramonicv Feb 20, 2026
87bd71e
added passed_cop3330 fields and helper data/widgets to ProfileForm
ramonicv Feb 20, 2026
ee11c20
added passed_cop3330 element for contestant role users in profile das…
ramonicv Feb 20, 2026
ae4f608
changed default passed_cop3330 value to false
ramonicv Feb 20, 2026
0fc0de9
added passed_cop3330 field to user registration form
ramonicv Feb 20, 2026
001f517
added logic to forward passed_3330 data from registration form "User"…
ramonicv Feb 20, 2026
e0eaa67
added logic to use http for email verification template links when on…
ramonicv Feb 24, 2026
b46b3f1
Merge pull request #67 from FSU-ACM/email_link_protocol_split
hoangvu5 Feb 26, 2026
9f6d5af
Added update_division(self) to Team model, to check for passed_cop333…
the-sam-sepiol Mar 2, 2026
2c17724
Remove division field from both TeamForms
the-sam-sepiol Mar 2, 2026
66f4e18
Update views to auto-update division
the-sam-sepiol Mar 2, 2026
2a57f36
Fix form field in TeamForm
the-sam-sepiol Mar 2, 2026
7397ad8
Fix leave_group team behavior
the-sam-sepiol Mar 2, 2026
0bc7560
Fix: Save team reference first, then update_division
the-sam-sepiol Mar 2, 2026
e01b58d
Fix: Update team division after removing a member
the-sam-sepiol Mar 2, 2026
91b2404
Merge branch 'dev' into add_cop3330_check
ramonicv Mar 6, 2026
a60c9da
Add team division update on check-in route
hoangvu5 Mar 6, 2026
990c64c
fixed admin checkin not updating team division based on user passed_c…
ramonicv Mar 6, 2026
023a583
propagated changes for both email and fsucard swipe checkins
ramonicv Mar 6, 2026
83135e9
Merge pull request #68 from FSU-ACM/add_cop3330_check
hoangvu5 Mar 6, 2026
c837075
Change view to sort student teams by "faculty=False" for both Teams p…
the-sam-sepiol Mar 7, 2026
07b85dd
Propagated changes from views to html templates for index and teams
the-sam-sepiol Mar 7, 2026
d55456d
added documentation for passed_cop3330 field
ramonicv Mar 9, 2026
8765898
added questions_for_extra_credit field
ramonicv Mar 9, 2026
aae98b3
implemented new json processing logic for contest result import file
ramonicv Mar 9, 2026
84dba80
updated variable names to use new team model field questions_for_extr…
ramonicv Mar 9, 2026
f798592
fixed comment typo
ramonicv Mar 9, 2026
ee2eedb
changed json key to check for problem ids to "problem_id" instead of …
ramonicv Mar 9, 2026
ee62872
Fix participants label on Teams page
the-sam-sepiol Mar 9, 2026
fe0a174
Merge pull request #69 from FSU-ACM/leaderboard_unify
hoangvu5 Mar 9, 2026
e56c4aa
redid json formatting assignments to match correct score metrics, and…
ramonicv Mar 9, 2026
1939efb
Merge branch 'dev' into update_contest_result_processing
hoangvu5 Mar 9, 2026
bc8865d
Merge pull request #70 from FSU-ACM/update_contest_result_processing
hoangvu5 Mar 9, 2026
e01583f
Edit FAQ and add help text for COP3330 checkbox
hoangvu5 Mar 10, 2026
6233a94
Edit contributor's list
hoangvu5 Mar 10, 2026
28dee16
Merge branch 'main' into dev
hoangvu5 Mar 10, 2026
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
2 changes: 1 addition & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ verify_ssl = true
name = "pypi"

[packages]
Django = ">=4.2.25,<4.3"
Django = ">=4.2.26,<4.3"
mysqlclient = ">=1.4.3"
redis = ">=3.4.0"
hiredis = "*"
Expand Down
1,806 changes: 1,012 additions & 794 deletions Pipfile.lock

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ The Programming Contest Suite (PCS) is a set of tools for running [ICPC](https:/

Simply clone this repository:

git clone https://github.com/mmcinnestaylor/Programming-Contest-Suite.git
git clone https://github.com/FSU-ACM/Programming-Contest-Suite.git


Alternatively, download one of the versions available on the [releases](https://github.com/mmcinnestaylor/Programming-Contest-Suite/releases) page.
Alternatively, download one of the versions available on the [releases](https://github.com/FSU-ACM/Programming-Contest-Suite/releases) page.

# Deployment

Expand Down Expand Up @@ -100,3 +100,4 @@ We welcome contributions to the project! Check out `CONTRIBUTING.md` to learn ho
- [Hoang Vu](https://github.com/hoangvu5)
- [Aidan Collins](https://github.com/getsbuffer)
- [Ramon Ortega](https://github.com/ramonicv)
- [Ethan Anderson](https://github.com/the-sam-sepiol)
2 changes: 1 addition & 1 deletion docs/deployment/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ The following variables, located in the main settings file[^1], map to miscellan
Variable | Default | Description
---|---|---
DOMJUDGE_URL | https://domjudge.cs.fsu.edu/public | Full URL of the DOMjudge server. Used for PCS homepage contest server status card.
PCS_DOCS_URL | https://mmcinnestaylor.github.io/Programming-Contest-Suite | Base URL of the project's documentation website. Used to link registration guide and other manuals.
PCS_DOCS_URL | https://fsu-acm.github.io/Programming-Contest-Suite | Base URL of the project's documentation website. Used to link registration guide and other manuals.
HASHID_FIELD_SALT | a long string[^4] | [Docs](https://github.com/nshafer/django-hashid-field#hashid_field_salt) The `django-hashid-field` library is used to hash sensitive PCS database model fields.
GTAG | None | [Google Analytics site tag](https://support.google.com/analytics/answer/12002338?hl=en)

Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ We welcome contributions to the project! Check out the [Contributor's Guide](htt
- [Hoang Vu](https://github.com/hoangvu5)
- [Aidan Collins](https://github.com/getsbuffer)
- [Ramon Ortega](https://github.com/ramonicv)
- [Ethan Anderson](https://github.com/the-sam-sepiol)
42 changes: 21 additions & 21 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,54 +1,54 @@
-i https://pypi.python.org/simple
aiohappyeyeballs==2.4.6; python_version >= '3.9'
aiohttp==3.11.13; python_version >= '3.9'
aiosignal==1.3.2; python_version >= '3.9'
aiohappyeyeballs==2.6.1; python_version >= '3.9'
aiohttp==3.13.2; python_version >= '3.9'
aiosignal==1.4.0; python_version >= '3.9'
amqp==5.3.1; python_version >= '3.6'
asgiref==3.9.2; python_version >= '3.9'
attrs==25.1.0; python_version >= '3.8'
billiard==4.2.2; python_version >= '3.7'
asgiref==3.11.0; python_version >= '3.9'
attrs==25.4.0; python_version >= '3.9'
billiard==4.2.3; python_version >= '3.7'
celery==5.5.3; python_version >= '3.8'
certifi==2025.8.3; python_version >= '3.7'
charset-normalizer==3.4.3; python_version >= '3.7'
click==8.3.0; python_version >= '3.10'
certifi==2025.11.12; python_version >= '3.7'
charset-normalizer==3.4.4; python_version >= '3.7'
click==8.3.1; python_version >= '3.10'
click-didyoumean==0.3.1; python_full_version >= '3.6.2'
click-plugins==1.1.1.2
click-repl==0.3.0; python_version >= '3.6'
cron-descriptor==2.0.6; python_version >= '3.9'
diff-match-patch==20241021; python_version >= '3.7'
discord-py==2.5.0; python_version >= '3.8'
django==4.2.25; python_version >= '3.8'
django==4.2.26; python_version >= '3.8'
django-celery-beat==2.8.1; python_version >= '3.8'
django-hashid-field==3.4.1
django-import-export==4.3.10; python_version >= '3.9'
django-import-export==4.3.14; python_version >= '3.9'
django-timezone-field==7.1; python_version >= '3.8' and python_version < '4.0'
flower==2.0.1; python_version >= '3.7'
frozenlist==1.5.0; python_version >= '3.8'
frozenlist==1.8.0; python_version >= '3.9'
gunicorn==23.0.0; python_version >= '3.7'
hashids==1.3.1; python_version >= '2.7'
hiredis==3.2.1; python_version >= '3.8'
humanize==4.13.0; python_version >= '3.9'
idna==3.10; python_version >= '3.6'
hiredis==3.3.0; python_version >= '3.8'
humanize==4.14.0; python_version >= '3.10'
idna==3.11; python_version >= '3.8'
kombu==5.5.4; python_version >= '3.8'
multidict==6.1.0; python_version >= '3.8'
multidict==6.7.0; python_version >= '3.9'
mysqlclient==2.2.7; python_version >= '3.8'
packaging==25.0; python_version >= '3.8'
pillow==11.3.0; python_version >= '3.9'
pillow==12.0.0; python_version >= '3.10'
prometheus-client==0.23.1; python_version >= '3.9'
prompt-toolkit==3.0.52; python_version >= '3.8'
propcache==0.3.0; python_version >= '3.9'
propcache==0.4.1; python_version >= '3.9'
python-crontab==3.3.0
python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'
pytz==2025.2
pyyaml==6.0.3; python_version >= '3.8'
redis==6.4.0; python_version >= '3.9'
redis==7.1.0; python_version >= '3.10'
requests==2.32.5; python_version >= '3.9'
six==1.17.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'
sqlparse==0.5.3; python_version >= '3.8'
tablib==3.8.0; python_version >= '3.9'
tablib==3.9.0; python_version >= '3.9'
tornado==6.5.2; python_version >= '3.9'
typing-extensions==4.15.0; python_version >= '3.9'
tzdata==2025.2; python_version >= '2'
urllib3==2.5.0; python_version >= '3.9'
vine==5.1.0; python_version >= '3.6'
wcwidth==0.2.14; python_version >= '3.6'
yarl==1.18.3; python_version >= '3.9'
yarl==1.22.0; python_version >= '3.9'
42 changes: 21 additions & 21 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,54 +1,54 @@
-i https://pypi.python.org/simple
aiohappyeyeballs==2.4.6; python_version >= '3.9'
aiohttp==3.11.13; python_version >= '3.9'
aiosignal==1.3.2; python_version >= '3.9'
aiohappyeyeballs==2.6.1; python_version >= '3.9'
aiohttp==3.13.2; python_version >= '3.9'
aiosignal==1.4.0; python_version >= '3.9'
amqp==5.3.1; python_version >= '3.6'
asgiref==3.9.2; python_version >= '3.9'
attrs==25.1.0; python_version >= '3.8'
billiard==4.2.2; python_version >= '3.7'
asgiref==3.11.0; python_version >= '3.9'
attrs==25.4.0; python_version >= '3.9'
billiard==4.2.3; python_version >= '3.7'
celery==5.5.3; python_version >= '3.8'
certifi==2025.8.3; python_version >= '3.7'
charset-normalizer==3.4.3; python_version >= '3.7'
click==8.3.0; python_version >= '3.10'
certifi==2025.11.12; python_version >= '3.7'
charset-normalizer==3.4.4; python_version >= '3.7'
click==8.3.1; python_version >= '3.10'
click-didyoumean==0.3.1; python_full_version >= '3.6.2'
click-plugins==1.1.1.2
click-repl==0.3.0; python_version >= '3.6'
cron-descriptor==2.0.6; python_version >= '3.9'
diff-match-patch==20241021; python_version >= '3.7'
discord-py==2.5.0; python_version >= '3.8'
django==4.2.25; python_version >= '3.8'
django==4.2.26; python_version >= '3.8'
django-celery-beat==2.8.1; python_version >= '3.8'
django-hashid-field==3.4.1
django-import-export==4.3.10; python_version >= '3.9'
django-import-export==4.3.14; python_version >= '3.9'
django-timezone-field==7.1; python_version >= '3.8' and python_version < '4.0'
flower==2.0.1; python_version >= '3.7'
frozenlist==1.5.0; python_version >= '3.8'
frozenlist==1.8.0; python_version >= '3.9'
gunicorn==23.0.0; python_version >= '3.7'
hashids==1.3.1; python_version >= '2.7'
hiredis==3.2.1; python_version >= '3.8'
humanize==4.13.0; python_version >= '3.9'
idna==3.10; python_version >= '3.6'
hiredis==3.3.0; python_version >= '3.8'
humanize==4.14.0; python_version >= '3.10'
idna==3.11; python_version >= '3.8'
kombu==5.5.4; python_version >= '3.8'
multidict==6.1.0; python_version >= '3.8'
multidict==6.7.0; python_version >= '3.9'
mysqlclient==2.2.7; python_version >= '3.8'
packaging==25.0; python_version >= '3.8'
pillow==11.3.0; python_version >= '3.9'
pillow==12.0.0; python_version >= '3.10'
prometheus-client==0.23.1; python_version >= '3.9'
prompt-toolkit==3.0.52; python_version >= '3.8'
propcache==0.3.0; python_version >= '3.9'
propcache==0.4.1; python_version >= '3.9'
python-crontab==3.3.0
python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'
pytz==2025.2
pyyaml==6.0.3; python_version >= '3.8'
redis==6.4.0; python_version >= '3.9'
redis==7.1.0; python_version >= '3.10'
requests==2.32.5; python_version >= '3.9'
six==1.17.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'
sqlparse==0.5.3; python_version >= '3.8'
tablib==3.8.0; python_version >= '3.9'
tablib==3.9.0; python_version >= '3.9'
tornado==6.5.2; python_version >= '3.9'
typing-extensions==4.15.0; python_version >= '3.9'
tzdata==2025.2; python_version >= '2'
urllib3==2.5.0; python_version >= '3.9'
vine==5.1.0; python_version >= '3.6'
wcwidth==0.2.14; python_version >= '3.6'
yarl==1.18.3; python_version >= '3.9'
yarl==1.22.0; python_version >= '3.9'
4 changes: 4 additions & 0 deletions src/checkin/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ def checkin(request):
walk_in_team.num_members += 1
walk_in_team.save()

user.profile.save()
user.profile.team.update_division()
user.save()

# Email user DOMjudge credentials
Expand Down Expand Up @@ -131,6 +133,8 @@ def checkin(request):
walk_in_team.num_members += 1
walk_in_team.save()

user.profile.save()
user.profile.team.update_division()
user.save()

# Email user DOMjudge credentials
Expand Down
117 changes: 91 additions & 26 deletions src/contestadmin/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,10 @@ def generate_ec_reports():
format <faculty-email_course-code.csv> will map correctly.

Updating Faculty model to support non-FSU CS addresses will require file naming update.

Update 3/8/2026:
Refactored questions_solved to new questions_for_extra_credit property
and changed variable names to more clearly reflect number of extra credits earned
"""

num_courses = 0
Expand All @@ -297,7 +301,7 @@ def generate_ec_reports():

# File header
writer.writerow(
['fsu_id', 'last_name', 'first_name', 'questions_answered', 'team_division', 'role'])
['fsu_id', 'last_name', 'first_name', 'extra_credits', 'team_division', 'role'])

for student in students:
if student.profile.fsu_id is None:
Expand All @@ -306,15 +310,15 @@ def generate_ec_reports():
fsu_id = student.profile.fsu_id

if student.profile.team is None:
questions_answered = 'none'
extra_credits = 'none'
else:
questions_answered = student.profile.team.questions_answered
extra_credits = student.profile.team.questions_for_extra_credit

writer.writerow([
fsu_id,
student.last_name,
student.first_name,
questions_answered,
extra_credits,
student.profile.team.get_division_code() if student.profile.team else 'none',
student.profile.get_role()
])
Expand Down Expand Up @@ -411,39 +415,100 @@ def email_faculty(domain):
def process_contest_results():
"""
Celery task which processes a DOMjudge results file uploaded to the server.
Update 3/8/2026:
We are now importing JSON from /api/v4/contests/{cid}/scoreboard
Reasoning: this API endpoint shows us problem IDs, which we can use
to determine which questions a given team is eligible
for extra credit from.

The old logic for processing TSVs is kept in comments for reference and potential future use
"""

# num_teams = 0
# contest = Contest.objects.all().first()
#
# if not contest:
# logger.error("No Contest object exists in database.")
# else:
# if Team.objects.all().count() > 0:
# # Determine width of numerical portion of DOMjudge usernames
# fill_width = ceil(log10(Team.objects.all().count()))
#
# with open(contest.results.path) as resultsfile:
# results = csv.reader(resultsfile, delimiter="\t", quotechar='"')
#
# # islice - Skip header of file
# for row in islice(results, 1, None):
# # [DOMjudge team ID] <id> -> [Registration team ID] acm-(zfill)<id>
# id = f"acm-{row[0].zfill(fill_width)}"
#
# try:
# team = Team.objects.get(contest_id=id)
# team.questions_answered = row[3]
# team.score = row[4]
# team.last_submission = row[5]
# team.save()
# except:
# logger.error(
# f"Could not process contest results for team {id}")
# else:
# logger.debug(f"Processed team {id}")
# num_teams += 1
#
# logger.info(f"Processed contest results for {num_teams} teams")
# else:
# logger.error("No Team objects exist in database.")

num_teams = 0
contest = Contest.objects.all().first()

if not contest:
logger.error("No Contest object exists in database.")

else:
if Team.objects.all().count() > 0:
# Determine width of numerical portion of DOMjudge usernames
fill_width = ceil(log10(Team.objects.all().count()))

with open(contest.results.path) as resultsfile:
results = csv.reader(resultsfile, delimiter="\t", quotechar='"')
# Load JSON data from file
data = json.load(resultsfile)

# islice - Skip header of file
for row in islice(results, 1, None):
# [DOMjudge team ID] <id> -> [Registration team ID] acm-(zfill)<id>
id = f"acm-{row[0].zfill(fill_width)}"

try:
team = Team.objects.get(contest_id=id)
team.questions_answered = row[3]
team.score = row[4]
team.last_submission = row[5]
team.save()
except:
logger.error(
f"Could not process contest results for team {id}")
for row in data.get("rows", []): # iterate through "rows" key in JSON data; "rows" is a list of team result objects

team_id = row.get("team_id")
fill_width = ceil(log10(Team.objects.all().count())) # fill width defined by number of teams
id = f"acm-{team_id.zfill(fill_width)}" # set id to match contest_id field of Team model for lookup
try:
team = Team.objects.get(contest_id=id)

score = row.get("score", {})
problems = row.get("problems", [])
team.questions_answered = score.get("num_solved", 0)
team.score = score.get("total_time", 0) # score = total time
team.last_submission = max((p.get("time", 0) for p in problems), default=0) # get the problem that was solved at the latest time

solved_problems = [
p for p in row.get("problems", [])
if p.get("solved")
]

if team.division == 2:
team.questions_for_extra_credit = len(solved_problems)
else:
logger.debug(f"Processed team {id}")
num_teams += 1
team.questions_for_extra_credit = sum(
1 for p in solved_problems
if int(p.get("problem_id", 0)) > 4 # problem ids > 4 are eligible for extra credit for lower division teams
)

team.save()

logger.info(f"Processed contest results for {num_teams} teams")
else:
logger.error("No Team objects exist in database.")
except Team.DoesNotExist:
logger.error(f"Could not find a team with contest_id {id}")

except Exception as e:
logger.error(f"Could not process results for team {id}: {e}")

else:
logger.debug(f"Processed team {id}")
num_teams += 1

logger.info(f"Processed contest results for {num_teams} teams")
2 changes: 1 addition & 1 deletion src/contestsuite/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,4 +298,4 @@ def get_secret(key, default=None):

# Documentation website base URL for navbar and homepage documentation links

PCS_DOCS_URL = get_secret('PCS_DOCS_URL', 'https://mmcinnestaylor.github.io/Programming-Contest-Suite')
PCS_DOCS_URL = get_secret('PCS_DOCS_URL', 'https://fsu-acm.github.io/Programming-Contest-Suite')
Loading
Loading