mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-17 18:26:32 +00:00
[CI] Enable python autoformat (#6169)
* Squashed commit of the following: commitf5cf7b2e78
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:36:57 2024 +0100 fixed reqs commit9d845bee98
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:32:35 2024 +0100 disable autofix/format commitaff5f27148
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:28:50 2024 +0100 adjust checks commit47271cf1ef
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:28:22 2024 +0100 reorder order of operations commite1bf178b40
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:01:09 2024 +0100 adapted ruff settings to better fit code base commitad7d88a6f4
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 19:59:45 2024 +0100 auto fixed docstring commita2e54a760e
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 19:46:35 2024 +0100 fix getattr useage commitcb80c73bc6
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 19:25:09 2024 +0100 fix requirements file commitb7780bbd21
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:42:28 2024 +0100 fix removed sections commit71f1681f55
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:41:21 2024 +0100 fix djlint syntax commita0bcf1bcce
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:35:28 2024 +0100 remove flake8 from code base commit22475b31cc
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:34:56 2024 +0100 remove flake8 from code base commit0413350f14
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:24:39 2024 +0100 moved ruff section commitd90c48a0bf
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:24:24 2024 +0100 move djlint config to pyproject commitc5ce55d511
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:20:39 2024 +0100 added isort again commit42a41d23af
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:19:02 2024 +0100 move config section commit8569233181
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:17:52 2024 +0100 fix codespell error commit2897c6704d
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 17:29:21 2024 +0100 replaced flake8 with ruff mostly for speed improvements * enable autoformat * added autofixes * switched to single quotes everywhere * switched to ruff for import sorting * fix wrong url response * switched to pathlib for lookup * fixed lookup * Squashed commit of the following: commitd3b795824b
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 22:56:17 2024 +0100 fixed source path commit0bac0c19b8
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 22:47:53 2024 +0100 fixed req commit9f61f01d9c
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 22:45:18 2024 +0100 added missing toml req commit91b71ed24a
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:49:50 2024 +0100 moved isort config commit12460b0419
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:43:22 2024 +0100 remove flake8 section from setup.cfg commitf5cf7b2e78
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:36:57 2024 +0100 fixed reqs commit9d845bee98
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:32:35 2024 +0100 disable autofix/format commitaff5f27148
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:28:50 2024 +0100 adjust checks commit47271cf1ef
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:28:22 2024 +0100 reorder order of operations commite1bf178b40
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:01:09 2024 +0100 adapted ruff settings to better fit code base commitad7d88a6f4
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 19:59:45 2024 +0100 auto fixed docstring commita2e54a760e
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 19:46:35 2024 +0100 fix getattr useage commitcb80c73bc6
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 19:25:09 2024 +0100 fix requirements file commitb7780bbd21
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:42:28 2024 +0100 fix removed sections commit71f1681f55
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:41:21 2024 +0100 fix djlint syntax commita0bcf1bcce
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:35:28 2024 +0100 remove flake8 from code base commit22475b31cc
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:34:56 2024 +0100 remove flake8 from code base commit0413350f14
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:24:39 2024 +0100 moved ruff section commitd90c48a0bf
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:24:24 2024 +0100 move djlint config to pyproject commitc5ce55d511
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:20:39 2024 +0100 added isort again commit42a41d23af
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:19:02 2024 +0100 move config section commit8569233181
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:17:52 2024 +0100 fix codespell error commit2897c6704d
Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 17:29:21 2024 +0100 replaced flake8 with ruff mostly for speed improvements * fix coverage souce format --------- Co-authored-by: Oliver Walters <oliver.henry.walters@gmail.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import label.models
|
||||
|
||||
class LabelAdmin(admin.ModelAdmin):
|
||||
"""Admin class for the various label models"""
|
||||
|
||||
list_display = ('name', 'description', 'label', 'filters', 'enabled')
|
||||
|
||||
|
||||
|
@@ -18,8 +18,7 @@ import label.models
|
||||
import label.serializers
|
||||
from InvenTree.api import MetadataView
|
||||
from InvenTree.filters import InvenTreeSearchFilter
|
||||
from InvenTree.mixins import (ListCreateAPI, RetrieveAPI,
|
||||
RetrieveUpdateDestroyAPI)
|
||||
from InvenTree.mixins import ListCreateAPI, RetrieveAPI, RetrieveUpdateDestroyAPI
|
||||
from part.models import Part
|
||||
from plugin.builtin.labels.inventree_label import InvenTreeLabelPlugin
|
||||
from plugin.registry import registry
|
||||
@@ -59,7 +58,7 @@ class LabelFilterMixin:
|
||||
for id in ids:
|
||||
try:
|
||||
valid_ids.append(int(id))
|
||||
except (ValueError):
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Filter queryset by matching ID values
|
||||
@@ -120,34 +119,23 @@ class LabelListView(LabelFilterMixin, ListCreateAPI):
|
||||
|
||||
return queryset
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
InvenTreeSearchFilter
|
||||
]
|
||||
filter_backends = [DjangoFilterBackend, InvenTreeSearchFilter]
|
||||
|
||||
filterset_fields = [
|
||||
'enabled',
|
||||
]
|
||||
filterset_fields = ['enabled']
|
||||
|
||||
search_fields = [
|
||||
'name',
|
||||
'description',
|
||||
]
|
||||
search_fields = ['name', 'description']
|
||||
|
||||
|
||||
@method_decorator(cache_page(5), name='dispatch')
|
||||
class LabelPrintMixin(LabelFilterMixin):
|
||||
"""Mixin for printing labels."""
|
||||
|
||||
rolemap = {
|
||||
"GET": "view",
|
||||
"POST": "view",
|
||||
}
|
||||
rolemap = {'GET': 'view', 'POST': 'view'}
|
||||
|
||||
def check_permissions(self, request):
|
||||
"""Override request method to GET so that also non superusers can print using a post request."""
|
||||
if request.method == "POST":
|
||||
request = clone_request(request, "GET")
|
||||
if request.method == 'POST':
|
||||
request = clone_request(request, 'GET')
|
||||
return super().check_permissions(request)
|
||||
|
||||
@method_decorator(never_cache)
|
||||
@@ -161,7 +149,9 @@ class LabelPrintMixin(LabelFilterMixin):
|
||||
plugin = self.get_plugin(self.request)
|
||||
|
||||
kwargs.setdefault('context', self.get_serializer_context())
|
||||
serializer = plugin.get_printing_options_serializer(self.request, *args, **kwargs)
|
||||
serializer = plugin.get_printing_options_serializer(
|
||||
self.request, *args, **kwargs
|
||||
)
|
||||
|
||||
# if no serializer is defined, return an empty serializer
|
||||
if not serializer:
|
||||
@@ -171,8 +161,12 @@ class LabelPrintMixin(LabelFilterMixin):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Perform a GET request against this endpoint to print labels"""
|
||||
common.models.InvenTreeUserSetting.set_setting('DEFAULT_' + self.ITEM_KEY.upper() + '_LABEL_TEMPLATE',
|
||||
self.get_object().pk, None, user=request.user)
|
||||
common.models.InvenTreeUserSetting.set_setting(
|
||||
'DEFAULT_' + self.ITEM_KEY.upper() + '_LABEL_TEMPLATE',
|
||||
self.get_object().pk,
|
||||
None,
|
||||
user=request.user,
|
||||
)
|
||||
return self.print(request, self.get_items())
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
@@ -205,8 +199,10 @@ class LabelPrintMixin(LabelFilterMixin):
|
||||
if not plugin.is_active():
|
||||
raise ValidationError(f"Plugin '{plugin_key}' is not enabled")
|
||||
|
||||
if not plugin.mixin_enabled("labels"):
|
||||
raise ValidationError(f"Plugin '{plugin_key}' is not a label printing plugin")
|
||||
if not plugin.mixin_enabled('labels'):
|
||||
raise ValidationError(
|
||||
f"Plugin '{plugin_key}' is not a label printing plugin"
|
||||
)
|
||||
|
||||
# Only return the plugin if it is enabled and has the label printing mixin
|
||||
return plugin
|
||||
@@ -228,18 +224,24 @@ class LabelPrintMixin(LabelFilterMixin):
|
||||
raise ValidationError('Label has invalid dimensions')
|
||||
|
||||
# if the plugin returns a serializer, validate the data
|
||||
if serializer := plugin.get_printing_options_serializer(request, data=request.data, context=self.get_serializer_context()):
|
||||
if serializer := plugin.get_printing_options_serializer(
|
||||
request, data=request.data, context=self.get_serializer_context()
|
||||
):
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
# At this point, we offload the label(s) to the selected plugin.
|
||||
# The plugin is responsible for handling the request and returning a response.
|
||||
|
||||
result = plugin.print_labels(label, items_to_print, request, printing_options=request.data)
|
||||
result = plugin.print_labels(
|
||||
label, items_to_print, request, printing_options=request.data
|
||||
)
|
||||
|
||||
if isinstance(result, JsonResponse):
|
||||
result['plugin'] = plugin.plugin_slug()
|
||||
return result
|
||||
raise ValidationError(f"Plugin '{plugin.plugin_slug()}' returned invalid response type '{type(result)}'")
|
||||
raise ValidationError(
|
||||
f"Plugin '{plugin.plugin_slug()}' returned invalid response type '{type(result)}'"
|
||||
)
|
||||
|
||||
|
||||
class StockItemLabelMixin:
|
||||
@@ -261,16 +263,19 @@ class StockItemLabelList(StockItemLabelMixin, LabelListView):
|
||||
- item: Filter by single stock item
|
||||
- items: Filter by list of stock items
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class StockItemLabelDetail(StockItemLabelMixin, RetrieveUpdateDestroyAPI):
|
||||
"""API endpoint for a single StockItemLabel object."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class StockItemLabelPrint(StockItemLabelMixin, LabelPrintMixin, RetrieveAPI):
|
||||
"""API endpoint for printing a StockItemLabel object."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -293,21 +298,25 @@ class StockLocationLabelList(StockLocationLabelMixin, LabelListView):
|
||||
- location: Filter by a single stock location
|
||||
- locations: Filter by list of stock locations
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class StockLocationLabelDetail(StockLocationLabelMixin, RetrieveUpdateDestroyAPI):
|
||||
"""API endpoint for a single StockLocationLabel object."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class StockLocationLabelPrint(StockLocationLabelMixin, LabelPrintMixin, RetrieveAPI):
|
||||
"""API endpoint for printing a StockLocationLabel object."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PartLabelMixin:
|
||||
"""Mixin for PartLabel endpoints"""
|
||||
|
||||
queryset = label.models.PartLabel.objects.all()
|
||||
serializer_class = label.serializers.PartLabelSerializer
|
||||
|
||||
@@ -317,16 +326,19 @@ class PartLabelMixin:
|
||||
|
||||
class PartLabelList(PartLabelMixin, LabelListView):
|
||||
"""API endpoint for viewing list of PartLabel objects."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PartLabelDetail(PartLabelMixin, RetrieveUpdateDestroyAPI):
|
||||
"""API endpoint for a single PartLabel object."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PartLabelPrint(PartLabelMixin, LabelPrintMixin, RetrieveAPI):
|
||||
"""API endpoint for printing a PartLabel object."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -342,70 +354,147 @@ class BuildLineLabelMixin:
|
||||
|
||||
class BuildLineLabelList(BuildLineLabelMixin, LabelListView):
|
||||
"""API endpoint for viewing a list of BuildLineLabel objects"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class BuildLineLabelDetail(BuildLineLabelMixin, RetrieveUpdateDestroyAPI):
|
||||
"""API endpoint for a single BuildLineLabel object"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class BuildLineLabelPrint(BuildLineLabelMixin, LabelPrintMixin, RetrieveAPI):
|
||||
"""API endpoint for printing a BuildLineLabel object"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
label_api_urls = [
|
||||
|
||||
# Stock item labels
|
||||
re_path(r'stock/', include([
|
||||
# Detail views
|
||||
path(r'<int:pk>/', include([
|
||||
re_path(r'print/?', StockItemLabelPrint.as_view(), name='api-stockitem-label-print'),
|
||||
re_path(r'metadata/', MetadataView.as_view(), {'model': label.models.StockItemLabel}, name='api-stockitem-label-metadata'),
|
||||
re_path(r'^.*$', StockItemLabelDetail.as_view(), name='api-stockitem-label-detail'),
|
||||
])),
|
||||
|
||||
# List view
|
||||
re_path(r'^.*$', StockItemLabelList.as_view(), name='api-stockitem-label-list'),
|
||||
])),
|
||||
|
||||
re_path(
|
||||
r'stock/',
|
||||
include([
|
||||
# Detail views
|
||||
path(
|
||||
r'<int:pk>/',
|
||||
include([
|
||||
re_path(
|
||||
r'print/?',
|
||||
StockItemLabelPrint.as_view(),
|
||||
name='api-stockitem-label-print',
|
||||
),
|
||||
re_path(
|
||||
r'metadata/',
|
||||
MetadataView.as_view(),
|
||||
{'model': label.models.StockItemLabel},
|
||||
name='api-stockitem-label-metadata',
|
||||
),
|
||||
re_path(
|
||||
r'^.*$',
|
||||
StockItemLabelDetail.as_view(),
|
||||
name='api-stockitem-label-detail',
|
||||
),
|
||||
]),
|
||||
),
|
||||
# List view
|
||||
re_path(
|
||||
r'^.*$', StockItemLabelList.as_view(), name='api-stockitem-label-list'
|
||||
),
|
||||
]),
|
||||
),
|
||||
# Stock location labels
|
||||
re_path(r'location/', include([
|
||||
# Detail views
|
||||
path(r'<int:pk>/', include([
|
||||
re_path(r'print/?', StockLocationLabelPrint.as_view(), name='api-stocklocation-label-print'),
|
||||
re_path(r'metadata/', MetadataView.as_view(), {'model': label.models.StockLocationLabel}, name='api-stocklocation-label-metadata'),
|
||||
re_path(r'^.*$', StockLocationLabelDetail.as_view(), name='api-stocklocation-label-detail'),
|
||||
])),
|
||||
|
||||
# List view
|
||||
re_path(r'^.*$', StockLocationLabelList.as_view(), name='api-stocklocation-label-list'),
|
||||
])),
|
||||
|
||||
re_path(
|
||||
r'location/',
|
||||
include([
|
||||
# Detail views
|
||||
path(
|
||||
r'<int:pk>/',
|
||||
include([
|
||||
re_path(
|
||||
r'print/?',
|
||||
StockLocationLabelPrint.as_view(),
|
||||
name='api-stocklocation-label-print',
|
||||
),
|
||||
re_path(
|
||||
r'metadata/',
|
||||
MetadataView.as_view(),
|
||||
{'model': label.models.StockLocationLabel},
|
||||
name='api-stocklocation-label-metadata',
|
||||
),
|
||||
re_path(
|
||||
r'^.*$',
|
||||
StockLocationLabelDetail.as_view(),
|
||||
name='api-stocklocation-label-detail',
|
||||
),
|
||||
]),
|
||||
),
|
||||
# List view
|
||||
re_path(
|
||||
r'^.*$',
|
||||
StockLocationLabelList.as_view(),
|
||||
name='api-stocklocation-label-list',
|
||||
),
|
||||
]),
|
||||
),
|
||||
# Part labels
|
||||
re_path(r'^part/', include([
|
||||
# Detail views
|
||||
path(r'<int:pk>/', include([
|
||||
re_path(r'^print/', PartLabelPrint.as_view(), name='api-part-label-print'),
|
||||
re_path(r'^metadata/', MetadataView.as_view(), {'model': label.models.PartLabel}, name='api-part-label-metadata'),
|
||||
re_path(r'^.*$', PartLabelDetail.as_view(), name='api-part-label-detail'),
|
||||
])),
|
||||
|
||||
# List view
|
||||
re_path(r'^.*$', PartLabelList.as_view(), name='api-part-label-list'),
|
||||
])),
|
||||
|
||||
re_path(
|
||||
r'^part/',
|
||||
include([
|
||||
# Detail views
|
||||
path(
|
||||
r'<int:pk>/',
|
||||
include([
|
||||
re_path(
|
||||
r'^print/',
|
||||
PartLabelPrint.as_view(),
|
||||
name='api-part-label-print',
|
||||
),
|
||||
re_path(
|
||||
r'^metadata/',
|
||||
MetadataView.as_view(),
|
||||
{'model': label.models.PartLabel},
|
||||
name='api-part-label-metadata',
|
||||
),
|
||||
re_path(
|
||||
r'^.*$', PartLabelDetail.as_view(), name='api-part-label-detail'
|
||||
),
|
||||
]),
|
||||
),
|
||||
# List view
|
||||
re_path(r'^.*$', PartLabelList.as_view(), name='api-part-label-list'),
|
||||
]),
|
||||
),
|
||||
# BuildLine labels
|
||||
re_path(r'^buildline/', include([
|
||||
# Detail views
|
||||
path(r'<int:pk>/', include([
|
||||
re_path(r'^print/', BuildLineLabelPrint.as_view(), name='api-buildline-label-print'),
|
||||
re_path(r'^metadata/', MetadataView.as_view(), {'model': label.models.BuildLineLabel}, name='api-buildline-label-metadata'),
|
||||
re_path(r'^.*$', BuildLineLabelDetail.as_view(), name='api-buildline-label-detail'),
|
||||
])),
|
||||
|
||||
# List view
|
||||
re_path(r'^.*$', BuildLineLabelList.as_view(), name='api-buildline-label-list'),
|
||||
])),
|
||||
re_path(
|
||||
r'^buildline/',
|
||||
include([
|
||||
# Detail views
|
||||
path(
|
||||
r'<int:pk>/',
|
||||
include([
|
||||
re_path(
|
||||
r'^print/',
|
||||
BuildLineLabelPrint.as_view(),
|
||||
name='api-buildline-label-print',
|
||||
),
|
||||
re_path(
|
||||
r'^metadata/',
|
||||
MetadataView.as_view(),
|
||||
{'model': label.models.BuildLineLabel},
|
||||
name='api-buildline-label-metadata',
|
||||
),
|
||||
re_path(
|
||||
r'^.*$',
|
||||
BuildLineLabelDetail.as_view(),
|
||||
name='api-buildline-label-detail',
|
||||
),
|
||||
]),
|
||||
),
|
||||
# List view
|
||||
re_path(
|
||||
r'^.*$', BuildLineLabelList.as_view(), name='api-buildline-label-list'
|
||||
),
|
||||
]),
|
||||
),
|
||||
]
|
||||
|
@@ -14,7 +14,7 @@ from django.db.utils import IntegrityError, OperationalError, ProgrammingError
|
||||
|
||||
import InvenTree.ready
|
||||
|
||||
logger = logging.getLogger("inventree")
|
||||
logger = logging.getLogger('inventree')
|
||||
|
||||
|
||||
def hashFile(filename):
|
||||
@@ -36,23 +36,37 @@ class LabelConfig(AppConfig):
|
||||
def ready(self):
|
||||
"""This function is called whenever the label app is loaded."""
|
||||
# skip loading if plugin registry is not loaded or we run in a background thread
|
||||
if not InvenTree.ready.isPluginRegistryLoaded() or not InvenTree.ready.isInMainThread():
|
||||
if (
|
||||
not InvenTree.ready.isPluginRegistryLoaded()
|
||||
or not InvenTree.ready.isInMainThread()
|
||||
):
|
||||
return
|
||||
|
||||
if InvenTree.ready.isRunningMigrations():
|
||||
return
|
||||
|
||||
if InvenTree.ready.canAppAccessDatabase(allow_test=False) and not InvenTree.ready.isImportingData():
|
||||
if (
|
||||
InvenTree.ready.canAppAccessDatabase(allow_test=False)
|
||||
and not InvenTree.ready.isImportingData()
|
||||
):
|
||||
try:
|
||||
self.create_labels() # pragma: no cover
|
||||
except (AppRegistryNotReady, IntegrityError, OperationalError, ProgrammingError):
|
||||
except (
|
||||
AppRegistryNotReady,
|
||||
IntegrityError,
|
||||
OperationalError,
|
||||
ProgrammingError,
|
||||
):
|
||||
# Database might not yet be ready
|
||||
warnings.warn('Database was not ready for creating labels', stacklevel=2)
|
||||
warnings.warn(
|
||||
'Database was not ready for creating labels', stacklevel=2
|
||||
)
|
||||
|
||||
def create_labels(self):
|
||||
"""Create all default templates."""
|
||||
# Test if models are ready
|
||||
import label.models
|
||||
|
||||
assert bool(label.models.StockLocationLabel is not None)
|
||||
|
||||
# Create the categories
|
||||
@@ -66,7 +80,7 @@ class LabelConfig(AppConfig):
|
||||
'description': 'Simple QR code label',
|
||||
'width': 24,
|
||||
'height': 24,
|
||||
},
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
@@ -87,8 +101,8 @@ class LabelConfig(AppConfig):
|
||||
'description': 'Label with QR code and name of location',
|
||||
'width': 50,
|
||||
'height': 24,
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
self.create_labels_category(
|
||||
@@ -109,7 +123,7 @@ class LabelConfig(AppConfig):
|
||||
'width': 70,
|
||||
'height': 24,
|
||||
},
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
self.create_labels_category(
|
||||
@@ -122,24 +136,16 @@ class LabelConfig(AppConfig):
|
||||
'description': 'Example build line label',
|
||||
'width': 125,
|
||||
'height': 48,
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
def create_labels_category(self, model, ref_name, labels):
|
||||
"""Create folder and database entries for the default templates, if they do not already exist."""
|
||||
# Create root dir for templates
|
||||
src_dir = Path(__file__).parent.joinpath(
|
||||
'templates',
|
||||
'label',
|
||||
ref_name,
|
||||
)
|
||||
src_dir = Path(__file__).parent.joinpath('templates', 'label', ref_name)
|
||||
|
||||
dst_dir = settings.MEDIA_ROOT.joinpath(
|
||||
'label',
|
||||
'inventree',
|
||||
ref_name,
|
||||
)
|
||||
dst_dir = settings.MEDIA_ROOT.joinpath('label', 'inventree', ref_name)
|
||||
|
||||
if not dst_dir.exists():
|
||||
logger.info("Creating required directory: '%s'", dst_dir)
|
||||
@@ -151,12 +157,7 @@ class LabelConfig(AppConfig):
|
||||
|
||||
def create_template_label(self, model, src_dir, ref_name, label):
|
||||
"""Ensure a label template is in place."""
|
||||
filename = os.path.join(
|
||||
'label',
|
||||
'inventree',
|
||||
ref_name,
|
||||
label['file']
|
||||
)
|
||||
filename = os.path.join('label', 'inventree', ref_name, label['file'])
|
||||
|
||||
src_file = src_dir.joinpath(label['file'])
|
||||
dst_file = settings.MEDIA_ROOT.joinpath(filename)
|
||||
@@ -187,7 +188,10 @@ class LabelConfig(AppConfig):
|
||||
if model.objects.filter(label=filename).exists():
|
||||
return # pragma: no cover
|
||||
except Exception:
|
||||
logger.exception("Failed to query label for '%s' - you should run 'invoke update' first!", filename)
|
||||
logger.exception(
|
||||
"Failed to query label for '%s' - you should run 'invoke update' first!",
|
||||
filename,
|
||||
)
|
||||
|
||||
logger.info("Creating entry for %s '%s'", model, label['name'])
|
||||
|
||||
|
@@ -25,12 +25,12 @@ from plugin.registry import registry
|
||||
try:
|
||||
from django_weasyprint import WeasyTemplateResponseMixin
|
||||
except OSError as err: # pragma: no cover
|
||||
print(f"OSError: {err}")
|
||||
print("You may require some further system packages to be installed.")
|
||||
print(f'OSError: {err}')
|
||||
print('You may require some further system packages to be installed.')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
logger = logging.getLogger("inventree")
|
||||
logger = logging.getLogger('inventree')
|
||||
|
||||
|
||||
def rename_label(instance, filename):
|
||||
@@ -97,7 +97,7 @@ class LabelTemplate(MetadataMixin, models.Model):
|
||||
abstract = True
|
||||
|
||||
# Each class of label files will be stored in a separate subdirectory
|
||||
SUBDIR = "label"
|
||||
SUBDIR = 'label'
|
||||
|
||||
# Object we will be printing against (will be filled out later)
|
||||
object_to_print = None
|
||||
@@ -109,17 +109,16 @@ class LabelTemplate(MetadataMixin, models.Model):
|
||||
|
||||
def __str__(self):
|
||||
"""Format a string representation of a label instance"""
|
||||
return f"{self.name} - {self.description}"
|
||||
return f'{self.name} - {self.description}'
|
||||
|
||||
name = models.CharField(
|
||||
blank=False, max_length=100,
|
||||
verbose_name=_('Name'),
|
||||
help_text=_('Label name'),
|
||||
blank=False, max_length=100, verbose_name=_('Name'), help_text=_('Label name')
|
||||
)
|
||||
|
||||
description = models.CharField(
|
||||
max_length=250,
|
||||
blank=True, null=True,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_('Description'),
|
||||
help_text=_('Label description'),
|
||||
)
|
||||
@@ -127,7 +126,8 @@ class LabelTemplate(MetadataMixin, models.Model):
|
||||
label = models.FileField(
|
||||
upload_to=rename_label,
|
||||
unique=True,
|
||||
blank=False, null=False,
|
||||
blank=False,
|
||||
null=False,
|
||||
verbose_name=_('Label'),
|
||||
help_text=_('Label template file'),
|
||||
validators=[FileExtensionValidator(allowed_extensions=['html'])],
|
||||
@@ -143,18 +143,18 @@ class LabelTemplate(MetadataMixin, models.Model):
|
||||
default=50,
|
||||
verbose_name=_('Width [mm]'),
|
||||
help_text=_('Label width, specified in mm'),
|
||||
validators=[MinValueValidator(2)]
|
||||
validators=[MinValueValidator(2)],
|
||||
)
|
||||
|
||||
height = models.FloatField(
|
||||
default=20,
|
||||
verbose_name=_('Height [mm]'),
|
||||
help_text=_('Label height, specified in mm'),
|
||||
validators=[MinValueValidator(2)]
|
||||
validators=[MinValueValidator(2)],
|
||||
)
|
||||
|
||||
filename_pattern = models.CharField(
|
||||
default="label.pdf",
|
||||
default='label.pdf',
|
||||
verbose_name=_('Filename Pattern'),
|
||||
help_text=_('Pattern for generating label filenames'),
|
||||
max_length=100,
|
||||
@@ -249,11 +249,7 @@ class LabelTemplate(MetadataMixin, models.Model):
|
||||
|
||||
context = self.context(request, **kwargs)
|
||||
|
||||
return render_to_string(
|
||||
self.template_name,
|
||||
context,
|
||||
request
|
||||
)
|
||||
return render_to_string(self.template_name, context, request)
|
||||
|
||||
def render(self, request, target_object=None, **kwargs):
|
||||
"""Render the label template to a PDF file.
|
||||
@@ -269,16 +265,13 @@ class LabelTemplate(MetadataMixin, models.Model):
|
||||
wp = WeasyprintLabelMixin(
|
||||
request,
|
||||
self.template_name,
|
||||
base_url=request.build_absolute_uri("/"),
|
||||
base_url=request.build_absolute_uri('/'),
|
||||
presentational_hints=True,
|
||||
filename=self.generate_filename(request),
|
||||
**kwargs
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
return wp.render_to_response(
|
||||
context,
|
||||
**kwargs
|
||||
)
|
||||
return wp.render_to_response(context, **kwargs)
|
||||
|
||||
|
||||
class LabelOutput(models.Model):
|
||||
@@ -293,22 +286,14 @@ class LabelOutput(models.Model):
|
||||
|
||||
# File will be stored in a subdirectory
|
||||
label = models.FileField(
|
||||
upload_to=rename_label_output,
|
||||
unique=True, blank=False, null=False,
|
||||
upload_to=rename_label_output, unique=True, blank=False, null=False
|
||||
)
|
||||
|
||||
# Creation date of label output
|
||||
created = models.DateField(
|
||||
auto_now_add=True,
|
||||
editable=False,
|
||||
)
|
||||
created = models.DateField(auto_now_add=True, editable=False)
|
||||
|
||||
# User who generated the label
|
||||
user = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.SET_NULL,
|
||||
blank=True, null=True,
|
||||
)
|
||||
user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True)
|
||||
|
||||
|
||||
class StockItemLabel(LabelTemplate):
|
||||
@@ -319,15 +304,14 @@ class StockItemLabel(LabelTemplate):
|
||||
"""Return the API URL associated with the StockItemLabel model"""
|
||||
return reverse('api-stockitem-label-list') # pragma: no cover
|
||||
|
||||
SUBDIR = "stockitem"
|
||||
SUBDIR = 'stockitem'
|
||||
|
||||
filters = models.CharField(
|
||||
blank=True, max_length=250,
|
||||
blank=True,
|
||||
max_length=250,
|
||||
help_text=_('Query filters (comma-separated list of key=value pairs)'),
|
||||
verbose_name=_('Filters'),
|
||||
validators=[
|
||||
validate_stock_item_filters
|
||||
]
|
||||
validators=[validate_stock_item_filters],
|
||||
)
|
||||
|
||||
def get_context_data(self, request):
|
||||
@@ -348,7 +332,6 @@ class StockItemLabel(LabelTemplate):
|
||||
'qr_url': request.build_absolute_uri(stock_item.get_absolute_url()),
|
||||
'tests': stock_item.testResultMap(),
|
||||
'parameters': stock_item.part.parameters_map(),
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -360,24 +343,21 @@ class StockLocationLabel(LabelTemplate):
|
||||
"""Return the API URL associated with the StockLocationLabel model"""
|
||||
return reverse('api-stocklocation-label-list') # pragma: no cover
|
||||
|
||||
SUBDIR = "stocklocation"
|
||||
SUBDIR = 'stocklocation'
|
||||
|
||||
filters = models.CharField(
|
||||
blank=True, max_length=250,
|
||||
blank=True,
|
||||
max_length=250,
|
||||
help_text=_('Query filters (comma-separated list of key=value pairs)'),
|
||||
verbose_name=_('Filters'),
|
||||
validators=[
|
||||
validate_stock_location_filters]
|
||||
validators=[validate_stock_location_filters],
|
||||
)
|
||||
|
||||
def get_context_data(self, request):
|
||||
"""Generate context data for each provided StockLocation."""
|
||||
location = self.object_to_print
|
||||
|
||||
return {
|
||||
'location': location,
|
||||
'qr_data': location.format_barcode(brief=True),
|
||||
}
|
||||
return {'location': location, 'qr_data': location.format_barcode(brief=True)}
|
||||
|
||||
|
||||
class PartLabel(LabelTemplate):
|
||||
@@ -391,12 +371,11 @@ class PartLabel(LabelTemplate):
|
||||
SUBDIR = 'part'
|
||||
|
||||
filters = models.CharField(
|
||||
blank=True, max_length=250,
|
||||
blank=True,
|
||||
max_length=250,
|
||||
help_text=_('Query filters (comma-separated list of key=value pairs)'),
|
||||
verbose_name=_('Filters'),
|
||||
validators=[
|
||||
validate_part_filters
|
||||
]
|
||||
validators=[validate_part_filters],
|
||||
)
|
||||
|
||||
def get_context_data(self, request):
|
||||
@@ -427,12 +406,11 @@ class BuildLineLabel(LabelTemplate):
|
||||
SUBDIR = 'buildline'
|
||||
|
||||
filters = models.CharField(
|
||||
blank=True, max_length=250,
|
||||
blank=True,
|
||||
max_length=250,
|
||||
help_text=_('Query filters (comma-separated list of key=value pairs)'),
|
||||
verbose_name=_('Filters'),
|
||||
validators=[
|
||||
validate_build_line_filters
|
||||
]
|
||||
validators=[validate_build_line_filters],
|
||||
)
|
||||
|
||||
def get_context_data(self, request):
|
||||
|
@@ -1,8 +1,10 @@
|
||||
"""API serializers for the label app"""
|
||||
|
||||
import label.models
|
||||
from InvenTree.serializers import (InvenTreeAttachmentSerializerField,
|
||||
InvenTreeModelSerializer)
|
||||
from InvenTree.serializers import (
|
||||
InvenTreeAttachmentSerializerField,
|
||||
InvenTreeModelSerializer,
|
||||
)
|
||||
|
||||
|
||||
class LabelSerializerBase(InvenTreeModelSerializer):
|
||||
@@ -13,14 +15,7 @@ class LabelSerializerBase(InvenTreeModelSerializer):
|
||||
@staticmethod
|
||||
def label_fields():
|
||||
"""Generic serializer fields for a label template"""
|
||||
return [
|
||||
'pk',
|
||||
'name',
|
||||
'description',
|
||||
'label',
|
||||
'filters',
|
||||
'enabled',
|
||||
]
|
||||
return ['pk', 'name', 'description', 'label', 'filters', 'enabled']
|
||||
|
||||
|
||||
class StockItemLabelSerializer(LabelSerializerBase):
|
||||
|
@@ -8,17 +8,9 @@ from InvenTree.unit_test import InvenTreeAPITestCase
|
||||
class TestReportTests(InvenTreeAPITestCase):
|
||||
"""Tests for the StockItem TestReport templates."""
|
||||
|
||||
fixtures = [
|
||||
'category',
|
||||
'part',
|
||||
'location',
|
||||
'stock',
|
||||
]
|
||||
fixtures = ['category', 'part', 'location', 'stock']
|
||||
|
||||
roles = [
|
||||
'stock.view',
|
||||
'stock_location.view',
|
||||
]
|
||||
roles = ['stock.view', 'stock_location.view']
|
||||
|
||||
list_url = reverse('api-stockitem-testreport-list')
|
||||
|
||||
@@ -42,22 +34,10 @@ class TestReportTests(InvenTreeAPITestCase):
|
||||
self.assertEqual(len(response), 0)
|
||||
|
||||
# TODO - Add some tests to this response
|
||||
response = self.do_list(
|
||||
{
|
||||
'item': 10,
|
||||
}
|
||||
)
|
||||
response = self.do_list({'item': 10})
|
||||
|
||||
# TODO - Add some tests to this response
|
||||
response = self.do_list(
|
||||
{
|
||||
'item': 100000,
|
||||
}
|
||||
)
|
||||
response = self.do_list({'item': 100000})
|
||||
|
||||
# TODO - Add some tests to this response
|
||||
response = self.do_list(
|
||||
{
|
||||
'items': [10, 11, 12],
|
||||
}
|
||||
)
|
||||
response = self.do_list({'items': [10, 11, 12]})
|
||||
|
@@ -24,12 +24,7 @@ from .models import PartLabel, StockItemLabel, StockLocationLabel
|
||||
class LabelTest(InvenTreeAPITestCase):
|
||||
"""Unit test class for label models"""
|
||||
|
||||
fixtures = [
|
||||
'category',
|
||||
'part',
|
||||
'location',
|
||||
'stock'
|
||||
]
|
||||
fixtures = ['category', 'part', 'location', 'stock']
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
@@ -49,12 +44,9 @@ class LabelTest(InvenTreeAPITestCase):
|
||||
|
||||
def test_default_files(self):
|
||||
"""Test that label files exist in the MEDIA directory."""
|
||||
|
||||
def test_subdir(ref_name):
|
||||
item_dir = settings.MEDIA_ROOT.joinpath(
|
||||
'label',
|
||||
'inventree',
|
||||
ref_name,
|
||||
)
|
||||
item_dir = settings.MEDIA_ROOT.joinpath('label', 'inventree', ref_name)
|
||||
self.assertTrue(len([item_dir.iterdir()]) > 0)
|
||||
|
||||
test_subdir('stockitem')
|
||||
@@ -63,13 +55,13 @@ class LabelTest(InvenTreeAPITestCase):
|
||||
|
||||
def test_filters(self):
|
||||
"""Test the label filters."""
|
||||
filter_string = "part__pk=10"
|
||||
filter_string = 'part__pk=10'
|
||||
|
||||
filters = validateFilterString(filter_string, model=StockItem)
|
||||
|
||||
self.assertEqual(type(filters), dict)
|
||||
|
||||
bad_filter_string = "part_pk=10"
|
||||
bad_filter_string = 'part_pk=10'
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
validateFilterString(bad_filter_string, model=StockItem)
|
||||
@@ -115,14 +107,11 @@ class LabelTest(InvenTreeAPITestCase):
|
||||
buffer = io.StringIO()
|
||||
buffer.write(label_data)
|
||||
|
||||
template = ContentFile(buffer.getvalue(), "label.html")
|
||||
template = ContentFile(buffer.getvalue(), 'label.html')
|
||||
|
||||
# Construct a label template
|
||||
label = PartLabel.objects.create(
|
||||
name='test',
|
||||
description='Test label',
|
||||
enabled=True,
|
||||
label=template,
|
||||
name='test', description='Test label', enabled=True, label=template
|
||||
)
|
||||
|
||||
# Ensure we are in "debug" mode (so the report is generated as HTML)
|
||||
@@ -151,7 +140,7 @@ class LabelTest(InvenTreeAPITestCase):
|
||||
content = f.read()
|
||||
|
||||
# Test that each element has been rendered correctly
|
||||
self.assertIn(f"part: {part_pk} - {part_name}", content)
|
||||
self.assertIn(f'part: {part_pk} - {part_name}', content)
|
||||
self.assertIn(f'data: {{"part": {part_pk}}}', content)
|
||||
self.assertIn(f'http://testserver/part/{part_pk}/', content)
|
||||
|
||||
|
Reference in New Issue
Block a user