Compare commits

...

4 Commits

Author SHA1 Message Date
e836195fef mild polishing 2021-07-06 14:51:43 +01:00
68a424d62b Some sample data and UX work 2021-07-06 12:16:43 +01:00
5e15b8bb59 Basic UX for adding requirements to training levels 2021-07-06 11:37:04 +01:00
d26c1b535e item ui vaguely working 2021-07-06 00:09:46 +01:00
13 changed files with 243 additions and 27 deletions

View File

@@ -3,7 +3,7 @@ from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.models import Group
from assets import models
from RIGS import models as rigsmodels
from training import models as tmodels
class Command(BaseCommand):
help = 'Deletes testing sample data'
@@ -31,6 +31,9 @@ class Command(BaseCommand):
self.delete_objects(rigsmodels.Payment)
self.delete_objects(rigsmodels.RiskAssessment)
self.delete_objects(rigsmodels.EventChecklist)
self.delete_objects(tmodels.TrainingCategory)
self.delete_objects(tmodels.TrainingItem)
self.delete_objects(tmodels.TrainingLevel)
def delete_objects(self, model):
for obj in model.objects.all():

View File

@@ -21,6 +21,7 @@ class Command(BaseCommand):
profiles = models.Profile.objects.all()
def handle(self, *args, **options):
print("Generating rigboard data")
from django.conf import settings
if not (settings.DEBUG or settings.STAGING):
@@ -35,6 +36,7 @@ class Command(BaseCommand):
self.setup_organisations()
self.setup_venues()
self.setup_events()
print("Done generating rigboard data")
def setup_people(self):
names = ["Regulus Black", "Sirius Black", "Lavender Brown", "Cho Chang", "Vincent Crabbe", "Vincent Crabbe",

View File

@@ -20,6 +20,7 @@ class Command(BaseCommand):
assets = []
def handle(self, *args, **kwargs):
print("Generating sample assets data")
from django.conf import settings
if not (settings.DEBUG or settings.STAGING):
@@ -34,6 +35,7 @@ class Command(BaseCommand):
self.create_assets()
self.create_connectors()
self.create_cables()
print("Done generating sample assets data")
def create_categories(self):
choices = ['Case', 'Video', 'General', 'Sound', 'Lighting', 'Rigging']

View File

@@ -36,7 +36,7 @@
<img src="{% static 'imgs/logo.webp' %}" width="40" height="40" alt="TEC's Logo: Serif 'TEC' vertically next to a blue box with the words 'PA and Lighting', surrounded by graduated rings" id="logo">
</a>
<div class="container" style="padding-left: 0; padding-right: 0;">
<div class="row w-100">
<div class="row w-100 flex-nowrap">
{% block titleheader %}
{% endblock %}
<button class="navbar-toggler ml-auto" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" onclick="document.getElementById('logo').classList.toggle('d-none');">

View File

@@ -3,6 +3,6 @@ from training import models
from reversion.admin import VersionAdmin
#admin.site.register(models.Trainee, VersionAdmin)
admin.site.register(models.TrainingLevel, VersionAdmin)
admin.site.register(models.TrainingCategory, VersionAdmin)
admin.site.register(models.TrainingItem, VersionAdmin)
admin.site.register(models.TrainingLevel, VersionAdmin)

View File

@@ -6,7 +6,7 @@ from RIGS.models import Profile
class SessionLogForm(forms.Form):
pass
# TODO Validation that dates cannot be in the future
class QualificationForm(forms.ModelForm):
class Meta:
model = models.TrainingItemQualification
@@ -18,3 +18,13 @@ class QualificationForm(forms.ModelForm):
super(QualificationForm, self).__init__(*args, **kwargs)
self.fields['trainee'].initial = Profile.objects.get(pk=pk)
class RequirementForm(forms.ModelForm):
class Meta:
model = models.TrainingLevelRequirement
fields = '__all__'
def __init__(self, *args, **kwargs):
pk = kwargs.pop('pk', None)
super(RequirementForm, self).__init__(*args, **kwargs)
self.fields['level'].initial = models.TrainingLevel.objects.get(pk=pk)

View File

@@ -16,8 +16,10 @@ class Command(BaseCommand):
categories = []
items = []
levels = []
def handle(self, *args, **options):
print("Generating training data")
from django.conf import settings
if not (settings.DEBUG or settings.STAGING):
@@ -28,6 +30,8 @@ class Command(BaseCommand):
with transaction.atomic():
self.setup_categories()
self.setup_items()
self.setup_levels()
print("Done generating training data")
def setup_categories(self):
names = [(1, "Basic"), (2, "Sound"), (3, "Lighting"), (4, "Rigging"), (5, "Power"), (6, "Haulage")]
@@ -43,3 +47,17 @@ class Command(BaseCommand):
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):
self.levels.append(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."))
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.")
supervisor = models.TrainingLevel.objects.create(level=models.TrainingLevel.SUPERVISOR, department=i, description="Spirit holiest merciful mountains inexpedient reason value. Suicide ultimate hope.")
supervisor.prerequisite_levels.add(technician)
for i in range(0, 30):
if i % 3 == 0:
models.TrainingLevelRequirement.objects.create(level=technician, item=random.choice(self.items), depth=random.choice(models.TrainingItemQualification.CHOICES)[0])
else:
models.TrainingLevelRequirement.objects.create(level=supervisor, item=random.choice(self.items), depth=random.choice(models.TrainingItemQualification.CHOICES)[0])
self.levels.append(technician)
self.levels.append(supervisor)

View File

@@ -0,0 +1,42 @@
# Generated by Django 3.1.5 on 2021-07-05 23:53
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('training', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='trainingcategory',
options={'verbose_name_plural': 'Training Categories'},
),
migrations.AddField(
model_name='traininglevel',
name='description',
field=models.CharField(blank=True, max_length=120),
),
migrations.AddField(
model_name='traininglevel',
name='prerequisite_levels',
field=models.ManyToManyField(blank=True, related_name='prerequisites', to='training.TrainingLevel'),
),
migrations.AlterField(
model_name='traininglevel',
name='department',
field=models.IntegerField(choices=[(0, 'Sound'), (1, 'Lighting'), (2, 'Power'), (3, 'Rigging'), (4, 'Haulage')], null=True),
),
migrations.CreateModel(
name='TrainingLevelRequirement',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('depth', models.IntegerField(verbose_name=((0, 'Training Started'), (1, 'Training Complete'), (2, 'Passed Out')))),
('item', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='training.trainingitem')),
('level', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='requirements', to='training.traininglevel')),
],
),
]

