mirror of
https://github.com/inventree/InvenTree.git
synced 2025-12-19 05:11:41 -06:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26bf51c20a | ||
|
|
f9c28eedaf | ||
|
|
9bdbb0137f | ||
|
|
412b464b09 |
31
.github/release.yml
vendored
Normal file
31
.github/release.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# .github/release.yml
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
exclude:
|
||||||
|
labels:
|
||||||
|
- translation
|
||||||
|
categories:
|
||||||
|
- title: Breaking Changes
|
||||||
|
labels:
|
||||||
|
- Semver-Major
|
||||||
|
- breaking
|
||||||
|
- title: Security Patches
|
||||||
|
labels:
|
||||||
|
- security
|
||||||
|
- title: New Features
|
||||||
|
labels:
|
||||||
|
- Semver-Minor
|
||||||
|
- enhancement
|
||||||
|
- title: Bug Fixes
|
||||||
|
labels:
|
||||||
|
- Semver-Patch
|
||||||
|
- bug
|
||||||
|
- title: Devops / Setup Changes
|
||||||
|
labels:
|
||||||
|
- docker
|
||||||
|
- setup
|
||||||
|
- demo
|
||||||
|
- CI
|
||||||
|
- title: Other Changes
|
||||||
|
labels:
|
||||||
|
- "*"
|
||||||
33
InvenTree/InvenTree/admin.py
Normal file
33
InvenTree/InvenTree/admin.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
"""Admin classes"""
|
||||||
|
|
||||||
|
from import_export.resources import ModelResource
|
||||||
|
|
||||||
|
|
||||||
|
class InvenTreeResource(ModelResource):
|
||||||
|
"""Custom subclass of the ModelResource class provided by django-import-export"
|
||||||
|
|
||||||
|
Ensures that exported data are escaped to prevent malicious formula injection.
|
||||||
|
Ref: https://owasp.org/www-community/attacks/CSV_Injection
|
||||||
|
"""
|
||||||
|
|
||||||
|
def export_resource(self, obj):
|
||||||
|
"""Custom function to override default row export behaviour.
|
||||||
|
|
||||||
|
Specifically, strip illegal leading characters to prevent formula injection
|
||||||
|
"""
|
||||||
|
row = super().export_resource(obj)
|
||||||
|
|
||||||
|
illegal_start_vals = ['@', '=', '+', '-', '@', '\t', '\r', '\n']
|
||||||
|
|
||||||
|
for idx, val in enumerate(row):
|
||||||
|
if type(val) is str:
|
||||||
|
val = val.strip()
|
||||||
|
|
||||||
|
# If the value starts with certain 'suspicious' values, remove it!
|
||||||
|
while len(val) > 0 and val[0] in illegal_start_vals:
|
||||||
|
# Remove the first character
|
||||||
|
val = val[1:]
|
||||||
|
|
||||||
|
row[idx] = val
|
||||||
|
|
||||||
|
return row
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
inventreeDocReady,
|
inventreeDocReady,
|
||||||
inventreeLoad,
|
inventreeLoad,
|
||||||
inventreeSave,
|
inventreeSave,
|
||||||
|
sanitizeData,
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function attachClipboard(selector, containerselector, textElement) {
|
function attachClipboard(selector, containerselector, textElement) {
|
||||||
@@ -273,6 +274,42 @@ function loadBrandIcon(element, name) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function to sanitize a (potentially nested) object.
|
||||||
|
* Iterates through all levels, and sanitizes each primitive string.
|
||||||
|
*
|
||||||
|
* Note that this function effectively provides a "deep copy" of the provided data,
|
||||||
|
* and the original data structure is unaltered.
|
||||||
|
*/
|
||||||
|
function sanitizeData(data) {
|
||||||
|
if (data == null) {
|
||||||
|
return null;
|
||||||
|
} else if (Array.isArray(data)) {
|
||||||
|
// Handle arrays
|
||||||
|
var arr = [];
|
||||||
|
data.forEach(function(val) {
|
||||||
|
arr.push(sanitizeData(val));
|
||||||
|
});
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
} else if (typeof(data) === 'object') {
|
||||||
|
// Handle nested structures
|
||||||
|
var nested = {};
|
||||||
|
$.each(data, function(k, v) {
|
||||||
|
nested[k] = sanitizeData(v);
|
||||||
|
});
|
||||||
|
|
||||||
|
return nested;
|
||||||
|
} else if (typeof(data) === 'string') {
|
||||||
|
// Perform string replacement
|
||||||
|
return data.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/`/g, '`');
|
||||||
|
} else {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Convenience function to determine if an element exists
|
// Convenience function to determine if an element exists
|
||||||
$.fn.exists = function() {
|
$.fn.exists = function() {
|
||||||
return this.length !== 0;
|
return this.length !== 0;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import common.models
|
|||||||
from InvenTree.api_version import INVENTREE_API_VERSION
|
from InvenTree.api_version import INVENTREE_API_VERSION
|
||||||
|
|
||||||
# InvenTree software version
|
# InvenTree software version
|
||||||
INVENTREE_SW_VERSION = "0.7.1"
|
INVENTREE_SW_VERSION = "0.7.2"
|
||||||
|
|
||||||
|
|
||||||
def inventreeInstanceName():
|
def inventreeInstanceName():
|
||||||
|
|||||||
@@ -2,16 +2,15 @@ from django.contrib import admin
|
|||||||
|
|
||||||
from import_export.admin import ImportExportModelAdmin
|
from import_export.admin import ImportExportModelAdmin
|
||||||
from import_export.fields import Field
|
from import_export.fields import Field
|
||||||
from import_export.resources import ModelResource
|
|
||||||
import import_export.widgets as widgets
|
import import_export.widgets as widgets
|
||||||
|
|
||||||
from build.models import Build, BuildItem
|
from build.models import Build, BuildItem
|
||||||
|
from InvenTree.admin import InvenTreeResource
|
||||||
import part.models
|
import part.models
|
||||||
|
|
||||||
|
|
||||||
class BuildResource(ModelResource):
|
class BuildResource(InvenTreeResource):
|
||||||
"""Class for managing import/export of Build data"""
|
"""Class for managing import/export of Build data."""
|
||||||
# For some reason, we need to specify the fields individually for this ModelResource,
|
# For some reason, we need to specify the fields individually for this ModelResource,
|
||||||
# but we don't for other ones.
|
# but we don't for other ones.
|
||||||
# TODO: 2022-05-12 - Need to investigate why this is the case!
|
# TODO: 2022-05-12 - Need to investigate why this is the case!
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ from django.contrib import admin
|
|||||||
import import_export.widgets as widgets
|
import import_export.widgets as widgets
|
||||||
from import_export.admin import ImportExportModelAdmin
|
from import_export.admin import ImportExportModelAdmin
|
||||||
from import_export.fields import Field
|
from import_export.fields import Field
|
||||||
from import_export.resources import ModelResource
|
|
||||||
|
|
||||||
|
from InvenTree.admin import InvenTreeResource
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
|
|
||||||
from .models import (Company, ManufacturerPart, ManufacturerPartAttachment,
|
from .models import (Company, ManufacturerPart, ManufacturerPartAttachment,
|
||||||
@@ -12,8 +12,8 @@ from .models import (Company, ManufacturerPart, ManufacturerPartAttachment,
|
|||||||
SupplierPriceBreak)
|
SupplierPriceBreak)
|
||||||
|
|
||||||
|
|
||||||
class CompanyResource(ModelResource):
|
class CompanyResource(InvenTreeResource):
|
||||||
""" Class for managing Company data import/export """
|
"""Class for managing Company data import/export."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Company
|
model = Company
|
||||||
@@ -34,10 +34,8 @@ class CompanyAdmin(ImportExportModelAdmin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SupplierPartResource(ModelResource):
|
class SupplierPartResource(InvenTreeResource):
|
||||||
"""
|
"""Class for managing SupplierPart data import/export."""
|
||||||
Class for managing SupplierPart data import/export
|
|
||||||
"""
|
|
||||||
|
|
||||||
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))
|
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))
|
||||||
|
|
||||||
@@ -70,10 +68,8 @@ class SupplierPartAdmin(ImportExportModelAdmin):
|
|||||||
autocomplete_fields = ('part', 'supplier', 'manufacturer_part',)
|
autocomplete_fields = ('part', 'supplier', 'manufacturer_part',)
|
||||||
|
|
||||||
|
|
||||||
class ManufacturerPartResource(ModelResource):
|
class ManufacturerPartResource(InvenTreeResource):
|
||||||
"""
|
"""Class for managing ManufacturerPart data import/export."""
|
||||||
Class for managing ManufacturerPart data import/export
|
|
||||||
"""
|
|
||||||
|
|
||||||
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))
|
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))
|
||||||
|
|
||||||
@@ -118,10 +114,8 @@ class ManufacturerPartAttachmentAdmin(ImportExportModelAdmin):
|
|||||||
autocomplete_fields = ('manufacturer_part',)
|
autocomplete_fields = ('manufacturer_part',)
|
||||||
|
|
||||||
|
|
||||||
class ManufacturerPartParameterResource(ModelResource):
|
class ManufacturerPartParameterResource(InvenTreeResource):
|
||||||
"""
|
"""Class for managing ManufacturerPartParameter data import/export."""
|
||||||
Class for managing ManufacturerPartParameter data import/export
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ManufacturerPartParameter
|
model = ManufacturerPartParameter
|
||||||
@@ -148,8 +142,8 @@ class ManufacturerPartParameterAdmin(ImportExportModelAdmin):
|
|||||||
autocomplete_fields = ('manufacturer_part',)
|
autocomplete_fields = ('manufacturer_part',)
|
||||||
|
|
||||||
|
|
||||||
class SupplierPriceBreakResource(ModelResource):
|
class SupplierPriceBreakResource(InvenTreeResource):
|
||||||
""" Class for managing SupplierPriceBreak data import/export """
|
"""Class for managing SupplierPriceBreak data import/export."""
|
||||||
|
|
||||||
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(SupplierPart))
|
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(SupplierPart))
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
"""Admin functionality for the 'order' app"""
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
import import_export.widgets as widgets
|
import import_export.widgets as widgets
|
||||||
from import_export.admin import ImportExportModelAdmin
|
from import_export.admin import ImportExportModelAdmin
|
||||||
from import_export.fields import Field
|
from import_export.fields import Field
|
||||||
from import_export.resources import ModelResource
|
|
||||||
|
from InvenTree.admin import InvenTreeResource
|
||||||
|
|
||||||
from .models import (PurchaseOrder, PurchaseOrderExtraLine,
|
from .models import (PurchaseOrder, PurchaseOrderExtraLine,
|
||||||
PurchaseOrderLineItem, SalesOrder, SalesOrderAllocation,
|
PurchaseOrderLineItem, SalesOrder, SalesOrderAllocation,
|
||||||
@@ -13,6 +16,7 @@ from .models import (PurchaseOrder, PurchaseOrderExtraLine,
|
|||||||
|
|
||||||
# region general classes
|
# region general classes
|
||||||
class GeneralExtraLineAdmin:
|
class GeneralExtraLineAdmin:
|
||||||
|
"""Admin class template for the 'ExtraLineItem' models"""
|
||||||
list_display = (
|
list_display = (
|
||||||
'order',
|
'order',
|
||||||
'quantity',
|
'quantity',
|
||||||
@@ -29,6 +33,7 @@ class GeneralExtraLineAdmin:
|
|||||||
|
|
||||||
|
|
||||||
class GeneralExtraLineMeta:
|
class GeneralExtraLineMeta:
|
||||||
|
"""Metaclass template for the 'ExtraLineItem' models"""
|
||||||
skip_unchanged = True
|
skip_unchanged = True
|
||||||
report_skipped = False
|
report_skipped = False
|
||||||
clean_model_instances = True
|
clean_model_instances = True
|
||||||
@@ -36,11 +41,13 @@ class GeneralExtraLineMeta:
|
|||||||
|
|
||||||
|
|
||||||
class PurchaseOrderLineItemInlineAdmin(admin.StackedInline):
|
class PurchaseOrderLineItemInlineAdmin(admin.StackedInline):
|
||||||
|
"""Inline admin class for the PurchaseOrderLineItem model"""
|
||||||
model = PurchaseOrderLineItem
|
model = PurchaseOrderLineItem
|
||||||
extra = 0
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrderAdmin(ImportExportModelAdmin):
|
class PurchaseOrderAdmin(ImportExportModelAdmin):
|
||||||
|
"""Admin class for the PurchaseOrder model"""
|
||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
'reference_int',
|
'reference_int',
|
||||||
@@ -68,6 +75,7 @@ class PurchaseOrderAdmin(ImportExportModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
class SalesOrderAdmin(ImportExportModelAdmin):
|
class SalesOrderAdmin(ImportExportModelAdmin):
|
||||||
|
"""Admin class for the SalesOrder model"""
|
||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
'reference_int',
|
'reference_int',
|
||||||
@@ -90,10 +98,8 @@ class SalesOrderAdmin(ImportExportModelAdmin):
|
|||||||
autocomplete_fields = ('customer',)
|
autocomplete_fields = ('customer',)
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrderResource(ModelResource):
|
class PurchaseOrderResource(InvenTreeResource):
|
||||||
"""
|
"""Class for managing import / export of PurchaseOrder data."""
|
||||||
Class for managing import / export of PurchaseOrder data
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Add number of line items
|
# Add number of line items
|
||||||
line_items = Field(attribute='line_count', widget=widgets.IntegerWidget(), readonly=True)
|
line_items = Field(attribute='line_count', widget=widgets.IntegerWidget(), readonly=True)
|
||||||
@@ -102,6 +108,7 @@ class PurchaseOrderResource(ModelResource):
|
|||||||
overdue = Field(attribute='is_overdue', widget=widgets.BooleanWidget(), readonly=True)
|
overdue = Field(attribute='is_overdue', widget=widgets.BooleanWidget(), readonly=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
"""Metaclass"""
|
||||||
model = PurchaseOrder
|
model = PurchaseOrder
|
||||||
skip_unchanged = True
|
skip_unchanged = True
|
||||||
clean_model_instances = True
|
clean_model_instances = True
|
||||||
@@ -110,8 +117,8 @@ class PurchaseOrderResource(ModelResource):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrderLineItemResource(ModelResource):
|
class PurchaseOrderLineItemResource(InvenTreeResource):
|
||||||
""" Class for managing import / export of PurchaseOrderLineItem data """
|
"""Class for managing import / export of PurchaseOrderLineItem data."""
|
||||||
|
|
||||||
part_name = Field(attribute='part__part__name', readonly=True)
|
part_name = Field(attribute='part__part__name', readonly=True)
|
||||||
|
|
||||||
@@ -122,23 +129,24 @@ class PurchaseOrderLineItemResource(ModelResource):
|
|||||||
SKU = Field(attribute='part__SKU', readonly=True)
|
SKU = Field(attribute='part__SKU', readonly=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
"""Metaclass"""
|
||||||
model = PurchaseOrderLineItem
|
model = PurchaseOrderLineItem
|
||||||
skip_unchanged = True
|
skip_unchanged = True
|
||||||
report_skipped = False
|
report_skipped = False
|
||||||
clean_model_instances = True
|
clean_model_instances = True
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrderExtraLineResource(ModelResource):
|
class PurchaseOrderExtraLineResource(InvenTreeResource):
|
||||||
""" Class for managing import / export of PurchaseOrderExtraLine data """
|
"""Class for managing import / export of PurchaseOrderExtraLine data."""
|
||||||
|
|
||||||
class Meta(GeneralExtraLineMeta):
|
class Meta(GeneralExtraLineMeta):
|
||||||
|
"""Metaclass options."""
|
||||||
|
|
||||||
model = PurchaseOrderExtraLine
|
model = PurchaseOrderExtraLine
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderResource(ModelResource):
|
class SalesOrderResource(InvenTreeResource):
|
||||||
"""
|
"""Class for managing import / export of SalesOrder data."""
|
||||||
Class for managing import / export of SalesOrder data
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Add number of line items
|
# Add number of line items
|
||||||
line_items = Field(attribute='line_count', widget=widgets.IntegerWidget(), readonly=True)
|
line_items = Field(attribute='line_count', widget=widgets.IntegerWidget(), readonly=True)
|
||||||
@@ -147,6 +155,7 @@ class SalesOrderResource(ModelResource):
|
|||||||
overdue = Field(attribute='is_overdue', widget=widgets.BooleanWidget(), readonly=True)
|
overdue = Field(attribute='is_overdue', widget=widgets.BooleanWidget(), readonly=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
"""Metaclass options"""
|
||||||
model = SalesOrder
|
model = SalesOrder
|
||||||
skip_unchanged = True
|
skip_unchanged = True
|
||||||
clean_model_instances = True
|
clean_model_instances = True
|
||||||
@@ -155,10 +164,8 @@ class SalesOrderResource(ModelResource):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderLineItemResource(ModelResource):
|
class SalesOrderLineItemResource(InvenTreeResource):
|
||||||
"""
|
"""Class for managing import / export of SalesOrderLineItem data."""
|
||||||
Class for managing import / export of SalesOrderLineItem data
|
|
||||||
"""
|
|
||||||
|
|
||||||
part_name = Field(attribute='part__name', readonly=True)
|
part_name = Field(attribute='part__name', readonly=True)
|
||||||
|
|
||||||
@@ -169,31 +176,34 @@ class SalesOrderLineItemResource(ModelResource):
|
|||||||
fulfilled = Field(attribute='fulfilled_quantity', readonly=True)
|
fulfilled = Field(attribute='fulfilled_quantity', readonly=True)
|
||||||
|
|
||||||
def dehydrate_sale_price(self, item):
|
def dehydrate_sale_price(self, item):
|
||||||
"""
|
"""Return a string value of the 'sale_price' field, rather than the 'Money' object.
|
||||||
Return a string value of the 'sale_price' field, rather than the 'Money' object.
|
|
||||||
Ref: https://github.com/inventree/InvenTree/issues/2207
|
Ref: https://github.com/inventree/InvenTree/issues/2207
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if item.sale_price:
|
if item.sale_price:
|
||||||
return str(item.sale_price)
|
return str(item.sale_price)
|
||||||
else:
|
else:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
"""Metaclass options"""
|
||||||
model = SalesOrderLineItem
|
model = SalesOrderLineItem
|
||||||
skip_unchanged = True
|
skip_unchanged = True
|
||||||
report_skipped = False
|
report_skipped = False
|
||||||
clean_model_instances = True
|
clean_model_instances = True
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderExtraLineResource(ModelResource):
|
class SalesOrderExtraLineResource(InvenTreeResource):
|
||||||
""" Class for managing import / export of SalesOrderExtraLine data """
|
"""Class for managing import / export of SalesOrderExtraLine data."""
|
||||||
|
|
||||||
class Meta(GeneralExtraLineMeta):
|
class Meta(GeneralExtraLineMeta):
|
||||||
|
"""Metaclass options."""
|
||||||
|
|
||||||
model = SalesOrderExtraLine
|
model = SalesOrderExtraLine
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrderLineItemAdmin(ImportExportModelAdmin):
|
class PurchaseOrderLineItemAdmin(ImportExportModelAdmin):
|
||||||
|
"""Admin class for the PurchaseOrderLine model"""
|
||||||
|
|
||||||
resource_class = PurchaseOrderLineItemResource
|
resource_class = PurchaseOrderLineItemResource
|
||||||
|
|
||||||
@@ -210,11 +220,12 @@ class PurchaseOrderLineItemAdmin(ImportExportModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
class PurchaseOrderExtraLineAdmin(GeneralExtraLineAdmin, ImportExportModelAdmin):
|
class PurchaseOrderExtraLineAdmin(GeneralExtraLineAdmin, ImportExportModelAdmin):
|
||||||
|
"""Admin class for the PurchaseOrderExtraLine model"""
|
||||||
resource_class = PurchaseOrderExtraLineResource
|
resource_class = PurchaseOrderExtraLineResource
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderLineItemAdmin(ImportExportModelAdmin):
|
class SalesOrderLineItemAdmin(ImportExportModelAdmin):
|
||||||
|
"""Admin class for the SalesOrderLine model"""
|
||||||
|
|
||||||
resource_class = SalesOrderLineItemResource
|
resource_class = SalesOrderLineItemResource
|
||||||
|
|
||||||
@@ -236,11 +247,12 @@ class SalesOrderLineItemAdmin(ImportExportModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
class SalesOrderExtraLineAdmin(GeneralExtraLineAdmin, ImportExportModelAdmin):
|
class SalesOrderExtraLineAdmin(GeneralExtraLineAdmin, ImportExportModelAdmin):
|
||||||
|
"""Admin class for the SalesOrderExtraLine model"""
|
||||||
resource_class = SalesOrderExtraLineResource
|
resource_class = SalesOrderExtraLineResource
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderShipmentAdmin(ImportExportModelAdmin):
|
class SalesOrderShipmentAdmin(ImportExportModelAdmin):
|
||||||
|
"""Admin class for the SalesOrderShipment model"""
|
||||||
|
|
||||||
list_display = [
|
list_display = [
|
||||||
'order',
|
'order',
|
||||||
@@ -258,6 +270,7 @@ class SalesOrderShipmentAdmin(ImportExportModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
class SalesOrderAllocationAdmin(ImportExportModelAdmin):
|
class SalesOrderAllocationAdmin(ImportExportModelAdmin):
|
||||||
|
"""Admin class for the SalesOrderAllocation model"""
|
||||||
|
|
||||||
list_display = (
|
list_display = (
|
||||||
'line',
|
'line',
|
||||||
|
|||||||
@@ -22,14 +22,15 @@ from django.dispatch.dispatcher import receiver
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from djmoney.contrib.exchange.models import convert_money
|
|
||||||
from djmoney.contrib.exchange.exceptions import MissingRate
|
from djmoney.contrib.exchange.exceptions import MissingRate
|
||||||
|
from djmoney.contrib.exchange.models import convert_money
|
||||||
from djmoney.money import Money
|
from djmoney.money import Money
|
||||||
from error_report.models import Error
|
from error_report.models import Error
|
||||||
from markdownx.models import MarkdownxField
|
from markdownx.models import MarkdownxField
|
||||||
from mptt.models import TreeForeignKey
|
from mptt.models import TreeForeignKey
|
||||||
|
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
|
import InvenTree.ready
|
||||||
from common.settings import currency_code_default
|
from common.settings import currency_code_default
|
||||||
from company.models import Company, SupplierPart
|
from company.models import Company, SupplierPart
|
||||||
from InvenTree.fields import InvenTreeModelMoneyField, RoundingDecimalField
|
from InvenTree.fields import InvenTreeModelMoneyField, RoundingDecimalField
|
||||||
@@ -43,7 +44,6 @@ from plugin.models import MetadataMixin
|
|||||||
from stock import models as stock_models
|
from stock import models as stock_models
|
||||||
from users import models as UserModels
|
from users import models as UserModels
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
|
|
||||||
|
|
||||||
@@ -868,9 +868,19 @@ class SalesOrder(Order):
|
|||||||
|
|
||||||
@receiver(post_save, sender=SalesOrder, dispatch_uid='build_post_save_log')
|
@receiver(post_save, sender=SalesOrder, dispatch_uid='build_post_save_log')
|
||||||
def after_save_sales_order(sender, instance: SalesOrder, created: bool, **kwargs):
|
def after_save_sales_order(sender, instance: SalesOrder, created: bool, **kwargs):
|
||||||
|
"""Callback function to be executed after a SalesOrder instance is saved.
|
||||||
|
|
||||||
|
- If the SALESORDER_DEFAULT_SHIPMENT setting is enabled, create a default shipment
|
||||||
|
- Ignore if the database is not ready for access
|
||||||
|
- Ignore if data import is active
|
||||||
"""
|
"""
|
||||||
Callback function to be executed after a SalesOrder instance is saved
|
|
||||||
"""
|
if not InvenTree.ready.canAppAccessDatabase(allow_test=True):
|
||||||
|
return
|
||||||
|
|
||||||
|
if InvenTree.ready.isImportingData():
|
||||||
|
return
|
||||||
|
|
||||||
if created and getSetting('SALESORDER_DEFAULT_SHIPMENT'):
|
if created and getSetting('SALESORDER_DEFAULT_SHIPMENT'):
|
||||||
# A new SalesOrder has just been created
|
# A new SalesOrder has just been created
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ from django.contrib import admin
|
|||||||
import import_export.widgets as widgets
|
import import_export.widgets as widgets
|
||||||
from import_export.admin import ImportExportModelAdmin
|
from import_export.admin import ImportExportModelAdmin
|
||||||
from import_export.fields import Field
|
from import_export.fields import Field
|
||||||
from import_export.resources import ModelResource
|
|
||||||
|
|
||||||
import part.models as models
|
import part.models as models
|
||||||
from company.models import SupplierPart
|
from company.models import SupplierPart
|
||||||
|
from InvenTree.admin import InvenTreeResource
|
||||||
from stock.models import StockLocation
|
from stock.models import StockLocation
|
||||||
|
|
||||||
|
|
||||||
class PartResource(ModelResource):
|
class PartResource(InvenTreeResource):
|
||||||
""" Class for managing Part data import/export """
|
"""Class for managing Part data import/export."""
|
||||||
|
|
||||||
# ForeignKey fields
|
# ForeignKey fields
|
||||||
category = Field(attribute='category', widget=widgets.ForeignKeyWidget(models.PartCategory))
|
category = Field(attribute='category', widget=widgets.ForeignKeyWidget(models.PartCategory))
|
||||||
@@ -81,8 +81,8 @@ class PartAdmin(ImportExportModelAdmin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class PartCategoryResource(ModelResource):
|
class PartCategoryResource(InvenTreeResource):
|
||||||
""" Class for managing PartCategory data import/export """
|
"""Class for managing PartCategory data import/export."""
|
||||||
|
|
||||||
parent = Field(attribute='parent', widget=widgets.ForeignKeyWidget(models.PartCategory))
|
parent = Field(attribute='parent', widget=widgets.ForeignKeyWidget(models.PartCategory))
|
||||||
|
|
||||||
@@ -157,8 +157,8 @@ class PartTestTemplateAdmin(admin.ModelAdmin):
|
|||||||
autocomplete_fields = ('part',)
|
autocomplete_fields = ('part',)
|
||||||
|
|
||||||
|
|
||||||
class BomItemResource(ModelResource):
|
class BomItemResource(InvenTreeResource):
|
||||||
""" Class for managing BomItem data import/export """
|
"""Class for managing BomItem data import/export."""
|
||||||
|
|
||||||
level = Field(attribute='level', readonly=True)
|
level = Field(attribute='level', readonly=True)
|
||||||
|
|
||||||
@@ -269,8 +269,8 @@ class ParameterTemplateAdmin(ImportExportModelAdmin):
|
|||||||
search_fields = ('name', 'units')
|
search_fields = ('name', 'units')
|
||||||
|
|
||||||
|
|
||||||
class ParameterResource(ModelResource):
|
class ParameterResource(InvenTreeResource):
|
||||||
""" Class for managing PartParameter data import/export """
|
"""Class for managing PartParameter data import/export."""
|
||||||
|
|
||||||
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(models.Part))
|
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(models.Part))
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,9 @@ def get_git_log(path):
|
|||||||
output = output.split('\n')
|
output = output.split('\n')
|
||||||
except subprocess.CalledProcessError: # pragma: no cover
|
except subprocess.CalledProcessError: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
|
except FileNotFoundError: # pragma: no cover
|
||||||
|
# Most likely the system does not have 'git' installed
|
||||||
|
pass
|
||||||
|
|
||||||
if not output:
|
if not output:
|
||||||
output = 7 * [''] # pragma: no cover
|
output = 7 * [''] # pragma: no cover
|
||||||
@@ -129,6 +132,9 @@ def check_git_version():
|
|||||||
output = str(subprocess.check_output(['git', '--version'], cwd=os.path.dirname(settings.BASE_DIR)), 'utf-8')
|
output = str(subprocess.check_output(['git', '--version'], cwd=os.path.dirname(settings.BASE_DIR)), 'utf-8')
|
||||||
except subprocess.CalledProcessError: # pragma: no cover
|
except subprocess.CalledProcessError: # pragma: no cover
|
||||||
return False
|
return False
|
||||||
|
except FileNotFoundError: # pragma: no cover
|
||||||
|
# Most likely the system does not have 'git' installed
|
||||||
|
return False
|
||||||
|
|
||||||
# process version string
|
# process version string
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -231,6 +231,9 @@ class PluginsRegistry:
|
|||||||
except subprocess.CalledProcessError as error: # pragma: no cover
|
except subprocess.CalledProcessError as error: # pragma: no cover
|
||||||
logger.error(f'Ran into error while trying to install plugins!\n{str(error)}')
|
logger.error(f'Ran into error while trying to install plugins!\n{str(error)}')
|
||||||
return False
|
return False
|
||||||
|
except FileNotFoundError: # pragma: no cover
|
||||||
|
# System most likely does not have 'git' installed
|
||||||
|
return False
|
||||||
|
|
||||||
logger.info(f'plugin requirements were run\n{output}')
|
logger.info(f'plugin requirements were run\n{output}')
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ from django.contrib import admin
|
|||||||
import import_export.widgets as widgets
|
import import_export.widgets as widgets
|
||||||
from import_export.admin import ImportExportModelAdmin
|
from import_export.admin import ImportExportModelAdmin
|
||||||
from import_export.fields import Field
|
from import_export.fields import Field
|
||||||
from import_export.resources import ModelResource
|
|
||||||
|
|
||||||
from build.models import Build
|
from build.models import Build
|
||||||
from company.models import Company, SupplierPart
|
from company.models import Company, SupplierPart
|
||||||
|
from InvenTree.admin import InvenTreeResource
|
||||||
from order.models import PurchaseOrder, SalesOrder
|
from order.models import PurchaseOrder, SalesOrder
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
|
|
||||||
@@ -14,8 +14,8 @@ from .models import (StockItem, StockItemAttachment, StockItemTestResult,
|
|||||||
StockItemTracking, StockLocation)
|
StockItemTracking, StockLocation)
|
||||||
|
|
||||||
|
|
||||||
class LocationResource(ModelResource):
|
class LocationResource(InvenTreeResource):
|
||||||
""" Class for managing StockLocation data import/export """
|
"""Class for managing StockLocation data import/export."""
|
||||||
|
|
||||||
parent = Field(attribute='parent', widget=widgets.ForeignKeyWidget(StockLocation))
|
parent = Field(attribute='parent', widget=widgets.ForeignKeyWidget(StockLocation))
|
||||||
|
|
||||||
@@ -65,8 +65,8 @@ class LocationAdmin(ImportExportModelAdmin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class StockItemResource(ModelResource):
|
class StockItemResource(InvenTreeResource):
|
||||||
""" Class for managing StockItem data import/export """
|
"""Class for managing StockItem data import/export."""
|
||||||
|
|
||||||
# Custom managers for ForeignKey fields
|
# Custom managers for ForeignKey fields
|
||||||
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))
|
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ function loadAttachmentTable(url, options) {
|
|||||||
|
|
||||||
var html = `<span class='fas ${icon}'></span> ${filename}`;
|
var html = `<span class='fas ${icon}'></span> ${filename}`;
|
||||||
|
|
||||||
return renderLink(html, value);
|
return renderLink(html, value, {download: true});
|
||||||
} else if (row.link) {
|
} else if (row.link) {
|
||||||
var html = `<span class='fas fa-link'></span> ${row.link}`;
|
var html = `<span class='fas fa-link'></span> ${row.link}`;
|
||||||
return renderLink(html, row.link);
|
return renderLink(html, row.link);
|
||||||
|
|||||||
@@ -204,6 +204,9 @@ function constructChangeForm(fields, options) {
|
|||||||
},
|
},
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
|
|
||||||
|
// Ensure the data are fully sanitized before we operate on it
|
||||||
|
data = sanitizeData(data);
|
||||||
|
|
||||||
// An optional function can be provided to process the returned results,
|
// An optional function can be provided to process the returned results,
|
||||||
// before they are rendered to the form
|
// before they are rendered to the form
|
||||||
if (options.processResults) {
|
if (options.processResults) {
|
||||||
|
|||||||
@@ -1306,7 +1306,8 @@ function loadStockTestResultsTable(table, options) {
|
|||||||
var html = value;
|
var html = value;
|
||||||
|
|
||||||
if (row.attachment) {
|
if (row.attachment) {
|
||||||
html += `<a href='${row.attachment}'><span class='fas fa-file-alt float-right'></span></a>`;
|
var text = `<span class='fas fa-file-alt float-right'></span>`;
|
||||||
|
html += renderLink(text, row.attachment, {download: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
|
|||||||
@@ -92,6 +92,13 @@ function renderLink(text, url, options={}) {
|
|||||||
|
|
||||||
var max_length = options.max_length || -1;
|
var max_length = options.max_length || -1;
|
||||||
|
|
||||||
|
var extra = '';
|
||||||
|
|
||||||
|
if (options.download) {
|
||||||
|
var fn = url.split('/').at(-1);
|
||||||
|
extra += ` download='${fn}'`;
|
||||||
|
}
|
||||||
|
|
||||||
// Shorten the displayed length if required
|
// Shorten the displayed length if required
|
||||||
if ((max_length > 0) && (text.length > max_length)) {
|
if ((max_length > 0) && (text.length > max_length)) {
|
||||||
var slice_length = (max_length - 3) / 2;
|
var slice_length = (max_length - 3) / 2;
|
||||||
@@ -102,7 +109,7 @@ function renderLink(text, url, options={}) {
|
|||||||
text = `${text_start}...${text_end}`;
|
text = `${text_start}...${text_end}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return '<a href="' + url + '">' + text + '</a>';
|
return `<a href='${url}'${extra}>${text}</a>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -282,6 +289,8 @@ $.fn.inventreeTable = function(options) {
|
|||||||
// Extract query params
|
// Extract query params
|
||||||
var filters = options.queryParams || options.filters || {};
|
var filters = options.queryParams || options.filters || {};
|
||||||
|
|
||||||
|
options.escape = true;
|
||||||
|
|
||||||
// Store the total set of query params
|
// Store the total set of query params
|
||||||
options.query_params = filters;
|
options.query_params = filters;
|
||||||
|
|
||||||
@@ -468,6 +477,49 @@ function customGroupSorter(sortName, sortOrder, sortData) {
|
|||||||
|
|
||||||
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales['en-US-custom']);
|
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales['en-US-custom']);
|
||||||
|
|
||||||
|
// Enable HTML escaping by default
|
||||||
|
$.fn.bootstrapTable.escape = true;
|
||||||
|
|
||||||
|
// Override the 'calculateObjectValue' function at bootstrap-table.js:3525
|
||||||
|
// Allows us to escape any nasty HTML tags which are rendered to the DOM
|
||||||
|
$.fn.bootstrapTable.utils._calculateObjectValue = $.fn.bootstrapTable.utils.calculateObjectValue;
|
||||||
|
|
||||||
|
$.fn.bootstrapTable.utils.calculateObjectValue = function escapeCellValue(self, name, args, defaultValue) {
|
||||||
|
|
||||||
|
var args_list = [];
|
||||||
|
|
||||||
|
if (args) {
|
||||||
|
|
||||||
|
args_list.push(args[0]);
|
||||||
|
|
||||||
|
if (name && typeof(name) === 'function' && name.name == 'formatter') {
|
||||||
|
/* This is a custom "formatter" function for a particular cell,
|
||||||
|
* which may side-step regular HTML escaping, and inject malicious code into the DOM.
|
||||||
|
*
|
||||||
|
* Here we have access to the 'args' supplied to the custom 'formatter' function,
|
||||||
|
* which are in the order:
|
||||||
|
* args = [value, row, index, field]
|
||||||
|
*
|
||||||
|
* 'row' is the one we are interested in
|
||||||
|
*/
|
||||||
|
|
||||||
|
var row = Object.assign({}, args[1]);
|
||||||
|
|
||||||
|
args_list.push(sanitizeData(row));
|
||||||
|
} else {
|
||||||
|
args_list.push(args[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var ii = 2; ii < args.length; ii++) {
|
||||||
|
args_list.push(args[ii]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = $.fn.bootstrapTable.utils._calculateObjectValue(self, name, args_list, defaultValue);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
|
|
||||||
$.extend($.fn.treegrid.defaults, {
|
$.extend($.fn.treegrid.defaults, {
|
||||||
|
|||||||
Reference in New Issue
Block a user