added models and the flag page
This commit is contained in:
parent
e1b5e95e0e
commit
8410cc33da
11 changed files with 274 additions and 6 deletions
|
@ -1,3 +1,17 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
from source.apps.ctf import models
|
||||
|
||||
|
||||
@admin.register(models.Team)
|
||||
class TeamAdmin(admin.ModelAdmin):
|
||||
list_display = ("id", "name")
|
||||
|
||||
@admin.register(models.Flag)
|
||||
class FlagAdmin(admin.ModelAdmin):
|
||||
list_display = ("id", "name", "type")
|
||||
|
||||
|
||||
@admin.register(models.Hint)
|
||||
class HintAdmin(admin.ModelAdmin):
|
||||
list_display = ("id", "flag")
|
||||
|
|
|
@ -3,4 +3,4 @@ from django.apps import AppConfig
|
|||
|
||||
class CtfConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.ctf'
|
||||
name = 'source.apps.ctf'
|
||||
|
|
9
source/apps/ctf/forms.py
Normal file
9
source/apps/ctf/forms.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from django import forms
|
||||
|
||||
|
||||
class TeamForm(forms.Form):
|
||||
name = forms.CharField(label="Team's name", max_length=32)
|
||||
|
||||
|
||||
class FlagForm(forms.Form):
|
||||
name = forms.UUIDField(label="Identifier", max_length=32)
|
|
@ -1,3 +1,65 @@
|
|||
import uuid
|
||||
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
|
||||
class Team(models.Model):
|
||||
"""
|
||||
Represent a team participating in the event
|
||||
"""
|
||||
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
name = models.CharField(max_length=32)
|
||||
|
||||
validated_flags = models.ManyToManyField(to="Flag", blank=True, related_name="validated_by_teams")
|
||||
|
||||
def __str__(self):
|
||||
return f"<{self.__class__.__name__}({self.name!r}, {self.id!r})>"
|
||||
|
||||
|
||||
class Flag(models.Model):
|
||||
"""
|
||||
Represent a flag that can be validated by the team
|
||||
"""
|
||||
|
||||
class FlagType(models.IntegerChoices):
|
||||
NORMAL = 0
|
||||
BONUS = 1
|
||||
MALUS = 2
|
||||
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
|
||||
# the flag might depend on other flags
|
||||
parents = models.ManyToManyField(
|
||||
"self",
|
||||
blank=True,
|
||||
symmetrical=False,
|
||||
related_name="children",
|
||||
)
|
||||
|
||||
# information about the flags
|
||||
# TODO(Faraphel): store code as hash ?
|
||||
code = models.CharField(max_length=32) # the password that must be given to obtain the flag
|
||||
name = models.CharField(max_length=32) # the name of the flag
|
||||
description = models.TextField() # the description on how to obtain the flag
|
||||
|
||||
# TODO(Faraphel): replace with or add a points amount you gain ?
|
||||
type = models.IntegerField(choices=FlagType.choices, default=FlagType.NORMAL)
|
||||
|
||||
def __str__(self):
|
||||
return f"<{self.__class__.__name__}({self.name!r}, {self.id!r})>"
|
||||
|
||||
|
||||
class Hint(models.Model):
|
||||
"""
|
||||
Represent a hint that can be given to a team
|
||||
"""
|
||||
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
importance = models.PositiveIntegerField()
|
||||
description = models.TextField()
|
||||
penalty = models.PositiveIntegerField()
|
||||
flag = models.ForeignKey(to=Flag, on_delete=models.CASCADE, related_name="hints")
|
||||
|
||||
def __str__(self):
|
||||
return f"<{self.__class__.__name__}({self.flag.name!r}, {self.id!r})>"
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
|
|
105
source/apps/ctf/templates/ctf/flag_list.html
Normal file
105
source/apps/ctf/templates/ctf/flag_list.html
Normal file
|
@ -0,0 +1,105 @@
|
|||
{% extends "ctf/base/base.html" %}
|
||||
|
||||
{% block title %}Flags{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<script src="https://unpkg.com/@popperjs/core@2"></script>
|
||||
<script src="https://unpkg.com/tippy.js@6"></script>
|
||||
<script src="https://cytoscape.org/cytoscape.js-popper/cytoscape-popper.js"></script>
|
||||
<script src="https://unpkg.com/cytoscape/dist/cytoscape.min.js"></script>
|
||||
|
||||
<style>
|
||||
#cy {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div id="cy"></div>
|
||||
<script>
|
||||
function tippyFactory(reference, content) {
|
||||
// create a basic element for the tips object
|
||||
const element = document.createElement('div');
|
||||
|
||||
// build the tip inside
|
||||
return tippy(element, {
|
||||
getReferenceClientRect: reference.getBoundingClientRect,
|
||||
trigger: "manual",
|
||||
content: content,
|
||||
|
||||
arrow: true,
|
||||
placement: "bottom",
|
||||
hideOnClick: false,
|
||||
sticky: "reference",
|
||||
|
||||
interactive: true,
|
||||
appendTo: cy.container()
|
||||
});
|
||||
}
|
||||
// use the factory when creating tips
|
||||
cytoscape.use(cytoscapePopper(tippyFactory));
|
||||
|
||||
// create a graph based on our data
|
||||
const cy = cytoscape({
|
||||
container: document.getElementById('cy'),
|
||||
// content
|
||||
elements: [
|
||||
// nodes
|
||||
{% for flag in flags %}
|
||||
{data: {
|
||||
id: "{{ flag.id }}",
|
||||
name: "{{ flag.name }}",
|
||||
description: "{{ flag.description }}",
|
||||
teams: [
|
||||
{% for team in flag.validated_by_teams.all %}
|
||||
"{{ team.name }}",
|
||||
{% endfor %}
|
||||
],
|
||||
}},
|
||||
{% endfor %}
|
||||
|
||||
// links
|
||||
{% for flag in flags %}
|
||||
{% for child_flag in flag.children.all %}
|
||||
{data: {source: "{{ child_flag.id }}", target: "{{ flag.id }}"}},
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
],
|
||||
// style
|
||||
style: [
|
||||
{selector: 'node', style: {'label': 'data(name)'}},
|
||||
{selector: 'edge', style: {'target-arrow-shape': 'triangle'}},
|
||||
],
|
||||
// layout
|
||||
layout: {
|
||||
name: 'breadthfirst',
|
||||
directed: true
|
||||
}
|
||||
});
|
||||
|
||||
// show the description of the nodes on hover
|
||||
cy.ready(() => {
|
||||
cy.nodes().forEach(node => {
|
||||
// build the tip
|
||||
const tippy = node.popper({
|
||||
content: () => {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = node.data("description") + "<br/>---<br/>" + node.data("teams");
|
||||
return div;
|
||||
},
|
||||
trigger: 'manual',
|
||||
interactive: true,
|
||||
appendTo: cy.container()
|
||||
});
|
||||
|
||||
// events
|
||||
node.on('mouseover', () => tippy.show());
|
||||
node.on('mouseout', () => tippy.hide());
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
11
source/apps/ctf/templates/ctf/flag_submit.html
Normal file
11
source/apps/ctf/templates/ctf/flag_submit.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
{% extends "ctf/base/base.html" %}
|
||||
|
||||
{% block title %}Flag - Submit{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<input type="submit">
|
||||
</form>
|
||||
{% endblock %}
|
11
source/apps/ctf/templates/ctf/team_create.html
Normal file
11
source/apps/ctf/templates/ctf/team_create.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
{% extends "ctf/base/base.html" %}
|
||||
|
||||
{% block title %}Team - Create{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<input type="submit">
|
||||
</form>
|
||||
{% endblock %}
|
13
source/apps/ctf/templates/ctf/team_list.html
Normal file
13
source/apps/ctf/templates/ctf/team_list.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
{% extends "ctf/base/base.html" %}
|
||||
|
||||
{% block title %}Teams{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<ul>
|
||||
{% for team in teams %}
|
||||
<li>{{ team.name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<a href="{% url "team_create" %}">Create</a>
|
||||
{% endblock %}
|
|
@ -3,5 +3,9 @@ from django.urls import path
|
|||
from source.apps.ctf import views
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.homepage, name='homepage'),
|
||||
path("", views.view_homepage, name='homepage'),
|
||||
path("teams", views.view_teams, name='teams'),
|
||||
path("teams/create", views.view_team_create, name='team_create'),
|
||||
path("flags", views.view_flags, name='flags'),
|
||||
path("flags/submit", views.view_flag_submit, name='flag_submit'),
|
||||
]
|
||||
|
|
|
@ -1,5 +1,43 @@
|
|||
from django.shortcuts import render
|
||||
from django.shortcuts import render, redirect
|
||||
|
||||
from source.apps.ctf import forms, models
|
||||
|
||||
|
||||
# Create your views here.
|
||||
async def homepage(request):
|
||||
async def view_homepage(request):
|
||||
return render(request, "ctf/homepage.html")
|
||||
|
||||
|
||||
async def view_teams(request):
|
||||
teams = [team async for team in models.Team.objects.all()]
|
||||
return render(request, "ctf/team_list.html", context={"teams": teams})
|
||||
|
||||
|
||||
async def view_team_create(request):
|
||||
if request.method == "POST":
|
||||
form = forms.TeamForm(request.POST)
|
||||
if form.is_valid():
|
||||
# TODO(Faraphel): additional password to prevent unwanted team creation ?
|
||||
await models.Team.objects.acreate(name=form.cleaned_data["name"])
|
||||
return redirect("/")
|
||||
|
||||
else:
|
||||
form = forms.TeamForm()
|
||||
|
||||
return render(request, "ctf/team_create.html", context={"form": form})
|
||||
|
||||
|
||||
def view_flags(request):
|
||||
flags = models.Flag.objects.all()
|
||||
return render(request, "ctf/flag_list.html", context={"flags": flags})
|
||||
|
||||
|
||||
async def view_flag_submit(request):
|
||||
if request.method == "POST":
|
||||
form = forms.FlagForm(request.POST)
|
||||
if form.is_valid():
|
||||
return redirect("/")
|
||||
else:
|
||||
form = forms.FlagForm()
|
||||
|
||||
return render(request, "ctf/flag_submit.html", context={"form": form})
|
||||
|
|
Loading…
Reference in a new issue