Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add description field to TrainingItems #523

Merged
merged 9 commits into from
Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 38 additions & 23 deletions PyRIGS/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,45 +321,60 @@ def get(self, request, pk=None):
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):
append_terms = False

def get_context_data(self, **kwargs):
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)

context = {
'object': obj,
'current_user': self.request.user,
'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

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()

rml = template.render(context)
buffer = rml2pdf.parseString(rml)
merger.append(PdfFileReader(buffer))
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)
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

response = HttpResponse(content_type='application/pdf')
f = context['filename']
response['Content-Disposition'] = f'filename="{f}"'
response.write(merged.getvalue())
return response
def get(self, request):
self.object_list = self.get_queryset()
return render_pdf_response(get_template(self.template_name), self.get_context_data(), False)
Binary file modified RIGS/static/imgs/paperwork/corner-tr-su.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions RIGS/templates/base_print.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<initialize>
<color id="LightGray" RGB="#D3D3D3"/>
<color id="DarkGray" RGB="#707070"/>
<color id="Brand" RGB="#3853a4"/>
</initialize>

<paraStyle name="style.para" fontName="OpenSans" />
Expand All @@ -27,6 +28,8 @@
<paraStyle name="style.times" fontName="OpenSans" fontSize="10" />
<paraStyle name="style.head_titles" fontName="OpenSans-Bold" 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" borderPadding="4"/>
<paraStyle name="style.breakbefore" parent="emheader" pageBreakBefore="1"/>

<blockTableStyle id="eventSpecifics">
<blockValign value="top"/>
Expand Down
4 changes: 2 additions & 2 deletions RIGS/templatetags/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ def button(type, url=None, pk=None, clazz="", icon=None, text="", id=None, style
text = "Edit"
elif type == 'print':
clazz += " btn-primary "
icon = "fa-print"
text = "Print"
icon = "fa-download"
text = "Export"
elif type == 'duplicate':
clazz += " btn-info "
icon = "fa-copy"
Expand Down
3 changes: 1 addition & 2 deletions assets/management/commands/generateSampleAssetsData.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,7 @@ def create_cables(self):

for i in range(100):
prefix = random.choice(asset_prefixes)
asset_id = str(get_available_asset_id(wanted_prefix=prefix))
asset_id = prefix + asset_id
asset_id = get_available_asset_id(wanted_prefix=prefix)
asset = models.Asset(
asset_id=asset_id,
description=random.choice(asset_description),
Expand Down
3 changes: 2 additions & 1 deletion assets/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ def search(self, query=None):

def get_available_asset_id(wanted_prefix=""):
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):
Expand Down
2 changes: 1 addition & 1 deletion assets/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def get(self, request, *args, **kwargs):
class AssetDuplicate(DuplicateMixin, AssetIDUrlMixin, AssetCreate):
def get_initial(self, *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

def get_context_data(self, **kwargs):
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion training/management/commands/generateSampleTrainingData.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,23 @@ def setup_items(self):
"Bin Diving",
"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):
category = random.choice(self.categories)
previous_item = models.TrainingItem.objects.filter(category=category).last()
if previous_item is not None:
number = previous_item.reference_number + 1
else:
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)

def setup_levels(self):
Expand Down
18 changes: 18 additions & 0 deletions training/migrations/0006_rename_description_trainingitem_name.py
Original file line number Diff line number Diff line change
@@ -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 changes: 18 additions & 0 deletions training/migrations/0007_trainingitem_description.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
13 changes: 5 additions & 8 deletions training/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class TrainingItemManager(QueryablePropertiesManager):
def search(self, query=None):
qs = self.get_queryset()
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
return qs

Expand All @@ -94,16 +94,13 @@ def search(self, query=None):
class TrainingItem(models.Model):
reference_number = models.IntegerField()
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)
prerequisites = models.ManyToManyField('self', symmetrical=False, blank=True)

objects = TrainingItemManager()

@property
def name(self):
return str(self)

@queryable_property
def display_id(self):
return f"{self.category.reference_number}.{self.reference_number}"
Expand All @@ -121,7 +118,7 @@ def display_id(cls, lookup, value):
return models.Q()

def __str__(self):
name = f"{self.display_id} {self.description}"
name = f"{self.display_id} {self.name}"
if not self.active:
name += " (inactive)"
return name
Expand Down Expand Up @@ -149,7 +146,7 @@ class TrainingItemQualificationManager(QueryablePropertiesManager):
def search(self, query=None):
qs = self.get_queryset().select_related('item', 'supervisor', 'item__category')
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:
or_lookup = Q(item__category__reference_number=int(query)) | or_lookup
Expand Down
9 changes: 7 additions & 2 deletions training/templates/item_list.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{% extends 'base_training.html' %}

{% load button from filters %}

{% block content %}
<div class="col-12 text-right py-2 pr-0">
{% button 'print' 'item_list_export' %}
</div>
<div id="accordion">
{% for category in categories %}
<div class="card">
Expand All @@ -13,7 +18,8 @@
<div class="card-body">
<div class="list-group list-group-flush">
{% 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 %}
<div class="ml-3 font-italic">
<p class="text-info mb-0">Passed Out Prerequisites:</p>
Expand All @@ -24,7 +30,6 @@
</ul>
</div>
{% endif %}
<a href="{% url 'item_qualification' item.pk %}" class="btn btn-info"><span class="fas fa-user"></span> Qualified Users</a>
</li>
{% endfor %}
</div>
Expand Down
25 changes: 25 additions & 0 deletions training/templates/item_list.xml
Original file line number Diff line number Diff line change
@@ -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 %}
<namedString id="lastPage"><pageNumber/></namedString>
{% endblock %}
1 change: 1 addition & 0 deletions training/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

urlpatterns = [
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('trainee/list/', login_required(views.TraineeList.as_view()), name='trainee_list'),
Expand Down
13 changes: 12 additions & 1 deletion training/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from django.db.models import Q, Count
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 users import views
from reversion.views import RevisionMixin
Expand All @@ -24,6 +24,17 @@ def get_context_data(self, **kwargs):
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):
template_name = "trainee_detail.html"
model = models.Trainee
Expand Down