From 54f2bd36bdb349321ee7c70e88cdef9693513913 Mon Sep 17 00:00:00 2001 From: Arona Jones Date: Tue, 6 Jul 2021 22:09:49 +0100 Subject: [PATCH] UI for editing training level requirements --- RIGS/templatetags/filters.py | 12 +++++ training/forms.py | 15 +++++-- training/models.py | 14 ++++++ training/templates/base_training.html | 1 + training/templates/edit_training_level.html | 4 +- training/templates/level_detail.html | 44 +++++++++++++++++++ training/templates/trainee_detail.html | 24 ++++++---- ...aininglevelrequirement_confirm_delete.html | 16 +++++++ training/urls.py | 4 +- training/views.py | 23 ++++++++++ versioning/urls.py | 2 +- 11 files changed, 142 insertions(+), 17 deletions(-) create mode 100644 training/templates/base_training.html create mode 100644 training/templates/level_detail.html create mode 100644 training/templates/traininglevelrequirement_confirm_delete.html diff --git a/RIGS/templatetags/filters.py b/RIGS/templatetags/filters.py index 07263d67..078ea459 100644 --- a/RIGS/templatetags/filters.py +++ b/RIGS/templatetags/filters.py @@ -10,6 +10,7 @@ from django.utils.safestring import SafeData, mark_safe from django.utils.text import normalize_newlines from RIGS import models +from training import models as tmodels register = template.Library() @@ -190,6 +191,17 @@ def linkornone(target, namespace=None, autoescape=True): return "None" +@register.simple_tag +def user_has_qualification(user, item, depth): + if tmodels.TrainingItem.user_has_qualification(item, user, depth) is not None: + return mark_safe("") + else: + return mark_safe("") + +@register.simple_tag +def percentage_complete(level, user): + return level.percentage_complete(user) + @register.inclusion_tag('partials/button.html') def button(type, url=None, pk=None, clazz="", icon=None, text="", id=None, style=None): if type == 'edit': diff --git a/training/forms.py b/training/forms.py index c7e3028f..7fab1883 100644 --- a/training/forms.py +++ b/training/forms.py @@ -1,30 +1,37 @@ from django import forms +from datetime import date + from training import models from RIGS.models import Profile class SessionLogForm(forms.Form): pass -# TODO Validation that dates cannot be in the future + class QualificationForm(forms.ModelForm): class Meta: model = models.TrainingItemQualification fields = '__all__' - # exclude = ['trainee'] def __init__(self, *args, **kwargs): pk = kwargs.pop('pk', None) - super(QualificationForm, self).__init__(*args, **kwargs) + super().__init__() self.fields['trainee'].initial = Profile.objects.get(pk=pk) + def clean_date(self): + date = self.cleaned_data['date'] + if date > date.today(): + raise ValidationError('Qualification date may not be in the future') class RequirementForm(forms.ModelForm): + depth = forms.ChoiceField(choices=models.TrainingItemQualification.CHOICES) + class Meta: model = models.TrainingLevelRequirement fields = '__all__' def __init__(self, *args, **kwargs): pk = kwargs.pop('pk', None) - super(RequirementForm, self).__init__(*args, **kwargs) + super().__init__() self.fields['level'].initial = models.TrainingLevel.objects.get(pk=pk) diff --git a/training/models.py b/training/models.py index ba49f91c..a63cab16 100644 --- a/training/models.py +++ b/training/models.py @@ -27,6 +27,11 @@ class TrainingItem(models.Model): def __str__(self): return "{}.{} {}".format(self.category.reference_number, self.reference_number, self.name) + def user_has_qualification(self, user, depth): + for q in user.qualifications_obtained.all(): + if q.item == self and q.depth > depth: + return True + class TrainingItemQualification(models.Model): STARTED = 0 @@ -86,12 +91,21 @@ class TrainingLevel(models.Model, RevisionMixin): def passed_out_requirements(self): return self.get_requirements_of_depth(TrainingItemQualification.PASSED_OUT) + def percentage_complete(self, user): # FIXME + needed_qualifications = self.requirements.all() + relavant_qualifications = [x for x in user.qualifications_obtained.all() if x in self.requirements.all()] + if len(needed_qualifications) > 0: + return round(len(relavant_qualifications) / len(needed_qualifications)) + else: + return 0 + def __str__(self): if self.department is None: # 2TA return self.get_level_display() else: return "{} {}".format(self.get_department_display(), self.get_level_display()) + class TrainingLevelRequirement(models.Model): level = models.ForeignKey('TrainingLevel', related_name='requirements', on_delete=models.RESTRICT) item = models.ForeignKey('TrainingItem', on_delete=models.RESTRICT) diff --git a/training/templates/base_training.html b/training/templates/base_training.html new file mode 100644 index 00000000..f57bf1fa --- /dev/null +++ b/training/templates/base_training.html @@ -0,0 +1 @@ +{% extends 'base_rigs.html' %} diff --git a/training/templates/edit_training_level.html b/training/templates/edit_training_level.html index 8fcf2e4c..4f529493 100644 --- a/training/templates/edit_training_level.html +++ b/training/templates/edit_training_level.html @@ -1,4 +1,4 @@ -{% extends 'base_rigs.html' %} +{% extends 'base_training.html' %} {% load static %} {% load widget_tweaks %} @@ -32,7 +32,7 @@
- {% render_field form.depth|add_class:'form-control custom-select selectpicker col-sm'|attr:'required' %} + {% render_field form.depth|add_class:'form-control custom-select col-sm'|attr:'required' %}
diff --git a/training/templates/level_detail.html b/training/templates/level_detail.html new file mode 100644 index 00000000..ec5b5a00 --- /dev/null +++ b/training/templates/level_detail.html @@ -0,0 +1,44 @@ +{% extends 'base_training.html' %} + +{% block content %} +{% if edit or True %} +
+ + Add New Requirement + +
+{% endif %} +
+

