mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-04-02 16:51:47 +00:00
Compare commits
4 Commits
99e05d91bb
...
6b19d0e8b8
| Author | SHA1 | Date | |
|---|---|---|---|
|
6b19d0e8b8
|
|||
|
3e8cfe4f11
|
|||
|
7a70270dfd
|
|||
|
5160eb7f78
|
@@ -201,7 +201,10 @@ class TrainingLevel(models.Model, RevisionMixin):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
def user_has_requirements(self, user):
|
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):
|
def __str__(self):
|
||||||
if self.department is None:
|
if self.department is None:
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<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">
|
<table class="table card-body">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -71,16 +71,16 @@
|
|||||||
{% for level in object.prerequisite_levels.all %}
|
{% 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 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">
|
<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.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 request.user req.item 1 %}</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 request.user req.item 2 %}</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>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<tr><th colspan="3" class="text-center">{{object}}</th></tr>
|
<tr><th colspan="3" class="text-center">{{object}}</th></tr>
|
||||||
<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.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 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.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 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.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>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -88,12 +88,12 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ul>
|
<ul>
|
||||||
{% for level in object.prerequisite_levels.all %}
|
{% for level in object.prerequisite_levels.all %}
|
||||||
{% user_level_if_present request.user level as level_qualification %}
|
{% user_level_if_present u 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>
|
<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 %}
|
{% 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>
|
<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>
|
</ul>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load percentage_complete from tags %}
|
{% load percentage_complete from tags %}
|
||||||
|
{% load confirm_button from tags %}
|
||||||
{% load markdown_tags %}
|
{% load markdown_tags %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
@@ -54,15 +55,7 @@
|
|||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
{{ qual.level.get_icon }}
|
{{ qual.level.get_icon }}
|
||||||
<a href="{% url 'level_detail' qual.level.pk %}">{{ qual.level }}</a>
|
<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 }}
|
Confirmed by <a href="{{ qual.confirmed_by.get_absolute_url }}">{{ qual.confirmed_by|default:'System' }}</a> on {{ qual.confirmed_on|date }}
|
||||||
{% endif %}
|
|
||||||
</li>
|
</li>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<div class="alert alert-warning mx-auto">No qualifications in any levels yet...did someone forget to fill out the paperwork?</div>
|
<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 %}
|
{% for level in started_levels %}
|
||||||
{% percentage_complete level object as completion %}
|
{% percentage_complete level object as completion %}
|
||||||
<div class="card my-3 border-warning">
|
<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">
|
<div class="card-body">
|
||||||
{{ level.description|markdown }}
|
{{ level.description|markdown }}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<div class="progress">
|
<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 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>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from django import template
|
|||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.safestring import SafeData, mark_safe
|
from django.utils.safestring import SafeData, mark_safe
|
||||||
from django.utils.text import normalize_newlines
|
from django.utils.text import normalize_newlines
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
from training import models
|
from training import models
|
||||||
|
|
||||||
@@ -40,3 +41,14 @@ def get_supervisor(tech):
|
|||||||
@register.filter
|
@register.filter
|
||||||
def get_levels_of_depth(trainee, level):
|
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)
|
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 ""
|
||||||
|
|||||||
@@ -3,9 +3,3 @@ import pytest
|
|||||||
from pytest_django.asserts import assertFormError, assertRedirects, assertContains, assertNotContains
|
from pytest_django.asserts import assertFormError, assertRedirects, assertContains, assertNotContains
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
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.')
|
|
||||||
|
|||||||
@@ -17,11 +17,13 @@ urlpatterns = [
|
|||||||
name='add_qualification'),
|
name='add_qualification'),
|
||||||
path('trainee/<int:pk>/edit_qualification/', permission_required_with_403('training.change_trainingitemqualification')(views.EditQualification.as_view()),
|
path('trainee/<int:pk>/edit_qualification/', permission_required_with_403('training.change_trainingitemqualification')(views.EditQualification.as_view()),
|
||||||
name='edit_qualification'),
|
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('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>/', 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/<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('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>/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'),
|
path('trainee/<int:pk>/item_record', login_required(views.TraineeItemDetail.as_view()), name='trainee_item_detail'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ class LevelDetail(generic.DetailView):
|
|||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["page_title"] = "Training Level {} {}".format(self.object, self.object.get_icon)
|
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["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
|
return context
|
||||||
|
|
||||||
|
|
||||||
@@ -114,17 +115,6 @@ class TraineeList(generic.ListView):
|
|||||||
return context
|
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):
|
class AddQualification(generic.CreateView, ModalURLMixin):
|
||||||
template_name = "edit_training_record.html"
|
template_name = "edit_training_record.html"
|
||||||
model = models.TrainingItemQualification
|
model = models.TrainingItemQualification
|
||||||
@@ -215,10 +205,7 @@ class ConfirmLevel(generic.RedirectView):
|
|||||||
@transaction.atomic()
|
@transaction.atomic()
|
||||||
@reversion.create_revision()
|
@reversion.create_revision()
|
||||||
def get_redirect_url(self, *args, **kwargs):
|
def get_redirect_url(self, *args, **kwargs):
|
||||||
level_qualification = models.TrainingLevelQualification.objects.get(trainee=kwargs['pk'], level=kwargs['level_pk'])
|
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())
|
||||||
level_qualification.confirmed_by = self.request.user
|
|
||||||
level_qualification.confirmed_on = timezone.now()
|
|
||||||
level_qualification.save()
|
|
||||||
reversion.add_to_revision(level_qualification.trainee)
|
reversion.add_to_revision(level_qualification.trainee)
|
||||||
reversion.set_user(self.request.user)
|
reversion.set_user(self.request.user)
|
||||||
return reverse_lazy('trainee_detail', kwargs={'pk': kwargs['pk']})
|
return reverse_lazy('trainee_detail', kwargs={'pk': kwargs['pk']})
|
||||||
|
|||||||
Reference in New Issue
Block a user