mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
commit
d5ad888b0a
@ -23,9 +23,16 @@ class InvenTreeTree(models.Model):
|
||||
abstract = True
|
||||
unique_together = ('name', 'parent')
|
||||
|
||||
name = models.CharField(max_length=100, unique=True)
|
||||
name = models.CharField(
|
||||
blank=False,
|
||||
max_length=100,
|
||||
unique=True
|
||||
)
|
||||
|
||||
description = models.CharField(max_length=250)
|
||||
description = models.CharField(
|
||||
blank=False,
|
||||
max_length=250
|
||||
)
|
||||
|
||||
# When a category is deleted, graft the children onto its parent
|
||||
parent = models.ForeignKey('self',
|
||||
|
@ -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
|
||||
|
@ -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'),
|
||||
|
||||
|
@ -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
|
||||
|
@ -87,7 +87,10 @@ class Build(models.Model):
|
||||
limit_choices_to={'buildable': True},
|
||||
)
|
||||
|
||||
title = models.CharField(max_length=100, help_text='Brief description of the build')
|
||||
title = models.CharField(
|
||||
blank=False,
|
||||
max_length=100,
|
||||
help_text='Brief description of the build')
|
||||
|
||||
quantity = models.PositiveIntegerField(
|
||||
default=1,
|
||||
|
@ -42,7 +42,7 @@ class Company(models.Model):
|
||||
It may be a supplier or a customer (or both).
|
||||
"""
|
||||
|
||||
name = models.CharField(max_length=100, unique=True,
|
||||
name = models.CharField(max_length=100, blank=False, unique=True,
|
||||
help_text='Company name')
|
||||
|
||||
description = models.CharField(max_length=500)
|
||||
|
@ -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 """
|
||||
|
||||
|
@ -41,7 +41,15 @@
|
||||
<hr>
|
||||
|
||||
<div id='button-toolbar'>
|
||||
<button style='float: right;' class='btn btn-success' id='part-create'>New Part</button>
|
||||
<div class='container-fluid' style="float: right;">
|
||||
<button class='btn btn-success' id='part-create'>New Part</button>
|
||||
<div class='dropdown' style='float: right;'>
|
||||
<button id='part-options' class='btn btn-primary dropdown-toggle' type='button' data-toggle="dropdown">Options<span class='caret'></span></button>
|
||||
<ul class='dropdown-menu'>
|
||||
<li><a href='#' id='multi-part-category' title='Set Part Category'>Set Category</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class='table table-striped table-condensed' data-toolbar='#button-toolbar' id='part-table'>
|
||||
@ -50,6 +58,7 @@
|
||||
{% endblock %}
|
||||
{% block js_load %}
|
||||
{{ block.super }}
|
||||
<script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script>
|
||||
<script type='text/javacript' src="{% static 'script/inventree/stock.js' %}"></script>
|
||||
{% endblock %}
|
||||
{% block js_ready %}
|
||||
@ -179,4 +188,9 @@
|
||||
url: "{% url 'api-part-list' %}",
|
||||
});
|
||||
|
||||
linkButtonsToSelection(
|
||||
$("#part-table"),
|
||||
['#part-options']
|
||||
);
|
||||
|
||||
{% endblock %}
|
@ -1,6 +1,5 @@
|
||||
{% extends "part/part_base.html" %}
|
||||
{% load static %}
|
||||
{% load qr_code %}
|
||||
{% block details %}
|
||||
|
||||
{% include 'part/tabs.html' with tab='detail' %}
|
||||
@ -15,15 +14,16 @@
|
||||
<button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Options
|
||||
<span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
{% if part.active %}
|
||||
<li><a href='#' id='duplicate-part' title='Duplicate Part'>Duplicate</a></li>
|
||||
<li><a href="#" id='edit-part' title='Edit part'>Edit</a></li>
|
||||
<li><a href='#' id='stocktake-part' title='Stocktake'>Stocktake</a></li>
|
||||
<hr>
|
||||
{% if part.active %}
|
||||
<li><a href="#" id='deactivate-part' title='Deactivate part'>Deactivate</a></li>
|
||||
{% else %}
|
||||
<li><a href="#" id='activate-part' title='Activate part'>Activate</a></li>
|
||||
{% endif %}
|
||||
<li><a href='#' id='show-qr-code' title='Generate QR Code'>Show QR Code</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</h3>
|
||||
@ -116,8 +116,6 @@
|
||||
</div>
|
||||
{% 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(
|
||||
@ -150,27 +157,46 @@
|
||||
});
|
||||
|
||||
$('#activate-part').click(function() {
|
||||
inventreeUpdate(
|
||||
"{% url 'api-part-detail' part.id %}",
|
||||
showQuestionDialog(
|
||||
'Activate Part?',
|
||||
'Are you sure you wish to reactivate {{ part.name }}?',
|
||||
{
|
||||
active: true,
|
||||
accept_text: 'Activate',
|
||||
accept: function() {
|
||||
inventreeUpdate(
|
||||
"{% url 'api-part-detail' part.id %}",
|
||||
{
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
method: 'PATCH',
|
||||
reloadOnSuccess: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
method: 'PATCH',
|
||||
reloadOnSuccess: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$('#deactivate-part').click(function() {
|
||||
inventreeUpdate(
|
||||
"{% url 'api-part-detail' part.id %}",
|
||||
showQuestionDialog(
|
||||
'Deactivate Part?',
|
||||
`Are you sure you wish to deactivate {{ part.name }}?<br>
|
||||
`,
|
||||
{
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
method: 'PATCH',
|
||||
reloadOnSuccess: true,
|
||||
accept_text: 'Deactivate',
|
||||
accept: function() {
|
||||
inventreeUpdate(
|
||||
"{% url 'api-part-detail' part.id %}",
|
||||
{
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
method: 'PATCH',
|
||||
reloadOnSuccess: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -22,9 +22,7 @@
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<h4>{{ part.name }}{% if part.active == False %} <i>- INACTIVE</i>{% endif %}</h4>
|
||||
{% if part.description %}
|
||||
<p><i>{{ part.description }}</i></p>
|
||||
{% endif %}
|
||||
{% if part.IPN %}
|
||||
<tr>
|
||||
<td>IPN</td>
|
||||
|
@ -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'),
|
||||
|
||||
|
@ -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 """
|
||||
|
||||
@ -395,6 +411,7 @@ class CategoryDelete(AjaxDeleteView):
|
||||
""" Delete view to delete a PartCategory """
|
||||
model = PartCategory
|
||||
ajax_template_name = 'part/category_delete.html'
|
||||
ajax_form_title = 'Delete Part Category'
|
||||
context_object_name = 'category'
|
||||
success_url = '/part/'
|
||||
|
||||
|
@ -152,3 +152,18 @@
|
||||
.part-allocation-overallocated {
|
||||
background: #ccf5ff;
|
||||
}
|
||||
|
||||
.glyphicon-refresh-animate {
|
||||
-animation: spin .7s infinite linear;
|
||||
-webkit-animation: spin2 .7s infinite linear;
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin2 {
|
||||
from { -webkit-transform: rotate(0deg);}
|
||||
to { -webkit-transform: rotate(360deg);}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: scale(1) rotate(0deg);}
|
||||
to { transform: scale(1) rotate(360deg);}
|
||||
}
|
@ -24,7 +24,7 @@ function loadingMessageContent() {
|
||||
*/
|
||||
|
||||
// TODO - This can be made a lot better
|
||||
return '<b>Loading...</b>';
|
||||
return "<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> Waiting for server...";
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
@ -155,16 +166,16 @@ function renderErrorMessage(xhr) {
|
||||
}
|
||||
|
||||
|
||||
function showDialog(title, content, options={}) {
|
||||
function showAlertDialog(title, content, options={}) {
|
||||
/* Display a modal dialog message box.
|
||||
*
|
||||
* title - Title text
|
||||
* content - HTML content of the dialog window
|
||||
* options:
|
||||
* modal - modal form to use (default = '#modal-dialog')
|
||||
* modal - modal form to use (default = '#modal-alert-dialog')
|
||||
*/
|
||||
|
||||
var modal = options.modal || '#modal-dialog';
|
||||
var modal = options.modal || '#modal-alert-dialog';
|
||||
|
||||
$(modal).on('shown.bs.modal', function() {
|
||||
$(modal + ' .modal-form-content').scrollTop(0);
|
||||
@ -181,6 +192,54 @@ function showDialog(title, content, options={}) {
|
||||
$(modal).modal('show');
|
||||
}
|
||||
|
||||
|
||||
function showQuestionDialog(title, content, options={}) {
|
||||
/* Display a modal dialog for user input (Yes/No confirmation dialog)
|
||||
*
|
||||
* title - Title text
|
||||
* content - HTML content of the dialog window
|
||||
* options:
|
||||
* modal - Modal target (default = 'modal-question-dialog')
|
||||
* accept_text - Text for the accept button (default = 'Accept')
|
||||
* cancel_text - Text for the cancel button (default = 'Cancel')
|
||||
* accept - Function to run if the user presses 'Accept'
|
||||
* cancel - Functino to run if the user presses 'Cancel'
|
||||
*/
|
||||
|
||||
var modal = options.modal || '#modal-question-dialog';
|
||||
|
||||
$(modal).on('shown.bs.modal', function() {
|
||||
$(modal + ' .modal-form-content').scrollTop(0);
|
||||
});
|
||||
|
||||
modalSetTitle(modal, title);
|
||||
modalSetContent(modal, content);
|
||||
|
||||
var accept_text = options.accept_text || 'Accept';
|
||||
var cancel_text = options.cancel_text || 'Cancel';
|
||||
|
||||
$(modal).find('#modal-form-cancel').html(cancel_text);
|
||||
$(modal).find('#modal-form-accept').html(accept_text);
|
||||
|
||||
$(modal).on('click', '#modal-form-accept', function() {
|
||||
$(modal).modal('hide');
|
||||
|
||||
if (options.accept) {
|
||||
options.accept();
|
||||
}
|
||||
});
|
||||
|
||||
$(modal).on('click', 'modal-form-cancel', function() {
|
||||
$(modal).modal('hide');
|
||||
|
||||
if (options.cancel) {
|
||||
options.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
$(modal).modal('show');
|
||||
}
|
||||
|
||||
function openModal(options) {
|
||||
/* Open a modal form, and perform some action based on the provided options object:
|
||||
*
|
||||
@ -215,7 +274,7 @@ function openModal(options) {
|
||||
if (options.title) {
|
||||
modalSetTitle(modal, options.title);
|
||||
} else {
|
||||
modalSetTitle(modal, 'Loading Form Data...');
|
||||
modalSetTitle(modal, 'Loading Data...');
|
||||
}
|
||||
|
||||
// Unless the content is explicitly set, display loading message
|
||||
@ -275,12 +334,12 @@ function launchDeleteForm(url, options = {}) {
|
||||
else {
|
||||
|
||||
$(modal).modal('hide');
|
||||
showDialog('Invalid form response', 'JSON response missing HTML data');
|
||||
showAlertDialog('Invalid form response', 'JSON response missing HTML data');
|
||||
}
|
||||
},
|
||||
error: function (xhr, ajaxOptions, thrownError) {
|
||||
$(modal).modal('hide');
|
||||
showDialog('Error requesting form data', renderErrorMessage(xhr));
|
||||
showAlertDialog('Error requesting form data', renderErrorMessage(xhr));
|
||||
}
|
||||
});
|
||||
|
||||
@ -299,7 +358,7 @@ function launchDeleteForm(url, options = {}) {
|
||||
},
|
||||
error: function (xhr, ajaxOptions, thrownError) {
|
||||
$(modal).modal('hide');
|
||||
showDialog('Error deleting item', renderErrorMessage(xhr));
|
||||
showAlertDialog('Error deleting item', renderErrorMessage(xhr));
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -365,7 +424,7 @@ function handleModalForm(url, options) {
|
||||
}
|
||||
else {
|
||||
$(modal).modal('hide');
|
||||
showDialog('Invalid response from server', 'Form data missing from server response');
|
||||
showAlertDialog('Invalid response from server', 'Form data missing from server response');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -378,7 +437,7 @@ function handleModalForm(url, options) {
|
||||
// There was an error submitting form data via POST
|
||||
|
||||
$(modal).modal('hide');
|
||||
showDialog('Error posting form data', renderErrorMessage(xhr));
|
||||
showAlertDialog('Error posting form data', renderErrorMessage(xhr));
|
||||
},
|
||||
complete: function(xhr) {
|
||||
//TODO
|
||||
@ -396,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';
|
||||
@ -427,16 +494,22 @@ 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');
|
||||
showDialog('Invalid server response', 'JSON response missing form data');
|
||||
showAlertDialog('Invalid server response', 'JSON response missing form data');
|
||||
}
|
||||
},
|
||||
error: function (xhr, ajaxOptions, thrownError) {
|
||||
$(modal).modal('hide');
|
||||
showDialog('Error requesting form data', renderErrorMessage(xhr));
|
||||
showAlertDialog('Error requesting form data', renderErrorMessage(xhr));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
@ -390,7 +388,7 @@ class StockItemTracking(models.Model):
|
||||
date = models.DateTimeField(auto_now_add=True, editable=False)
|
||||
|
||||
# Short-form title for this tracking entry
|
||||
title = models.CharField(max_length=250)
|
||||
title = models.CharField(blank=False, max_length=250)
|
||||
|
||||
# Optional longer description
|
||||
notes = models.TextField(blank=True)
|
||||
|
@ -2,8 +2,6 @@
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
|
||||
{% load qr_code %}
|
||||
|
||||
<div class='row'>
|
||||
<div class='col-sm-6'>
|
||||
<h3>Stock Item Details</h3>
|
||||
@ -25,6 +23,8 @@
|
||||
<li><a href='#' id='stock-stocktake' title='Count stock'>Stocktake</a></li>
|
||||
{% endif %}
|
||||
<li><a href="#" id='stock-delete' title='Delete stock item'>Delete stock item</a></li>
|
||||
<hr>
|
||||
<li><a href="#" id='item-qr-code' title='Generate QR code'>Show QR code</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@ -109,9 +109,6 @@
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
<div class='col-sm-6'>
|
||||
{% qr_from_text item.format_barcode size="s" image_format="png" error_correction="L" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -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(
|
||||
|
@ -1,6 +1,5 @@
|
||||
{% extends "stock/stock_app_base.html" %}
|
||||
{% load static %}
|
||||
{% load qr_code %}
|
||||
{% block content %}
|
||||
|
||||
<div class='row'>
|
||||
@ -24,12 +23,13 @@
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#" id='location-edit' title='Edit stock location'>Edit</a></li>
|
||||
<li><a href="#" id='location-delete' title='Delete stock location'>Delete</a></li>
|
||||
<hr>
|
||||
<li><a href="#" id='location-qr-code' title='Generate QR code'>Show QR code</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% qr_from_text location.format_barcode size="s" image_format="png" error_correction="L" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</h3>
|
||||
</div>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -43,10 +43,9 @@
|
||||
|
||||
<div id='button-toolbar'>
|
||||
<div class='container-fluid' style='float: right;'>
|
||||
<button class="btn btn-success" id='item-create'>New Stock Item</span></button>
|
||||
<button class="btn btn-success" id='item-create'>New Stock Item</button>
|
||||
<div class="dropdown" style='float: right;'>
|
||||
<button id='stock-options' class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Options
|
||||
<span class="caret"></span></button>
|
||||
<button id='stock-options' class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Options<span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#" id='multi-item-add' title='Add to selected stock items'>Add stock</a></li>
|
||||
<li><a href="#" id='multi-item-remove' title='Remove from selected stock items'>Remove stock</a></li>
|
||||
@ -102,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 () {
|
||||
@ -171,5 +177,4 @@
|
||||
},
|
||||
url: "{% url 'api-stock-list' %}",
|
||||
});
|
||||
|
||||
{% endblock %}
|
||||
|
@ -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'),
|
||||
]
|
||||
|
@ -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
|
||||
@ -205,7 +234,7 @@ class StockLocationDelete(AjaxDeleteView):
|
||||
|
||||
model = StockLocation
|
||||
success_url = '/stock'
|
||||
template_name = 'stock/location_delete.html'
|
||||
ajax_template_name = 'stock/location_delete.html'
|
||||
context_object_name = 'location'
|
||||
ajax_form_title = 'Delete Stock Location'
|
||||
|
||||
|
@ -39,14 +39,33 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='modal fade modal-fixed-footer' tabindex='-1' role='dialog' id='modal-dialog'>
|
||||
<div class='modal fade modal-fixed-footer' tabindex='-1' role='dialog' id='modal-question-dialog'>
|
||||
<div class='modal-dialog'>
|
||||
<div class='modal-content'>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h3 id='modal-title'>Question Here</h3>
|
||||
</div>
|
||||
<div class='modal-form-content'>
|
||||
</div>
|
||||
<div class='modal-footer'>
|
||||
<button type='button' class='btn btn-default' id='modal-form-cancel'>Cancel</button>
|
||||
<button type='button' class='btn btn-primary' id='modal-form-accept'>Accept</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='modal fade modal-fixed-footer' tabindex='-1' role='dialog' id='modal-alert-dialog'>
|
||||
<div class='modal-dialog'>
|
||||
<div class='modal-content'>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h3 id='modal-title'>Confirm Item Deletion</h3>
|
||||
<h3 id='modal-title'>Alert Information</h3>
|
||||
</div>
|
||||
<div class='modal-form-content'>
|
||||
</div>
|
||||
|
10
InvenTree/templates/qr_code.html
Normal file
10
InvenTree/templates/qr_code.html
Normal file
@ -0,0 +1,10 @@
|
||||
{% load qr_code %}
|
||||
|
||||
<div class='container' style='width: 80%;'>
|
||||
{% if qr_data %}
|
||||
<img src="{% qr_url_from_text qr_data size='m' error_correction='q' %}" alt="QR Code">
|
||||
{% else %}
|
||||
<b>Error:</b><br>
|
||||
{{ error_msg }}
|
||||
{% endif %}
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user