mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-04-16 15:11:46 +00:00
Compare commits
5 Commits
3b9848d457
...
3ae507b469
| Author | SHA1 | Date | |
|---|---|---|---|
|
3ae507b469
|
|||
|
33754eed60
|
|||
|
15ab626593
|
|||
|
7bc47b446c
|
|||
|
83b287a418
|
@@ -8,6 +8,7 @@ from django.db.models import Count
|
|||||||
from django.forms import ModelForm
|
from django.forms import ModelForm
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.db import IntegrityError
|
||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
from reversion.admin import VersionAdmin
|
from reversion.admin import VersionAdmin
|
||||||
|
|
||||||
@@ -21,45 +22,11 @@ admin.site.register(models.EventItem, VersionAdmin)
|
|||||||
admin.site.register(models.Invoice, VersionAdmin)
|
admin.site.register(models.Invoice, VersionAdmin)
|
||||||
|
|
||||||
|
|
||||||
def approve_user(modeladmin, request, queryset):
|
|
||||||
queryset.update(is_approved=True)
|
|
||||||
|
|
||||||
|
|
||||||
approve_user.short_description = "Approve selected users"
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Profile)
|
|
||||||
class ProfileAdmin(UserAdmin):
|
|
||||||
# Don't know how to add 'is_approved' whilst preserving the default list...
|
|
||||||
list_filter = ('is_approved', 'is_active', 'is_staff', 'is_superuser', 'groups')
|
|
||||||
fieldsets = (
|
|
||||||
(None, {'fields': ('username', 'password')}),
|
|
||||||
(_('Personal info'), {
|
|
||||||
'fields': ('first_name', 'last_name', 'email', 'initials', 'phone')}),
|
|
||||||
(_('Permissions'), {'fields': ('is_approved', 'is_active', 'is_staff', 'is_superuser',
|
|
||||||
'groups', 'user_permissions')}),
|
|
||||||
(_('Important dates'), {
|
|
||||||
'fields': ('last_login', 'date_joined')}),
|
|
||||||
)
|
|
||||||
add_fieldsets = (
|
|
||||||
(None, {
|
|
||||||
'classes': ('wide',),
|
|
||||||
'fields': ('username', 'password1', 'password2'),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
form = user_forms.ProfileChangeForm
|
|
||||||
add_form = user_forms.ProfileCreationForm
|
|
||||||
actions = [approve_user]
|
|
||||||
|
|
||||||
|
|
||||||
class AssociateAdmin(VersionAdmin):
|
class AssociateAdmin(VersionAdmin):
|
||||||
list_display = ('id', 'name', 'number_of_events')
|
|
||||||
search_fields = ['id', 'name']
|
search_fields = ['id', 'name']
|
||||||
list_display_links = ['id', 'name']
|
list_display_links = ['id', 'name']
|
||||||
actions = ['merge']
|
actions = ['merge']
|
||||||
|
|
||||||
merge_fields = ['name']
|
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
return super(AssociateAdmin, self).get_queryset(request).annotate(event_count=Count('event'))
|
return super(AssociateAdmin, self).get_queryset(request).annotate(event_count=Count('event'))
|
||||||
|
|
||||||
@@ -71,17 +38,37 @@ class AssociateAdmin(VersionAdmin):
|
|||||||
def merge(self, request, queryset):
|
def merge(self, request, queryset):
|
||||||
if request.POST.get('post'): # Has the user confirmed which is the master record?
|
if request.POST.get('post'): # Has the user confirmed which is the master record?
|
||||||
try:
|
try:
|
||||||
masterObjectPk = request.POST.get('master')
|
master_object_pk = request.POST.get('master')
|
||||||
masterObject = queryset.get(pk=masterObjectPk)
|
master_object = queryset.get(pk=master_object_pk)
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
self.message_user(request, "An error occured. Did you select a 'master' record?", level=messages.ERROR)
|
self.message_user(request, "An error occured. Did you select a 'master' record?", level=messages.ERROR)
|
||||||
return
|
return
|
||||||
|
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
for obj in queryset.exclude(pk=masterObjectPk):
|
for obj in queryset.exclude(pk=master_object_pk):
|
||||||
events = obj.event_set.all()
|
# If we're merging profiles, merge their training information
|
||||||
for event in events:
|
if hasattr(obj, 'event_mic'):
|
||||||
masterObject.event_set.add(event)
|
events = obj.event_mic.all()
|
||||||
|
for event in events:
|
||||||
|
master_object.event_mic.add(event)
|
||||||
|
for qual in obj.qualifications_obtained.all():
|
||||||
|
try:
|
||||||
|
with transaction.atomic():
|
||||||
|
master_object.qualifications_obtained.add(qual)
|
||||||
|
except IntegrityError:
|
||||||
|
existing_qual = master_object.qualifications_obtained.get(item=qual.item, depth=qual.depth)
|
||||||
|
existing_qual.notes += qual.notes
|
||||||
|
existing_qual.save()
|
||||||
|
for level in obj.level_qualifications.all():
|
||||||
|
try:
|
||||||
|
with transaction.atomic():
|
||||||
|
master_object.level_qualifications.add(level)
|
||||||
|
except IntegrityError:
|
||||||
|
continue # Exists, oh well
|
||||||
|
else:
|
||||||
|
events = obj.event_set.all()
|
||||||
|
for event in events:
|
||||||
|
master_object.event_set.add(event)
|
||||||
obj.delete()
|
obj.delete()
|
||||||
reversion.set_comment('Merging Objects')
|
reversion.set_comment('Merging Objects')
|
||||||
|
|
||||||
@@ -107,6 +94,35 @@ class AssociateAdmin(VersionAdmin):
|
|||||||
return TemplateResponse(request, 'admin_associate_merge.html', context)
|
return TemplateResponse(request, 'admin_associate_merge.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.Profile)
|
||||||
|
class ProfileAdmin(UserAdmin, AssociateAdmin):
|
||||||
|
list_display = ('username', 'name', 'is_approved', 'is_staff', 'is_superuser', 'is_supervisor', 'number_of_events')
|
||||||
|
list_display_links = ['username']
|
||||||
|
fieldsets = (
|
||||||
|
(None, {'fields': ('username', 'password')}),
|
||||||
|
(_('Personal info'), {
|
||||||
|
'fields': ('first_name', 'last_name', 'email', 'initials', 'phone')}),
|
||||||
|
(_('Permissions'), {'fields': ('is_approved', 'is_active', 'is_staff', 'is_superuser',
|
||||||
|
'groups', 'user_permissions')}),
|
||||||
|
(_('Important dates'), {
|
||||||
|
'fields': ('last_login', 'date_joined')}),
|
||||||
|
)
|
||||||
|
add_fieldsets = (
|
||||||
|
(None, {
|
||||||
|
'classes': ('wide',),
|
||||||
|
'fields': ('username', 'password1', 'password2'),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
form = user_forms.ProfileChangeForm
|
||||||
|
add_form = user_forms.ProfileCreationForm
|
||||||
|
actions = ['approve_user', 'merge']
|
||||||
|
|
||||||
|
merge_fields = ['username', 'first_name', 'last_name', 'initials', 'email', 'phone', 'is_supervisor']
|
||||||
|
|
||||||
|
def approve_user(modeladmin, request, queryset):
|
||||||
|
queryset.update(is_approved=True)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Person)
|
@admin.register(models.Person)
|
||||||
class PersonAdmin(AssociateAdmin):
|
class PersonAdmin(AssociateAdmin):
|
||||||
list_display = ('id', 'name', 'phone', 'email', 'number_of_events')
|
list_display = ('id', 'name', 'phone', 'email', 'number_of_events')
|
||||||
|
|||||||
@@ -106,7 +106,7 @@
|
|||||||
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
|
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
|
||||||
<setFont name="OpenSans" size="7" />
|
<setFont name="OpenSans" size="7" />
|
||||||
<drawCenteredString x="302.5" y="26">
|
<drawCenteredString x="302.5" y="26">
|
||||||
[Paperwork generated{% if current_user %} by {{current_user.name}} |{% endif %} {% now "d/m/Y H:i" %} | {{object.current_version_id}}]
|
{{info_string}}
|
||||||
</drawCenteredString>
|
</drawCenteredString>
|
||||||
</pageGraphics>
|
</pageGraphics>
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
|
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
|
||||||
<setFont name="OpenSans" size="7" />
|
<setFont name="OpenSans" size="7" />
|
||||||
<drawCenteredString x="302.5" y="26">
|
<drawCenteredString x="302.5" y="26">
|
||||||
[Paperwork generated{% if current_user %} by {{current_user.name}} |{% endif %} {% now "d/m/Y H:i" %} | {{object.current_version_id}}]
|
{{info_string}}
|
||||||
</drawCenteredString>
|
</drawCenteredString>
|
||||||
</pageGraphics>
|
</pageGraphics>
|
||||||
<frame id="main" x1="50" y1="65" width="495" height="727"/>
|
<frame id="main" x1="50" y1="65" width="495" height="727"/>
|
||||||
|
|||||||
@@ -185,11 +185,15 @@ class EventPrint(generic.View):
|
|||||||
|
|
||||||
merger = PdfFileMerger()
|
merger = PdfFileMerger()
|
||||||
|
|
||||||
|
user_str = f"by {request.user.name} " if request.user is not None else ""
|
||||||
|
time = timezone.now().strftime('%d/%m/%Y %H:%I')
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'object': object,
|
'object': object,
|
||||||
'quote': True,
|
'quote': True,
|
||||||
'current_user': request.user,
|
'current_user': request.user,
|
||||||
'filename': 'Event_{}_{}_{}.pdf'.format(object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name), object.start_date)
|
'filename': 'Event_{}_{}_{}.pdf'.format(object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name), object.start_date),
|
||||||
|
'info_string': f"[Paperwork generated {user_str}on {time} - {object.current_version_id}]",
|
||||||
}
|
}
|
||||||
|
|
||||||
rml = template.render(context)
|
rml = template.render(context)
|
||||||
|
|||||||
@@ -23,9 +23,13 @@ class QualificationForm(forms.ModelForm):
|
|||||||
|
|
||||||
def clean_supervisor(self):
|
def clean_supervisor(self):
|
||||||
supervisor = self.cleaned_data['supervisor']
|
supervisor = self.cleaned_data['supervisor']
|
||||||
|
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 not supervisor.is_supervisor:
|
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...')
|
raise forms.ValidationError('Selected supervisor must actually *be* a supervisor...')
|
||||||
return supervisor
|
return supervisor
|
||||||
|
|
||||||
|
|||||||
19
training/migrations/0003_trainingcategory_training_level.py
Normal file
19
training/migrations/0003_trainingcategory_training_level.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 3.2.11 on 2022-01-25 12:58
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('training', '0002_alter_traininglevel_options'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='trainingcategory',
|
||||||
|
name='training_level',
|
||||||
|
field=models.ForeignKey(help_text='If this is set, any user with the selected level may pass out users within this category, regardless of other status', null=True, on_delete=django.db.models.deletion.CASCADE, to='training.traininglevel'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -6,31 +6,42 @@ from django.utils.safestring import mark_safe
|
|||||||
from versioning.versioning import RevisionMixin
|
from versioning.versioning import RevisionMixin
|
||||||
|
|
||||||
|
|
||||||
|
class TraineeManager(models.Manager):
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().filter(is_active=True, is_approved=True)
|
||||||
|
|
||||||
|
|
||||||
@reversion.register(for_concrete_model=False, fields=[])
|
@reversion.register(for_concrete_model=False, fields=[])
|
||||||
class Trainee(Profile, RevisionMixin):
|
class Trainee(Profile, RevisionMixin):
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
|
|
||||||
|
objects = TraineeManager()
|
||||||
|
|
||||||
# FIXME use queryset
|
# FIXME use queryset
|
||||||
def started_levels(self):
|
def started_levels(self):
|
||||||
return [level for level in TrainingLevel.objects.all() if level.percentage_complete(self) > 0 and level.pk not in self.level_qualifications.values_list('level', flat=True)]
|
return [level for level in TrainingLevel.objects.all() if level.percentage_complete(self) > 0 and level.pk not in self.level_qualifications.values_list('level', flat=True)]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def confirmed_levels(self):
|
||||||
|
return self.level_qualifications.exclude(confirmed_on=None).select_related('level')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_technician(self):
|
def is_technician(self):
|
||||||
return self.level_qualifications.exclude(confirmed_on=None).select_related('level') \
|
return self.confirmed_levels \
|
||||||
.filter(level__level=TrainingLevel.TECHNICIAN) \
|
.filter(level__level=TrainingLevel.TECHNICIAN) \
|
||||||
.exclude(level__department=TrainingLevel.HAULAGE) \
|
.exclude(level__department=TrainingLevel.HAULAGE) \
|
||||||
.exclude(level__department__isnull=True).exists()
|
.exclude(level__department__isnull=True).exists()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_driver(self):
|
def is_driver(self):
|
||||||
return self.level_qualifications.all().exclude(confirmed_on=None).select_related('level').filter(level__department=TrainingLevel.HAULAGE).exists()
|
return self.confirmed_levels.filter(level__department=TrainingLevel.HAULAGE).exists()
|
||||||
|
|
||||||
def get_records_of_depth(self, depth):
|
def get_records_of_depth(self, depth):
|
||||||
return self.qualifications_obtained.filter(depth=depth).select_related('item', 'trainee', 'supervisor')
|
return self.qualifications_obtained.filter(depth=depth).select_related('item', 'trainee', 'supervisor')
|
||||||
|
|
||||||
def is_user_qualified_in(self, item, required_depth):
|
def is_user_qualified_in(self, item, required_depth):
|
||||||
return self.qualifications_obtained.values('item', 'depth').filter(item=item).filter(depth__gte=required_depth).first() is not None # this is a somewhat ghetto version of get_or_none
|
return self.qualifications_obtained.values('item', 'depth').filter(item=item).filter(depth__gte=required_depth).exists()
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('trainee_detail', kwargs={'pk': self.pk})
|
return reverse('trainee_detail', kwargs={'pk': self.pk})
|
||||||
@@ -47,6 +58,7 @@ class Trainee(Profile, RevisionMixin):
|
|||||||
class TrainingCategory(models.Model):
|
class TrainingCategory(models.Model):
|
||||||
reference_number = models.IntegerField(unique=True)
|
reference_number = models.IntegerField(unique=True)
|
||||||
name = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
|
training_level = models.ForeignKey('TrainingLevel', on_delete=models.CASCADE, null=True, help_text="If this is set, any user with the selected level may pass out users within this category, regardless of other status")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.reference_number}. {self.name}"
|
return f"{self.reference_number}. {self.name}"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
</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>
|
<label for="supervisor" class="col-sm-2 col-form-label">Supervisor</label>
|
||||||
<select name="supervisor" id="supervisor_id" class="form-control selectpicker custom-select col-sm-10" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials&filters=is_supervisor" required>
|
<select name="supervisor" id="supervisor_id" class="form-control selectpicker custom-select col-sm-10" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" required>
|
||||||
{% if object.supervisor %}
|
{% if object.supervisor %}
|
||||||
<option value="{{object.supervisor.pk}}" selected>{{object.supervisor}}</option>
|
<option value="{{object.supervisor.pk}}" selected>{{object.supervisor}}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -16,7 +16,17 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% include 'partials/list_search.html' %}
|
<form method="GET" class="ml-auto w-25 d-flex flex-column justify-content-end">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="search" name="q" placeholder="Search" value="{{ request.GET.q }}"
|
||||||
|
class="form-control" id="id_search_text"/>
|
||||||
|
<span class="input-group-append">{% button 'search' id="id_search" %}</span>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary mt-2 {% if request.GET.is_supervisor %}active{%endif%}" data-toggle="button" aria-pressed="{% if request.GET.is_supervisor %}true{%endif%}" name="is_supervisor" value="{% if request.GET.is_supervisor %}{% else %}True{% endif %}">
|
||||||
|
Only Supervisors
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
<div class="row pt-2">
|
<div class="row pt-2">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
|
|||||||
@@ -108,6 +108,9 @@ class TraineeList(generic.ListView):
|
|||||||
# not an integer
|
# not an integer
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if self.request.GET.get('is_supervisor', ''):
|
||||||
|
filt = filt & Q(is_supervisor=True)
|
||||||
|
|
||||||
return self.model.objects.filter(filt).annotate(num_qualifications=Count('qualifications_obtained')).order_by('-num_qualifications').prefetch_related('level_qualifications', 'qualifications_obtained', 'qualifications_obtained__item')
|
return self.model.objects.filter(filt).annotate(num_qualifications=Count('qualifications_obtained')).order_by('-num_qualifications').prefetch_related('level_qualifications', 'qualifications_obtained', 'qualifications_obtained__item')
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ from reversion.models import Version, VersionQuerySet
|
|||||||
class RevisionMixin:
|
class RevisionMixin:
|
||||||
@property
|
@property
|
||||||
def is_first_version(self):
|
def is_first_version(self):
|
||||||
versions = Version.objects.get_for_object(self)
|
versions = RIGSVersion.objects.get_for_object(self)
|
||||||
return len(versions) == 1
|
return len(versions) == 1
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_version(self):
|
def current_version(self):
|
||||||
version = Version.objects.get_for_object(self).select_related('revision').first()
|
version = RIGSVersion.objects.get_for_object(self).select_related('revision').first()
|
||||||
return version
|
return version
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
Reference in New Issue
Block a user