Merge branch 'master' of https://github.com/inventree/InvenTree into homepage-settings

This commit is contained in:
matmair committed 2021-07-23 00:03:21 +02:00
commit 50356afd41
50 files changed
+2692 -177

No files matched your search

+4 -3
View File
@@ -20,7 +20,6 @@ from djmoney.forms.fields import MoneyField
from djmoney.models.validators import MinMoneyValidator
import InvenTree.helpers
import common.settings
class InvenTreeURLFormField(FormURLField):
@@ -42,9 +41,11 @@ class InvenTreeURLField(models.URLField):
def money_kwargs():
""" returns the database settings for MoneyFields """
from common.settings import currency_code_mappings, currency_code_default
kwargs = {}
kwargs['currency_choices'] = common.settings.currency_code_mappings()
kwargs['default_currency'] = common.settings.currency_code_default
kwargs['currency_choices'] = currency_code_mappings()
kwargs['default_currency'] = currency_code_default()
return kwargs
+24 -3
View File
@@ -631,13 +631,34 @@ def clean_decimal(number):
""" Clean-up decimal value """
# Check if empty
if number is None or number == '':
if number is None or number == '' or number == 0:
return Decimal(0)
# Check if decimal type
# Convert to string and remove spaces
number = str(number).replace(' ', '')
# Guess what type of decimal and thousands separators are used
count_comma = number.count(',')
count_point = number.count('.')
if count_comma == 1:
# Comma is used as decimal separator
if count_point > 0:
# Points are used as thousands separators: remove them
number = number.replace('.', '')
# Replace decimal separator with point
number = number.replace(',', '.')
elif count_point == 1:
# Point is used as decimal separator
if count_comma > 0:
# Commas are used as thousands separators: remove them
number = number.replace(',', '')
# Convert to Decimal type
try:
clean_number = Decimal(number)
except InvalidOperation:
clean_number = number
# Number cannot be converted to Decimal (eg. a string containing letters)
return Decimal(0)
return clean_number.quantize(Decimal(1)) if clean_number == clean_number.to_integral() else clean_number.normalize()
+39
View File
@@ -32,6 +32,9 @@ class InvenTreeMetadata(SimpleMetadata):
def determine_metadata(self, request, view):
self.request = request
self.view = view
metadata = super().determine_metadata(request, view)
user = request.user
@@ -136,6 +139,42 @@ class InvenTreeMetadata(SimpleMetadata):
except AttributeError:
pass
# Try to extract 'instance' information
instance = None
# Extract extra information if an instance is available
if hasattr(serializer, 'instance'):
instance = serializer.instance
if instance is None:
try:
instance = self.view.get_object()
except:
pass
if instance is not None:
"""
If there is an instance associated with this API View,
introspect that instance to find any specific API info.
"""
if hasattr(instance, 'api_instance_filters'):
instance_filters = instance.api_instance_filters()
for field_name, field_filters in instance_filters.items():
if field_name not in serializer_info.keys():
# The field might be missing, but is added later on
# This function seems to get called multiple times?
continue
if 'instance_filters' not in serializer_info[field_name].keys():
serializer_info[field_name]['instance_filters'] = {}
for key, value in field_filters.items():
serializer_info[field_name]['instance_filters'][key] = value
return serializer_info
def get_field_info(self, field):
+11
View File
@@ -93,6 +93,17 @@ class InvenTreeTree(MPTTModel):
parent: The item immediately above this one. An item with a null parent is a top-level item
"""
def api_instance_filters(self):
"""
Instance filters for InvenTreeTree models
"""
return {
'parent': {
'exclude_tree': self.pk,
}
}
def save(self, *args, **kwargs):
try:
@@ -1037,3 +1037,10 @@ a.anchor {
height: 30px;
}
.search-menu {
padding-top: 2rem;
}
.search-menu .ui-menu-item {
margin-top: 0.5rem;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

@@ -59,6 +59,11 @@
<h1>YOUR COMPONENTS:</h1>
<!-- Autocomplete -->
<h2 class="demoHeaders">Autocomplete</h2>
<div>
<input id="autocomplete" title="type &quot;a&quot;">
</div>
@@ -248,6 +253,23 @@
<!-- Menu -->
<h2 class="demoHeaders">Menu</h2>
<ul style="width:100px;" id="menu">
<li><div>Item 1</div></li>
<li><div>Item 2</div></li>
<li><div>Item 3</div>
<ul>
<li><div>Item 3-1</div></li>
<li><div>Item 3-2</div></li>
<li><div>Item 3-3</div></li>
<li><div>Item 3-4</div></li>
<li><div>Item 3-5</div></li>
</ul>
</li>
<li><div>Item 4</div></li>
<li><div>Item 5</div></li>
</ul>
<!-- Highlight / Error -->
@@ -270,6 +292,33 @@
<script src="jquery-ui.js"></script>
<script>
var availableTags = [
"ActionScript",
"AppleScript",
"Asp",
"BASIC",
"C",
"C++",
"Clojure",
"COBOL",
"ColdFusion",
"Erlang",
"Fortran",
"Groovy",
"Haskell",
"Java",
"JavaScript",
"Lisp",
"Perl",
"PHP",
"Python",
"Ruby",
"Scala",
"Scheme"
];
$( "#autocomplete" ).autocomplete({
source: availableTags
});
@@ -280,6 +329,7 @@
$( "#menu" ).menu();
+62 -2
View File
@@ -1,6 +1,6 @@
/*! jQuery UI - v1.12.1 - 2021-02-23
/*! jQuery UI - v1.12.1 - 2021-07-18
* http://jqueryui.com
* Includes: core.css, resizable.css, theme.css
* Includes: core.css, resizable.css, autocomplete.css, menu.css, theme.css
* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=base&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cc0000&fcError=5f3f3f&borderColorError=f1a899&bgTextureError=flat&bgColorError=fddfdf&iconColorHighlight=777620&fcHighlight=777620&borderColorHighlight=dad55e&bgTextureHighlight=flat&bgColorHighlight=fffa90&iconColorActive=ffffff&fcActive=ffffff&borderColorActive=003eff&bgTextureActive=flat&bgColorActive=007fff&iconColorHover=555555&fcHover=2b2b2b&borderColorHover=cccccc&bgTextureHover=flat&bgColorHover=ededed&iconColorDefault=777777&fcDefault=454545&borderColorDefault=c5c5c5&bgTextureDefault=flat&bgColorDefault=f6f6f6&iconColorContent=444444&fcContent=333333&borderColorContent=dddddd&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=444444&fcHeader=333333&borderColorHeader=dddddd&bgTextureHeader=flat&bgColorHeader=e9e9e9&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif
* Copyright jQuery Foundation and other contributors; Licensed MIT */
@@ -160,6 +160,66 @@
right: -5px;
top: -5px;
}
.ui-autocomplete {
position: absolute;
top: 0;
left: 0;
cursor: default;
}
.ui-menu {
list-style: none;
padding: 0;
margin: 0;
display: block;
outline: 0;
}
.ui-menu .ui-menu {
position: absolute;
}
.ui-menu .ui-menu-item {
margin: 0;
cursor: pointer;
/* support: IE10, see #8844 */
list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
}
.ui-menu .ui-menu-item-wrapper {
position: relative;
padding: 3px 1em 3px .4em;
}
.ui-menu .ui-menu-divider {
margin: 5px 0;
height: 0;
font-size: 0;
line-height: 0;
border-width: 1px 0 0 0;
}
.ui-menu .ui-state-focus,
.ui-menu .ui-state-active {
margin: -1px;
}
/* icon support */
.ui-menu-icons {
position: relative;
}
.ui-menu-icons .ui-menu-item-wrapper {
padding-left: 2em;
}
/* left-aligned */
.ui-menu .ui-icon {
position: absolute;
top: 0;
bottom: 0;
left: .2em;
margin: auto 0;
}
/* right-aligned */
.ui-menu .ui-menu-icon {
left: auto;
right: 0;
}
/* Component containers
----------------------------------*/
File diff suppressed because it is too large. Load diff
@@ -1,7 +1,7 @@
/*! jQuery UI - v1.12.1 - 2021-02-23
/*! jQuery UI - v1.12.1 - 2021-07-18
* http://jqueryui.com
* Includes: core.css, resizable.css, theme.css
* Includes: core.css, resizable.css, autocomplete.css, menu.css, theme.css
* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=base&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cc0000&fcError=5f3f3f&borderColorError=f1a899&bgTextureError=flat&bgColorError=fddfdf&iconColorHighlight=777620&fcHighlight=777620&borderColorHighlight=dad55e&bgTextureHighlight=flat&bgColorHighlight=fffa90&iconColorActive=ffffff&fcActive=ffffff&borderColorActive=003eff&bgTextureActive=flat&bgColorActive=007fff&iconColorHover=555555&fcHover=2b2b2b&borderColorHover=cccccc&bgTextureHover=flat&bgColorHover=ededed&iconColorDefault=777777&fcDefault=454545&borderColorDefault=c5c5c5&bgTextureDefault=flat&bgColorDefault=f6f6f6&iconColorContent=444444&fcContent=333333&borderColorContent=dddddd&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=444444&fcHeader=333333&borderColorHeader=dddddd&bgTextureHeader=flat&bgColorHeader=e9e9e9&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif
* Copyright jQuery Foundation and other contributors; Licensed MIT */
.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-widget{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget.ui-widget-content{border:1px solid #c5c5c5}.ui-widget-content{border:1px solid #ddd;background:#fff;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #ddd;background:#e9e9e9;color:#333;font-weight:bold}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default,.ui-button,html .ui-button.ui-state-disabled:hover,html .ui-button.ui-state-disabled:active{border:1px solid #c5c5c5;background:#f6f6f6;font-weight:normal;color:#454545}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited,a.ui-button,a:link.ui-button,a:visited.ui-button,.ui-button{color:#454545;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus,.ui-button:hover,.ui-button:focus{border:1px solid #ccc;background:#ededed;font-weight:normal;color:#2b2b2b}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited,a.ui-button:hover,a.ui-button:focus{color:#2b2b2b;text-decoration:none}.ui-visual-focus{box-shadow:0 0 3px 1px rgb(94,158,214)}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active,a.ui-button:active,.ui-button:active,.ui-button.ui-state-active:hover{border:1px solid #003eff;background:#007fff;font-weight:normal;color:#fff}.ui-icon-background,.ui-state-active .ui-icon-background{border:#003eff;background-color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #dad55e;background:#fffa90;color:#777620}.ui-state-checked{border:1px solid #dad55e;background:#fffa90}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#777620}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #f1a899;background:#fddfdf;color:#5f3f3f}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#5f3f3f}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#5f3f3f}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-widget-headerLine truncated
.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:0}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{margin:0;cursor:pointer;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")}.ui-menu .ui-menu-item-wrapper{position:relative;padding:3px 1em 3px .4em}.ui-menu .ui-menu-divider{margin:5px 0;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-state-focus,.ui-menu .ui-state-active{margin:-1px}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item-wrapper{padding-left:2em}.ui-menu .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0}.ui-widget{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget.ui-widget-content{border:1px solid #c5c5c5}.ui-widget-content{border:1px solid #ddd;background:#fff;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #ddd;background:#e9e9e9;color:#333;font-weight:bold}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default,.ui-button,html .ui-button.ui-state-disabled:hover,html .ui-button.ui-state-disabled:active{border:1px solid #c5c5c5;background:#f6f6f6;font-weight:normal;color:#454545}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited,a.ui-button,a:link.ui-button,a:visited.ui-button,.ui-button{color:#454545;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus,.ui-button:hover,.ui-button:focus{border:1px solid #ccc;background:#ededed;font-weight:normal;color:#2b2b2b}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited,a.ui-button:hover,a.ui-button:focus{color:#2b2b2b;text-decoration:none}.ui-visual-focus{box-shadow:0 0 3px 1px rgb(94,158,214)}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active,a.ui-button:active,.ui-button:active,.ui-button.ui-state-active:hover{border:1px solid #003eff;background:#007fff;font-weight:normal;color:#fff}.ui-icon-background,.ui-state-active .ui-icon-background{border:#003eff;background-color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #dad55e;background:#fffa90;color:#777620}.ui-state-checked{border:1px solid #dad55e;background:#fffa90}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#777620}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #f1a899;background:#fddfdf;color:#5f3f3f}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{colLine truncated
@@ -1,6 +1,6 @@
/*! jQuery UI - v1.12.1 - 2021-02-23
/*! jQuery UI - v1.12.1 - 2021-07-18
* http://jqueryui.com
* Includes: widget.js, disable-selection.js, widgets/resizable.js, widgets/mouse.js
* Includes: widget.js, position.js, disable-selection.js, keycode.js, unique-id.js, widgets/resizable.js, widgets/autocomplete.js, widgets/menu.js, widgets/mouse.js
* Copyright jQuery Foundation and other contributors; Licensed MIT */
!function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)}(function(_){_.ui=_.ui||{};_.ui.version="1.12.1";var n,i=0,a=Array.prototype.slice;_.cleanData=(n=_.cleanData,function(t){for(var e,i,s=0;null!=(i=t[s]);s++)try{(e=_._data(i,"events"))&&e.remove&&_(i).triggerHandler("remove")}catch(t){}n(t)}),_.widget=function(t,i,e){var s,n,o,h={},a=t.split(".")[0],r=a+"-"+(t=t.split(".")[1]);return e||(e=i,i=_.Widget),_.isArray(e)&&(e=_.extend.apply(null,[{}].concat(e))),_.expr[":"][r.toLowerCase()]=function(t){return!!_.data(t,r)},_[a]=_[a]||{},s=_[a][t],n=_[a][t]=function(t,e){if(!this._createWidget)return new n(t,e);arguments.length&&this._createWidget(t,e)},_.extend(n,s,{version:e.version,_proto:_.extend({},e),_childConstructors:[]}),(o=new i).options=_.widget.extend({},o.options),_.each(e,function(e,s){function n(){return i.prototype[e].apply(this,arguments)}function o(t){return i.prototype[e].apply(this,t)}_.isFunction(s)?h[e]=function(){var t,e=this._super,i=this._superApply;return this._super=n,this._superApply=o,t=s.apply(this,arguments),this._super=e,this._superApply=i,t}:h[e]=s}),n.prototype=_.widget.extend(o,{widgetEventPrefix:s&&o.widgetEventPrefix||t},h,{constructor:n,namespace:a,widgetName:t,widgetFullName:r}),s?(_.each(s._childConstructors,function(t,e){var i=e.prototype;_.widget(i.namespace+"."+i.widgetName,n,e._proto)}),delete s._childConstructors):i._childConstructors.push(n),_.widget.bridge(t,n),n},_.widget.extend=function(t){for(var e,i,s=a.call(arguments,1),n=0,o=s.length;n<o;n++)for(e in s[n])i=s[n][e],s[n].hasOwnProperty(e)&&void 0!==i&&(_.isPlainObject(i)?t[e]=_.isPlainObject(t[e])?_.widget.extend({},t[e],i):_.widget.extend({},i):t[e]=i);return t},_.widget.bridge=function(o,e){var h=e.prototype.widgetFullName||o;_.fn[o]=function(i){var t="string"==typeof i,s=a.call(arguments,1),n=this;return t?this.length||"instance"!==i?this.each(function(){var t,e=_.data(this,h);return"instance"===i?(n=e,!1):e?_.isFunction(e[i])&&"_"!==i.charAt(0)?(t=e[i].apply(e,s))!==e&&void 0!==t?(n=t&&t.jquery?n.pushStack(t.get()):t,!1):void 0:_.error("no such method '"+i+"' for "+o+" widget instance"):_.error("cannot call methods on "+o+" prior to initialization; attempted to call method '"+i+"'")}):n=void 0:(s.length&&(i=_.widget.extend.apply(null,[i].concat(s))),this.each(function(){var t=_.data(this,h);t?(t.option(i||{}),t._init&&t._init()):_.data(this,h,new e(i,this))})),n}},_.Widget=function(){},_.Widget._childConstructors=[],_.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{classes:{},disabled:!1,create:null},_createWidget:function(t,e){e=_(e||this.defaultElement||this)[0],this.element=_(e),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=_(),this.hoverable=_(),this.focusable=_(),this.classesElementLookup={},e!==this&&(_.data(e,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===e&&this.destroy()}}),this.document=_(e.style?e.ownerDocument:e.document||e),this.window=_(this.document[0].defaultView||this.document[0].parentWindow)),this.options=_.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:_.noop,_create:_.noop,_init:_.noop,destroy:function(){var i=this;this._destroy(),_.each(this.classesElementLookup,function(t,e){i._removeClass(e,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:_.noop,widget:function(){return this.element},option:function(t,e){var i,s,n,o=t;if(0===arguments.length)return _.widget.extend({},this.options);if("string"==typeof t)if(o={},t=(i=t.split(".")).shift(),i.length){for(s=o[t]=_.widget.extend({},this.options[t]),n=0;n<i.length-1;n++)s[i[n]]=s[i[n]]||{},s=s[i[n]];if(t=i.pop(),1===arguments.length)return void 0===s[t]?null:s[t];s[t]=e}else{if(1===arguments.length)return void 0===this.options[t]?null:this.options[t];o[t]=e}return this._setOptions(o),this},_setOptions:function(t){for(var e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(t){var e,i,s;for(e in t)s=this.classesElementLookup[e],t[e]!==this.options.classes[e]&&s&&s.length&&(i=_(s.get()),this._removeClass(s,e),i.addClass(this._classes({element:i,keys:e,classes:t,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions(Line truncated
!function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)}(function(z){z.ui=z.ui||{};z.ui.version="1.12.1";var n,i=0,a=Array.prototype.slice;z.cleanData=(n=z.cleanData,function(t){for(var e,i,s=0;null!=(i=t[s]);s++)try{(e=z._data(i,"events"))&&e.remove&&z(i).triggerHandler("remove")}catch(t){}n(t)}),z.widget=function(t,i,e){var s,n,o,h={},a=t.split(".")[0],l=a+"-"+(t=t.split(".")[1]);return e||(e=i,i=z.Widget),z.isArray(e)&&(e=z.extend.apply(null,[{}].concat(e))),z.expr[":"][l.toLowerCase()]=function(t){return!!z.data(t,l)},z[a]=z[a]||{},s=z[a][t],n=z[a][t]=function(t,e){if(!this._createWidget)return new n(t,e);arguments.length&&this._createWidget(t,e)},z.extend(n,s,{version:e.version,_proto:z.extend({},e),_childConstructors:[]}),(o=new i).options=z.widget.extend({},o.options),z.each(e,function(e,s){function n(){return i.prototype[e].apply(this,arguments)}function o(t){return i.prototype[e].apply(this,t)}z.isFunction(s)?h[e]=function(){var t,e=this._super,i=this._superApply;return this._super=n,this._superApply=o,t=s.apply(this,arguments),this._super=e,this._superApply=i,t}:h[e]=s}),n.prototype=z.widget.extend(o,{widgetEventPrefix:s&&o.widgetEventPrefix||t},h,{constructor:n,namespace:a,widgetName:t,widgetFullName:l}),s?(z.each(s._childConstructors,function(t,e){var i=e.prototype;z.widget(i.namespace+"."+i.widgetName,n,e._proto)}),delete s._childConstructors):i._childConstructors.push(n),z.widget.bridge(t,n),n},z.widget.extend=function(t){for(var e,i,s=a.call(arguments,1),n=0,o=s.length;n<o;n++)for(e in s[n])i=s[n][e],s[n].hasOwnProperty(e)&&void 0!==i&&(z.isPlainObject(i)?t[e]=z.isPlainObject(t[e])?z.widget.extend({},t[e],i):z.widget.extend({},i):t[e]=i);return t},z.widget.bridge=function(o,e){var h=e.prototype.widgetFullName||o;z.fn[o]=function(i){var t="string"==typeof i,s=a.call(arguments,1),n=this;return t?this.length||"instance"!==i?this.each(function(){var t,e=z.data(this,h);return"instance"===i?(n=e,!1):e?z.isFunction(e[i])&&"_"!==i.charAt(0)?(t=e[i].apply(e,s))!==e&&void 0!==t?(n=t&&t.jquery?n.pushStack(t.get()):t,!1):void 0:z.error("no such method '"+i+"' for "+o+" widget instance"):z.error("cannot call methods on "+o+" prior to initialization; attempted to call method '"+i+"'")}):n=void 0:(s.length&&(i=z.widget.extend.apply(null,[i].concat(s))),this.each(function(){var t=z.data(this,h);t?(t.option(i||{}),t._init&&t._init()):z.data(this,h,new e(i,this))})),n}},z.Widget=function(){},z.Widget._childConstructors=[],z.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{classes:{},disabled:!1,create:null},_createWidget:function(t,e){e=z(e||this.defaultElement||this)[0],this.element=z(e),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=z(),this.hoverable=z(),this.focusable=z(),this.classesElementLookup={},e!==this&&(z.data(e,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===e&&this.destroy()}}),this.document=z(e.style?e.ownerDocument:e.document||e),this.window=z(this.document[0].defaultView||this.document[0].parentWindow)),this.options=z.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:z.noop,_create:z.noop,_init:z.noop,destroy:function(){var i=this;this._destroy(),z.each(this.classesElementLookup,function(t,e){i._removeClass(e,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:z.noop,widget:function(){return this.element},option:function(t,e){var i,s,n,o=t;if(0===arguments.length)return z.widget.extend({},this.options);if("string"==typeof t)if(o={},t=(i=t.split(".")).shift(),i.length){for(s=o[t]=z.widget.extend({},this.options[t]),n=0;n<i.length-1;n++)s[i[n]]=s[i[n]]||{},s=s[i[n]];if(t=i.pop(),1===arguments.length)return void 0===s[t]?null:s[t];s[t]=e}else{if(1===arguments.length)return void 0===this.options[t]?null:this.options[t];o[t]=e}return this._setOptions(o),this},_setOptions:function(t){for(var e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(t){var e,i,s;for(e in t)s=this.classesElementLookup[e],t[e]!==this.options.classes[e]&&s&&s.length&&(i=z(s.get()),this._removeClass(s,e),i.addClass(this._classes({element:i,keys:e,classes:t,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions(Line truncated
@@ -164,3 +164,63 @@
right: -5px;
top: -5px;
}
.ui-autocomplete {
position: absolute;
top: 0;
left: 0;
cursor: default;
}
.ui-menu {
list-style: none;
padding: 0;
margin: 0;
display: block;
outline: 0;
}
.ui-menu .ui-menu {
position: absolute;
}
.ui-menu .ui-menu-item {
margin: 0;
cursor: pointer;
/* support: IE10, see #8844 */
list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
}
.ui-menu .ui-menu-item-wrapper {
position: relative;
padding: 3px 1em 3px .4em;
}
.ui-menu .ui-menu-divider {
margin: 5px 0;
height: 0;
font-size: 0;
line-height: 0;
border-width: 1px 0 0 0;
}
.ui-menu .ui-state-focus,
.ui-menu .ui-state-active {
margin: -1px;
}
/* icon support */
.ui-menu-icons {
position: relative;
}
.ui-menu-icons .ui-menu-item-wrapper {
padding-left: 2em;
}
/* left-aligned */
.ui-menu .ui-icon {
position: absolute;
top: 0;
bottom: 0;
left: .2em;
margin: auto 0;
}
/* right-aligned */
.ui-menu .ui-menu-icon {
left: auto;
right: 0;
}
@@ -1,5 +1,5 @@
/*! jQuery UI - v1.12.1 - 2021-02-23
/*! jQuery UI - v1.12.1 - 2021-07-18
* http://jqueryui.com
* Copyright jQuery Foundation and other contributors; Licensed MIT */
.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}
.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:0}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{margin:0;cursor:pointer;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")}.ui-menu .ui-menu-item-wrapper{position:relative;padding:3px 1em 3px .4em}.ui-menu .ui-menu-divider{margin:5px 0;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-state-focus,.ui-menu .ui-state-active{margin:-1px}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item-wrapper{padding-left:2em}.ui-menu .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0}
@@ -1,4 +1,4 @@
/*! jQuery UI - v1.12.1 - 2021-02-23
/*! jQuery UI - v1.12.1 - 2021-07-18
* http://jqueryui.com
* Copyright jQuery Foundation and other contributors; Licensed MIT */
+142
View File
@@ -0,0 +1,142 @@
"""
Validate that all URLs specified in template files are correct.
"""
from django.test import TestCase
from django.urls import reverse
import os
import re
from pathlib import Path
class URLTest(TestCase):
# Need fixture data in the database
fixtures = [
'settings',
'build',
'company',
'manufacturer_part',
'price_breaks',
'supplier_part',
'order',
'sales_order',
'bom',
'category',
'params',
'part_pricebreaks',
'part',
'test_templates',
'location',
'stock_tests',
'stock',
'users',
]
def find_files(self, suffix):
"""
Search for all files in the template directories,
which can have URLs rendered
"""
template_dirs = [
('build', 'templates'),
('common', 'templates'),
('company', 'templates'),
('label', 'templates'),
('order', 'templates'),
('part', 'templates'),
('report', 'templates'),
('stock', 'templates'),
('templates', ),
]
template_files = []
here = os.path.abspath(os.path.dirname(__file__))
tld = os.path.join(here, '..')
for directory in template_dirs:
template_dir = os.path.join(tld, *directory)
for path in Path(template_dir).rglob(suffix):
f = os.path.abspath(path)
if f not in template_files:
template_files.append(f)
return template_files
def find_urls(self, input_file):
"""
Search for all instances of {% url %} in supplied template file
"""
urls = []
pattern = "{% url ['\"]([^'\"]+)['\"]([^%]*)%}"
with open(input_file, 'r') as f:
data = f.read()
results = re.findall(pattern, data)
for result in results:
if len(result) == 2:
urls.append([
result[0].strip(),
result[1].strip()
])
elif len(result) == 1:
urls.append([
result[0].strip(),
''
])
return urls
def reverse_url(self, url_pair):
"""
Perform lookup on the URL
"""
url, pk = url_pair
# TODO: Handle reverse lookup of admin URLs!
if url.startswith("admin:"):
return
if pk:
# We will assume that there is at least one item in the database
reverse(url, kwargs={"pk": 1})
else:
reverse(url)
def check_file(self, f):
"""
Run URL checks for the provided file
"""
urls = self.find_urls(f)
for url in urls:
self.reverse_url(url)
def test_html_templates(self):
template_files = self.find_files("*.html")
for f in template_files:
self.check_file(f)
def test_js_templates(self):
template_files = self.find_files("*.js")
for f in template_files:
self.check_file(f)
+1
View File
@@ -112,6 +112,7 @@ dynamic_javascript_urls = [
url(r'^company.js', DynamicJsView.as_view(template_name='js/company.js'), name='company.js'),
url(r'^filters.js', DynamicJsView.as_view(template_name='js/filters.js'), name='filters.js'),
url(r'^forms.js', DynamicJsView.as_view(template_name='js/forms.js'), name='forms.js'),
url(r'^inventree.js', DynamicJsView.as_view(template_name='js/inventree.js'), name='inventree.js'),
url(r'^label.js', DynamicJsView.as_view(template_name='js/label.js'), name='label.js'),
url(r'^model_renderers.js', DynamicJsView.as_view(template_name='js/model_renderers.js'), name='model_renderers.js'),
url(r'^modals.js', DynamicJsView.as_view(template_name='js/modals.js'), name='modals.js'),
+15
View File
@@ -104,6 +104,21 @@ class BuildList(generics.ListCreateAPIView):
params = self.request.query_params
# exclude parent tree
exclude_tree = params.get('exclude_tree', None)
if exclude_tree is not None:
try:
build = Build.objects.get(pk=exclude_tree)
queryset = queryset.exclude(
pk__in=[bld.pk for bld in build.get_descendants(include_self=True)]
)
except (ValueError, Build.DoesNotExist):
pass
# Filter by "parent"
parent = params.get('parent', None)
+8
View File
@@ -96,6 +96,14 @@ class Build(MPTTModel):
def get_api_url():
return reverse('api-build-list')
def api_instance_filters(self):
return {
'parent': {
'exclude_tree': self.pk,
}
}
def save(self, *args, **kwargs):
try:
+14 -11
View File
@@ -53,17 +53,20 @@ class FileManager:
ext = os.path.splitext(file.name)[-1].lower().replace('.', '')
if ext in ['csv', 'tsv', ]:
# These file formats need string decoding
raw_data = file.read().decode('utf-8')
# Reset stream position to beginning of file
file.seek(0)
elif ext in ['xls', 'xlsx', 'json', 'yaml', ]:
raw_data = file.read()
# Reset stream position to beginning of file
file.seek(0)
else:
raise ValidationError(_(f'Unsupported file format: {ext.upper()}'))
try:
if ext in ['csv', 'tsv', ]:
# These file formats need string decoding
raw_data = file.read().decode('utf-8')
# Reset stream position to beginning of file
file.seek(0)
elif ext in ['xls', 'xlsx', 'json', 'yaml', ]:
raw_data = file.read()
# Reset stream position to beginning of file
file.seek(0)
else:
raise ValidationError(_(f'Unsupported file format: {ext.upper()}'))
except UnicodeEncodeError:
raise ValidationError(_('Error reading file (invalid encoding)'))
try:
cleaned_data = tablib.Dataset().load(raw_data, format=ext)
+9 -3
View File
@@ -19,8 +19,6 @@ from djmoney.settings import CURRENCY_CHOICES
from djmoney.contrib.exchange.models import convert_money
from djmoney.contrib.exchange.exceptions import MissingRate
import common.settings
from django.utils.translation import ugettext_lazy as _
from django.core.validators import MinValueValidator, URLValidator
from django.core.exceptions import ValidationError
@@ -670,6 +668,13 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'validator': bool,
},
'SEARCH_PREVIEW_RESULTS': {
'name': _('Search Preview Results'),
'description': _('Number of results to show in search preview window'),
'default': 10,
'validator': [int, MinValueValidator(1)]
},
'STOCK_ENABLE_EXPIRY': {
'name': _('Stock Expiry'),
'description': _('Enable stock expiry functionality'),
@@ -853,6 +858,7 @@ def get_price(instance, quantity, moq=True, multiples=True, currency=None, break
- If MOQ (minimum order quantity) is required, bump quantity
- If order multiples are to be observed, then we need to calculate based on that, too
"""
from common.settings import currency_code_default
if hasattr(instance, break_name):
price_breaks = getattr(instance, break_name).all()
@@ -876,7 +882,7 @@ def get_price(instance, quantity, moq=True, multiples=True, currency=None, break
if currency is None:
# Default currency selection
currency = common.settings.currency_code_default()
currency = currency_code_default()
pb_min = None
for pb in price_breaks:
+9 -4
View File
@@ -8,15 +8,19 @@ from __future__ import unicode_literals
from moneyed import CURRENCIES
from django.conf import settings
import common.models
def currency_code_default():
"""
Returns the default currency code (or USD if not specified)
"""
from django.db.utils import ProgrammingError
from common.models import InvenTreeSetting
code = common.models.InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY')
try:
code = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY')
except ProgrammingError:
# database is not initialized yet
code = ''
if code not in CURRENCIES:
code = 'USD'
@@ -42,5 +46,6 @@ def stock_expiry_enabled():
"""
Returns True if the stock expiry feature is enabled
"""
from common.models import InvenTreeSetting
return common.models.InvenTreeSetting.get_setting('STOCK_ENABLE_EXPIRY')
return InvenTreeSetting.get_setting('STOCK_ENABLE_EXPIRY')
@@ -24,14 +24,14 @@
{% comment "for later" %}
<li class='list-group-item {% if tab == "stock" %}active{% endif %}' title='{% trans "Manufacturer Part Stock" %}'>
<a href='{% url "manufacturer-part-stock" part.id %}'>
<a href='#'>
<span class='fas fa-boxes sidebar-icon'></span>
{% trans "Stock" %}
</a>
</li>
<li class='list-group-item {% if tab == "orders" %}active{% endif %}' title='{% trans "Manufacturer Part Orders" %}'>
<a href='{% url "manufacturer-part-orders" part.id %}'>
<a href='#'>
<span class='fas fa-shopping-cart sidebar-icon'></span>
{% trans "Orders" %}
</a>
+2 -4
View File
@@ -30,11 +30,9 @@ company_urls = [
manufacturer_part_urls = [
url(r'^(?P<pk>\d+)/', include([
url('^.*$', views.ManufacturerPartDetail.as_view(template_name='company/manufacturer_part.html'), name='manufacturer-part-detail'),
])),
url(r'^(?P<pk>\d+)/', views.ManufacturerPartDetail.as_view(template_name='company/manufacturer_part.html'), name='manufacturer-part-detail'),
]
supplier_part_urls = [
url('^.*$', views.SupplierPartDetail.as_view(template_name='company/supplier_part.html'), name='supplier-part-detail'),
url(r'^(?P<pk>\d+)/', views.SupplierPartDetail.as_view(template_name='company/supplier_part.html'), name='supplier-part-detail'),
]
+4 -1
View File
@@ -296,7 +296,9 @@ class StockItemLabel(LabelTemplate):
'uid': stock_item.uid,
'qr_data': stock_item.format_barcode(brief=True),
'qr_url': stock_item.format_barcode(url=True, request=request),
'tests': stock_item.testResultMap()
'tests': stock_item.testResultMap(),
'parameters': stock_item.part.parameters_map(),
}
@@ -398,4 +400,5 @@ class PartLabel(LabelTemplate):
'revision': part.revision,
'qr_data': part.format_barcode(brief=True),
'qr_url': part.format_barcode(url=True, request=request),
'parameters': part.parameters_map(),
}
+2 -2
View File
@@ -84,9 +84,9 @@ class ReceivePurchaseOrderForm(HelperForm):
location = TreeNodeChoiceField(
queryset=StockLocation.objects.all(),
required=True,
required=False,
label=_("Destination"),
help_text=_("Receive parts to this location"),
help_text=_("Set all received parts listed above to this location (if left blank, use \"Destination\" column value in above table)"),
)
class Meta:
@@ -1,50 +1,74 @@
{% extends "order/order_base.html" %}
{% extends "order/purchase_order_detail.html" %}
{% load inventree_extras %}
{% load i18n %}
{% load static %}
{% block heading %}
{% trans "Upload File for Purchase Order" %}
{{ wizard.form.media }}
{% block menubar %}
<ul class='list-group'>
<li class='list-group-item'>
<a href='#' id='po-menu-toggle'>
<span class='menu-tab-icon fas fa-expand-arrows-alt'></span>
</a>
</li>
<li class='list-group-item' title='{% trans "Return To Order" %}'>
<a href='{% url "po-detail" order.id %}' id='select-upload-file' class='nav-toggle'>
<span class='fas fa-undo side-icon'></span>
{% trans "Return To Order" %}
</a>
</li>
</ul>
{% endblock %}
{% block details %}
{% if order.status == PurchaseOrderStatus.PENDING and roles.purchase_order.change %}
{% block page_content %}
<p>{% blocktrans with step=wizard.steps.step1 count=wizard.steps.count %}Step {{step}} of {{count}}{% endblocktrans %}
{% if description %}- {{ description }}{% endif %}</p>
<div class='panel panel-default panel-inventree' id='panel-upload-file'>
<div class='panel-heading'>
{% block heading %}
<h4>{% trans "Upload File for Purchase Order" %}</h4>
{{ wizard.form.media }}
{% endblock %}
</div>
<div class='panel-content'>
{% block details %}
{% if order.status == PurchaseOrderStatus.PENDING and roles.purchase_order.change %}
{% block form_alert %}
{% endblock form_alert %}
<p>{% blocktrans with step=wizard.steps.step1 count=wizard.steps.count %}Step {{step}} of {{count}}{% endblocktrans %}
{% if description %}- {{ description }}{% endif %}</p>
<form action="" method="post" class='js-modal-form' enctype="multipart/form-data">
{% csrf_token %}
{% load crispy_forms_tags %}
{% block form_alert %}
{% endblock form_alert %}
{% block form_buttons_top %}
{% endblock form_buttons_top %}
<form action="" method="post" class='js-modal-form' enctype="multipart/form-data">
{% csrf_token %}
{% load crispy_forms_tags %}
<table class='table table-striped' style='margin-top: 12px; margin-bottom: 0px'>
{{ wizard.management_form }}
{% block form_content %}
{% crispy wizard.form %}
{% endblock form_content %}
</table>
{% block form_buttons_top %}
{% endblock form_buttons_top %}
{% block form_buttons_bottom %}
{% if wizard.steps.prev %}
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}" class="save btn btn-default">{% trans "Previous Step" %}</button>
{% endif %}
<button type="submit" class="save btn btn-default">{% trans "Upload File" %}</button>
</form>
{% endblock form_buttons_bottom %}
<table class='table table-striped' style='margin-top: 12px; margin-bottom: 0px'>
{{ wizard.management_form }}
{% block form_content %}
{% crispy wizard.form %}
{% endblock form_content %}
</table>
{% else %}
<div class='alert alert-danger alert-block' role='alert'>
{% trans "Order is already processed. Files cannot be uploaded." %}
</div>
{% endif %}
{% endblock details %}
{% block form_buttons_bottom %}
{% if wizard.steps.prev %}
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}" class="save btn btn-default">{% trans "Previous Step" %}</button>
{% endif %}
<button type="submit" class="save btn btn-default">{% trans "Upload File" %}</button>
</form>
{% endblock form_buttons_bottom %}
{% else %}
<div class='alert alert-danger alert-block' role='alert'>
{% trans "Order is already processed. Files cannot be uploaded." %}
</div>
{% endif %}
{% endblock details %}
</div>
{% endblock %}
{% block js_ready %}
{{ block.super }}
@@ -15,14 +15,6 @@
{% trans "Order Items" %}
</a>
</li>
{% if order.status == PurchaseOrderStatus.PENDING and roles.purchase_order.change %}
<li class='list-group-item' title='{% trans "Upload File" %}'>
<a href='{% url "po-upload" order.id %}'>
<span class='fas fa-file-upload side-icon'></span>
{% trans "Upload File" %}
</a>
</li>
{% endif %}
<li class='list-group-item' title='{% trans "Received Stock Items" %}'>
<a href='#' id='select-received-items' class='nav-toggle'>
<span class='fas fa-sign-in-alt side-icon'></span>
@@ -22,6 +22,9 @@
<button type='button' class='btn btn-primary' id='new-po-line'>
<span class='fas fa-plus-circle'></span> {% trans "Add Line Item" %}
</button>
<a class='btn btn-primary' href='{% url "po-upload" order.id %}' role='button'>
<span class='fas fa-file-upload side-icon'></span> {% trans "Upload File" %}
</a>
{% endif %}
</div>
@@ -12,7 +12,7 @@
{% load crispy_forms_tags %}
<label class='control-label'>{% trans "Parts" %}</label>
<p class='help-block'>{% trans "Select parts to receive against this order" %}</p>
<p class='help-block'>{% trans "Fill out number of parts received, the status and destination" %}</p>
<table class='table table-striped'>
<tr>
@@ -55,7 +55,14 @@
</div>
</td>
<td>
{{ line.get_destination }}
<div class='control-group'>
<select class='select' name='destination-{{ line.id }}'>
<option value="">----------</option>
{% for location in stock_locations %}
<option value="{{ location.pk }}" {% if location == line.get_destination %}selected="selected"{% endif %}>{{ location }}</option>
{% endfor %}
</select>
</div>
</td>
<td>
<button class='btn btn-default btn-remove' onClick="removeOrderRowFromOrderWizard()" id='del_item_{{ line.id }}' title='{% trans "Remove line" %}' type='button'>
@@ -67,6 +74,8 @@
</table>
{% crispy form %}
<div id='form-errors'>{{ form_errors }}</div>
</form>
{% endblock %}
+19 -17
View File
@@ -393,16 +393,7 @@ class PurchaseOrderUpload(FileManagementFormView):
p_val = row['data'][p_idx]['cell']
if p_val:
# Delete commas
p_val = p_val.replace(',', '')
try:
# Attempt to extract a valid decimal value from the field
purchase_price = Decimal(p_val)
# Store the 'purchase_price' value
row['purchase_price'] = purchase_price
except (ValueError, InvalidOperation):
pass
row['purchase_price'] = p_val
# Check if there is a column corresponding to "reference"
if r_idx >= 0:
@@ -500,6 +491,7 @@ class PurchaseOrderReceive(AjaxUpdateView):
ctx = {
'order': self.order,
'lines': self.lines,
'stock_locations': StockLocation.objects.all(),
}
return ctx
@@ -552,6 +544,7 @@ class PurchaseOrderReceive(AjaxUpdateView):
self.request = request
self.order = get_object_or_404(PurchaseOrder, pk=self.kwargs['pk'])
errors = False
self.lines = []
self.destination = None
@@ -566,12 +559,6 @@ class PurchaseOrderReceive(AjaxUpdateView):
except (StockLocation.DoesNotExist, ValueError):
pass
errors = False
if self.destination is None:
errors = True
msg = _("No destination set")
# Extract information on all submitted line items
for item in request.POST:
if item.startswith('line-'):
@@ -596,6 +583,21 @@ class PurchaseOrderReceive(AjaxUpdateView):
else:
line.status_code = StockStatus.OK
# Check the destination field
line.destination = None
if self.destination:
# If global destination is set, overwrite line value
line.destination = self.destination
else:
destination_key = f'destination-{pk}'
destination = request.POST.get(destination_key, None)
if destination:
try:
line.destination = StockLocation.objects.get(pk=destination)
except (StockLocation.DoesNotExist, ValueError):
pass
# Check that line matches the order
if not line.order == self.order:
# TODO - Display a non-field error?
@@ -654,7 +656,7 @@ class PurchaseOrderReceive(AjaxUpdateView):
self.order.receive_line_item(
line,
self.destination,
line.destination,
line.receive_quantity,
self.request.user,
status=line.status_code,
+28 -7
View File
@@ -105,6 +105,20 @@ class CategoryList(generics.ListCreateAPIView):
except (ValueError, PartCategory.DoesNotExist):
pass
# Exclude PartCategory tree
exclude_tree = params.get('exclude_tree', None)
if exclude_tree is not None:
try:
cat = PartCategory.objects.get(pk=exclude_tree)
queryset = queryset.exclude(
pk__in=[c.pk for c in cat.get_descendants(include_self=True)]
)
except (ValueError, PartCategory.DoesNotExist):
pass
return queryset
filter_backends = [
@@ -361,7 +375,6 @@ class PartDetail(generics.RetrieveUpdateDestroyAPIView):
def get_queryset(self, *args, **kwargs):
queryset = super().get_queryset(*args, **kwargs)
queryset = part_serializers.PartSerializer.prefetch_queryset(queryset)
queryset = part_serializers.PartSerializer.annotate_queryset(queryset)
return queryset
@@ -619,8 +632,6 @@ class PartList(generics.ListCreateAPIView):
def get_queryset(self, *args, **kwargs):
queryset = super().get_queryset(*args, **kwargs)
queryset = part_serializers.PartSerializer.prefetch_queryset(queryset)
queryset = part_serializers.PartSerializer.annotate_queryset(queryset)
return queryset
@@ -633,10 +644,6 @@ class PartList(generics.ListCreateAPIView):
params = self.request.query_params
# Annotate calculated data to the queryset
# (This will be used for further filtering)
queryset = part_serializers.PartSerializer.annotate_queryset(queryset)
queryset = super().filter_queryset(queryset)
# Filter by "uses" query - Limit to parts which use the provided part
@@ -651,6 +658,20 @@ class PartList(generics.ListCreateAPIView):
except (ValueError, Part.DoesNotExist):
pass
# Exclude part variant tree?
exclude_tree = params.get('exclude_tree', None)
if exclude_tree is not None:
try:
top_level_part = Part.objects.get(pk=exclude_tree)
queryset = queryset.exclude(
pk__in=[prt.pk for prt in top_level_part.get_descendants(include_self=True)]
)
except (ValueError, Part.DoesNotExist):
pass
# Filter by 'ancestor'?
ancestor = params.get('ancestor', None)
+60 -5
View File
@@ -27,6 +27,8 @@ from markdownx.models import MarkdownxField
from django_cleanup import cleanup
from mptt.models import TreeForeignKey, MPTTModel
from mptt.exceptions import InvalidMove
from mptt.managers import TreeManager
from stdimage.models import StdImageField
@@ -284,6 +286,24 @@ def match_part_names(match, threshold=80, reverse=True, compare_length=False):
return matches
class PartManager(TreeManager):
"""
Defines a custom object manager for the Part model.
The main purpose of this manager is to reduce the number of database hits,
as the Part model has a large number of ForeignKey fields!
"""
def get_queryset(self):
return super().get_queryset().prefetch_related(
'category',
'category__parent',
'stock_items',
'builds',
)
@cleanup.ignore
class Part(MPTTModel):
""" The Part object represents an abstract part, the 'concept' of an actual entity.
@@ -321,6 +341,8 @@ class Part(MPTTModel):
responsible: User who is responsible for this part (optional)
"""
objects = PartManager()
class Meta:
verbose_name = _("Part")
verbose_name_plural = _("Parts")
@@ -338,6 +360,17 @@ class Part(MPTTModel):
return reverse('api-part-list')
def api_instance_filters(self):
"""
Return API query filters for limiting field results against this instance
"""
return {
'variant_of': {
'exclude_tree': self.pk,
}
}
def get_context_data(self, request, **kwargs):
"""
Return some useful context data about this part for template rendering
@@ -393,7 +426,12 @@ class Part(MPTTModel):
self.full_clean()
super().save(*args, **kwargs)
try:
super().save(*args, **kwargs)
except InvalidMove:
raise ValidationError({
'variant_of': _('Invalid choice for parent part'),
})
if add_category_templates:
# Get part category
@@ -1473,16 +1511,16 @@ class Part(MPTTModel):
return self.supplier_parts.count()
@property
def has_pricing_info(self):
def has_pricing_info(self, internal=False):
""" Return true if there is pricing information for this part """
return self.get_price_range() is not None
return self.get_price_range(internal=internal) is not None
@property
def has_complete_bom_pricing(self):
""" Return true if there is pricing information for each item in the BOM. """
use_internal = common.models.get_setting('PART_BOM_USE_INTERNAL_PRICE', False)
for item in self.get_bom_items().all().select_related('sub_part'):
if not item.sub_part.has_pricing_info:
if not item.sub_part.has_pricing_info(use_internal):
return False
return True
@@ -1866,6 +1904,23 @@ class Part(MPTTModel):
return self.parameters.order_by('template__name')
def parameters_map(self):
"""
Return a map (dict) of parameter values assocaited with this Part instance,
of the form:
{
"name_1": "value_1",
"name_2": "value_2",
}
"""
params = {}
for parameter in self.parameters.all():
params[parameter.template.name] = parameter.data
return params
@property
def has_variants(self):
""" Check if this Part object has variants underneath it. """
-19
View File
@@ -215,25 +215,6 @@ class PartSerializer(InvenTreeModelSerializer):
if category_detail is not True:
self.fields.pop('category_detail')
@staticmethod
def prefetch_queryset(queryset):
"""
Prefetch related database tables,
to reduce database hits.
"""
return queryset.prefetch_related(
'category',
'category__parts',
'category__parent',
'stock_items',
'bom_items',
'builds',
'supplier_parts',
'supplier_parts__purchase_order_line_items',
'supplier_parts__purchase_order_line_items__order',
)
@staticmethod
def annotate_queryset(queryset):
"""
+1 -1
View File
@@ -99,7 +99,7 @@ class CategoryTest(TestCase):
""" Test that the Category parameters are correctly fetched """
# Check number of SQL queries to iterate other parameters
with self.assertNumQueries(3):
with self.assertNumQueries(7):
# Prefetch: 3 queries (parts, parameters and parameters_template)
fasteners = self.fasteners.prefetch_parts_parameters()
# Iterate through all parts and parameters
+1
View File
@@ -356,6 +356,7 @@ class TestReport(ReportTemplateBase):
'stock_item': stock_item,
'serial': stock_item.serial,
'part': stock_item.part,
'parameters': stock_item.part.parameters_map(),
'results': stock_item.testResultMap(include_installed=self.include_installed),
'result_list': stock_item.testResultList(include_installed=self.include_installed),
'installed_items': stock_item.get_installed_items(cascade=True),
+28 -2
View File
@@ -84,7 +84,6 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView):
def get_queryset(self, *args, **kwargs):
queryset = super().get_queryset(*args, **kwargs)
queryset = StockItemSerializer.prefetch_queryset(queryset)
queryset = StockItemSerializer.annotate_queryset(queryset)
return queryset
@@ -344,6 +343,20 @@ class StockLocationList(generics.ListCreateAPIView):
except (ValueError, StockLocation.DoesNotExist):
pass
# Exclude StockLocation tree
exclude_tree = params.get('exclude_tree', None)
if exclude_tree is not None:
try:
loc = StockLocation.objects.get(pk=exclude_tree)
queryset = queryset.exclude(
pk__in=[subloc.pk for subloc in loc.get_descendants(include_self=True)]
)
except (ValueError, StockLocation.DoesNotExist):
pass
return queryset
filter_backends = [
@@ -637,7 +650,6 @@ class StockList(generics.ListCreateAPIView):
queryset = super().get_queryset(*args, **kwargs)
queryset = StockItemSerializer.prefetch_queryset(queryset)
queryset = StockItemSerializer.annotate_queryset(queryset)
return queryset
@@ -721,6 +733,20 @@ class StockList(generics.ListCreateAPIView):
if customer:
queryset = queryset.filter(customer=customer)
# Exclude stock item tree
exclude_tree = params.get('exclude_tree', None)
if exclude_tree is not None:
try:
item = StockItem.objects.get(pk=exclude_tree)
queryset = queryset.exclude(
pk__in=[it.pk for it in item.get_descendants(include_self=True)]
)
except (ValueError, StockItem.DoesNotExist):
pass
# Filter by 'allocated' parts?
allocated = params.get('allocated', None)
+37
View File
@@ -23,6 +23,7 @@ from django.dispatch import receiver
from markdownx.models import MarkdownxField
from mptt.models import MPTTModel, TreeForeignKey
from mptt.managers import TreeManager
from decimal import Decimal, InvalidOperation
from datetime import datetime, timedelta
@@ -130,6 +131,31 @@ def before_delete_stock_location(sender, instance, using, **kwargs):
child.save()
class StockItemManager(TreeManager):
"""
Custom database manager for the StockItem class.
StockItem querysets will automatically prefetch related fields.
"""
def get_queryset(self):
return super().get_queryset().prefetch_related(
'belongs_to',
'build',
'customer',
'purchase_order',
'sales_order',
'supplier_part',
'supplier_part__supplier',
'allocations',
'sales_order_allocations',
'location',
'part',
'tracking_info'
)
class StockItem(MPTTModel):
"""
A StockItem object represents a quantity of physical instances of a part.
@@ -165,6 +191,17 @@ class StockItem(MPTTModel):
def get_api_url():
return reverse('api-stock-list')
def api_instance_filters(self):
"""
Custom API instance filters
"""
return {
'parent': {
'exclude_tree': self.pk,
}
}
# A Query filter which will be re-used in multiple places to determine if a StockItem is actually "in stock"
IN_STOCK_FILTER = Q(
quantity__gt=0,
-23
View File
@@ -70,29 +70,6 @@ class StockItemSerializer(InvenTreeModelSerializer):
- Includes serialization for the item location
"""
@staticmethod
def prefetch_queryset(queryset):
"""
Prefetch related database tables,
to reduce database hits.
"""
return queryset.prefetch_related(
'belongs_to',
'build',
'customer',
'purchase_order',
'sales_order',
'supplier_part',
'supplier_part__supplier',
'supplier_part__manufacturer_part__manufacturer',
'allocations',
'sales_order_allocations',
'location',
'part',
'tracking_info',
)
@staticmethod
def annotate_queryset(queryset):
"""
@@ -272,7 +272,7 @@
<tr>
<td><span class='fas fa-user-tie'></span></td>
<td>{% trans "Customer" %}</td>
<td><a href="{% url 'company-detail-assigned-stock' item.customer.id %}">{{ item.customer.name }}</a></td>
<td><a href="{% url 'company-detail' item.customer.id %}?display=assigned-stock">{{ item.customer.name }}</a></td>
</tr>
{% endif %}
{% if item.belongs_to %}
@@ -31,4 +31,11 @@
</tbody>
</table>
<h4>{% trans "Search Settings" %}</h4>
<table class='table table-striped table-condensed'>
<tbody>
{% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_RESULTS" icon="fa-search" %}
</tbody>
</table>
{% endblock %}
+1 -1
View File
@@ -142,11 +142,11 @@
<script type='text/javascript' src="{% static 'script/randomColor.min.js' %}"></script>
<!-- general InvenTree -->
<script type='text/javascript' src="{% static 'script/inventree/inventree.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/notification.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/sidenav.js' %}"></script>
<!-- translated -->
<script type='text/javascript' src="{% i18n_static 'inventree.js' %}"></script>
<script type='text/javascript' src="{% i18n_static 'api.js' %}"></script>
<script type='text/javascript' src="{% i18n_static 'attachment.js' %}"></script>
<script type='text/javascript' src="{% i18n_static 'barcode.js' %}"></script>
+6
View File
@@ -350,6 +350,12 @@ function constructFormBody(fields, options) {
for(field in fields) {
fields[field].name = field;
// If any "instance_filters" are defined for the endpoint, copy them across (overwrite)
if (fields[field].instance_filters) {
fields[field].filters = Object.assign(fields[field].filters || {}, fields[field].instance_filters);
}
var field_options = displayed_fields[field];
// Copy custom options across to the fields object
@@ -1,3 +1,5 @@
{% load inventree_extras %}
function attachClipboard(selector, containerselector, textElement) {
// set container
if (containerselector){
@@ -13,7 +15,8 @@ function attachClipboard(selector, containerselector, textElement) {
}
} else {
text = function(trigger) {
var content = trigger.parentElement.parentElement.textContent;return content.trim();
var content = trigger.parentElement.parentElement.textContent;
return content.trim();
}
}
@@ -80,6 +83,45 @@ function inventreeDocReady() {
attachClipboard('.clip-btn');
attachClipboard('.clip-btn', 'modal-about'); // modals
attachClipboard('.clip-btn-version', 'modal-about', 'about-copy-text'); // version-text
// Add autocomplete to the search-bar
$("#search-bar" ).autocomplete({
source: function (request, response) {
$.ajax({
url: '/api/part/',
data: {
search: request.term,
limit: {% settings_value 'SEARCH_PREVIEW_RESULTS' %},
offset: 0
},
success: function (data) {
var transformed = $.map(data.results, function (el) {
return {
label: el.name,
id: el.pk,
thumbnail: el.thumbnail
};
});
response(transformed);
},
error: function () {
response([]);
}
});
},
create: function () {
$(this).data('ui-autocomplete')._renderItem = function (ul, item) {
return $('<li>')
.append('<span>' + imageHoverIcon(item.thumbnail) + item.label + '</span>')
.appendTo(ul);
};
},
select: function( event, ui ) {
window.location = '/part/' + ui.item.id + '/';
},
minLength: 2,
classes: {'ui-autocomplete': 'dropdown-menu search-menu'},
});
}
function isFileTransfer(transfer) {
+1 -1
View File
@@ -3,7 +3,7 @@
<form class="navbar-form navbar-left" action="{% url 'search' %}" method='post'>
{% csrf_token %}
<div class="form-group">
<input type="text" name='search' class="form-control" placeholder="{% trans 'Search' %}"{% if query_text %} value="{{ query }}"{% endif %}>
<input type="text" name='search' class="form-control" id="search-bar" placeholder="{% trans 'Search' %}"{% if query_text %} value="{{ query }}"{% endif %}>
</div>
<button type="submit" id='search-submit' class="btn btn-default" title='{% trans "Search" %}'>
<span class='fas fa-search'></span>
-9
View File
@@ -34,15 +34,6 @@ InvenTree is supported by a [companion mobile app](https://inventree.readthedocs
# Translation
![de translation](https://img.shields.io/badge/dynamic/json?color=blue&label=de&style=flat&query=%24.progress.0.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-14720186-452300.json)
![es-ES translation](https://img.shields.io/badge/dynamic/json?color=blue&label=es-ES&style=flat&query=%24.progress.1.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-14720186-452300.json)
![fr translation](https://img.shields.io/badge/dynamic/json?color=blue&label=fr&style=flat&query=%24.progress.3.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-14720186-452300.json)
![it translation](https://img.shields.io/badge/dynamic/json?color=blue&label=it&style=flat&query=%24.progress.4.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-14720186-452300.json)
![pl translation](https://img.shields.io/badge/dynamic/json?color=blue&label=pl&style=flat&query=%24.progress.5.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-14720186-452300.json)
![ru translation](https://img.shields.io/badge/dynamic/json?color=blue&label=ru&style=flat&query=%24.progress.6.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-14720186-452300.json)
![tr translation](https://img.shields.io/badge/dynamic/json?color=blue&label=tr&style=flat&query=%24.progress.6.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-14720186-452300.json)
![zh-CN translation](https://img.shields.io/badge/dynamic/json?color=blue&label=zh-CN&style=flat&query=%24.progress.7.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-14720186-452300.json)
Native language translation of the InvenTree web application is [community contributed via crowdin](https://crowdin.com/project/inventree). **Contributions are welcomed and encouraged**.
To contribute to the translation effort, navigate to the [InvenTree crowdin project](https://crowdin.com/project/inventree), create a free account, and start making translations suggestions for your language of choice!