diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py
index 82c8a99507..fbced0fda7 100644
--- a/InvenTree/InvenTree/settings.py
+++ b/InvenTree/InvenTree/settings.py
@@ -124,6 +124,19 @@ DATABASES = {
}
}
+CACHES = {
+ 'default': {
+ 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
+ },
+ 'qr-code': {
+ 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
+ 'LOCATION': 'qr-code-cache',
+ 'TIMEOUT': 3600
+ }
+}
+
+QR_CODE_CACHE_ALIAS = 'qr-code'
+
# Password validation
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py
index ccc828de55..4038c2870a 100644
--- a/InvenTree/InvenTree/urls.py
+++ b/InvenTree/InvenTree/urls.py
@@ -8,6 +8,7 @@ Passes URL lookup downstream to each app as required.
from django.conf.urls import url, include
from django.contrib import admin
from django.contrib.auth import views as auth_views
+from qr_code import urls as qr_code_urls
from company.urls import company_urls
@@ -62,6 +63,8 @@ urlpatterns = [
url(r'^logout/', auth_views.LogoutView.as_view(template_name='registration/logout.html'), name='logout'),
url(r'^admin/', admin.site.urls, name='inventree-admin'),
+ url(r'^qr_code/', include(qr_code_urls, namespace='qr_code')),
+
url(r'^index/', IndexView.as_view(), name='index'),
url(r'^search/', SearchView.as_view(), name='search'),
diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py
index e727063f0f..ca72dd2b36 100644
--- a/InvenTree/InvenTree/views.py
+++ b/InvenTree/InvenTree/views.py
@@ -12,7 +12,7 @@ from django.template.loader import render_to_string
from django.http import JsonResponse
from django.views import View
-from django.views.generic import UpdateView, CreateView, DeleteView
+from django.views.generic import UpdateView, CreateView, DeleteView, DetailView
from django.views.generic.base import TemplateView
from part.models import Part
@@ -144,6 +144,43 @@ 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):
+ 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 AjaxCreateView(AjaxMixin, CreateView):
""" An 'AJAXified' CreateView for creating a new object in the db
diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py
index 9f5d9f0e29..e2823ba93b 100644
--- a/InvenTree/part/models.py
+++ b/InvenTree/part/models.py
@@ -180,7 +180,6 @@ class Part(models.Model):
def __str__(self):
return "{n} - {d}".format(n=self.name, d=self.description)
- @property
def format_barcode(self):
""" Return a JSON string for formatting a barcode for this Part object """
diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html
index 56acbd56b1..789a40b166 100644
--- a/InvenTree/part/templates/part/detail.html
+++ b/InvenTree/part/templates/part/detail.html
@@ -1,6 +1,5 @@
{% extends "part/part_base.html" %}
{% load static %}
-{% load qr_code %}
{% block details %}
{% include 'part/tabs.html' with tab='detail' %}
@@ -24,6 +23,7 @@
{% else %}
Activate
{% endif %}
+ Show QR Code
@@ -116,8 +116,6 @@
{% endif %}
-{% qr_from_text part.format_barcode size="s" image_format="png" error_correction="L" %}
-
{% endblock %}
{% block js_load %}
@@ -128,6 +126,15 @@
{% block js_ready %}
{{ block.super }}
+
+ $("#show-qr-code").click(function() {
+ launchModalForm(
+ "{% url 'part-qr' part.id %}",
+ {
+ no_post: true,
+ }
+ );
+ });
$("#duplicate-part").click(function() {
launchModalForm(
diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html
index a1b4ddc2d1..de1ab2c6e0 100644
--- a/InvenTree/part/templates/part/part_base.html
+++ b/InvenTree/part/templates/part/part_base.html
@@ -22,9 +22,7 @@
{{ part.name }}{% if part.active == False %} - INACTIVE{% endif %}
- {% if part.description %}
{{ part.description }}
- {% endif %}
{% if part.IPN %}
IPN |
diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py
index 401a038193..4cda10a0f0 100644
--- a/InvenTree/part/urls.py
+++ b/InvenTree/part/urls.py
@@ -34,8 +34,9 @@ part_attachment_urls = [
part_detail_urls = [
url(r'^edit/?', views.PartEdit.as_view(), name='part-edit'),
url(r'^delete/?', views.PartDelete.as_view(), name='part-delete'),
- url(r'^track/?', views.PartDetail.as_view(template_name='part/track.html'), name='part-track'),
url(r'^bom-export/?', views.BomDownload.as_view(), name='bom-export'),
+
+ url(r'^track/?', views.PartDetail.as_view(template_name='part/track.html'), name='part-track'),
url(r'^attachments/?', views.PartDetail.as_view(template_name='part/attachments.html'), name='part-attachments'),
url(r'^bom/?', views.PartDetail.as_view(template_name='part/bom.html'), name='part-bom'),
url(r'^build/?', views.PartDetail.as_view(template_name='part/build.html'), name='part-build'),
@@ -43,6 +44,8 @@ part_detail_urls = [
url(r'^used/?', views.PartDetail.as_view(template_name='part/used_in.html'), name='part-used-in'),
url(r'^allocation/?', views.PartDetail.as_view(template_name='part/allocation.html'), name='part-allocation'),
url(r'^suppliers/?', views.PartDetail.as_view(template_name='part/supplier.html'), name='part-suppliers'),
+
+ url(r'^qr_code/?', views.PartQRCode.as_view(), name='part-qr'),
url(r'^thumbnail/?', views.PartImage.as_view(), name='part-image'),
diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py
index 2c67df7153..d14827a446 100644
--- a/InvenTree/part/views.py
+++ b/InvenTree/part/views.py
@@ -27,6 +27,7 @@ from .forms import BomExportForm
from .forms import EditSupplierPartForm
from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView
+from InvenTree.views import QRCodeView
from InvenTree.helpers import DownloadFile, str2bool
@@ -234,6 +235,21 @@ class PartDetail(DetailView):
return context
+class PartQRCode(QRCodeView):
+ """ View for displaying a QR code for a Part object """
+
+ ajax_form_title = "Part QR Code"
+
+ 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 PartImage(AjaxUpdateView):
""" View for uploading Part image """
diff --git a/InvenTree/static/script/inventree/modals.js b/InvenTree/static/script/inventree/modals.js
index b331343c3e..bc7228063f 100644
--- a/InvenTree/static/script/inventree/modals.js
+++ b/InvenTree/static/script/inventree/modals.js
@@ -73,6 +73,17 @@ function afterForm(response, options) {
}
}
+function modalShowSubmitButton(modal, show=true) {
+ /* Show (or hide) the 'Submit' button for the given modal form
+ */
+
+ if (show) {
+ $(modal).find('#modal-form-submit').show();
+ } else {
+ $(modal).find('#modal-form-submit').hide();
+ }
+}
+
function modalEnable(modal, enable=true) {
/* Enable (or disable) modal form elements to prevent user input
@@ -444,6 +455,14 @@ function launchModalForm(url, options = {}) {
* an object called 'html_form'
*
* If the request is NOT successful, displays an appropriate error message.
+ *
+ * options:
+ *
+ * modal - Name of the modal (default = '#modal-form')
+ * data - Data to pass through to the AJAX request to fill the form
+ * submit_text - Text for the submit button (default = 'Submit')
+ * close_text - Text for the close button (default = 'Close')
+ * no_post - If true, only display form data, hide submit button, and disallow POST
*/
var modal = options.modal || '#modal-form';
@@ -475,7 +494,13 @@ function launchModalForm(url, options = {}) {
if (response.html_form) {
injectModalForm(modal, response.html_form);
- handleModalForm(url, options);
+
+ if (options.no_post) {
+ modalShowSubmitButton(modal, false);
+ } else {
+ modalShowSubmitButton(modal, true);
+ handleModalForm(url, options);
+ }
} else {
$(modal).modal('hide');
diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py
index 4643ae904c..2c16fd079c 100644
--- a/InvenTree/stock/models.py
+++ b/InvenTree/stock/models.py
@@ -36,7 +36,6 @@ class StockLocation(InvenTreeTree):
def has_items(self):
return self.stock_items.count() > 0
- @property
def format_barcode(self):
""" Return a JSON string for formatting a barcode for this StockLocation object """
@@ -139,7 +138,6 @@ class StockItem(models.Model):
('part', 'serial'),
]
- @property
def format_barcode(self):
""" Return a JSON string for formatting a barcode for this StockItem.
Can be used to perform lookup of a stockitem using barcode
diff --git a/InvenTree/stock/templates/stock/item.html b/InvenTree/stock/templates/stock/item.html
index 30b7902886..ca38ea29ef 100644
--- a/InvenTree/stock/templates/stock/item.html
+++ b/InvenTree/stock/templates/stock/item.html
@@ -2,8 +2,6 @@
{% load static %}
{% block content %}
-{% load qr_code %}
-
@@ -109,9 +109,6 @@
{% endif %}
-
- {% qr_from_text item.format_barcode size="s" image_format="png" error_correction="L" %}
-
@@ -148,6 +145,13 @@
});
});
+ $("#item-qr-code").click(function() {
+ launchModalForm("{% url 'stock-item-qr' item.id %}",
+ {
+ no_post: true,
+ });
+ });
+
{% if item.in_stock %}
$("#stock-move").click(function() {
launchModalForm(
diff --git a/InvenTree/stock/templates/stock/location.html b/InvenTree/stock/templates/stock/location.html
index 66d1fd2179..20769669a2 100644
--- a/InvenTree/stock/templates/stock/location.html
+++ b/InvenTree/stock/templates/stock/location.html
@@ -1,6 +1,5 @@
{% extends "stock/stock_app_base.html" %}
{% load static %}
-{% load qr_code %}
{% block content %}
@@ -24,12 +23,13 @@
- {% qr_from_text location.format_barcode size="s" image_format="png" error_correction="L" %}
{% endif %}
-
-
+
+
@@ -101,6 +101,13 @@
return false;
});
+ $('#location-qr-code').click(function() {
+ launchModalForm("{% url 'stock-location-qr' location.id %}",
+ {
+ no_post: true,
+ });
+ });
+
{% endif %}
$('#item-create').click(function () {
@@ -170,5 +177,4 @@
},
url: "{% url 'api-stock-list' %}",
});
-
{% endblock %}
diff --git a/InvenTree/stock/urls.py b/InvenTree/stock/urls.py
index 6b3fc0ff2d..37e54750de 100644
--- a/InvenTree/stock/urls.py
+++ b/InvenTree/stock/urls.py
@@ -10,6 +10,7 @@ from . import views
stock_location_detail_urls = [
url(r'^edit/?', views.StockLocationEdit.as_view(), name='stock-location-edit'),
url(r'^delete/?', views.StockLocationDelete.as_view(), name='stock-location-delete'),
+ url(r'^qr_code/?', views.StockLocationQRCode.as_view(), name='stock-location-qr'),
# Anything else
url('^.*$', views.StockLocationDetail.as_view(), name='stock-location-detail'),
@@ -20,6 +21,7 @@ stock_item_detail_urls = [
url(r'^delete/?', views.StockItemDelete.as_view(), name='stock-item-delete'),
url(r'^move/?', views.StockItemMove.as_view(), name='stock-item-move'),
url(r'^stocktake/?', views.StockItemStocktake.as_view(), name='stock-item-stocktake'),
+ url(r'^qr_code/?', views.StockItemQRCode.as_view(), name='stock-item-qr'),
url('^.*$', views.StockItemDetail.as_view(), name='stock-item-detail'),
]
diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py
index 7a9513c8c8..c0d1e0dc9d 100644
--- a/InvenTree/stock/views.py
+++ b/InvenTree/stock/views.py
@@ -10,6 +10,7 @@ from django.forms.models import model_to_dict
from django.forms import HiddenInput
from InvenTree.views import AjaxUpdateView, AjaxDeleteView, AjaxCreateView
+from InvenTree.views import QRCodeView
from part.models import Part
from .models import StockItem, StockLocation, StockItemTracking
@@ -75,6 +76,34 @@ class StockLocationEdit(AjaxUpdateView):
ajax_form_title = 'Edit Stock Location'
+class StockLocationQRCode(QRCodeView):
+ """ View for displaying a QR code for a StockLocation object """
+
+ ajax_form_title = "Stock Location QR code"
+
+ 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"
+
+ 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
+
+
class StockItemEdit(AjaxUpdateView):
"""
View for editing details of a single StockItem
diff --git a/InvenTree/templates/qr_code.html b/InvenTree/templates/qr_code.html
new file mode 100644
index 0000000000..9c0c698368
--- /dev/null
+++ b/InvenTree/templates/qr_code.html
@@ -0,0 +1,10 @@
+{% load qr_code %}
+
+
+ {% if qr_data %}
+
+ {% else %}
+
Error:
+ {{ error_msg }}
+ {% endif %}
+
\ No newline at end of file