Compare commits

...

2 Commits

Author SHA1 Message Date
6414e68231 Migrate to is_supervisor being a database field
More efficient, but it does reduce app seperation. Probably not an issue for us!
2022-01-09 15:06:11 +00:00
b539312538 Reversion should mostly work now 2022-01-09 14:52:59 +00:00
12 changed files with 97 additions and 32 deletions

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.11 on 2022-01-09 14:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0043_auto_20211027_1519'),
]
operations = [
migrations.AddField(
model_name='profile',
name='is_supervisor',
field=models.BooleanField(default=False),
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 3.2.11 on 2022-01-09 15:00
from django.db import migrations
from training.models import TrainingLevel
def find_existing_supervisors(apps, schema_editor):
Profile = apps.get_model('RIGS', 'Profile')
for person in Profile.objects.all():
if person.level_qualifications.exclude(confirmed_on=None).select_related('level') \
.filter(level__level__gte=TrainingLevel.SUPERVISOR) \
.exclude(level__department=TrainingLevel.HAULAGE) \
.exclude(level__department__isnull=True).exists():
person.is_supervisor = True
person.save()
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0044_profile_is_supervisor'),
]
operations = [
migrations.RunPython(find_existing_supervisors, migrations.RunPython.noop)
]

View File

@@ -27,6 +27,7 @@ class Profile(AbstractUser):
# Currently only populated by the admin approval email. TODO: Populate it each time we send any email, might need that...
last_emailed = models.DateTimeField(blank=True, null=True)
dark_theme = models.BooleanField(default=False)
is_supervisor = models.BooleanField(default=False)
reversion_hide = True
@@ -56,11 +57,6 @@ class Profile(AbstractUser):
def latest_events(self):
return self.event_mic.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic', 'riskassessment', 'invoice').prefetch_related('checklists')
@cached_property
def as_trainee(self):
from training.models import Trainee
return Trainee.objects.get(pk=self.pk)
@classmethod
def admins(cls):
return Profile.objects.filter(email__in=[y for x in settings.ADMINS for y in x])

View File

@@ -2,4 +2,4 @@ from PyRIGS.decorators import user_passes_test_with_403
def has_perm_or_supervisor(perm, login_url=None, oembed_view=None):
return user_passes_test_with_403(lambda u: (hasattr(u, 'as_trainee') and u.as_trainee.is_supervisor) or u.has_perm(perm), login_url=login_url, oembed_view=oembed_view)
return user_passes_test_with_403(lambda u: u.is_supervisor or u.has_perm(perm), login_url=login_url, oembed_view=oembed_view)

View File

@@ -7,7 +7,7 @@ from django.urls import reverse
from django.utils.safestring import SafeData, mark_safe
@reversion.register(for_concrete_model=False)
@reversion.register(for_concrete_model=False, fields=[], follow=["qualifications_obtained", "level_qualifications"])
class Trainee(Profile, RevisionMixin):
class Meta:
proxy = True
@@ -23,13 +23,6 @@ class Trainee(Profile, RevisionMixin):
.exclude(level__department=TrainingLevel.HAULAGE) \
.exclude(level__department__isnull=True).exists()
@property
def is_supervisor(self):
return self.level_qualifications.exclude(confirmed_on=None).select_related('level') \
.filter(level__level__gte=TrainingLevel.SUPERVISOR) \
.exclude(level__department=TrainingLevel.HAULAGE) \
.exclude(level__department__isnull=True).exists()
@property
def is_driver(self):
return self.level_qualifications.all().exclude(confirmed_on=None).select_related('level').filter(level__department=TrainingLevel.HAULAGE).exists()
@@ -43,6 +36,10 @@ class Trainee(Profile, RevisionMixin):
def get_absolute_url(self):
return reverse('trainee_detail', kwargs={'pk': self.pk})
@property
def display_id(self):
return str(self)
class TrainingCategory(models.Model):
reference_number = models.IntegerField(unique=True)
@@ -55,6 +52,7 @@ class TrainingCategory(models.Model):
verbose_name_plural = 'Training Categories'
@reversion.register
class TrainingItem(models.Model):
reference_number = models.IntegerField()
category = models.ForeignKey('TrainingCategory', related_name='items', on_delete=models.CASCADE)
@@ -80,8 +78,8 @@ class TrainingItem(models.Model):
ordering = ['category__reference_number', 'reference_number']
@reversion.register(follow=['trainee'])
class TrainingItemQualification(models.Model):
@reversion.register
class TrainingItemQualification(models.Model, RevisionMixin):
STARTED = 0
COMPLETE = 1
PASSED_OUT = 2
@@ -248,7 +246,7 @@ class TrainingLevelRequirement(models.Model, RevisionMixin):
unique_together = ["level", "item"]
@reversion.register(follow=['trainee'])
@reversion.register
class TrainingLevelQualification(models.Model, RevisionMixin):
trainee = models.ForeignKey('Trainee', related_name='level_qualifications', on_delete=models.CASCADE)
level = models.ForeignKey('TrainingLevel', on_delete=models.CASCADE)
@@ -261,6 +259,11 @@ class TrainingLevelQualification(models.Model, RevisionMixin):
def get_icon(self):
return self.level.get_icon
def clean(self):
if level.level >= TrainingLevel.SUPERVISOR and level.department != TrainingLevel.HAULAGE:
trainee.is_supervisor = True
trainee.save()
def __str__(self):
if self.level.is_common_competencies:
return "{} is qualified in the {}".format(self.trainee, self.level)

View File

@@ -22,6 +22,13 @@
{% block content %}
{% if form.errors %}
{% include 'form_errors.html' %}
<script src="{% static 'js/autocompleter.js' %}"></script>
<script>
//Has to be done here or the pickers disappear on modal error
$('document').ready(function(){
$(document).find(".selectpicker").selectpicker().each(function(){initPicker($(this))});
});
</script>
{% endif %}
<form id="requirement-form" action="{{ form.action|default:request.path }}" method="post">{% csrf_token %}
{% render_field form.level|attr:'hidden' value=form.level.initial %}

View File

@@ -42,7 +42,7 @@
</div>
<div class="form-group form-row">
<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 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&filters=is_supervisor" required>
{% if object.supervisor %}
<option value="{{object.supervisor.pk}}" selected>{{object.supervisor}}</option>
{% endif %}

View File

@@ -44,7 +44,7 @@
{% endblock %}
{% block content %}
{% if request.user.as_trainee.is_supervisor or perms.training.add_traininglevelrequirement %}
{% if request.user.is_supervisor or perms.training.add_traininglevelrequirement %}
<div class="col-sm-12 text-right pr-0">
<a type="button" class="btn btn-success mb-3" href="{% url 'add_requirement' pk=object.pk %}" id="requirement_button">
<span class="fas fa-plus"></span> Add New Requirement
@@ -79,9 +79,9 @@
{% endfor %}
<tr><th colspan="3" class="text-center">{{object}}</th></tr>
<tr>
<td><ul class="list-unstyled">{% for req in object.started_requirements %}<li>{{ req.item }} {% user_has_qualification u req.item 0 %} {% if request.user.as_trainee.is_supervisor or perms.training.delete_traininglevelrequirement %}<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.as_trainee.is_supervisor or perms.training.delete_traininglevelrequirement %}<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 u req.item 2 %} {% if request.user.as_trainee.is_supervisor or perms.training.delete_traininglevelrequirement %}<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.started_requirements %}<li>{{ req.item }} {% user_has_qualification u req.item 0 %} {% if request.user.is_supervisor or perms.training.delete_traininglevelrequirement %}<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.delete_traininglevelrequirement %}<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 u req.item 2 %} {% if request.user.is_supervisor or perms.training.delete_traininglevelrequirement %}<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>
</tbody>
</table>

