From 5eb113156b585ba54f72e423be360421a86f95aa Mon Sep 17 00:00:00 2001 From: FreneticScribbler Date: Sun, 27 Feb 2022 21:20:34 +0000 Subject: [PATCH] FEAT(T): First version of the 'session log' form --- PyRIGS/views.py | 7 +++ RIGS/views/rigboard.py | 9 +--- training/admin.py | 6 ++- training/forms.py | 54 ++++++++++++++----- .../generate_sample_training_users.py | 2 +- training/models.py | 6 +++ training/templates/base_training.html | 3 ++ training/templates/edit_training_record.html | 7 +-- .../templates/partials/supervisor_field.html | 6 +++ training/templates/session_log_form.html | 39 ++++++++------ training/urls.py | 2 + training/views.py | 26 ++++++++- 12 files changed, 122 insertions(+), 45 deletions(-) create mode 100644 training/templates/partials/supervisor_field.html diff --git a/PyRIGS/views.py b/PyRIGS/views.py index dcf2d02c..1a47b7da 100644 --- a/PyRIGS/views.py +++ b/PyRIGS/views.py @@ -35,6 +35,13 @@ def is_ajax(request): 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 template_name = 'index.html' diff --git a/RIGS/views/rigboard.py b/RIGS/views/rigboard.py index 327c9088..1a146c47 100644 --- a/RIGS/views/rigboard.py +++ b/RIGS/views/rigboard.py @@ -21,7 +21,7 @@ from django.utils.decorators import method_decorator from django.views import generic 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 __author__ = 'ghost' @@ -77,13 +77,6 @@ class EventOEmbed(OEmbedView): 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): model = models.Event form_class = forms.EventForm diff --git a/training/admin.py b/training/admin.py index f465c509..80bf1e03 100644 --- a/training/admin.py +++ b/training/admin.py @@ -6,6 +6,10 @@ from reversion.admin import VersionAdmin admin.site.register(models.TrainingCategory, VersionAdmin) admin.site.register(models.TrainingItem, VersionAdmin) admin.site.register(models.TrainingLevel, VersionAdmin) -admin.site.register(models.TrainingItemQualification, VersionAdmin) admin.site.register(models.TrainingLevelQualification, VersionAdmin) admin.site.register(models.TrainingLevelRequirement, VersionAdmin) + + +@admin.register(models.TrainingItemQualification) +class TrainingItemQualificationAdmin(VersionAdmin): + list_display = ['__str__', 'trainee'] diff --git a/training/forms.py b/training/forms.py index 026513a3..b7234797 100644 --- a/training/forms.py +++ b/training/forms.py @@ -1,21 +1,24 @@ +import datetime + from django import forms from training import models 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 Meta: model = models.TrainingItemQualification 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): cleaned_data = super().clean() item = cleaned_data.get('item') @@ -34,13 +37,16 @@ class QualificationForm(forms.ModelForm): item = self.cleaned_data['item'] if supervisor.pk == self.cleaned_data['trainee'].pk: raise forms.ValidationError('One may not supervise oneself...') - 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...') + validate_user_can_train_in(supervisor, item) 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): depth = forms.ChoiceField(choices=models.TrainingItemQualification.CHOICES) @@ -53,3 +59,27 @@ class RequirementForm(forms.ModelForm): pk = kwargs.pop('pk', None) super().__init__(*args, **kwargs) 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 diff --git a/training/management/commands/generate_sample_training_users.py b/training/management/commands/generate_sample_training_users.py index a02abe2e..3d90a2ac 100644 --- a/training/management/commands/generate_sample_training_users.py +++ b/training/management/commands/generate_sample_training_users.py @@ -55,7 +55,7 @@ class Command(BaseCommand): supervisor = Profile.objects.create(username="supervisor", first_name="Super", last_name="Visor", initials="SV", 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.groups.add(Group.objects.get(name="Keyholders")) supervisor.save() diff --git a/training/models.py b/training/models.py index 0e8ec5dd..b7e086ca 100644 --- a/training/models.py +++ b/training/models.py @@ -1,5 +1,7 @@ +import datetime from RIGS.models import Profile, filter_by_pk from reversion import revisions as reversion +from django.core.exceptions import ValidationError from django.db import models from django.db.models import Q, F, Value, CharField from django.db.models.functions import Concat @@ -8,6 +10,7 @@ from django.utils.safestring import mark_safe from versioning.versioning import RevisionMixin from queryable_properties.properties import queryable_property from queryable_properties.managers import QueryablePropertiesManager +from django.utils.translation import gettext_lazy as _ class TraineeManager(models.Manager): @@ -351,6 +354,9 @@ class TrainingLevelQualification(models.Model, RevisionMixin): def activity_feed_string(self): return str(self) + def get_absolute_url(self): + return reverse('trainee_detail', kwargs={'pk': self.trainee.pk}) + class Meta: unique_together = ["trainee", "level"] ordering = ['-confirmed_on'] diff --git a/training/templates/base_training.html b/training/templates/base_training.html index 6f5e499e..56957302 100644 --- a/training/templates/base_training.html +++ b/training/templates/base_training.html @@ -30,6 +30,9 @@ Item List + {% if perms.training.add_trainingitemqualification or request.user.is_supervisor %} + + {% endif %} {% endif %} {% endblock %} diff --git a/training/templates/edit_training_record.html b/training/templates/edit_training_record.html index ccffa1b8..4536c133 100644 --- a/training/templates/edit_training_record.html +++ b/training/templates/edit_training_record.html @@ -41,12 +41,7 @@ {% render_field form.depth|add_class:'form-control custom-select col-sm-8' %}
- - + {% include 'partials/supervisor_field.html' %}
diff --git a/training/templates/partials/supervisor_field.html b/training/templates/partials/supervisor_field.html new file mode 100644 index 00000000..b0e07815 --- /dev/null +++ b/training/templates/partials/supervisor_field.html @@ -0,0 +1,6 @@ + + diff --git a/training/templates/session_log_form.html b/training/templates/session_log_form.html index 739ecf03..0cc7b429 100644 --- a/training/templates/session_log_form.html +++ b/training/templates/session_log_form.html @@ -1,4 +1,4 @@ -{% extends 'base_rigs.html' %} +{% extends 'base_training.html' %} {% load static %} {% load button from filters %} @@ -23,27 +23,34 @@ {% block content %}
-
+ + {% include 'form_errors.html' %} + {% csrf_token %}

People

-
- - +
+ {% include 'partials/supervisor_field.html' %}
-
- -

Training Items

-
- {% for depth in depths %} -
-

{{ depth.1 }}

- +
+
+ +
- {% endfor %} +
+ {% include 'partials/form_field.html' with field=form.depth %} +
+
+ {% include 'partials/form_field.html' with field=form.date %} +
+
+
+ {% include 'partials/form_field.html' with field=form.notes %}
{% button 'submit' %} diff --git a/training/urls.py b/training/urls.py index 2ebc1049..58760048 100644 --- a/training/urls.py +++ b/training/urls.py @@ -26,4 +26,6 @@ urlpatterns = [ path('trainee//level//confirm', login_required(views.ConfirmLevel.as_view()), name='confirm_level'), path('trainee//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'), ] diff --git a/training/views.py b/training/views.py index a75679d6..1f6b17e1 100644 --- a/training/views.py +++ b/training/views.py @@ -5,8 +5,9 @@ from django.views import generic from django.utils import timezone from django.db import transaction 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 users import views from reversion.views import RevisionMixin @@ -207,3 +208,26 @@ class ConfirmLevel(generic.RedirectView): reversion.add_to_revision(trainee) 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