UI for editing training level requirements

This commit is contained in:
2021-07-06 22:09:49 +01:00
parent e836195fef
commit 54f2bd36bd
11 changed files with 142 additions and 17 deletions

View File

@@ -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("<span class='fas fa-check text-success'></span>")
else:
return mark_safe("<span class='fas fa-hourglass-start text-warning'></span>")
@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':

View File

@@ -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)

View File

@@ -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)

View File

@@ -0,0 +1 @@
{% extends 'base_rigs.html' %}

View File

@@ -1,4 +1,4 @@
{% extends 'base_rigs.html' %}
{% extends 'base_training.html' %}
{% load static %}
{% load widget_tweaks %}
@@ -32,7 +32,7 @@
</div>
<div class="form-group form-row">
<label for="depth" class="col-sm-2 col-form-label">Depth</label>
{% 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' %}
</div>
<input type="submit" class="btn btn-primary">
</form>

View File

@@ -0,0 +1,44 @@
{% extends 'base_training.html' %}
{% block content %}
{% if edit or True %}
<div class="col-sm-12 text-right pr-0">
<a type="button" class="btn btn-success mb-3" href="{% url 'add_requirement' pk=object.pk %}">
<span class="fas fa-plus"></span> Add New Requirement
</a>
</div>
{% endif %}
<div class="card mb-3">
<h4 class="card-header">Description</h4>
<div class="card-body">
<p>{{ object.description }}</p>
</div>
</div>
<div class="card mb-3">
<div class="card-body">
<p>Users with this level...{% lorem %}</p>
</div>
</div>
<div class="card">
<h4 class="card-header">Level Requirements</h4>
<div class="card-body">
<table class="table card-body">
<thead>
<tr>
<th scope="col" class="table-warning">Training Started</th>
<th scope="col" class="table-success">Training Complete</th>
<th scope="col" class="table-info">Passed Out</th>
</tr>
</thead>
<tbody>
<tr>
<td><ul class="list-unstyled">{% for req in object.started_requirements %}<li>{{ req.item }} <a type="button" class="btn btn-danger btn-sm" href="{% url 'remove_requirement' pk=req.pk %}"><span class="fas fa-times-circle"></span></a></li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in object.complete_requirements %}<li>{{ req.item }} <a type="button" class="btn btn-danger btn-sm" href="{% url 'remove_requirement' pk=req.pk %}"><span class="fas fa-times-circle"></span></a></li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in object.passed_out_requirements %}<li>{{ req.item }} <a type="button" class="btn btn-danger btn-sm" href="{% url 'remove_requirement' pk=req.pk %}"><span class="fas fa-times-circle"></span></a></li>{% endfor %}</ul></td>
</tr>
</tbody>
</table>
</div>
</div>
{% include 'partials/last_edited.html' with target="traininglevel_history" %}
{% endblock %}

View File

@@ -1,9 +1,12 @@
{% extends 'base_rigs.html' %}
{% load user_has_qualification from filters %}
{% load percentage_complete from filters %}
{% block content %}
<div class="row my-3 text-right">
<div class="col-sm-12 text-right">
<a type="button" class="btn btn-success" href="{% url 'edit_record' pk=request.user.pk %}">
<i class="fas fa-plus"></i> Add New Training Record
<span class="fas fa-plus"></span> Add New Training Record
</a>
</div>
<div class="row mb-3">
@@ -11,13 +14,16 @@
<div class="card-columns">
{% for level in levels %}
<div class="card my-3">
<h3 class="card-header"><a href="{% url 'level_edit' level.pk %}">{{ level }}</a></h3>
<h3 class="card-header"><a href="{% url 'level_detail' level.pk %}">{{ level }}</a></h3>
<div class="card-body">
<p>{{ level.description|truncatewords:30 }}</p>
<div class="progress mb-2">
<div class="progress-bar progress-bar-striped" role="progressbar" style="width: 25%" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100">25% complete</div>
{% percentage_complete level request.user as completion %}
<div class="progress-bar progress-bar-striped" role="progressbar" style="width: 25%" aria-valuenow="{{completion}}" aria-valuemin="0" aria-valuemax="100">{{completion}}% complete</div>
</div>
<p>{{ level.description }}</p>
<button class="btn btn-link p-0" type="button" data-toggle="collapse" data-target=".reqs_{{level.pk}}" aria-expanded="false" aria-controls="reqs">
<button class="btn btn-link p-0" type="button" data-toggle="collapse" data-target=".reqs_{{level.pk}}" aria-expanded="false" aria-controls="reqs_{{level.pk}}">
Requirements <span class="fas fa-caret-right reqs_{{level.pk}} collapse show"></span><span class="fas fa-caret-down collapse reqs_{{level.pk}}"></span>
</button>
<div class="collapse reqs_{{level.pk}}">
@@ -31,9 +37,9 @@
</thead>
<tbody>
<tr>
<td><ul class="list-unstyled">{% for req in level.started_requirements %}<li>{{ req.item }}</li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in level.complete_requirements %}<li>{{ req.item }}</li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in level.passed_out_requirements %}<li>{{ req.item }}</li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in level.started_requirements %}<li>{{ req.item }} {% user_has_qualification request.user req.item 0 %}</li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in level.complete_requirements %}<li>{{ req.item }} {% user_has_qualification request.user req.item 1 %}</li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in level.passed_out_requirements %}<li>{{ req.item }} {% user_has_qualification request.user req.item 2 %}</li>{% endfor %}</ul></td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,16 @@
{% extends 'base_training.html' %}
{% block content %}
<div class="alert alert-danger" role="alert">
<p>Are you sure you wish to delete {{ page_title }}</p>
<div class="text-right">
<form action="{{ action_link }}" method="post">
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'level_detail' object.level.pk %}"/>
<input type="submit" value="Yes" class="btn btn-danger col-sm-1"/>
<a href="{% url 'level_detail' object.level.pk %}" class="btn btn-success col-sm-1">No</a>
</form>
</div>
</div>
{% endblock %}

View File

@@ -15,5 +15,7 @@ urlpatterns = [
path('trainee/<int:pk>/edit/', views.AddQualification.as_view(),
name='edit_record'),
path('session/', views.SessionLog.as_view(), name='session_log'),
path('level/<int:pk>/edit/', views.AddLevelRequirement.as_view(), name='level_edit'),
path('level/<int:pk>/', views.LevelDetail.as_view(), name='level_detail'),
path('level/<int:pk>/add_requirement/', views.AddLevelRequirement.as_view(), name='add_requirement'),
path('level/remove_requirement/<int:pk>/', views.RemoveRequirement.as_view(), name='remove_requirement'),
]

View File

@@ -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')

View File

@@ -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'