Compare commits

...

5 Commits

Author SHA1 Message Date
458a734331 Machine switch 2021-07-01 09:50:13 +01:00
b1646d556c Start work on sample data command 2021-06-30 15:56:28 +01:00
f8624d3b7a Restructure based on actual thought put in by @mattysmith22 2021-06-30 15:17:00 +01:00
f6836fdab6 Merge branch 'master' into training 2021-06-29 17:17:48 +01:00
b3949f2903 Initial sketching 2021-06-29 17:13:36 +01:00
25 changed files with 522 additions and 2 deletions

View File

@@ -61,6 +61,7 @@ INSTALLED_APPS = (
'users', 'users',
'RIGS', 'RIGS',
'assets', 'assets',
'training',
'debug_toolbar', 'debug_toolbar',
'registration', 'registration',

View File

@@ -12,6 +12,7 @@ urlpatterns = [
path('', include('versioning.urls')), path('', include('versioning.urls')),
path('', include('RIGS.urls')), path('', include('RIGS.urls')),
path('assets/', include('assets.urls')), path('assets/', include('assets.urls')),
path('training/', include('training.urls')),
path('', login_required(views.Index.as_view()), name='index'), path('', login_required(views.Index.as_view()), name='index'),

View File

@@ -14,7 +14,7 @@ from reversion.admin import VersionAdmin
from RIGS import models from RIGS import models
from users import forms as user_forms from users import forms as user_forms
# Register your models here.
admin.site.register(models.VatRate, VersionAdmin) admin.site.register(models.VatRate, VersionAdmin)
admin.site.register(models.Event, VersionAdmin) admin.site.register(models.Event, VersionAdmin)
admin.site.register(models.EventItem, VersionAdmin) admin.site.register(models.EventItem, VersionAdmin)

View File

@@ -12,3 +12,4 @@ class Command(BaseCommand):
call_command('generateSampleUserData') call_command('generateSampleUserData')
call_command('generateSampleRIGSData') call_command('generateSampleRIGSData')
call_command('generateSampleAssetsData') call_command('generateSampleAssetsData')
call_command('generateSampleTrainingData')

BIN
RIGS/static/imgs/assets.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
RIGS/static/imgs/rigs.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 KiB

View File

@@ -1,5 +1,6 @@
{% extends 'base_rigs.html' %} {% extends 'base_rigs.html' %}
{% load humanize %} {% load humanize %}
{% load static %}
{% block title %}RIGS{% endblock %} {% block title %}RIGS{% endblock %}
@@ -7,8 +8,9 @@
<div class="row"> <div class="row">
<h1 class="col-sm-12 pb-3">R<small class="text-muted">ig</small> I<small class="text-muted">nformation</small> G<small class="text-muted">athering</small> S<small class="text-muted">ystem</small></h1> <h1 class="col-sm-12 pb-3">R<small class="text-muted">ig</small> I<small class="text-muted">nformation</small> G<small class="text-muted">athering</small> S<small class="text-muted">ystem</small></h1>
<h2 class="col-sm-12 pb-3">Welcome back {{ user.get_full_name }}, there {%if rig_count == 1 %}is one rig coming up{%else%}are {{ rig_count|apnumber }} rigs coming up.{%endif%}</h2> <h2 class="col-sm-12 pb-3">Welcome back {{ user.get_full_name }}, there {%if rig_count == 1 %}is one rig coming up{%else%}are {{ rig_count|apnumber }} rigs coming up.{%endif%}</h2>
<div class="col-sm mb-3"> <div class="col-sm-4 mb-3">
<div class="card"> <div class="card">
<img class="card-img-top" src="{% static 'imgs/rigs.jpg' %}" alt="" style="height: 150px; object-fit: cover;">
<h4 class="card-header">Rigboard</h4> <h4 class="card-header">Rigboard</h4>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="{% url 'rigboard' %}"><span class="fas fa-list align-middle"></span><span class="align-middle"> Rigboard</span></a> <a class="list-group-item list-group-item-action" href="{% url 'rigboard' %}"><span class="fas fa-list align-middle"></span><span class="align-middle"> Rigboard</span></a>
@@ -17,6 +19,12 @@
<a class="list-group-item list-group-item-action" href="{% url 'event_create' %}"><span class="fas fa-plus align-middle"></span><span class="align-middle"> New Event</span></a> <a class="list-group-item list-group-item-action" href="{% url 'event_create' %}"><span class="fas fa-plus align-middle"></span><span class="align-middle"> New Event</span></a>
{% endif %} {% endif %}
</div> </div>
</div>
</div>
<div class="col-sm-4 mb-3">
<div class="card">
{% now "m-d" as todays_date %}
<img class="card-img-top" src="{% if todays_date == '04-01' %}{% static 'imgs/tappytaptap.gif' %}{%else%}{% static 'imgs/assets.jpg' %}{%endif%}" alt="" style="height: 150px; object-fit: cover;">
<h4 class="card-header">Asset Database</h4> <h4 class="card-header">Asset Database</h4>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="{% url 'asset_index' %}"><span class="fas fa-tag align-middle"></span><span class="align-middle"> Asset List</span></a> <a class="list-group-item list-group-item-action" href="{% url 'asset_index' %}"><span class="fas fa-tag align-middle"></span><span class="align-middle"> Asset List</span></a>
@@ -28,6 +36,21 @@
<a class="list-group-item list-group-item-action" href="{% url 'supplier_create' %}"><span class="fas fa-plus align-middle"></span><span class="align-middle"> New Supplier</span></a> <a class="list-group-item list-group-item-action" href="{% url 'supplier_create' %}"><span class="fas fa-plus align-middle"></span><span class="align-middle"> New Supplier</span></a>
{% endif %} {% endif %}
</div> </div>
</div>
</div>
<div class="col-sm-4 mb-3">
<div class="card">
<img class="card-img-top" src="{% static 'imgs/training.jpg' %}" alt="" style="height: 150px; object-fit: cover;">
<h4 class="card-header">Training Database</h4>
<div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="{% url 'trainee_detail' %}"><span class="fas fa-file-signature align-middle"></span><span class="align-middle"> My Training Record</span></a>
<a class="list-group-item list-group-item-action" href="{% url 'item_list' %}"><span class="fas fa-eye align-middle"></span><span class="align-middle"> View Training Items</span></a>
<a class="list-group-item list-group-item-action" href=""><span class="fas fa-plus align-middle"></span><span class="align-middle"> Log Training Session</span></a>
</div>
</div>
</div>
<div class="col-sm mb-3">
<div class="card">
<h4 class="card-header">Quick Links</h4> <h4 class="card-header">Quick Links</h4>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="https://forum.nottinghamtec.co.uk" target="_blank" rel="noopener noreferrer"><span class="fas fa-comment-alt text-info align-middle"></span><span class="align-middle"> TEC Forum</span></a> <a class="list-group-item list-group-item-action" href="https://forum.nottinghamtec.co.uk" target="_blank" rel="noopener noreferrer"><span class="fas fa-comment-alt text-info align-middle"></span><span class="align-middle"> TEC Forum</span></a>

0
training/__init__.py Normal file
View File

8
training/admin.py Normal file
View File

@@ -0,0 +1,8 @@
from django.contrib import admin
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)

5
training/apps.py Normal file
View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class TrainingConfig(AppConfig):
name = 'training'

View File

View File

@@ -0,0 +1,45 @@
import datetime
import random
from django.contrib.auth.models import Group, Permission
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from django.utils import timezone
from reversion import revisions as reversion
from training import models
class Command(BaseCommand):
help = 'Adds sample data to use for testing'
can_import_settings = True
categories = []
items = []
def handle(self, *args, **options):
from django.conf import settings
if not (settings.DEBUG or settings.STAGING):
raise CommandError('You cannot run this command in production')
random.seed('otherwise it is done by time, which could lead to inconsistant tests')
with transaction.atomic():
self.setup_categories()
self.setup_items()
def setup_categories(self):
names = [(1, "Basic"), (2, "Sound"), (3, "Lighting"), (4, "Rigging"), (5, "Power"), (6, "Haulage")]
for i, name in names:
category = models.TrainingCategory.objects.create(reference_number=i, name=name)
category.save()
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"]
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)