View File

@@ -16,6 +16,8 @@ class TrainingCategory(models.Model):
reference_number = models.CharField(max_length=3)
name = models.CharField(max_length=50)
class Meta:
verbose_name_plural = 'Training Categories'
class TrainingItem(models.Model):
reference_number = models.CharField(max_length=3)
@@ -26,7 +28,6 @@ class TrainingItem(models.Model):
return "{}.{} {}".format(self.category.reference_number, self.reference_number, self.name)
# TODO Validation that dates cannot be in the future
class TrainingItemQualification(models.Model):
STARTED = 0
COMPLETE = 1
@@ -44,17 +45,60 @@ class TrainingItemQualification(models.Model):
supervisor = models.ForeignKey('Trainee', related_name='qualifications_granted', on_delete=models.RESTRICT)
notes = models.TextField(blank=True)
def __str__(self):
return "{} in {} on {}".format(self.depth, self.item, self.date)
# Levels
# FIXME Common Competencies...
class TrainingLevel(models.Model, RevisionMixin):
description = models.CharField(max_length=120, blank=True)
TA = 0
TECHNICIAN = 1
SUPERVISOR = 2
CHOICES = (
(0, 'Technical Assistant'),
(1, 'Technician'),
(2, 'Supervisor'),
(TA, 'Technical Assistant'),
(TECHNICIAN, 'Technician'),
(SUPERVISOR, 'Supervisor'),
)
department = models.CharField(max_length=50, null=True) # N.B. Technical Assistant does not have a department
DEPARTMENTS = (
(0, 'Sound'),
(1, 'Lighting'),
(2, 'Power'),
(3, 'Rigging'),
(4, 'Haulage'),
)
department = models.IntegerField(choices=DEPARTMENTS, null=True) # N.B. Technical Assistant does not have a department
level = models.IntegerField(choices=CHOICES)
prerequisite_levels = models.ManyToManyField('self', related_name='prerequisites', symmetrical=False, blank=True)
def get_requirements_of_depth(self, depth):
return self.requirements.filter(depth=depth)
@property
def started_requirements(self):
return self.get_requirements_of_depth(TrainingItemQualification.STARTED)
@property
def complete_requirements(self):
return self.get_requirements_of_depth(TrainingItemQualification.COMPLETE)
@property
def passed_out_requirements(self):
return self.get_requirements_of_depth(TrainingItemQualification.PASSED_OUT)
def __str__(self):
if self.department is None: # 2TA
return self.get_level_display()
else:
return "{} {}".format(self.get_department_display(), self.get_level_display())
class TrainingLevelRequirement(models.Model):
level = models.ForeignKey('TrainingLevel', related_name='requirements', on_delete=models.RESTRICT)
item = models.ForeignKey('TrainingItem', on_delete=models.RESTRICT)
depth = models.IntegerField(TrainingItemQualification.CHOICES)
def __str__(self):
return "{} in {}".format(TrainingItemQualification.CHOICES[self.depth][1], self.item)
class TrainingLevelQualification(models.Model):

