2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-18 21:15:41 +00:00

Merge remote-tracking branch 'inventree/master' into order-parts-wizard

# Conflicts:
#	InvenTree/order/serializers.py
#	InvenTree/templates/js/translated/model_renderers.js
This commit is contained in:
Oliver Walters
2022-05-02 16:11:11 +10:00
170 changed files with 58651 additions and 45643 deletions

View File

@ -7,11 +7,11 @@ from __future__ import unicode_literals
import datetime
from django.conf.urls import url, include
from django.urls import include, path, re_path
from django.http import JsonResponse
from django.db.models import Q, F, Count, Min, Max, Avg
from django.db import transaction
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import status
from rest_framework.response import Response
@ -383,7 +383,7 @@ class PartTestTemplateList(generics.ListCreateAPIView):
required = params.get('required', None)
if required is not None:
queryset = queryset.filter(required=required)
queryset = queryset.filter(required=str2bool(required))
return queryset
@ -1916,100 +1916,100 @@ class BomItemSubstituteDetail(generics.RetrieveUpdateDestroyAPIView):
part_api_urls = [
# Base URL for PartCategory API endpoints
url(r'^category/', include([
url(r'^tree/', CategoryTree.as_view(), name='api-part-category-tree'),
url(r'^parameters/', CategoryParameterList.as_view(), name='api-part-category-parameter-list'),
re_path(r'^category/', include([
re_path(r'^tree/', CategoryTree.as_view(), name='api-part-category-tree'),
re_path(r'^parameters/', CategoryParameterList.as_view(), name='api-part-category-parameter-list'),
url(r'^(?P<pk>\d+)/?', CategoryDetail.as_view(), name='api-part-category-detail'),
url(r'^$', CategoryList.as_view(), name='api-part-category-list'),
re_path(r'^(?P<pk>\d+)/?', CategoryDetail.as_view(), name='api-part-category-detail'),
path('', CategoryList.as_view(), name='api-part-category-list'),
])),
# Base URL for PartTestTemplate API endpoints
url(r'^test-template/', include([
url(r'^(?P<pk>\d+)/', PartTestTemplateDetail.as_view(), name='api-part-test-template-detail'),
url(r'^$', PartTestTemplateList.as_view(), name='api-part-test-template-list'),
re_path(r'^test-template/', include([
re_path(r'^(?P<pk>\d+)/', PartTestTemplateDetail.as_view(), name='api-part-test-template-detail'),
path('', PartTestTemplateList.as_view(), name='api-part-test-template-list'),
])),
# Base URL for PartAttachment API endpoints
url(r'^attachment/', include([
url(r'^(?P<pk>\d+)/', PartAttachmentDetail.as_view(), name='api-part-attachment-detail'),
url(r'^$', PartAttachmentList.as_view(), name='api-part-attachment-list'),
re_path(r'^attachment/', include([
re_path(r'^(?P<pk>\d+)/', PartAttachmentDetail.as_view(), name='api-part-attachment-detail'),
path('', PartAttachmentList.as_view(), name='api-part-attachment-list'),
])),
# Base URL for part sale pricing
url(r'^sale-price/', include([
url(r'^(?P<pk>\d+)/', PartSalePriceDetail.as_view(), name='api-part-sale-price-detail'),
url(r'^.*$', PartSalePriceList.as_view(), name='api-part-sale-price-list'),
re_path(r'^sale-price/', include([
re_path(r'^(?P<pk>\d+)/', PartSalePriceDetail.as_view(), name='api-part-sale-price-detail'),
re_path(r'^.*$', PartSalePriceList.as_view(), name='api-part-sale-price-list'),
])),
# Base URL for part internal pricing
url(r'^internal-price/', include([
url(r'^(?P<pk>\d+)/', PartInternalPriceDetail.as_view(), name='api-part-internal-price-detail'),
url(r'^.*$', PartInternalPriceList.as_view(), name='api-part-internal-price-list'),
re_path(r'^internal-price/', include([
re_path(r'^(?P<pk>\d+)/', PartInternalPriceDetail.as_view(), name='api-part-internal-price-detail'),
re_path(r'^.*$', PartInternalPriceList.as_view(), name='api-part-internal-price-list'),
])),
# Base URL for PartRelated API endpoints
url(r'^related/', include([
url(r'^(?P<pk>\d+)/', PartRelatedDetail.as_view(), name='api-part-related-detail'),
url(r'^.*$', PartRelatedList.as_view(), name='api-part-related-list'),
re_path(r'^related/', include([
re_path(r'^(?P<pk>\d+)/', PartRelatedDetail.as_view(), name='api-part-related-detail'),
re_path(r'^.*$', PartRelatedList.as_view(), name='api-part-related-list'),
])),
# Base URL for PartParameter API endpoints
url(r'^parameter/', include([
url(r'^template/$', PartParameterTemplateList.as_view(), name='api-part-parameter-template-list'),
re_path(r'^parameter/', include([
path('template/', PartParameterTemplateList.as_view(), name='api-part-parameter-template-list'),
url(r'^(?P<pk>\d+)/', PartParameterDetail.as_view(), name='api-part-parameter-detail'),
url(r'^.*$', PartParameterList.as_view(), name='api-part-parameter-list'),
re_path(r'^(?P<pk>\d+)/', PartParameterDetail.as_view(), name='api-part-parameter-detail'),
re_path(r'^.*$', PartParameterList.as_view(), name='api-part-parameter-list'),
])),
url(r'^thumbs/', include([
url(r'^$', PartThumbs.as_view(), name='api-part-thumbs'),
url(r'^(?P<pk>\d+)/?', PartThumbsUpdate.as_view(), name='api-part-thumbs-update'),
re_path(r'^thumbs/', include([
path('', PartThumbs.as_view(), name='api-part-thumbs'),
re_path(r'^(?P<pk>\d+)/?', PartThumbsUpdate.as_view(), name='api-part-thumbs-update'),
])),
url(r'^(?P<pk>\d+)/', include([
re_path(r'^(?P<pk>\d+)/', include([
# Endpoint for extra serial number information
url(r'^serial-numbers/', PartSerialNumberDetail.as_view(), name='api-part-serial-number-detail'),
re_path(r'^serial-numbers/', PartSerialNumberDetail.as_view(), name='api-part-serial-number-detail'),
# Endpoint for future scheduling information
url(r'^scheduling/', PartScheduling.as_view(), name='api-part-scheduling'),
re_path(r'^scheduling/', PartScheduling.as_view(), name='api-part-scheduling'),
# Endpoint for duplicating a BOM for the specific Part
url(r'^bom-copy/', PartCopyBOM.as_view(), name='api-part-bom-copy'),
re_path(r'^bom-copy/', PartCopyBOM.as_view(), name='api-part-bom-copy'),
# Endpoint for validating a BOM for the specific Part
url(r'^bom-validate/', PartValidateBOM.as_view(), name='api-part-bom-validate'),
re_path(r'^bom-validate/', PartValidateBOM.as_view(), name='api-part-bom-validate'),
# Part detail endpoint
url(r'^.*$', PartDetail.as_view(), name='api-part-detail'),
re_path(r'^.*$', PartDetail.as_view(), name='api-part-detail'),
])),
url(r'^.*$', PartList.as_view(), name='api-part-list'),
re_path(r'^.*$', PartList.as_view(), name='api-part-list'),
]
bom_api_urls = [
url(r'^substitute/', include([
re_path(r'^substitute/', include([
# Detail view
url(r'^(?P<pk>\d+)/', BomItemSubstituteDetail.as_view(), name='api-bom-substitute-detail'),
re_path(r'^(?P<pk>\d+)/', BomItemSubstituteDetail.as_view(), name='api-bom-substitute-detail'),
# Catch all
url(r'^.*$', BomItemSubstituteList.as_view(), name='api-bom-substitute-list'),
re_path(r'^.*$', BomItemSubstituteList.as_view(), name='api-bom-substitute-list'),
])),
# BOM Item Detail
url(r'^(?P<pk>\d+)/', include([
url(r'^validate/?', BomItemValidate.as_view(), name='api-bom-item-validate'),
url(r'^.*$', BomDetail.as_view(), name='api-bom-item-detail'),
re_path(r'^(?P<pk>\d+)/', include([
re_path(r'^validate/?', BomItemValidate.as_view(), name='api-bom-item-validate'),
re_path(r'^.*$', BomDetail.as_view(), name='api-bom-item-detail'),
])),
# API endpoint URLs for importing BOM data
url(r'^import/upload/', BomImportUpload.as_view(), name='api-bom-import-upload'),
url(r'^import/extract/', BomImportExtract.as_view(), name='api-bom-import-extract'),
url(r'^import/submit/', BomImportSubmit.as_view(), name='api-bom-import-submit'),
re_path(r'^import/upload/', BomImportUpload.as_view(), name='api-bom-import-upload'),
re_path(r'^import/extract/', BomImportExtract.as_view(), name='api-bom-import-extract'),
re_path(r'^import/submit/', BomImportSubmit.as_view(), name='api-bom-import-submit'),
# Catch-all
url(r'^.*$', BomList.as_view(), name='api-bom-list'),
re_path(r'^.*$', BomList.as_view(), name='api-bom-list'),
]

View File

@ -6,7 +6,7 @@ Django Forms for interacting with Part objects
from __future__ import unicode_literals
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from mptt.fields import TreeNodeChoiceField

View File

@ -2510,7 +2510,7 @@ def validate_template_name(name):
Prevent illegal characters in "name" field for PartParameterTemplate
"""
for c in "!@#$%^&*()<>{}[].,?/\|~`_+-=\'\"":
for c in "!@#$%^&*()<>{}[].,?/\\|~`_+-=\'\"":
if c in str(name):
raise ValidationError(_(f"Illegal character in template name ({c})"))
@ -2740,8 +2740,8 @@ class BomItem(models.Model, DataImportMixin):
if not p.active:
continue
# Trackable parts cannot be 'auto allocated'
if p.trackable:
# Trackable status must be the same as the sub_part
if p.trackable != self.sub_part.trackable:
continue
valid_parts.append(p)

View File

@ -11,7 +11,7 @@ from django.db.models import ExpressionWrapper, F, Q, Func
from django.db.models import Subquery, OuterRef, FloatField
from django.db.models.functions import Coalesce
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from sql_util.utils import SubqueryCount, SubquerySum

View File

@ -3,7 +3,7 @@ from __future__ import unicode_literals
import logging
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
import InvenTree.helpers
import InvenTree.tasks

View File

@ -12,13 +12,14 @@ import logging
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from django.conf import settings as djangosettings
from django import template
from django.urls import reverse
from django.utils.safestring import mark_safe
from django.templatetags.static import StaticNode
from django.templatetags.static import StaticNode, static
from django.core.files.storage import default_storage
from InvenTree import version, settings
@ -160,10 +161,11 @@ def inventree_in_debug_mode(*args, **kwargs):
@register.simple_tag()
def inventree_demo_mode(*args, **kwargs):
""" Return True if the server is running in DEMO mode """
return djangosettings.DEMO_MODE
def inventree_show_about(user, *args, **kwargs):
""" Return True if the about modal should be shown """
if InvenTreeSetting.get_setting('INVENTREE_RESTRICT_ABOUT') and not user.is_superuser:
return False
return True
@register.simple_tag()
@ -220,8 +222,13 @@ def python_version(*args, **kwargs):
@register.simple_tag()
def inventree_version(*args, **kwargs):
def inventree_version(shortstring=False, *args, **kwargs):
""" Return InvenTree version string """
if shortstring:
return _("{title} v{version}".format(
title=version.inventreeInstanceTitle(),
version=version.inventreeVersion()
))
return version.inventreeVersion()
@ -512,6 +519,22 @@ def mail_configured():
return bool(settings.EMAIL_HOST)
@register.simple_tag()
def inventree_customize(reference, *args, **kwargs):
""" Return customization values for the user interface """
return djangosettings.CUSTOMIZE.get(reference, '')
@register.simple_tag()
def inventree_logo(*args, **kwargs):
""" Return the path to the logo-file """
if settings.CUSTOM_LOGO:
return default_storage.url(settings.CUSTOM_LOGO)
return static('img/inventree.png')
class I18nStaticNode(StaticNode):
"""
custom StaticNode

View File

@ -8,53 +8,53 @@ URL lookup for Part app. Provides URL endpoints for:
"""
from django.conf.urls import url, include
from django.urls import include, re_path
from . import views
part_parameter_urls = [
url(r'^template/new/', views.PartParameterTemplateCreate.as_view(), name='part-param-template-create'),
url(r'^template/(?P<pk>\d+)/edit/', views.PartParameterTemplateEdit.as_view(), name='part-param-template-edit'),
url(r'^template/(?P<pk>\d+)/delete/', views.PartParameterTemplateDelete.as_view(), name='part-param-template-edit'),
re_path(r'^template/new/', views.PartParameterTemplateCreate.as_view(), name='part-param-template-create'),
re_path(r'^template/(?P<pk>\d+)/edit/', views.PartParameterTemplateEdit.as_view(), name='part-param-template-edit'),
re_path(r'^template/(?P<pk>\d+)/delete/', views.PartParameterTemplateDelete.as_view(), name='part-param-template-edit'),
]
part_detail_urls = [
url(r'^delete/?', views.PartDelete.as_view(), name='part-delete'),
url(r'^bom-download/?', views.BomDownload.as_view(), name='bom-download'),
re_path(r'^delete/?', views.PartDelete.as_view(), name='part-delete'),
re_path(r'^bom-download/?', views.BomDownload.as_view(), name='bom-download'),
url(r'^pricing/', views.PartPricing.as_view(), name='part-pricing'),
re_path(r'^pricing/', views.PartPricing.as_view(), name='part-pricing'),
url(r'^bom-upload/?', views.BomUpload.as_view(), name='upload-bom'),
re_path(r'^bom-upload/?', views.BomUpload.as_view(), name='upload-bom'),
url(r'^qr_code/?', views.PartQRCode.as_view(), name='part-qr'),
re_path(r'^qr_code/?', views.PartQRCode.as_view(), name='part-qr'),
# Normal thumbnail with form
url(r'^thumb-select/?', views.PartImageSelect.as_view(), name='part-image-select'),
url(r'^thumb-download/', views.PartImageDownloadFromURL.as_view(), name='part-image-download'),
re_path(r'^thumb-select/?', views.PartImageSelect.as_view(), name='part-image-select'),
re_path(r'^thumb-download/', views.PartImageDownloadFromURL.as_view(), name='part-image-download'),
# Any other URLs go to the part detail page
url(r'^.*$', views.PartDetail.as_view(), name='part-detail'),
re_path(r'^.*$', views.PartDetail.as_view(), name='part-detail'),
]
category_parameter_urls = [
url(r'^new/', views.CategoryParameterTemplateCreate.as_view(), name='category-param-template-create'),
url(r'^(?P<pid>\d+)/edit/', views.CategoryParameterTemplateEdit.as_view(), name='category-param-template-edit'),
url(r'^(?P<pid>\d+)/delete/', views.CategoryParameterTemplateDelete.as_view(), name='category-param-template-delete'),
re_path(r'^new/', views.CategoryParameterTemplateCreate.as_view(), name='category-param-template-create'),
re_path(r'^(?P<pid>\d+)/edit/', views.CategoryParameterTemplateEdit.as_view(), name='category-param-template-edit'),
re_path(r'^(?P<pid>\d+)/delete/', views.CategoryParameterTemplateDelete.as_view(), name='category-param-template-delete'),
]
category_urls = [
# Top level subcategory display
url(r'^subcategory/', views.PartIndex.as_view(template_name='part/subcategory.html'), name='category-index-subcategory'),
re_path(r'^subcategory/', views.PartIndex.as_view(template_name='part/subcategory.html'), name='category-index-subcategory'),
# Category detail views
url(r'(?P<pk>\d+)/', include([
url(r'^delete/', views.CategoryDelete.as_view(), name='category-delete'),
url(r'^parameters/', include(category_parameter_urls)),
re_path(r'(?P<pk>\d+)/', include([
re_path(r'^delete/', views.CategoryDelete.as_view(), name='category-delete'),
re_path(r'^parameters/', include(category_parameter_urls)),
# Anything else
url(r'^.*$', views.CategoryDetail.as_view(), name='category-detail'),
re_path(r'^.*$', views.CategoryDetail.as_view(), name='category-detail'),
]))
]
@ -62,27 +62,27 @@ category_urls = [
part_urls = [
# Upload a part
url(r'^import/', views.PartImport.as_view(), name='part-import'),
url(r'^import-api/', views.PartImportAjax.as_view(), name='api-part-import'),
re_path(r'^import/', views.PartImport.as_view(), name='part-import'),
re_path(r'^import-api/', views.PartImportAjax.as_view(), name='api-part-import'),
# Download a BOM upload template
url(r'^bom_template/?', views.BomUploadTemplate.as_view(), name='bom-upload-template'),
re_path(r'^bom_template/?', views.BomUploadTemplate.as_view(), name='bom-upload-template'),
# Individual part using pk
url(r'^(?P<pk>\d+)/', include(part_detail_urls)),
re_path(r'^(?P<pk>\d+)/', include(part_detail_urls)),
# Part category
url(r'^category/', include(category_urls)),
re_path(r'^category/', include(category_urls)),
# Part parameters
url(r'^parameter/', include(part_parameter_urls)),
re_path(r'^parameter/', include(part_parameter_urls)),
# Change category for multiple parts
url(r'^set-category/?', views.PartSetCategory.as_view(), name='part-set-category'),
re_path(r'^set-category/?', views.PartSetCategory.as_view(), name='part-set-category'),
# Individual part using IPN as slug
url(r'^(?P<slug>[-\w]+)/', views.PartDetailFromIPN.as_view(), name='part-detail-from-ipn'),
re_path(r'^(?P<slug>[-\w]+)/', views.PartDetailFromIPN.as_view(), name='part-detail-from-ipn'),
# Top level part list (display top level parts and categories)
url(r'^.*$', views.PartIndex.as_view(), name='part-index'),
re_path(r'^.*$', views.PartIndex.as_view(), name='part-index'),
]