mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-02-12 09:39:42 +00:00
Compare commits
7 Commits
estates-co
...
2871dcd281
| Author | SHA1 | Date | |
|---|---|---|---|
|
2871dcd281
|
|||
|
a743d18dc9
|
|||
| c12dae6676 | |||
| ea6dfca56c | |||
| 5ab338c373 | |||
| 67ba872c26 | |||
| d55ec47b18 |
@@ -321,45 +321,60 @@ class OEmbedView(generic.View):
|
|||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
|
||||||
|
def get_info_string(user):
|
||||||
|
user_str = f"by {user.name} " if user else ""
|
||||||
|
time = timezone.now().strftime('%d/%m/%Y %H:%I')
|
||||||
|
return f"[Paperwork generated {user_str}on {time}"
|
||||||
|
|
||||||
|
|
||||||
|
def render_pdf_response(template, context, append_terms):
|
||||||
|
merger = PdfFileMerger()
|
||||||
|
rml = template.render(context)
|
||||||
|
buffer = rml2pdf.parseString(rml)
|
||||||
|
merger.append(PdfFileReader(buffer))
|
||||||
|
buffer.close()
|
||||||
|
|
||||||
|
if append_terms:
|
||||||
|
terms = urllib.request.urlopen(settings.TERMS_OF_HIRE_URL)
|
||||||
|
merger.append(BytesIO(terms.read()))
|
||||||
|
|
||||||
|
merged = BytesIO()
|
||||||
|
merger.write(merged)
|
||||||
|
|
||||||
|
response = HttpResponse(content_type='application/pdf')
|
||||||
|
f = context['filename']
|
||||||
|
response['Content-Disposition'] = f'filename="{f}"'
|
||||||
|
response.write(merged.getvalue())
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
class PrintView(generic.View):
|
class PrintView(generic.View):
|
||||||
append_terms = False
|
append_terms = False
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
obj = get_object_or_404(self.model, pk=self.kwargs['pk'])
|
obj = get_object_or_404(self.model, pk=self.kwargs['pk'])
|
||||||
user_str = f"by {self.request.user.name} " if self.request.user is not None else ""
|
|
||||||
time = timezone.now().strftime('%d/%m/%Y %H:%I')
|
|
||||||
object_name = re.sub(r'[^a-zA-Z0-9 \n\.]', '', obj.name)
|
object_name = re.sub(r'[^a-zA-Z0-9 \n\.]', '', obj.name)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'object': obj,
|
'object': obj,
|
||||||
'current_user': self.request.user,
|
'current_user': self.request.user,
|
||||||
'object_name': object_name,
|
'object_name': object_name,
|
||||||
'info_string': f"[Paperwork generated {user_str}on {time} - {obj.current_version_id}]",
|
'info_string': get_info_string(self.request.user) + f"- {obj.current_version_id}]",
|
||||||
}
|
}
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
template = get_template(self.template_name)
|
return render_pdf_response(get_template(self.template_name), self.get_context_data(), self.append_terms)
|
||||||
|
|
||||||
merger = PdfFileMerger()
|
|
||||||
|
|
||||||
context = self.get_context_data()
|
class PrintListView(generic.ListView):
|
||||||
|
def get_context_data(self, *args, **kwargs):
|
||||||
|
context = super().get_context_data(*args, **kwargs)
|
||||||
|
context['current_user'] = self.request.user
|
||||||
|
context['info_string'] = get_info_string(self.request.user) + "]"
|
||||||
|
return context
|
||||||
|
|
||||||
rml = template.render(context)
|
def get(self, request):
|
||||||
buffer = rml2pdf.parseString(rml)
|
self.object_list = self.get_queryset()
|
||||||
merger.append(PdfFileReader(buffer))
|
return render_pdf_response(get_template(self.template_name), self.get_context_data(), False)
|
||||||
buffer.close()
|
|
||||||
|
|
||||||
if self.append_terms:
|
|
||||||
terms = urllib.request.urlopen(settings.TERMS_OF_HIRE_URL)
|
|
||||||
merger.append(BytesIO(terms.read()))
|
|
||||||
|
|
||||||
merged = BytesIO()
|
|
||||||
merger.write(merged)
|
|
||||||
|
|
||||||
response = HttpResponse(content_type='application/pdf')
|
|
||||||
f = context['filename']
|
|
||||||
response['Content-Disposition'] = f'filename="{f}"'
|
|
||||||
response.write(merged.getvalue())
|
|
||||||
return response
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 63 KiB |
@@ -11,6 +11,7 @@
|
|||||||
<initialize>
|
<initialize>
|
||||||
<color id="LightGray" RGB="#D3D3D3"/>
|
<color id="LightGray" RGB="#D3D3D3"/>
|
||||||
<color id="DarkGray" RGB="#707070"/>
|
<color id="DarkGray" RGB="#707070"/>
|
||||||
|
<color id="Brand" RGB="#3853a4"/>
|
||||||
</initialize>
|
</initialize>
|
||||||
|
|
||||||
<paraStyle name="style.para" fontName="OpenSans" />
|
<paraStyle name="style.para" fontName="OpenSans" />
|
||||||
@@ -27,6 +28,8 @@
|
|||||||
<paraStyle name="style.times" fontName="OpenSans" fontSize="10" />
|
<paraStyle name="style.times" fontName="OpenSans" fontSize="10" />
|
||||||
<paraStyle name="style.head_titles" fontName="OpenSans-Bold" fontSize="10" />
|
<paraStyle name="style.head_titles" fontName="OpenSans-Bold" fontSize="10" />
|
||||||
<paraStyle name="style.head_numbers" fontName="OpenSans" fontSize="10" />
|
<paraStyle name="style.head_numbers" fontName="OpenSans" fontSize="10" />
|
||||||
|
<paraStyle name="style.emheader" fontName="OpenSans" textColor="White" fontSize="12" backColor="Brand" leading="20"/>
|
||||||
|
<paraStyle name="style.breakbefore" parent="emheader" pageBreakBefore="1"/>
|
||||||
|
|
||||||
<blockTableStyle id="eventSpecifics">
|
<blockTableStyle id="eventSpecifics">
|
||||||
<blockValign value="top"/>
|
<blockValign value="top"/>
|
||||||
|
|||||||
@@ -196,8 +196,8 @@ def button(type, url=None, pk=None, clazz="", icon=None, text="", id=None, style
|
|||||||
text = "Edit"
|
text = "Edit"
|
||||||
elif type == 'print':
|
elif type == 'print':
|
||||||
clazz += " btn-primary "
|
clazz += " btn-primary "
|
||||||
icon = "fa-print"
|
icon = "fa-download"
|
||||||
text = "Print"
|
text = "Export"
|
||||||
elif type == 'duplicate':
|
elif type == 'duplicate':
|
||||||
clazz += " btn-info "
|
clazz += " btn-info "
|
||||||
icon = "fa-copy"
|
icon = "fa-copy"
|
||||||
|
|||||||
@@ -105,8 +105,7 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
for i in range(100):
|
for i in range(100):
|
||||||
prefix = random.choice(asset_prefixes)
|
prefix = random.choice(asset_prefixes)
|
||||||
asset_id = str(get_available_asset_id(wanted_prefix=prefix))
|
asset_id = get_available_asset_id(wanted_prefix=prefix)
|
||||||
asset_id = prefix + asset_id
|
|
||||||
asset = models.Asset(
|
asset = models.Asset(
|
||||||
asset_id=asset_id,
|
asset_id=asset_id,
|
||||||
description=random.choice(asset_description),
|
description=random.choice(asset_description),
|
||||||
|
|||||||
@@ -102,7 +102,8 @@ class AssetManager(models.Manager):
|
|||||||
|
|
||||||
def get_available_asset_id(wanted_prefix=""):
|
def get_available_asset_id(wanted_prefix=""):
|
||||||
last_asset = Asset.objects.filter(asset_id_prefix=wanted_prefix).last()
|
last_asset = Asset.objects.filter(asset_id_prefix=wanted_prefix).last()
|
||||||
return 9000 if last_asset is None else wanted_prefix + str(last_asset.asset_id_number + 1)
|
last_asset_id = last_asset.asset_id_number if last_asset else 0
|
||||||
|
return wanted_prefix + str(last_asset_id + 1)
|
||||||
|
|
||||||
|
|
||||||
def validate_positive(value):
|
def validate_positive(value):
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ class DuplicateMixin:
|
|||||||
class AssetDuplicate(DuplicateMixin, AssetIDUrlMixin, AssetCreate):
|
class AssetDuplicate(DuplicateMixin, AssetIDUrlMixin, AssetCreate):
|
||||||
def get_initial(self, *args, **kwargs):
|
def get_initial(self, *args, **kwargs):
|
||||||
initial = super().get_initial(*args, **kwargs)
|
initial = super().get_initial(*args, **kwargs)
|
||||||
initial["asset_id"] = models.get_available_asset_id(wanted_prefix=self.get_object().asset_id_prefix)
|
initial["asset_id"] = models.get_available_asset_id()
|
||||||
return initial
|
return initial
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
|||||||
1
package-lock.json
generated
1
package-lock.json
generated
@@ -5,6 +5,7 @@
|
|||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
|
"name": "PyRIGS",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "Custom",
|
"license": "Custom",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -143,6 +143,15 @@ class Command(BaseCommand):
|
|||||||
"Bin Diving",
|
"Bin Diving",
|
||||||
"Wiki Editing"]
|
"Wiki Editing"]
|
||||||
|
|
||||||
|
descriptions = [
|
||||||
|
"Physical training concentrates on mechanistic goals: training programs in this area develop specific motor skills, agility, strength or physical fitness, often with an intention of peaking at a particular time.",
|
||||||
|
"In military use, training means gaining the physical ability to perform and survive in combat, and learn the many skills needed in a time of war.",
|
||||||
|
"These include how to use a variety of weapons, outdoor survival skills, and how to survive being captured by the enemy, among many others. See military education and training.",
|
||||||
|
"While some studies have indicated relaxation training is useful for some medical conditions, autogenic training has limited results or has been the result of few studies.",
|
||||||
|
"Some occupations are inherently hazardous, and require a minimum level of competence before the practitioners can perform the work at an acceptable level of safety to themselves or others in the vicinity.",
|
||||||
|
"Occupational diving, rescue, firefighting and operation of certain types of machinery and vehicles may require assessment and certification of a minimum acceptable competence before the person is allowed to practice as a licensed instructor."
|
||||||
|
]
|
||||||
|
|
||||||
for i, name in enumerate(names):
|
for i, name in enumerate(names):
|
||||||
category = random.choice(self.categories)
|
category = random.choice(self.categories)
|
||||||
previous_item = models.TrainingItem.objects.filter(category=category).last()
|
previous_item = models.TrainingItem.objects.filter(category=category).last()
|
||||||
@@ -150,7 +159,7 @@ class Command(BaseCommand):
|
|||||||
number = previous_item.reference_number + 1
|
number = previous_item.reference_number + 1
|
||||||
else:
|
else:
|
||||||
number = 0
|
number = 0
|
||||||
item = models.TrainingItem.objects.create(category=category, reference_number=number, description=name)
|
item = models.TrainingItem.objects.create(category=category, reference_number=number, name=name, description=random.choice(descriptions) + random.choice(descriptions) + random.choice(descriptions))
|
||||||
self.items.append(item)
|
self.items.append(item)
|
||||||
|
|
||||||
def setup_levels(self):
|
def setup_levels(self):
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.18 on 2023-02-19 14:02
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('training', '0005_auto_20220223_1535'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='trainingitem',
|
||||||
|
old_name='description',
|
||||||
|
new_name='name',
|
||||||
|
),
|
||||||
|
]
|
||||||
18
training/migrations/0007_trainingitem_description.py
Normal file
18
training/migrations/0007_trainingitem_description.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.18 on 2023-02-19 14:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('training', '0006_rename_description_trainingitem_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='trainingitem',
|
||||||
|
name='description',
|
||||||
|
field=models.TextField(blank=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -85,7 +85,7 @@ class TrainingItemManager(QueryablePropertiesManager):
|
|||||||
def search(self, query=None):
|
def search(self, query=None):
|
||||||
qs = self.get_queryset()
|
qs = self.get_queryset()
|
||||||
if query is not None:
|
if query is not None:
|
||||||
or_lookup = (Q(description__icontains=query) | Q(display_id=query))
|
or_lookup = (Q(name__icontains=query) | Q(description__icontains=query) | Q(display_id=query))
|
||||||
qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups
|
qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
@@ -94,16 +94,13 @@ class TrainingItemManager(QueryablePropertiesManager):
|
|||||||
class TrainingItem(models.Model):
|
class TrainingItem(models.Model):
|
||||||
reference_number = models.IntegerField()
|
reference_number = models.IntegerField()
|
||||||
category = models.ForeignKey('TrainingCategory', related_name='items', on_delete=models.CASCADE)
|
category = models.ForeignKey('TrainingCategory', related_name='items', on_delete=models.CASCADE)
|
||||||
description = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
|
description = models.TextField(blank=True)
|
||||||
active = models.BooleanField(default=True)
|
active = models.BooleanField(default=True)
|
||||||
prerequisites = models.ManyToManyField('self', symmetrical=False, blank=True)
|
prerequisites = models.ManyToManyField('self', symmetrical=False, blank=True)
|
||||||
|
|
||||||
objects = TrainingItemManager()
|
objects = TrainingItemManager()
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return str(self)
|
|
||||||
|
|
||||||
@queryable_property
|
@queryable_property
|
||||||
def display_id(self):
|
def display_id(self):
|
||||||
return f"{self.category.reference_number}.{self.reference_number}"
|
return f"{self.category.reference_number}.{self.reference_number}"
|
||||||
@@ -121,7 +118,7 @@ class TrainingItem(models.Model):
|
|||||||
return models.Q()
|
return models.Q()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
name = f"{self.display_id} {self.description}"
|
name = f"{self.display_id} {self.name}"
|
||||||
if not self.active:
|
if not self.active:
|
||||||
name += " (inactive)"
|
name += " (inactive)"
|
||||||
return name
|
return name
|
||||||
@@ -149,7 +146,7 @@ class TrainingItemQualificationManager(QueryablePropertiesManager):
|
|||||||
def search(self, query=None):
|
def search(self, query=None):
|
||||||
qs = self.get_queryset().select_related('item', 'supervisor', 'item__category')
|
qs = self.get_queryset().select_related('item', 'supervisor', 'item__category')
|
||||||
if query is not None:
|
if query is not None:
|
||||||
or_lookup = (Q(item__description__icontains=query) | Q(supervisor__first_name__icontains=query) | Q(supervisor__last_name__icontains=query) | Q(item__category__name__icontains=query) | Q(item__display_id=query))
|
or_lookup = (Q(item__name__icontains=query) | Q(supervisor__first_name__icontains=query) | Q(supervisor__last_name__icontains=query) | Q(item__category__name__icontains=query) | Q(item__display_id=query))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
or_lookup = Q(item__category__reference_number=int(query)) | or_lookup
|
or_lookup = Q(item__category__reference_number=int(query)) | or_lookup
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
{% extends 'base_training.html' %}
|
{% extends 'base_training.html' %}
|
||||||
|
|
||||||
|
{% load button from filters %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="col-12 text-right py-2 pr-0">
|
||||||
|
{% button 'print' 'item_list_export' %}
|
||||||
|
</div>
|
||||||
<div id="accordion">
|
<div id="accordion">
|
||||||
{% for category in categories %}
|
{% for category in categories %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@@ -13,7 +18,8 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="list-group list-group-flush">
|
<div class="list-group list-group-flush">
|
||||||
{% for item in category.items.all %}
|
{% for item in category.items.all %}
|
||||||
<li class="list-group-item {% if not item.active%}text-warning{%endif%}">{{ item }}
|
<li class="list-group-item {% if not item.active%}text-warning{%endif%}">{{ item }} <a href="{% url 'item_qualification' item.pk %}" class="btn btn-info float-right"><span class="fas fa-user"></span> Qualified Users</a>
|
||||||
|
<br><small>{{ item.description }}</small>
|
||||||
{% if item.prerequisites.exists %}
|
{% if item.prerequisites.exists %}
|
||||||
<div class="ml-3 font-italic">
|
<div class="ml-3 font-italic">
|
||||||
<p class="text-info mb-0">Passed Out Prerequisites:</p>
|
<p class="text-info mb-0">Passed Out Prerequisites:</p>
|
||||||
@@ -24,7 +30,6 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'item_qualification' item.pk %}" class="btn btn-info"><span class="fas fa-user"></span> Qualified Users</a>
|
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
25
training/templates/item_list.xml
Normal file
25
training/templates/item_list.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{% extends 'base_print.xml' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 style="page-head">TEC Training Item List</h1>
|
||||||
|
<spacer length="15" />
|
||||||
|
{% for category in categories %}
|
||||||
|
|
||||||
|
<h2 {% if not forloop.first %}style="breakbefore"{%else%}style="emheader"{%endif%}>{{category}}</h2>
|
||||||
|
<spacer length="10" />
|
||||||
|
{% for item in category.items.all %}
|
||||||
|
<h3>{{ item }}</h3>
|
||||||
|
<spacer length="4" />
|
||||||
|
<para>{{ item.description }}</para>
|
||||||
|
{% if item.prerequisites.exists %}
|
||||||
|
<h4>Prerequisites:</h4>
|
||||||
|
<ul bulletFontSize="5">
|
||||||
|
{% for p in item.prerequisites.all %}
|
||||||
|
<li><para>{{p}}</para></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
<spacer length="8" />
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
@@ -8,6 +8,7 @@ from versioning.views import VersionHistory
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('items/', login_required(views.ItemList.as_view()), name='item_list'),
|
path('items/', login_required(views.ItemList.as_view()), name='item_list'),
|
||||||
|
path('items/export/', login_required(views.ItemListExport.as_view()), name='item_list_export'),
|
||||||
path('item/<int:pk>/qualified_users/', login_required(views.ItemQualifications.as_view()), name='item_qualification'),
|
path('item/<int:pk>/qualified_users/', login_required(views.ItemQualifications.as_view()), name='item_qualification'),
|
||||||
|
|
||||||
path('trainee/list/', login_required(views.TraineeList.as_view()), name='trainee_list'),
|
path('trainee/list/', login_required(views.TraineeList.as_view()), name='trainee_list'),
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ 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 django.db.utils import IntegrityError
|
||||||
|
|
||||||
from PyRIGS.views import is_ajax, ModalURLMixin, get_related
|
from PyRIGS.views import is_ajax, ModalURLMixin, get_related, PrintListView
|
||||||
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
|
||||||
@@ -24,6 +24,17 @@ class ItemList(generic.ListView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class ItemListExport(PrintListView):
|
||||||
|
model = models.TrainingItem
|
||||||
|
template_name = 'item_list.xml'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['filename'] = "TrainingItemList.pdf"
|
||||||
|
context["categories"] = models.TrainingCategory.objects.all()
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
class TraineeDetail(views.ProfileDetail):
|
class TraineeDetail(views.ProfileDetail):
|
||||||
template_name = "trainee_detail.html"
|
template_name = "trainee_detail.html"
|
||||||
model = models.Trainee
|
model = models.Trainee
|
||||||
|
|||||||
Reference in New Issue
Block a user