Description

+
+

{{ object.description }}

+
+
+
+
+

Users with this level...{% lorem %}

+
+
+
+

Level Requirements

+
+ + + + + + + + + + + + + + + +
Training StartedTraining CompletePassed Out
    {% for req in object.started_requirements %}
  • {{ req.item }}
  • {% endfor %}
    {% for req in object.complete_requirements %}
  • {{ req.item }}
  • {% endfor %}
    {% for req in object.passed_out_requirements %}
  • {{ req.item }}
  • {% endfor %}
+
+
+{% include 'partials/last_edited.html' with target="traininglevel_history" %} +{% endblock %} diff --git a/training/templates/trainee_detail.html b/training/templates/trainee_detail.html index 652cebdc..5dee7551 100644 --- a/training/templates/trainee_detail.html +++ b/training/templates/trainee_detail.html @@ -1,9 +1,12 @@ {% extends 'base_rigs.html' %} +{% load user_has_qualification from filters %} +{% load percentage_complete from filters %} + {% block content %} -
+
@@ -11,13 +14,16 @@
{% for level in levels %}
-

{{ level }}

+

{{ level }}

+

{{ level.description|truncatewords:30 }}

-
25% complete
+ {% percentage_complete level request.user as completion %} + +
{{completion}}% complete
-

{{ level.description }}

-
@@ -31,9 +37,9 @@ -
    {% for req in level.started_requirements %}
  • {{ req.item }}
  • {% endfor %}
-
    {% for req in level.complete_requirements %}
  • {{ req.item }}
  • {% endfor %}
-
    {% for req in level.passed_out_requirements %}
  • {{ req.item }}
  • {% endfor %}
+
    {% for req in level.started_requirements %}
  • {{ req.item }} {% user_has_qualification request.user req.item 0 %}
  • {% endfor %}
+
    {% for req in level.complete_requirements %}
  • {{ req.item }} {% user_has_qualification request.user req.item 1 %}
  • {% endfor %}
+
    {% for req in level.passed_out_requirements %}
  • {{ req.item }} {% user_has_qualification request.user req.item 2 %}
  • {% endfor %}
diff --git a/training/templates/traininglevelrequirement_confirm_delete.html b/training/templates/traininglevelrequirement_confirm_delete.html new file mode 100644 index 00000000..5bd77bdd --- /dev/null +++ b/training/templates/traininglevelrequirement_confirm_delete.html @@ -0,0 +1,16 @@ +{% extends 'base_training.html' %} + +{% block content %} + +{% endblock %} diff --git a/training/urls.py b/training/urls.py index 6c2071e3..11aa55fb 100644 --- a/training/urls.py +++ b/training/urls.py @@ -15,5 +15,7 @@ urlpatterns = [ path('trainee//edit/', views.AddQualification.as_view(), name='edit_record'), path('session/', views.SessionLog.as_view(), name='session_log'), - path('level//edit/', views.AddLevelRequirement.as_view(), name='level_edit'), + path('level//', views.LevelDetail.as_view(), name='level_detail'), + path('level//add_requirement/', views.AddLevelRequirement.as_view(), name='add_requirement'), + path('level/remove_requirement//', views.RemoveRequirement.as_view(), name='remove_requirement'), ] diff --git a/training/views.py b/training/views.py index 6e90f5aa..e0e502a4 100644 --- a/training/views.py +++ b/training/views.py @@ -86,3 +86,26 @@ class AddLevelRequirement(generic.CreateView): def get_success_url(self): return reverse_lazy('trainee_detail') + + +class LevelDetail(generic.DetailView): + template_name = "level_detail.html" + model = models.TrainingLevel + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["page_title"] = "Training Level {}".format(self.object) + return context + + +class RemoveRequirement(generic.DeleteView): + model = models.TrainingLevelRequirement + template_name = 'traininglevelrequirement_confirm_delete.html' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["page_title"] = "Delete Requirement '{}' from Training Level {}?".format(self.object, self.object.level) + return context + + def get_success_url(self): + return self.request.POST.get('next') diff --git a/versioning/urls.py b/versioning/urls.py index 9bc250be..f0551eb0 100644 --- a/versioning/urls.py +++ b/versioning/urls.py @@ -13,7 +13,7 @@ urlpatterns = [ name='activity_feed'), ] -for app in [apps.get_app_config(label) for label in ("RIGS", "assets")]: +for app in [apps.get_app_config(label) for label in ("RIGS", "assets", "training")]: appname = str(app.label) if appname == 'RIGS': appname = 'rigboard'