From 70d4c42676b00572157ef552b86fb6bfb87f03a7 Mon Sep 17 00:00:00 2001 From: FreneticScribbler Date: Sat, 1 Jan 2022 19:53:03 +0000 Subject: [PATCH] Much versioning work --- RIGS/models.py | 10 +++------- training/models.py | 22 ++++++++-------------- training/templatetags/tags.py | 2 +- training/tests/pages.py | 0 training/tests/test_unit.py | 11 +++++++++++ training/urls.py | 4 +++- training/views.py | 4 ++-- users/templates/profile_detail.html | 2 +- users/views.py | 2 +- versioning/versioning.py | 3 ++- 10 files changed, 32 insertions(+), 28 deletions(-) create mode 100644 training/tests/pages.py create mode 100644 training/tests/test_unit.py diff --git a/RIGS/models.py b/RIGS/models.py index 53d64602..3bca29bd 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -19,8 +19,7 @@ from reversion import revisions as reversion from reversion.models import Version -@reversion.register -class Profile(AbstractUser): # TODO move to versioning - currently get import errors with that +class Profile(AbstractUser): initials = models.CharField(max_length=5, null=True, blank=False) phone = models.CharField(max_length=13, blank=True, 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) dark_theme = models.BooleanField(default=False) + reversion_hide = True + @classmethod def make_api_key(cls): size = 20 @@ -66,11 +67,6 @@ class Profile(AbstractUser): # TODO move to versioning - currently get import e def __str__(self): return self.name - @property - def as_trainee(self): - from training.models import Trainee - return Trainee.objects.get(pk=self.pk) - class RevisionMixin(object): @property diff --git a/training/models.py b/training/models.py index 860c0fe2..6501d1cf 100644 --- a/training/models.py +++ b/training/models.py @@ -7,8 +7,8 @@ from django.urls import reverse from django.utils.safestring import SafeData, mark_safe -@reversion.register(follow=['qualifications_obtained']) # profile is already registered, but this triggers my custom versioning logic -class Trainee(Profile, RevisionMixin): # 'shim' overtop the profile model to neatly contain all training related fields etc +@reversion.register(for_concrete_model=False) +class Trainee(Profile, RevisionMixin): class Meta: proxy = True @@ -17,22 +17,16 @@ class Trainee(Profile, RevisionMixin): # 'shim' overtop the profile model to ne def started_levels(self): 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 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) \ .exclude(level__department=TrainingLevel.HAULAGE) \ .exclude(level__department__isnull=True).exists() @property 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): 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'] -@reversion.register +@reversion.register(follow=['trainee']) class TrainingItemQualification(models.Model): STARTED = 0 COMPLETE = 1 @@ -104,7 +98,7 @@ class TrainingItemQualification(models.Model): @property 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 def get_colour_from_depth(obj, depth): @@ -231,9 +225,9 @@ class TrainingLevelRequirement(models.Model, RevisionMixin): unique_together = ["level", "item"] -@reversion.register +@reversion.register(follow=['trainee']) 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) confirmed_on = models.DateTimeField(null=True) confirmed_by = models.ForeignKey('Trainee', related_name='confirmer', on_delete=models.RESTRICT, null=True) diff --git a/training/templatetags/tags.py b/training/templatetags/tags.py index 5042b883..b3a97fbb 100644 --- a/training/templatetags/tags.py +++ b/training/templatetags/tags.py @@ -39,4 +39,4 @@ def get_supervisor(tech): @register.filter 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) diff --git a/training/tests/pages.py b/training/tests/pages.py new file mode 100644 index 00000000..e69de29b diff --git a/training/tests/test_unit.py b/training/tests/test_unit.py new file mode 100644 index 00000000..1bca896a --- /dev/null +++ b/training/tests/test_unit.py @@ -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.') diff --git a/training/urls.py b/training/urls.py index 287ef240..eca70c49 100644 --- a/training/urls.py +++ b/training/urls.py @@ -3,7 +3,8 @@ from django.urls import path from django.contrib.auth.decorators import login_required from PyRIGS.decorators import permission_required_with_403 -from training import views +from training import views, models +from versioning.views import VersionHistory urlpatterns = [ path('items/', login_required(views.ItemList.as_view()), name='item_list'), @@ -11,6 +12,7 @@ urlpatterns = [ path('trainee//', permission_required_with_403('RIGS.view_profile')(views.TraineeDetail.as_view()), name='trainee_detail'), + path('trainee//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//add_qualification/', login_required(views.AddQualification.as_view()), name='add_qualification'), path('session/', login_required(views.SessionLog.as_view()), name='session_log'), diff --git a/training/views.py b/training/views.py index 23ebaf34..a36a812b 100644 --- a/training/views.py +++ b/training/views.py @@ -37,7 +37,7 @@ class TraineeDetail(views.ProfileDetail): else: context["page_title"] = "{}'s Training Record".format(self.object.first_name + " " + self.object.last_name) 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') return context @@ -97,7 +97,7 @@ class TraineeList(generic.ListView): # not an integer 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): context = super().get_context_data(**kwargs) diff --git a/users/templates/profile_detail.html b/users/templates/profile_detail.html index 841f27be..a3c6ec11 100644 --- a/users/templates/profile_detail.html +++ b/users/templates/profile_detail.html @@ -158,7 +158,7 @@ diff --git a/users/views.py b/users/views.py index 1def6045..7707591b 100644 --- a/users/views.py +++ b/users/views.py @@ -42,7 +42,7 @@ class ProfileDetail(generic.DetailView): def get_context_data(self, **kwargs): context = super(ProfileDetail, self).get_context_data(**kwargs) 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 diff --git a/versioning/versioning.py b/versioning/versioning.py index c7ee68ba..5b18c2b4 100644 --- a/versioning/versioning.py +++ b/versioning/versioning.py @@ -8,6 +8,7 @@ from django.utils.functional import cached_property from reversion.models import Version, VersionQuerySet from RIGS import models +from training.models import Trainee 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) 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 item_dict = {} # build a list of items, key is the item_pk