View File

@@ -0,0 +1,39 @@
{% extends 'base_rigs.html' %}
{% load static %}
{% load widget_tweaks %}
{% block css %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'css/selects.css' %}"/>
{% endblock %}
{% block preload_js %}
{{ block.super }}
<script src="{% static 'js/selects.js' %}"></script>
{% endblock %}
{% block js %}
{{ block.super }}
<script src="{% static 'js/autocompleter.js' %}"></script>
<script src="{% static 'js/tooltip.js' %}"></script>
{% endblock %}
{% block content %}
{% if form.errors %}
{% include 'form_errors.html' %}
{% endif %}
<form id="requirement-form" action="{{ form.action|default:request.path }}" method="post">{% csrf_token %}
{% render_field form.level|attr:'hidden' value=form.trainee.initial %}
<div class="form-group form-row">
<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-10" data-live-search="true" data-sourceurl="{% url 'api_secure' model='training_item' %}" required>
</select>
</div>
<div class="form-group form-row">
<label for="depth" class="col-sm-2 col-form-label">Depth</label>
{% render_field form.depth|add_class:'form-control custom-select selectpicker col-sm'|attr:'required' %}
</div>
<input type="submit" class="btn btn-primary">
</form>
{% endblock %}

View File

@@ -1,34 +1,69 @@
{% extends 'base_rigs.html' %}
{% block content %}
<button type="button" class="btn btn-success btn-sm item-add"
data-toggle="modal"
data-target="#record_modal">
<div class="row my-3 text-right">
<a type="button" class="btn btn-success" href="{% url 'edit_record' pk=request.user.pk %}">
<i class="fas fa-plus"></i> Add New Training Record
</button>
<div class="row mb-3">
<h2 class="col-12">Training Levels</h2>
<p>{{ user.name }} is a...<br></p>
<div class="col">
<h2><div class="badge badge-success">Sound Supervisor <span class="fas fa-volume-up"></span></div><h2>
<h3><div class="badge badge-danger">Power Technician <span class="fas fa-plug"></span></div></h3>
<h4><div class="badge badge-primary">Technical Assistant <span class="fas fa-wrench"></span></div></h4>
</a>
</div>
<div class="row mb-3">
<h2 class="col-12">Training Levels</h2>
<div class="card-columns">
{% for level in levels %}
<div class="card my-3">
<h3 class="card-header"><a href="{% url 'level_edit' level.pk %}">{{ level }}</a></h3>
<div class="card-body">
<div class="progress mb-2">
<div class="progress-bar progress-bar-striped" role="progressbar" style="width: 25%" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100">25% complete</div>
</div>
<p>{{ level.description }}</p>
<button class="btn btn-link p-0" type="button" data-toggle="collapse" data-target=".reqs_{{level.pk}}" aria-expanded="false" aria-controls="reqs">
Requirements <span class="fas fa-caret-right reqs_{{level.pk}} collapse show"></span><span class="fas fa-caret-down collapse reqs_{{level.pk}}"></span>
</button>
<div class="collapse reqs_{{level.pk}}">
<table class="table table-sm">
<thead>
<tr>
<th scope="col" class="table-warning">Training Started</th>
<th scope="col" class="table-success">Training Complete</th>
<th scope="col" class="table-info">Passed Out</th>
</tr>
</thead>
<tbody>
<tr>
<td><ul class="list-unstyled">{% for req in level.started_requirements %}<li>{{ req.item }}</li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in level.complete_requirements %}<li>{{ req.item }}</li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in level.passed_out_requirements %}<li>{{ req.item }}</li>{% endfor %}</ul></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="card-footer">
<button class="btn btn-success text-right" disabled>Incomplete</button>
</div>
</div>
{% endfor %}
</div>
</div>
<div class="row">
<h2 class="col-12">Training Items</h2><br>
{% for category in categories %}
<div class="col-md-3">
<h2 class="col-12">Training Items</h2><br>
<div class="card-columns">
{% for category in categories %}
<div class="card mb-3">
<h3 class="card-header">{{ category.name }}</h3>
<div class="list-group list-group-flush">
{% for depth in depths %}
<li class="list-group-item {% if depth.0 == 0 %}list-group-item-warning{%elif depth.0 == 1%}list-group-item-success{%else%}list-group-item-info{%endif%}">{{depth.1}}</li>
<li class="list-group-item">Dummy Item</li>
{% for q in object.qualifications_obtained.all %}
{% if q.depth == depth.0 and q.item.category == category %}
<li class="list-group-item">{{q.item}} ({{q.date}})</li>
{% endif %}
{% endfor %}
{% endfor %}
</div>
</div>
</div>
{% endfor %}
{% endfor %}
</div>
</div>
{% endblock %}

