mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
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:
parent
0f445ea6e4
commit
cde2050236
1
InvenTree/InvenTree/static/script/qrcode.min.js
vendored
Normal file
1
InvenTree/InvenTree/static/script/qrcode.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -320,44 +320,6 @@ class AjaxView(AjaxMixin, View):
|
|||||||
return self.renderJsonResponse(request)
|
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):
|
class AjaxUpdateView(AjaxMixin, UpdateView):
|
||||||
"""An 'AJAXified' UpdateView for updating an object in the db.
|
"""An 'AJAXified' UpdateView for updating an object in the db.
|
||||||
|
|
||||||
|
@ -270,10 +270,10 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
{% if barcodes %}
|
{% if barcodes %}
|
||||||
|
|
||||||
$("#show-qr-code").click(function() {
|
$("#show-qr-code").click(function() {
|
||||||
launchModalForm("{% url 'supplier-part-qr' part.pk %}",
|
showQRDialog(
|
||||||
{
|
'{% trans "Supplier Part QR Code" %}',
|
||||||
no_post: true,
|
'{"supplierpart": {{ part.pk }}}'
|
||||||
});
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#barcode-link").click(function() {
|
$("#barcode-link").click(function() {
|
||||||
|
@ -26,7 +26,6 @@ manufacturer_part_urls = [
|
|||||||
|
|
||||||
supplier_part_urls = [
|
supplier_part_urls = [
|
||||||
re_path(r'^(?P<pk>\d+)/', include([
|
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'),
|
re_path('^.*$', views.SupplierPartDetail.as_view(template_name='company/supplier_part.html'), name='supplier-part-detail'),
|
||||||
]))
|
]))
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ from django.urls import reverse
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import DetailView, ListView
|
from django.views.generic import DetailView, ListView
|
||||||
|
|
||||||
from InvenTree.views import InvenTreeRoleMixin, QRCodeView
|
from InvenTree.views import InvenTreeRoleMixin
|
||||||
from plugin.views import InvenTreePluginViewMixin
|
from plugin.views import InvenTreePluginViewMixin
|
||||||
|
|
||||||
from .models import Company, ManufacturerPart, SupplierPart
|
from .models import Company, ManufacturerPart, SupplierPart
|
||||||
@ -112,18 +112,3 @@ class SupplierPartDetail(InvenTreePluginViewMixin, DetailView):
|
|||||||
context_object_name = 'part'
|
context_object_name = 'part'
|
||||||
queryset = SupplierPart.objects.all()
|
queryset = SupplierPart.objects.all()
|
||||||
permission_required = 'purchase_order.view'
|
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
|
|
||||||
|
@ -2118,9 +2118,6 @@ part_api_urls = [
|
|||||||
# BOM download
|
# BOM download
|
||||||
re_path(r'^bom-download/?', views.BomDownload.as_view(), name='api-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
|
# Old pricing endpoint
|
||||||
re_path(r'^pricing2/', views.PartPricing.as_view(), name='part-pricing'),
|
re_path(r'^pricing2/', views.PartPricing.as_view(), name='part-pricing'),
|
||||||
|
|
||||||
|
@ -447,11 +447,9 @@
|
|||||||
|
|
||||||
{% if barcodes %}
|
{% if barcodes %}
|
||||||
$("#show-qr-code").click(function() {
|
$("#show-qr-code").click(function() {
|
||||||
launchModalForm(
|
showQRDialog(
|
||||||
"{% url 'api-part-qr' part.id %}",
|
'{% trans "Part QR Code" %}',
|
||||||
{
|
'{"part": {{ part.pk }}}',
|
||||||
no_post: true,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -113,28 +113,3 @@ class PartDetailTest(PartViewTestCase):
|
|||||||
response = self.client.get(reverse('api-bom-download', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
response = self.client.get(reverse('api-bom-download', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertIn('streaming_content', dir(response))
|
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)
|
|
||||||
|
@ -16,8 +16,7 @@ from common.models import InvenTreeSetting
|
|||||||
from common.views import FileManagementAjaxView, FileManagementFormView
|
from common.views import FileManagementAjaxView, FileManagementFormView
|
||||||
from company.models import SupplierPart
|
from company.models import SupplierPart
|
||||||
from InvenTree.helpers import str2bool, str2int
|
from InvenTree.helpers import str2bool, str2int
|
||||||
from InvenTree.views import (AjaxUpdateView, AjaxView, InvenTreeRoleMixin,
|
from InvenTree.views import AjaxUpdateView, AjaxView, InvenTreeRoleMixin
|
||||||
QRCodeView)
|
|
||||||
from plugin.views import InvenTreePluginViewMixin
|
from plugin.views import InvenTreePluginViewMixin
|
||||||
from stock.models import StockItem, StockLocation
|
from stock.models import StockItem, StockLocation
|
||||||
|
|
||||||
@ -372,22 +371,6 @@ class PartDetailFromIPN(PartDetail):
|
|||||||
return super(PartDetailFromIPN, self).get(request, *args, **kwargs)
|
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):
|
class PartImageSelect(AjaxUpdateView):
|
||||||
"""View for selecting Part image from existing images."""
|
"""View for selecting Part image from existing images."""
|
||||||
|
|
||||||
|
@ -523,10 +523,10 @@ $('#stock-edit-status').click(function () {
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
$("#show-qr-code").click(function() {
|
$("#show-qr-code").click(function() {
|
||||||
launchModalForm("{% url 'stock-item-qr' item.id %}",
|
showQRDialog(
|
||||||
{
|
'{% trans "Stock Item QR Code" %}',
|
||||||
no_post: true,
|
'{"stockitem": {{ item.pk }}}',
|
||||||
});
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
{% if barcodes %}
|
{% if barcodes %}
|
||||||
|
@ -399,10 +399,10 @@
|
|||||||
|
|
||||||
{% if barcodes %}
|
{% if barcodes %}
|
||||||
$('#show-qr-code').click(function() {
|
$('#show-qr-code').click(function() {
|
||||||
launchModalForm("{% url 'stock-location-qr' location.id %}",
|
showQRDialog(
|
||||||
{
|
'{% trans "Stock Location QR Code" %}',
|
||||||
no_post: true,
|
'{"stocklocation": {{ location.pk }}}'
|
||||||
});
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#barcode-link").click(function() {
|
$("#barcode-link").click(function() {
|
||||||
|
@ -7,8 +7,6 @@ from stock import views
|
|||||||
location_urls = [
|
location_urls = [
|
||||||
|
|
||||||
re_path(r'^(?P<pk>\d+)/', include([
|
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
|
# Anything else - direct to the location detail view
|
||||||
re_path('^.*$', views.StockLocationDetail.as_view(), name='stock-location-detail'),
|
re_path('^.*$', views.StockLocationDetail.as_view(), name='stock-location-detail'),
|
||||||
])),
|
])),
|
||||||
@ -16,8 +14,6 @@ location_urls = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
stock_item_detail_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
|
# Anything else - direct to the item detail view
|
||||||
re_path('^.*$', views.StockItemDetail.as_view(), name='stock-item-detail'),
|
re_path('^.*$', views.StockItemDetail.as_view(), name='stock-item-detail'),
|
||||||
]
|
]
|
||||||
|
@ -2,11 +2,10 @@
|
|||||||
|
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.views.generic import DetailView, ListView
|
from django.views.generic import DetailView, ListView
|
||||||
|
|
||||||
import common.settings
|
import common.settings
|
||||||
from InvenTree.views import InvenTreeRoleMixin, QRCodeView
|
from InvenTree.views import InvenTreeRoleMixin
|
||||||
from plugin.views import InvenTreePluginViewMixin
|
from plugin.views import InvenTreePluginViewMixin
|
||||||
|
|
||||||
from .models import StockItem, StockLocation
|
from .models import StockItem, StockLocation
|
||||||
@ -101,34 +100,3 @@ class StockItemDetail(InvenTreeRoleMixin, InvenTreePluginViewMixin, DetailView):
|
|||||||
return HttpResponseRedirect(reverse('stock-index'))
|
return HttpResponseRedirect(reverse('stock-index'))
|
||||||
|
|
||||||
return super().get(request, *args, **kwargs)
|
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
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
getFieldValue,
|
getFieldValue,
|
||||||
reloadFieldOptions,
|
reloadFieldOptions,
|
||||||
showModalImage,
|
showModalImage,
|
||||||
|
showQRDialog,
|
||||||
showQuestionDialog,
|
showQuestionDialog,
|
||||||
showModalSpinner,
|
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={}) {
|
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) {
|
if (options.alert_style) {
|
||||||
// Wrap content in an alert block
|
// Wrap content in an alert block
|
||||||
content = `<div class='alert alert-block alert-${options.alert_style}'>${content}</div>`;
|
content = `<div class='alert alert-block alert-${options.alert_style}'>${content}</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var modal = createNewModal({
|
var modal = createNewModal({
|
||||||
title: title,
|
title: title,
|
||||||
closeText: '{% trans "Close" %}',
|
closeText: '{% trans "Close" %}',
|
||||||
@ -612,6 +612,36 @@ function showAlertDialog(title, content, options={}) {
|
|||||||
modalSetContent(modal, content);
|
modalSetContent(modal, content);
|
||||||
|
|
||||||
$(modal).modal('show');
|
$(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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,3 +35,4 @@
|
|||||||
<script defer type='text/javascript' src="{% static 'easymde/easymde.min.js' %}"></script>
|
<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/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/html5-qrcode.min.js' %}"></script>
|
||||||
|
<script defer type='text/javascript' src="{% static 'script/qrcode.min.js' %}"></script>
|
||||||
|
Loading…
Reference in New Issue
Block a user