mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-01-18 14:02:15 +00:00
Fettling with level granting logic
Untested as all of my forms broke I guess
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -83,7 +83,7 @@ function browserSync(done) {
|
||||
notify: false,
|
||||
open: false,
|
||||
port: 8001,
|
||||
proxy: 'localhost:8000'
|
||||
proxy: '127.0.0.1:8000'
|
||||
});
|
||||
done();
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
38
training/templates/trainee_list.html
Normal file
38
training/templates/trainee_list.html
Normal 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 %}
|
||||
@@ -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()),
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user