Fettling with level granting logic

Untested as all of my forms broke I guess
This commit is contained in:
2021-07-07 17:34:19 +01:00
parent 54f2bd36bd
commit 0a9f82e480
9 changed files with 96 additions and 12 deletions

View File

@@ -198,6 +198,10 @@ def user_has_qualification(user, item, depth):
else:
return mark_safe("<span class='fas fa-hourglass-start text-warning'></span>")
@register.simple_tag
def user_level_if_present(user, level):
return tmodels.TrainingLevelQualification.objects.filter(trainee=user, level=level).first()
@register.simple_tag
def percentage_complete(level, user):
return level.percentage_complete(user)

View File

@@ -83,7 +83,7 @@ function browserSync(done) {
notify: false,
open: false,
port: 8001,
proxy: 'localhost:8000'
proxy: '127.0.0.1:8000'
});
done();
}

View File

@@ -43,7 +43,8 @@
<img class="card-img-top" src="{% static 'imgs/training.jpg' %}" alt="" style="height: 150px; object-fit: cover;">
<h4 class="card-header">Training Database</h4>
<div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="{% url 'trainee_detail' %}"><span class="fas fa-file-signature align-middle"></span><span class="align-middle"> My Training Record</span></a>
<a class="list-group-item list-group-item-action text-info" href="{% url 'trainee_detail' %}"><span class="fas fa-file-signature align-middle"></span><span class="align-middle"> My Training Record</span></a>
<a class="list-group-item list-group-item-action" href="{% url 'trainee_list' %}"><span class="fas fa-clipboard-list align-middle"></span><span class="align-middle"> View Training Records</span></a>
<a class="list-group-item list-group-item-action" href="{% url 'item_list' %}"><span class="fas fa-eye align-middle"></span><span class="align-middle"> View Training Items</span></a>
<a class="list-group-item list-group-item-action" href="{% url 'session_log' %}"><span class="fas fa-plus align-middle"></span><span class="align-middle"> Log Training Session</span></a>
</div>

View File

@@ -8,9 +8,19 @@ class Trainee(Profile):
class Meta:
proxy = True
@property
def is_supervisor(self):
for level_qualification in self.levels.all():
if confirmed_on is not None and level_qualification.level.level >= TrainingLevel.SUPERVISOR:
return True
def get_records_of_depth(self, depth):
return self.qualifications_obtained.filter(depth=depth)
def is_user_qualified_in(self, item, required_depth):
qual = self.qualifications_obtained.get(item=item)
return qual is not None and qual.depth >= required_depth
# Items
class TrainingCategory(models.Model):
reference_number = models.CharField(max_length=3)
@@ -27,9 +37,10 @@ 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):
@staticmethod
def user_has_qualification(item, user, depth):
for q in user.qualifications_obtained.all():
if q.item == self and q.depth > depth:
if q.item == item and q.depth > depth:
return True
@@ -49,10 +60,17 @@ class TrainingItemQualification(models.Model):
# TODO Remember that some training is external. Support for making an organisation the trainer?
supervisor = models.ForeignKey('Trainee', related_name='qualifications_granted', on_delete=models.RESTRICT)
notes = models.TextField(blank=True)
# TODO Maximum depth - some things stop at Complete and you can't be passed out in them
def __str__(self):
return "{} in {} on {}".format(self.depth, self.item, self.date)
def save(self, *args, **kwargs):
super().save()
for level in TrainingLevel.models.all(): # Mm yes efficiency
if level.user_has_requirements(self.trainee):
level_qualification = models.TrainingLevelQualification.create(trainee=self.trainee, level=level)
# Levels
class TrainingLevel(models.Model, RevisionMixin):
@@ -99,6 +117,10 @@ class TrainingLevel(models.Model, RevisionMixin):
else:
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())
def __str__(self):
if self.department is None: # 2TA
return self.get_level_display()
@@ -118,5 +140,5 @@ class TrainingLevelRequirement(models.Model):
class TrainingLevelQualification(models.Model):
trainee = models.ForeignKey('Trainee', related_name='levels', on_delete=models.RESTRICT)
level = models.ForeignKey('TrainingLevel', on_delete=models.RESTRICT)
confirmed_on = models.DateTimeField()
confirmed_by = models.ForeignKey('Trainee', related_name='confirmer', on_delete=models.RESTRICT)
confirmed_on = models.DateTimeField(null=True)
confirmed_by = models.ForeignKey('Trainee', related_name='confirmer', on_delete=models.RESTRICT, null=True)

