mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-01-16 21:12:13 +00:00
FEAT(T): First version of the 'session log' form
This commit is contained in:
@@ -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'
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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']
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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']
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
6
training/templates/partials/supervisor_field.html
Normal file
6
training/templates/partials/supervisor_field.html
Normal 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>
|
||||||
@@ -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' %}
|
||||||
|
|||||||
@@ -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'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user