View File

@@ -0,0 +1,106 @@
# Generated by Django 3.1.5 on 2021-06-29 16:10
import RIGS.models
import django.contrib.auth.models
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('RIGS', '0041_auto_20210302_1204'),
]
operations = [
migrations.CreateModel(
name='Department',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
],
),
migrations.CreateModel(
name='Trainee',
fields=[
('profile_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='RIGS.profile')),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
bases=('RIGS.profile',),
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='TrainingCategory',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('number', models.CharField(max_length=3)),
('name', models.CharField(max_length=50)),
],
),
migrations.CreateModel(
name='TrainingItem',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('number', models.CharField(max_length=3)),
('name', models.CharField(max_length=50)),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='training.trainingcategory')),
],
),
migrations.CreateModel(
name='TrainingItemInstance',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('training_started_on', models.DateField()),
('training_complete_on', models.DateField()),
('passed_out_on', models.DateField()),
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='training.trainingitem')),
('passed_out_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='passed_out', to='training.trainee')),
('trainee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='training.trainee')),
('training_complete_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='training_complete', to='training.trainee')),
('training_started_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='training_started', to='training.trainee')),
],
),
migrations.CreateModel(
name='Technician',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('department', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='training.department')),
('requirements', models.ManyToManyField(to='training.TrainingItem')),
],
options={
'abstract': False,
},
bases=(models.Model, RIGS.models.RevisionMixin),
),
migrations.CreateModel(
name='TechnicalAssistant',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('requirements', models.ManyToManyField(to='training.TrainingItem')),
],
options={
'abstract': False,
},
bases=(models.Model, RIGS.models.RevisionMixin),
),
migrations.CreateModel(
name='Supervisor',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('department', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='training.department')),
('requirements', models.ManyToManyField(to='training.TrainingItem')),
],
options={
'abstract': False,
},
bases=(models.Model, RIGS.models.RevisionMixin),
),
]

