diff --git a/PyRIGS/views.py b/PyRIGS/views.py index 2803ea55..e9258a70 100644 --- a/PyRIGS/views.py +++ b/PyRIGS/views.py @@ -50,7 +50,7 @@ class SecureAPIRequest(generic.View): 'profile': 'RIGS.view_profile', 'event': None, 'supplier': None, - 'training_item': None, # TODO + 'training_item': None, # TODO } ''' diff --git a/RIGS/management/commands/deleteSampleData.py b/RIGS/management/commands/deleteSampleData.py index 7eae9ef0..227356a3 100644 --- a/RIGS/management/commands/deleteSampleData.py +++ b/RIGS/management/commands/deleteSampleData.py @@ -5,6 +5,7 @@ from assets import models from RIGS import models as rigsmodels from training import models as tmodels + class Command(BaseCommand): help = 'Deletes testing sample data' diff --git a/assets/forms.py b/assets/forms.py index c7f7fb5b..d0c598c9 100644 --- a/assets/forms.py +++ b/assets/forms.py @@ -45,7 +45,7 @@ class CableTypeForm(forms.ModelForm): model = models.CableType fields = '__all__' - def clean(self): # TODO Does unique_together work better than this? + def clean(self): # TODO Does unique_together work better than this? form_data = self.cleaned_data queryset = models.CableType.objects.filter(Q(plug=form_data['plug']) & Q(socket=form_data['socket']) & Q(circuits=form_data['circuits']) & Q(cores=form_data['cores'])) # Being identical to itself shouldn't count... diff --git a/training/admin.py b/training/admin.py index 8a965144..71775e03 100644 --- a/training/admin.py +++ b/training/admin.py @@ -2,7 +2,7 @@ from django.contrib import admin from training import models from reversion.admin import VersionAdmin -#admin.site.register(models.Trainee, VersionAdmin) +# admin.site.register(models.Trainee, VersionAdmin) admin.site.register(models.TrainingCategory, VersionAdmin) admin.site.register(models.TrainingItem, VersionAdmin) admin.site.register(models.TrainingLevel, VersionAdmin) diff --git a/training/management/commands/generateSampleTrainingData.py b/training/management/commands/generateSampleTrainingData.py index f44bdbc6..d994670d 100644 --- a/training/management/commands/generateSampleTrainingData.py +++ b/training/management/commands/generateSampleTrainingData.py @@ -43,28 +43,131 @@ class Command(BaseCommand): self.categories.append(category) def setup_items(self): - names = ["Motorised Power Towers", "Catering", "Forgetting Cables", "Gazebo Construction", "Balanced Audio", "Unbalanced Audio", "BBQ/Bin Interactions", "Pushing Boxes", "How Not To Die", "Setting up projectors", "Basketing truss", "First Aid", "Digging Trenches", "Avoiding Bin Lorries", "Getting cherry pickers stuck in mud", "Crashing the Van", "Getting pigs to fly", "Basketing picnics", "Python programming", "Building Cables", "Unbuilding Cables", "Cat Herding", "Pancake making", "Tidying up", "Reading Manuals", "Bikeshedding", "DJing", "Partying", "Teccie Gym", "Putting dust covers on", "Cleaning Lights", "Water Skiing", "Drinking", "Fundamentals of Audio", "Fundamentals of Photons", "Social Interaction", "Discourse Searching", "Discord Searching", "Coiling Cables", "Kit Amnesties", "Van Insurance", "Subhire Insurance", "Paperwork", "More Paperwork", "Second Aid", "Being Old", "Maxihoists", "Sleazyhoists", "Telehoists", "Prolyte", "Prolights", "Making Phonecalls", "Quoting For A Rig", "Basic MIC", "Advanced MIC", "Avoiding MIC", "Washing Cables", "Cable Ramp", "Van Loading", "Trailer Loading", "Storeroom Loading", "Welding", "Fire Extinguishers", "Boring Conference AV", "Flyaway", "Short Leads", "RF Systems", "QLab", "Use of Ladders", "Working at Height", "Organising Training", "Organising Organising Training Training", "Mental Health First Aid", "Writing RAMS", "Makros Runs", "PAT", "Kit Fixing", "Kit Breaking", "Replacing Lamps", "Flying Pig Systems", "Procrastination", "Drinking Beer", "Sending Emails", "Email Signatures", "Digital Sound Desks", "Digital Lighting Desks", "Painting PS10s", "Chain Lubrication", "Big Power", "BIGGER POWER", "Pixel Mapping", "RDM", "Ladder Inspections", "Losing Crimpaz", "Scrapping Trilite", "Bin Diving", "Wiki Editing"] + names = [ + "Motorised Power Towers", + "Catering", + "Forgetting Cables", + "Gazebo Construction", + "Balanced Audio", + "Unbalanced Audio", + "BBQ/Bin Interactions", + "Pushing Boxes", + "How Not To Die", + "Setting up projectors", + "Basketing truss", + "First Aid", + "Digging Trenches", + "Avoiding Bin Lorries", + "Getting cherry pickers stuck in mud", + "Crashing the Van", + "Getting pigs to fly", + "Basketing picnics", + "Python programming", + "Building Cables", + "Unbuilding Cables", + "Cat Herding", + "Pancake making", + "Tidying up", + "Reading Manuals", + "Bikeshedding", + "DJing", + "Partying", + "Teccie Gym", + "Putting dust covers on", + "Cleaning Lights", + "Water Skiing", + "Drinking", + "Fundamentals of Audio", + "Fundamentals of Photons", + "Social Interaction", + "Discourse Searching", + "Discord Searching", + "Coiling Cables", + "Kit Amnesties", + "Van Insurance", + "Subhire Insurance", + "Paperwork", + "More Paperwork", + "Second Aid", + "Being Old", + "Maxihoists", + "Sleazyhoists", + "Telehoists", + "Prolyte", + "Prolights", + "Making Phonecalls", + "Quoting For A Rig", + "Basic MIC", + "Advanced MIC", + "Avoiding MIC", + "Washing Cables", + "Cable Ramp", + "Van Loading", + "Trailer Loading", + "Storeroom Loading", + "Welding", + "Fire Extinguishers", + "Boring Conference AV", + "Flyaway", + "Short Leads", + "RF Systems", + "QLab", + "Use of Ladders", + "Working at Height", + "Organising Training", + "Organising Organising Training Training", + "Mental Health First Aid", + "Writing RAMS", + "Makros Runs", + "PAT", + "Kit Fixing", + "Kit Breaking", + "Replacing Lamps", + "Flying Pig Systems", + "Procrastination", + "Drinking Beer", + "Sending Emails", + "Email Signatures", + "Digital Sound Desks", + "Digital Lighting Desks", + "Painting PS10s", + "Chain Lubrication", + "Big Power", + "BIGGER POWER", + "Pixel Mapping", + "RDM", + "Ladder Inspections", + "Losing Crimpaz", + "Scrapping Trilite", + "Bin Diving", + "Wiki Editing"] - for i,name in enumerate(names): + for i, name in enumerate(names): item = models.TrainingItem.objects.create(category=random.choice(self.categories), reference_number=random.randint(0, 100), name=name) self.items.append(item) def setup_levels(self): items = self.items.copy() - ta = models.TrainingLevel.objects.create(level=models.TrainingLevel.TA, description="Passion will hatred faithful evil suicide noble battle. Truth aversion gains grandeur noble. Dead play gains prejudice god ascetic grandeur zarathustra dead good. Faithful ultimate justice overcome love will mountains inexpedient.", icon="address-card") + ta = models.TrainingLevel.objects.create( + level=models.TrainingLevel.TA, + description="Passion will hatred faithful evil suicide noble battle. Truth aversion gains grandeur noble. Dead play gains prejudice god ascetic grandeur zarathustra dead good. Faithful ultimate justice overcome love will mountains inexpedient.", + icon="address-card") self.levels.append(ta) - tech_ccs = models.TrainingLevel.objects.create(level=models.TrainingLevel.TECHNICIAN, description="Technician Common Competencies. Spirit abstract endless insofar horror sexuality depths war decrepit against strong aversion revaluation free. Christianity reason joy sea law mountains transvaluation. Sea battle aversion dead ultimate morality self. Faithful morality.", icon="book-reader") + tech_ccs = models.TrainingLevel.objects.create( + level=models.TrainingLevel.TECHNICIAN, + description="Technician Common Competencies. Spirit abstract endless insofar horror sexuality depths war decrepit against strong aversion revaluation free. Christianity reason joy sea law mountains transvaluation. Sea battle aversion dead ultimate morality self. Faithful morality.", + icon="book-reader") tech_ccs.prerequisite_levels.add(ta) super_ccs = models.TrainingLevel.objects.create(level=models.TrainingLevel.SUPERVISOR, description="Depths disgust hope faith of against hatred will victorious. Law...", icon="user-graduate") for i in range(0, 5): - if len(items) == 0: - break - item = random.choice(items) - items.remove(item) - if i % 3 == 0: - models.TrainingLevelRequirement.objects.create(level=tech_ccs, item=item, depth=random.choice(models.TrainingItemQualification.CHOICES)[0]) - else: - models.TrainingLevelRequirement.objects.create(level=super_ccs, item=item, depth=random.choice(models.TrainingItemQualification.CHOICES)[0]) + if len(items) == 0: + break + item = random.choice(items) + items.remove(item) + if i % 3 == 0: + models.TrainingLevelRequirement.objects.create(level=tech_ccs, item=item, depth=random.choice(models.TrainingItemQualification.CHOICES)[0]) + else: + models.TrainingLevelRequirement.objects.create(level=super_ccs, item=item, depth=random.choice(models.TrainingItemQualification.CHOICES)[0]) icons = { models.TrainingLevel.SOUND: ('microphone', 'microphone-alt'), models.TrainingLevel.LIGHTING: ('lightbulb', 'traffic-light'), @@ -72,7 +175,7 @@ class Command(BaseCommand): models.TrainingLevel.RIGGING: ('link', 'pallet'), models.TrainingLevel.HAULAGE: ('truck', 'route'), } - for i,name in models.TrainingLevel.DEPARTMENTS: + for i, name in models.TrainingLevel.DEPARTMENTS: technician = models.TrainingLevel.objects.create(level=models.TrainingLevel.TECHNICIAN, department=i, description="Moral pinnacle derive ultimate war dead. Strong fearful joy contradict battle christian faithful enlightenment prejudice zarathustra moral.", icon=icons[i][0]) technician.prerequisite_levels.add(tech_ccs) supervisor = models.TrainingLevel.objects.create(level=models.TrainingLevel.SUPERVISOR, department=i, description="Spirit holiest merciful mountains inexpedient reason value. Suicide ultimate hope.", icon=icons[i][1]) @@ -98,4 +201,11 @@ class Command(BaseCommand): supervisor.set_password('supervisor') supervisor.groups.add(Group.objects.get(name="Keyholders")) supervisor.save() - models.TrainingLevelQualification.objects.create(trainee=supervisor, level=models.TrainingLevel.objects.filter(level__gte=models.TrainingLevel.SUPERVISOR).exclude(department=models.TrainingLevel.HAULAGE).exclude(department__isnull=True).first(), confirmed_on=timezone.now(), confirmed_by=models.Trainee.objects.first()) + models.TrainingLevelQualification.objects.create( + trainee=supervisor, + level=models.TrainingLevel.objects.filter( + level__gte=models.TrainingLevel.SUPERVISOR).exclude( + department=models.TrainingLevel.HAULAGE).exclude( + department__isnull=True).first(), + confirmed_on=timezone.now(), + confirmed_by=models.Trainee.objects.first()) diff --git a/training/management/commands/import_old_db.py b/training/management/commands/import_old_db.py index de5c0847..2bfb9c6e 100644 --- a/training/management/commands/import_old_db.py +++ b/training/management/commands/import_old_db.py @@ -10,6 +10,7 @@ from django.utils.timezone import make_aware from training import models from RIGS.models import Profile + class Command(BaseCommand): epoch = datetime.date(1970, 1, 1) id_map = {} @@ -50,15 +51,15 @@ class Command(BaseCommand): tally[0] += 1 else: # PYTHONIC, BABY - initials = first_name[0] + "".join([name_section[0] for name_section in re.split("\s*-", last_name.replace("(", ""))]) + initials = first_name[0] + "".join([name_section[0] for name_section in re.split("\\s*-", last_name.replace("(", ""))]) # print(initials) new_profile = Profile.objects.create(username=name.replace(" ", ""), - first_name=first_name, - last_name=last_name, - initials=initials) + first_name=first_name, + last_name=last_name, + initials=initials) self.id_map[child.find('ID').text] = new_profile.pk tally[1] += 1 - except AttributeError: # W.T.F + except AttributeError: # W.T.F print("Trainee #{} is FUBAR".format(child.find('ID').text)) print('Trainees - Updated: {}, Created: {}'.format(tally[0], tally[1])) @@ -71,7 +72,7 @@ class Command(BaseCommand): for child in root: obj, created = models.TrainingCategory.objects.update_or_create( pk=int(child.find('ID').text), - reference_number = int(child.find('Category_x0020_Number').text), + reference_number=int(child.find('Category_x0020_Number').text), name=child.find('Category_x0020_Name').text ) @@ -88,10 +89,10 @@ class Command(BaseCommand): root = self.parse_xml(self.xml_path('Training Items.xml')) for child in root: - if child.find('active').text == '0': + if child.find('active').text == '0': active = False - else: - active = True + else: + active = True number = int(child.find('Item_x0020_Number').text) name = child.find('Item_x0020_Name').text @@ -99,11 +100,11 @@ class Command(BaseCommand): try: obj, created = models.TrainingItem.objects.update_or_create( - pk = int(child.find('ID').text), - reference_number = number, - name = name, - category = category, - active = active + pk=int(child.find('ID').text), + reference_number=number, + name=name, + category=category, + active=active ) except IntegrityError: print("Training Item {}.{} {} has a duplicate reference number".format(category.reference_number, number, name)) @@ -139,11 +140,11 @@ class Command(BaseCommand): try: obj, created = models.TrainingItemQualification.objects.update_or_create( pk=int(child.find('ID').text), - item = models.TrainingItem.objects.get(pk=int(child.find('Training_Item_ID').text)), - trainee = Profile.objects.get(pk=self.id_map[child.find('Member_ID').text]), - depth = depth_index, - date = child.find('{}_Date'.format(depth)).text[:-9], # Stored as datetime with time as midnight because fuck you I guess - supervisor = supervisor + item=models.TrainingItem.objects.get(pk=int(child.find('Training_Item_ID').text)), + trainee=Profile.objects.get(pk=self.id_map[child.find('Member_ID').text]), + depth=depth_index, + date=child.find('{}_Date'.format(depth)).text[:-9], # Stored as datetime with time as midnight because fuck you I guess + supervisor=supervisor ) notes = child.find('{}_Notes'.format(depth)) if notes: @@ -153,7 +154,7 @@ class Command(BaseCommand): tally[1] += 1 else: tally[0] += 1 - except IntegrityError: # Eh? + except IntegrityError: # Eh? print("Training Record #{} is duplicate. ಠ_ಠ".format(child.find('ID').text)) except AttributeError: print(child.find('ID').text) @@ -197,8 +198,8 @@ class Command(BaseCommand): obj, created = models.TrainingLevel.objects.update_or_create( pk=int(child.find('ID').text), - description = desc, - level = level + description=desc, + level=level ) if depString is not None: obj.department = department @@ -210,12 +211,12 @@ class Command(BaseCommand): tally[0] += 1 for level in models.TrainingLevel.objects.all(): - if level.department != None: + if level.department is not None: if level.level == models.TrainingLevel.TECHNICIAN: level.prerequisite_levels.add(models.TrainingLevel.objects.get(level=models.TrainingLevel.TA), models.TrainingLevel.objects.get(level=models.TrainingLevel.TECHNICIAN, department=None)) elif level.level == models.TrainingLevel.SUPERVISOR: level.prerequisite_levels.add(models.TrainingLevel.objects.get(level=models.TrainingLevel.TECHNICIAN, department=level.department), models.TrainingLevel.objects.get(level=models.TrainingLevel.SUPERVISOR, department=None)) - + print('Training Levels - Updated: {}, Created: {}'.format(tally[0], tally[1])) def import_TrainingLevelQualification(self): @@ -232,21 +233,21 @@ class Command(BaseCommand): print('Training Level Qualification #{} does not qualify anyone. How?!'.format(child.find('ID').text)) continue obj, created = models.TrainingLevelQualification.objects.update_or_create( - pk = int(child.find('ID').text), - trainee = Profile.objects.get(pk=self.id_map[child.find('Member_x0020_ID').text]), - level = models.TrainingLevel.objects.get(pk=int(child.find('Training_x0020_Level_x0020_ID').text)) + pk=int(child.find('ID').text), + trainee=Profile.objects.get(pk=self.id_map[child.find('Member_x0020_ID').text]), + level=models.TrainingLevel.objects.get(pk=int(child.find('Training_x0020_Level_x0020_ID').text)) ) - + if child.find('Date_x0020_Level_x0020_Awarded') is not None: - obj.confirmed_on = make_aware(datetime.datetime.strptime(child.find('Date_x0020_Level_x0020_Awarded').text.split('T')[0], "%Y-%m-%d")) + obj.confirmed_on = make_aware(datetime.datetime.strptime(child.find('Date_x0020_Level_x0020_Awarded').text.split('T')[0], "%Y-%m-%d")) obj.save() - #confirmed by? + # confirmed by? if created: tally[1] += 1 else: tally[0] += 1 - except IntegrityError: # Eh? + except IntegrityError: # Eh? print("Training Level Qualification #{} is duplicate. ಠ_ಠ".format(child.find('ID').text)) print('TrainingLevelQualifications - Updated: {}, Created: {}'.format(tally[0], tally[1])) @@ -259,7 +260,13 @@ class Command(BaseCommand): for child in root: try: item = child.find('Item').text.split(".") - obj, created = models.TrainingLevelRequirement.objects.update_or_create(level=models.TrainingLevel.objects.get(pk=int(child.find('Level').text)),item=models.TrainingItem.objects.get(active=True, reference_number=item[1], category=models.TrainingCategory.objects.get(reference_number=item[0])), depth=int(child.find('Depth').text)) + obj, created = models.TrainingLevelRequirement.objects.update_or_create( + level=models.TrainingLevel.objects.get( + pk=int( + child.find('Level').text)), item=models.TrainingItem.objects.get( + active=True, reference_number=item[1], category=models.TrainingCategory.objects.get( + reference_number=item[0])), depth=int( + child.find('Depth').text)) if created: tally[1] += 1 diff --git a/training/models.py b/training/models.py index 1a439a09..2b88d8e7 100644 --- a/training/models.py +++ b/training/models.py @@ -15,7 +15,7 @@ class Trainee(Profile, RevisionMixin): proxy = True 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 and level.percentage_complete(self) < 100] def level_qualifications(self, only_confirmed=False): return self.levels.all().filter(confirmed_on__isnull=only_confirmed).select_related('level') @@ -56,7 +56,7 @@ class TrainingItem(models.Model): reference_number = models.IntegerField() category = models.ForeignKey('TrainingCategory', related_name='items', on_delete=models.RESTRICT) name = models.CharField(max_length=50) - active = models.BooleanField(default = True) + active = models.BooleanField(default=True) @property def number(self): @@ -70,9 +70,7 @@ class TrainingItem(models.Model): @staticmethod def user_has_qualification(item, user, depth): - for q in user.qualifications_obtained.all().select_related('item'): - if q.item == item and q.depth > depth: - return True + return user.qualifications_obtained.values('item', 'depth').filter(item=item, depth_gte=depth).exists() class Meta: unique_together = ["reference_number", "active", "category"] @@ -199,7 +197,7 @@ class TrainingLevel(models.Model, RevisionMixin): return 0 def user_has_requirements(self, user): - return all(TrainingItem.user_has_qualification(req.item, user, req.depth) for req in self.requirements.select_related().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: diff --git a/training/templatetags/tags.py b/training/templatetags/tags.py index d980ec0c..5042b883 100644 --- a/training/templatetags/tags.py +++ b/training/templatetags/tags.py @@ -8,6 +8,7 @@ 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: @@ -15,22 +16,27 @@ def user_has_qualification(user, item, depth): else: return mark_safe("") + @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) + @register.simple_tag def colour_from_depth(depth): return models.TrainingItemQualification.get_colour_from_depth(depth) + @register.filter def get_supervisor(tech): return models.TrainingLevel.objects.get(department=tech.department, level=models.TrainingLevel.SUPERVISOR) + @register.filter def get_levels_of_depth(trainee, level): return trainee.level_qualifications(True).filter(level__level=level)