FEAT(T): First version of the 'session log' form

This commit is contained in:
2022-02-27 21:20:34 +00:00
parent ab03ad081a
commit 5eb113156b
12 changed files with 122 additions and 45 deletions

View File

@@ -35,6 +35,13 @@ def is_ajax(request):
return request.headers.get('x-requested-with') == 'XMLHttpRequest' return request.headers.get('x-requested-with') == 'XMLHttpRequest'
def get_related(form, context): # Get some other objects to include in the form. Used when there are errors but also nice and quick.
for field, model in form.related_models.items():
value = form[field].value()
if value is not None and value != '':
context[field] = model.objects.get(pk=value)
class Index(generic.TemplateView): # Displays the current rig count along with a few other bits and pieces class Index(generic.TemplateView): # Displays the current rig count along with a few other bits and pieces
template_name = 'index.html' template_name = 'index.html'

View File

@@ -21,7 +21,7 @@ from django.utils.decorators import method_decorator
from django.views import generic from django.views import generic
from PyRIGS import decorators from PyRIGS import decorators
from PyRIGS.views import OEmbedView, is_ajax, ModalURLMixin, PrintView from PyRIGS.views import OEmbedView, is_ajax, ModalURLMixin, PrintView, get_related
from RIGS import models, forms from RIGS import models, forms
__author__ = 'ghost' __author__ = 'ghost'
@@ -77,13 +77,6 @@ class EventOEmbed(OEmbedView):
url_name = 'event_embed' url_name = 'event_embed'
def get_related(form, context): # Get some other objects to include in the form. Used when there are errors but also nice and quick.
for field, model in form.related_models.items():
value = form[field].value()
if value is not None and value != '':
context[field] = model.objects.get(pk=value)
class EventCreate(generic.CreateView): class EventCreate(generic.CreateView):
model = models.Event model = models.Event
form_class = forms.EventForm form_class = forms.EventForm

View File

@@ -6,6 +6,10 @@ from reversion.admin import 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)
admin.site.register(models.TrainingItemQualification, VersionAdmin)
admin.site.register(models.TrainingLevelQualification, VersionAdmin) admin.site.register(models.TrainingLevelQualification, VersionAdmin)
admin.site.register(models.TrainingLevelRequirement, VersionAdmin) admin.site.register(models.TrainingLevelRequirement, VersionAdmin)
@admin.register(models.TrainingItemQualification)
class TrainingItemQualificationAdmin(VersionAdmin):
list_display = ['__str__', 'trainee']

View File

