Compare commits

..

4 Commits

9 changed files with 54 additions and 39 deletions

View File

@@ -27,6 +27,7 @@ def admin_user(admin_user):
admin_user.first_name = "Event" admin_user.first_name = "Event"
admin_user.last_name = "Test" admin_user.last_name = "Test"
admin_user.initials = "ETU" admin_user.initials = "ETU"
admin_user.is_approved = True
admin_user.save() admin_user.save()
return admin_user return admin_user

View File

@@ -77,13 +77,13 @@
border-collapse: separate !important; border-collapse: separate !important;
border-spacing: 0; border-spacing: 0;
} }
.table tr th { #event_table tr th {
border-right: 0 !important; border-right: 0 !important;
} }
.table tr td { #event_table tr td {
border-left: 0 !important; border-left: 0 !important;
} }
.table tr td:not(:last-child) { #event_table tr td:not(:last-child) {
border-right: 0 !important; border-right: 0 !important;
} }
@each $color, $value in $theme-colors { @each $color, $value in $theme-colors {

View File

@@ -16,8 +16,9 @@
<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>
<a class="list-group-item list-group-item-action" href="{% url 'web_calendar' %}"><span class="fas fa-calendar align-middle"></span><span class="align-middle"> Calendar</span></a> <a class="list-group-item list-group-item-action" href="{% url 'web_calendar' %}"><span class="fas fa-calendar align-middle"></span><span class="align-middle"> Calendar</span></a>
{% if perms.RIGS.add_event %} {% if perms.RIGS.add_event %}
<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 text-success"></span><span class="align-middle"> New Event</span></a>
{% endif %} {% endif %}
<a class="list-group-item list-group-item-action" href="{% url 'event_archive' %}"><span class="fas fa-book align-middle"></span><span class="align-middle"> Event Archive</span></a>
</div> </div>
</div> </div>
</div> </div>
@@ -29,11 +30,11 @@
<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>
{% if perms.assets.add_asset %} {% if perms.assets.add_asset %}
<a class="list-group-item list-group-item-action" href="{% url 'asset_create' %}"><span class="fas fa-plus align-middle"></span><span class="align-middle"> New Asset</span></a> <a class="list-group-item list-group-item-action" href="{% url 'asset_create' %}"><span class="fas fa-plus align-middle text-success"></span><span class="align-middle"> New Asset</span></a>
{% endif %} {% endif %}
<a class="list-group-item list-group-item-action" href="{% url 'supplier_list' %}"><span class="fas fa-parachute-box align-middle"></span><span class="align-middle"> Supplier List</span></a> <a class="list-group-item list-group-item-action" href="{% url 'supplier_list' %}"><span class="fas fa-parachute-box align-middle"></span><span class="align-middle"> Supplier List</span></a>
{% if perms.assets.add_supplier %} {% if perms.assets.add_supplier %}
<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 text-success"></span><span class="align-middle"> New Supplier</span></a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@@ -43,10 +44,10 @@
<img class="card-img-top d-none d-sm-block" src="{% static 'imgs/training.jpg' %}" alt="People watching a presentation" style="height: 150px; object-fit: cover;"> <img class="card-img-top d-none d-sm-block" src="{% static 'imgs/training.jpg' %}" alt="People watching a presentation" style="height: 150px; object-fit: cover;">
<h4 class="card-header">Training Database</h4> <h4 class="card-header">Training 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 text-info" href="{% url 'trainee_detail' request.user.pk %}"><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 'trainee_detail' request.user.pk %}"><span class="fas fa-file-signature align-middle text-info"></span><span class="align-middle"> My Training Record</span></a>
<a class="list-group-item list-group-item-action" href="{% url 'trainee_list' %}"><span class="fas fa-users"></span> Trainee List</a> <a class="list-group-item list-group-item-action" href="{% url 'trainee_list' %}"><span class="fas fa-users"></span><span class="align-middle"> Trainee List</span></a>
<a class="list-group-item list-group-item-action" href="{% url 'level_list' %}"><span class="fas fa-layer-group"></span> Level List</a></a> <a class="list-group-item list-group-item-action" href="{% url 'level_list' %}"><span class="fas fa-layer-group"></span> <span class="align-middle">Level List</span></a>
<a class="list-group-item list-group-item-action" href="{% url 'item_list' %}"><span class="fas fa-sitemap"></span> Item List</a></a> <a class="list-group-item list-group-item-action" href="{% url 'item_list' %}"><span class="fas fa-sitemap"></span> <span class="align-middle">Item List</span></a>
</div> </div>
</div> </div>
</div> </div>
@@ -57,6 +58,7 @@
<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-primary 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-primary align-middle"></span><span class="align-middle"> TEC Forum</span></a>
<a class="list-group-item list-group-item-action" href="//nottinghamtec.sharepoint.com" target="_blank" rel="noopener noreferrer"><span class="fas fa-folder text-info align-middle"></span><span class="align-middle"> TEC Sharepoint</span></a> <a class="list-group-item list-group-item-action" href="//nottinghamtec.sharepoint.com" target="_blank" rel="noopener noreferrer"><span class="fas fa-folder text-info align-middle"></span><span class="align-middle"> TEC Sharepoint</span></a>
<a class="list-group-item list-group-item-action" href="//wiki.nottinghamtec.co.uk" target="_blank" rel="noopener noreferrer"><span class="fas fa-pen-square align-middle"></span><span class="align-middle"> TEC Wiki</span></a> <a class="list-group-item list-group-item-action" href="//wiki.nottinghamtec.co.uk" target="_blank" rel="noopener noreferrer"><span class="fas fa-pen-square align-middle"></span><span class="align-middle"> TEC Wiki</span></a>
<a class="list-group-item list-group-item-action" href="https://secure.jotformeu.com/UoNSU/accident_report_form?studentGroup=Media+or+Service+Group&mediaserviceGroup=TEC" target="_blank" rel="noopener noreferrer"><span class="fas fa-heartbeat align-middle text-danger"></span><span class="align-middle"> H&S Report Form</span></a>
{% if perms.RIGS.change_event %} {% if perms.RIGS.change_event %}
<a class="list-group-item list-group-item-action" href="//members.nottinghamtec.co.uk/price" target="_blank" rel="noopener noreferrer"><span class="fas fa-pound-sign text-warning align-middle"></span><span class="align-middle"> Price List</span></a> <a class="list-group-item list-group-item-action" href="//members.nottinghamtec.co.uk/price" target="_blank" rel="noopener noreferrer"><span class="fas fa-pound-sign text-warning align-middle"></span><span class="align-middle"> Price List</span></a>
{% endif %} {% endif %}

View File

@@ -2,7 +2,7 @@ from django.contrib import admin
from training import models from training import models
from reversion.admin import VersionAdmin from reversion.admin import VersionAdmin
# admin.site.register(models.Trainee, 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)

View File

@@ -11,7 +11,7 @@ class TraineeManager(models.Manager):
return super().get_queryset().filter(is_active=True, is_approved=True) 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=['is_supervisor'])
class Trainee(Profile, RevisionMixin): class Trainee(Profile, RevisionMixin):
class Meta: class Meta:
proxy = True proxy = True
@@ -50,10 +50,6 @@ class Trainee(Profile, RevisionMixin):
def display_id(self): def display_id(self):
return str(self) return str(self)
@property
def full_name(self):
return self.first_name + " " + self.last_name
class TrainingCategory(models.Model): class TrainingCategory(models.Model):
reference_number = models.IntegerField(unique=True) reference_number = models.IntegerField(unique=True)
@@ -226,9 +222,9 @@ class TrainingLevel(models.Model, RevisionMixin):
if self.level == self.TA: if self.level == self.TA:
return self.get_level_display() return self.get_level_display()
else: else:
return "{} Common Competencies".format(self.get_level_display()) return f"{self.get_level_display()} Common Competencies"
else: else:
return "{} {}".format(self.get_department_display(), self.get_level_display()) return f"{self.get_department_display()} {self.get_level_display()}"
@property @property
def activity_feed_string(self): def activity_feed_string(self):
@@ -243,7 +239,7 @@ class TrainingLevel(models.Model, RevisionMixin):
icon = f"<span class='fas fa-{self.icon}'></span>" icon = f"<span class='fas fa-{self.icon}'></span>"
else: else:
icon = "".join([w[0] for w in str(self).split()]) icon = "".join([w[0] for w in str(self).split()])
return mark_safe("<span class='badge badge-{} badge-pill' data-toggle='tooltip' title='{}'>{}</span>".format(self.department_colour, str(self), icon)) return mark_safe(f"<span class='badge badge-{self.department_colour} badge-pill' data-toggle='tooltip' title='{str(self)}'>{icon}</span>")
@reversion.register @reversion.register
@@ -252,8 +248,6 @@ class TrainingLevelRequirement(models.Model, RevisionMixin):
item = models.ForeignKey('TrainingItem', on_delete=models.CASCADE) item = models.ForeignKey('TrainingItem', on_delete=models.CASCADE)
depth = models.IntegerField(choices=TrainingItemQualification.CHOICES) depth = models.IntegerField(choices=TrainingItemQualification.CHOICES)
reversion_hide = True
def __str__(self): def __str__(self):
depth = TrainingItemQualification.CHOICES[self.depth][1] depth = TrainingItemQualification.CHOICES[self.depth][1]
return f"{depth} in {self.item}" return f"{depth} in {self.item}"
@@ -269,8 +263,6 @@ class TrainingLevelQualification(models.Model, RevisionMixin):
confirmed_on = models.DateTimeField(null=True) confirmed_on = models.DateTimeField(null=True)
confirmed_by = models.ForeignKey('Trainee', related_name='confirmer', on_delete=models.CASCADE, null=True) confirmed_by = models.ForeignKey('Trainee', related_name='confirmer', on_delete=models.CASCADE, null=True)
reversion_hide = True
@property @property
def get_icon(self): def get_icon(self):
return self.level.get_icon return self.level.get_icon

View File

@@ -7,7 +7,10 @@
{% load button from filters %} {% load button from filters %}
{% load get_levels_of_depth from tags %} {% load get_levels_of_depth from tags %}
{% load static %}
{% block js %} {% block js %}
<script src="{% static "js/tooltip.js" %}"></script>
<script> <script>
$(function () { $(function () {
$('[data-toggle="tooltip"]').tooltip(); $('[data-toggle="tooltip"]').tooltip();

View File

@@ -10,13 +10,13 @@ from training import models
from reversion.models import Version, Revision from reversion.models import Version, Revision
def test_add_qualification(admin_client, trainee, admin_user): def test_add_qualification(admin_client, trainee, admin_user, training_item):
url = reverse('add_qualification', kwargs={'pk': trainee.pk}) url = reverse('add_qualification', kwargs={'pk': trainee.pk})
date = (timezone.now() + datetime.timedelta(days=3)).strftime("%Y-%m-%d") date = (timezone.now() + datetime.timedelta(days=3)).strftime("%Y-%m-%d")
response = admin_client.post(url, {'date': date, 'trainee': trainee.pk, 'supervisor': trainee.pk}) response = admin_client.post(url, {'date': date, 'trainee': trainee.pk, 'supervisor': trainee.pk, 'item': training_item.pk})
assertFormError(response, 'form', 'date', 'Qualification date may not be in the future') assertFormError(response, 'form', 'date', 'Qualification date may not be in the future')
assertFormError(response, 'form', 'supervisor', 'One may not supervise oneself...') assertFormError(response, 'form', 'supervisor', 'One may not supervise oneself...')
response = admin_client.post(url, {'date': date, 'trainee': trainee.pk, 'supervisor': admin_user.pk}) response = admin_client.post(url, {'date': date, 'trainee': trainee.pk, 'supervisor': admin_user.pk, 'item': training_item.pk})
assertFormError(response, 'form', 'supervisor', 'Selected supervisor must actually *be* a supervisor...') assertFormError(response, 'form', 'supervisor', 'Selected supervisor must actually *be* a supervisor...')
@@ -42,21 +42,40 @@ def test_add_requirement(admin_client, level):
assertContains(response, level.pk) assertContains(response, level.pk)
def test_trainee_detail(admin_client, trainee, admin_user): def get_response(admin_client, url, kwargs={}):
url = reverse('trainee_detail', kwargs={'pk': admin_user.pk}) url = reverse(url, kwargs=kwargs)
response = admin_client.get(url) response = admin_client.get(url)
assert response.status_code == 200
return response
def test_trainee_detail(admin_client, trainee, admin_user):
response = get_response(admin_client, 'trainee_detail', {'pk': admin_user.pk})
assertContains(response, "Your Training Record") assertContains(response, "Your Training Record")
assertContains(response, "No qualifications in any levels") assertContains(response, "No qualifications in any levels")
url = reverse('trainee_detail', kwargs={'pk': trainee.pk}) response = get_response(admin_client, 'trainee_detail', {'pk': trainee.pk})
response = admin_client.get(url)
assertNotContains(response, "Your") assertNotContains(response, "Your")
name = trainee.first_name + " " + trainee.last_name assertContains(response, f"{trainee.get_full_name()}'s Training Record")
assertContains(response, f"{name}'s Training Record")
def test_trainee_item_detail(admin_client, trainee): def test_trainee_item_detail(admin_client, trainee):
url = reverse('trainee_item_detail', kwargs={'pk': trainee.pk}) response = get_response(admin_client, 'trainee_item_detail', {'pk': trainee.pk})
response = admin_client.get(url)
assert response.status_code == 200
assertContains(response, "Nothing found") assertContains(response, "Nothing found")
def test_item_list(admin_client, training_item):
response = get_response(admin_client, 'item_list')
assertContains(response, str(training_item.category))
def test_trainee_list_search(admin_client, admin_user, trainee, supervisor):
response = get_response(admin_client, 'trainee_list')
assertContains(response, admin_user.get_full_name())
assertContains(response, trainee.get_full_name())
assertContains(response, supervisor.get_full_name())
url = reverse('trainee_list')
response = admin_client.get(url, {'q': trainee.get_full_name()})
assertContains(response, trainee.get_full_name())
assertNotContains(response, supervisor.get_full_name())

View File

@@ -35,7 +35,7 @@ class TraineeDetail(views.ProfileDetail):
if self.request.user.pk == self.object.pk: if self.request.user.pk == self.object.pk:
context["page_title"] = "Your Training Record" context["page_title"] = "Your Training Record"
else: else:
context["page_title"] = f"{self.object.full_name}'s Training Record" context["page_title"] = f"{self.object.get_full_name()}'s Training Record"
context["started_levels"] = self.object.started_levels() context["started_levels"] = self.object.started_levels()
context["completed_levels"] = self.object.level_qualifications.all() context["completed_levels"] = self.object.level_qualifications.all()
context["categories"] = models.TrainingCategory.objects.all().prefetch_related('items') context["categories"] = models.TrainingCategory.objects.all().prefetch_related('items')

View File

@@ -22,9 +22,7 @@
{% endblock %} {% endblock %}
<div class="card"> <div class="card">
<div class="card-header"> <h4 class="card-header">Recent Changes</h4>
<h4>Recent Changes</h4>
</div>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
<div id="activity_loading" class="list-group-item text-center"> <div id="activity_loading" class="list-group-item text-center">
<div class="spinner-border text-primary" role="status"> <div class="spinner-border text-primary" role="status">