Compare commits

...

4 Commits

Author SHA1 Message Date
6b19d0e8b8 Fix has_required_levels logic being backward 2022-01-03 15:31:54 +00:00
3e8cfe4f11 Repair confirmation logic 2022-01-03 15:28:17 +00:00
7a70270dfd Add ability to view other users progress on a level
That's kind of important huh :p
2022-01-03 14:59:30 +00:00
5160eb7f78 Add confirm button stuff 2022-01-03 14:38:43 +00:00
7 changed files with 44 additions and 44 deletions

View File

@@ -201,7 +201,10 @@ class TrainingLevel(models.Model, RevisionMixin):
return 0
def user_has_requirements(self, user):
return all(TrainingItem.user_has_qualification(req.item, user, req.depth) for req in self.requirements.all())
has_required_items = all(TrainingItem.user_has_qualification(req.item, user, req.depth) for req in self.requirements.all())
# Always true if there are no prerequisites, otherwise get a set of prerequsite IDs and check if they are a subset of the set of qualification IDs
has_required_levels = not self.prerequisite_levels.all().exists() or set(self.prerequisite_levels.values_list('pk', flat=True)).issubset(set(user.level_qualifications.values_list('level', flat=True)))
return has_required_items and has_required_levels
def __str__(self):
if self.department is None:

View File

@@ -58,7 +58,7 @@
</div>
</div>
<div class="card">
<h4 class="card-header">Level Requirements</h4>
<div class="card-header"><h4 class="card-title">Level Requirements</h4> {% if u.pk != request.user.pk %}<h5 class="card-subtitle font-italic">for {{ u }}</h5>{% endif %}</div>
<table class="table card-body">
<thead>
<tr>
@@ -71,16 +71,16 @@
{% for level in object.prerequisite_levels.all %}
<tr data-toggle="collapse" data-target="#{{level.pk}}" style="cursor: pointer;"><th colspan="3" class="text-center font-italic" data-toggle="collapse" data-target="#level_{{level.pk}}">{{level}} (prerequisite)</th></tr>
<tr id="level_{{level.pk}}" class="collapse">
<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>
<td><ul class="list-unstyled">{% for req in level.started_requirements %}<li>{{ req.item }} {% user_has_qualification u req.item 0 %}</li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in level.complete_requirements %}<li>{{ req.item }} {% user_has_qualification u 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 u req.item 2 %}</li>{% endfor %}</ul></td>
</tr>
{% endfor %}
<tr><th colspan="3" class="text-center">{{object}}</th></tr>
<tr>
<td><ul class="list-unstyled">{% for req in object.started_requirements %}<li>{{ req.item }} {% user_has_qualification request.user req.item 0 %} {% if request.user.is_supervisor or perms.training.change_traininglevel %}<a type="button" class="btn btn-link tn-sm p-0 align-baseline" href="{% url 'remove_requirement' pk=req.pk %}"><span class="fas fa-trash-alt text-danger"></span></a>{%endif%}</li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in object.complete_requirements %}<li>{{ req.item }} {% user_has_qualification request.user req.item 1 %} {% if request.user.is_supervisor or perms.training.change_traininglevel %}<a type="button" class="btn btn-link tn-sm p-0 align-baseline" href="{% url 'remove_requirement' pk=req.pk %}"><span class="fas fa-trash-alt text-danger"></span></a>{%endif%}</li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in object.passed_out_requirements %}<li>{{ req.item }} {% user_has_qualification request.user req.item 2 %} {% if request.user.is_supervisor or perms.training.change_traininglevel %}<a type="button" class="btn btn-link tn-sm p-0 align-baseline"" href="{% url 'remove_requirement' pk=req.pk %}" title="Delete requirement"><span class="fas fa-trash-alt text-danger"></span></a>{%endif%}</li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in object.started_requirements %}<li>{{ req.item }} {% user_has_qualification u req.item 0 %} {% if request.user.is_supervisor or perms.training.change_traininglevel %}<a type="button" class="btn btn-link tn-sm p-0 align-baseline" href="{% url 'remove_requirement' pk=req.pk %}"><span class="fas fa-trash-alt text-danger"></span></a>{%endif%}</li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in object.complete_requirements %}<li>{{ req.item }} {% user_has_qualification u req.item 1 %} {% if request.user.is_supervisor or perms.training.change_traininglevel %}<a type="button" class="btn btn-link tn-sm p-0 align-baseline" href="{% url 'remove_requirement' pk=req.pk %}"><span class="fas fa-trash-alt text-danger"></span></a>{%endif%}</li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in object.passed_out_requirements %}<li>{{ req.item }} {% user_has_qualification u req.item 2 %} {% if request.user.is_supervisor or perms.training.change_traininglevel %}<a type="button" class="btn btn-link tn-sm p-0 align-baseline"" href="{% url 'remove_requirement' pk=req.pk %}" title="Delete requirement"><span class="fas fa-trash-alt text-danger"></span></a>{%endif%}</li>{% endfor %}</ul></td>
</tr>
</tbody>
</table>
@@ -88,12 +88,12 @@
<div class="card-body">
<ul>
{% for level in object.prerequisite_levels.all %}
{% user_level_if_present request.user level as level_qualification %}
<li><a href="{{level.get_absolute_url}}">{{ level }}</a> <span class="fas {% if level_qualification %}text-success fa-check{% if level_qualification.confirmed_by is not None %}-double{% endif %}{% else %}fa-hourglass-start text-warning{%endif%}"></span></li>
{% user_level_if_present u level as level_qualification %}
<li><a href="{% url 'level_detail' level.pk u.pk %}">{{ level }}</a> <span class="fas {% if level_qualification %}text-success fa-check{% if level_qualification.confirmed_by is not None %}-double{% endif %}{% else %}fa-hourglass-start text-warning{%endif%}"></span></li>
{% for nested_level in level.prerequisite_levels.all %}
{% user_level_if_present request.user nested_level as nested_level_qualification %}
{% user_level_if_present u nested_level as nested_level_qualification %}
<ul>
<li><a href="{{nested_level.get_absolute_url}}">{{ nested_level }}</a> <span class="fas {% if nested_level_qualification %}text-success fa-check{% if nested_level_qualification.confirmed_by is not None %}-double{% endif %}{% else %}fa-hourglass-start text-warning{%endif%}"></span></li>
<li><a href="{% url 'level_detail' nested_level.pk u.pk %}">{{ nested_level }}</a> <span class="fas {% if nested_level_qualification %}text-success fa-check{% if nested_level_qualification.confirmed_by is not None %}-double{% endif %}{% else %}fa-hourglass-start text-warning{%endif%}"></span></li>
</ul>
{% endfor %}
{% empty %}

