mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-02-01 04:42:21 +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 django.utils.text import normalize_newlines
|
||||||
|
|
||||||
from RIGS import models
|
from RIGS import models
|
||||||
|
from training import models as tmodels
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
@@ -190,6 +191,17 @@ def linkornone(target, namespace=None, autoescape=True):
|
|||||||
return "None"
|
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')
|
@register.inclusion_tag('partials/button.html')
|
||||||
def button(type, url=None, pk=None, clazz="", icon=None, text="", id=None, style=None):
|
def button(type, url=None, pk=None, clazz="", icon=None, text="", id=None, style=None):
|
||||||
if type == 'edit':
|
if type == 'edit':
|
||||||
|
|||||||
@@ -1,30 +1,37 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
from training import models
|
from training import models
|
||||||
from RIGS.models import Profile
|
from RIGS.models import Profile
|
||||||
|
|
||||||
class SessionLogForm(forms.Form):
|
class SessionLogForm(forms.Form):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# TODO Validation that dates cannot be in the future
|
|
||||||
class QualificationForm(forms.ModelForm):
|
class QualificationForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.TrainingItemQualification
|
model = models.TrainingItemQualification
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
# exclude = ['trainee']
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
pk = kwargs.pop('pk', None)
|
pk = kwargs.pop('pk', None)
|
||||||
super(QualificationForm, self).__init__(*args, **kwargs)
|
super().__init__()
|
||||||
self.fields['trainee'].initial = Profile.objects.get(pk=pk)
|
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):
|
class RequirementForm(forms.ModelForm):
|
||||||
|
depth = forms.ChoiceField(choices=models.TrainingItemQualification.CHOICES)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.TrainingLevelRequirement
|
model = models.TrainingLevelRequirement
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
pk = kwargs.pop('pk', None)
|
pk = kwargs.pop('pk', None)
|
||||||
super(RequirementForm, self).__init__(*args, **kwargs)
|
super().__init__()
|
||||||
self.fields['level'].initial = models.TrainingLevel.objects.get(pk=pk)
|
self.fields['level'].initial = models.TrainingLevel.objects.get(pk=pk)
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ class TrainingItem(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{}.{} {}".format(self.category.reference_number, self.reference_number, self.name)
|
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):
|
class TrainingItemQualification(models.Model):
|
||||||
STARTED = 0
|
STARTED = 0
|
||||||
@@ -86,12 +91,21 @@ class TrainingLevel(models.Model, RevisionMixin):
|
|||||||
def passed_out_requirements(self):
|
def passed_out_requirements(self):
|
||||||
return self.get_requirements_of_depth(TrainingItemQualification.PASSED_OUT)
|
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):
|
def __str__(self):
|
||||||
if self.department is None: # 2TA
|
if self.department is None: # 2TA
|
||||||
return self.get_level_display()
|
return self.get_level_display()
|
||||||
else:
|
else:
|
||||||
return "{} {}".format(self.get_department_display(), self.get_level_display())
|
return "{} {}".format(self.get_department_display(), self.get_level_display())
|
||||||
|
|
||||||
|
|
||||||
class TrainingLevelRequirement(models.Model):
|
class TrainingLevelRequirement(models.Model):
|
||||||
level = models.ForeignKey('TrainingLevel', related_name='requirements', on_delete=models.RESTRICT)
|
level = models.ForeignKey('TrainingLevel', related_name='requirements', on_delete=models.RESTRICT)
|
||||||
item = models.ForeignKey('TrainingItem', 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 static %}
|
||||||
{% load widget_tweaks %}
|
{% load widget_tweaks %}
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group form-row">
|
<div class="form-group form-row">
|
||||||
<label for="depth" class="col-sm-2 col-form-label">Depth</label>
|
<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>
|
</div>
|
||||||
<input type="submit" class="btn btn-primary">
|
<input type="submit" class="btn btn-primary">
|
||||||
</form>
|
</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' %}
|
{% extends 'base_rigs.html' %}
|
||||||
|
|
||||||
|
{% load user_has_qualification from filters %}
|
||||||
|
{% load percentage_complete from filters %}
|
||||||
|
|
||||||
{% block content %}
|
{% 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 %}">
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
@@ -11,13 +14,16 @@
|
|||||||
<div class="card-columns">
|
<div class="card-columns">
|
||||||
{% for level in levels %}
|
{% for level in levels %}
|
||||||
<div class="card my-3">
|
<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">
|
<div class="card-body">
|
||||||
|
<p>{{ level.description|truncatewords:30 }}</p>
|
||||||
<div class="progress mb-2">
|
<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>
|
</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>
|
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>
|
</button>
|
||||||
<div class="collapse reqs_{{level.pk}}">
|
<div class="collapse reqs_{{level.pk}}">
|
||||||
@@ -31,9 +37,9 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<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.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 }}</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 }}</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>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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(),
|
path('trainee/<int:pk>/edit/', views.AddQualification.as_view(),
|
||||||
name='edit_record'),
|
name='edit_record'),
|
||||||
path('session/', views.SessionLog.as_view(), name='session_log'),
|
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):
|
def get_success_url(self):
|
||||||
return reverse_lazy('trainee_detail')
|
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'),
|
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)
|
appname = str(app.label)
|
||||||
if appname == 'RIGS':
|
if appname == 'RIGS':
|
||||||
appname = 'rigboard'
|
appname = 'rigboard'
|
||||||
|
|||||||
Reference in New Issue
Block a user