Skip to content
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
29 changes: 25 additions & 4 deletions challenge/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,45 @@
# Generated by Django 3.2.8 on 2021-10-10 12:04
# Generated by Django 3.2.8 on 2021-10-17 13:46

import datetime
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='Challenge',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('order', models.IntegerField(primary_key=True, serialize=False)),
('type', models.CharField(choices=[('img', 'Image'), ('file', 'File'), ('mp3', 'Audio'), ('mp4', 'Video')], max_length=10)),
('solution', models.CharField(max_length=100)),
('description', models.TextField(blank=True, max_length=5000)),
('order', models.IntegerField()),
('type', models.CharField(choices=[('img', 'Image'), ('file', 'File'), ('mp3', 'Audio'), ('mp4', 'Video'), ('html', 'HTML Template')], max_length=10)),
('solution', models.CharField(max_length=1000)),
('file', models.FileField(upload_to='')),
('activation_date', models.DateTimeField(default=datetime.datetime.now)),
],
),
migrations.CreateModel(
name='ChallengeUser',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_try', models.DateTimeField(default=datetime.datetime.now)),
('last_try', models.DateTimeField(auto_now_add=True)),
('attempt_date', models.DateTimeField(default=datetime.datetime.now)),
('success', models.BooleanField(default=False)),
('attempts', models.IntegerField(default=0)),
('total_attempts', models.IntegerField(default=0)),
('challenge', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='challenge.challenge')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL)),
],
),
]
41 changes: 0 additions & 41 deletions challenge/migrations/0002_auto_20211010_1451.py

This file was deleted.

27 changes: 19 additions & 8 deletions challenge/mixins.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
from datetime import datetime

import pytz
from django.contrib.auth.mixins import AccessMixin
from django.shortcuts import redirect, get_object_or_404
from django.urls import reverse

from challenge.models import ChallengeUser
from challenge.models import ChallengeUser, Challenge


class ChallengePermissionMixin(AccessMixin):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
c_id = kwargs.get('id', 1)
if c_id != 1:
c_id -= 1
try:
if not ChallengeUser.objects.get(challenge_id=c_id, user=request.user).success:
return self.handle_no_permission()
except ChallengeUser.DoesNotExist:
if request.user.username is None:
return redirect(reverse('index'))
if not request.user.is_staff:
c_id = kwargs.get('c_id', 1)
challenge = get_object_or_404(Challenge, pk=c_id)
if pytz.utc.localize(datetime.now()) < challenge.activation_date:
return self.handle_no_permission()
if challenge.order != 1:
c_order = challenge.order - 1
success = ChallengeUser.objects.filter(challenge__order=c_order, user=request.user,
success=True).count()
challenges = Challenge.objects.filter(order=c_order).count()
if challenges > success:
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
19 changes: 17 additions & 2 deletions challenge/models.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,46 @@
from datetime import datetime

import pytz
from django.contrib.auth.hashers import make_password, check_password
from django.db import models
from django.template import Template, Context


class Challenge(models.Model):
TYPE_IMAGE = 'img'
TYPE_FILE = 'file'
TYPE_AUDIO = 'mp3'
TYPE_VIDEO = 'mp4'
TYPE_HTML = 'html'
TYPES = (
(TYPE_IMAGE, 'Image'),
(TYPE_FILE, 'File'),
(TYPE_AUDIO, 'Audio'),
(TYPE_VIDEO, 'Video'),
(TYPE_HTML, 'HTML Template'),
)

name = models.CharField(max_length=100)
description = models.TextField(max_length=5000, blank=True)
order = models.IntegerField(primary_key=True)
order = models.IntegerField()
type = models.CharField(choices=TYPES, max_length=10)
solution = models.CharField(max_length=1000)
file = models.FileField()
activation_date = models.DateTimeField(default=datetime.now)

def __str__(self):
return '%s - %s' % (self.order, self.name)

def active(self):
return self.activation_date <= pytz.utc.localize(datetime.now())

def get_template_html(self):
with self.file.open('r') as file:
text = file.read()
template = Template(text)
context = Context({'self': self})
return template.render(context=context)

# solution will be hard encrypted for security reasons
def set_solution(self, solution):
self.solution = make_password(solution)
Expand All @@ -38,7 +53,7 @@ class ChallengeUser(models.Model):
user = models.ForeignKey('user.User', on_delete=models.DO_NOTHING)
challenge = models.ForeignKey('challenge.Challenge', on_delete=models.DO_NOTHING)
first_try = models.DateTimeField(default=datetime.now)
last_try = models.DateTimeField(auto_now_add=True)
last_try = models.DateTimeField(null=True)
attempt_date = models.DateTimeField(default=datetime.now)
success = models.BooleanField(default=False)
attempts = models.IntegerField(default=0)
Expand Down
17 changes: 17 additions & 0 deletions challenge/tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import django_tables2 as tables

from challenge.models import ChallengeUser


class ChallengeStatsTable(tables.Table):
first_try = tables.TemplateColumn(template_code='<span name="dates">'
'{{ record.first_try|date:"m/d/Y H:i:s T" }}</span>')
last_try = tables.TemplateColumn(template_code='<span name="dates">'
'{{ record.last_try|date:"m/d/Y H:i:s T" }}</span>')