View File

@@ -2,6 +2,7 @@
{% load static %}
{% load percentage_complete from tags %}
{% load confirm_button from tags %}
{% load markdown_tags %}
{% block css %}
@@ -54,15 +55,7 @@
<li class="list-group-item">
{{ qual.level.get_icon }}
<a href="{% url 'level_detail' qual.level.pk %}">{{ qual.level }}</a>
{% if qual.confirmed_on is None %}
{% if request.user.pk != object.pk and request.user.is_supervisor %}
<span class="badge badge-warning">Awaiting Confirmation</span> <a class="btn btn-info" href="{% url 'confirm_level' object.pk qual.level.pk %}">Confirm</a>
{% else %}
<button class="btn btn-warning" disabled>Awaiting Confirmation</button>
{% endif %}
{% else %}
Confirmed by <a href="{{ qual.confirmed_by.get_absolute_url }}">{{ qual.confirmed_by|default:'System' }}</a> on {{ qual.confirmed_on|date }}
{% endif %}
</li>
{% empty %}
<div class="alert alert-warning mx-auto">No qualifications in any levels yet...did someone forget to fill out the paperwork?</div>
@@ -72,14 +65,23 @@
{% for level in started_levels %}
{% percentage_complete level object as completion %}
<div class="card my-3 border-warning">
<h3 class="card-header"><a href="{{ level.get_absolute_url }}">{{ level }}</a></h3>
<h3 class="card-header"><a href="{% url 'level_detail' level.pk object.pk %}">{{ level }}</a></h3>
<div class="card-body">
{{ level.description|markdown }}
</div>
<div class="card-footer">
<div class="progress">
<div class="progress-bar progress-bar-striped" role="progressbar" style="width: {{completion}}%" aria-valuenow="{{completion}}" aria-valuemin="0" aria-valuemax="100">{{completion}}% complete</div>
</div>
</div>
{% if completion == 100 %}
<br>
{% confirm_button request.user object level as cb %}
{% if cb %}
<div class="d-flex justify-content-between">{{ cb }}</div>
{% else %}
<p class="font-italic pt-2 pb-0">Missing prequisite level(s)</p>
{% endif %}
{% endif %}
</div>
</div>
{% endfor %}

View File

