Adding learning line app

This commit is contained in:
Trullemans Gregory 2021-11-11 15:21:48 +01:00
parent a7c77d32c7
commit 35f864e18a
14 changed files with 408 additions and 67 deletions

View File

@ -30,7 +30,8 @@ urlpatterns = [
path(r"club/", include(jumpers.urls.club_urlpatterns)),
# Follow-up management
path(r"chrono/", include(followup.urls.chronos_urlpatterns)),
path(r"chrono/", include(followup.urls.chrono_urlpatterns)),
path(r"skill/", include(followup.urls.skill_urlpatterns)),
# path(r"search/", config.views.search, name="global_search"),

View File

@ -1,30 +1,41 @@
from django.contrib import admin
from .models import Chrono, LearnedSkill
from .models import Chrono, LearnedSkill, Skill
from django_extensions.admin import ForeignKeyAutocompleteAdmin
class ChronoAdmin(ForeignKeyAutocompleteAdmin):
model = Chrono
list_display = ('date', 'jumper', 'score', 'type')
list_filter = ('type', )
# search_fields = ('jumper', 'routine')
autocomplete_fields = ['jumper']
related_search_fields = {
'jumper': ('last_name', 'first_name')
}
class SkillAdmin(admin.ModelAdmin):
model = Skill
list_display = ('name', 'numeric_notation', 'difficulty', 'level', 'rank')
search_fields = ('name', )
# autocomplete_fields = ['ancestor']
# related_search_fields = {
# 'jumper': ('last_name', 'first_name')
# }
class LearnedSkillAdmin(admin.ModelAdmin):
model = LearnedSkill
list_display = ("date", "jumper", "skill")
# search_fields = ('jumper', 'routine')
autocomplete_fields = ["jumper"]
related_search_fields = {
"jumper": ("last_name", "first_name")
}
class ChronoAdmin(ForeignKeyAutocompleteAdmin):
model = Chrono
list_display = ("date", "jumper", "score", "type")
list_filter = ("type", )
# search_fields = ('jumper', 'routine')
autocomplete_fields = ["jumper"]
related_search_fields = {
"jumper": ("last_name", "first_name")
}
list_display = ('jumper', 'skill', 'cando', 'date')
list_filter = ('jumper', 'skill', 'cando')
search_fields = ('jumper', 'skill')
autocomplete_fields = ['jumper', 'skill']
admin.site.register(Chrono, ChronoAdmin)
admin.site.register(LearnedSkill, LearnedSkillAdmin)
admin.site.register(Skill, SkillAdmin)

View File

@ -0,0 +1,56 @@
# Generated by Django 3.2.8 on 2021-11-09 11:21
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('jumpers', '0005_club_city'),
('followup', '0004_auto_20211031_1530'),
]
operations = [
migrations.AlterModelOptions(
name='learnedskill',
options={'verbose_name': 'Can Do', 'verbose_name_plural': "Can Do's"},
),
migrations.AddField(
model_name='learnedskill',
name='cando',
field=models.PositiveSmallIntegerField(choices=[(0, 'No'), (1, 'With help'), (2, 'Without help'), (3, 'Chained')], default=0, verbose_name='Can do type'),
preserve_default=False,
),
migrations.AlterField(
model_name='learnedskill',
name='jumper',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='can_do_skill', to='jumpers.jumper', verbose_name='Jumper'),
),
migrations.CreateModel(
name='LearningLine',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
('description', models.CharField(max_length=255)),
('difficulty', models.DecimalField(decimal_places=1, max_digits=3, verbose_name='Difficulty')),
('level', models.PositiveSmallIntegerField(default=0, verbose_name='Level')),
('rank', models.PositiveSmallIntegerField(default=0, verbose_name='Rank')),
('numeric_notation', models.CharField(max_length=25)),
('ancestor', models.ManyToManyField(blank=True, null=True, related_name='_followup_learningline_ancestor_+', to='followup.LearningLine')),
],
options={
'verbose_name': 'Learning Line',
'ordering': ['name'],
},
),
migrations.AlterField(
model_name='learnedskill',
name='skill',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='done_by_jumpers', to='followup.learningline', verbose_name='Skill'),
),
migrations.AlterUniqueTogether(
name='learnedskill',
unique_together={('jumper', 'skill', 'date')},
),
]

View File