View File

@@ -0,0 +1,118 @@
# Generated by Django 3.1.5 on 2021-06-30 14:14
import RIGS.models
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('training', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='TrainingItemQualification',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('depth', models.IntegerField(choices=[(0, 'Training Started'), (1, 'Training Complete'), (2, 'Passed Out')])),
('date', models.DateTimeField()),
('notes', models.TextField()),
],
),
migrations.CreateModel(
name='TrainingLevel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('department', models.CharField(max_length=50, null=True)),
('level', models.IntegerField(choices=[(0, 'Technical Assistant'), (1, 'Technician'), (2, 'Supervisor')])),
],
bases=(models.Model, RIGS.models.RevisionMixin),
),
migrations.CreateModel(
name='TrainingLevelQualification',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('confirmed_on', models.DateTimeField()),
('confirmed_by', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='confirmer', to='training.trainee')),
('level', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='training.traininglevel')),
('trainee', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='levels', to='training.trainee')),
],
),
migrations.RemoveField(
model_name='supervisor',
name='department',
),
migrations.RemoveField(
model_name='supervisor',
name='requirements',
),
migrations.RemoveField(
model_name='technicalassistant',
name='requirements',
),
migrations.RemoveField(
model_name='technician',
name='department',
),
migrations.RemoveField(
model_name='technician',
name='requirements',
),
migrations.RemoveField(
model_name='trainingiteminstance',
name='item',
),
migrations.RemoveField(
model_name='trainingiteminstance',
name='passed_out_by',
),
migrations.RemoveField(
model_name='trainingiteminstance',
name='trainee',
),
migrations.RemoveField(
model_name='trainingiteminstance',
name='training_complete_by',
),
migrations.RemoveField(
model_name='trainingiteminstance',
name='training_started_by',
),
migrations.AlterField(
model_name='trainingitem',
name='category',
field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='training.trainingcategory'),
),
migrations.DeleteModel(
name='Department',
),
migrations.DeleteModel(
name='Supervisor',
),
migrations.DeleteModel(
name='TechnicalAssistant',
),
migrations.DeleteModel(
name='Technician',
),
migrations.DeleteModel(
name='TrainingItemInstance',
),
migrations.AddField(
model_name='trainingitemqualification',
name='item',
field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='training.trainingitem'),
),
migrations.AddField(
model_name='trainingitemqualification',
name='supervisor',
field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='training_started', to='training.trainee'),
),
migrations.AddField(
model_name='trainingitemqualification',
name='trainee',
field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='items', to='training.trainee'),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 3.1.5 on 2021-06-30 15:24
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('training', '0002_auto_20210630_1514'),
]
operations = [
migrations.AlterField(
model_name='trainingitem',
name='category',
field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='items', to='training.trainingcategory'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 3.1.5 on 2021-06-30 15:41
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('training', '0003_auto_20210630_1624'),
]
operations = [
migrations.RenameField(
model_name='trainingcategory',
old_name='number',
new_name='reference_number',
),
migrations.RenameField(
model_name='trainingitem',
old_name='number',
new_name='reference_number',
),
]

View File

64
training/models.py Normal file
View File

@@ -0,0 +1,64 @@
from django.db import models
from RIGS.models import RevisionMixin, Profile
from reversion import revisions as reversion
# 'shim' overtop the profile model to neatly contain all training related fields etc
class Trainee(Profile):
class Meta:
proxy = True
def get_records_of_depth(self, depth):
return self.training_items.filter(depth=depth)
# Items
class TrainingCategory(models.Model):
reference_number = models.CharField(max_length=3)
name = models.CharField(max_length=50)
class TrainingItem(models.Model):
reference_number = models.CharField(max_length=3)
category = models.ForeignKey('TrainingCategory', related_name='items', on_delete=models.RESTRICT)
name = models.CharField(max_length=50)
def __str__(self):
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
PASSED_OUT = 2
CHOICES = (
(STARTED, 'Training Started'),
(COMPLETE, 'Training Complete'),
(PASSED_OUT, 'Passed Out'),
)
item = models.ForeignKey('TrainingItem', on_delete=models.RESTRICT)
trainee = models.ForeignKey('Trainee', related_name='training_items', on_delete=models.RESTRICT)
depth = models.IntegerField(choices=CHOICES)
date = models.DateTimeField()
# TODO Remember that some training is external. Support for making an organisation the trainer?
supervisor = models.ForeignKey('Trainee', related_name='training_started', on_delete=models.RESTRICT)
notes = models.TextField()
# Levels
# FIXME Common Competencies...
class TrainingLevel(models.Model, RevisionMixin):
CHOICES = (
(0, 'Technical Assistant'),
(1, 'Technician'),
(2, 'Supervisor'),
)
department = models.CharField(max_length=50, null=True) # N.B. Technical Assistant does not have a department
level = models.IntegerField(choices=CHOICES)
class TrainingLevelQualification(models.Model):
trainee = models.ForeignKey('Trainee', related_name='levels', on_delete=models.RESTRICT)
level = models.ForeignKey('TrainingLevel', on_delete=models.RESTRICT)
confirmed_on = models.DateTimeField()
confirmed_by = models.ForeignKey('Trainee', related_name='confirmer', on_delete=models.RESTRICT)

View File

@@ -0,0 +1,18 @@
{% extends 'base_rigs.html' %}
{% block content %}
<div class="row">
{% for category in categories %}
<div class="col">
<div class="card mb-3">
<h4 class="card-header">{{ category.name }}</h4>
<div class="list-group list-group-flush">
{% for item in category.items.all %}
<li class="list-group-item">{{ item }}</li>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@@ -0,0 +1,12 @@
{% extends 'base_rigs.html' %}
{% block content %}
<div class="row">
<h1>Log New Training Session</h1>
<div class="form-group">
<label for="selectpicker">Select Attendees</label>
<select name="attendees" id="attendees_id" class="form-control selectpicker" data-live-search="true">
</select>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,29 @@
{% extends 'base_rigs.html' %}
{% block content %}
<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>
</div>
</div>
<div class="row">
<h2 class="col-12">Training Items</h2><br>
{% for category in categories %}
<div class="col-md-3">
<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>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}

15
training/urls.py Normal file
View File

@@ -0,0 +1,15 @@
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
urlpatterns = [
path('items/', views.ItemList.as_view(), name='item_list'),
path('trainee/', login_required(views.TraineeDetail.as_view()), name='trainee_detail'),
path('trainee/<int:pk>/',
permission_required_with_403('RIGS.view_profile')(views.TraineeDetail.as_view()),
name='trainee_detail'),
]

32
training/views.py Normal file
View File

@@ -0,0 +1,32 @@
from django.shortcuts import render
from django.views import generic
from training import models
from users import views
class ItemList(generic.ListView):
template_name = "item_list.html"
model = models.TrainingItem
def get_context_data(self, **kwargs):
context = super(ItemList, self).get_context_data(**kwargs)
context["page_title"] = "Training Items"
context["categories"] = models.TrainingCategory.objects.all()
return context
class TraineeDetail(views.ProfileDetail):
template_name = "trainee_detail.html"
model = models.Trainee
def get_context_data(self, **kwargs):
context = super(TraineeDetail, self).get_context_data(**kwargs)
context["page_title"] = "{}'s Training Record".format(self.object)
context["categories"] = models.TrainingCategory.objects.all()
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