@@ -3,6 +3,7 @@ from django import template
from django.utils.html import escape
from django.utils.safestring import SafeData, mark_safe
from django.utils.text import normalize_newlines
from django.urls import reverse
from training import models
@@ -40,3 +41,14 @@ def get_supervisor(tech):
@register.filter
def get_levels_of_depth(trainee, level):
return trainee.level_qualifications.all().exclude(confirmed_on=None).exclude(level__department=models.TrainingLevel.HAULAGE).select_related('level').filter(level__level=level)
@register.simple_tag
def confirm_button(user, trainee, level):
if level.user_has_requirements(trainee):
string = "<span class='badge badge-warning p-2'>Awaiting Confirmation</span>"
if models.Trainee.objects.get(pk=user.pk).is_supervisor or user.has_perm('training.add_traininglevelqualification'):
string += "<a class='btn btn-info' href='{}'>Confirm</a>".format(reverse('confirm_level', kwargs={'pk': trainee.pk, 'level_pk': level.pk}))
return mark_safe(string)
else:
return ""

View File

@@ -3,9 +3,3 @@ import pytest
from pytest_django.asserts import assertFormError, assertRedirects, assertContains, assertNotContains
pytestmark = pytest.mark.django_db
def test_(admin_client):
url = reverse('add_qualification')
response = admin_client.post(url)
assertFormError(response, 'form', 'name', 'This field is required.')

View File

@@ -17,11 +17,13 @@ urlpatterns = [
name='add_qualification'),
path('trainee/<int:pk>/edit_qualification/', permission_required_with_403('training.change_trainingitemqualification')(views.EditQualification.as_view()),
name='edit_qualification'),
path('session/', login_required(views.SessionLog.as_view()), name='session_log'),
path('levels/', login_required(views.LevelList.as_view()), name='level_list'),
path('level/<int:pk>/', login_required(views.LevelDetail.as_view()), name='level_detail'),
path('level/<int:pk>/user/<int:u>/', login_required(views.LevelDetail.as_view()), name='level_detail'),
path('level/<int:pk>/add_requirement/', login_required(views.AddLevelRequirement.as_view()), name='add_requirement'),
path('level/remove_requirement/<int:pk>/', login_required(views.RemoveRequirement.as_view()), name='remove_requirement'),
path('trainee/<int:pk>/level/<int:level_pk>/confirm', login_required(views.ConfirmLevel.as_view()), name='confirm_level'),
path('trainee/<int:pk>/item_record', login_required(views.TraineeItemDetail.as_view()), name='trainee_item_detail'),
]

View File

@@ -75,6 +75,7 @@ class LevelDetail(generic.DetailView):
context = super().get_context_data(**kwargs)
context["page_title"] = "Training Level {} {}".format(self.object, self.object.get_icon)
context["users_with"] = map(lambda qual: qual.trainee, models.TrainingLevelQualification.objects.filter(level=self.object))
context["u"] = models.Trainee.objects.get(pk=self.kwargs['u']) if 'u' in self.kwargs else self.request.user
return context
@@ -114,17 +115,6 @@ class TraineeList(generic.ListView):
return context
class SessionLog(generic.FormView):
template_name = "session_log_form.html"
form_class = forms.SessionLogForm
def get_context_data(self, **kwargs):
context = super(SessionLog, self).get_context_data(**kwargs)
context["page_title"] = "Log New Training Session"
context["depths"] = models.TrainingItemQualification.CHOICES
return context
class AddQualification(generic.CreateView, ModalURLMixin):
template_name = "edit_training_record.html"
model = models.TrainingItemQualification
@@ -215,10 +205,7 @@ class ConfirmLevel(generic.RedirectView):
@transaction.atomic()
@reversion.create_revision()
def get_redirect_url(self, *args, **kwargs):
level_qualification = models.TrainingLevelQualification.objects.get(trainee=kwargs['pk'], level=kwargs['level_pk'])
level_qualification.confirmed_by = self.request.user
level_qualification.confirmed_on = timezone.now()
level_qualification.save()
level_qualification = models.TrainingLevelQualification.objects.create(trainee=models.Trainee.objects.get(pk=kwargs['pk']), level=models.TrainingLevel.objects.get(pk=kwargs['level_pk']), confirmed_by=self.request.user, confirmed_on=timezone.now())
reversion.add_to_revision(level_qualification.trainee)
reversion.set_user(self.request.user)
return reverse_lazy('trainee_detail', kwargs={'pk': kwargs['pk']})