diff --git a/PyRIGS/views.py b/PyRIGS/views.py index 1a47b7da4..fe7a44e08 100644 --- a/PyRIGS/views.py +++ b/PyRIGS/views.py @@ -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) diff --git a/RIGS/static/imgs/paperwork/corner-tr-su.jpg b/RIGS/static/imgs/paperwork/corner-tr-su.jpg index 4cbe12986..e1f960fad 100644 Binary files a/RIGS/static/imgs/paperwork/corner-tr-su.jpg and b/RIGS/static/imgs/paperwork/corner-tr-su.jpg differ diff --git a/RIGS/templates/base_print.xml b/RIGS/templates/base_print.xml index 04bcfdd95..678c354f1 100644 --- a/RIGS/templates/base_print.xml +++ b/RIGS/templates/base_print.xml @@ -11,6 +11,7 @@ + @@ -27,6 +28,8 @@ + + diff --git a/RIGS/templatetags/filters.py b/RIGS/templatetags/filters.py index 21d4a1aff..b2480d400 100644 --- a/RIGS/templatetags/filters.py +++ b/RIGS/templatetags/filters.py @@ -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" diff --git a/assets/management/commands/generateSampleAssetsData.py b/assets/management/commands/generateSampleAssetsData.py index 49e709ab9..d9d66f165 100644 --- a/assets/management/commands/generateSampleAssetsData.py +++ b/assets/management/commands/generateSampleAssetsData.py @@ -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), diff --git a/assets/models.py b/assets/models.py index ef535fa4a..323310290 100644 --- a/assets/models.py +++ b/assets/models.py @@ -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): diff --git a/assets/views.py b/assets/views.py index 4ad4c94c5..46f83fb33 100644 --- a/assets/views.py +++ b/assets/views.py @@ -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): diff --git a/package-lock.json b/package-lock.json index 24528b693..2b73da2d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "PyRIGS", "version": "1.0.0", "license": "Custom", "dependencies": { diff --git a/training/management/commands/generateSampleTrainingData.py b/training/management/commands/generateSampleTrainingData.py index 8b92452d6..f342c0238 100644 --- a/training/management/commands/generateSampleTrainingData.py +++ b/training/management/commands/generateSampleTrainingData.py @@ -143,6 +143,15 @@ 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() @@ -150,7 +159,7 @@ def setup_items(self): 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): diff --git a/training/migrations/0006_rename_description_trainingitem_name.py b/training/migrations/0006_rename_description_trainingitem_name.py new file mode 100644 index 000000000..5976da1cb --- /dev/null +++ b/training/migrations/0006_rename_description_trainingitem_name.py @@ -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', + ), + ] diff --git a/training/migrations/0007_trainingitem_description.py b/training/migrations/0007_trainingitem_description.py new file mode 100644 index 000000000..4a087668d --- /dev/null +++ b/training/migrations/0007_trainingitem_description.py @@ -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), + ), + ] diff --git a/training/models.py b/training/models.py index db36c7ed0..9f6fddc83 100644 --- a/training/models.py +++ b/training/models.py @@ -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 @@ -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}" @@ -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 @@ -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 diff --git a/training/templates/item_list.html b/training/templates/item_list.html index 556c550b3..550efdfa0 100644 --- a/training/templates/item_list.html +++ b/training/templates/item_list.html @@ -1,6 +1,11 @@ {% extends 'base_training.html' %} +{% load button from filters %} + {% block content %} +
+{% button 'print' 'item_list_export' %} +
{% for category in categories %}
@@ -13,7 +18,8 @@
{% for item in category.items.all %} -
  • {{ item }} +
  • {{ item }} Qualified Users +
    {{ item.description }} {% if item.prerequisites.exists %}

    Passed Out Prerequisites:

    @@ -24,7 +30,6 @@
    {% endif %} - Qualified Users
  • {% endfor %}
    diff --git a/training/templates/item_list.xml b/training/templates/item_list.xml new file mode 100644 index 000000000..576eaed18 --- /dev/null +++ b/training/templates/item_list.xml @@ -0,0 +1,25 @@ +{% extends 'base_print.xml' %} + +{% block content %} +

    TEC Training Item List

    + +{% for category in categories %} +

    {{category}}

    + + {% for item in category.items.all %} +

    {{ item }}

    + + {{ item.description }} + {% if item.prerequisites.exists %} +

    Prerequisites:

    +
      + {% for p in item.prerequisites.all %} +
    • {{p}}
    • + {% endfor %} +
    + {% endif %} + + {% endfor %} +{% endfor %} + +{% endblock %} diff --git a/training/urls.py b/training/urls.py index de0dd2e65..55ab7523e 100644 --- a/training/urls.py +++ b/training/urls.py @@ -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//qualified_users/', login_required(views.ItemQualifications.as_view()), name='item_qualification'), path('trainee/list/', login_required(views.TraineeList.as_view()), name='trainee_list'), diff --git a/training/views.py b/training/views.py index 50d1c4a6c..e9d95d9de 100644 --- a/training/views.py +++ b/training/views.py @@ -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 @@ -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