@ -0,0 +1,43 @@
# Generated by Django 3.2.8 on 2021-11-09 17:37
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('followup', '0005_auto_20211109_1121'),
]
operations = [
migrations.AlterModelOptions(
name='learnedskill',
options={'verbose_name': 'Learned Skill', 'verbose_name_plural': 'Learned Skills'},
),
migrations.CreateModel(
name='Skill',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
('description', models.CharField(max_length=255)),
('difficulty', models.DecimalField(decimal_places=1, max_digits=3, verbose_name='Difficulty')),
('level', models.PositiveSmallIntegerField(default=0, verbose_name='Level')),
('rank', models.PositiveSmallIntegerField(default=0, verbose_name='Rank')),
('numeric_notation', models.CharField(max_length=25)),
('ancestor', models.ManyToManyField(blank=True, null=True, related_name='_followup_skill_ancestor_+', to='followup.Skill')),
],
options={
'verbose_name': 'Skill',
'ordering': ['name'],
},
),
migrations.AlterField(
model_name='learnedskill',
name='skill',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='done_by_jumpers', to='followup.skill', verbose_name='Skill'),
),
migrations.DeleteModel(
name='LearningLine',
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 3.2.8 on 2021-11-09 18:29
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('followup', '0006_auto_20211109_1737'),
]
operations = [
migrations.AlterModelOptions(
name='skill',
options={'ordering': ['name'], 'verbose_name': 'Skill', 'verbose_name_plural': 'Skills'},
),
]

View File

@ -1,33 +1,9 @@
from django.db import models
from datetime import date
from django.db.models.deletion import CASCADE
from jumpers.models import Jumper
class LearnedSkill(models.Model):
"""
Représente les skills qu'un gymnaste sait faire.
"""
class Meta:
verbose_name = "Learned Skill"
verbose_name_plural = "Learned Skills"
ordering = ["date", "jumper"]
unique_together = ("jumper", "skill")
jumper = models.ForeignKey(
Jumper,
verbose_name="Jumper",
related_name="learned_skill",
on_delete=models.CASCADE,
)
skill = models.CharField(max_length=25, null=False, blank=False)
date = models.DateField(default=date.today, verbose_name="Date")
def __str__(self):
return "%s - %s (%s)" % (
self.jumper,
self.skill,
self.date,
)
class Chrono(models.Model):
"""
@ -75,3 +51,64 @@ class Chrono(models.Model):
tof = round(( self.score * 13 ) / 15, 3)
tof = ( tof * 1000 ) - (( tof * 1000 ) % 5)
return tof / 1000
class Skill(models.Model):
"""
Représente la ligne d'apprentissage.
"""
class Meta:
verbose_name = "Skill"
verbose_name_plural = 'Skills'
ordering = ['name']
name = models.CharField(max_length=50, null=False, blank=False)
description = models.CharField(max_length=255, null=False, blank=False)
difficulty = models.DecimalField(
max_digits=3,
decimal_places=1,
verbose_name="Difficulty"
)
level = models.PositiveSmallIntegerField(verbose_name="Level", default=0)
rank = models.PositiveSmallIntegerField(verbose_name="Rank", default=0)
numeric_notation = models.CharField(max_length=25)
ancestor = models.ManyToManyField("self", null=True, blank=True)
def __str__(self):
return "%s (%s) - %s" % (
self.name,
self.numeric_notation,
self.difficulty
)
class LearnedSkill(models.Model):
"""
Représente la capacité d'un gymnaste à savori faire un skill de la ligne d'apprentissage.
"""
TYPE_CHOICES = ((0, "No"), (1, "With help"), (2, "Without help"), (3, "Chained"))
class Meta:
verbose_name = 'Learned Skill'
verbose_name_plural = 'Learned Skills'
unique_together = ('jumper', 'skill', 'date')
jumper = models.ForeignKey(
Jumper,
verbose_name='Jumper',
related_name='can_do_skill',
on_delete=models.CASCADE,
)
skill = models.ForeignKey(
Skill,
verbose_name='Skill',
related_name='done_by_jumpers',
on_delete=models.CASCADE,
)
cando = models.PositiveSmallIntegerField(
choices=TYPE_CHOICES, verbose_name="Can do type"
)
date = models.DateField(default=date.today, verbose_name="Date")

View File

@ -1,9 +1,19 @@
from django.urls import path
from django.urls import path, re_path
from . import views
chronos_urlpatterns = [
path(r"", views.chrono_listing, name="chrono_list"),
path(r"create/", views.chrono_create_or_update, name="chrono_create"),
path(r"create/<int:jumperid>/", views.chrono_create_or_update, name="chrono_create"),
chrono_urlpatterns = [
path(r"", views.chrono_listing, name='chrono_list'),
path(r"create/", views.chrono_create_or_update, name='chrono_create'),
path(r"create/<int:jumperid>/", views.chrono_create_or_update, name='chrono_create'),
]
skill_urlpatterns = [
re_path(
r"^(?P<field>(level|rank|difficulty))/(?P<expression>[\w]+)/(?P<value>[\w]+)$",
views.skill_listing,
name="skill_listing_by_key",
),
path(r"<int:skillid>/", views.skill_details, name="skill_details"),
path(r"", views.skill_listing, name='skill_list'),
]

View File

@ -2,15 +2,17 @@ from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from django.http import HttpResponse, HttpResponseRedirect
from django.db.models import Q
from jumpers.models import Jumper
from .models import Chrono
from .models import Chrono, Skill
from .forms import ChronoForm
@login_required
# @require_http_methods(["GET"])
@require_http_methods(["GET"])
def chrono_listing(request):
""" Récupère la liste des chronos """
chrono_list = Chrono.objects.all()
context = {'chrono_list': chrono_list}
return render(request, "chronos/list.html", context)
@ -19,8 +21,7 @@ def chrono_listing(request):
@login_required
@require_http_methods(["GET", "POST"])
def chrono_create_or_update(request, chronoid=None, jumperid=None):
""" Création ou modification d'un chrono.
"""
""" Création ou modification d'un chrono. """
if chronoid:
chrono = get_object_or_404(Chrono, pk=chronoid)
@ -54,4 +55,49 @@ def chrono_create_or_update(request, chronoid=None, jumperid=None):
form = ChronoForm(instance=chrono, initial=data)
context = {"form": form, "chronoid": chronoid}
return render(request, "chronos/create.html", context)
return render(request, "chronos/create.html", context)
@login_required
@require_http_methods(["GET"])
def skill_listing(request, field=None, expression=None, value=None, level=None):
"""
Récupère la liste des skills suivant un pattern si celui-ci est définit.
"""
pattern = None
if not field or not value or not expression:
pattern = request.GET.get("pattern", None)
if pattern:
skill_list = Skill.objects.filter(
Q(longLabel__icontains=pattern) | Q(shortLabel__icontains=pattern)
)
elif field and expression and value:
kwargs = {"{0}__{1}".format(field, expression): value}
# print(kwargs)
# skill_list = Skill.objects.filter(level__lte=4)
skill_list = Skill.objects.filter(**kwargs)
elif level is not None:
skill_list = Skill.objects.filter
else:
skill_list = Skill.objects.all()
context = {'skill_list': skill_list}
return render(request, 'skills/list.html', context)
@login_required
@require_http_methods(["GET"])
def skill_details(request, skillid):
"""
Récupère toutes les informations d'un skill.
:param skillig: id d'un `skill`
:type skillid: int
:return: skill
"""
context = {"skill": get_object_or_404(Skill, pk=skillid)}
return render(request, "skills/details.html", context)