@@ -1,21 +1,24 @@
import datetime
from django import forms from django import forms
from training import models from training import models
from RIGS.models import Profile from RIGS.models import Profile
def validate_user_can_train_in(supervisor, item):
if item.category.training_level:
if not supervisor.level_qualifications.filter(level=item.category.training_level):
raise forms.ValidationError('Selected supervising person is missing requisite training level to train in this department')
elif not supervisor.is_supervisor:
raise forms.ValidationError('Selected supervisor must actually *be* a supervisor...')
class QualificationForm(forms.ModelForm): class QualificationForm(forms.ModelForm):
class Meta: class Meta:
model = models.TrainingItemQualification model = models.TrainingItemQualification
fields = '__all__' fields = '__all__'
def __init__(self, *args, **kwargs):
pk = kwargs.pop('pk', None)
super().__init__(*args, **kwargs)
if pk:
self.fields['trainee'].initial = Profile.objects.get(pk=pk)
self.fields['date'].widget.format = '%Y-%m-%d'
def clean(self): def clean(self):
cleaned_data = super().clean() cleaned_data = super().clean()
item = cleaned_data.get('item') item = cleaned_data.get('item')
@@ -34,13 +37,16 @@ class QualificationForm(forms.ModelForm):
item = self.cleaned_data['item'] item = self.cleaned_data['item']
if supervisor.pk == self.cleaned_data['trainee'].pk: if supervisor.pk == self.cleaned_data['trainee'].pk:
raise forms.ValidationError('One may not supervise oneself...') raise forms.ValidationError('One may not supervise oneself...')
if item.category.training_level: validate_user_can_train_in(supervisor, item)
if not supervisor.level_qualifications.filter(level=item.category.training_level):
raise forms.ValidationError('Selected supervising person is missing requisite training level to train in this department')
elif not supervisor.is_supervisor:
raise forms.ValidationError('Selected supervisor must actually *be* a supervisor...')
return supervisor return supervisor
def __init__(self, *args, **kwargs):
pk = kwargs.pop('pk', None)
super().__init__(*args, **kwargs)
if pk:
self.fields['trainee'].initial = Profile.objects.get(pk=pk)
self.fields['date'].widget.format = '%Y-%m-%d'
class RequirementForm(forms.ModelForm): class RequirementForm(forms.ModelForm):
depth = forms.ChoiceField(choices=models.TrainingItemQualification.CHOICES) depth = forms.ChoiceField(choices=models.TrainingItemQualification.CHOICES)
@@ -53,3 +59,27 @@ class RequirementForm(forms.ModelForm):
pk = kwargs.pop('pk', None) pk = kwargs.pop('pk', None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['level'].initial = models.TrainingLevel.objects.get(pk=pk) self.fields['level'].initial = models.TrainingLevel.objects.get(pk=pk)
class SessionLogForm(forms.Form):
trainees = forms.ModelMultipleChoiceField(models.Trainee.objects.all())
items = forms.ModelMultipleChoiceField(models.TrainingItem.objects.all())
depth = forms.ChoiceField(choices=models.TrainingItemQualification.CHOICES)
supervisor = forms.ModelChoiceField(models.Trainee.objects.all())
date = forms.DateField(initial=datetime.date.today)
notes = forms.CharField(required=False, widget=forms.Textarea)
related_models = {
'supervisor': models.Trainee
}
def clean_date(self):
return QualificationForm.clean_date(self)
def clean_supervisor(self):
supervisor = self.cleaned_data['supervisor']
if supervisor in self.cleaned_data.get('trainees', []):
raise forms.ValidationError('One may not supervise oneself...')
for item in self.cleaned_data.get('items', []):
validate_user_can_train_in(supervisor, item)
return supervisor

View File

@@ -55,7 +55,7 @@ class Command(BaseCommand):
supervisor = Profile.objects.create(username="supervisor", first_name="Super", last_name="Visor", supervisor = Profile.objects.create(username="supervisor", first_name="Super", last_name="Visor",
initials="SV", initials="SV",
email="supervisor@example.com", is_active=True, email="supervisor@example.com", is_active=True,
is_staff=True, is_approved=True) is_staff=True, is_approved=True, is_supervisor=True)
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()

View File

@@ -1,5 +1,7 @@
import datetime
from RIGS.models import Profile, filter_by_pk from RIGS.models import Profile, filter_by_pk
from reversion import revisions as reversion from reversion import revisions as reversion
from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.db.models import Q, F, Value, CharField from django.db.models import Q, F, Value, CharField
from django.db.models.functions import Concat from django.db.models.functions import Concat
@@ -8,6 +10,7 @@ from django.utils.safestring import mark_safe
from versioning.versioning import RevisionMixin from versioning.versioning import RevisionMixin
from queryable_properties.properties import queryable_property from queryable_properties.properties import queryable_property
from queryable_properties.managers import QueryablePropertiesManager from queryable_properties.managers import QueryablePropertiesManager
from django.utils.translation import gettext_lazy as _
class TraineeManager(models.Manager): class TraineeManager(models.Manager):
@@ -351,6 +354,9 @@ class TrainingLevelQualification(models.Model, RevisionMixin):
def activity_feed_string(self): def activity_feed_string(self):
return str(self) return str(self)
def get_absolute_url(self):
return reverse('trainee_detail', kwargs={'pk': self.trainee.pk})
class Meta: class Meta:
unique_together = ["trainee", "level"] unique_together = ["trainee", "level"]
ordering = ['-confirmed_on'] ordering = ['-confirmed_on']

View File

@@ -30,6 +30,9 @@
<a class="dropdown-item" href="{% url 'item_list' %}"><span class="fas fa-sitemap"></span> Item List</a> <a class="dropdown-item" href="{% url 'item_list' %}"><span class="fas fa-sitemap"></span> Item List</a>
</div> </div>
</li> </li>
{% if perms.training.add_trainingitemqualification or request.user.is_supervisor %}
<li class="nav-item"><a class="nav-link" href="{% url 'session_log' %}"><span class="fas fa-users"></span> Log Session</a></li>
{% endif %}
<li class="nav-item"><a class="nav-link" href="{% url 'training_activity_table' %}"><span class="fas fa-random"></span> Recent Changes</a></li> <li class="nav-item"><a class="nav-link" href="{% url 'training_activity_table' %}"><span class="fas fa-random"></span> Recent Changes</a></li>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@@ -41,12 +41,7 @@
{% render_field form.depth|add_class:'form-control custom-select col-sm-8' %} {% render_field form.depth|add_class:'form-control custom-select col-sm-8' %}
</div> </div>
<div class="form-group form-row"> <div class="form-group form-row">
<label for="supervisor" class="col-sm-2 col-form-label">Supervisor</label> {% include 'partials/supervisor_field.html' %}
<select name="supervisor" id="supervisor_id" class="selectpicker px-0 col-sm-10" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" required>
{% if object.supervisor %}
<option value="{{object.supervisor.pk}}" selected>{{object.supervisor}}</option>
{% endif %}
</select>
</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>

View File

@@ -0,0 +1,6 @@
<label for="supervisor" class="col-sm-2 col-form-label">Supervisor</label>
<select name="supervisor" id="supervisor_id" class="selectpicker col-sm-10" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" required>
{% if supervisor %}
<option value="{{form.supervisor.value}}" selected="selected">{{ supervisor }}</option>
{% endif %}
</select>

View File

@@ -1,4 +1,4 @@
{% extends 'base_rigs.html' %} {% extends 'base_training.html' %}
{% load static %} {% load static %}
{% load button from filters %} {% load button from filters %}
@@ -23,27 +23,34 @@
{% block content %} {% block content %}
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<form class="form"> <form class="form" method="POST" id="session_form" action="{% url 'session_log' %}">
{% include 'form_errors.html' %}
{% csrf_token %}
<h3>People</h3> <h3>People</h3>
<div class="form-group"> <div class="form-group row">
<label for="selectpicker">Select Supervisor</label> {% include 'partials/supervisor_field.html' %}
<select name="supervisor" id="supervisor_id" class="form-control selectpicker custom-select" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
</select>
</div> </div>
<div class="form-group"> <div class="form-group row">
<label for="selectpicker">Select Attendees</label> <label for="trainees_id" class="col-sm-2">Select Attendees</label>
<select multiple name="attendees" id="attendees_id" class="form-control selectpicker custom-select" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials"> <select multiple name="trainees" id="trainees_id" class="selectpicker col-sm-10" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
</select> </select>
</div> </div>
<h3>Training Items</h3> <h3>Training Items</h3>
<div class="row"> <div class="row px-3">
{% for depth in depths %} <div class="form-group">
<div class="col"> <label for="selectpicker">Training Items</label>
<h4>{{ depth.1 }}</h4> <select multiple name="items" id="items_id" class="selectpicker col-12 px-0" data-live-search="true" data-sourceurl="{% url 'api_secure' model='training_item' %}?fields=description,reference_number&filters=active">
<select multiple name="{{ depth.0 }}" id="{{ depth.0 }}_id" class="form-control selectpicker custom-select" data-live-search="true" data-sourceurl="{% url 'api_secure' model='training_item' %}"> </select>
</select>
</div> </div>
{% endfor %} <div class="form-group pl-3">
{% include 'partials/form_field.html' with field=form.depth %}
</div>
<div class="form-group pl-3">
{% include 'partials/form_field.html' with field=form.date %}
</div>
</div>
<div class="form-group">
{% include 'partials/form_field.html' with field=form.notes %}
</div> </div>
<div class="col-sm-12 text-right my-3"> <div class="col-sm-12 text-right my-3">
{% button 'submit' %} {% button 'submit' %}

View File

@@ -26,4 +26,6 @@ urlpatterns = [
path('trainee/<int:pk>/level/<int:level_pk>/confirm', login_required(views.ConfirmLevel.as_view()), name='confirm_level'), path('trainee/<int:pk>/level/<int:level_pk>/confirm', login_required(views.ConfirmLevel.as_view()), name='confirm_level'),
path('trainee/<int:pk>/item_record', login_required(views.TraineeItemDetail.as_view()), name='trainee_item_detail'), path('trainee/<int:pk>/item_record', login_required(views.TraineeItemDetail.as_view()), name='trainee_item_detail'),
path('session_log', has_perm_or_supervisor('training.add_trainingitemqualification')(views.SessionLog.as_view()), name='session_log'),
] ]

