mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-01-23 08:22:15 +00:00
Much versioning work
This commit is contained in:
@@ -19,8 +19,7 @@ from reversion import revisions as reversion
|
|||||||
from reversion.models import Version
|
from reversion.models import Version
|
||||||
|
|
||||||
|
|
||||||
@reversion.register
|
class Profile(AbstractUser):
|
||||||
class Profile(AbstractUser): # TODO move to versioning - currently get import errors with that
|
|
||||||
initials = models.CharField(max_length=5, null=True, blank=False)
|
initials = models.CharField(max_length=5, null=True, blank=False)
|
||||||
phone = models.CharField(max_length=13, blank=True, default='')
|
phone = models.CharField(max_length=13, blank=True, default='')
|
||||||
api_key = models.CharField(max_length=40, blank=True, editable=False, default='')
|
api_key = models.CharField(max_length=40, blank=True, editable=False, default='')
|
||||||
@@ -29,6 +28,8 @@ class Profile(AbstractUser): # TODO move to versioning - currently get import e
|
|||||||
last_emailed = models.DateTimeField(blank=True, null=True)
|
last_emailed = models.DateTimeField(blank=True, null=True)
|
||||||
dark_theme = models.BooleanField(default=False)
|
dark_theme = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
reversion_hide = True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def make_api_key(cls):
|
def make_api_key(cls):
|
||||||
size = 20
|
size = 20
|
||||||
@@ -66,11 +67,6 @@ class Profile(AbstractUser): # TODO move to versioning - currently get import e
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@property
|
|
||||||
def as_trainee(self):
|
|
||||||
from training.models import Trainee
|
|
||||||
return Trainee.objects.get(pk=self.pk)
|
|
||||||
|
|
||||||
|
|
||||||
class RevisionMixin(object):
|
class RevisionMixin(object):
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ from django.urls import reverse
|
|||||||
from django.utils.safestring import SafeData, mark_safe
|
from django.utils.safestring import SafeData, mark_safe
|
||||||
|
|
||||||
|
|
||||||
@reversion.register(follow=['qualifications_obtained']) # profile is already registered, but this triggers my custom versioning logic
|
@reversion.register(for_concrete_model=False)
|
||||||
class Trainee(Profile, RevisionMixin): # 'shim' overtop the profile model to neatly contain all training related fields etc
|
class Trainee(Profile, RevisionMixin):
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
|
|
||||||
@@ -17,22 +17,16 @@ class Trainee(Profile, RevisionMixin): # 'shim' overtop the profile model to ne
|
|||||||
def started_levels(self):
|
def started_levels(self):
|
||||||
return [level for level in TrainingLevel.objects.all() if level.percentage_complete(self) > 0]
|
return [level for level in TrainingLevel.objects.all() if level.percentage_complete(self) > 0]
|
||||||
|
|
||||||
def level_qualifications(self, only_confirmed=False):
|
|
||||||
levels = self.levels.all()
|
|
||||||
if only_confirmed:
|
|
||||||
levels = levels.exclude(confirmed_on=None)
|
|
||||||
return levels.select_related('level')
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_supervisor(self):
|
def is_supervisor(self):
|
||||||
return self.level_qualifications(True) \
|
return self.level_qualifications.all().exclude(confirmed_on=None).select_related('level') \
|
||||||
.filter(level__gte=TrainingLevel.SUPERVISOR) \
|
.filter(level__gte=TrainingLevel.SUPERVISOR) \
|
||||||
.exclude(level__department=TrainingLevel.HAULAGE) \
|
.exclude(level__department=TrainingLevel.HAULAGE) \
|
||||||
.exclude(level__department__isnull=True).exists()
|
.exclude(level__department__isnull=True).exists()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_driver(self):
|
def is_driver(self):
|
||||||
return self.level_qualifications(True).filter(level__department=TrainingLevel.HAULAGE).exists()
|
return self.level_qualifications.all().exclude(confirmed_on=None).select_related('level').filter(level__department=TrainingLevel.HAULAGE).exists()
|
||||||
|
|
||||||
def get_records_of_depth(self, depth):
|
def get_records_of_depth(self, depth):
|
||||||
return self.qualifications_obtained.filter(depth=depth).select_related('item', 'trainee', 'supervisor')
|
return self.qualifications_obtained.filter(depth=depth).select_related('item', 'trainee', 'supervisor')
|
||||||
@@ -80,7 +74,7 @@ class TrainingItem(models.Model):
|
|||||||
ordering = ['category__reference_number', 'reference_number']
|
ordering = ['category__reference_number', 'reference_number']
|
||||||
|
|
||||||
|
|
||||||
@reversion.register
|
@reversion.register(follow=['trainee'])
|
||||||
class TrainingItemQualification(models.Model):
|
class TrainingItemQualification(models.Model):
|
||||||
STARTED = 0
|
STARTED = 0
|
||||||
COMPLETE = 1
|
COMPLETE = 1
|
||||||
@@ -104,7 +98,7 @@ class TrainingItemQualification(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def activity_feed_string(self):
|
def activity_feed_string(self):
|
||||||
return str("qualification for {} in {} ({})".format(self.trainee, self.item, self.get_depth_display()))
|
return str("{} in {}".format(self.get_depth_display(), self.item))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_colour_from_depth(obj, depth):
|
def get_colour_from_depth(obj, depth):
|
||||||
@@ -231,9 +225,9 @@ class TrainingLevelRequirement(models.Model, RevisionMixin):
|
|||||||
unique_together = ["level", "item"]
|
unique_together = ["level", "item"]
|
||||||
|
|
||||||
|
|
||||||
@reversion.register
|
@reversion.register(follow=['trainee'])
|
||||||
class TrainingLevelQualification(models.Model, RevisionMixin):
|
class TrainingLevelQualification(models.Model, RevisionMixin):
|
||||||
trainee = models.ForeignKey('Trainee', related_name='levels', on_delete=models.RESTRICT)
|
trainee = models.ForeignKey('Trainee', related_name='level_qualifications', on_delete=models.RESTRICT)
|
||||||
level = models.ForeignKey('TrainingLevel', on_delete=models.RESTRICT)
|
level = models.ForeignKey('TrainingLevel', on_delete=models.RESTRICT)
|
||||||
confirmed_on = models.DateTimeField(null=True)
|
confirmed_on = models.DateTimeField(null=True)
|
||||||
confirmed_by = models.ForeignKey('Trainee', related_name='confirmer', on_delete=models.RESTRICT, null=True)
|
confirmed_by = models.ForeignKey('Trainee', related_name='confirmer', on_delete=models.RESTRICT, null=True)
|
||||||
|
|||||||
@@ -39,4 +39,4 @@ def get_supervisor(tech):
|
|||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def get_levels_of_depth(trainee, level):
|
def get_levels_of_depth(trainee, level):
|
||||||
return trainee.level_qualifications(True).filter(level__level=level)
|
return trainee.level_qualifications.all().exclude(confirmed_on=None).select_related('level').filter(level__level=level)
|
||||||
|
|||||||
0
training/tests/pages.py
Normal file
0
training/tests/pages.py
Normal file
11
training/tests/test_unit.py
Normal file
11
training/tests/test_unit.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from pytest_django.asserts import assertFormError, assertRedirects, assertContains, assertNotContains
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def test_(admin_client):
|
||||||
|
url = reverse('add_qualification')
|
||||||
|
response = admin_client.post(url)
|
||||||
|
assertFormError(response, 'form', 'name', 'This field is required.')
|
||||||
@@ -3,7 +3,8 @@ from django.urls import path
|
|||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from PyRIGS.decorators import permission_required_with_403
|
from PyRIGS.decorators import permission_required_with_403
|
||||||
|
|
||||||
from training import views
|
from training import views, models
|
||||||
|
from versioning.views import VersionHistory
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('items/', login_required(views.ItemList.as_view()), name='item_list'),
|
path('items/', login_required(views.ItemList.as_view()), name='item_list'),
|
||||||
@@ -11,6 +12,7 @@ urlpatterns = [
|
|||||||
path('trainee/<int:pk>/',
|
path('trainee/<int:pk>/',
|
||||||
permission_required_with_403('RIGS.view_profile')(views.TraineeDetail.as_view()),
|
permission_required_with_403('RIGS.view_profile')(views.TraineeDetail.as_view()),
|
||||||
name='trainee_detail'),
|
name='trainee_detail'),
|
||||||
|
path('trainee/<int:pk>/history', permission_required_with_403('RIGS.view_profile')(VersionHistory.as_view()), name='trainee_history', kwargs={'model': models.Trainee, 'app': 'training'}), # Not picked up automatically because proxy model (I think)
|
||||||
path('trainee/<int:pk>/add_qualification/', login_required(views.AddQualification.as_view()),
|
path('trainee/<int:pk>/add_qualification/', login_required(views.AddQualification.as_view()),
|
||||||
name='add_qualification'),
|
name='add_qualification'),
|
||||||
path('session/', login_required(views.SessionLog.as_view()), name='session_log'),
|
path('session/', login_required(views.SessionLog.as_view()), name='session_log'),
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class TraineeDetail(views.ProfileDetail):
|
|||||||
else:
|
else:
|
||||||
context["page_title"] = "{}'s Training Record".format(self.object.first_name + " " + self.object.last_name)
|
context["page_title"] = "{}'s Training Record".format(self.object.first_name + " " + self.object.last_name)
|
||||||
context["started_levels"] = self.object.started_levels()
|
context["started_levels"] = self.object.started_levels()
|
||||||
context["completed_levels"] = self.object.level_qualifications().select_related('level')
|
context["completed_levels"] = self.object.level_qualifications.all()
|
||||||
context["categories"] = models.TrainingCategory.objects.all().prefetch_related('items')
|
context["categories"] = models.TrainingCategory.objects.all().prefetch_related('items')
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ class TraineeList(generic.ListView):
|
|||||||
# not an integer
|
# not an integer
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return self.model.objects.filter(filter).annotate(num_qualifications=Count('qualifications_obtained')).order_by('-num_qualifications').prefetch_related('levels', 'qualifications_obtained', 'qualifications_obtained__item')
|
return self.model.objects.filter(filter).annotate(num_qualifications=Count('qualifications_obtained')).order_by('-num_qualifications').prefetch_related('level_qualifications', 'qualifications_obtained', 'qualifications_obtained__item')
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|||||||
@@ -158,7 +158,7 @@
|
|||||||
<ul class="list-group pt-3">
|
<ul class="list-group pt-3">
|
||||||
<li class="list-group-item active">Achieved Levels:</li>
|
<li class="list-group-item active">Achieved Levels:</li>
|
||||||
{% for qual in completed_levels %}
|
{% for qual in completed_levels %}
|
||||||
<a href="{% url 'level_detail' qual.level.pk %}"class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">{{ qual.level }}<span class='badge badge-{{qual.level.department_colour}} badge-pill'><span class='fas fa-{{qual.level.icon|default:"question"}}'></span></span></a>
|
<a href="{% url 'level_detail' qual.level.pk %}"class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">{{ qual.level }}{{ qual.get_icon }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class ProfileDetail(generic.DetailView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(ProfileDetail, self).get_context_data(**kwargs)
|
context = super(ProfileDetail, self).get_context_data(**kwargs)
|
||||||
context['page_title'] = "Profile: {}".format(self.object)
|
context['page_title'] = "Profile: {}".format(self.object)
|
||||||
context["completed_levels"] = self.object.as_trainee.level_qualifications()
|
context["completed_levels"] = self.object.level_qualifications.all().select_related('level')
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from django.utils.functional import cached_property
|
|||||||
from reversion.models import Version, VersionQuerySet
|
from reversion.models import Version, VersionQuerySet
|
||||||
|
|
||||||
from RIGS import models
|
from RIGS import models
|
||||||
|
from training.models import Trainee
|
||||||
|
|
||||||
logger = logging.getLogger('tec.pyrigs')
|
logger = logging.getLogger('tec.pyrigs')
|
||||||
|
|
||||||
@@ -121,7 +122,7 @@ class ModelComparison(object):
|
|||||||
old_item_versions = self.version.parent.revision.version_set.exclude(content_type=item_type)
|
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))
|
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']}
|
comparisonParams = {'excluded_keys': ['id', 'event', 'order', 'checklist', 'level', '_order', 'last_login']}
|
||||||
|
|
||||||
# Build some dicts of what we have
|
# Build some dicts of what we have
|
||||||
item_dict = {} # build a list of items, key is the item_pk
|
item_dict = {} # build a list of items, key is the item_pk
|
||||||
|
|||||||
Reference in New Issue
Block a user