View File

@ -23,7 +23,7 @@ def jumper_listing(request):
"""Liste tous les gymnasts connus
"""
jumper_list = Jumper.objects.all()
context = {"jumper_list": jumper_list}
context = {'jumper_list': jumper_list}
return render(request, "jumpers/list.html", context)

View File

@ -59,6 +59,7 @@
<ul class="nav">
{% menuitem 'home' 'tim-icons icon-chart-pie-36' 'Dashboard' %}
{% menuitem 'jumper_list' 'tim-icons icon-badge' 'Jumpers' %}
{% menuitem 'skill_list' 'tim-icons icon-molecule-40' 'Skills' %}
{% menuitem 'club_list' 'tim-icons icon-square-pin' 'Clubs' %}
{% menuitem 'chrono_list' 'far fa-stopwatch' 'Chronos' %}
<li>

View File

@ -11,21 +11,17 @@
<table class="table tablesorter table-striped table-condensed" data-sort="table" id="maintable">
<thead>
<tr>
<th style="width: 3%"></th>
<th class="header text-left" style="width: 35%">Name</th>
<th class="header text-left" style="width: 10%">Acronym</th>
<th class="header text-left" style="width: 35%">City</th>
<th class="header text-center">Active ?</th>
</tr>
</thead>
<tbody>
{% for club in club_list %}
<tr>
<td></td>
<td class="text-left">{{ club.name }}</td>
<td class="text-left"> {{ club.name }}</td>
<td class="text-left">{{ club.acronym }}</td>
<td class="text-left">{{ club.city }}</td>
<td class="text-center">{{ place.active }}</td>
</tr>
{% endfor %}
</tbody>

View File

