Commit b84b91fc authored by Léo Grange's avatar Léo Grange
Browse files

several style improvements and factorization of results table

parent c065bb31
......@@ -41,6 +41,8 @@ INSTALLED_APPS = [
'sass_processor',
'compressor',
'django_markup',
# an awful customization... sorry guys!
'django_node_assets',
# managing long running tasks
'django_rq',
# our own applications ;)
......@@ -147,6 +149,8 @@ STATICFILES_FINDERS = [
'sass_processor.finders.CssFinder',
# and for the resource compressor
'compressor.finders.CompressorFinder',
# finally, the dark magic
'django_node_assets.finders.NodeModulesFinder',
]
# misc stuff for SASS processor
......@@ -195,6 +199,10 @@ LOGGING = {
}
}
# node modules stuff...
NODE_PACKAGE_JSON = os.path.join(BASE_DIR, 'package.json')
NODE_MODULES_ROOT = os.path.join(BASE_DIR, 'node_modules')
# local override of config files if needed (for production)
# TODO this is not the most elegant design, see other possibilities in
......
......@@ -63,3 +63,21 @@ class ExerciseSubmission(models.Model):
# TODO improve
return f'Submission of <{self.user}> for <{self.exercise}> [{self.id}]'
# XXX is it the good place to put this model/view weird mix?
class Metric:
def __init__(self, field, name, unit, description):
self.field = field
self.name = name
self.unit = unit
self.description = description
metrics = [
Metric('compiled_size', 'Compiled size', 'bytes', "Total size of the submission binary (program and data)."),
Metric('total_instructions', 'Instructions completed', 'n', "Number of instructions completed to solve a test case (including conditional instructions not executed)."),
]
def get_metric(self, metric):
return self.__getattribute__(metric.field)
......@@ -25,6 +25,12 @@ $breakpoints: (
}
// better navigation
body {
margin: 0;
}
.content {
@include respond-to('small') {
......@@ -165,3 +171,88 @@ input:required:invalid {
font-size: 100%;
}
}
// for beautiful sorting, using tablesort package (direct CSS inclusion here)
th[role=columnheader]:not(.no-sort) {
cursor: pointer;
}
th[role=columnheader]:not(.no-sort):after {
content: '';
float: right;
margin-top: 7px;
border-width: 0 4px 4px;
border-style: solid;
border-color: #404040 transparent;
visibility: hidden;
opacity: 0;
-ms-user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
th[aria-sort=ascending]:not(.no-sort):after {
border-bottom: none;
border-width: 4px 4px 0;
}
th[aria-sort]:not(.no-sort):after {
visibility: visible;
opacity: 0.4;
}
th[role=columnheader]:not(.no-sort):hover:after {
visibility: visible;
opacity: 1;
}
// other result-related stuff
.metric-best {
font-weight: bold;
color: black;
&:after{
display: inline-block;
content: url("/static/images/medal.svg");
height: 1em;
width: 1em;
vertical-align: -0.1em;
}
}
// header and navigation
header {
display: flex;
// to improve
justify-content: center;
align-items: flex-end;
padding: 1rem 0;
margin-bottom: 1rem;
background: #B4A0FF;
a {
font-weight: bold;
}
nav {
display: flex;
align-items: flex-end;
ul {
display: flex;
list-style-type: none;
margin-bottom: 0;
}
li {
margin: 0 1em;
}
button {
margin-bottom: 0;
}
}
}
<?xml version="1.0" encoding="iso-8859-1"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.043 185.043"><path d="M153.243.006h-33.528L98.011 31.012l16.754 23.951zM98.017 68.524v-5.717h11.262L65.321 0H31.8l43.958 62.807h11.286v5.717c-29.63 2.783-52.906 27.782-52.906 58.118 0 32.199 26.199 58.401 58.398 58.401 32.196 0 58.404-26.202 58.404-58.401-.012-30.348-23.284-55.341-52.923-58.118zM92.531 175.69c-27.048 0-49.045-22-49.045-49.049 0-27.048 21.997-49.045 49.045-49.045 27.051 0 49.045 21.997 49.045 49.045 0 27.049-21.994 49.049-49.045 49.049z"/><path d="M92.531 92.348a34.29 34.29 0 0125.967 11.893l4.713-4.074a40.496 40.496 0 00-30.68-14.051 40.49 40.49 0 00-30.684 14.051l4.713 4.067a34.326 34.326 0 0125.971-11.886z"/><path d="M99.914 118.884l-7.383-14.954-7.384 14.954-16.51 2.399 11.946 11.643-2.812 16.44 14.76-7.763 14.762 7.763-2.812-16.44 11.946-11.643zM92.531 161.149c1.97 3.258 4.39 4.634 8.065 5.547 3.684.907 6.441.828 9.724-1.127-3.641-5.839-11.853-7.879-17.789-4.42zM103.653 158.915c2.923 2.436 5.645 2.946 9.426 2.605 3.763-.328 6.363-1.303 8.829-4.226-5.347-4.335-13.774-3.604-18.255 1.621zM113.425 153.166c3.562 1.364 6.314.95 9.773-.591 3.452-1.546 5.596-3.318 6.979-6.874-6.467-2.375-14.2 1.071-16.752 7.465zM120.799 144.556c3.812.122 6.284-1.162 9.043-3.738 2.764-2.588 4.207-4.957 4.354-8.769-6.881-.146-13.056 5.633-13.397 12.507zM124.977 134.01c3.641-1.12 5.565-3.142 7.331-6.479 1.778-3.35 2.368-6.053 1.254-9.706-6.539 2.107-10.51 9.578-8.585 16.185zM125.488 122.678c3.075-2.247 4.238-4.78 4.823-8.519.591-3.732.256-6.497-1.961-9.584-5.517 4.128-6.832 12.494-2.862 18.103zM74.747 165.569c3.273 1.955 6.038 2.041 9.715 1.127 3.672-.913 6.086-2.289 8.068-5.547-5.943-3.459-14.16-1.419-17.783 4.42zM92.531 163.999c-3.267 1.017-5.289 4.707-4.372 8.014 1.83-.543 2.783-1.535 3.699-3.186.35-.645.523-1.229.673-1.826.155.597.332 1.182.679 1.826.91 1.656 1.873 2.643 3.693 3.186.929-3.307-1.106-7.003-4.372-8.014zM63.157 157.295c2.457 2.91 5.063 3.897 8.829 4.226 3.772.341 6.491-.17 9.42-2.605-4.482-5.226-12.918-5.957-18.249-1.621zM71.629 153.166c-2.542-6.388-10.281-9.84-16.748-7.465 1.373 3.556 3.522 5.328 6.984 6.874 3.468 1.535 6.212 1.944 9.764.591zM64.268 144.556c-.344-6.874-6.519-12.652-13.402-12.507.137 3.812 1.586 6.187 4.36 8.769 2.755 2.576 5.218 3.86 9.042 3.738zM60.082 134.01c1.921-6.606-2.031-14.078-8.592-16.185-1.096 3.653-.505 6.356 1.267 9.706 1.762 3.337 3.683 5.359 7.325 6.479zM59.57 122.678c3.979-5.62 2.664-13.975-2.85-18.103-2.226 3.087-2.554 5.852-1.964 9.596.582 3.739 1.742 6.26 4.814 8.507z"/></svg>
\ No newline at end of file
{% comment %}
The view logic to show a sortable and customizable table with the different results is quite complex...
This (partial) template can be included where it is required.
TODO: Find a way to avoid duplicating the tablesort JS include files (maybe django-sekizai?)
{% endcomment %}
<table class="sortable">
<thead>
<th>Utilisateur</th>
<th>Instructions</th>
<th>Taille (octets)</th>
</thead>
<tbody>
{% for result in results %}
<tr>
<td>{{ result.user.username }}</td>
<td class="numeric"><span {% if result.rank == 1 %}class="metric-best"{% endif %}>{{ result.total_instructions }}</span></td>
<td class="numeric">{{ result.compiled_size }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% comment %}
Page header with navigation links and co...
{% endcomment %}
<header>
<div><a href="{% url 'benchmark:index' %}">Assembly</a></div>
<nav role="navigation">
<ul>
<li><a href="#">Challenges en cours</a></li>
<li><a href="#">Mes soumissions</a></li>
<li><a href="#">Top utilisateurs</a></li>
</ul>
<button>Connexion</button>
</nav>
</header>
......@@ -10,6 +10,7 @@
{% block header %}{% endblock %}
</head>
<body>
{% include "./_header.html" %}
<div class="content">
{% block content %}
{% endblock %}
......
{% extends "benchmark/base.html" %}
{% load markup_tags %}
{% load static %}
{% block title %}ARM code competition!{% endblock %}
......@@ -21,18 +22,13 @@
<br/>
<h3>Résultats des participants</h3>
<table>
<tr>
<th>Utilisateur</th>
<th>Instructions</th>
<th>Taille (octets)</th>
</tr>
{% for result in exercise_best_results %}
<tr>
<td>{{ result.user.username }}</td>
<td class="numeric">{{ result.total_instructions }}</td>
<td class="numeric">{{ result.compiled_size }}</td>
</tr>
{% endfor %}
</table>
{% include "./_challenge_result_table.html" with results=exercise_best_results %}
{% endblock %}
{% block scripts %}
<script src="{% static 'tablesort/dist/tablesort.min.js' %}" type="text/javascript" charset="utf-8"></script>
<script src="{% static 'tablesort/dist/sorts/tablesort.number.min.js' %}" type="text/javascript" charset="utf-8"></script>
<script>
new Tablesort(document.querySelector('table.sortable'));
</script>
{% endblock %}
{% extends "benchmark/base.html" %}
{% load static %}
{% block title %}ARM code competition!{% endblock %}
{% block content %}
<h1>Compétition de code assembleur ARM</h1>
<div>
Tests divers et variés
</div>
<div>
<button>Push me!</button>
</div>
<h2>Résultats pour le challenge «{{ challenge.title }}»</h2>
<a href="/benchmark/challenge/{{ challenge.id }}">Détails du challenge</a>
<table>
<tr>
<th>Utilisateur</th>
<th>Instructions</th>
<th>Taille (octets)</th>
</tr>
{% for result in exercise_best_results %}
<tr>
<td>{{ result.user.username }}</td>
<td class="numeric">{{ result.total_instructions }}</td>
<td class="numeric">{{ result.compiled_size }}</td>
</tr>
{% endfor %}
</table>
{% include "./_challenge_result_table.html" with results=exercise_best_results %}
{% endblock %}
{% block scripts %}
<script src="{% static 'tablesort/dist/tablesort.min.js' %}" type="text/javascript" charset="utf-8"></script>
<script src="{% static 'tablesort/dist/sorts/tablesort.number.min.js' %}" type="text/javascript" charset="utf-8"></script>
<script>
new Tablesort(document.querySelector('table.sortable'));
</script>
{% endblock %}
......@@ -20,9 +20,27 @@ def index(request):
def challenge_details(request, challenge_id):
challenge = Exercise.objects.get(pk=challenge_id)
# TODO improve the best results search...
best_results = ExerciseSubmission.objects.filter(exercise__id=challenge.id).filter(status=ExerciseSubmission.Status.SUCCESS)
# TODO improve the requests for finding the best metrics
for metric in ExerciseSubmission.metrics:
bests_metric = [r.get_metric(metric) for r in best_results.order_by(metric.field)[:2]]
#best_results = list(best_results)
for res in best_results:
value = res.get_metric(metric)
if value == bests_metric[0]:
res.rank = 1
elif value == bests_metric[1]:
res.rank = 2
else:
res.rank = 0
context = {
'exercise_best_results': ExerciseSubmission.objects.filter(exercise__id=challenge.id)
.filter(status=ExerciseSubmission.Status.SUCCESS),
'exercise_best_results': best_results,
'challenge': challenge,
}
return render(request, 'benchmark/challenge_details.html', context)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment