mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-01-17 13:32:15 +00:00
UI for editing training level requirements
This commit is contained in:
@@ -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':
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
1
training/templates/base_training.html
Normal file
1
training/templates/base_training.html
Normal file
@@ -0,0 +1 @@
|
||||
{% extends 'base_rigs.html' %}
|
||||
@@ -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>
|
||||
|
||||
44
training/templates/level_detail.html
Normal file
44
training/templates/level_detail.html
Normal 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 %}
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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'),
|
||||
]
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user