mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-10-10 22:55:29 +00:00
Fix training items for Haulage Supervisors
This commit is contained in:
@@ -80,12 +80,12 @@ class SessionLogForm(forms.Form):
|
||||
if supervisor.pk != self.user.pk:
|
||||
self.add_error("supervisor", "You may only log sessions that you delivered yourself")
|
||||
else:
|
||||
if cleaned_data.get("items_2"):
|
||||
self.add_error("items_2", "Technicians may not pass people out")
|
||||
for depth in (models.TrainingItemQualification.STARTED, models.TrainingItemQualification.COMPLETE):
|
||||
for depth, _ in models.TrainingItemQualification.CHOICES:
|
||||
for item in cleaned_data.get(f"items_{depth}", []):
|
||||
if not supervisor.can_deliver_training(item, depth):
|
||||
self.add_error(f"items_{depth}", f"You are not permitted to deliver training in {item}")
|
||||
self.add_error(
|
||||
f"items_{depth}", f"You are not permitted to deliver this training in {item}"
|
||||
)
|
||||
return cleaned_data
|
||||
|
||||
def clean_date(self):
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.2.17 on 2026-10-03 16:37
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('training', '0010_trainingcategory_training_level_help'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='trainingcategory',
|
||||
name='training_level',
|
||||
field=models.ForeignKey(blank=True, help_text='If this is set, technicians must hold this level (e.g. Sound Technician) to deliver training in technician-trainable items in this category, and holders of a Supervisor level (e.g. Haulage Supervisor) may pass people out in it. If not set, any technician may deliver training in technician-trainable items.', null=True, on_delete=django.db.models.deletion.CASCADE, to='training.traininglevel'),
|
||||
),
|
||||
]
|
||||
@@ -56,12 +56,34 @@ class Trainee(Profile, RevisionMixin):
|
||||
def is_driver(self):
|
||||
return self.confirmed_levels.filter(level__department=TrainingLevel.HAULAGE).exists()
|
||||
|
||||
@property
|
||||
def can_log_training(self):
|
||||
"""Whether this person may use the training logging pages at all"""
|
||||
category_levels = TrainingCategory.objects.exclude(training_level=None).values("training_level")
|
||||
return (
|
||||
self.is_supervisor or self.is_technician or self.confirmed_levels.filter(level__in=category_levels).exists()
|
||||
)
|
||||
|
||||
def can_deliver_training(self, item, depth):
|
||||
"""Technicians may deliver training up to Training Complete on flagged items that they have been passed out on.
|
||||
If the item's category specifies a training level, they must also hold that level, otherwise any technician will do."""
|
||||
if depth == TrainingItemQualification.PASSED_OUT or not item.technician_can_train:
|
||||
return False
|
||||
"""Whether this person may record training of the given depth in the item (i.e. be named as the supervisor)
|
||||
|
||||
- Supervisors may deliver anything.
|
||||
- Passing out requires holding a Supervisor level set on the item's category (e.g. Haulage Supervisor)
|
||||
and being passed out in the item oneself.
|
||||
- Started/Complete require the item to be technician trainable and the person to be passed out in it
|
||||
themselves, and to hold the category's level, or to be a technician if the category has none."""
|
||||
if self.is_supervisor:
|
||||
return True
|
||||
training_level = item.category.training_level
|
||||
if depth == TrainingItemQualification.PASSED_OUT:
|
||||
return (
|
||||
training_level is not None
|
||||
and training_level.level == TrainingLevel.SUPERVISOR
|
||||
and self.confirmed_levels.filter(level=training_level).exists()
|
||||
and self.is_user_qualified_in(item, TrainingItemQualification.PASSED_OUT)
|
||||
)
|
||||
if not item.technician_can_train:
|
||||
return False
|
||||
if training_level is None:
|
||||
is_permitted = self.is_technician
|
||||
else:
|
||||
@@ -95,7 +117,7 @@ class TrainingCategory(models.Model):
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="If this is set, technicians must hold this level (e.g. Sound Technician) to deliver training in technician-trainable items in this category. If not set, any technician may.",
|
||||
help_text="If this is set, technicians must hold this level (e.g. Sound Technician) to deliver training in technician-trainable items in this category, and holders of a Supervisor level (e.g. Haulage Supervisor) may pass people out in it. If not set, any technician may deliver training in technician-trainable items.",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
@@ -227,7 +249,7 @@ class TrainingItemQualification(models.Model, RevisionMixin):
|
||||
errdict = {}
|
||||
# Validate supervisor can train in this item
|
||||
if hasattr(self, "supervisor"): # This will be false if form validation fails
|
||||
if not self.supervisor.can_deliver_training(self.item, self.depth) and not self.supervisor.is_supervisor:
|
||||
if not self.supervisor.can_deliver_training(self.item, self.depth):
|
||||
errdict["supervisor"] = "Selected supervisor must actually *be* a supervisor..."
|
||||
# Item requirements only apply to being passed out
|
||||
if self.depth == TrainingItemQualification.PASSED_OUT and not self.item.user_has_requirements(self.trainee):
|
||||
|
||||
@@ -61,11 +61,11 @@
|
||||
</button>
|
||||
<div class="collapse mt-3" id="deliverable_items">
|
||||
<div class="card card-body">
|
||||
<p class="text-muted">You may deliver up to Training Complete in these items, as you are a technician who is passed out in them. Passing people out is for supervisors only.</p>
|
||||
<p class="text-muted">You may deliver training in these items. Passing people out is only possible where marked.</p>
|
||||
{% for category, items in deliverable_items %}
|
||||
<h5>{{ category }}</h5>
|
||||
<ul>
|
||||
{% for item in items %}<li>{{ item.display_name }}</li>{% endfor %}
|
||||
{% for item in items %}<li>{{ item.display_name }}{% if item.can_pass_out %} <span class="text-muted">(including passing out)</span>{% endif %}</li>{% endfor %}
|
||||
</ul>
|
||||
{% empty %}
|
||||
<p class="mb-0">You can't currently deliver training in any items. An item must be marked as technician-trainable, you must be passed out in it, and you must hold any technician level its category requires.</p>
|
||||
|
||||
@@ -215,3 +215,57 @@ def test_plain_trainee_cannot_log_session(client, trainee):
|
||||
def test_supervisor_does_not_see_deliverable_items(admin_client):
|
||||
response = admin_client.get(reverse("session_log"))
|
||||
assertNotContains(response, "What training items can I deliver training in?")
|
||||
|
||||
|
||||
def _haulage_setup(trainee, supervisor):
|
||||
haul_level = models.TrainingLevel.objects.create(
|
||||
level=models.TrainingLevel.SUPERVISOR, department=models.TrainingLevel.HAULAGE, description="x"
|
||||
)
|
||||
models.TrainingLevelQualification.objects.create(
|
||||
trainee=models.Trainee.objects.get(pk=trainee.pk), level=haul_level, confirmed_on=timezone.now()
|
||||
)
|
||||
category = models.TrainingCategory.objects.create(reference_number=4, name="Haulage", training_level=haul_level)
|
||||
return models.TrainingItem.objects.create(category=category, reference_number=1, name="Tail lifts")
|
||||
|
||||
|
||||
def test_haulage_supervisor_can_pass_out_without_supervisor_flag(client, trainee, supervisor, admin_user):
|
||||
item = _haulage_setup(trainee, supervisor)
|
||||
tech = models.Trainee.objects.get(pk=trainee.pk)
|
||||
assert not tech.is_supervisor
|
||||
assert tech.can_log_training
|
||||
# Must be passed out in the item oneself
|
||||
assert not tech.can_deliver_training(item, models.TrainingItemQualification.PASSED_OUT)
|
||||
models.TrainingItemQualification.objects.create(
|
||||
item=item,
|
||||
depth=models.TrainingItemQualification.PASSED_OUT,
|
||||
trainee=tech,
|
||||
supervisor=models.Trainee.objects.get(pk=supervisor.pk),
|
||||
date=datetime.date.today(),
|
||||
)
|
||||
assert tech.can_deliver_training(item, models.TrainingItemQualification.PASSED_OUT)
|
||||
client.force_login(trainee)
|
||||
url = reverse("session_log")
|
||||
data = {
|
||||
"trainees": [admin_user.pk],
|
||||
"items_2": [item.pk],
|
||||
"supervisor": trainee.pk,
|
||||
"date": datetime.date.today().strftime("%Y-%m-%d"),
|
||||
}
|
||||
assert client.post(url, data).status_code == 302
|
||||
assert models.TrainingItemQualification.objects.filter(
|
||||
trainee=admin_user.pk, item=item, depth=models.TrainingItemQualification.PASSED_OUT
|
||||
).exists()
|
||||
|
||||
|
||||
def test_technician_level_does_not_allow_pass_out(trainee, supervisor):
|
||||
item = _technician_setup(trainee, supervisor)
|
||||
assert not models.Trainee.objects.get(pk=trainee.pk).can_deliver_training(
|
||||
item, models.TrainingItemQualification.PASSED_OUT
|
||||
)
|
||||
|
||||
|
||||
def test_flagged_supervisor_can_deliver_anything(trainee, supervisor):
|
||||
item = _technician_setup(trainee, supervisor)
|
||||
sup = models.Trainee.objects.get(pk=supervisor.pk)
|
||||
for depth, _ in models.TrainingItemQualification.CHOICES:
|
||||
assert sup.can_deliver_training(item, depth)
|
||||
|
||||
@@ -299,12 +299,15 @@ class SessionLog(generic.FormView):
|
||||
def get_deliverable_items(self):
|
||||
"""Active items the current user may deliver training in, grouped by category, using the same test as form validation"""
|
||||
trainee = models.Trainee.objects.get(pk=self.request.user.pk)
|
||||
items = models.TrainingItem.objects.filter(active=True, technician_can_train=True).select_related(
|
||||
"category", "category__training_level"
|
||||
items = (
|
||||
models.TrainingItem.objects.filter(active=True)
|
||||
.filter(Q(technician_can_train=True) | Q(category__training_level__level=models.TrainingLevel.SUPERVISOR))
|
||||
.select_related("category", "category__training_level")
|
||||
)
|
||||
grouped = {}
|
||||
for item in items:
|
||||
if trainee.can_deliver_training(item, models.TrainingItemQualification.COMPLETE):
|
||||
item.can_pass_out = trainee.can_deliver_training(item, models.TrainingItemQualification.PASSED_OUT)
|
||||
if item.can_pass_out or trainee.can_deliver_training(item, models.TrainingItemQualification.COMPLETE):
|
||||
grouped.setdefault(item.category, []).append(item)
|
||||
return list(grouped.items())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user