View File

@@ -1,4 +1,4 @@
{% if request.user.as_trainee.is_supervisor or perms.training.add_trainingitemqualification %}
{% if request.user.is_supervisor or perms.training.add_trainingitemqualification %}
<a type="button" class="btn btn-success" href="{% url 'add_qualification' object.pk %}" id="add_record">
<span class="fas fa-plus"></span> Add New Training Record
</a>

View File

@@ -19,7 +19,7 @@
<th>Date</th>
<th>Supervisor</th>
<th>Notes</th>
{% if request.user.as_trainee.is_supervisor or perms.training.change_trainingitemqualification %}
{% if request.user.is_supervisor or perms.training.change_trainingitemqualification %}
<th></th>
{% endif %}
</tr>
@@ -32,7 +32,7 @@
<td>{{ object.date }}</td>
<td><a href="{{ object.supervisor.get_absolute_url}}">{{ object.supervisor }}</a></td>
<td>{{ object.notes }}</td>
{% if request.user.as_trainee.is_supervisor or perms.training.change_trainingitemqualification %}
{% if request.user.is_supervisor or perms.training.change_trainingitemqualification %}
<td>{% button 'edit' 'edit_qualification' trainee.pk %}</td>
{% endif %}
</tr>
@@ -46,4 +46,9 @@
</div>
</div>
</div>
<div class="row">
<div class="col text-right">
{% include 'partials/last_edited.html' with target="trainee_history" object=trainee %}
</div>
</div>
{% endblock %}

