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
|
abstract = True
|
||||||
unique_together = ('name', 'parent')
|
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
|
# When a category is deleted, graft the children onto its parent
|
||||||
parent = models.ForeignKey('self',
|
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
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
|
# 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.conf.urls import url, include
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
|
from qr_code import urls as qr_code_urls
|
||||||
|
|
||||||
from company.urls import company_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'^logout/', auth_views.LogoutView.as_view(template_name='registration/logout.html'), name='logout'),
|
||||||
url(r'^admin/', admin.site.urls, name='inventree-admin'),
|
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'^index/', IndexView.as_view(), name='index'),
|
||||||
url(r'^search/', SearchView.as_view(), name='search'),
|
url(r'^search/', SearchView.as_view(), name='search'),
|
||||||
|
|
||||||
|
@ -144,6 +144,43 @@ 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):
|
||||||
|
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):
|
class AjaxCreateView(AjaxMixin, CreateView):
|
||||||
|
|
||||||
""" An 'AJAXified' CreateView for creating a new object in the db
|
""" An 'AJAXified' CreateView for creating a new object in the db
|
||||||
|
@ -87,7 +87,10 @@ class Build(models.Model):
|
|||||||
limit_choices_to={'buildable': True},
|
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(
|
quantity = models.PositiveIntegerField(
|
||||||
default=1,
|
default=1,
|
||||||
|
@ -42,7 +42,7 @@ class Company(models.Model):
|
|||||||
It may be a supplier or a customer (or both).
|
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')
|
help_text='Company name')
|
||||||
|
|
||||||
description = models.CharField(max_length=500)
|
description = models.CharField(max_length=500)
|
||||||
|
@ -180,7 +180,6 @@ class Part(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{n} - {d}".format(n=self.name, d=self.description)
|
return "{n} - {d}".format(n=self.name, d=self.description)
|
||||||
|
|
||||||
@property
|
|
||||||
def format_barcode(self):
|
def format_barcode(self):
|
||||||
""" Return a JSON string for formatting a barcode for this Part object """
|
""" Return a JSON string for formatting a barcode for this Part object """
|
||||||
|
|
||||||
|
@ -41,7 +41,15 @@
|
|||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div id='button-toolbar'>
|
<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>
|
</div>
|
||||||
|
|
||||||
<table class='table table-striped table-condensed' data-toolbar='#button-toolbar' id='part-table'>
|
<table class='table table-striped table-condensed' data-toolbar='#button-toolbar' id='part-table'>
|
||||||
@ -50,6 +58,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block js_load %}
|
{% block js_load %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
<script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script>
|
||||||
<script type='text/javacript' src="{% static 'script/inventree/stock.js' %}"></script>
|
<script type='text/javacript' src="{% static 'script/inventree/stock.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block js_ready %}
|
{% block js_ready %}
|
||||||
@ -179,4 +188,9 @@
|
|||||||
url: "{% url 'api-part-list' %}",
|
url: "{% url 'api-part-list' %}",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
linkButtonsToSelection(
|
||||||
|
$("#part-table"),
|
||||||
|
['#part-options']
|
||||||
|
);
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -1,6 +1,5 @@
|
|||||||
{% extends "part/part_base.html" %}
|
{% extends "part/part_base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load qr_code %}
|
|
||||||
{% block details %}
|
{% block details %}
|
||||||
|
|
||||||
{% include 'part/tabs.html' with tab='detail' %}
|
{% include 'part/tabs.html' with tab='detail' %}
|
||||||
@ -15,15 +14,16 @@
|
|||||||
<button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Options
|
<button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Options
|
||||||
<span class="caret"></span></button>
|
<span class="caret"></span></button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
|
{% if part.active %}
|
||||||
<li><a href='#' id='duplicate-part' title='Duplicate Part'>Duplicate</a></li>
|
<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='edit-part' title='Edit part'>Edit</a></li>
|
||||||
<li><a href='#' id='stocktake-part' title='Stocktake'>Stocktake</a></li>
|
<li><a href='#' id='stocktake-part' title='Stocktake'>Stocktake</a></li>
|
||||||
<hr>
|
<hr>
|
||||||
{% if part.active %}
|
|
||||||
<li><a href="#" id='deactivate-part' title='Deactivate part'>Deactivate</a></li>
|
<li><a href="#" id='deactivate-part' title='Deactivate part'>Deactivate</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="#" id='activate-part' title='Activate part'>Activate</a></li>
|
<li><a href="#" id='activate-part' title='Activate part'>Activate</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<li><a href='#' id='show-qr-code' title='Generate QR Code'>Show QR Code</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
</h3>
|
||||||
@ -116,8 +116,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% qr_from_text part.format_barcode size="s" image_format="png" error_correction="L" %}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js_load %}
|
{% block js_load %}
|
||||||
@ -129,6 +127,15 @@
|
|||||||
{% block js_ready %}
|
{% block js_ready %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
|
$("#show-qr-code").click(function() {
|
||||||
|
launchModalForm(
|
||||||
|
"{% url 'part-qr' part.id %}",
|
||||||
|
{
|
||||||
|
no_post: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
$("#duplicate-part").click(function() {
|
$("#duplicate-part").click(function() {
|
||||||
launchModalForm(
|
launchModalForm(
|
||||||
"{% url 'part-create' %}",
|
"{% url 'part-create' %}",
|
||||||
@ -150,6 +157,12 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
$('#activate-part').click(function() {
|
$('#activate-part').click(function() {
|
||||||
|
showQuestionDialog(
|
||||||
|
'Activate Part?',
|
||||||
|
'Are you sure you wish to reactivate {{ part.name }}?',
|
||||||
|
{
|
||||||
|
accept_text: 'Activate',
|
||||||
|
accept: function() {
|
||||||
inventreeUpdate(
|
inventreeUpdate(
|
||||||
"{% url 'api-part-detail' part.id %}",
|
"{% url 'api-part-detail' part.id %}",
|
||||||
{
|
{
|
||||||
@ -160,9 +173,19 @@
|
|||||||
reloadOnSuccess: true,
|
reloadOnSuccess: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#deactivate-part').click(function() {
|
$('#deactivate-part').click(function() {
|
||||||
|
showQuestionDialog(
|
||||||
|
'Deactivate Part?',
|
||||||
|
`Are you sure you wish to deactivate {{ part.name }}?<br>
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
accept_text: 'Deactivate',
|
||||||
|
accept: function() {
|
||||||
inventreeUpdate(
|
inventreeUpdate(
|
||||||
"{% url 'api-part-detail' part.id %}",
|
"{% url 'api-part-detail' part.id %}",
|
||||||
{
|
{
|
||||||
@ -173,6 +196,9 @@
|
|||||||
reloadOnSuccess: true,
|
reloadOnSuccess: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,9 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="media-body">
|
<div class="media-body">
|
||||||
<h4>{{ part.name }}{% if part.active == False %} <i>- INACTIVE</i>{% endif %}</h4>
|
<h4>{{ part.name }}{% if part.active == False %} <i>- INACTIVE</i>{% endif %}</h4>
|
||||||
{% if part.description %}
|
|
||||||
<p><i>{{ part.description }}</i></p>
|
<p><i>{{ part.description }}</i></p>
|
||||||
{% endif %}
|
|
||||||
{% if part.IPN %}
|
{% if part.IPN %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>IPN</td>
|
<td>IPN</td>
|
||||||
|
@ -34,8 +34,9 @@ part_attachment_urls = [
|
|||||||
part_detail_urls = [
|
part_detail_urls = [
|
||||||
url(r'^edit/?', views.PartEdit.as_view(), name='part-edit'),
|
url(r'^edit/?', views.PartEdit.as_view(), name='part-edit'),
|
||||||
url(r'^delete/?', views.PartDelete.as_view(), name='part-delete'),
|
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'^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'^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'^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'),
|
url(r'^build/?', views.PartDetail.as_view(template_name='part/build.html'), name='part-build'),
|
||||||
@ -44,6 +45,8 @@ part_detail_urls = [
|
|||||||
url(r'^allocation/?', views.PartDetail.as_view(template_name='part/allocation.html'), name='part-allocation'),
|
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'^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'),
|
url(r'^thumbnail/?', views.PartImage.as_view(), name='part-image'),
|
||||||
|
|
||||||
# Any other URLs go to the part detail page
|
# Any other URLs go to the part detail page
|
||||||
|
@ -27,6 +27,7 @@ from .forms import BomExportForm
|
|||||||
from .forms import EditSupplierPartForm
|
from .forms import EditSupplierPartForm
|
||||||
|
|
||||||
from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView
|
from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView
|
||||||
|
from InvenTree.views import QRCodeView
|
||||||
|
|
||||||
from InvenTree.helpers import DownloadFile, str2bool
|
from InvenTree.helpers import DownloadFile, str2bool
|
||||||
|
|
||||||
@ -234,6 +235,21 @@ class PartDetail(DetailView):
|
|||||||
return context
|
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):
|
class PartImage(AjaxUpdateView):
|
||||||
""" View for uploading Part image """
|
""" View for uploading Part image """
|
||||||
|
|
||||||
@ -395,6 +411,7 @@ class CategoryDelete(AjaxDeleteView):
|
|||||||
""" Delete view to delete a PartCategory """
|
""" Delete view to delete a PartCategory """
|
||||||
model = PartCategory
|
model = PartCategory
|
||||||
ajax_template_name = 'part/category_delete.html'
|
ajax_template_name = 'part/category_delete.html'
|
||||||
|
ajax_form_title = 'Delete Part Category'
|
||||||
context_object_name = 'category'
|
context_object_name = 'category'
|
||||||
success_url = '/part/'
|
success_url = '/part/'
|
||||||
|
|
||||||
|
@ -152,3 +152,18 @@
|
|||||||
.part-allocation-overallocated {
|
.part-allocation-overallocated {
|
||||||
background: #ccf5ff;
|
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
|
// 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) {
|
function modalEnable(modal, enable=true) {
|
||||||
/* Enable (or disable) modal form elements to prevent user input
|
/* 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.
|
/* Display a modal dialog message box.
|
||||||
*
|
*
|
||||||
* title - Title text
|
* title - Title text
|
||||||
* content - HTML content of the dialog window
|
* content - HTML content of the dialog window
|
||||||
* options:
|
* 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).on('shown.bs.modal', function() {
|
||||||
$(modal + ' .modal-form-content').scrollTop(0);
|
$(modal + ' .modal-form-content').scrollTop(0);
|
||||||
@ -181,6 +192,54 @@ function showDialog(title, content, options={}) {
|
|||||||
$(modal).modal('show');
|
$(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) {
|
function openModal(options) {
|
||||||
/* Open a modal form, and perform some action based on the provided options object:
|
/* Open a modal form, and perform some action based on the provided options object:
|
||||||
*
|
*
|
||||||
@ -215,7 +274,7 @@ function openModal(options) {
|
|||||||
if (options.title) {
|
if (options.title) {
|
||||||
modalSetTitle(modal, options.title);
|
modalSetTitle(modal, options.title);
|
||||||
} else {
|
} else {
|
||||||
modalSetTitle(modal, 'Loading Form Data...');
|
modalSetTitle(modal, 'Loading Data...');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unless the content is explicitly set, display loading message
|
// Unless the content is explicitly set, display loading message
|
||||||
@ -275,12 +334,12 @@ function launchDeleteForm(url, options = {}) {
|
|||||||
else {
|
else {
|
||||||
|
|
||||||
$(modal).modal('hide');
|
$(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) {
|
error: function (xhr, ajaxOptions, thrownError) {
|
||||||
$(modal).modal('hide');
|
$(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) {
|
error: function (xhr, ajaxOptions, thrownError) {
|
||||||
$(modal).modal('hide');
|
$(modal).modal('hide');
|
||||||
showDialog('Error deleting item', renderErrorMessage(xhr));
|
showAlertDialog('Error deleting item', renderErrorMessage(xhr));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -365,7 +424,7 @@ function handleModalForm(url, options) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$(modal).modal('hide');
|
$(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
|
// There was an error submitting form data via POST
|
||||||
|
|
||||||
$(modal).modal('hide');
|
$(modal).modal('hide');
|
||||||
showDialog('Error posting form data', renderErrorMessage(xhr));
|
showAlertDialog('Error posting form data', renderErrorMessage(xhr));
|
||||||
},
|
},
|
||||||
complete: function(xhr) {
|
complete: function(xhr) {
|
||||||
//TODO
|
//TODO
|
||||||
@ -396,6 +455,14 @@ function launchModalForm(url, options = {}) {
|
|||||||
* an object called 'html_form'
|
* an object called 'html_form'
|
||||||
*
|
*
|
||||||
* If the request is NOT successful, displays an appropriate error message.
|
* 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';
|
var modal = options.modal || '#modal-form';
|
||||||
@ -427,16 +494,22 @@ function launchModalForm(url, options = {}) {
|
|||||||
|
|
||||||
if (response.html_form) {
|
if (response.html_form) {
|
||||||
injectModalForm(modal, response.html_form);
|
injectModalForm(modal, response.html_form);
|
||||||
|
|
||||||
|
if (options.no_post) {
|
||||||
|
modalShowSubmitButton(modal, false);
|
||||||
|
} else {
|
||||||
|
modalShowSubmitButton(modal, true);
|
||||||
handleModalForm(url, options);
|
handleModalForm(url, options);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$(modal).modal('hide');
|
$(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) {
|
error: function (xhr, ajaxOptions, thrownError) {
|
||||||
$(modal).modal('hide');
|
$(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):
|
def has_items(self):
|
||||||
return self.stock_items.count() > 0
|
return self.stock_items.count() > 0
|
||||||
|
|
||||||
@property
|
|
||||||
def format_barcode(self):
|
def format_barcode(self):
|
||||||
""" Return a JSON string for formatting a barcode for this StockLocation object """
|
""" Return a JSON string for formatting a barcode for this StockLocation object """
|
||||||
|
|
||||||
@ -139,7 +138,6 @@ class StockItem(models.Model):
|
|||||||
('part', 'serial'),
|
('part', 'serial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
|
||||||
def format_barcode(self):
|
def format_barcode(self):
|
||||||
""" Return a JSON string for formatting a barcode for this StockItem.
|
""" Return a JSON string for formatting a barcode for this StockItem.
|
||||||
Can be used to perform lookup of a stockitem using barcode
|
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)
|
date = models.DateTimeField(auto_now_add=True, editable=False)
|
||||||
|
|
||||||
# Short-form title for this tracking entry
|
# Short-form title for this tracking entry
|
||||||
title = models.CharField(max_length=250)
|
title = models.CharField(blank=False, max_length=250)
|
||||||
|
|
||||||
# Optional longer description
|
# Optional longer description
|
||||||
notes = models.TextField(blank=True)
|
notes = models.TextField(blank=True)
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% load qr_code %}
|
|
||||||
|
|
||||||
<div class='row'>
|
<div class='row'>
|
||||||
<div class='col-sm-6'>
|
<div class='col-sm-6'>
|
||||||
<h3>Stock Item Details</h3>
|
<h3>Stock Item Details</h3>
|
||||||
@ -25,6 +23,8 @@
|
|||||||
<li><a href='#' id='stock-stocktake' title='Count stock'>Stocktake</a></li>
|
<li><a href='#' id='stock-stocktake' title='Count stock'>Stocktake</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li><a href="#" id='stock-delete' title='Delete stock item'>Delete stock item</a></li>
|
<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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -109,9 +109,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class='col-sm-6'>
|
|
||||||
{% qr_from_text item.format_barcode size="s" image_format="png" error_correction="L" %}
|
|
||||||
</div>
|
|
||||||
</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 %}
|
{% if item.in_stock %}
|
||||||
$("#stock-move").click(function() {
|
$("#stock-move").click(function() {
|
||||||
launchModalForm(
|
launchModalForm(
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{% extends "stock/stock_app_base.html" %}
|
{% extends "stock/stock_app_base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load qr_code %}
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class='row'>
|
<div class='row'>
|
||||||
@ -24,12 +23,13 @@
|
|||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a href="#" id='location-edit' title='Edit stock location'>Edit</a></li>
|
<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>
|
<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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% qr_from_text location.format_barcode size="s" image_format="png" error_correction="L" %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -43,10 +43,9 @@
|
|||||||
|
|
||||||
<div id='button-toolbar'>
|
<div id='button-toolbar'>
|
||||||
<div class='container-fluid' style='float: right;'>
|
<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;'>
|
<div class="dropdown" style='float: right;'>
|
||||||
<button id='stock-options' class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Options
|
<button id='stock-options' class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Options<span class="caret"></span></button>
|
||||||
<span class="caret"></span></button>
|
|
||||||
<ul class="dropdown-menu">
|
<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-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>
|
<li><a href="#" id='multi-item-remove' title='Remove from selected stock items'>Remove stock</a></li>
|
||||||
@ -102,6 +101,13 @@
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#location-qr-code').click(function() {
|
||||||
|
launchModalForm("{% url 'stock-location-qr' location.id %}",
|
||||||
|
{
|
||||||
|
no_post: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
$('#item-create').click(function () {
|
$('#item-create').click(function () {
|
||||||
@ -171,5 +177,4 @@
|
|||||||
},
|
},
|
||||||
url: "{% url 'api-stock-list' %}",
|
url: "{% url 'api-stock-list' %}",
|
||||||
});
|
});
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -10,6 +10,7 @@ from . import views
|
|||||||
stock_location_detail_urls = [
|
stock_location_detail_urls = [
|
||||||
url(r'^edit/?', views.StockLocationEdit.as_view(), name='stock-location-edit'),
|
url(r'^edit/?', views.StockLocationEdit.as_view(), name='stock-location-edit'),
|
||||||
url(r'^delete/?', views.StockLocationDelete.as_view(), name='stock-location-delete'),
|
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
|
# Anything else
|
||||||
url('^.*$', views.StockLocationDetail.as_view(), name='stock-location-detail'),
|
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'^delete/?', views.StockItemDelete.as_view(), name='stock-item-delete'),
|
||||||
url(r'^move/?', views.StockItemMove.as_view(), name='stock-item-move'),
|
url(r'^move/?', views.StockItemMove.as_view(), name='stock-item-move'),
|
||||||
url(r'^stocktake/?', views.StockItemStocktake.as_view(), name='stock-item-stocktake'),
|
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'),
|
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 django.forms import HiddenInput
|
||||||
|
|
||||||
from InvenTree.views import AjaxUpdateView, AjaxDeleteView, AjaxCreateView
|
from InvenTree.views import AjaxUpdateView, AjaxDeleteView, AjaxCreateView
|
||||||
|
from InvenTree.views import QRCodeView
|
||||||
|
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
from .models import StockItem, StockLocation, StockItemTracking
|
from .models import StockItem, StockLocation, StockItemTracking
|
||||||
@ -75,6 +76,34 @@ class StockLocationEdit(AjaxUpdateView):
|
|||||||
ajax_form_title = 'Edit Stock Location'
|
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):
|
class StockItemEdit(AjaxUpdateView):
|
||||||
"""
|
"""
|
||||||
View for editing details of a single StockItem
|
View for editing details of a single StockItem
|
||||||
@ -205,7 +234,7 @@ class StockLocationDelete(AjaxDeleteView):
|
|||||||
|
|
||||||
model = StockLocation
|
model = StockLocation
|
||||||
success_url = '/stock'
|
success_url = '/stock'
|
||||||
template_name = 'stock/location_delete.html'
|
ajax_template_name = 'stock/location_delete.html'
|
||||||
context_object_name = 'location'
|
context_object_name = 'location'
|
||||||
ajax_form_title = 'Delete Stock Location'
|
ajax_form_title = 'Delete Stock Location'
|
||||||
|
|
||||||
|
@ -39,14 +39,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</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-dialog'>
|
||||||
<div class='modal-content'>
|
<div class='modal-content'>
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
<h3 id='modal-title'>Confirm Item Deletion</h3>
|
<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'>Alert Information</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class='modal-form-content'>
|
<div class='modal-form-content'>
|
||||||
</div>
|
</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