From 35f864e18a428632bd80adf9fc626343a9529ede Mon Sep 17 00:00:00 2001 From: Trullemans Gregory Date: Thu, 11 Nov 2021 15:21:48 +0100 Subject: [PATCH] Adding learning line app --- Ultron/urls.py | 3 +- followup/admin.py | 49 ++++++---- .../migrations/0005_auto_20211109_1121.py | 56 ++++++++++++ .../migrations/0006_auto_20211109_1737.py | 43 +++++++++ .../migrations/0007_alter_skill_options.py | 17 ++++ followup/models.py | 89 +++++++++++++------ followup/urls.py | 20 +++-- followup/views.py | 56 ++++++++++-- jumpers/views.py | 2 +- templates/base.html | 1 + templates/clubs/list.html | 6 +- templates/jumpers/details.html | 12 +-- templates/skills/details.html | 40 +++++++++ templates/skills/list.html | 81 +++++++++++++++++ 14 files changed, 408 insertions(+), 67 deletions(-) create mode 100644 followup/migrations/0005_auto_20211109_1121.py create mode 100644 followup/migrations/0006_auto_20211109_1737.py create mode 100644 followup/migrations/0007_alter_skill_options.py create mode 100644 templates/skills/details.html create mode 100644 templates/skills/list.html diff --git a/Ultron/urls.py b/Ultron/urls.py index 3ae63b1de2..1881472cfe 100644 --- a/Ultron/urls.py +++ b/Ultron/urls.py @@ -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"), diff --git a/followup/admin.py b/followup/admin.py index 7ab3341ed0..c5de5854d1 100644 --- a/followup/admin.py +++ b/followup/admin.py @@ -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) diff --git a/followup/migrations/0005_auto_20211109_1121.py b/followup/migrations/0005_auto_20211109_1121.py new file mode 100644 index 0000000000..c1f68c48c3 --- /dev/null +++ b/followup/migrations/0005_auto_20211109_1121.py @@ -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')}, + ), + ] diff --git a/followup/migrations/0006_auto_20211109_1737.py b/followup/migrations/0006_auto_20211109_1737.py new file mode 100644 index 0000000000..f1848578ed --- /dev/null +++ b/followup/migrations/0006_auto_20211109_1737.py @@ -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', + ), + ] diff --git a/followup/migrations/0007_alter_skill_options.py b/followup/migrations/0007_alter_skill_options.py new file mode 100644 index 0000000000..c3031b6628 --- /dev/null +++ b/followup/migrations/0007_alter_skill_options.py @@ -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'}, + ), + ] diff --git a/followup/models.py b/followup/models.py index 8f71f038f7..f7145ddd87 100644 --- a/followup/models.py +++ b/followup/models.py @@ -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") \ No newline at end of file diff --git a/followup/urls.py b/followup/urls.py index d1d9862a42..f1e4530598 100644 --- a/followup/urls.py +++ b/followup/urls.py @@ -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//", 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//", views.chrono_create_or_update, name='chrono_create'), +] + +skill_urlpatterns = [ + re_path( + r"^(?P(level|rank|difficulty))/(?P[\w]+)/(?P[\w]+)$", + views.skill_listing, + name="skill_listing_by_key", + ), + path(r"/", views.skill_details, name="skill_details"), + path(r"", views.skill_listing, name='skill_list'), ] \ No newline at end of file diff --git a/followup/views.py b/followup/views.py index fb78079bb0..f0ea7ea6d2 100644 --- a/followup/views.py +++ b/followup/views.py @@ -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) \ No newline at end of file + 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) \ No newline at end of file diff --git a/jumpers/views.py b/jumpers/views.py index 69b40887c2..ab2f0b3213 100644 --- a/jumpers/views.py +++ b/jumpers/views.py @@ -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) diff --git a/templates/base.html b/templates/base.html index 8cd3f7febe..3d31be63b8 100644 --- a/templates/base.html +++ b/templates/base.html @@ -59,6 +59,7 @@