View File

@@ -24,7 +24,7 @@
{% include 'form_errors.html' %}
{% endif %}
<form id="requirement-form" action="{{ form.action|default:request.path }}" method="post">{% csrf_token %}
{% render_field form.level|attr:'hidden' value=form.trainee.initial %}
{% render_field form.level|attr:'hidden' value=form.level.initial %}
<div class="form-group form-row">
<label for="item_id" class="col-sm-2 col-form-label">Item</label>
<select name="item" id="item_id" class="form-control selectpicker custom-select col-sm-10" data-live-search="true" data-sourceurl="{% url 'api_secure' model='training_item' %}" required>
@@ -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 col-sm'|attr:'required' %}
{% render_field form.depth|add_class:'form-control col-sm'|attr:'required' %}
</div>
<input type="submit" class="btn btn-primary">
</form>

View File

@@ -2,6 +2,7 @@
{% load user_has_qualification from filters %}
{% load percentage_complete from filters %}
{% load user_level_if_present from filters %}
{% block content %}
<div class="col-sm-12 text-right">
@@ -11,6 +12,7 @@
</div>
<div class="row mb-3">
<h2 class="col-12">Training Levels</h2>
<p>Technical Assistant is conferred automatically when the item requirements are met. Technician status is also automatic, but notification of status should be made at the next general meeting, at which point 'approval' should be granted on the system. Supervisor status is <em>not automatic</em> and until signed off at a general meeting, does not count.<sup>Correct as of 7th July 2021, check the Training Policy.</sup></p>
<div class="card-columns">
{% for level in levels %}
<div class="card my-3">
@@ -37,16 +39,21 @@
</thead>
<tbody>
<tr>
<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 object req.item 0 %}</li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in level.complete_requirements %}<li>{{ req.item }} {% user_has_qualification object 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 object req.item 2 %}</li>{% endfor %}</ul></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="card-footer">
<button class="btn btn-success text-right" disabled>Incomplete</button>
{% user_level_if_present object level as level_qualification %}
{% if level_qualification %}
<button class="btn btn-warning text-right" disabled>Awaiting Confirmation</button>
{% else %}
<button class="btn btn-danger text-right" disabled>Incomplete</button>
{% endif %}
</div>
</div>
{% endfor %}

View File

@@ -0,0 +1,38 @@
{% extends 'base_training.html' %}
{% load url_replace from filters %}
{% load orderby from filters %}
{% load paginator from filters %}
{% load linkornone from filters %}
{% load button from filters %}
{% block content %}
<div class="row">
<div class="col">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th scope="col">Name<a href="?{% orderby request 'orderBy' 'name' %}"><span class="caret"></span></a></th>
<th>Supervisor?</th>
<th>-</th>
</tr>
</thead>
<tbody>
{% for object in object_list %}
<tr id="row_item">
<th scope="row" class="align-middle" id="cell_name">{{ object.name }}</th>
<td>No</td>
<td><a class="btn btn-info" href="{% url 'trainee_detail' pk=object.pk %}"><span class="fas fa-eye"></span> View Training Record</a></td>
</tr>
{% empty %}
<tr class="table-warning">
<td colspan="6" class="text-center">Nothing found</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}

View File

@@ -8,6 +8,7 @@ from training import views
urlpatterns = [
path('items/', views.ItemList.as_view(), name='item_list'),
path('trainee/list/', views.TraineeList.as_view(), name='trainee_list'),
path('trainee/', login_required(views.TraineeDetail.as_view()), name='trainee_detail'),
path('trainee/<int:pk>/',
permission_required_with_403('RIGS.view_profile')(views.TraineeDetail.as_view()),

View File

@@ -34,6 +34,16 @@ class TraineeDetail(views.ProfileDetail):
return context
class TraineeList(generic.ListView):
model = models.Trainee
template_name = 'trainee_list.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_title"] = "Profile List"
return context
class SessionLog(generic.FormView):
template_name = "session_log_form.html"
form_class = forms.SessionLogForm
@@ -109,3 +119,4 @@ class RemoveRequirement(generic.DeleteView):
def get_success_url(self):
return self.request.POST.get('next')