View File

@@ -15,4 +15,5 @@ urlpatterns = [
path('trainee/<int:pk>/edit/', views.AddQualification.as_view(),
name='edit_record'),
path('session/', views.SessionLog.as_view(), name='session_log'),
path('level/<int:pk>/edit/', views.AddLevelRequirement.as_view(), name='level_edit'),
]

View File

@@ -25,6 +25,7 @@ class TraineeDetail(views.ProfileDetail):
def get_context_data(self, **kwargs):
context = super(TraineeDetail, self).get_context_data(**kwargs)
context["page_title"] = "{}'s Training Record".format(self.object)
context["levels"] = models.TrainingLevel.objects.all()
context["categories"] = models.TrainingCategory.objects.all()
choices = models.TrainingItemQualification.CHOICES
context["depths"] = choices
@@ -66,3 +67,22 @@ class AddQualification(generic.CreateView):
kwargs = super(AddQualification, self).get_form_kwargs()
kwargs['pk'] = self.kwargs['pk']
return kwargs
class AddLevelRequirement(generic.CreateView):
template_name = "edit_training_level.html"
model = models.TrainingLevelRequirement
form_class = forms.RequirementForm
def get_context_data(self, **kwargs):
context = super(AddLevelRequirement, self).get_context_data(**kwargs)
context["page_title"] = "Add Requirements to Training Level {}".format(models.TrainingLevel.objects.get(pk=self.kwargs['pk']))
return context
def get_form_kwargs(self):
kwargs = super(AddLevelRequirement, self).get_form_kwargs()
kwargs['pk'] = self.kwargs['pk']
return kwargs
def get_success_url(self):
return reverse_lazy('trainee_detail')