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
|
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):
|
class CtfConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
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
|
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">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}{% endblock %}</title>
|
<title>{% block title %}{% endblock %}</title>
|
||||||
{% block head %}
|
{% block head %}
|
||||||
{% endblock %}
|
{% 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
|
from source.apps.ctf import views
|
||||||
|
|
||||||
urlpatterns = [
|
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.
|
# Create your views here.
|
||||||
async def homepage(request):
|
async def view_homepage(request):
|
||||||
return render(request, "ctf/homepage.html")
|
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