View File

@@ -121,6 +121,12 @@ class AddQualification(generic.CreateView, ModalURLMixin):
model = models.TrainingItemQualification
form_class = forms.QualificationForm
@transaction.atomic()
@reversion.create_revision()
def form_valid(self, form, *args, **kwargs):
reversion.add_to_revision(form.cleaned_data['trainee'])
return super().form_valid(form, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(AddQualification, self).get_context_data(**kwargs)
context["depths"] = models.TrainingItemQualification.CHOICES
@@ -156,6 +162,12 @@ class EditQualification(generic.UpdateView):
kwargs['pk'] = self.kwargs['pk']
return kwargs
@transaction.atomic()
@reversion.create_revision()
def form_valid(self, form, *args, **kwargs):
reversion.add_to_revision(form.cleaned_data['trainee'])
return super().form_valid(form, *args, **kwargs)
class AddLevelRequirement(generic.CreateView, ModalURLMixin):
template_name = "add_level_requirement.html"
@@ -179,7 +191,6 @@ class AddLevelRequirement(generic.CreateView, ModalURLMixin):
@reversion.create_revision()
def form_valid(self, form, *args, **kwargs):
reversion.add_to_revision(form.cleaned_data['level'])
reversion.set_comment("Level requirement added")
return super().form_valid(form, *args, **kwargs)
@@ -206,13 +217,13 @@ class ConfirmLevel(generic.RedirectView):
@transaction.atomic()
@reversion.create_revision()
def get_redirect_url(self, *args, **kwargs):
level_qualification, created = models.TrainingLevelQualification.objects.get_or_create(trainee=models.Trainee.objects.get(pk=kwargs['pk']), level=models.TrainingLevel.objects.get(pk=kwargs['level_pk']))
trainee = models.Trainee.objects.get(pk=kwargs['pk'])
level_qualification, created = models.TrainingLevelQualification.objects.get_or_create(trainee=trainee, level=models.TrainingLevel.objects.get(pk=kwargs['level_pk']))
if created:
level_qualification.confirmed_by = self.request.user
level_qualification.confirmed_on = timezone.now()
level_qualification.save()
reversion.add_to_revision(level_qualification.trainee)
reversion.set_user(self.request.user)
reversion.add_to_revision(trainee)
return reverse_lazy('trainee_detail', kwargs={'pk': kwargs['pk']})

View File

@@ -72,7 +72,7 @@ class FieldComparison(object):
class ModelComparison(object):
def __init__(self, old=None, new=None, version=None, follow=False, excluded_keys=[]):
def __init__(self, old=None, new=None, version=None, follow=False, excluded_keys=['date_joined']):
# recieves two objects of the same model, and compares them. Returns an array of FieldCompare objects
try:
self.fields = old._meta.get_fields()
@@ -122,7 +122,7 @@ class ModelComparison(object):
old_item_versions = self.version.parent.revision.version_set.exclude(content_type=item_type)
new_item_versions = self.version.revision.version_set.exclude(content_type=item_type).exclude(content_type=ContentType.objects.get_for_model(models.EventAuthorisation))
comparisonParams = {'excluded_keys': ['id', 'event', 'order', 'checklist', 'level', '_order', 'last_login']}
comparisonParams = {'excluded_keys': ['id', 'event', 'order', 'checklist', 'level', '_order', 'date_joined']}
# Build some dicts of what we have
item_dict = {} # build a list of items, key is the item_pk
@@ -170,7 +170,7 @@ class RIGSVersionManager(VersionQuerySet):
for model in model_array:
content_types.append(ContentType.objects.get_for_model(model))
return self.filter(content_type__in=content_types).select_related("revision").order_by(
return self.filter(content_type__in=content_types).select_related("revision",).order_by(
"-revision__date_created")