From afa9b6ba9d31864de1de430af7c168a59a023050 Mon Sep 17 00:00:00 2001 From: Joelle Linder Date: Tue, 18 Feb 2025 19:26:03 +0100 Subject: [PATCH 1/2] Fixed rescaling of grid cells --- static/shell.css | 1 - templates/shell.html | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/static/shell.css b/static/shell.css index 95204ea..c95c636 100644 --- a/static/shell.css +++ b/static/shell.css @@ -27,7 +27,6 @@ body { } .content { - grid-column: main; grid-row: main; padding-top: 1%; diff --git a/templates/shell.html b/templates/shell.html index 777b88f..580eb2b 100644 --- a/templates/shell.html +++ b/templates/shell.html @@ -76,7 +76,9 @@

{% endif %} -
+ +
{% block content %}{% endblock %}
From 30771fc985ea87776b20e074bc32c364c7056fcf Mon Sep 17 00:00:00 2001 From: Joelle Linder Date: Tue, 15 Apr 2025 18:13:58 +0200 Subject: [PATCH 2/2] Added infrastructure support for leaderboard and competitions --- ctef_core/admin.py | 11 +++- ctef_core/common.py | 36 ++++++++--- ...10_competition_competitionparticipation.py | 59 +++++++++++++++++++ .../migrations/0011_default_competition.py | 22 +++++++ .../0012_taskattempt_competition.py | 25 ++++++++ .../0013_competitionparticipation_score.py | 18 ++++++ ctef_core/models.py | 17 ++++++ ctef_web/templates/ctef_web/leaderboard.html | 6 ++ ctef_web/views.py | 12 +++- 9 files changed, 195 insertions(+), 11 deletions(-) create mode 100644 ctef_core/migrations/0010_competition_competitionparticipation.py create mode 100644 ctef_core/migrations/0011_default_competition.py create mode 100644 ctef_core/migrations/0012_taskattempt_competition.py create mode 100644 ctef_core/migrations/0013_competitionparticipation_score.py diff --git a/ctef_core/admin.py b/ctef_core/admin.py index 681ca27..cf71e06 100644 --- a/ctef_core/admin.py +++ b/ctef_core/admin.py @@ -1,8 +1,17 @@ from django.contrib import admin -from .models import TaskModule, Task, TaskAttempt, TaskClue +from .models import ( + TaskModule, + Task, + TaskAttempt, + TaskClue, + Competition, + CompetitionParticipation, +) # Register your models here. admin.site.register(TaskModule) admin.site.register(Task) admin.site.register(TaskAttempt) admin.site.register(TaskClue) +admin.site.register(Competition) +admin.site.register(CompetitionParticipation) diff --git a/ctef_core/common.py b/ctef_core/common.py index 444a38b..ef50b2f 100644 --- a/ctef_core/common.py +++ b/ctef_core/common.py @@ -1,8 +1,7 @@ from django.apps import apps from django.http import HttpRequest, HttpResponseBadRequest -from django.contrib.auth.models import User -from ctef_core.models import Task, TaskAttempt +from ctef_core.models import Task, TaskAttempt, Competition, CompetitionParticipation from ctef_web.forms import SecretForm @@ -10,6 +9,11 @@ def get_module_key(module): return module.split(".")[0] +def get_default_competition(): + default_competition = Competition.objects.get(id=0) + return default_competition + + def fetch_ctf_modules(additional_flag=None): """Fetches all django apps with modules installed within them.""" @@ -49,14 +53,23 @@ def get_base_context(request, include_tasks=True): def validate_secret(attempt: TaskAttempt, secret: str) -> bool: - if attempt.task.secret == secret: - attempt.passed = True - attempt.save() - return True - else: + if attempt.task.secret != secret: return False + attempt.passed = True + attempt.save() + + default_competition = get_default_competition() + + participation, _ = CompetitionParticipation.objects.get_or_create( + user=attempt.user, competition=default_competition + ) + participation.score += 1 + participation.save() + + return True + def task_view_wrapper(view, slug): def wrapper(request: HttpRequest): @@ -67,9 +80,16 @@ def wrapper(request: HttpRequest): task = Task.objects.get(slug=slug) context["task"] = task + # Get the default competition + default_competition = get_default_competition() + attempt, _ = TaskAttempt.objects.get_or_create( - task=task, user=user, defaults={"points": task.points} + task=task, + user=user, + defaults={"points": task.points}, + competition=default_competition, ) + context["task_attempt"] = attempt if request.method == "POST": diff --git a/ctef_core/migrations/0010_competition_competitionparticipation.py b/ctef_core/migrations/0010_competition_competitionparticipation.py new file mode 100644 index 0000000..8ce69d3 --- /dev/null +++ b/ctef_core/migrations/0010_competition_competitionparticipation.py @@ -0,0 +1,59 @@ +# Generated by Django 5.0.11 on 2025-04-15 15:36 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("ctef_core", "0009_task_desc"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Competition", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=500, unique=True)), + ], + ), + migrations.CreateModel( + name="CompetitionParticipation", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "competition", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="ctef_core.competition", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + ] diff --git a/ctef_core/migrations/0011_default_competition.py b/ctef_core/migrations/0011_default_competition.py new file mode 100644 index 0000000..543e1b5 --- /dev/null +++ b/ctef_core/migrations/0011_default_competition.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.11 on 2025-04-15 15:25 + +from django.db import migrations, transaction + + +def add_default_competition(apps, schema_editor): + Competition = apps.get_model("ctef_core.Competition") + + with transaction.atomic(): + default_competition, _ = Competition.objects.get_or_create( + name="Default Competition", id=0 + ) + default_competition.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("ctef_core", "0010_competition_competitionparticipation"), + ] + + operations = [migrations.RunPython(add_default_competition)] diff --git a/ctef_core/migrations/0012_taskattempt_competition.py b/ctef_core/migrations/0012_taskattempt_competition.py new file mode 100644 index 0000000..dfb829b --- /dev/null +++ b/ctef_core/migrations/0012_taskattempt_competition.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0.11 on 2025-04-15 15:47 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("ctef_core", "0011_default_competition"), + ] + + operations = [ + migrations.AddField( + model_name="taskattempt", + name="competition", + field=models.ForeignKey( + default=0, + null=None, + on_delete=django.db.models.deletion.CASCADE, + to="ctef_core.competition", + ), + preserve_default=False, + ), + ] diff --git a/ctef_core/migrations/0013_competitionparticipation_score.py b/ctef_core/migrations/0013_competitionparticipation_score.py new file mode 100644 index 0000000..e940d45 --- /dev/null +++ b/ctef_core/migrations/0013_competitionparticipation_score.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.11 on 2025-04-15 15:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("ctef_core", "0012_taskattempt_competition"), + ] + + operations = [ + migrations.AddField( + model_name="competitionparticipation", + name="score", + field=models.PositiveSmallIntegerField(default=0), + ), + ] diff --git a/ctef_core/models.py b/ctef_core/models.py index 0666184..7478ee2 100644 --- a/ctef_core/models.py +++ b/ctef_core/models.py @@ -9,6 +9,22 @@ # Create your models here. +class Competition(models.Model): + name = models.CharField(max_length=500, unique=True, null=False) + + def __str__(self): + return f'Competition "{self.name}"' + + +class CompetitionParticipation(models.Model): + score = models.PositiveSmallIntegerField(default=0) + user = models.ForeignKey(User, on_delete=models.CASCADE) + competition = models.ForeignKey(Competition, on_delete=models.CASCADE) + + def __str__(self): + return f'{self.user.username}\'s participation in competition "{self.competition.name}"' + + class TaskModule(models.Model): """A grouping of tasks with a common theme or technique.""" @@ -55,6 +71,7 @@ class TaskAttempt(models.Model): passed = models.BooleanField(default=False) task = models.ForeignKey(Task, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE) + competition = models.ForeignKey(Competition, on_delete=models.CASCADE, null=None) def __str__(self) -> str: return f"{self.user.username}'s attempt on task '{self.task.name}'" diff --git a/ctef_web/templates/ctef_web/leaderboard.html b/ctef_web/templates/ctef_web/leaderboard.html index f0aac69..22be3a9 100644 --- a/ctef_web/templates/ctef_web/leaderboard.html +++ b/ctef_web/templates/ctef_web/leaderboard.html @@ -4,4 +4,10 @@

Leaderboard!

+
    + {% for participant in participants %} +
  1. {{participant.user.username}} ({{participant.score}} points)
  2. + {% endfor %} +
+ {% endblock %} \ No newline at end of file diff --git a/ctef_web/views.py b/ctef_web/views.py index 414df45..f05a8a6 100644 --- a/ctef_web/views.py +++ b/ctef_web/views.py @@ -3,9 +3,9 @@ from django.contrib.auth.models import User from django.contrib.auth import login, logout as logout_user -from ctef_core.common import get_base_context +from ctef_core.common import get_base_context, get_default_competition from ctef_core.decorators import TaskDesc -from ctef_core.models import Task, TaskAttempt, TaskClue +from ctef_core.models import Task, TaskAttempt, TaskClue, CompetitionParticipation from .forms import EnterForm @@ -25,6 +25,14 @@ def tasks(request: HttpRequest): def leaderboard(request: HttpRequest): context = get_base_context(request, False) + + default_competition = get_default_competition() + participants = CompetitionParticipation.objects.filter( + competition=default_competition + ).order_by("score") + + context["participants"] = participants + return render(request, "ctef_web/leaderboard.html", context)