Files
PyRIGS/assets/views.py

433 lines
14 KiB
Python

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
from django.db.models import Q, Sum
from django.http import Http404, HttpResponse, JsonResponse
from django.urls import reverse, reverse_lazy
from django.utils import timezone
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, ImageOps
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
from assets import forms, models
class AssetList(LoginRequiredMixin, generic.ListView):
model = models.Asset
template_name = 'asset_list.html'
paginate_by = 40
hide_hidden_status = True
def get_initial(self):
initial = {'status': models.AssetStatus.objects.filter(should_show=True)}
return initial
def get_queryset(self):
if self.request.method == 'GET' and len(self.request.GET) > 0:
self.form = forms.AssetSearchForm(data=self.request.GET)
else:
self.form = forms.AssetSearchForm(data=self.get_initial())
form = self.form
if not form.is_valid():
return self.model.objects.none()
# TODO Feedback to user when search fails
query_string = form.cleaned_data['q'] or ""
queryset = models.Asset.objects.search(query=query_string)
if form.cleaned_data['is_cable']:
queryset = queryset.filter(is_cable=True)
if form.cleaned_data['date_acquired']:
queryset = queryset.filter(date_acquired=form.cleaned_data['date_acquired'])
if form.cleaned_data['category']:
queryset = queryset.filter(category__in=form.cleaned_data['category'])
if len(form.cleaned_data['status']) > 0:
queryset = queryset.filter(status__in=form.cleaned_data['status'])
elif self.hide_hidden_status:
queryset = queryset.filter(
status__in=models.AssetStatus.objects.filter(should_show=True))
return queryset.select_related('category', 'status')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["form"] = self.form
if hasattr(self.form, 'cleaned_data'):
context["category_filters"] = self.form.cleaned_data.get('category')
context["status_filters"] = self.form.cleaned_data.get('status')
context["categories"] = models.AssetCategory.objects.all()
context["statuses"] = models.AssetStatus.objects.all()
context["page_title"] = "Asset List"
return context
class CableList(AssetList):
template_name = 'cable_list.html'
paginator = None
def get_queryset(self):
queryset = super().get_queryset().filter(is_cable=True)
if self.form.cleaned_data['cable_type']:
queryset = queryset.filter(cable_type__in=self.form.cleaned_data['cable_type'])
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_title"] = "Cable List"
context["total_length"] = self.get_queryset().aggregate(Sum('length'))['length__sum']
return context
class AssetIDUrlMixin:
def get_object(self, queryset=None):
pk = self.kwargs.get(self.pk_url_kwarg)
return get_object_or_404(models.Asset, asset_id=pk)
class AssetDetail(LoginRequiredMixin, AssetIDUrlMixin, generic.DetailView):
model = models.Asset
template_name = 'asset_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_title"] = f"Asset {self.object.display_id}"
return context
class AssetEdit(LoginRequiredMixin, AssetIDUrlMixin, generic.UpdateView):
template_name = 'asset_form.html'
model = models.Asset
form_class = forms.AssetForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["edit"] = True
context["connectors"] = models.Connector.objects.all()
context["page_title"] = f"Edit Asset: {self.object.display_id}"
return context
def get_success_url(self):
if is_ajax(self.request):
url = reverse_lazy('closemodal')
update_url = str(reverse_lazy('asset_update', kwargs={'pk': self.object.pk}))
messages.info(self.request, "modalobject=" + serializers.serialize("json", [self.object]))
messages.info(self.request, "modalobject[0]['update_url']='" + update_url + "'")
else:
url = reverse_lazy('asset_detail', kwargs={'pk': self.object.asset_id, })
return url
class AssetCreate(LoginRequiredMixin, generic.CreateView):
template_name = 'asset_form.html'
model = models.Asset
form_class = forms.AssetForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["create"] = True
context["connectors"] = models.Connector.objects.all()
context["page_title"] = "Create Asset"
return context
def get_initial(self, *args, **kwargs):
initial = super().get_initial(*args, **kwargs)
initial["asset_id"] = models.get_available_asset_id()
return initial
def get_success_url(self):
return reverse("asset_detail", kwargs={"pk": self.object.asset_id})
class DuplicateMixin:
def get(self, request, *args, **kwargs):
self.object = self.get_object()
self.object.pk = None
return self.render_to_response(self.get_context_data())
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()
return initial
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["create"] = None
context["duplicate"] = True
old_id = self.get_object().asset_id
context['previous_asset_id'] = old_id
context["page_title"] = f"Duplication of Asset: {old_id}"
return context
class AssetEmbed(AssetDetail):
template_name = 'asset_embed.html'
class AssetOEmbed(OEmbedView):
model = models.Asset
url_name = 'asset_embed'
class AssetAuditList(AssetList):
template_name = 'asset_audit_list.html'
hide_hidden_status = True
# TODO Refresh this when the modal is submitted
def get_queryset(self):
self.form = forms.AssetSearchForm(data=self.request.GET)
return self.model.objects.filter(Q(last_audited_at__isnull=True)).select_related('category', 'status')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['page_title'] = "Asset Audit List"
return context
class AssetAudit(AssetEdit):
template_name = 'asset_audit.html'
form_class = forms.AssetAuditForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_title"] = f"Audit Asset: {self.object.display_id}"
return context
def get_success_url(self):
# TODO For some reason this doesn't stick when done in form_valid??
asset = self.get_object()
asset.last_audited_by = self.request.user
asset.last_audited_at = timezone.now()
asset.save()
return super().get_success_url()
class SupplierList(GenericListView):
model = models.Supplier
ordering = ['name']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['create'] = 'supplier_create'
context['edit'] = 'supplier_update'
context['can_edit'] = self.request.user.has_perm('assets.change_supplier')
context['detail'] = 'supplier_detail'
if is_ajax(self.request):
context['override'] = "base_ajax.html"
else:
context['override'] = 'base_assets.html'
return context
class SupplierDetail(GenericDetailView):
model = models.Supplier
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['history_link'] = 'supplier_history'
context['update_link'] = 'supplier_update'
context['detail_link'] = 'supplier_detail'
context['associated'] = 'partials/associated_assets.html'
context['associated2'] = ''
if is_ajax(self.request):
context['override'] = "base_ajax.html"
else:
context['override'] = 'base_assets.html'
context['can_edit'] = self.request.user.has_perm('assets.change_supplier')
return context
class SupplierCreate(GenericCreateView, ModalURLMixin):
model = models.Supplier
form_class = forms.SupplierForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if is_ajax(self.request):
context['override'] = "base_ajax.html"
else:
context['override'] = 'base_assets.html'
return context
def get_success_url(self):
return self.get_close_url('supplier_update', 'supplier_detail')
class SupplierUpdate(GenericUpdateView, ModalURLMixin):
model = models.Supplier
form_class = forms.SupplierForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if is_ajax(self.request):
context['override'] = "base_ajax.html"
else:
context['override'] = 'base_assets.html'
return context
def get_success_url(self):
return self.get_close_url('supplier_update', 'supplier_detail')
class CableTypeList(generic.ListView):
model = models.CableType
template_name = 'cable_type_list.html'
paginate_by = 40
def get_queryset(self):
return self.model.objects.select_related('plug', 'socket')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_title"] = "Cable Type List"
return context
class CableTypeDetail(generic.DetailView):
model = models.CableType
template_name = 'cable_type_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_title"] = f"Cable Type {self.object}"
return context
class CableTypeCreate(generic.CreateView):
model = models.CableType
template_name = "cable_type_form.html"
form_class = forms.CableTypeForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["create"] = True
context["page_title"] = "Create Cable Type"
return context
def get_success_url(self):
return reverse("cable_type_detail", kwargs={"pk": self.object.pk})
class CableTypeUpdate(generic.UpdateView):
model = models.CableType
template_name = "cable_type_form.html"
form_class = forms.CableTypeForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["edit"] = True
context["page_title"] = f"Edit Cable Type {self.object}"
return context
def get_success_url(self):
return reverse("cable_type_detail", kwargs={"pk": self.object.pk})
def generate_label(pk):
black = (0, 0, 0)
white = (255, 255, 255)
size = (700, 200)
font_size = 22
font = ImageFont.truetype("static/fonts/OpenSans-Regular.tff", font_size)
heavy_font = ImageFont.truetype("static/fonts/OpenSans-Bold.tff", font_size + 13)
obj = get_object_or_404(models.Asset, asset_id=pk)
asset_id = f"Asset: {obj.asset_id}"
if obj.is_cable:
length = f"Length: {obj.length}m"
csa = f"CSA: {obj.csa}mm²"
image = Image.new("RGB", size, white)
image = ImageOps.expand(image, border=(5, 5, 5, 5), fill=black)
logo = Image.open("static/imgs/square_logo.png")
draw = ImageDraw.Draw(image)
draw.text((300, 0), asset_id, fill=black, font=heavy_font)
if obj.is_cable:
y = 140
draw.text((210, y), length, fill=black, font=font)
if obj.csa:
draw.text((365, y), csa, fill=black, font=font)
draw.text((210, size[1] - font_size - 8), "TEC PA & Lighting (0115) 84 68720", fill=black, font=font)
barcode = Code39(str(obj.asset_id), writer=ImageWriter())
logo_size = (200, 200)
image.paste(logo.resize(logo_size, Image.LANCZOS), box=(5, 5))
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, 100)), (int(((size[0] + logo_size[0]) - width) / 2), 40))
return image
class GenerateLabel(generic.View): # TODO Caching
def get(self, request, pk):
response = HttpResponse(content_type="image/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((get_object_or_404(models.Asset, asset_id=asset_id), base64_encoded_result_str))
name = f"Asset Label Sheet generated at {timezone.now()}"
context = {
'images0': images[::3],
'images1': images[1::3],
'images2': images[2::3],
# 'images3': images[3::4],
'filename': name
}
merger = PdfFileMerger()
rml = template.render(context)
buffer = rml2pdf.parseString(rml)
merger.append(PdfFileReader(buffer))
buffer.close()
merged = BytesIO()
merger.write(merged)
response['Content-Disposition'] = f'filename="{name}"'
response.write(merged.getvalue())
return response