View File

@@ -5,8 +5,9 @@ from django.views import generic
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
from django.db.utils import IntegrityError
from PyRIGS.views import is_ajax, ModalURLMixin from PyRIGS.views import is_ajax, ModalURLMixin, get_related
from training import models, forms from training import models, forms
from users import views from users import views
from reversion.views import RevisionMixin from reversion.views import RevisionMixin
@@ -207,3 +208,26 @@ class ConfirmLevel(generic.RedirectView):
reversion.add_to_revision(trainee) reversion.add_to_revision(trainee)
return reverse_lazy('trainee_detail', kwargs={'pk': kwargs['pk']}) return reverse_lazy('trainee_detail', kwargs={'pk': kwargs['pk']})
class SessionLog(generic.FormView):
template_name = 'session_log_form.html'
form_class = forms.SessionLogForm
success_url = reverse_lazy('trainee_list')
def form_valid(self, form, *args, **kwargs):
for trainee in form.cleaned_data.get('trainees'):
for item in form.cleaned_data.get('items'):
try:
with transaction.atomic():
models.TrainingItemQualification.objects.create(trainee=trainee, item=item, supervisor=form.cleaned_data.get('supervisor'), depth=form.cleaned_data.get('depth'), notes=form.cleaned_data.get('notes'), date=form.cleaned_data.get('date'))
reversion.add_to_revision(trainee)
except IntegrityError:
pass # There was an attempt to create a duplicate qualification, ignore it
return super().form_valid(form, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_title"] = "Log Training Session"
get_related(context['form'], context)
return context