class Meta:
orderable = False
model = ChallengeUser
template_name = "django_tables2/bootstrap.html"
fields = ['user__username', 'user__email', 'total_attempts', 'last_try', 'first_try', 'success']
empty_text = "User hasn't tried this challenge yet"
32 changes: 25 additions & 7 deletions challenge/templates/challenge.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,22 @@
{% load bootstrap3 %}
{% block head_title %}{{ challenge.name }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-6" style="padding-left: 0px !important;">
<a href="{% url 'challenge-index' %}" class="btn btn-default">< Back</a>
</div>
{% if request.user.is_staff %}
<div class="col-sm-6 text-right" style="padding-right: 0px !important;">
<a href="{% url 'challenge-stats' challenge.pk %}" class="btn btn-primary">Stats</a>
</div>
{% endif %}
</div>

<h1 style="text-align: center">{{ challenge.name }}</h1>
<p>{{ challenge.description }}</p>
{% if form is None %}
<div class="alert alert-success" role="alert">Challenge completed!</div>
{% endif %}
{{ challenge.description|safe }}
<div style="display:flex">
<div style="margin: 0 auto;">
{% if challenge.type == challenge.TYPE_VIDEO %}
Expand All @@ -20,13 +34,17 @@ <h1 style="text-align: center">{{ challenge.name }}</h1>
<a href="{{ challenge.file.url }}" class="btn btn-block btn-default" target="_blank">Download files</a>
{% elif challenge.type == challenge.TYPE_IMAGE %}
<img src="{{ challenge.file.url }}" width="100%">
{% endif %}
{% elif challenge.type == challenge.TYPE_HTML %}
{{ challenge.get_template_html|safe }}
{% endif %}
</div>
</div>
<form method="post" style="margin-top: 20px">
{% csrf_token %}
{% bootstrap_form form %}
<button class="btn btn-block btn-primary">Submit</button>
</form>
{% if form %}
<form method="post" style="margin-top: 20px">
{% csrf_token %}
{% bootstrap_form form %}
<button class="btn btn-block btn-primary">Submit</button>
</form>
{% endif %}

{% endblock %}
39 changes: 39 additions & 0 deletions challenge/templates/challenge_index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{% extends 'base.html' %}
{% load bootstrap3 %}
{% block head_title %}Challenges{% endblock %}
{% block content %}
<h1 style="text-align: center">Challenges</h1>

{% for phase, group in challenge_groups.items %}
<h2>Phase {{ phase }}</h2>
<div class="list-group">
{% for challenge in group %}
<a {% if challenge.active and player_phase >= challenge.order or request.user.is_staff %}href="{% url 'challenge' challenge.pk %}"{% endif %} class="list-group-item" style="font-size: x-large">
<div class="row">
<div class="col-lg-11" style="padding-left: 0px !important;">
Challenge: {{ challenge.name }}<br>
<p style="font-size: small; margin: 0px !important;">{% if challenge.active %}Active now{% else %}Not active. Starts at <span name="dates">{{ challenge.activation_date|date:"m/d/Y H:i:s T" }}{% endif %}</span></p>
</div>
<div class="col-lg-1 text-right" style="padding-right: 0px !important;">
{% if challenge.player_try.0.success %}<span class="badge badge-primary">Done</span>{% endif %}
</div>
</div>
</a>
{% endfor %}
</div>
{% endfor %}
{% endblock %}
{% block extra_scripts %}
<script>
function to_date(text) {
let date = new Date(text);
return date.toLocaleString()
}
$( document ).ready(function() {
$('span[name="dates"]').each(function () {
this.innerHTML = to_date(this.innerHTML);
})
});

</script>
{% endblock %}
45 changes: 45 additions & 0 deletions challenge/templates/challenge_stats.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{% extends 'base.html' %}
{% load bootstrap3 %}
{% load render_table from django_tables2 %}
{% block head_title %}{{ challenge.name }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-6" style="padding-left: 0px !important;">
<a href="{% url 'challenge' challenge.pk %}" class="btn btn-default">< Back</a>
</div>
</div>

<h1 style="text-align: center">Stats from challenge: {{ challenge.name }}</h1>
<p>Reloading page in <span id="clock">60</span> seconds<br>
Tried by {{ object_list|length }} users<br>
Succeed by {{ succeed }} users</p>

{% render_table table %}

{% endblock %}
{% block extra_scripts %}
<script>
let time = 60;
function to_date(text) {
let date = new Date(text);
return date.toLocaleString()
}
function timer() {
time -= 1;
if (time <= 0) {
location.reload();
} else {
$('#clock').each(function () {
this.innerHTML = time.toString();
})
}
}
$( document ).ready(function() {
$('span[name="dates"]').each(function () {
this.innerHTML = to_date(this.innerHTML);
})
setInterval(timer, 1000); // Reloads page in 1 minute
});

</script>
{% endblock %}
7 changes: 4 additions & 3 deletions challenge/urls.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from django.urls import path

from challenge.views import HomeView, ChallengeView
from challenge.views import HomeView, ChallengeView, ChallengeStatsView

urlpatterns = [
path('', HomeView.as_view(), name='home'),
path('challenge/<int:c_id>/', ChallengeView.as_view(), name='challenge'),
path('', HomeView.as_view(), name='challenge-index'),
path('<int:c_id>/', ChallengeView.as_view(), name='challenge'),
path('<int:c_id>/stats/', ChallengeStatsView.as_view(), name='challenge-stats'),
]
Loading