mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-08-12 05:08:20 +00:00
Compare commits
2 Commits
0a9f82e480
...
2698798035
| Author | SHA1 | Date | |
|---|---|---|---|
| 2698798035 | |||
| dbaab5cf8c |
@@ -191,21 +191,6 @@ def linkornone(target, namespace=None, autoescape=True):
|
||||
return "None"
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def user_has_qualification(user, item, depth):
|
||||
if tmodels.TrainingItem.user_has_qualification(item, user, depth) is not None:
|
||||
return mark_safe("<span class='fas fa-check text-success'></span>")
|
||||
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)
|
||||
|
||||
@register.inclusion_tag('partials/button.html')
|
||||
def button(type, url=None, pk=None, clazz="", icon=None, text="", id=None, style=None):
|
||||
if type == 'edit':
|
||||
|
||||
@@ -16,13 +16,21 @@ class QualificationForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
pk = kwargs.pop('pk', None)
|
||||
super().__init__()
|
||||
super(QualificationForm, self).__init__(*args, **kwargs)
|
||||
self.fields['trainee'].initial = Profile.objects.get(pk=pk)
|
||||
self.fields['date'].initial = date.today()
|
||||
|
||||
def clean_date(self):
|
||||
date = self.cleaned_data['date']
|
||||
if date > date.today():
|
||||
raise ValidationError('Qualification date may not be in the future')
|
||||
raise forms.ValidationError('Qualification date may not be in the future')
|
||||
return date
|
||||
|
||||
def clean_supervisor(self):
|
||||
supervisor = self.cleaned_data['supervisor']
|
||||
if supervisor.pk == self.cleaned_data['trainee'].pk:
|
||||
raise forms.ValidationError('One may not supervise oneself...')
|
||||
return supervisor # TODO also confirm that the supervisor is a Supervisor
|
||||
|
||||
class RequirementForm(forms.ModelForm):
|
||||
depth = forms.ChoiceField(choices=models.TrainingItemQualification.CHOICES)
|
||||
@@ -33,5 +41,5 @@ class RequirementForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
pk = kwargs.pop('pk', None)
|
||||
super().__init__()
|
||||
super(RequirementForm, self).__init__(*args, **kwargs)
|
||||
self.fields['level'].initial = models.TrainingLevel.objects.get(pk=pk)
|
||||
|
||||
24
training/migrations/0003_auto_20210716_0150.py
Normal file
24
training/migrations/0003_auto_20210716_0150.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 3.1.5 on 2021-07-16 00:50
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('training', '0002_auto_20210706_0053'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='traininglevelqualification',
|
||||
name='confirmed_by',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.RESTRICT, related_name='confirmer', to='training.trainee'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='traininglevelqualification',
|
||||
name='confirmed_on',
|
||||
field=models.DateTimeField(null=True),
|
||||
),
|
||||
]
|
||||
@@ -18,7 +18,7 @@ class Trainee(Profile):
|
||||
return self.qualifications_obtained.filter(depth=depth)
|
||||
|
||||
def is_user_qualified_in(self, item, required_depth):
|
||||
qual = self.qualifications_obtained.get(item=item)
|
||||
qual = self.qualifications_obtained.filter(item=item).first() # this is a somewhat ghetto version of get_or_none
|
||||
return qual is not None and qual.depth >= required_depth
|
||||
|
||||
# Items
|
||||
@@ -26,6 +26,9 @@ class TrainingCategory(models.Model):
|
||||
reference_number = models.CharField(max_length=3)
|
||||
name = models.CharField(max_length=50)
|
||||
|
||||
def __str__(self):
|
||||
return "{}. {}".format(self.reference_number, self.name)
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = 'Training Categories'
|
||||
|
||||
@@ -67,9 +70,9 @@ class TrainingItemQualification(models.Model):
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save()
|
||||
for level in TrainingLevel.models.all(): # Mm yes efficiency
|
||||
for level in TrainingLevel.objects.all(): # Mm yes efficiency
|
||||
if level.user_has_requirements(self.trainee):
|
||||
level_qualification = models.TrainingLevelQualification.create(trainee=self.trainee, level=level)
|
||||
level_qualification = TrainingLevelQualification.objects.create(trainee=self.trainee, level=level)
|
||||
|
||||
|
||||
# Levels
|
||||
@@ -111,15 +114,19 @@ class TrainingLevel(models.Model, RevisionMixin):
|
||||
|
||||
def percentage_complete(self, user): # FIXME
|
||||
needed_qualifications = self.requirements.all()
|
||||
relavant_qualifications = [x for x in user.qualifications_obtained.all() if x in self.requirements.all()]
|
||||
relavant_qualifications = 0.0
|
||||
# TODO Efficiency...
|
||||
for req in needed_qualifications:
|
||||
if user.is_user_qualified_in(req.item, req.depth):
|
||||
relavant_qualifications += 1.0
|
||||
|
||||
if len(needed_qualifications) > 0:
|
||||
return round(len(relavant_qualifications) / len(needed_qualifications))
|
||||
return int(relavant_qualifications / float(len(needed_qualifications)) * 100)
|
||||
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())
|
||||
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
{% load static %}
|
||||
{% load widget_tweaks %}
|
||||
{% load button from filters %}
|
||||
|
||||
{% block css %}
|
||||
{{ block.super }}
|
||||
@@ -23,26 +24,26 @@
|
||||
{% if form.errors %}
|
||||
{% include 'form_errors.html' %}
|
||||
{% endif %}
|
||||
<form id="item-form" action="{{ form.action|default:request.path }}" method="post">{% csrf_token %}
|
||||
<form role="form" action="{{ form.action|default:request.path }}" method="POST">{% csrf_token %}
|
||||
{% render_field form.trainee|attr:'hidden' value=form.trainee.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>
|
||||
<select name="item" id="item_id" class="form-control selectpicker custom-select col-sm-4" data-live-search="true" data-sourceurl="{% url 'api_secure' model='training_item' %}" required>
|
||||
</select>
|
||||
</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 selectpicker col-sm' %}
|
||||
{% render_field form.depth|add_class:'form-control custom-select col-sm-4' %}
|
||||
</div>
|
||||
<div class="form-group form-row">
|
||||
<label for="selectpicker" class="col-sm-2 col-form-label">Supervisor</label>
|
||||
<label for="supervisor" class="col-sm-2 col-form-label">Supervisor</label>
|
||||
<select name="supervisor" id="supervisor_id" class="form-control selectpicker custom-select col-sm-10" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" required>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group form-row">
|
||||
<label for="date" class="col-sm-2 col-form-label">Training Date</label>
|
||||
<div class="col-sm-10">
|
||||
{% render_field form.date|add_class:'form-control'|attr:'type="date"' %}
|
||||
{% render_field form.date|add_class:'form-control'|attr:'type="date"' value=form.date.initial %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group form-row">
|
||||
@@ -52,6 +53,8 @@
|
||||
id="notes" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<input type="submit" class="btn btn-primary">
|
||||
<div class="col-sm-12 text-right pr-0"}>
|
||||
{% button 'submit' %}
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{% extends 'base_rigs.html' %}
|
||||
|
||||
{% load user_has_qualification from filters %}
|
||||
{% load percentage_complete from filters %}
|
||||
{% load user_level_if_present from filters %}
|
||||
{% load user_has_qualification from tags %}
|
||||
{% load percentage_complete from tags %}
|
||||
{% load user_level_if_present from tags %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-sm-12 text-right">
|
||||
@@ -20,9 +20,9 @@
|
||||
<div class="card-body">
|
||||
<p>{{ level.description|truncatewords:30 }}</p>
|
||||
<div class="progress mb-2">
|
||||
{% percentage_complete level request.user as completion %}
|
||||
{% percentage_complete level object as completion %}
|
||||
|
||||
<div class="progress-bar progress-bar-striped" role="progressbar" style="width: 25%" 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>
|
||||
|
||||
<button class="btn btn-link p-0" type="button" data-toggle="collapse" data-target=".reqs_{{level.pk}}" aria-expanded="false" aria-controls="reqs_{{level.pk}}">
|
||||
@@ -61,19 +61,17 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<h2 class="col-12">Training Items</h2><br>
|
||||
<h3 class="col-12">Key: <span class="badge badge-warning">Training Started</span> <span class="badge badge-success">Training Complete</span> <span class="badge badge-info">Passed Out</span></h3>
|
||||
<div class="card-columns">
|
||||
{% for category in categories %}
|
||||
<div class="card mb-3">
|
||||
<h3 class="card-header">{{ category.name }}</h3>
|
||||
<h3 class="card-header">{{ category }}</h3>
|
||||
<div class="list-group list-group-flush">
|
||||
{% for depth in depths %}
|
||||
<li class="list-group-item {% if depth.0 == 0 %}list-group-item-warning{%elif depth.0 == 1%}list-group-item-success{%else%}list-group-item-info{%endif%}">{{depth.1}}</li>
|
||||
{% for q in object.qualifications_obtained.all %}
|
||||
{% if q.depth == depth.0 and q.item.category == category %}
|
||||
<li class="list-group-item">{{q.item}} ({{q.date}})</li>
|
||||
{% if q.item.category == category %}
|
||||
<li class="list-group-item {% if q.depth == 0 %}list-group-item-warning{%elif q.depth == 1%}list-group-item-success{%else%}list-group-item-info{%endif%}">{{q.item}} ({{q.date}})</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
0
training/templatetags/__init__.py
Normal file
0
training/templatetags/__init__.py
Normal file
25
training/templatetags/tags.py
Normal file
25
training/templatetags/tags.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from django import forms
|
||||
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 training import models
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.simple_tag
|
||||
def user_has_qualification(user, item, depth):
|
||||
if models.TrainingItem.user_has_qualification(item, user, depth) is not None:
|
||||
return mark_safe("<span class='fas fa-check text-success'></span>")
|
||||
else:
|
||||
return mark_safe("<span class='fas fa-hourglass-start text-warning'></span>")
|
||||
|
||||
@register.simple_tag
|
||||
def user_level_if_present(user, level):
|
||||
return models.TrainingLevelQualification.objects.filter(trainee=user, level=level).first()
|
||||
|
||||
@register.simple_tag
|
||||
def percentage_complete(level, user):
|
||||
return level.percentage_complete(user)
|
||||
|
||||
@@ -62,16 +62,16 @@ class AddQualification(generic.CreateView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(AddQualification, self).get_context_data(**kwargs)
|
||||
# context["page_title"] = "Edit {}'s Training Record".format(self.object)
|
||||
context["depths"] = models.TrainingItemQualification.CHOICES
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
else:
|
||||
context['override'] = 'base_rigs.html' # TODO
|
||||
context['override'] = 'base_training.html'
|
||||
context['page_title'] = "Add Qualification for {}".format(models.Trainee.objects.get(pk=self.kwargs['pk']))
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('trainee_detail')
|
||||
return reverse_lazy('trainee_detail', kwargs={"pk": self.object.pk })
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(AddQualification, self).get_form_kwargs()
|
||||
|
||||
Reference in New Issue
Block a user