Client side QR Codes (#4357)

* Add JS for qrcodejs

Ref: https://davidshimjs.github.io/qrcodejs/

* Simple function for rendering a QR code

* Refactor QR code view for Part

* Replace QR code view for SupplierPart

* Refactor QR codes for stock item and stock location models

* Remove base QRCodeView entirely
This commit is contained in:
Oliver 2023-02-17 13:33:36 +11:00 committed by GitHub
parent 0f445ea6e4
commit cde2050236
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 56 additions and 161 deletions

File diff suppressed because one or more lines are too long

View File

@ -320,44 +320,6 @@ class AjaxView(AjaxMixin, View):
return self.renderJsonResponse(request)
class QRCodeView(AjaxView):
"""An 'AJAXified' view for displaying a QR code.
Subclasses should implement the get_qr_data(self) function.
"""
ajax_template_name = "qr_code.html"
def get(self, request, *args, **kwargs):
"""Return json with qr-code data."""
self.request = request
self.pk = self.kwargs['pk']
return self.renderJsonResponse(request, None, context=self.get_context_data())
def get_qr_data(self):
"""Returns the text object to render to a QR code.
The actual rendering will be handled by the template
"""
return None
def get_context_data(self):
"""Get context data for passing to the rendering template.
Explicity passes the parameter 'qr_data'
"""
context = {}
qr = self.get_qr_data()
if qr:
context['qr_data'] = qr
else:
context['error_msg'] = 'Error generating QR code'
return context
class AjaxUpdateView(AjaxMixin, UpdateView):
"""An 'AJAXified' UpdateView for updating an object in the db.

View File

@ -270,10 +270,10 @@ src="{% static 'img/blank_image.png' %}"
{% if barcodes %}
$("#show-qr-code").click(function() {
launchModalForm("{% url 'supplier-part-qr' part.pk %}",
{
no_post: true,
});
showQRDialog(
'{% trans "Supplier Part QR Code" %}',
'{"supplierpart": {{ part.pk }}}'
);
});
$("#barcode-link").click(function() {

View File

@ -26,7 +26,6 @@ manufacturer_part_urls = [
supplier_part_urls = [
re_path(r'^(?P<pk>\d+)/', include([
re_path('^qr_code/?', views.SupplierPartQRCode.as_view(), name='supplier-part-qr'),
re_path('^.*$', views.SupplierPartDetail.as_view(template_name='company/supplier_part.html'), name='supplier-part-detail'),
]))

View File

@ -4,7 +4,7 @@ from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView
from InvenTree.views import InvenTreeRoleMixin, QRCodeView
from InvenTree.views import InvenTreeRoleMixin
from plugin.views import InvenTreePluginViewMixin
from .models import Company, ManufacturerPart, SupplierPart
@ -112,18 +112,3 @@ class SupplierPartDetail(InvenTreePluginViewMixin, DetailView):
context_object_name = 'part'
queryset = SupplierPart.objects.all()
permission_required = 'purchase_order.view'
class SupplierPartQRCode(QRCodeView):
"""View for displaying a QR code for a StockItem object."""
ajax_form_title = _("Stock Item QR Code")
role_required = 'stock.view'
def get_qr_data(self):
"""Generate QR code data for the StockItem."""
try:
part = SupplierPart.objects.get(id=self.pk)
return part.format_barcode()
except SupplierPart.DoesNotExist:
return None

View File

@ -2118,9 +2118,6 @@ part_api_urls = [
# BOM download
re_path(r'^bom-download/?', views.BomDownload.as_view(), name='api-bom-download'),
# QR code download
re_path(r'^qr_code/?', views.PartQRCode.as_view(), name='api-part-qr'),
# Old pricing endpoint
re_path(r'^pricing2/', views.PartPricing.as_view(), name='part-pricing'),

View File

@ -447,11 +447,9 @@
{% if barcodes %}
$("#show-qr-code").click(function() {
launchModalForm(
"{% url 'api-part-qr' part.id %}",
{
no_post: true,
}
showQRDialog(
'{% trans "Part QR Code" %}',
'{"part": {{ part.pk }}}',
);
});

View File

@ -113,28 +113,3 @@ class PartDetailTest(PartViewTestCase):
response = self.client.get(reverse('api-bom-download', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
self.assertIn('streaming_content', dir(response))
class PartQRTest(PartViewTestCase):
"""Tests for the Part QR Code AJAX view."""
def test_html_redirect(self):
"""A HTML request for a QR code should be redirected (use an AJAX request instead)"""
response = self.client.get(reverse('api-part-qr', args=(1,)))
self.assertEqual(response.status_code, 302)
def test_valid_part(self):
"""Test QR code response for a Part"""
response = self.client.get(reverse('api-part-qr', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
data = str(response.content)
self.assertIn('Part QR Code', data)
self.assertIn('<img src=', data)
def test_invalid_part(self):
"""Test response for an invalid Part ID value"""
response = self.client.get(reverse('api-part-qr', args=(9999,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)

View File

@ -16,8 +16,7 @@ from common.models import InvenTreeSetting
from common.views import FileManagementAjaxView, FileManagementFormView
from company.models import SupplierPart
from InvenTree.helpers import str2bool, str2int
from InvenTree.views import (AjaxUpdateView, AjaxView, InvenTreeRoleMixin,
QRCodeView)
from InvenTree.views import AjaxUpdateView, AjaxView, InvenTreeRoleMixin
from plugin.views import InvenTreePluginViewMixin
from stock.models import StockItem, StockLocation
@ -372,22 +371,6 @@ class PartDetailFromIPN(PartDetail):
return super(PartDetailFromIPN, self).get(request, *args, **kwargs)
class PartQRCode(QRCodeView):
"""View for displaying a QR code for a Part object."""
ajax_form_title = _("Part QR Code")
role_required = 'part.view'
def get_qr_data(self):
"""Generate QR code data for the Part."""
try:
part = Part.objects.get(id=self.pk)
return part.format_barcode()
except Part.DoesNotExist:
return None
class PartImageSelect(AjaxUpdateView):
"""View for selecting Part image from existing images."""

View File

@ -523,10 +523,10 @@ $('#stock-edit-status').click(function () {
{% endif %}
$("#show-qr-code").click(function() {
launchModalForm("{% url 'stock-item-qr' item.id %}",
{
no_post: true,
});
showQRDialog(
'{% trans "Stock Item QR Code" %}',
'{"stockitem": {{ item.pk }}}',
);
});
{% if barcodes %}

View File

@ -399,10 +399,10 @@
{% if barcodes %}
$('#show-qr-code').click(function() {
launchModalForm("{% url 'stock-location-qr' location.id %}",
{
no_post: true,
});
showQRDialog(
'{% trans "Stock Location QR Code" %}',
'{"stocklocation": {{ location.pk }}}'
);
});
$("#barcode-link").click(function() {

View File

@ -7,8 +7,6 @@ from stock import views
location_urls = [
re_path(r'^(?P<pk>\d+)/', include([
re_path(r'^qr_code/?', views.StockLocationQRCode.as_view(), name='stock-location-qr'),
# Anything else - direct to the location detail view
re_path('^.*$', views.StockLocationDetail.as_view(), name='stock-location-detail'),
])),
@ -16,8 +14,6 @@ location_urls = [
]
stock_item_detail_urls = [
re_path(r'^qr_code/', views.StockItemQRCode.as_view(), name='stock-item-qr'),
# Anything else - direct to the item detail view
re_path('^.*$', views.StockItemDetail.as_view(), name='stock-item-detail'),
]

View File

@ -2,11 +2,10 @@
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView
import common.settings
from InvenTree.views import InvenTreeRoleMixin, QRCodeView
from InvenTree.views import InvenTreeRoleMixin
from plugin.views import InvenTreePluginViewMixin
from .models import StockItem, StockLocation
@ -101,34 +100,3 @@ class StockItemDetail(InvenTreeRoleMixin, InvenTreePluginViewMixin, DetailView):
return HttpResponseRedirect(reverse('stock-index'))
return super().get(request, *args, **kwargs)
class StockLocationQRCode(QRCodeView):
"""View for displaying a QR code for a StockLocation object."""
ajax_form_title = _("Stock Location QR code")
role_required = ['stock_location.view', 'stock.view']
def get_qr_data(self):
"""Generate QR code data for the StockLocation."""
try:
loc = StockLocation.objects.get(id=self.pk)
return loc.format_barcode()
except StockLocation.DoesNotExist:
return None
class StockItemQRCode(QRCodeView):
"""View for displaying a QR code for a StockItem object."""
ajax_form_title = _("Stock Item QR Code")
role_required = 'stock.view'
def get_qr_data(self):
"""Generate QR code data for the StockItem."""
try:
item = StockItem.objects.get(id=self.pk)
return item.format_barcode()
except StockItem.DoesNotExist:
return None

View File

@ -15,6 +15,7 @@
getFieldValue,
reloadFieldOptions,
showModalImage,
showQRDialog,
showQuestionDialog,
showModalSpinner,
*/
@ -590,19 +591,18 @@ function renderErrorMessage(xhr) {
}
/* Display a modal dialog message box.
*
* title - Title text
* content - HTML content of the dialog window
*/
function showAlertDialog(title, content, options={}) {
/* Display a modal dialog message box.
*
* title - Title text
* content - HTML content of the dialog window
*/
if (options.alert_style) {
// Wrap content in an alert block
content = `<div class='alert alert-block alert-${options.alert_style}'>${content}</div>`;
}
var modal = createNewModal({
title: title,
closeText: '{% trans "Close" %}',
@ -612,6 +612,36 @@ function showAlertDialog(title, content, options={}) {
modalSetContent(modal, content);
$(modal).modal('show');
if (options.after_render) {
options.after_render(modal);
}
}
/*
* Display a simple modal window with a QR code
*/
function showQRDialog(title, data, options={}) {
let content = `
<div id='qrcode-container' style='margin: auto; width: 256px; padding: 25px;'>
<div id='qrcode'></div>
</div>`;
options.after_render = function(modal) {
let qrcode = new QRCode('qrcode', {
width: 256,
height: 256,
});
qrcode.makeCode(data);
};
showAlertDialog(
title,
content,
options
);
}

View File

@ -35,3 +35,4 @@
<script defer type='text/javascript' src="{% static 'easymde/easymde.min.js' %}"></script>
<script defer type='text/javascript' src="{% static 'script/randomColor.min.js' %}"></script>
<script defer type='text/javascript' src="{% static 'script/html5-qrcode.min.js' %}"></script>
<script defer type='text/javascript' src="{% static 'script/qrcode.min.js' %}"></script>