2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-06-15 04:50:49 +00:00

Merge branch 'master' into generic-parameters

This commit is contained in:
Oliver
2025-11-20 22:32:29 +11:00
committed by GitHub
120 changed files with 81562 additions and 73215 deletions
+13 -1
View File
@@ -7,6 +7,7 @@ import os
import random
import shutil
import string
import sys
from pathlib import Path
from typing import Optional
@@ -195,7 +196,13 @@ def load_config_data(set_cache: bool = False) -> map | None:
cfg_file = get_config_file()
with open(cfg_file, encoding='utf-8') as cfg:
data = yaml.safe_load(cfg)
try:
data = yaml.safe_load(cfg)
except yaml.parser.ParserError as error:
logger.error(
"Error reading InvenTree configuration file '%s': %s", cfg_file, error
)
sys.exit(1)
# Set the cache if requested
if set_cache:
@@ -223,6 +230,11 @@ def do_typecast(value, type, var_name=None):
elif type is dict:
value = to_dict(value)
# Special handling for boolean typecasting
elif type is bool:
val = is_true(value)
return val
elif type is not None:
# Try to typecast the value
try:
+60 -4
View File
@@ -2,30 +2,76 @@
import logging
import re
from hashlib import md5
from typing import Optional
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
import pint
_unit_registry = None
import structlog
from common.settings import get_global_setting, set_global_setting
from InvenTree.cache import get_session_cache, set_session_cache
_UNIT_REG_CACHE_KEY = 'unit_registry_hash'
_unit_registry = None
_unit_registry_hash: str = ''
logger = structlog.get_logger('inventree')
# Disable log output for Pint library
logging.getLogger('pint').setLevel(logging.ERROR)
def get_unit_registry_hash():
"""Return a hash representing the current state of the unit registry.
We use this to determine if we need to reload the unit registry,
due to changes in the database.
"""
# Look in the session cache first (faster, and potentially newer)
registry_hash = get_session_cache(_UNIT_REG_CACHE_KEY)
if registry_hash is None:
registry_hash = get_global_setting(
'_UNIT_REGISTRY_HASH', create=False, backup_value=''
)
if registry_hash:
set_session_cache(_UNIT_REG_CACHE_KEY, registry_hash)
return registry_hash
def set_unit_registry_hash(registry_hash: str):
"""Save the hash representing the current state of the unit registry.
Because most of the registry is static, we only need to consider the
CustomUnit entries in the database.
"""
global _unit_registry_hash
_unit_registry_hash = registry_hash
# Save to both the global settings and the session cache
set_global_setting('_UNIT_REGISTRY_HASH', registry_hash)
set_session_cache(_UNIT_REG_CACHE_KEY, registry_hash)
def get_unit_registry():
"""Return a custom instance of the Pint UnitRegistry."""
global _unit_registry
global _unit_registry_hash
# Cache the unit registry for speedier access
if _unit_registry is None:
return reload_unit_registry()
# Check if the unit registry has changed
if _unit_registry_hash != get_unit_registry_hash():
logger.info('Unit registry hash has changed, reloading unit registry')
return reload_unit_registry()
return _unit_registry
@@ -60,9 +106,16 @@ def reload_unit_registry():
try:
from common.models import CustomUnit
# Calculate a hash of all custom units
hash_md5 = md5()
for cu in CustomUnit.objects.all():
try:
reg.define(cu.fmt_string())
fmt = cu.fmt_string()
reg.define(fmt)
hash_md5.update(fmt.encode('utf-8'))
except Exception as e:
logger.exception(
'Failed to load custom unit: %s - %s', cu.fmt_string(), e
@@ -71,6 +124,9 @@ def reload_unit_registry():
# Once custom units are loaded, save registry
_unit_registry = reg
# Update the unit registry hash
set_unit_registry_hash(hash_md5.hexdigest())
except Exception:
# Database is not ready, or CustomUnit model is not available
pass
+8 -4
View File
@@ -16,7 +16,7 @@ from django.conf import settings
from django.contrib.staticfiles.storage import StaticFilesStorage
from django.core.exceptions import FieldError, ValidationError
from django.core.files.storage import default_storage
from django.db.models.fields.files import ImageFieldFile
from django.db.models.fields.files import FieldFile, ImageFieldFile
from django.http import StreamingHttpResponse
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
@@ -176,10 +176,14 @@ def constructPathString(path: list[str], max_chars: int = 250) -> str:
return pathstring
def getMediaUrl(file: StdImageFieldFile | ImageFieldFile, name: str | None = None):
def getMediaUrl(
file: FieldFile | ImageFieldFile | StdImageFieldFile, name: str | None = None
):
"""Return the qualified access path for the given file, under the media directory."""
if not isinstance(file, StdImageFieldFile):
raise ValueError('file_obj must be an instance of StdImageFieldFile')
if not isinstance(file, (FieldFile, ImageFieldFile, StdImageFieldFile)):
raise TypeError(
'file must be one of FileField, ImageFileField, StdImageFieldFile'
)
if name is not None:
file = regenerate_imagefile(file, name)
if settings.STORAGE_TARGET == StorageBackends.S3:
+1 -1
View File
@@ -702,7 +702,7 @@ class TestHelpers(TestCase):
def testMediaUrl(self):
"""Test getMediaUrl."""
# Str should not work
with self.assertRaises(ValueError):
with self.assertRaises(TypeError):
helpers.getMediaUrl('xx/yy.png') # type: ignore
# Correct usage
+17 -15
View File
@@ -39,6 +39,7 @@ from InvenTree.serializers import (
NotesFieldMixin,
enable_filter,
)
from InvenTree.tasks import offload_task
from stock.generators import generate_batch_code
from stock.models import StockItem, StockLocation
from stock.serializers import (
@@ -51,6 +52,7 @@ from users.serializers import OwnerSerializer, UserSerializer
from .models import Build, BuildItem, BuildLine
from .status_codes import BuildStatus
from .tasks import consume_build_item, consume_build_line
class BuildSerializer(
@@ -1845,12 +1847,14 @@ class BuildConsumeSerializer(serializers.Serializer):
return data
@transaction.atomic
def save(self):
"""Perform the stock consumption step."""
data = self.validated_data
request = self.context.get('request')
notes = data.get('notes', '')
# We may be passed either a list of BuildItem or BuildLine instances
items = data.get('items', [])
lines = data.get('lines', [])
@@ -1865,25 +1869,23 @@ class BuildConsumeSerializer(serializers.Serializer):
# Instead, it gets consumed when the output is completed
continue
build_item.complete_allocation(
quantity=quantity,
# Offload a background task to consume this BuildItem
offload_task(
consume_build_item,
build_item.pk,
quantity,
notes=notes,
user=request.user if request else None,
user_id=request.user.pk if request else None,
)
# Process the provided BuildLine objects
for line in lines:
build_line = line['build_line']
# In this case, perform full consumption of all allocated stock
for item in build_line.allocations.all():
# If the build item is tracked into an output, we do not consume now
# Instead, it gets consumed when the output is completed
if item.install_into:
continue
item.complete_allocation(
quantity=item.quantity,
notes=notes,
user=request.user if request else None,
)
# Offload a background task to consume this BuildLine
offload_task(
consume_build_line,
build_line.pk,
notes=notes,
user_id=request.user.pk if request else None,
)
+45
View File
@@ -39,6 +39,51 @@ def auto_allocate_build(build_id: int, **kwargs):
build_order.auto_allocate_stock(**kwargs)
@tracer.start_as_current_span('consume_build_item')
def consume_build_item(
item_id: str, quantity, notes: str = '', user_id: int | None = None
):
"""Consume stock against a particular BuildOrderLineItem allocation."""
from build.models import BuildItem
item = BuildItem.objects.filter(pk=item_id).first()
if not item:
logger.warning(
'Could not consume stock for BuildItem <%s> - BuildItem does not exist',
item_id,
)
return
item.complete_allocation(
quantity=quantity,
notes=notes,
user=User.objects.filter(pk=user_id).first() if user_id else None,
)
@tracer.start_as_current_span('consume_build_line')
def consume_build_line(line_id: int, notes: str = '', user_id: int | None = None):
"""Consume stock against a particular BuildOrderLineItem."""
from build.models import BuildLine
line_item = BuildLine.objects.filter(pk=line_id).first()
if not line_item:
logger.warning(
'Could not consume stock for LineItem <%s> - LineItem does not exist',
line_id,
)
return
for item in line_item.allocations.all():
item.complete_allocation(
quantity=item.quantity,
notes=notes,
user=User.objects.filter(pk=user_id).first() if user_id else None,
)
@tracer.start_as_current_span('complete_build_allocations')
def complete_build_allocations(build_id: int, user_id: int):
"""Complete build allocations for a specified BuildOrder."""
+1 -1
View File
@@ -2039,7 +2039,7 @@ class Attachment(InvenTree.models.MetadataMixin, InvenTree.models.InvenTreeModel
if self.attachment:
import InvenTree.helpers_model
media_url = InvenTree.helpers.getMediaUrl(self.attachment.url)
media_url = InvenTree.helpers.getMediaUrl(self.attachment)
return InvenTree.helpers_model.construct_absolute_url(media_url)
return ''
+15
View File
@@ -202,6 +202,21 @@ class AttachmentTest(InvenTreeAPITestCase):
self.assignRole('part.delete')
self.delete(url, expected_code=204)
def test_fully_qualified_url(self):
"""Test that the fully qualified URL is returned correctly."""
part = Part.objects.first()
attachment = Attachment.objects.create(
attachment=self.generate_file('test.txt'),
comment='Testing filename: test.txt',
model_type='part',
model_id=part.pk,
)
url = attachment.fully_qualified_url()
self.assertIs(type(url), str)
self.assertIn(f'/media/attachments/part/{part.pk}/test', url)
class SettingsTest(InvenTreeTestCase):
"""Tests for the 'settings' model."""
+13 -3
View File
@@ -309,13 +309,23 @@ class StatusCodeMixin:
return status is not None and status == self.get_custom_status()
def set_status(self, status: int) -> bool:
"""Set the status code for this object."""
def set_status(self, status: int, custom_values=None) -> bool:
"""Set the status code for this object.
Arguments:
status: The status code to set
custom_values: Optional list of custom values to consider (can be used to avoid DB queries)
"""
if not self.status_class:
raise NotImplementedError('Status class not defined')
base_values = self.status_class.values()
custom_value_set = self.status_class.custom_values()
custom_value_set = (
self.status_class.custom_values()
if custom_values is None
else custom_values
)
custom_field = f'{self.STATUS_FIELD}_custom_key'
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+43 -18
View File
@@ -976,6 +976,9 @@ class PurchaseOrder(TotalPriceMixin, Order):
# Prefetch line item objects for DB efficiency
line_items_ids = [item['line_item'].pk for item in items]
# Cache the custom status options for the StockItem model
custom_stock_status_values = stock.models.StockItem.STATUS_CLASS.custom_values()
line_items = PurchaseOrderLineItem.objects.filter(
pk__in=line_items_ids
).prefetch_related('part', 'part__part', 'order')
@@ -1050,7 +1053,6 @@ class PurchaseOrder(TotalPriceMixin, Order):
'supplier_part': supplier_part,
'purchase_order': self,
'purchase_price': purchase_price,
'status': item.get('status', StockStatus.OK.value),
'location': stock_location,
'quantity': 1 if serialize else stock_quantity,
'batch': item.get('batch_code', ''),
@@ -1059,6 +1061,9 @@ class PurchaseOrder(TotalPriceMixin, Order):
'packaging': item.get('packaging') or supplier_part.packaging,
}
# Extract the "status" field
status = item.get('status', StockStatus.OK.value)
# Check linked build order
# This is for receiving against an *external* build order
if build_order := line.build_order:
@@ -1099,11 +1104,14 @@ class PurchaseOrder(TotalPriceMixin, Order):
# Now, create the new stock items
if serialize:
stock_items.extend(
stock.models.StockItem._create_serial_numbers(
serials=serials, **stock_data
)
new_items = stock.models.StockItem._create_serial_numbers(
serials=serials, **stock_data
)
for item in new_items:
item.set_status(status, custom_values=custom_stock_status_values)
stock_items.append(item)
else:
new_item = stock.models.StockItem(
**stock_data,
@@ -1115,10 +1123,11 @@ class PurchaseOrder(TotalPriceMixin, Order):
rght=2,
)
new_item.set_status(status, custom_values=custom_stock_status_values)
if barcode:
new_item.assign_barcode(barcode_data=barcode, save=False)
# new_item.save()
bulk_create_items.append(new_item)
# Update the line item quantity
@@ -1379,8 +1388,13 @@ class SalesOrder(TotalPriceMixin, Order):
return any(line.is_overallocated() for line in self.lines.all())
def is_completed(self) -> bool:
"""Check if this order is "shipped" (all line items delivered)."""
return all(line.is_completed() for line in self.lines.all())
"""Check if this order is "shipped" (all line items delivered).
Note: Any "virtual" parts are ignored in this calculation.
"""
lines = self.lines.all().filter(part__virtual=False)
return all(line.is_completed() for line in lines)
def can_complete(
self, raise_error: bool = False, allow_incomplete_lines: bool = False
@@ -1415,10 +1429,15 @@ class SalesOrder(TotalPriceMixin, Order):
_('Order cannot be completed as there are incomplete allocations')
)
if not allow_incomplete_lines and self.pending_line_count > 0:
raise ValidationError(
_('Order cannot be completed as there are incomplete line items')
)
if not allow_incomplete_lines:
pending_lines = self.pending_line_items().exclude(part__virtual=True)
if pending_lines.count() > 0:
raise ValidationError(
_(
'Order cannot be completed as there are incomplete line items'
)
)
except ValidationError as e:
if raise_error:
@@ -1475,6 +1494,7 @@ class SalesOrder(TotalPriceMixin, Order):
trigger_event(SalesOrderEvents.HOLD, id=self.pk)
@transaction.atomic
def _action_complete(self, *args, **kwargs):
"""Mark this order as "complete."""
user = kwargs.pop('user', None)
@@ -1486,6 +1506,16 @@ class SalesOrder(TotalPriceMixin, Order):
get_global_setting('SALESORDER_SHIP_COMPLETE')
)
# Update line items
for line in self.lines.all():
# Mark any "virtual" parts as shipped at this point
if line.part and line.part.virtual and line.shipped != line.quantity:
line.shipped = line.quantity
line.save()
if line.part:
line.part.schedule_pricing_update(create=True)
if bypass_shipped or self.status == SalesOrderStatus.SHIPPED:
self.status = SalesOrderStatus.COMPLETE.value
else:
@@ -1497,11 +1527,6 @@ class SalesOrder(TotalPriceMixin, Order):
self.save()
# Schedule pricing update for any referenced parts
for line in self.lines.all():
if line.part:
line.part.schedule_pricing_update(create=True)
trigger_event(SalesOrderEvents.COMPLETED, id=self.pk)
return True
@@ -1565,7 +1590,7 @@ class SalesOrder(TotalPriceMixin, Order):
"""Attempt to transition to COMPLETED status."""
return self.handle_transition(
self.status,
SalesOrderStatus.COMPLETED.value,
SalesOrderStatus.COMPLETE.value,
self,
self._action_complete,
user=user,
+9 -1
View File
@@ -1226,6 +1226,8 @@ class PurchaseOrderReceiveTest(OrderTest):
def test_receive_large_quantity(self):
"""Test receipt of a large number of items."""
from stock.status_codes import StockStatus
sp = SupplierPart.objects.first()
# Create a new order
@@ -1256,7 +1258,12 @@ class PurchaseOrderReceiveTest(OrderTest):
url,
{
'items': [
{'line_item': line.pk, 'quantity': line.quantity} for line in lines
{
'line_item': line.pk,
'quantity': line.quantity,
'status': StockStatus.QUARANTINED.value,
}
for line in lines
],
'location': location.pk,
},
@@ -1269,6 +1276,7 @@ class PurchaseOrderReceiveTest(OrderTest):
for item in response:
self.assertEqual(item['purchase_order'], po.pk)
self.assertEqual(item['status'], StockStatus.QUARANTINED)
# Check that the order has been completed
po.refresh_from_db()
@@ -488,3 +488,45 @@ class SalesOrderTest(InvenTreeTestCase):
p.set_metadata(k, k)
self.assertEqual(len(p.metadata.keys()), 4)
def test_virtual_parts(self):
"""Test shipment of virtual parts against an order."""
vp = Part.objects.create(
name='Virtual Part',
salable=True,
virtual=True,
description='A virtual part that I sell',
)
so = SalesOrder.objects.create(
customer=self.customer,
reference='SO-VIRTUAL-1',
customer_reference='VIRT-001',
)
for qty in [5, 10, 15]:
SalesOrderLineItem.objects.create(order=so, part=vp, quantity=qty)
# Delete pending shipments (if any)
so.shipments.all().delete()
for line in so.lines.all():
self.assertEqual(line.part.virtual, True)
self.assertEqual(line.shipped, 0)
self.assertGreater(line.quantity, 0)
self.assertTrue(line.is_fully_allocated())
self.assertTrue(line.is_completed())
# Complete the order
so.ship_order(None)
so.refresh_from_db()
self.assertEqual(so.status, status.SalesOrderStatus.SHIPPED)
so.complete_order(None)
so.refresh_from_db()
self.assertEqual(so.status, status.SalesOrderStatus.COMPLETE)
# Ensure that virtual line item quantity values have been updated
for line in so.lines.all():
self.assertEqual(line.shipped, line.quantity)
+1 -1
View File
@@ -827,7 +827,7 @@ class StockItem(
super().save(*args, **kwargs)
# If user information is provided, and no existing note exists, create one!
if user and add_note and self.tracking_info.count() == 0:
if add_note and self.tracking_info.count() == 0:
tracking_info = {'status': self.status}
self.add_tracking_entry(
+10 -3
View File
@@ -1060,12 +1060,19 @@ class StockChangeStatusSerializer(serializers.Serializer):
# Instead of performing database updates for each item,
# perform bulk database updates (much more efficient)
# Pre-cache the custom status values (to reduce DB hits)
custom_status_codes = StockItem.STATUS_CLASS.custom_values()
for item in items:
# Ignore items which are already in the desired status
if item.compare_status(status):
continue
item.set_status(status)
# Careful check for custom status codes also
if item.compare_status(status):
custom_status = item.get_custom_status()
if status == custom_status or custom_status is None:
continue
item.set_status(status, custom_values=custom_status_codes)
item.save(add_note=False)
# Create a new transaction note for each item
+10 -4
View File
@@ -1701,12 +1701,18 @@ class StockItemTest(StockAPITestCase):
prt = Part.objects.first()
# Number of items to create
N_ITEMS = 10
# Create a bunch of items
items = [StockItem.objects.create(part=prt, quantity=10) for _ in range(10)]
items = [
StockItem.objects.create(part=prt, quantity=10) for _ in range(N_ITEMS)
]
for item in items:
item.refresh_from_db()
self.assertEqual(item.status, StockStatus.OK.value)
self.assertEqual(item.tracking_info.count(), 1)
data = {
'items': [item.pk for item in items],
@@ -1719,10 +1725,10 @@ class StockItemTest(StockAPITestCase):
for item in items:
item.refresh_from_db()
self.assertEqual(item.status, StockStatus.DAMAGED.value)
self.assertEqual(item.tracking_info.count(), 1)
self.assertEqual(item.tracking_info.count(), 2)
# Same test, but with one item unchanged
items[0].status = StockStatus.ATTENTION.value
items[0].set_status(StockStatus.ATTENTION.value)
items[0].save()
data['status'] = StockStatus.ATTENTION.value
@@ -1732,7 +1738,7 @@ class StockItemTest(StockAPITestCase):
for item in items:
item.refresh_from_db()
self.assertEqual(item.status, StockStatus.ATTENTION.value)
self.assertEqual(item.tracking_info.count(), 2)
self.assertEqual(item.tracking_info.count(), 3)
tracking = item.tracking_info.last()
self.assertEqual(tracking.tracking_type, StockHistoryCode.EDITED.value)
+96 -96
View File
@@ -230,99 +230,99 @@ click==8.3.0 \
--hash=sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc \
--hash=sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4
# via pip-tools
coverage[toml]==7.11.2 \
--hash=sha256:004bdc5985b86f565772af627925e368256ee2172623db10a0d78a3b53f20ef1 \
--hash=sha256:03e7e7dc31a7deaebf121c3c3bd3c6442b7fbf50aca72aae2a1d08aa30ca2a20 \
--hash=sha256:07e14a4050525fd98bf3d793f229eb8b3ae81678f4031e38e6a18a068bd59fd4 \
--hash=sha256:0f4a958ff286038ac870f836351e9fb8912f1614d1cdbda200fc899235f7dc9b \
--hash=sha256:10f10c9acf584ef82bfaaa7296163bd11c7487237f1670e81fc2fa7e972be67b \
--hash=sha256:17047fb65fcd1ce8a2f97dd2247c2b59cb4bc8848b3911db02dcb05856f91b71 \
--hash=sha256:1ac3f647ecf25d883051ef42d38d823016e715b9f289f8c1768be5117075d1bd \
--hash=sha256:230317450af65a37c1fdbdd3546f7277e0c1c1b65e0d57409248e5dd0fa13493 \
--hash=sha256:2442afabe9e83b881be083238bb7cf5afd4a10e47f29b6094470338d2336b33c \
--hash=sha256:2970c03fefee2a5f1aebc91201a0706a7d0061cc71ab452bb5c5345b7174a349 \
--hash=sha256:2ca963994d28e44285dc104cf94b25d8a7fd0c6f87cf944f46a23f473910703f \
--hash=sha256:30c437e8b51ce081fe3903c9e368e85c9a803b093fd062c49215f3bf4fd1df37 \
--hash=sha256:36c41bf2ee6f6062de8177e249fee17cd5c9662cd373f7a41e6468a34c5b9c0f \
--hash=sha256:38a5509fe7fabb6fb3161059b947641753b6529150ef483fc01c4516a546f2ad \
--hash=sha256:397778cf6d50df59c890bd3ac10acb5bf413388ff6a013305134f1403d5db648 \
--hash=sha256:3aa8c62460499e10ceac5ea61cc09c4f7ddcd8a68c6313cf08785ad353dfd311 \
--hash=sha256:410cafc1aba1f7eb8c09823d5da381be30a2c9b3595758a4c176fcfc04732731 \
--hash=sha256:43ecf9dca4fcb3baf8a886019dd5ce663c95a5e1c5172719c414f0ebd9eeb785 \
--hash=sha256:44b6e04bb94e59927a2807cd4de86386ce34248eaea95d9f1049a72f81828c38 \
--hash=sha256:461577af3f8ad4da244a55af66c0731b68540ce571dbdc02598b5ec9e7a09e73 \
--hash=sha256:4648c90cf741fb61e142826db1557a44079de0ca868c5c5a363c53d852897e84 \
--hash=sha256:4aa799c61869318d2b86c0d3c413d6805546aec42069f009cbb27df2eefb2790 \
--hash=sha256:4aaf2212302b6f748dde596424b0f08bc3e1285192104e2480f43d56b6824f35 \
--hash=sha256:4c4423ea9c28749080b41e18ec74d658e6c9f148a6b47e719f3d7f56197f8227 \
--hash=sha256:4d1ff4b87ad438148976f2215141a490ae000e878536370d53f8da8c59a175a6 \
--hash=sha256:4f8f6bcaa7fe162460abb38f7a5dbfd7f47cfc51e2a0bf0d3ef9e51427298391 \
--hash=sha256:55ae008253df6000bc885a780c1b0e939bd8c932f41e16df1cfe19a00428a98a \
--hash=sha256:595c6bb2b565cc2d930ee634cae47fa959dfd24cc0e8ae4cf2b6e7e131e0d1f7 \
--hash=sha256:5a02818ec44803e325d66bd022828212df934739b894d1699c9a05b9105d30f2 \
--hash=sha256:5b284931d57389ec97a63fb1edf91c68ec369cee44bc40b37b5c3985ba0a2914 \
--hash=sha256:5c31cdbb95ab0f4a60224a04efc43cfb406ce904f0b60fb6b2a72f37718ea5cb \
--hash=sha256:5db683000ff6217273071c752bd6a1d341b6dc5d6aaa56678c53577a4e70e78a \
--hash=sha256:5f72a49504e1f35443b157d97997c9259a017384373eab52fd09b8ade2ae4674 \
--hash=sha256:61d6a7cc1e7a7a761ac59dcc88cee54219fd4231face52bd1257cfd3df29ae9f \
--hash=sha256:632904d126ca97e5d4ecf7e51ae8b20f086b6f002c6075adcfd4ff3a28574527 \
--hash=sha256:6681164bc697b93676945c8c814b76ac72204c395e11b71ba796a93b33331c24 \
--hash=sha256:689d3b4dd0d4c912ed8bfd7a1b5ff2c5ecb1fa16571840573174704ff5437862 \
--hash=sha256:6f70fa1ef17cba5dada94e144ea1b6e117d4f174666842d1da3aaf765d6eb477 \
--hash=sha256:72a3d109ac233666064d60b29ae5801dd28bc51d1990e69f183a2b91b92d4baf \
--hash=sha256:75ef769be19d69ea71b0417d7fbf090032c444792579cdf9b166346a340987d5 \
--hash=sha256:7e01ab8d69b6cffa2463e78a4d760a6b69dfebe5bf21837eabcc273655c7e7b3 \
--hash=sha256:7ea36e981a8a591acdaa920704f8dc798f9fff356c97dbd5d5702046ae967ce1 \
--hash=sha256:7f1aa017b47e1879d7bac50161b00d2b886f2ff3882fa09427119e1b3572ede1 \
--hash=sha256:7f933bc1fead57373922e383d803e1dd5ec7b5a786c220161152ebee1aa3f006 \
--hash=sha256:8014a28a37ffabf7da7107f4f154d68c6b89672f27fef835a0574591c5cd140b \
--hash=sha256:805efa416085999da918f15f81b26636d8e79863e1fbac1495664686d1e6a6e9 \
--hash=sha256:811bff1f93566a8556a9aeb078bd82573e37f4d802a185fba4cbe75468615050 \
--hash=sha256:84e8e0f5ab5134a2d32d4ebadc18b433dbbeddd0b73481f816333b1edd3ff1c8 \
--hash=sha256:87d7c7b0b2279e174f36d276e2afb7bf16c9ea04e824d4fa277eea1854f4cfd4 \
--hash=sha256:89565d7c9340858424a5ca3223bfefe449aeb116942cdc98cd76c07ca50e9db8 \
--hash=sha256:940d195f4c8ba3ec6e7c302c9f546cdbe63e57289ed535452bc52089b1634f1c \
--hash=sha256:94ced4a29a6987af99faaa49a513bf8d0458e8af004c54174e05dd7a8a31c7d9 \
--hash=sha256:9a6468e1a3a40d3d1f9120a9ff221d3eacef4540a6f819fff58868fe0bd44fa9 \
--hash=sha256:9a95b7a6043b221ec1a0d4d5481e424272b37028353265fbe5fcd3768d652eb7 \
--hash=sha256:9f5f6ee021b3b25e748a9a053f3a8dd61a62b6689efd6425cb47e27360994903 \
--hash=sha256:a35701fe0b5ee9d4b67d31aa76555237af32a36b0cf8dd33f8a74470cf7cd2f5 \
--hash=sha256:a913b21f716aa05b149a8656e9e234d9da04bc1f9842136ad25a53172fecc20e \
--hash=sha256:ae43149b7732df15c3ca9879b310c48b71d08cd8a7ba77fda7f9108f78499e93 \
--hash=sha256:b4776c6555a9f378f37fa06408f2e1cc1d06e4c4e06adb3d157a4926b549efbe \
--hash=sha256:b7658f3d4f728092368c091c18efcfb679be9b612c93bfdf345f33635a325188 \
--hash=sha256:b7fc943097fa48de00d14d2a2f3bcebfede024e031d7cd96063fe135f8cbe96e \
--hash=sha256:b9f28b900d96d83e2ae855b68d5cf5a704fa0b5e618999133fd2fb3bbe35ecb1 \
--hash=sha256:bc65e32fe5bb942f0f5247e1500e355cbbdf326181198f5e27e3bb3ddb81e203 \
--hash=sha256:bee1911c44c52cad6b51d436aa8c6ff5ca5d414fa089c7444592df9e7b890be9 \
--hash=sha256:c4b1bea4c707f4c09f682fe0e646a114dfd068f627880d4a208850d01f8164ad \
--hash=sha256:c5769159986eb174f0f66d049a52da03f2d976ac1355679371f1269e83528599 \
--hash=sha256:c65f4291aec39692a3bfbe1d92ae5bea58c16b5553fdf021de61c655d987233f \
--hash=sha256:c7ea5dec77d79dabb7b5fc712c59361aac52e459cd22028480625c3c743323d0 \
--hash=sha256:c85f44ed4260221e46a4e9e8e8df4b359ab6c0a742c79e85d649779bcf77b534 \
--hash=sha256:c8b9a7ebc6a29202fb095877fd8362aab09882894d1c950060c76d61fb116114 \
--hash=sha256:cbffd1d5c5bf4c576ca247bf77646cdad4dced82928337eeb0b85e2b3be4d64b \
--hash=sha256:d0e80c9946da61cc0bf55dfd90d65707acc1aa5bdcb551d4285ea8906255bb33 \
--hash=sha256:d30a717493583c2a83c99f195e934c073be7f4291b32b7352c246d52e43f6893 \
--hash=sha256:d423991415f73a70c0a5f3e0a226cf4ab374dd0da7409978069b844df3d31582 \
--hash=sha256:d73da4893125e0671f762e408dea9957b2bda0036c9589c2fd258a6b870acbdb \
--hash=sha256:d752a8e398a19e2fb24781e4c73089bfeb417b6ac55f96c2c42cfe5bdb21cc18 \
--hash=sha256:e3b92e10ca996b5421232dd6629b9933f97eb57ce374bca800ab56681fbeda2b \
--hash=sha256:e448ceee2fb880427eafc9a3f8e6162b2ac7cc3e9b30b85d6511f25cc8a11820 \
--hash=sha256:e48b95abe2983be98cdf52900e07127eb7fe7067c87a700851f4f1f53d2b00e6 \
--hash=sha256:e52a028a56889d3ad036c0420e866e4a69417d3203e2fc5f03dcb8841274b64c \
--hash=sha256:e7d3fccd5781c5d29ca0bd1ea272630f05cd40a71d419e7e6105c0991400eb14 \
--hash=sha256:e8eb6cbd7d3b238335b5da0f3ce281102435afb503be4d7bdd69eea3c700a952 \
--hash=sha256:ea10a57568af7cf082a7a4d98a699f993652c2ffbdd5a6c9d63c9ca10b693b4d \
--hash=sha256:ea910cc737ee8553c81ad5c104bc5b135106ebb36f88be506c3493e001b4c733 \
--hash=sha256:eaa2a5eeb82fa7a6a9cd65c4f968ee2a53839d451b4e88e060c67d87a0a40732 \
--hash=sha256:ed6ba9f1777fdd1c8e5650c7d123211fa484a187c61af4d82948dc5ee3c0afcc \
--hash=sha256:ef2d3081562cd83f97984a96e02e7a294efa28f58d5e7f4e28920f59fd752b41 \
--hash=sha256:f633da28958f57b846e955d28661b2b323d8ae84668756e1eea64045414dbe34 \
--hash=sha256:f6b2498f86f2554ed6cb8df64201ee95b8c70fb77064a8b2ae8a7185e7a4a5f0 \
--hash=sha256:f6f985e175dfa1fb8c0a01f47186720ae25d5e20c181cc5f3b9eba95589b8148 \
--hash=sha256:f80cb5b328e870bf3df0568b41643a85ee4b8ccd219a096812389e39aa310ea4 \
--hash=sha256:fd3f7cc6cb999e3eff91a2998a70c662b0fcd3c123d875766147c530ca0d3248
coverage[toml]==7.11.3 \
--hash=sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63 \
--hash=sha256:0542ddf6107adbd2592f29da9f59f5d9cff7947b5bb4f734805085c327dcffaa \
--hash=sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd \
--hash=sha256:074e6a5cd38e06671580b4d872c1a67955d4e69639e4b04e87fc03b494c1f060 \
--hash=sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5 \
--hash=sha256:08c0bcf932e47795c49f0406054824b9d45671362dfc4269e0bc6e4bff010704 \
--hash=sha256:097acc18bedf2c6e3144eaf09b5f6034926c3c9bb9e10574ffd0942717232507 \
--hash=sha256:0c986537abca9b064510f3fd104ba33e98d3036608c7f2f5537f869bc10e1ee5 \
--hash=sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5 \
--hash=sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5 \
--hash=sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b \
--hash=sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c \
--hash=sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0 \
--hash=sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2 \
--hash=sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f \
--hash=sha256:149eccc85d48c8f06547534068c41d69a1a35322deaa4d69ba1561e2e9127e75 \
--hash=sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc \
--hash=sha256:200bb89fd2a8a07780eafcdff6463104dec459f3c838d980455cfa84f5e5e6e1 \
--hash=sha256:2376e8a9c889016f25472c452389e98bc6e54a19570b107e27cde9d47f387b64 \
--hash=sha256:28c5251b3ab1d23e66f1130ca0c419747edfbcb4690de19467cd616861507af7 \
--hash=sha256:2ec27a7a991d229213c8070d31e3ecf44d005d96a9edc30c78eaeafaa421c001 \
--hash=sha256:305716afb19133762e8cf62745c46c4853ad6f9eeba54a593e373289e24ea237 \
--hash=sha256:31663572f20bf3406d7ac00d6981c7bbbcec302539d26b5ac596ca499664de31 \
--hash=sha256:3224c7baf34e923ffc78cb45e793925539d640d42c96646db62dbd61bbcfa131 \
--hash=sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe \
--hash=sha256:385977d94fc155f8731c895accdfcc3dd0d9dd9ef90d102969df95d3c637ab80 \
--hash=sha256:39764c6167c82d68a2d8c97c33dba45ec0ad9172570860e12191416f4f8e6e1b \
--hash=sha256:3e33a968672be1394eded257ec10d4acbb9af2ae263ba05a99ff901bb863557e \
--hash=sha256:4234914b8c67238a3c4af2bba648dc716aa029ca44d01f3d51536d44ac16854f \
--hash=sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c \
--hash=sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d \
--hash=sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e \
--hash=sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7 \
--hash=sha256:4ca5f876bf41b24378ee67c41d688155f0e54cdc720de8ef9ad6544005899240 \
--hash=sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1 \
--hash=sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f \
--hash=sha256:4f2bb4ee8dd40f9b2a80bb4adb2aecece9480ba1fa60d9382e8c8e0bd558e2eb \
--hash=sha256:56f909a40d68947ef726ce6a34eb38f0ed241ffbe55c5007c64e616663bcbafc \
--hash=sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f \
--hash=sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e \
--hash=sha256:60ca149a446da255d56c2a7a813b51a80d9497a62250532598d249b3cdb1a926 \
--hash=sha256:68c4eb92997dbaaf839ea13527be463178ac0ddd37a7ac636b8bc11a51af2428 \
--hash=sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2 \
--hash=sha256:6f033dec603eea88204589175782290a038b436105a8f3637a81c4359df27832 \
--hash=sha256:72c8b494bd20ae1c58528b97c4a67d5cfeafcb3845c73542875ecd43924296de \
--hash=sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a \
--hash=sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36 \
--hash=sha256:86d27d2dd7c7c5a44710565933c7dc9cd70e65ef97142e260d16d555667deef7 \
--hash=sha256:876a3ee7fd2613eb79602e4cdb39deb6b28c186e76124c3f29e580099ec21a87 \
--hash=sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5 \
--hash=sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a \
--hash=sha256:8d264402fc179776d43e557e1ca4a7d953020d3ee95f7ec19cc2c9d769277f06 \
--hash=sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3 \
--hash=sha256:8f4cbfff5cf01fa07464439a8510affc9df281535f41a1f5312fbd2b59b4ab5c \
--hash=sha256:900580bc99c145e2561ea91a2d207e639171870d8a18756eb57db944a017d4bb \
--hash=sha256:9061a3e3c92b27fd8036dafa26f25d95695b6aa2e4514ab16a254f297e664f83 \
--hash=sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac \
--hash=sha256:9245bd392572b9f799261c4c9e7216bafc9405537d0f4ce3ad93afe081a12dc9 \
--hash=sha256:9799bd6a910961cb666196b8583ed0ee125fa225c6fdee2cbf00232b861f29d2 \
--hash=sha256:9a1d577c20b4334e5e814c3d5fe07fa4a8c3ae42a601945e8d7940bab811d0bd \
--hash=sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76 \
--hash=sha256:a730cd0824e8083989f304e97b3f884189efb48e2151e07f57e9e138ab104200 \
--hash=sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655 \
--hash=sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71 \
--hash=sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055 \
--hash=sha256:abcea3b5f0dc44e1d01c27090bc32ce6ffb7aa665f884f1890710454113ea902 \
--hash=sha256:ac5d5329c9c942bbe6295f4251b135d860ed9f86acd912d418dce186de7c19ac \
--hash=sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044 \
--hash=sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094 \
--hash=sha256:b5cd111d3ab7390be0c07ad839235d5ad54d2ca497b5f5db86896098a77180a4 \
--hash=sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944 \
--hash=sha256:c0a3b6e32457535df0d41d2d895da46434706dd85dbaf53fbc0d3bd7d914b362 \
--hash=sha256:c381a252317f63ca0179d2c7918e83b99a4ff3101e1b24849b999a00f9cd4f86 \
--hash=sha256:c713c1c528284d636cd37723b0b4c35c11190da6f932794e145fc40f8210a14a \
--hash=sha256:c8be5bfcdc7832011b2652db29ed7672ce9d353dd19bce5272ca33dbcf60aaa8 \
--hash=sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7 \
--hash=sha256:ca90ef33a152205fb6f2f0c1f3e55c50df4ef049bb0940ebba666edd4cdebc55 \
--hash=sha256:d60bf4d7f886989ddf80e121a7f4d140d9eac91f1d2385ce8eb6bda93d563297 \
--hash=sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739 \
--hash=sha256:dd9ca2d44ed8018c90efb72f237a2a140325a4c3339971364d758e78b175f58e \
--hash=sha256:e22539b676fafba17f0a90ac725f029a309eb6e483f364c86dcadee060429d46 \
--hash=sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405 \
--hash=sha256:e5f4bfac975a2138215a38bda599ef00162e4143541cf7dd186da10a7f8e69f1 \
--hash=sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e \
--hash=sha256:eb5069074db19a534de3859c43eec78e962d6d119f637c41c8e028c5ab3f59dd \
--hash=sha256:f0b4101e2b3c6c352ff1f70b3a6fcc7c17c1ab1a91ccb7a33013cb0782af9820 \
--hash=sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e \
--hash=sha256:f9c96a29c6d65bd36a91f5634fef800212dff69dacdb44345c4c9783943ab0df \
--hash=sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428 \
--hash=sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7 \
--hash=sha256:fdc5255eb4815babcdf236fa1a806ccb546724c8a9b129fd1ea4a5448a0bf07c \
--hash=sha256:fe3425dc6021f906c6325d3c415e048e7cdb955505a94f1eb774dafc779ba203
# via -r src/backend/requirements-dev.in
cryptography==46.0.3 \
--hash=sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217 \
@@ -452,9 +452,9 @@ pip==25.3 \
--hash=sha256:8d0538dbbd7babbd207f261ed969c65de439f6bc9e5dbd3b3b9a77f25d95f343 \
--hash=sha256:9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd
# via pip-tools
pip-tools==7.5.1 \
--hash=sha256:a051a94794ba52df9acad2d7c9b0b09ae001617db458a543f8287fea7b89c2cf \
--hash=sha256:f5ff803823529edc0e6e40c86b1aa7da7266fb1078093c8beea4e5b77877036a
pip-tools==7.5.2 \
--hash=sha256:2d64d72da6a044da1110257d333960563d7a4743637e8617dd2610ae7b82d60f \
--hash=sha256:2fe16db727bbe5bf28765aeb581e792e61be51fc275545ef6725374ad720a1ce
# via -r src/backend/requirements-dev.in
platformdirs==4.5.0 \
--hash=sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312 \
+1 -1
View File
@@ -71,4 +71,4 @@ opentelemetry-instrumentation-psycopg
opentelemetry-instrumentation-pymysql
# Pins
xmlsec==1.3.14 # 2025-06-02 pinned to avoid issues with builds - see https://github.com/inventree/InvenTree/pull/9713
xmlsec==1.3.17
+146 -94
View File
@@ -661,38 +661,38 @@ drf-spectacular==0.29.0 \
--hash=sha256:0a069339ea390ce7f14a75e8b5af4a0860a46e833fd4af027411a3e94fc1a0cc \
--hash=sha256:d1ee7c9535d89848affb4427347f7c4a22c5d22530b8842ef133d7b72e19b41a
# via -r src/backend/requirements.in
dulwich==0.24.8 \
--hash=sha256:07aa6e7d41358fcba2a8ac53731e1b8ab201cac7a192ec678ef0da34c7643cf1 \
--hash=sha256:0e9aacbbb0b0cf4b3fecac2c29ddd4d4e36f03ced30851889c193986e8bb327e \
--hash=sha256:108c74b329599931bfe66c4a34fb9312cd8136053cbfc04e7007e7c34081c6b7 \
--hash=sha256:138fd2480c1db43f372d52e4e6ed050c15f92ffbeeab9a0877dceb9801c65d2a \
--hash=sha256:16e335bce0d5192d476db0ca81de1f90fb56863ad7d0b985b0333a8194c73c64 \
--hash=sha256:17d8223cc69cf79ddd7a2f0893e223708f7efc2ea372f919ddcc0d852b3f5d06 \
--hash=sha256:19855e8a0ce299cdcdafdc8bc4f6653bea9e02124a5022e13cda8103fb36912d \
--hash=sha256:19be46710a9d810a66d4a925754abf3818ba22d61134e7d7e1d7b1585c9445b6 \
--hash=sha256:1bac020051cf228b33c787294e17ac80a284e028c3749437ee72577ee04e1cd9 \
--hash=sha256:267e79367cd6f438091248c1e826c6cf7abd84d5b641b9fe46fbc4d9119e11ed \
--hash=sha256:2e3a9a713fda94f3216da4743db3cc8d670330f44c4d98580ac4600242dba2c4 \
--hash=sha256:5593a7216b27412333b99b2e1851bcc2485485d5c4694430aa86d34a36f08d63 \
--hash=sha256:6016e3f7a0f1dd5e19df14b772cb8f42bfde0cd55c504642c05e1e8328de21e3 \
--hash=sha256:661af1fa3852d970fef68b0ab431f0bd488c3f94306e89244c173c4e6abb978e \
--hash=sha256:6a51a41e858e0427b14bb19df7ac1207275dd6b5cc1976f54710bf15cb4c5614 \
--hash=sha256:6ffdd616135bcb31eb2edcccf82d4408720f1db69f596f687ffa2d26c2f5e6f4 \
--hash=sha256:8ac85e3ea42878fa91b6a9282652327df54f5abea4aaf674036e1000608a15f0 \
--hash=sha256:8eca5242f8aed324394c95ecd13a0a66d2a0c31c2556f0a52e7bb8dd67edef20 \
--hash=sha256:97f7c64b02fbd366c36557aa2e8642fc686aaec7d6569bc1460d0b38a63169f9 \
--hash=sha256:a0a780b512d4144336eac2f1f6b982eb78c313442e88ba0db853a224e2b918ef \
--hash=sha256:a6055b12cf2b90a0b4c21d38594f2681667d26bb0c135dfc36c2ea6de7458a37 \
--hash=sha256:a60f8a5d718c7cc1f60bb931cc915311fd5198d85d68dde0ef3c569311c14a70 \
--hash=sha256:b03474c16bcfa3524241b4ae89007931d79ac37c0b19e059133a6e63b7039845 \
--hash=sha256:b31e08bcd0a4eb29915987fa5273a827cccca7ee83eb27ef17bd5899f5668055 \
--hash=sha256:c9f4748bbcca56fb57458c71c0d30e2351ac15e0583d428c739c09228be68f05 \
--hash=sha256:da03c7a6629b7ed37e7139739a175f2c9678080a45444418c54ab28d2ec6524b \
--hash=sha256:ea6c63d3e40fc321ec7c5673b92be036b57aba7802c7e94a18f61451af382de0 \
--hash=sha256:ec0f62538b6fb26cdd1b2fb70788ccfdb17df26a4ba1ca70e623e196c4004f5c \
--hash=sha256:efbf0f29d8d3d56a098e2b4a9260bdfa5f313142180a882c7b28e648d9b5ca9e \
--hash=sha256:f7519f3b8c66ba2e4ea66f47c2156a66cefedce2a121ac3227e030abe95698f3 \
--hash=sha256:f7a0d2cef91cf92a44071daa92639f648ab756d3db63a99e37d3a08ebacf69f3
dulwich==0.24.10 \
--hash=sha256:019af16c850ae85254289f9633a29dea02f45351c4182ea20b0c1394c074a13b \
--hash=sha256:0dfae8c59b97964a907fdf4c5809154a18fd8c55f2eb6d8fd1607464165a9aa2 \
--hash=sha256:0e1601789554e3d15b294356c78a5403521c27d5460e64dbbc44ffd5b10af4c3 \
--hash=sha256:15b32f8c3116a1c0a042dde8da96f65a607e263e860ee42b3d4a98ce2c2f4a06 \
--hash=sha256:1601bfea3906b52c924fae5b6ba32a0b087fb8fae927607e6b5381e6f7559611 \
--hash=sha256:1b19af8a3ab051003ba05f15fc5c0d6f0d427e795639490790f34ec0558e99e3 \
--hash=sha256:1f511f7afe1f36e37193214e4e069685d7d0378e756cc96a2fcb138bdf9fefca \
--hash=sha256:2a56f9838e5d2414a2b57bab370b73b9803fefd98836ef841f0fd489b5cc1349 \
--hash=sha256:30e028979b6fa7220c913da9c786026611c10746c06496149742602b36a11f6b \
--hash=sha256:3581ae0af33f28e6c0834d2f41ca67ca81cd92a589e6a5f985e6c64373232958 \
--hash=sha256:393e9c3cdd382cff20b5beb66989376d6da69e3b0dfec046a884707ab5d27ac9 \
--hash=sha256:44f62e0244531a8c43ca7771e201ec9e7f6a2fb27f8c3c623939bc03c1f50423 \
--hash=sha256:470d6cd8207e1a5ff1fb34c4c6fac2ec9a96d618f7062e5fb96c5260927bb9a7 \
--hash=sha256:4914abb6408a719b7a1f7d9a182d1efd92c326e178b440faf582df50f9f032db \
--hash=sha256:4b5c225477a529e1d4a2b5e51272a418177e34803938391ce41b7573b2e5b0d0 \
--hash=sha256:5c724e5fc67c45f3c813f2630795ac388e3e6310534212f799a7a6bf230648c8 \
--hash=sha256:6a25ca1605a94090514af408f9df64427281aefbb726f542e97d86d3a7c8ec18 \
--hash=sha256:752c32d517dc608dbb8414061eaaec8ac8a05591b29531f81a83336b018b26c6 \
--hash=sha256:843de5f678436a27b33aea0f2b87fd0453afdd0135f885a3ca44bc3147846dd2 \
--hash=sha256:858fae0c7121715282a993abb1919385a28e1a9c4f136f568748d283c2ba874f \
--hash=sha256:8df79c8080471f363e4dfcfc4e0d2e61e6da73af1fd7d31cb6ae0d34af45a6b4 \
--hash=sha256:90028182b9a47ea4efed51c81298f3a98e279d7bf5c1f91c47101927a309ee45 \
--hash=sha256:90b24c0827299cfb53c4f4d4fedc811be5c4b10c11172ff6e5a5c52277fe0b3a \
--hash=sha256:b715a9f85ed71bef8027275c1bded064e4925071ae8c8a8d9a20c67b31faf3cd \
--hash=sha256:c262ffc94338999e7808b434dccafaccd572d03b42d4ef140059d4b7cad765a3 \
--hash=sha256:ce6e05ec50f258ccd14d83114eb32cc5bb241ae4a8c7199d014fd7568de285b1 \
--hash=sha256:d9793fc1e42149a650a017dc8ce38485368a41729b9937e1dfcfedd0591ebe9d \
--hash=sha256:e2eda4a634d6f1ac4c0d4786f8772495c8840dfc2b3e595507376bf5e5b0f9c5 \
--hash=sha256:f102c38207540fa485e85e0b763ce3725a2d49d846dbf316ed271e27fd85ff21 \
--hash=sha256:f7bfa9f0bfae57685754b163eef6641609047460939d28052e3beeb63efa6795 \
--hash=sha256:fbf94fa73211d2f029751a72e1ca3a2fd35c6f5d9bb434acdf10a4a79ca322dd
# via -r src/backend/requirements.in
et-xmlfile==2.0.0 \
--hash=sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa \
@@ -1832,9 +1832,9 @@ s3transfer==0.14.0 \
--hash=sha256:ea3b790c7077558ed1f02a3072fb3cb992bbbd253392f4b6e9e8976941c7d456 \
--hash=sha256:eff12264e7c8b4985074ccce27a3b38a485bb7f7422cc8046fee9be4983e4125
# via boto3
sentry-sdk==2.43.0 \
--hash=sha256:4aacafcf1756ef066d359ae35030881917160ba7f6fc3ae11e0e58b09edc2d5d \
--hash=sha256:52ed6e251c5d2c084224d73efee56b007ef5c2d408a4a071270e82131d336e20
sentry-sdk==2.44.0 \
--hash=sha256:5b1fe54dfafa332e900b07dd8f4dfe35753b64e78e7d9b1655a28fd3065e2493 \
--hash=sha256:9e36a0372b881e8f92fdbff4564764ce6cec4b7f25424d0a3a8d609c9e4651a7
# via
# -r src/backend/requirements.in
# django-q-sentry
@@ -2026,65 +2026,117 @@ xlwt==1.3.0 \
--hash=sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e \
--hash=sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88
# via tablib
xmlsec==1.3.14 \
--hash=sha256:004e8a82e26728bf8a60f8ece1ef3ffafdac30ef538139dfe28870e8503ca64a \
--hash=sha256:03ccba7dacf197850de954666af0221c740a5de631a80136362a1559223fab75 \
--hash=sha256:0bae37b2920115cf00759ee9fb7841cbdebcef3a8a92734ab93ae8fa41ac581d \
--hash=sha256:0be3b7a28e54a03b87faf07fb3c6dc3e50a2c79b686718c3ad08300b8bf6bb67 \
--hash=sha256:1072878301cb9243a54679e0520e6a5be2266c07a28b0ecef9e029d05a90ffcd \
--hash=sha256:12d90059308bb0c1b94bde065784e6852999d08b91bcb2048c17e62b954acb07 \
--hash=sha256:147934bd39dfd840663fb6b920ea9201455fa886427975713f1b42d9f20b9b29 \
--hash=sha256:19c86bab1498e4c2e56d8e2c878f461ccb6e56b67fd7522b0c8fda46d8910781 \
--hash=sha256:1b9b5de6bc69fdec23147e5f712cb05dc86df105462f254f140d743cc680cc7b \
--hash=sha256:1eb3dcf244a52f796377112d8f238dbb522eb87facffb498425dc8582a84a6bf \
--hash=sha256:1fa1311f7489d050dde9028f5a2b5849c2927bb09c9a93491cb2f28fdc563912 \
--hash=sha256:1fe23c2dd5f5dbcb24f40e2c1061e2672a32aabee7cf8ac5337036a485607d72 \
--hash=sha256:204d3c586b8bd6f02a5d4c59850a8157205569d40c32567f49576fa5795d897d \
--hash=sha256:2401e162aaab7d9416c3405bac7a270e5f370988a0f1f46f0f29b735edba87e1 \
--hash=sha256:28cd9f513cf01dc0c5b9d9f0728714ecde2e7f46b3b6f63de91f4ae32f3008b3 \
--hash=sha256:2f84a1c509c52773365645a87949081ee9ea9c535cd452048cc8ca4ad3b45666 \
--hash=sha256:330147ce59fbe56a9be5b2085d739c55a569f112576b3f1b33681f87416eaf33 \
--hash=sha256:34c61ec0c0e70fda710290ae74b9efe1928d9242ed82c4eecf97aa696cff68e6 \
--hash=sha256:38e035bf48300b7dbde2dd01d3b8569f8584fc9c73809be13886e6b6c77b74fb \
--hash=sha256:48e894ad3e7de373f56efc09d6a56f7eae73a8dd4cec8943313134849e9c6607 \
--hash=sha256:4922afa9234d1c5763950b26c328a5320019e55eb6000272a79dfe54fee8e704 \
--hash=sha256:4af81ce8044862ec865782efd353d22abdcd95b92364eef3c934de57ae6d5852 \
--hash=sha256:4dea6df3ffcb65d0b215678c3a0fe7bbc66785d6eae81291296e372498bad43a \
--hash=sha256:4edd8db4df04bbac9c4a5ab4af855b74fe2bf2c248d07cac2e6d92a485f1a685 \
--hash=sha256:4fac2a787ae3b9fb761f9aec6b9f10f2d1c1b87abb574ebd8ff68435bdc97e3d \
--hash=sha256:57fed3bc7943681c9ed4d2221600ab440f060d8d1a8f92f346f2b41effe175b8 \
--hash=sha256:6566434e2e5c58e472362a6187f208601f1627a148683a6f92bd16479f1d9e20 \
--hash=sha256:6679cec780386d848e7351d4b0de92c4483289ea4f0a2187e216159f939a4c6b \
--hash=sha256:73eabf5ef58189d81655058cf328c1dfa9893d89f1bff5fc941481f08533f338 \
--hash=sha256:774d5d1e45f07f953c1cc14fd055c1063f0725f7248b6b0e681f59fd8638934d \
--hash=sha256:77749b338503fb6e151052c664064b34264f4168e2cb0cca1de78b7e5312a783 \
--hash=sha256:7799a9ff3593f9dd43464e18b1a621640bffc40456c47c23383727f937dca7fc \
--hash=sha256:7882963e9cb9c0bd0e8c2715a29159a366417ff4a30d8baf42b05bc5cf249446 \
--hash=sha256:7e8e0171916026cbe8e2022c959558d02086655fd3c3466f2bc0451b09cf9ee8 \
--hash=sha256:82ac81deb7d7bf5cc8a748148948e5df5386597ff43fb92ec651cc5c7addb0e7 \
--hash=sha256:86ff7b2711557c1087b72b0a1a88d82eafbf2a6d38b97309a6f7101d4a7041c3 \
--hash=sha256:934f804f2f895bcdb86f1eaee236b661013560ee69ec108d29cdd6e5f292a2d9 \
--hash=sha256:995e87acecc263a2f6f2aa3cc204268f651cac8f4d7a2047f11b2cd49979cc38 \
--hash=sha256:a487c3d144f791c32f5e560aa27a705fba23171728b8a8511f36de053ff6bc93 \
--hash=sha256:a98eadfcb0c3b23ccceb7a2f245811f8d784bd287640dcfe696a26b9db1e2fc0 \
--hash=sha256:ad1634cabe0915fe2a12e142db0ed2daf5be80cbe3891a2cecbba0750195cc6b \
--hash=sha256:b109cdf717257fd4daa77c1d3ec8a3fb2a81318a6d06a36c55a8a53ae381ae5e \
--hash=sha256:b6dd86f440fec9242515c64f0be93fec8b4289287db1f6de2651eee9995aaecb \
--hash=sha256:b7ba2ea38e3d9efa520b14f3c0b7d99a7c055244ae5ba8bc9f4ca73b18f3a215 \
--hash=sha256:ba3b39c493e3b04354615068a3218f30897fcc2f42c6d8986d0c1d63aca87782 \
--hash=sha256:bd10ca3201f164482775a7ce61bf7ee9aade2e7d032046044dd0f6f52c91d79d \
--hash=sha256:bddd2a2328b4e08c8a112e06cf2cd2b4d281f4ad94df15b4cef18f06cdc49d78 \
--hash=sha256:c12900e1903e289deb84eb893dca88591d6884d3e3cda4fb711b8812118416e8 \
--hash=sha256:c42735cc68fdb4c6065cf0a0701dfff3a12a1734c63a36376349af9a5481f27b \
--hash=sha256:c4d41c83c8a2b8d8030204391ebeb6174fbdb044f0331653c4b5a4ce4150bcc0 \
--hash=sha256:ce4e165a1436697e5e39587c4fba24db4545a5c9801e0d749f1afd09ad3ab901 \
--hash=sha256:cf35a25be3eb6263b2e0544ba26294651113fab79064f994d347a2ca5973e8e2 \
--hash=sha256:d0762f4232bce2c7f6c0af329db8b821b4460bbe123a2528fb5677d03db7a4b5 \
--hash=sha256:dba457ff87c39cbae3c5020475a728d24bbd9d00376df9af9724cd3bb59ff07a \
--hash=sha256:df4aa0782a53032fd35e18dcd6d328d6126324bfcfdef0cb5c2856f25b4b6f94 \
--hash=sha256:e6cbc914d77678db0c8bc39e723d994174633d18f9d6be4665ec29cce978a96d \
--hash=sha256:e732a75fcb6b84872b168f972fbbf3749baf76308635f14015d1d35ed0c5719c \
--hash=sha256:ed4034939d8566ccdcd3b4e4f23c63fd807fb8763ae5668d59a19e11640a8242
xmlsec==1.3.17 \
--hash=sha256:00d43d4f68ac6b11f6e1e69bcb389495f54da77bf1168b4de08f4a7785e47bbb \
--hash=sha256:00d43d4f68ac6b11f6e1e69bcb389495f54da77bf1168b4de08f4a7785e47bbb \
--hash=sha256:040f28a7aacfdb467df46d423e4af05569e9376bc8c7f6416b0761e16a0e3d0b \
--hash=sha256:040f28a7aacfdb467df46d423e4af05569e9376bc8c7f6416b0761e16a0e3d0b \
--hash=sha256:1a0b9a1dcda547e0340eefa6f4a04b87dbd9e40cd514487f347934f94fd559ab \
--hash=sha256:1a0b9a1dcda547e0340eefa6f4a04b87dbd9e40cd514487f347934f94fd559ab \
--hash=sha256:26cc3d81437b51839946d2e93d09371dfd73ed2831dc7e37eff0fb52fc33747c \
--hash=sha256:26cc3d81437b51839946d2e93d09371dfd73ed2831dc7e37eff0fb52fc33747c \
--hash=sha256:2aa5081e1e05dcb6029660ddad795c7daebb3c5771001f60850ab24a16a9cf5e \
--hash=sha256:2aa5081e1e05dcb6029660ddad795c7daebb3c5771001f60850ab24a16a9cf5e \
--hash=sha256:320bf7162e2c442638233da9826af1476049999da1b474b5fe07c60952610131 \
--hash=sha256:320bf7162e2c442638233da9826af1476049999da1b474b5fe07c60952610131 \
--hash=sha256:393eb5cc6b8d6e67c04fd64979ec30d766b3d226550b208803cd410d4b416d91 \
--hash=sha256:393eb5cc6b8d6e67c04fd64979ec30d766b3d226550b208803cd410d4b416d91 \
--hash=sha256:3a53c14d4bc40b0f0fcc6d7908b88f3cbbcf36e25c392f796d88aee7dee5beea \
--hash=sha256:3a53c14d4bc40b0f0fcc6d7908b88f3cbbcf36e25c392f796d88aee7dee5beea \
--hash=sha256:3a6ced8c7744e896cb5a9fd0156d204df3143a62bae11be91cab8e9743d40eec \
--hash=sha256:3a6ced8c7744e896cb5a9fd0156d204df3143a62bae11be91cab8e9743d40eec \
--hash=sha256:3d1fc1fbe2e8585a3f468cf4154d0ec36cd95a15e68429ad8cc8ccd7c04e84ae \
--hash=sha256:3d1fc1fbe2e8585a3f468cf4154d0ec36cd95a15e68429ad8cc8ccd7c04e84ae \
--hash=sha256:40fcb197bdb76301c82b10096c67cc44a6e887317b5b914dbbd166339d36fce4 \
--hash=sha256:40fcb197bdb76301c82b10096c67cc44a6e887317b5b914dbbd166339d36fce4 \
--hash=sha256:46fc9c80e8898ad212b6ad389469bb82d1ab89b90555db42e3e73235b143739d \
--hash=sha256:46fc9c80e8898ad212b6ad389469bb82d1ab89b90555db42e3e73235b143739d \
--hash=sha256:4f2bf6bbf04f8a912483d268b4c2727d400d1806d054624da13bee4b9f6fa28a \
--hash=sha256:4f2bf6bbf04f8a912483d268b4c2727d400d1806d054624da13bee4b9f6fa28a \
--hash=sha256:5319d0bdaf9e597a0ba8dfb3840c4ae57e51f462e7620953f32b07df6267f2ba \
--hash=sha256:5319d0bdaf9e597a0ba8dfb3840c4ae57e51f462e7620953f32b07df6267f2ba \
--hash=sha256:5346616e1fe1015f7800698c15225c7902f45db199e217af2039a21989aff7e9 \
--hash=sha256:5346616e1fe1015f7800698c15225c7902f45db199e217af2039a21989aff7e9 \
--hash=sha256:5616ad5016794b0dd41d03eef5b721e31bb306353226b25fc88fedb7d4f7c37e \
--hash=sha256:5616ad5016794b0dd41d03eef5b721e31bb306353226b25fc88fedb7d4f7c37e \
--hash=sha256:593264c192d1836162d75478c8b1cb5874f3b69dcc5bdfac642a0933abefa93a \
--hash=sha256:593264c192d1836162d75478c8b1cb5874f3b69dcc5bdfac642a0933abefa93a \
--hash=sha256:5c3008b32a15d24b6c9da39bf6ede8dc3122570a640a73795d763aea55a2193e \
--hash=sha256:5c3008b32a15d24b6c9da39bf6ede8dc3122570a640a73795d763aea55a2193e \
--hash=sha256:5c6d4b2ece9d109591d08128a1656b458e24d9eba6c02c32e93573e14eee2447 \
--hash=sha256:5c6d4b2ece9d109591d08128a1656b458e24d9eba6c02c32e93573e14eee2447 \
--hash=sha256:5d0e69291f90b28e9442d8e0e69d3e06cede8a3c44e856413fd284de81ce2888 \
--hash=sha256:5d0e69291f90b28e9442d8e0e69d3e06cede8a3c44e856413fd284de81ce2888 \
--hash=sha256:64c1184d51c8a67e3d1eb3ac477e307a07e2b40fd03cd0c8084b147ea0f342db \
--hash=sha256:64c1184d51c8a67e3d1eb3ac477e307a07e2b40fd03cd0c8084b147ea0f342db \
--hash=sha256:66fe5aaccf68fb85fe0b64277e3f594d6b01ddefb98ef1ceb0a666652d6ec580 \
--hash=sha256:66fe5aaccf68fb85fe0b64277e3f594d6b01ddefb98ef1ceb0a666652d6ec580 \
--hash=sha256:672e41dc7962da4ce84b67aa1c3a008338e3b88332f5484b9911b91cee0997ed \
--hash=sha256:672e41dc7962da4ce84b67aa1c3a008338e3b88332f5484b9911b91cee0997ed \
--hash=sha256:67717fe5151df68987a1387cba11ba28ce19b3bb9a2d10d650277cd910e510e7 \
--hash=sha256:67717fe5151df68987a1387cba11ba28ce19b3bb9a2d10d650277cd910e510e7 \
--hash=sha256:6cbf8a556c74451a7455d506ce3edc81c7cd0f60895ef7792c77dc97434a8e82 \
--hash=sha256:6cbf8a556c74451a7455d506ce3edc81c7cd0f60895ef7792c77dc97434a8e82 \
--hash=sha256:6d1e9ff3398e87e3abb2ab8f1576c0761e5c5c18b086b41f2b39a3eb6b2f7e11 \
--hash=sha256:6d1e9ff3398e87e3abb2ab8f1576c0761e5c5c18b086b41f2b39a3eb6b2f7e11 \
--hash=sha256:728058a1623a620811a3cdf2dd4894b5d9413ede20c8ddddf98fdea5eafe9529 \
--hash=sha256:728058a1623a620811a3cdf2dd4894b5d9413ede20c8ddddf98fdea5eafe9529 \
--hash=sha256:72fc6d336dd68d62822c6536ff4b2453fda94ea652eddb4a958ac97b16ac7001 \
--hash=sha256:72fc6d336dd68d62822c6536ff4b2453fda94ea652eddb4a958ac97b16ac7001 \
--hash=sha256:79b471fdd1d3a92b80907828eaa809f6e34023583488b1b8dc3f951529e7a2f8 \
--hash=sha256:79b471fdd1d3a92b80907828eaa809f6e34023583488b1b8dc3f951529e7a2f8 \
--hash=sha256:7a9f7f7c9ded817f8476b5d6762a831034a8c54783f6175a928f61d57bec78cb \
--hash=sha256:7a9f7f7c9ded817f8476b5d6762a831034a8c54783f6175a928f61d57bec78cb \
--hash=sha256:80fff2251d0e73714435b5860ce200990dffe85466dd91d08d75c4d64ee9967d \
--hash=sha256:80fff2251d0e73714435b5860ce200990dffe85466dd91d08d75c4d64ee9967d \
--hash=sha256:9877303e8c72d7aa2467d1af12e56d67b8fb50d324eda5848e0ec5ee2176aac5 \
--hash=sha256:9877303e8c72d7aa2467d1af12e56d67b8fb50d324eda5848e0ec5ee2176aac5 \
--hash=sha256:99b5b4b6fe232f2234bcec2bdd533b7ab7030b3ce6cfb8bd7153bf02441c8520 \
--hash=sha256:99b5b4b6fe232f2234bcec2bdd533b7ab7030b3ce6cfb8bd7153bf02441c8520 \
--hash=sha256:9bb6faa4ae0268204cfa6b0c0de1c9121eb606eea8c66c7d7ce62e89a17f9efa \
--hash=sha256:9bb6faa4ae0268204cfa6b0c0de1c9121eb606eea8c66c7d7ce62e89a17f9efa \
--hash=sha256:a1ef656421d01851618d0fe5518e57469159c14a48e05125f7bd3225631952f9 \
--hash=sha256:a1ef656421d01851618d0fe5518e57469159c14a48e05125f7bd3225631952f9 \
--hash=sha256:a3961102a6ba8250670814bd1086139fb918e03bf146ef85dc8b6084a9b027d1 \
--hash=sha256:a3961102a6ba8250670814bd1086139fb918e03bf146ef85dc8b6084a9b027d1 \
--hash=sha256:a603584ceee175036e1bccdbe65d551c0fff67343fd506bfa6cec52bc64d9a75 \
--hash=sha256:a603584ceee175036e1bccdbe65d551c0fff67343fd506bfa6cec52bc64d9a75 \
--hash=sha256:abd585642d1fb169dd014e1037dcebe21d12a06e71b6d8f0e37650a81d57d1f1 \
--hash=sha256:abd585642d1fb169dd014e1037dcebe21d12a06e71b6d8f0e37650a81d57d1f1 \
--hash=sha256:ac68a6ac9c0e74f6f3e476f5ac2271b1df7b349a4021b6b2cdfe32cc8fa4beec \
--hash=sha256:ac68a6ac9c0e74f6f3e476f5ac2271b1df7b349a4021b6b2cdfe32cc8fa4beec \
--hash=sha256:ac92a5d0104640e0ff6fd9ef82441005e08fef460fccd790240d3e39abd69ad3 \
--hash=sha256:ac92a5d0104640e0ff6fd9ef82441005e08fef460fccd790240d3e39abd69ad3 \
--hash=sha256:ae88c3aaab5704adfdbce913b3a18db1eb96c49c970657cc01c0d1c420ffdec3 \
--hash=sha256:ae88c3aaab5704adfdbce913b3a18db1eb96c49c970657cc01c0d1c420ffdec3 \
--hash=sha256:b3f306f5aef47336b8299d8dbee31fa0b2eba4579f9f41396070f7a97d0dcd49 \
--hash=sha256:b3f306f5aef47336b8299d8dbee31fa0b2eba4579f9f41396070f7a97d0dcd49 \
--hash=sha256:b4be73fbde421d6188300e02ad92d2d5435c708a35ede8124ebdf6b00330d7cb \
--hash=sha256:b4be73fbde421d6188300e02ad92d2d5435c708a35ede8124ebdf6b00330d7cb \
--hash=sha256:bc8d0a75a43a45349b37186ecce5ae028325ede47cbc217802ff3ef4db3f3cb9 \
--hash=sha256:bc8d0a75a43a45349b37186ecce5ae028325ede47cbc217802ff3ef4db3f3cb9 \
--hash=sha256:c4533780d91f547b841f2522a419da9f2cee2f906dbd4aa58083bc944526a45c \
--hash=sha256:c4533780d91f547b841f2522a419da9f2cee2f906dbd4aa58083bc944526a45c \
--hash=sha256:d360d4adfb53d3adeca398c225cb7e2a73a2246414455937082a1fa19bd8572b \
--hash=sha256:d360d4adfb53d3adeca398c225cb7e2a73a2246414455937082a1fa19bd8572b \
--hash=sha256:d4a7ee007c6b55f7621330aee8330ef2dafa4225fce554064571ca826beafe7e \
--hash=sha256:d4a7ee007c6b55f7621330aee8330ef2dafa4225fce554064571ca826beafe7e \
--hash=sha256:d586bb09f146235f82de624ff05fbca76f8aadc627eb9a072df1899317a1a9eb \
--hash=sha256:d586bb09f146235f82de624ff05fbca76f8aadc627eb9a072df1899317a1a9eb \
--hash=sha256:d862f023f56a49c06576be41dfaf213c9ac77e7a344e7f204278c365bb36d00e \
--hash=sha256:d862f023f56a49c06576be41dfaf213c9ac77e7a344e7f204278c365bb36d00e \
--hash=sha256:df4a8d7fef3ffe90e572400d47392ea480120e339c292f802830ed09d449e622 \
--hash=sha256:df4a8d7fef3ffe90e572400d47392ea480120e339c292f802830ed09d449e622 \
--hash=sha256:e2bf1d07c4f97afeb957f626b8c3ebb8cef300efa0cb95599e936c69a66a1b17 \
--hash=sha256:e2bf1d07c4f97afeb957f626b8c3ebb8cef300efa0cb95599e936c69a66a1b17 \
--hash=sha256:ea2d65749c4c6a35a3ba138debda2f910713a9f4f06b4647510c184c284d7c62 \
--hash=sha256:ea2d65749c4c6a35a3ba138debda2f910713a9f4f06b4647510c184c284d7c62 \
--hash=sha256:ed63cbd87dd69ebcf3a9f82d87b67818c9a7d656325dd4fb34d6c4dfbaa84017 \
--hash=sha256:ed63cbd87dd69ebcf3a9f82d87b67818c9a7d656325dd4fb34d6c4dfbaa84017 \
--hash=sha256:eee89c268a35f8a08a8e9abef6f466b97577e94f5cac8bf32c25e97cd5020097 \
--hash=sha256:eee89c268a35f8a08a8e9abef6f466b97577e94f5cac8bf32c25e97cd5020097 \
--hash=sha256:f3fac9ae679f66585925cc00c5f6839ae36c1d03157619571dee18acc05b9c01 \
--hash=sha256:f3fac9ae679f66585925cc00c5f6839ae36c1d03157619571dee18acc05b9c01
# via
# -r src/backend/requirements.in
# python3-saml
+22
View File
@@ -7,6 +7,28 @@ import { cancelEvent } from './Events';
export const getBaseUrl = (): string =>
(window as any).INVENTREE_SETTINGS?.base_url || 'web';
/**
* Returns the overview URL for a given model type.
* This is the UI URL, not the API URL.
*/
export function getOverviewUrl(model: ModelType, absolute?: boolean): string {
const modelInfo = ModelInformationDict[model];
if (modelInfo?.url_overview) {
const url = modelInfo.url_overview;
const base = getBaseUrl();
if (absolute && base) {
return `/${base}${url}`;
} else {
return url;
}
}
console.error(`No overview URL found for model ${model}`);
return '';
}
/**
* Returns the detail view URL for a given model type.
* This is the UI URL, not the API URL.
+1
View File
@@ -7,6 +7,7 @@
[build.environment]
VITE_DEMO = "true"
PYTHON_VERSION = "3.11"
# Send requests to subpath
+18 -11
View File
@@ -24,11 +24,7 @@ import { type NavigateFunction, useNavigate } from 'react-router-dom';
import { isTrue } from '@lib/functions/Conversion';
import { getDetailUrl } from '@lib/functions/Navigation';
import type {
ApiFormFieldSet,
ApiFormFieldType,
ApiFormProps
} from '@lib/types/Forms';
import type { ApiFormFieldSet, ApiFormProps } from '@lib/types/Forms';
import { useApi } from '../../contexts/ApiContext';
import {
type NestedDict,
@@ -46,9 +42,11 @@ import { ApiFormField } from './fields/ApiFormField';
export function OptionsApiForm({
props: _props,
opened,
id: pId
}: Readonly<{
props: ApiFormProps;
opened?: boolean;
id?: string;
}>) {
const api = useApi();
@@ -75,24 +73,26 @@ export function OptionsApiForm({
);
const optionsQuery = useQuery({
enabled: true,
enabled: opened !== false && props.ignorePermissionCheck !== true,
refetchOnMount: false,
queryKey: [
'form-options-data',
id,
opened,
props.ignorePermissionCheck,
props.method,
props.url,
props.pk,
props.pathParams
],
queryFn: async () => {
const response = await api.options(url);
let fields: Record<string, ApiFormFieldType> | null = {};
if (!props.ignorePermissionCheck) {
fields = extractAvailableFields(response, props.method);
if (props.ignorePermissionCheck === true || opened === false) {
return {};
}
return fields;
return api.options(url).then((response: any) => {
return extractAvailableFields(response, props.method);
});
},
throwOnError: (error: any) => {
if (error.response) {
@@ -110,6 +110,13 @@ export function OptionsApiForm({
}
});
// Refetch form options whenever the modal is opened
useEffect(() => {
if (opened !== false) {
optionsQuery.refetch();
}
}, [opened]);
const formProps: ApiFormProps = useMemo(() => {
const _props = { ...props };
@@ -62,6 +62,13 @@ export function RelatedModelField({
const [isOpen, setIsOpen] = useState<boolean>(false);
const [autoFilled, setAutoFilled] = useState<boolean>(false);
useEffect(() => {
// Reset auto-fill status when the form is reconstructed
setAutoFilled(false);
}, []);
// Auto-fill the field with data from the API
useEffect(() => {
// If there is *no value defined*, and autoFill is enabled, then fetch data from the API
@@ -69,10 +76,17 @@ export function RelatedModelField({
return;
}
// Return if the autofill has already been performed
if (autoFilled) {
return;
}
if (field.value != undefined) {
return;
}
setAutoFilled(true);
// Construct parameters for auto-filling the field
const params = {
...(definition?.filters ?? {}),
@@ -114,6 +128,7 @@ export function RelatedModelField({
}
});
}, [
autoFilled,
definition.autoFill,
definition.api_url,
definition.filters,
@@ -395,7 +410,14 @@ export function RelatedModelField({
menuPortalTarget={document.body}
noOptionsMessage={() => t`No results found`}
menuPosition='fixed'
styles={{ menuPortal: (base: any) => ({ ...base, zIndex: 9999 }) }}
styles={{
menuPortal: (base: any) => ({ ...base, zIndex: 9999 }),
clearIndicator: (base: any) => ({
...base,
color: 'red',
':hover': { color: 'red' }
})
}}
formatOptionLabel={(option: any) => formatOption(option)}
theme={(theme) => {
return {
@@ -1,6 +1,5 @@
import { t } from '@lingui/core/macro';
import { TextInput, Tooltip } from '@mantine/core';
import { useDebouncedValue } from '@mantine/hooks';
import { IconCopyCheck, IconX } from '@tabler/icons-react';
import {
type ReactNode,
@@ -40,30 +39,26 @@ export default function TextField({
const { value } = useMemo(() => field, [field]);
const [rawText, setRawText] = useState<string>(value || '');
const [textValue, setTextValue] = useState<string>(value || '');
const [debouncedText] = useDebouncedValue(rawText, 100);
const onTextChange = useCallback(
(value: any) => {
setTextValue(value);
onChange(value);
},
[onChange]
);
useEffect(() => {
setRawText(value || '');
setTextValue(value || '');
}, [value]);
const onTextChange = useCallback((value: any) => {
setRawText(value);
}, []);
useEffect(() => {
if (debouncedText !== value) {
onChange(debouncedText);
}
}, [debouncedText]);
// Construct a "right section" for the text field
const textFieldRightSection: ReactNode = useMemo(() => {
if (definition.rightSection) {
// Use the specified override value
return definition.rightSection;
} else if (value) {
} else if (textValue) {
if (!definition.required && !definition.disabled) {
// Render a button to clear the text field
return (
@@ -78,7 +73,7 @@ export default function TextField({
);
}
} else if (
!value &&
!textValue &&
definition.placeholder &&
placeholderAutofill &&
!definition.disabled
@@ -94,7 +89,7 @@ export default function TextField({
</Tooltip>
);
}
}, [placeholderAutofill, definition, value]);
}, [placeholderAutofill, definition, textValue]);
return (
<TextInput
@@ -103,19 +98,19 @@ export default function TextField({
id={fieldId}
aria-label={`text-field-${field.name}`}
type={definition.field_type}
value={rawText || ''}
value={textValue || ''}
error={definition.error ?? error?.message}
radius='sm'
onChange={(event) => onTextChange(event.currentTarget.value)}
onBlur={(event) => {
if (event.currentTarget.value != value) {
onChange(event.currentTarget.value);
if (event.currentTarget.value != textValue) {
onTextChange(event.currentTarget.value);
}
}}
onKeyDown={(event) => {
if (event.code === 'Enter') {
// Bypass debounce on enter key
onChange(event.currentTarget.value);
onTextChange(event.currentTarget.value);
}
onKeyDown(event.code);
}}
@@ -16,6 +16,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { apiUrl } from '@lib/functions/Api';
import type { ApiFormFieldType } from '@lib/types/Forms';
import { useDebouncedValue } from '@mantine/hooks';
import { useApi } from '../../contexts/ApiContext';
import type { ImportSessionState } from '../../hooks/UseImportSession';
import { StandaloneField } from '../forms/StandaloneField';
@@ -83,6 +84,14 @@ function ImporterDefaultField({
}) {
const api = useApi();
const [rawValue, setRawValue] = useState<any>('');
const fieldType: string = useMemo(() => {
return session.availableFields[fieldName]?.type;
}, [fieldName, session.availableFields]);
const [value] = useDebouncedValue(rawValue, fieldType == 'string' ? 500 : 10);
const onChange = useCallback(
(value: any) => {
// Update the default value for the field
@@ -105,6 +114,11 @@ function ImporterDefaultField({
[fieldName, session, session.fieldDefaults]
);
// Update the default value after the debounced value changes
useEffect(() => {
onChange(value);
}, [value]);
const fieldDef: ApiFormFieldType = useMemo(() => {
let def: any = session.availableFields[fieldName];
@@ -114,7 +128,10 @@ function ImporterDefaultField({
value: session.fieldDefaults[fieldName],
field_type: def.type,
description: def.help_text,
onValueChange: onChange
required: false,
onValueChange: (value: string) => {
setRawValue(value);
}
};
}
@@ -215,7 +215,7 @@ export function RenderInlineModel({
return (
<Group gap='xs' justify='space-between' title={tooltip}>
<Group gap='xs' justify='left' wrap='nowrap'>
<Group gap='xs' justify='left'>
{prefix}
{image && <Thumbnail src={image} size={18} />}
{url ? (
+3 -3
View File
@@ -517,7 +517,7 @@ function BuildAllocateLineRow({
field_type: 'related field',
api_url: apiUrl(ApiEndpoints.stock_item_list),
model: ModelType.stockitem,
autoFill: !!output?.serial,
autoFill: !output || !!output?.serial,
autoFillFilters: {
serial: output?.serial
},
@@ -814,7 +814,7 @@ export function useConsumeBuildItemsForm({
url: ApiEndpoints.build_order_consume,
pk: buildId,
title: t`Consume Stock`,
successMessage: t`Stock items consumed`,
successMessage: t`Stock items scheduled to be consumed`,
onFormSuccess: onFormSuccess,
size: '80%',
fields: consumeFields,
@@ -915,7 +915,7 @@ export function useConsumeBuildLinesForm({
url: ApiEndpoints.build_order_consume,
pk: buildId,
title: t`Consume Stock`,
successMessage: t`Stock items consumed`,
successMessage: t`Stock items scheduled to be consumed`,
onFormSuccess: onFormSuccess,
fields: consumeFields,
initialData: {
+46 -29
View File
@@ -20,6 +20,9 @@ export function usePartFields({
}): ApiFormFieldSet {
const settings = useGlobalSettingsState();
const [virtual, setVirtual] = useState<boolean>(false);
const [purchaseable, setPurchaseable] = useState<boolean>(false);
return useMemo(() => {
const fields: ApiFormFieldSet = {
category: {
@@ -62,9 +65,19 @@ export function usePartFields({
is_template: {},
testable: {},
trackable: {},
purchaseable: {},
purchaseable: {
value: purchaseable,
onValueChange: (value: boolean) => {
setPurchaseable(value);
}
},
salable: {},
virtual: {},
virtual: {
value: virtual,
onValueChange: (value: boolean) => {
setVirtual(value);
}
},
locked: {},
active: {},
starred: {
@@ -80,33 +93,37 @@ export function usePartFields({
if (create) {
fields.copy_category_parameters = {};
fields.initial_stock = {
icon: <IconPackages />,
children: {
quantity: {
value: 0
},
location: {}
}
};
if (!virtual) {
fields.initial_stock = {
icon: <IconPackages />,
children: {
quantity: {
value: 0
},
location: {}
}
};
}
fields.initial_supplier = {
icon: <IconBuildingStore />,
children: {
supplier: {
filters: {
is_supplier: true
}
},
sku: {},
manufacturer: {
filters: {
is_manufacturer: true
}
},
mpn: {}
}
};
if (purchaseable) {
fields.initial_supplier = {
icon: <IconBuildingStore />,
children: {
supplier: {
filters: {
is_supplier: true
}
},
sku: {},
manufacturer: {
filters: {
is_manufacturer: true
}
},
mpn: {}
}
};
}
}
// Additional fields for part duplication
@@ -159,7 +176,7 @@ export function usePartFields({
}
return fields;
}, [create, duplicatePartInstance, settings]);
}, [virtual, purchaseable, create, duplicatePartInstance, settings]);
}
/**
+5 -1
View File
@@ -125,8 +125,11 @@ export function useSalesOrderLineItemFields({
)
.sort((a: any, b: any) => a.quantity - b.quantity);
if (applicablePriceBreaks.length)
if (applicablePriceBreaks.length) {
setSalePrice(applicablePriceBreaks[0].price);
} else {
setSalePrice('');
}
}, [part, quantity, partCurrency, create]);
return useMemo(() => {
@@ -184,6 +187,7 @@ function SalesOrderAllocateLineRow({
field_type: 'related field',
api_url: apiUrl(ApiEndpoints.stock_item_list),
model: ModelType.stockitem,
autoFill: true,
filters: {
available: true,
part_detail: true,
+6 -2
View File
@@ -1,7 +1,7 @@
import { t } from '@lingui/core/macro';
import { Alert, Divider, Stack } from '@mantine/core';
import { useId } from '@mantine/hooks';
import { useEffect, useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import type {
ApiFormModalProps,
@@ -50,14 +50,18 @@ export function useApiFormModal(props: ApiFormModalProps) {
[props]
);
const [isOpen, setIsOpen] = useState<boolean>(false);
const modal = useModal({
id: modalId,
title: formProps.title,
onOpen: () => {
setIsOpen(true);
modalState.setModalOpen(modalId, true);
formProps.onOpen?.();
},
onClose: () => {
setIsOpen(false);
modalState.setModalOpen(modalId, false);
formProps.onClose?.();
},
@@ -66,7 +70,7 @@ export function useApiFormModal(props: ApiFormModalProps) {
children: (
<Stack gap={'xs'}>
<Divider />
<OptionsApiForm props={formProps} id={modalId} />
<OptionsApiForm props={formProps} id={modalId} opened={isOpen} />
</Stack>
)
});
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More