@ -39,15 +39,17 @@
<table class="table tablesorter table-striped table-condensed" data-sort="table" id="skilltable">
<thead>
<tr>
<th class="header text-left" style="width: 40%">Date</th>
<th class="header text-left" style="width: 60%">Skill</th>
<th class="header text-left" style="width: 25%">Date</th>
<th class="header text-left" style="width: 50%">Skill</th>
<th class="header text-left" style="width: 30%">Type</th>
</tr>
</thead>
<tbody>
{% for skill in learnedskills_list %}
{% for learnedskill in learnedskills_list %}
<tr>
<td class="text-left">{{ skill.date | date:"d-m-Y" }}</a></td>
<td class="text-left">{{ skill.skill }}</a></td>
<td class="text-left">{{ learnedskill.date | date:"d-m-Y" }}</a></td>
<td class="text-left"><a href="{% url 'skill_details' learnedskill.skill.id %}">{{ learnedskill.skill.name }}</a></td>
<td class="text-left">{{ learnedskill.get_cando_display }}</a></td>
</tr>
{% endfor %}
</tbody>

View File

@ -0,0 +1,40 @@
{% extends "base.html" %}
<!-- {% block page_title %}.: Skill details :.{% endblock %} -->
<!-- {% block title %}Skill{% endblock %} -->
{% block content %}
<div class="card mb-0">
<div class="card-header">
<h3 class="card-title"> {{ skill.name }}</h3>
<h4 class="card-subtitle"> {{ skill.numeric_notation }}</h4>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h4>Informations</h4>
Notation : <a href="#">{{ skill.numeric_notation }}</a><br />
Difficulty : <a href="/skill/difficulty/exact/{{ skill.difficulty }}">{{ skill.difficulty }}</a><br />
Level : <a href="{% url 'skill_listing_by_key' 'level' 'exact' skill.level %}">{{ skill.level }}</a><br />
Rank : <a href="{% url 'skill_listing_by_key' 'rank' 'exact' skill.rank %}">{{ skill.level }}</a></p>
</div>
<div class="col-md-6">
{% if skill.ancestor.all %}
<h4>Ancestor</h4>
<ul>
{% for ancestor in skill.ancestor.all %}
<li><a href="{% url 'skill_details' ancestor.id %}">{{ ancestor.name }}</a></li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}
{% block footerscript %}
{% endblock %}

View File

@ -0,0 +1,81 @@
{% extends "listing.html" %}
<!-- {% block page_title %}.: Skill's list :.{% endblock %}
{% block title %}Skills{% endblock %}
{% block searchurl %}skill{% endblock %}
{% block search %}skill{% endblock %} -->
{% block datacontent %}
<div class="card mb-0">
<div class="card-header">
<h4 class="card-title"> Skills' Listing</h4>
</div>
<div class="card-body">
<div class="table-responsive">
{% if skill_list %}
<table class="table tablesorter table-striped table-condensed" data-sort="table" id="maintable">
<thead class="text-primary">
<tr>
<th class="text-left">Label</th>
<th class="text-center">Notation</th>
<th class="header text-center">Diff.</th>
<th class="header text-center">Level</th>
<th class="header text-center">Rank</th>
</tr>
</thead>
<tbody>
{% for skill in skill_list %}
<tr>
<td class="text-left">&nbsp;<a href="{% url 'skill_details' skill.id %}">{{ skill.name }}</a></td>
<td class="text-center">{{ skill.numeric_notation }}</td>
<td class="text-center">{{ skill.difficulty }}</td>
<td class="text-center">{{ skill.level }}</td>
<td class="text-center">{{ skill.rank }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>There are no skills corresponding to your criterias</p>
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% block pagination %}
{% if skills.has_previous and skills.has_next %}
<div class="table-full">
<div class="btn-group">
{% if skills.has_previous %}
<button type="button" class="btn btn-primary-outline">
<a href="?page={{ skills.previous_page_number }}"><<</a>
</button>
{% endif %}
<button type="button" class="btn btn-primary-outline">&nbsp;{{ skills.number }}&nbsp;</button>
{% if skills.has_next %}
<button type="button" class="btn btn-primary-outline">
<a href="?page={{ skills.next_page_number }}">>></a>
</button>
{% endif %}
</div>
</div>
{% endif %}
{% endblock %}
{% block footerscript %}
<script type="text/javascript">
$(document).ready(function() {
$('#maintable').tablesorter({
headers: {
6: { sorter: false },
2: { sorter: false },
},
sortList: [[4,0], [0,0]]
})
});
</script>
{% endblock %}