diff --git a/RIGS/static/imgs/square_logo.png b/RIGS/static/imgs/square_logo.png
deleted file mode 100644
index 3c2143ba..00000000
Binary files a/RIGS/static/imgs/square_logo.png and /dev/null differ
diff --git a/RIGS/templatetags/filters.py b/RIGS/templatetags/filters.py
index 8a42a568..7efd42bb 100644
--- a/RIGS/templatetags/filters.py
+++ b/RIGS/templatetags/filters.py
@@ -114,10 +114,8 @@ def orderby(request, field, attr):
return dict_.urlencode()
-# Used for accessing outside of a form, i.e. in detail views of RiskAssessment and EventChecklist
-
-@register.filter(needs_autoescape=True)
+@register.filter(needs_autoescape=True) # Used for accessing outside of a form, i.e. in detail views of RiskAssessment and EventChecklist
def get_field(obj, field, autoescape=True):
value = getattr(obj, field)
if(isinstance(value, bool)):
@@ -221,7 +219,7 @@ def button(type, url=None, pk=None, clazz="", icon=None, text="", id=None, style
return {'target': url, 'pk': pk, 'class': clazz, 'icon': icon, 'text': text, 'id': id, 'style': style}
-@register.simple_tag
+@register.simple_tag # TODO Can these be done with annotation/aggregation?
def invoices_waiting():
return len(models.Event.objects.waiting_invoices())
diff --git a/assets/converters.py b/assets/converters.py
index da55d2e6..e57c56bd 100644
--- a/assets/converters.py
+++ b/assets/converters.py
@@ -1,3 +1,6 @@
+import urllib.parse
+
+
class AssetIDConverter: # Forces lowercase to uppercase
regex = '[^/]+'
@@ -6,3 +9,16 @@ class AssetIDConverter: # Forces lowercase to uppercase
def to_url(self, value):
return str(value).upper()
+
+
+class ListConverter:
+ regex = '[^/]+'
+
+ def to_python(self, value):
+ return value.split(',')
+
+ def to_url(self, value):
+ string = ""
+ for i in value:
+ string += "," + str(i)
+ return string[1:]
diff --git a/assets/static/imgs/square_logo.png b/assets/static/imgs/square_logo.png
new file mode 100644
index 00000000..445184da
Binary files /dev/null and b/assets/static/imgs/square_logo.png differ
diff --git a/assets/templates/asset_list.html b/assets/templates/asset_list.html
index c89d1bc3..d335fad7 100644
--- a/assets/templates/asset_list.html
+++ b/assets/templates/asset_list.html
@@ -1,6 +1,7 @@
{% extends 'base_assets.html' %}
{% load paginator from filters %}
{% load button from filters %}
+{% load ids_from_objects from asset_tags %}
{% load widget_tweaks %}
{% load static %}
@@ -85,6 +86,9 @@
{% button 'new' 'asset_create' style="width: 6em" %}
+ {% if object_list %}
+
Generate Labels
+ {% endif %}
diff --git a/assets/templates/labels_print.xml b/assets/templates/labels_print.xml
new file mode 100644
index 00000000..b8adfd79
--- /dev/null
+++ b/assets/templates/labels_print.xml
@@ -0,0 +1,35 @@
+
+
+{% load multiply from filters %}
+{% load index from asset_tags %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% for i in images0 %}
+
+ | {% with images0|index:forloop.counter0 as image %}{% if image %}{% endif %}{% endwith %} |
+ {% with images1|index:forloop.counter0 as image %}{% if image %}{% endif %}{% endwith %} |
+ {% with images2|index:forloop.counter0 as image %}{% if image %}{% endif %}{% endwith %} |
+ {% with images3|index:forloop.counter0 as image %}{% if image %}{% endif %}{% endwith %} |
+
+ {% endfor %}
+
+
+
diff --git a/assets/templates/partials/asset_buttons.html b/assets/templates/partials/asset_buttons.html
index 2551365e..71f2a11b 100644
--- a/assets/templates/partials/asset_buttons.html
+++ b/assets/templates/partials/asset_buttons.html
@@ -12,9 +12,7 @@
{% button 'edit' url='asset_update' pk=object.asset_id %}
{% button 'duplicate' url='asset_duplicate' pk=object.asset_id %}
Audit
- {% if object.is_cable %}
Generate Label
- {% endif %}
{% endif %}
{% if create or edit or duplicate %}
diff --git a/assets/templatetags/asset_tags.py b/assets/templatetags/asset_tags.py
new file mode 100644
index 00000000..553f646f
--- /dev/null
+++ b/assets/templatetags/asset_tags.py
@@ -0,0 +1,17 @@
+from django import template
+from assets import models
+
+register = template.Library()
+
+
+@register.filter
+def ids_from_objects(object_list):
+ id_list = []
+ for obj in object_list:
+ id_list.append(obj.asset_id)
+ return id_list
+
+
+@register.filter
+def index(indexable, i):
+ return indexable[i] if i < len(indexable) else None
diff --git a/assets/urls.py b/assets/urls.py
index 4ec111c1..5f45d2e8 100644
--- a/assets/urls.py
+++ b/assets/urls.py
@@ -7,6 +7,7 @@ from PyRIGS.views import OEmbedView
from . import views, converters
register_converter(converters.AssetIDConverter, 'asset')
+register_converter(converters.ListConverter, 'list')
urlpatterns = [
path('', login_required(views.AssetList.as_view()), name='asset_index'),
@@ -19,6 +20,7 @@ urlpatterns = [
path('asset/id//duplicate/', permission_required_with_403('assets.add_asset')
(views.AssetDuplicate.as_view()), name='asset_duplicate'),
path('asset/id//label', login_required(views.GenerateLabel.as_view()), name='generate_label'),
+ path('asset//list/label', views.GenerateLabels.as_view(), name='generate_labels'),
path('cabletype/list/', login_required(views.CableTypeList.as_view()), name='cable_type_list'),
path('cabletype/create/', permission_required_with_403('assets.add_cable_type')(views.CableTypeCreate.as_view()), name='cable_type_create'),
diff --git a/assets/views.py b/assets/views.py
index a449112d..28894de8 100644
--- a/assets/views.py
+++ b/assets/views.py
@@ -1,5 +1,8 @@
import simplejson
import random
+import base64
+from io import BytesIO
+
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core import serializers
@@ -11,10 +14,13 @@ from django.utils.decorators import method_decorator
from django.views import generic
from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import get_object_or_404
+from django.template.loader import get_template
+from PyPDF2 import PdfFileMerger, PdfFileReader
from PIL import Image, ImageDraw, ImageFont
from barcode import Code39
from barcode.writer import ImageWriter
+from z3c.rml import rml2pdf
from PyRIGS.views import GenericListView, GenericDetailView, GenericUpdateView, GenericCreateView, ModalURLMixin, \
is_ajax, OEmbedView
@@ -349,35 +355,82 @@ class CableTypeUpdate(generic.UpdateView):
return reverse("cable_type_detail", kwargs={"pk": self.object.pk})
-class GenerateLabel(generic.View):
- def get(self, request, pk):
- black = (0, 0, 0)
- white = (255, 255, 255)
- size = (700, 200)
- font = ImageFont.truetype("static/fonts/OpenSans-Regular.tff", 20)
- obj = get_object_or_404(models.Asset, asset_id=pk)
+def generate_label(pk):
+ black = (0, 0, 0)
+ white = (255, 255, 255)
+ size = (700, 200)
+ font = ImageFont.truetype("static/fonts/OpenSans-Regular.tff", 20)
+ obj = get_object_or_404(models.Asset, asset_id=pk)
- asset_id = "Asset: {}".format(obj.asset_id)
+ asset_id = "Asset: {}".format(obj.asset_id)
+ if obj.is_cable:
length = "Length: {}m".format(obj.length)
csa = "CSA: {}mm²".format(obj.csa)
- image = Image.new("RGB", size, white)
- logo = Image.open("static/imgs/square_logo.png")
- draw = ImageDraw.Draw(image)
+ image = Image.new("RGB", size, white)
+ logo = Image.open("static/imgs/square_logo.png")
+ draw = ImageDraw.Draw(image)
- draw.text((210, 140), asset_id, fill=black, font=font)
+ draw.text((210, 140), asset_id, fill=black, font=font)
+ if obj.is_cable:
draw.text((210, 170), length, fill=black, font=font)
- draw.text((350, 170), csa, fill=black, font=font)
- draw.multiline_text((500, 140), "TEC PA & Lighting\n(0115) 84 68720", fill=black, font=font)
+ draw.text((360, 170), csa, fill=black, font=font)
+ draw.multiline_text((500, 140), "TEC PA & Lighting\n(0115) 84 68720", fill=black, font=font)
- barcode = Code39(str(obj.asset_id), writer=ImageWriter())
+ barcode = Code39(str(obj.asset_id), writer=ImageWriter())
- logo_size = (200, 200)
- image.paste(logo.resize(logo_size, Image.ANTIALIAS))
- barcode_image = barcode.render(writer_options={"quiet_zone": 0, "write_text": False})
- width, height = barcode_image.size
- image.paste(barcode_image.crop((0, 0, width, 135)), (int(((size[0] + logo_size[0]) - width) / 2), 0))
+ logo_size = (200, 200)
+ image.paste(logo.resize(logo_size, Image.ANTIALIAS))
+ barcode_image = barcode.render(writer_options={"quiet_zone": 0, "write_text": False})
+ width, height = barcode_image.size
+ image.paste(barcode_image.crop((0, 0, width, 135)), (int(((size[0] + logo_size[0]) - width) / 2), 0))
+ return image
+
+
+class GenerateLabel(generic.View): # TODO Caching
+ def get(self, request, pk):
response = HttpResponse(content_type="image/png")
- image.save(response, "PNG")
+ generate_label(pk).save(response, "PNG")
+ return response
+
+
+class GenerateLabels(generic.View):
+ def get(self, request, ids):
+ response = HttpResponse(content_type='application/pdf')
+ template = get_template('labels_print.xml')
+
+ images = []
+
+ for asset_id in ids:
+ image = generate_label(asset_id)
+ in_mem_file = BytesIO()
+ image.save(in_mem_file, format="PNG")
+ # reset file pointer to start
+ in_mem_file.seek(0)
+ img_bytes = in_mem_file.read()
+
+ base64_encoded_result_bytes = base64.b64encode(img_bytes)
+ base64_encoded_result_str = base64_encoded_result_bytes.decode('ascii')
+ images.append(base64_encoded_result_str)
+
+ context = {
+ 'images0': images[::4],
+ 'images1': images[1::4],
+ 'images2': images[2::4],
+ 'images3': images[3::4],
+ 'filename': "Asset Label Sheet generated at {}".format(timezone.now())
+ }
+ merger = PdfFileMerger()
+
+ rml = template.render(context)
+ buffer = rml2pdf.parseString(rml)
+ merger.append(PdfFileReader(buffer))
+ buffer.close()
+
+ merged = BytesIO()
+ merger.write(merged)
+
+ response['Content-Disposition'] = 'filename="{}"'.format(context['filename'])
+ response.write(merged.getvalue())
return response