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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ venv/

files/*
!files/.keep
attempt_logs/*

*.log
cache/
Expand Down
4 changes: 3 additions & 1 deletion challenge/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from django.contrib.admin import ModelAdmin

from challenge.forms import ChallengeAdminForm
from challenge.models import Challenge, ChallengeUser
from challenge.models import Challenge, ChallengeUser, ChallengeTroll, VoteReaction


class ChallengeAdmin(ModelAdmin):
Expand All @@ -11,3 +11,5 @@ class ChallengeAdmin(ModelAdmin):

admin.site.register(Challenge, admin_class=ChallengeAdmin)
admin.site.register(ChallengeUser)
admin.site.register(ChallengeTroll)
admin.site.register(VoteReaction)
8 changes: 7 additions & 1 deletion challenge/forms.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django import forms
from django.forms import ModelForm, Form

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


class ChallengeAdminForm(ModelForm):
Expand All @@ -21,3 +21,9 @@ class Meta:

class ChallengeTryForm(Form):
code = forms.CharField(initial='', required=True)


class VoteForm(ModelForm):
class Meta:
fields = ('vote', 'comment')
model = ChallengeUser
32 changes: 29 additions & 3 deletions challenge/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 3.2.8 on 2021-10-17 13:46
# Generated by Django 3.2.8 on 2021-10-29 14:10

import datetime
from django.conf import settings
Expand All @@ -22,7 +22,7 @@ class Migration(migrations.Migration):
('name', 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)),
('type', models.CharField(choices=[('img', 'Image'), ('file', 'File'), ('mp3', 'Audio'), ('mp4', 'Video'), ('html', 'HTML Template'), ('none', 'None')], max_length=10)),
('solution', models.CharField(max_length=1000)),
('file', models.FileField(upload_to='')),
('activation_date', models.DateTimeField(default=datetime.datetime.now)),
Expand All @@ -33,13 +33,39 @@ class Migration(migrations.Migration):
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)),
('last_try', models.DateTimeField(null=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)),
('vote', models.IntegerField(null=True)),
('comment', models.TextField(blank=True, null=True)),
('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)),
],
),
migrations.CreateModel(
name='VoteReaction',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.FileField(upload_to='')),
('type', models.CharField(choices=[('happy', 'Happy'), ('sad', 'Sad')], max_length=10)),
('challenge', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='challenge.challenge')),
],
options={
'unique_together': {('challenge', 'type')},
},
),
migrations.CreateModel(
name='ChallengeTroll',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.CharField(max_length=1000)),
('url', models.URLField()),
('challenge', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='challenge.challenge')),
],
options={
'unique_together': {('challenge', 'code')},
},
),
]
41 changes: 41 additions & 0 deletions challenge/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ class Challenge(models.Model):
TYPE_AUDIO = 'mp3'
TYPE_VIDEO = 'mp4'
TYPE_HTML = 'html'
TYPE_NONE = 'none'
TYPES = (
(TYPE_IMAGE, 'Image'),
(TYPE_FILE, 'File'),
(TYPE_AUDIO, 'Audio'),
(TYPE_VIDEO, 'Video'),
(TYPE_HTML, 'HTML Template'),
(TYPE_NONE, 'None'),
)

name = models.CharField(max_length=100)
Expand Down Expand Up @@ -48,6 +50,12 @@ def set_solution(self, solution):
def check_solution(self, solution):
return check_password(solution, self.solution)

def check_troll(self, code):
try:
return self.challengetroll_set.get(code=code).url
except ChallengeTroll.DoesNotExist:
return None


class ChallengeUser(models.Model):
user = models.ForeignKey('user.User', on_delete=models.DO_NOTHING)
Expand All @@ -58,3 +66,36 @@ class ChallengeUser(models.Model):
success = models.BooleanField(default=False)
attempts = models.IntegerField(default=0)
total_attempts = models.IntegerField(default=0)
vote = models.IntegerField(null=True)
comment = models.TextField(null=True, blank=True)


class ChallengeTroll(models.Model):
challenge = models.ForeignKey(Challenge, on_delete=models.CASCADE)
code = models.CharField(max_length=1000)
url = models.URLField()

def __str__(self):
return '%s (%s)' % (self.challenge, self.pk)

class Meta:
unique_together = ('challenge', 'code')


class VoteReaction(models.Model):
TYPE_HAPPY = 'happy'
TYPE_SAD = 'sad'
TYPES = [
(TYPE_HAPPY, 'Happy'),
(TYPE_SAD, 'Sad'),
]

challenge = models.ForeignKey(Challenge, on_delete=models.CASCADE)
image = models.FileField()
type = models.CharField(max_length=10, choices=TYPES)

def __str__(self):
return '%s - %s' % (self.challenge, self.get_type_display())

class Meta:
unique_together = ('challenge', 'type')
1 change: 1 addition & 0 deletions challenge/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class ChallengeStatsTable(tables.Table):
'{{ 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>')
action = tables.TemplateColumn(template_name='table_column/stats_action.html', orderable=False)

class Meta:
orderable = False
Expand Down
12 changes: 7 additions & 5 deletions challenge/templates/challenge.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
{% endif %}
</div>

<h1 style="text-align: center">{{ challenge.name }}</h1>
{% if form is None %}
<div class="alert alert-success" role="alert">Challenge completed!</div>
{% endif %}
{{ challenge.description|safe }}
<h1 style="text-align: center">{{ challenge.name }}{% if not form %} (Completed){% endif %}</h1>
<div style="margin-bottom: 20px">
{{ challenge.description|safe }}
</div>

<div style="display:flex">
<div style="margin: 0 auto;">
{% if challenge.type == challenge.TYPE_VIDEO %}
Expand Down Expand Up @@ -45,6 +45,8 @@ <h1 style="text-align: center">{{ challenge.name }}</h1>
{% bootstrap_form form %}
<button class="btn btn-block btn-primary">Submit</button>
</form>
{% else %}
<a class="btn btn-block btn-primary" href="{% url 'challenge-vote' challenge.id %}">Vote</a>
{% endif %}

{% endblock %}
6 changes: 5 additions & 1 deletion challenge/templates/challenge_stats.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@
<div class="col-sm-6" style="padding-left: 0px !important;">
<a href="{% url 'challenge' challenge.pk %}" class="btn btn-default">< Back</a>
</div>
<div class="col-sm-6 text-right" style="padding-right: 0px !important;">
<a href="{% url 'challenge-stats-attempt-all' challenge.pk %}" class="btn btn-primary">All attempts</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>
Succeed by {{ succeed }} users<br>
Average vote: {{ average_vote|floatformat:2 }}</p>

{% render_table table %}

Expand Down
34 changes: 34 additions & 0 deletions challenge/templates/challenge_stats_attempts.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{% 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-stats' challenge.pk %}" class="btn btn-default">< Back</a>
</div>
</div>

<h1 style="text-align: center">Attempts from challenge: {{ challenge.name }}</h1>
{% if user %}
<h2 style="text-align: center">From user: {{ user.username }}</h2>
{% endif %}

<table class="table">
<thead>
<tr>
<th>Attempt</th>
<th>Number of tries</th>
</tr>
</thead>
<tbody>
{% for attempt in attempts %}
<tr>
<td>{{ attempt.0 }}</td>
<td>{{ attempt.1 }}</td>
</tr>
{% endfor %}
</tbody>
</table>

{% endblock %}
46 changes: 46 additions & 0 deletions challenge/templates/challenge_vote.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{% extends 'base.html' %}
{% load bootstrap3 %}
{% load static %}
{% block head_title %}{{ challenge.name }}{% endblock %}
{% block head %}
<link rel="stylesheet" href="{% static 'lib/bars-square.css' %}">
{% 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>
</div>

<h1 style="text-align: center">Challenge completed: {{ challenge.name }}</h1>
<form method="post" style="margin-top: 20px">
{% csrf_token %}
<h2>Vote</h2>
{% bootstrap_form_errors form %}
<p>You have completed this challenge. Congratulations! We would like to hear what did you think about this challenge. <br>
This is completely optional but if you rate the challenge we would appreciate it a lot!</p>
<div style="margin-top: 20px; margin-bottom: 20px; align-content: center; width: 100%">
{% include 'include/number10.html' with name='vote' %}<br>
</div>
{% bootstrap_field form.comment %}
<button type="submit" class="btn btn-block btn-primary">Submit</button>
</form>

{% endblock %}
{% block extra_scripts %}
<script src="{% static 'lib/barrating.min.js' %}"></script>
<script type="text/javascript">
$(function () {
$('.barrating').barrating({
theme: 'bars-square',
showValues: true,
showSelectedRating: false
});
});
$(document).ready(function () {
let value = '{{ form.instance.vote }}';
if (value === 'None') value = '5';
$('.barrating').barrating('set', value);
})
</script>
{% endblock %}
19 changes: 19 additions & 0 deletions challenge/templates/challenge_vote_reaction.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% extends 'base.html' %}
{% load bootstrap3 %}
{% load static %}
{% block head_title %}Thanks!{% 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>
</div>

<h1 style="text-align: center">Thanks you {{ request.user.name }}!</h1>
<div style="display:flex">
<div style="margin: 0 auto; width: 50%">
<img src="{{ reaction.image.url }}" width="100%">
</div>
</div>

{% endblock %}
7 changes: 7 additions & 0 deletions challenge/templates/include/number10.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div>
<select name="{{ name }}" class="barrating" required>
{% for int, str in max_vote %}
<option value={{ str }}>{{ int }}</option>
{% endfor %}
</select>
</div>
1 change: 1 addition & 0 deletions challenge/templates/table_column/stats_action.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<a class="btn btn-primary" href="{% url 'challenge-stats-attempt' record.challenge_id record.user_id %}">Attempts</a>
7 changes: 6 additions & 1 deletion challenge/urls.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
from django.urls import path

from challenge.views import HomeView, ChallengeView, ChallengeStatsView
from challenge.views import HomeView, ChallengeView, ChallengeStatsView, ChallengeStatsAttemptView, VoteChallengeView, \
VoteChallengeReactionView

urlpatterns = [
path('', HomeView.as_view(), name='challenge-index'),
path('<int:c_id>/', ChallengeView.as_view(), name='challenge'),
path('<int:c_id>/vote/', VoteChallengeView.as_view(), name='challenge-vote'),
path('<int:c_id>/vote/reaction/<str:r_type>/', VoteChallengeReactionView.as_view(), name='challenge-reaction'),
path('<int:c_id>/stats/', ChallengeStatsView.as_view(), name='challenge-stats'),
path('<int:c_id>/stats/attempt/', ChallengeStatsAttemptView.as_view(), name='challenge-stats-attempt-all'),
path('<int:c_id>/stats/attempt/<str:u_id>/', ChallengeStatsAttemptView.as_view(), name='challenge-stats-attempt'),
]
Loading