mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 15:15:42 +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:
		
							
								
								
									
										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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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() {
 | 
			
		||||
 
 | 
			
		||||
@@ -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'),
 | 
			
		||||
    ]))
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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'),
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 }}}',
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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."""
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 %}
 | 
			
		||||
 
 | 
			
		||||
@@ -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() {
 | 
			
		||||
 
 | 
			
		||||
@@ -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'),
 | 
			
		||||
]
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user