mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-02-22 06:18:24 +00:00
Compare commits
5 Commits
732affa0b2
...
f0b3a6daf3
| Author | SHA1 | Date | |
|---|---|---|---|
| f0b3a6daf3 | |||
| 3c5f6da363 | |||
| ee9be86465 | |||
| 5554edf977 | |||
| 14b73f6f50 |
12
Pipfile
12
Pipfile
@@ -19,7 +19,7 @@ cssselect = "~=1.1.0"
|
|||||||
cssutils = "~=1.0.2"
|
cssutils = "~=1.0.2"
|
||||||
dj-database-url = "~=0.5.0"
|
dj-database-url = "~=0.5.0"
|
||||||
dj-static = "~=0.0.6"
|
dj-static = "~=0.0.6"
|
||||||
Django = "~=3.1.12"
|
Django = "~=3.2"
|
||||||
django-debug-toolbar = "~=3.2"
|
django-debug-toolbar = "~=3.2"
|
||||||
django-filter = "~=2.4.0"
|
django-filter = "~=2.4.0"
|
||||||
django-ical = "~=1.7.1"
|
django-ical = "~=1.7.1"
|
||||||
@@ -89,14 +89,8 @@ pytest-django = "*"
|
|||||||
pluggy = "*"
|
pluggy = "*"
|
||||||
pytest-splinter = "*"
|
pytest-splinter = "*"
|
||||||
pytest = "*"
|
pytest = "*"
|
||||||
|
pytest-xdist = {extras = [ "psutil",], version = "*"}
|
||||||
|
PyPOM = {extras = [ "splinter",], version = "*"}
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
python_version = "3.9"
|
python_version = "3.9"
|
||||||
|
|
||||||
[dev-packages.pytest-xdist]
|
|
||||||
extras = [ "psutil",]
|
|
||||||
version = "*"
|
|
||||||
|
|
||||||
[dev-packages.PyPOM]
|
|
||||||
extras = [ "splinter",]
|
|
||||||
version = "*"
|
|
||||||
|
|||||||
@@ -260,3 +260,5 @@ USE_GRAVATAR = True
|
|||||||
|
|
||||||
TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf"
|
TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf"
|
||||||
AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk'
|
AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk'
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class SecureAPIRequest(generic.View):
|
|||||||
'profile': 'RIGS.view_profile',
|
'profile': 'RIGS.view_profile',
|
||||||
'event': None,
|
'event': None,
|
||||||
'supplier': None,
|
'supplier': None,
|
||||||
'training_item': None, # TODO
|
'training_item': None, # TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@@ -78,6 +78,9 @@ class SecureAPIRequest(generic.View):
|
|||||||
fields = request.GET.get('fields', None)
|
fields = request.GET.get('fields', None)
|
||||||
if fields:
|
if fields:
|
||||||
fields = fields.split(",")
|
fields = fields.split(",")
|
||||||
|
filters = request.GET.get('filters', [])
|
||||||
|
if filters:
|
||||||
|
filters = filters.split(",")
|
||||||
|
|
||||||
# Supply data for one record
|
# Supply data for one record
|
||||||
if pk:
|
if pk:
|
||||||
@@ -98,6 +101,9 @@ class SecureAPIRequest(generic.View):
|
|||||||
for field in fields:
|
for field in fields:
|
||||||
q = Q(**{field + "__icontains": part})
|
q = Q(**{field + "__icontains": part})
|
||||||
qs.append(q)
|
qs.append(q)
|
||||||
|
for filter in filters:
|
||||||
|
q = Q(**{field: True})
|
||||||
|
qs.append(q)
|
||||||
queries.append(reduce(operator.or_, qs))
|
queries.append(reduce(operator.or_, qs))
|
||||||
|
|
||||||
# Build the data response list
|
# Build the data response list
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from assets import models
|
|||||||
from RIGS import models as rigsmodels
|
from RIGS import models as rigsmodels
|
||||||
from training import models as tmodels
|
from training import models as tmodels
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Deletes testing sample data'
|
help = 'Deletes testing sample data'
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class CableTypeForm(forms.ModelForm):
|
|||||||
model = models.CableType
|
model = models.CableType
|
||||||
fields = '__all__'
|
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
|
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']))
|
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...
|
# Being identical to itself shouldn't count...
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from django.contrib import admin
|
|||||||
from training import models
|
from training import models
|
||||||
from reversion.admin import VersionAdmin
|
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.TrainingCategory, VersionAdmin)
|
||||||
admin.site.register(models.TrainingItem, VersionAdmin)
|
admin.site.register(models.TrainingItem, VersionAdmin)
|
||||||
admin.site.register(models.TrainingLevel, VersionAdmin)
|
admin.site.register(models.TrainingLevel, VersionAdmin)
|
||||||
|
|||||||
@@ -43,28 +43,131 @@ class Command(BaseCommand):
|
|||||||
self.categories.append(category)
|
self.categories.append(category)
|
||||||
|
|
||||||
def setup_items(self):
|
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)
|
item = models.TrainingItem.objects.create(category=random.choice(self.categories), reference_number=random.randint(0, 100), name=name)
|
||||||
self.items.append(item)
|
self.items.append(item)
|
||||||
|
|
||||||
def setup_levels(self):
|
def setup_levels(self):
|
||||||
items = self.items.copy()
|
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)
|
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)
|
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")
|
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):
|
for i in range(0, 5):
|
||||||
if len(items) == 0:
|
if len(items) == 0:
|
||||||
break
|
break
|
||||||
item = random.choice(items)
|
item = random.choice(items)
|
||||||
items.remove(item)
|
items.remove(item)
|
||||||
if i % 3 == 0:
|
if i % 3 == 0:
|
||||||
models.TrainingLevelRequirement.objects.create(level=tech_ccs, item=item, depth=random.choice(models.TrainingItemQualification.CHOICES)[0])
|
models.TrainingLevelRequirement.objects.create(level=tech_ccs, item=item, depth=random.choice(models.TrainingItemQualification.CHOICES)[0])
|
||||||
else:
|
else:
|
||||||
models.TrainingLevelRequirement.objects.create(level=super_ccs, item=item, depth=random.choice(models.TrainingItemQualification.CHOICES)[0])
|
models.TrainingLevelRequirement.objects.create(level=super_ccs, item=item, depth=random.choice(models.TrainingItemQualification.CHOICES)[0])
|
||||||
icons = {
|
icons = {
|
||||||
models.TrainingLevel.SOUND: ('microphone', 'microphone-alt'),
|
models.TrainingLevel.SOUND: ('microphone', 'microphone-alt'),
|
||||||
models.TrainingLevel.LIGHTING: ('lightbulb', 'traffic-light'),
|
models.TrainingLevel.LIGHTING: ('lightbulb', 'traffic-light'),
|
||||||
@@ -72,7 +175,7 @@ class Command(BaseCommand):
|
|||||||
models.TrainingLevel.RIGGING: ('link', 'pallet'),
|
models.TrainingLevel.RIGGING: ('link', 'pallet'),
|
||||||
models.TrainingLevel.HAULAGE: ('truck', 'route'),
|
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 = 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)
|
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])
|
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.set_password('supervisor')
|
||||||
supervisor.groups.add(Group.objects.get(name="Keyholders"))
|
supervisor.groups.add(Group.objects.get(name="Keyholders"))
|
||||||
supervisor.save()
|
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())
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from django.utils.timezone import make_aware
|
|||||||
from training import models
|
from training import models
|
||||||
from RIGS.models import Profile
|
from RIGS.models import Profile
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
epoch = datetime.date(1970, 1, 1)
|
epoch = datetime.date(1970, 1, 1)
|
||||||
id_map = {}
|
id_map = {}
|
||||||
@@ -50,15 +51,15 @@ class Command(BaseCommand):
|
|||||||
tally[0] += 1
|
tally[0] += 1
|
||||||
else:
|
else:
|
||||||
# PYTHONIC, BABY
|
# 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)
|
# print(initials)
|
||||||
new_profile = Profile.objects.create(username=name.replace(" ", ""),
|
new_profile = Profile.objects.create(username=name.replace(" ", ""),
|
||||||
first_name=first_name,
|
first_name=first_name,
|
||||||
last_name=last_name,
|
last_name=last_name,
|
||||||
initials=initials)
|
initials=initials)
|
||||||
self.id_map[child.find('ID').text] = new_profile.pk
|
self.id_map[child.find('ID').text] = new_profile.pk
|
||||||
tally[1] += 1
|
tally[1] += 1
|
||||||
except AttributeError: # W.T.F
|
except AttributeError: # W.T.F
|
||||||
print("Trainee #{} is FUBAR".format(child.find('ID').text))
|
print("Trainee #{} is FUBAR".format(child.find('ID').text))
|
||||||
|
|
||||||
print('Trainees - Updated: {}, Created: {}'.format(tally[0], tally[1]))
|
print('Trainees - Updated: {}, Created: {}'.format(tally[0], tally[1]))
|
||||||
@@ -71,7 +72,7 @@ class Command(BaseCommand):
|
|||||||
for child in root:
|
for child in root:
|
||||||
obj, created = models.TrainingCategory.objects.update_or_create(
|
obj, created = models.TrainingCategory.objects.update_or_create(
|
||||||
pk=int(child.find('ID').text),
|
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
|
name=child.find('Category_x0020_Name').text
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -88,10 +89,10 @@ class Command(BaseCommand):
|
|||||||
root = self.parse_xml(self.xml_path('Training Items.xml'))
|
root = self.parse_xml(self.xml_path('Training Items.xml'))
|
||||||
|
|
||||||
for child in root:
|
for child in root:
|
||||||
if child.find('active').text == '0':
|
if child.find('active').text == '0':
|
||||||
active = False
|
active = False
|
||||||
else:
|
else:
|
||||||
active = True
|
active = True
|
||||||
|
|
||||||
number = int(child.find('Item_x0020_Number').text)
|
number = int(child.find('Item_x0020_Number').text)
|
||||||
name = child.find('Item_x0020_Name').text
|
name = child.find('Item_x0020_Name').text
|
||||||
@@ -99,11 +100,11 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
obj, created = models.TrainingItem.objects.update_or_create(
|
obj, created = models.TrainingItem.objects.update_or_create(
|
||||||
pk = int(child.find('ID').text),
|
pk=int(child.find('ID').text),
|
||||||
reference_number = number,
|
reference_number=number,
|
||||||
name = name,
|
name=name,
|
||||||
category = category,
|
category=category,
|
||||||
active = active
|
active=active
|
||||||
)
|
)
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
print("Training Item {}.{} {} has a duplicate reference number".format(category.reference_number, number, name))
|
print("Training Item {}.{} {} has a duplicate reference number".format(category.reference_number, number, name))
|
||||||
@@ -139,11 +140,11 @@ class Command(BaseCommand):
|
|||||||
try:
|
try:
|
||||||
obj, created = models.TrainingItemQualification.objects.update_or_create(
|
obj, created = models.TrainingItemQualification.objects.update_or_create(
|
||||||
pk=int(child.find('ID').text),
|
pk=int(child.find('ID').text),
|
||||||
item = models.TrainingItem.objects.get(pk=int(child.find('Training_Item_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]),
|
trainee=Profile.objects.get(pk=self.id_map[child.find('Member_ID').text]),
|
||||||
depth = depth_index,
|
depth=depth_index,
|
||||||
date = child.find('{}_Date'.format(depth)).text[:-9], # Stored as datetime with time as midnight because fuck you I guess
|
date=child.find('{}_Date'.format(depth)).text[:-9], # Stored as datetime with time as midnight because fuck you I guess
|
||||||
supervisor = supervisor
|
supervisor=supervisor
|
||||||
)
|
)
|
||||||
notes = child.find('{}_Notes'.format(depth))
|
notes = child.find('{}_Notes'.format(depth))
|
||||||
if notes:
|
if notes:
|
||||||
@@ -153,7 +154,7 @@ class Command(BaseCommand):
|
|||||||
tally[1] += 1
|
tally[1] += 1
|
||||||
else:
|
else:
|
||||||
tally[0] += 1
|
tally[0] += 1
|
||||||
except IntegrityError: # Eh?
|
except IntegrityError: # Eh?
|
||||||
print("Training Record #{} is duplicate. ಠ_ಠ".format(child.find('ID').text))
|
print("Training Record #{} is duplicate. ಠ_ಠ".format(child.find('ID').text))
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
print(child.find('ID').text)
|
print(child.find('ID').text)
|
||||||
@@ -197,8 +198,8 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
obj, created = models.TrainingLevel.objects.update_or_create(
|
obj, created = models.TrainingLevel.objects.update_or_create(
|
||||||
pk=int(child.find('ID').text),
|
pk=int(child.find('ID').text),
|
||||||
description = desc,
|
description=desc,
|
||||||
level = level
|
level=level
|
||||||
)
|
)
|
||||||
if depString is not None:
|
if depString is not None:
|
||||||
obj.department = department
|
obj.department = department
|
||||||
@@ -210,12 +211,12 @@ class Command(BaseCommand):
|
|||||||
tally[0] += 1
|
tally[0] += 1
|
||||||
|
|
||||||
for level in models.TrainingLevel.objects.all():
|
for level in models.TrainingLevel.objects.all():
|
||||||
if level.department != None:
|
if level.department is not None:
|
||||||
if level.level == models.TrainingLevel.TECHNICIAN:
|
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))
|
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:
|
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))
|
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]))
|
print('Training Levels - Updated: {}, Created: {}'.format(tally[0], tally[1]))
|
||||||
|
|
||||||
def import_TrainingLevelQualification(self):
|
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))
|
print('Training Level Qualification #{} does not qualify anyone. How?!'.format(child.find('ID').text))
|
||||||
continue
|
continue
|
||||||
obj, created = models.TrainingLevelQualification.objects.update_or_create(
|
obj, created = models.TrainingLevelQualification.objects.update_or_create(
|
||||||
pk = int(child.find('ID').text),
|
pk=int(child.find('ID').text),
|
||||||
trainee = Profile.objects.get(pk=self.id_map[child.find('Member_x0020_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))
|
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:
|
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()
|
obj.save()
|
||||||
#confirmed by?
|
# confirmed by?
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
tally[1] += 1
|
tally[1] += 1
|
||||||
else:
|
else:
|
||||||
tally[0] += 1
|
tally[0] += 1
|
||||||
except IntegrityError: # Eh?
|
except IntegrityError: # Eh?
|
||||||
print("Training Level Qualification #{} is duplicate. ಠ_ಠ".format(child.find('ID').text))
|
print("Training Level Qualification #{} is duplicate. ಠ_ಠ".format(child.find('ID').text))
|
||||||
|
|
||||||
print('TrainingLevelQualifications - Updated: {}, Created: {}'.format(tally[0], tally[1]))
|
print('TrainingLevelQualifications - Updated: {}, Created: {}'.format(tally[0], tally[1]))
|
||||||
@@ -259,7 +260,13 @@ class Command(BaseCommand):
|
|||||||
for child in root:
|
for child in root:
|
||||||
try:
|
try:
|
||||||
item = child.find('Item').text.split(".")
|
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:
|
if created:
|
||||||
tally[1] += 1
|
tally[1] += 1
|
||||||
|
|||||||
@@ -14,14 +14,13 @@ class Trainee(Profile, RevisionMixin):
|
|||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
|
|
||||||
|
# TODO remove levels that the user has a qualification in
|
||||||
|
# FIXME use queryset
|
||||||
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):
|
def level_qualifications(self, only_confirmed=False):
|
||||||
levels = self.levels.all()
|
return self.levels.all().filter(confirmed_on__isnull=only_confirmed).select_related('level')
|
||||||
if only_confirmed:
|
|
||||||
levels = levels.exclude(confirmed_on__isnull=True)
|
|
||||||
return levels.select_related('level')
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_supervisor(self):
|
def is_supervisor(self):
|
||||||
@@ -38,8 +37,7 @@ class Trainee(Profile, RevisionMixin):
|
|||||||
return self.qualifications_obtained.filter(depth=depth).select_related('item', 'trainee', 'supervisor')
|
return self.qualifications_obtained.filter(depth=depth).select_related('item', 'trainee', 'supervisor')
|
||||||
|
|
||||||
def is_user_qualified_in(self, item, required_depth):
|
def is_user_qualified_in(self, item, required_depth):
|
||||||
qual = self.qualifications_obtained.filter(item=item).first() # this is a somewhat ghetto version of get_or_none
|
return self.qualifications_obtained.values('item', 'depth').filter(item=item).filter(depth__gte=required_depth).first() is not None # this is a somewhat ghetto version of get_or_none
|
||||||
return qual is not None and qual.depth >= required_depth
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('trainee_detail', kwargs={'pk': self.pk})
|
return reverse('trainee_detail', kwargs={'pk': self.pk})
|
||||||
@@ -60,7 +58,7 @@ class TrainingItem(models.Model):
|
|||||||
reference_number = models.IntegerField()
|
reference_number = models.IntegerField()
|
||||||
category = models.ForeignKey('TrainingCategory', related_name='items', on_delete=models.RESTRICT)
|
category = models.ForeignKey('TrainingCategory', related_name='items', on_delete=models.RESTRICT)
|
||||||
name = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
active = models.BooleanField(default = True)
|
active = models.BooleanField(default=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def number(self):
|
def number(self):
|
||||||
@@ -74,9 +72,7 @@ class TrainingItem(models.Model):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def user_has_qualification(item, user, depth):
|
def user_has_qualification(item, user, depth):
|
||||||
for q in user.qualifications_obtained.all().select_related('item'):
|
return user.qualifications_obtained.values('item', 'depth').filter(item=item, depth__gte=depth).exists()
|
||||||
if q.item == item and q.depth > depth:
|
|
||||||
return True
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ["reference_number", "active", "category"]
|
unique_together = ["reference_number", "active", "category"]
|
||||||
@@ -93,8 +89,8 @@ class TrainingItemQualification(models.Model):
|
|||||||
(PASSED_OUT, 'Passed Out'),
|
(PASSED_OUT, 'Passed Out'),
|
||||||
)
|
)
|
||||||
item = models.ForeignKey('TrainingItem', on_delete=models.RESTRICT)
|
item = models.ForeignKey('TrainingItem', on_delete=models.RESTRICT)
|
||||||
trainee = models.ForeignKey('Trainee', related_name='qualifications_obtained', on_delete=models.RESTRICT)
|
|
||||||
depth = models.IntegerField(choices=CHOICES)
|
depth = models.IntegerField(choices=CHOICES)
|
||||||
|
trainee = models.ForeignKey('Trainee', related_name='qualifications_obtained', on_delete=models.RESTRICT)
|
||||||
date = models.DateField()
|
date = models.DateField()
|
||||||
# TODO Remember that some training is external. Support for making an organisation the trainer?
|
# TODO Remember that some training is external. Support for making an organisation the trainer?
|
||||||
supervisor = models.ForeignKey('Trainee', related_name='qualifications_granted', on_delete=models.RESTRICT)
|
supervisor = models.ForeignKey('Trainee', related_name='qualifications_granted', on_delete=models.RESTRICT)
|
||||||
@@ -189,18 +185,8 @@ class TrainingLevel(models.Model, RevisionMixin):
|
|||||||
def passed_out_requirements(self):
|
def passed_out_requirements(self):
|
||||||
return self.get_requirements_of_depth(TrainingItemQualification.PASSED_OUT)
|
return self.get_requirements_of_depth(TrainingItemQualification.PASSED_OUT)
|
||||||
|
|
||||||
def get_related_level(self, dif):
|
def percentage_complete(self, user):
|
||||||
if (level == 0 and dif < 0) or (level == 2 and dif > 0):
|
needed_qualifications = self.requirements.all().select_related('item')
|
||||||
return None
|
|
||||||
return TrainingLevel.objects.get(department=self.department, level=self.level+dif)
|
|
||||||
|
|
||||||
def get_common_competencies(self):
|
|
||||||
if is_common_competencies:
|
|
||||||
return self
|
|
||||||
return TrainingLevel.objects.get(level=self.level, department=None)
|
|
||||||
|
|
||||||
def percentage_complete(self, user): # FIXME
|
|
||||||
needed_qualifications = self.requirements.all().select_related()
|
|
||||||
relavant_qualifications = 0.0
|
relavant_qualifications = 0.0
|
||||||
# TODO Efficiency...
|
# TODO Efficiency...
|
||||||
for req in needed_qualifications:
|
for req in needed_qualifications:
|
||||||
@@ -213,7 +199,7 @@ class TrainingLevel(models.Model, RevisionMixin):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
def user_has_requirements(self, user):
|
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):
|
def __str__(self):
|
||||||
if self.department is None:
|
if self.department is None:
|
||||||
|
|||||||
@@ -13,18 +13,24 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
<script src="{% static 'js/autocompleter.js' %}"></script>
|
|
||||||
<script src="{% static 'js/tooltip.js' %}"></script>
|
<script src="{% static 'js/tooltip.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<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>
|
||||||
<form role="form" action="{{ form.action|default:request.path }}" method="POST" id="add_record_form">
|
<form role="form" action="{{ form.action|default:request.path }}" method="POST" id="add_record_form">
|
||||||
{% include 'form_errors.html' %}
|
{% include 'form_errors.html' %}
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% render_field form.trainee|attr:'hidden' value=form.trainee.initial %}
|
{% render_field form.trainee|attr:'hidden' value=form.trainee.initial %}
|
||||||
<div class="form-group form-row">
|
<div class="form-group form-row">
|
||||||
<label for="item_id" class="col-sm-2 col-form-label">Item</label>
|
<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-4" data-live-search="true" data-sourceurl="{% url 'api_secure' model='training_item' %}?fields=reference_number,name" 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' %}?fields=reference_number,name&filters=active" required>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group form-row">
|
<div class="form-group form-row">
|
||||||
@@ -32,15 +38,9 @@
|
|||||||
{% render_field form.depth|add_class:'form-control custom-select col-sm-4' %}
|
{% render_field form.depth|add_class:'form-control custom-select col-sm-4' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group form-row">
|
<div class="form-group form-row">
|
||||||
{% if external %}
|
|
||||||
<label for="supervisor" class="col-sm-2 col-form-label">Supervising Organisation</label>
|
|
||||||
<select name="supervisor" id="supervising_organisation_id" class="form-control selectpicker custom-select col-sm-10" data-live-search="true" data-sourceurl="{% url 'api_secure' model='organisation' %}" required>
|
|
||||||
</select>
|
|
||||||
{% else %}
|
|
||||||
<label for="supervisor" 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 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>
|
</select>
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group form-row">
|
<div class="form-group form-row">
|
||||||
<label for="date" class="col-sm-2 col-form-label">Training Date</label>
|
<label for="date" class="col-sm-2 col-form-label">Training Date</label>
|
||||||
|
|||||||
@@ -1,45 +1,42 @@
|
|||||||
{% extends 'base_training.html' %}
|
{% extends 'base_training.html' %}
|
||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load user_has_qualification from tags %}
|
|
||||||
{% load percentage_complete from tags %}
|
{% load percentage_complete from tags %}
|
||||||
{% load user_level_if_present from tags %}
|
{% load markdown_tags %}
|
||||||
{% load colour_from_depth from tags %}
|
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'css/selects.css' %}"/>
|
<link rel="stylesheet" type="text/css" href="{% static 'css/selects.css' %}"/>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block preload_js %}
|
{% block preload_js %}
|
||||||
<script src="{% static 'js/selects.js' %}"></script>
|
<script src="{% static 'js/selects.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
<script src="{% static 'js/autocompleter.js' %}"></script>
|
<script src="{% static 'js/tooltip.js' %}"></script>
|
||||||
<script src="{% static 'js/tooltip.js' %}"></script>
|
<script>
|
||||||
<script>
|
|
||||||
$('document').ready(function(){
|
$('document').ready(function(){
|
||||||
$('#add_record,#add_external').click(function (e) {
|
$('#add_record').click(function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var url = $(this).attr("href");
|
var url = $(this).attr("href");
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
success: function(){
|
success: function(){
|
||||||
$link = $(this);
|
$link = $(this);
|
||||||
// Anti modal inception
|
// Anti modal inception
|
||||||
if ($link.parents('#modal').length === 0) {
|
if ($link.parents('#modal').length === 0) {
|
||||||
modaltarget = $link.data('target');
|
modaltarget = $link.data('target');
|
||||||
modalobject = "";
|
modalobject = "";
|
||||||
$('#modal').load(url, function (e) {
|
$('#modal').load(url, function (e) {
|
||||||
$('#modal').modal();
|
$('#modal').modal();
|
||||||
$(".selectpicker").selectpicker().each(function(){initPicker($(this))});
|
//$(".selectpicker").selectpicker().each(function(){initPicker($(this))});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@@ -51,60 +48,40 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<h2 class="col-12">Training Levels</h2>
|
<h2 class="col-12">Training Levels</h2>
|
||||||
<h3 class="col-12">Qualified</h3>
|
|
||||||
<ul class="list-group col-12">
|
<ul class="list-group col-12">
|
||||||
{% for qual in completed_levels %}
|
{% for qual in completed_levels %}
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<a href="{% url 'level_detail' qual.level.pk %}">{{ qual.level }}</a>
|
<a href="{% url 'level_detail' qual.level.pk %}">{{ qual.level }}</a>
|
||||||
{% if qual.confirmed_by is None %}
|
{% if qual.confirmed_on is None %}
|
||||||
{% if request.user.pk != object.pk and request.user.is_supervisor %}
|
{% if request.user.pk != object.pk and request.user.is_supervisor %}
|
||||||
<span class="badge badge-warning">Awaiting Confirmation</span> <a class="btn btn-info" href="{% url 'confirm_level' object.pk qual.level.pk %}">Confirm</a>
|
<span class="badge badge-warning">Awaiting Confirmation</span> <a class="btn btn-info" href="{% url 'confirm_level' object.pk qual.level.pk %}">Confirm</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<button class="btn btn-warning" disabled>Awaiting Confirmation</button>
|
<button class="btn btn-warning" disabled>Awaiting Confirmation</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<button class="btn btn-success active">Confirmed <small>by {{ qual.confirmed_by }}</small></button>
|
<button class="btn btn-success active">Confirmed <small>by {{ qual.confirmed_by|default:'System' }}</small></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<div class="alert alert-warning mx-auto">No qualifications in any levels yet...did someone forget to fill out the paperwork?</div>
|
<div class="alert alert-warning mx-auto">No qualifications in any levels yet...did someone forget to fill out the paperwork?</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
<h3>In Progress</h3>
|
|
||||||
<div class="card-columns">
|
<div class="card-columns">
|
||||||
{% for level in started_levels %}
|
{% for level in started_levels %}
|
||||||
{% percentage_complete level object as completion %}
|
{% percentage_complete level object as completion %}
|
||||||
<div class="card my-3 border-warning">
|
<div class="card my-3 border-warning">
|
||||||
<h3 class="card-header"><a href="{% url 'level_detail' level.pk %}">{{ level }}</a></h3>
|
<h3 class="card-header"><a href="{{ level.get_absolute_url }}">{{ level }}</a></h3>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>{{ level.description|truncatewords:30 }}</p>
|
{{ level.description|markdown }}
|
||||||
<div class="progress mb-2">
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div class="progress">
|
||||||
<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 class="progress-bar progress-bar-striped" role="progressbar" style="width: {{completion}}%" aria-valuenow="{{completion}}" aria-valuemin="0" aria-valuemax="100">{{completion}}% complete</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<h2 class="col-12 pb-2">Training Items <small class="bg-light rounded-sm p-2"> 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></small></h2>
|
|
||||||
{% for category in categories %}
|
|
||||||
{% if forloop.first or forloop.counter|divisibleby:3 %}<div class="card-deck col-12">{% endif %}
|
|
||||||
<div class="card mb-3">
|
|
||||||
<h3 class="card-header">{{ category }}</h3>
|
|
||||||
<div class="list-group list-group-flush">
|
|
||||||
{% for q in object.qualifications_obtained.all %}
|
|
||||||
{% if q.item.category == category %}
|
|
||||||
<li class="list-group-item list-group-item-{% colour_from_depth q.depth %}">{{q.item}} ({{q.date}})</li>
|
|
||||||
{% endif %}
|
|
||||||
{% empty %}
|
|
||||||
<li class="list-group-item text-muted">None yet...</li>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if forloop.counter|add:"1"|divisibleby:3 %}</div>{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col text-right">
|
<div class="col text-right">
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<th scope="row" class="align-middle" id="cell_name">{{ object.item }}</th>
|
<th scope="row" class="align-middle" id="cell_name">{{ object.item }}</th>
|
||||||
<td class="table-{% colour_from_depth object.depth %}">{{ object.get_depth_display }}</td>
|
<td class="table-{% colour_from_depth object.depth %}">{{ object.get_depth_display }}</td>
|
||||||
<td>{{ object.date }}</td>
|
<td>{{ object.date }}</td>
|
||||||
<td>{{ object.supervisor }}</td>
|
<td><a href="{{ object.supervisor.get_absolute_url}}">{{ object.supervisor }}</a></td>
|
||||||
<td>{{ object.notes }}</td>
|
<td>{{ object.notes }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from training import models
|
|||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def user_has_qualification(user, item, depth):
|
def user_has_qualification(user, item, depth):
|
||||||
if models.TrainingItem.user_has_qualification(item, user, depth) is not None:
|
if models.TrainingItem.user_has_qualification(item, user, depth) is not None:
|
||||||
@@ -15,22 +16,27 @@ def user_has_qualification(user, item, depth):
|
|||||||
else:
|
else:
|
||||||
return mark_safe("<span class='fas fa-hourglass-start text-warning'></span>")
|
return mark_safe("<span class='fas fa-hourglass-start text-warning'></span>")
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def user_level_if_present(user, level):
|
def user_level_if_present(user, level):
|
||||||
return models.TrainingLevelQualification.objects.filter(trainee=user, level=level).first()
|
return models.TrainingLevelQualification.objects.filter(trainee=user, level=level).first()
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def percentage_complete(level, user):
|
def percentage_complete(level, user):
|
||||||
return level.percentage_complete(user)
|
return level.percentage_complete(user)
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def colour_from_depth(depth):
|
def colour_from_depth(depth):
|
||||||
return models.TrainingItemQualification.get_colour_from_depth(depth)
|
return models.TrainingItemQualification.get_colour_from_depth(depth)
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def get_supervisor(tech):
|
def get_supervisor(tech):
|
||||||
return models.TrainingLevel.objects.get(department=tech.department, level=models.TrainingLevel.SUPERVISOR)
|
return models.TrainingLevel.objects.get(department=tech.department, level=models.TrainingLevel.SUPERVISOR)
|
||||||
|
|
||||||
|
|
||||||
@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(True).filter(level__level=level)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from PyRIGS.views import OEmbedView, is_ajax, ModalURLMixin
|
|||||||
from training import models, forms
|
from training import models, forms
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Q, Count
|
from django.db.models import Q, Count, OuterRef, F, Subquery, Window
|
||||||
|
|
||||||
from users import views
|
from users import views
|
||||||
|
|
||||||
@@ -37,12 +37,8 @@ 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()
|
context["completed_levels"] = self.object.level_qualifications().select_related('level')
|
||||||
context["categories"] = models.TrainingCategory.objects.all().prefetch_related('items')
|
context["categories"] = models.TrainingCategory.objects.all().prefetch_related('items')
|
||||||
choices = models.TrainingItemQualification.CHOICES
|
|
||||||
context["depths"] = choices
|
|
||||||
for i in [x for x, _ in choices]:
|
|
||||||
context[str(i)] = self.object.get_records_of_depth(i)
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@@ -101,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')
|
return self.model.objects.filter(filter).annotate(num_qualifications=Count('qualifications_obtained')).order_by('-num_qualifications').prefetch_related('levels', '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)
|
||||||
|
|||||||
Reference in New Issue
Block a user