Merge branch 'master' into matmair/issue11460

This commit is contained in:
Oliver 2026-06-01 21:56:59 +10:00 committed by GitHub
commit f20d3456f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
149 changed files with 51014 additions and 44391 deletions

View File

@ -165,7 +165,7 @@ jobs:
pip install --require-hashes -r docs/requirements.txt
python docs/ci/check_mkdocs_config.py
- name: Check Links
uses: gaurav-nelson/github-action-markdown-link-check@5c5dfc0ac2e225883c0e5f03a85311ec2830d368 # pin@v1
uses: tcort/github-action-markdown-link-check@e7c7a18363c842693fadde5d41a3bd3573a7a225 # pin@v1
with:
folder-path: docs
config-file: docs/mlc_config.json
@ -713,9 +713,24 @@ jobs:
invoke dev.setup-test -iv
invoke int.rebuild-thumbnails
- name: Install dependencies
run: invoke int.frontend-compile --extract
- name: Cache Playwright browsers
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # pin@v4.3.0
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('src/frontend/yarn.lock') }}
- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: cd src/frontend && npx playwright install --with-deps
- name: Install Playwright OS dependencies
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: cd src/frontend && npx playwright install-deps
- name: Install Sample Plugin
run: |
invoke int.frontend-compile --extract
cd src/frontend && npx playwright install --with-deps
pip install -U inventree-plugin-creator
create-inventree-plugin --default
cd MyCustomPlugin && pip install -e . && cd frontend && npm install && npm run translate && npm run build
- name: Run Playwright tests
id: tests
run: |

View File

@ -227,7 +227,7 @@ jobs:
INVENTREE_PLUGIN_FILE=/opt/inventree/plugins.txt
INVENTREE_CONFIG_FILE=/opt/inventree/config.yaml
APP_REPO=inventree/InvenTree
- name: Publish to go.packager.io
- name: Publish to go.packager.io - current release channel
uses: pkgr/action/publish@3bce081ae512c5020856e237d37b3f5479d4aa71 # pin@main
with:
target: ${{ matrix.target }}
@ -235,10 +235,18 @@ jobs:
repository: inventree/InvenTree
channel: ${{ env.pkg_channel }}
file: ${{ steps.package.outputs.package_path }}
- name: Publish to go.packager.io - stable release channel
uses: pkgr/action/publish@3bce081ae512c5020856e237d37b3f5479d4aa71 # pin@main
with:
target: ${{ matrix.target }}
token: ${{ secrets.PACKAGER_RELEASE_TOKEN }}
repository: inventree/InvenTree
channel: stable
file: ${{ steps.package.outputs.package_path }}
- name: Publish to artifact
run: gh release upload ${REF} ${PACKAGE_PATH}#${PACKAGE_NAME}
env:
REF: ${{ github.ref_name }}
PACKAGE_PATH: ${{ steps.package.outputs.package_path }}
PACKAGE_NAME: ${{ matrix.target }}-{{ steps.setup.outputs.version }}.tar.gz
PACKAGE_NAME: ${{ matrix.target }}-${{ steps.setup.outputs.version }}.tar.gz
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -19,7 +19,7 @@ threads = 4
# Worker timeout (default = 90 seconds)
timeout = os.environ.get('INVENTREE_GUNICORN_TIMEOUT', '90')
timeout = int(os.environ.get('INVENTREE_GUNICORN_TIMEOUT', '90'))
# Number of worker processes
workers = os.environ.get('INVENTREE_GUNICORN_WORKERS', None)
@ -43,7 +43,13 @@ preload_app = True
def post_fork(server, worker):
"""Post-fork hook to set up logging for each worker."""
"""Post-fork hook called after each worker process is forked."""
from django.db import connections
# Close any DB connections inherited from the master process — PostgreSQL
# connections are not fork-safe, so each worker must open its own.
connections.close_all()
from django.conf import settings
if not settings.TRACING_ENABLED:

View File

@ -1,8 +1,8 @@
# This file was autogenerated by uv via the following command:
# uv pip compile contrib/dev_reqs/requirements.in -o contrib/dev_reqs/requirements.txt -c src/backend/requirements.txt
certifi==2026.4.22 \
--hash=sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a \
--hash=sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580
certifi==2026.5.20 \
--hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \
--hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d
# via
# -c src/backend/requirements.txt
# requests
@ -139,9 +139,9 @@ charset-normalizer==3.4.7 \
# via
# -c src/backend/requirements.txt
# requests
idna==3.15 \
--hash=sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8 \
--hash=sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc
idna==3.16 \
--hash=sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5 \
--hash=sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d
# via
# -c src/backend/requirements.txt
# requests

View File

@ -67,6 +67,8 @@ Transfer the currently selected stock location into another location. Scanning a
Receive incoming purchase order items into the selected location. Scanning a *new* barcode which is associated with an item in an incoming purchase order will receive the item into the selected location.
*Note: Both purchase order number and supplier SKU's are required to be found on the barcode for this function to find the associated line item. Missing one will lead to an error.*
#### Scan Items Into Location
the *Scan Items Into Location* action allows you to scan items into the selected location. Scanning a valid barcode associated with a stock item (already in the database) will result in that item being transferred to the selected location.
@ -105,6 +107,8 @@ From the [Purchase Order detail page](./po.md#purchase-order-detail) page, the f
Receive incoming purchase order items against the selected purchase order. Scanning a *new* barcode which is associated with an item in an incoming purchase order will receive the item into stock.
*Note: supplier SKU's are required to be found on the barcode for this function to find the associated line item.*
### Sales Order Actions
The following barcode actions are available for [Sales Orders](./so.md):

View File

@ -29,6 +29,7 @@ The *Display Settings* screen shows general display configuration options:
{{ usersetting("SHOW_FULL_CATEGORY_IN_TABLES")}}
{{ usersetting("SHOW_BOM_SUBASSEMBLY_LEVELS")}}
{{ usersetting("ENABLE_LAST_BREADCRUMB") }}
{{ usersetting("SHOW_EXTRA_MODEL_INFO") }}
{{ usersetting("SHOW_FULL_LOCATION_IN_TABLES") }}
{{ usersetting("DISPLAY_ITEMS_FINAL_LEVEL") }}

View File

@ -256,6 +256,9 @@ The following database options can be configured:
{{ configsetting("INVENTREE_DB_PASSWORD") }} Database password (if required) |
{{ configsetting("INVENTREE_DB_HOST") }} Database host address (if required) |
{{ configsetting("INVENTREE_DB_PORT") }} Database host port (if required) |
{{ configsetting("INVENTREE_DB_CONN_MAX_AGE") }} Database connection max age (s) |
{{ configsetting("INVENTREE_DB_CONN_HEALTH_CHECKS") }} Enable database connection health checks |
{{ configsetting("INVENTREE_DB_OPTIONS") }} Additional database options (as a JSON object) |
!!! tip "Database Password"
@ -266,11 +269,12 @@ The following database options can be configured:
If running with a PostgreSQL database backend, the following additional options are available:
{{ configtable() }}
{{ configsetting("INVENTREE_DB_TIMEOUT", default="2") }} Database connection timeout (s) |
{{ configsetting("INVENTREE_DB_TIMEOUT", default="10") }} Database connection timeout (s) |
| `INVENTREE_DB_TCP_KEEPALIVES` | database.tcp_keepalives | 1 | TCP keepalive |
| `INVENTREE_DB_TCP_KEEPALIVES_IDLE` | database.tcp_keepalives_idle | 1 | Idle TCP keepalive |
| `INVENTREE_DB_TCP_KEEPALIVES_INTERVAL` | database.tcp_keepalives_interval | 1| TCP keepalive interval |
| `INVENTREE_DB_TCP_KEEPALIVES_IDLE` | database.tcp_keepalives_idle | 5 | Idle TCP keepalive |
| `INVENTREE_DB_TCP_KEEPALIVES_INTERVAL` | database.tcp_keepalives_interval | 5 | TCP keepalive interval |
| `INVENTREE_DB_TCP_KEEPALIVES_COUNT` | database.tcp_keepalives_count | 5 | TCP keepalive count |
| `INVENTREE_DB_TCP_USER_TIMEOUT` | database.tcp_user_timeout | 2000 | TCP user timeout (ms) |
| `INVENTREE_DB_ISOLATION_SERIALIZABLE` | database.serializable | False | Database isolation level configured to "serializable" |
### MySQL Settings
@ -278,6 +282,7 @@ If running with a PostgreSQL database backend, the following additional options
If running with a MySQL database backend, the following additional options are available:
{{ configtable() }}
{{ configsetting("INVENTREE_DB_TIMEOUT", default="10") }} Database connection timeout (s) |
| `INVENTREE_DB_ISOLATION_SERIALIZABLE` | database.serializable | False | Database isolation level configured to "serializable" |
### SQLite Settings

View File

@ -28,9 +28,9 @@ bracex==2.6 \
--hash=sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952 \
--hash=sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7
# via wcmatch
certifi==2026.4.22 \
--hash=sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a \
--hash=sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580
certifi==2026.5.20 \
--hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \
--hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d
# via
# -c src/backend/requirements.txt
# httpcore
@ -169,9 +169,9 @@ charset-normalizer==3.4.7 \
# via
# -c src/backend/requirements.txt
# requests
click==8.3.3 \
--hash=sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2 \
--hash=sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613
click==8.4.1 \
--hash=sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2 \
--hash=sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96
# via
# mkdocs
# neoteroi-mkdocs
@ -227,9 +227,9 @@ httpx==0.28.1 \
--hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \
--hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad
# via neoteroi-mkdocs
idna==3.15 \
--hash=sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8 \
--hash=sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc
idna==3.16 \
--hash=sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5 \
--hash=sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d
# via
# -c src/backend/requirements.txt
# anyio

View File

@ -2,7 +2,12 @@
from django.contrib import admin
from django.http.request import HttpRequest
from django.utils import timezone
from allauth.usersessions.admin import UserSessionAdmin
from allauth.usersessions.models import UserSession
from django_q.admin import ScheduleAdmin
from django_q.models import Schedule
from djmoney.contrib.exchange.admin import RateAdmin
from djmoney.contrib.exchange.models import Rate
@ -17,3 +22,51 @@ class CustomRateAdmin(RateAdmin):
admin.site.unregister(Rate)
admin.site.register(Rate, CustomRateAdmin)
def run_schedule_now(modeladmin, request, queryset):
"""Immediately queue the selected scheduled tasks for execution."""
queryset.update(next_run=timezone.now())
count = queryset.count()
modeladmin.message_user(request, f'{count} task(s) queued for immediate execution.')
run_schedule_now.short_description = 'Run selected tasks now'
class ReadOnlyScheduleAdmin(ScheduleAdmin):
"""Read-only admin interface for django-q Schedule objects."""
actions = [run_schedule_now]
def has_add_permission(self, request: HttpRequest) -> bool:
"""Prevent adding new Schedule objects."""
return False
def has_change_permission(self, request: HttpRequest, obj=None) -> bool:
"""Prevent changing existing Schedule objects."""
return False
def has_delete_permission(self, request: HttpRequest, obj=None) -> bool:
"""Prevent deleting Schedule objects."""
return False
admin.site.unregister(Schedule)
admin.site.register(Schedule, ReadOnlyScheduleAdmin)
class InvenTreeUserSessionAdmin(UserSessionAdmin):
"""Admin interface for UserSession - view and delete only, no add or edit."""
def has_add_permission(self, request: HttpRequest) -> bool:
"""Prevent creating sessions via admin."""
return False
def has_change_permission(self, request: HttpRequest, obj=None) -> bool:
"""Prevent editing sessions via admin."""
return False
admin.site.unregister(UserSession)
admin.site.register(UserSession, InvenTreeUserSessionAdmin)

View File

@ -1,11 +1,17 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 497
INVENTREE_API_VERSION = 499
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v499 -> 2026-06-01 : https://github.com/inventree/InvenTree/pull/12057
- Fixes search field issues on the BarcodeScanHistory API endpoint
v498 -> 2026-05-31 : https://github.com/inventree/InvenTree/pull/12055
- Updates the "status_text" field for models which support custom status values
v497 -> 2026-05-27 : https://github.com/inventree/InvenTree/pull/12019
- Adds "location" field to StockCount API endpoint

View File

@ -64,9 +64,20 @@ class InvenTreeConfig(AppConfig):
self.collect_tasks()
self.start_background_tasks()
if not InvenTree.ready.isInTestMode(): # pragma: no cover
if (
not InvenTree.ready.isInTestMode()
and not InvenTree.ready.isInWorkerThread()
): # pragma: no cover
# Let the background worker check for migrations
InvenTree.tasks.offload_task(InvenTree.tasks.check_for_migrations)
# Don't offload task if we are already *in* the background worker, otherwise we might end up in a deadlock situation
try:
InvenTree.tasks.offload_task(InvenTree.tasks.check_for_migrations)
except Exception as exc:
logger.exception(
'Failed to offload check_for_migrations task: %s', exc
)
# Update exchange rates
InvenTree.tasks.offload_task(
InvenTree.tasks.update_exchange_rates, force_async=True

View File

@ -764,6 +764,56 @@ class InvenTreeDecimalField(serializers.FloatField):
raise serializers.ValidationError(_('Invalid value'))
class CustomStatusSerializerMixin(serializers.Serializer):
"""Serializer mixin for models that support custom status values.
Provides a `status_text` SerializerMethodField that resolves custom
status labels with a single database query per model per serializer
context (i.e. one query for a whole list page) rather than one query per
object (N+1).
"""
status_text = serializers.SerializerMethodField()
@extend_schema_field(serializers.CharField(allow_null=True))
def get_status_text(self, instance) -> Optional[str]:
"""Return the human-readable status text for the instance.
Uses a per-context cache keyed by model name so that all objects in a
single serialization pass share one DB hit for custom label lookup.
During write operations DRF may call to_representation on the raw
validated_data dict rather than a model instance (e.g. when building
response headers). Return None in that case the response body is
always produced from a real instance via a separate serializer call.
"""
if not hasattr(instance, 'get_custom_status'):
return None
custom_key = instance.get_custom_status()
if custom_key is None:
return instance.status_class.label(instance.get_status())
model_name = instance._meta.model_name
cache_key = f'_custom_status_labels_{model_name}'
# Cache a dict of custom status labels for this model, if not already cached
if cache_key not in self.context:
from common.models import InvenTreeCustomUserStateModel
self.context[cache_key] = {
obj.key: obj.label
for obj in InvenTreeCustomUserStateModel.objects.filter(
model__model=model_name
)
}
return self.context[cache_key].get(
custom_key, instance.status_class.label(instance.get_status())
)
class NotesFieldMixin:
"""Serializer mixin for handling 'notes' fields.

View File

@ -5,10 +5,23 @@ from pathlib import Path
import structlog
from InvenTree.config import get_boolean_setting, get_config_value, get_setting
from InvenTree.ready import isInWorkerThread
logger = structlog.get_logger('inventree')
def _get_conn_max_age() -> int | None:
"""Return the configured CONN_MAX_AGE value.
Accepts an integer number of seconds, or 'None' for unlimited persistence.
Defaults to 0 (close the connection after each request).
"""
value = get_setting('INVENTREE_DB_CONN_MAX_AGE', 'database.conn_max_age', 0)
if value is None or str(value).strip().lower() == 'none':
return None
return int(value)
def get_db_backend():
"""Return the database backend configuration."""
# For the core database configuration values, we test for UPPERCASE configuration values as a backup,
@ -43,6 +56,13 @@ def get_db_backend():
)
or get_config_value('database.OPTIONS')
or {},
# Seconds to keep idle connections open across requests (0 = close after each request).
# Set to None for unlimited. Enable CONN_HEALTH_CHECKS alongside any non-zero value
# so stale connections are detected before use rather than causing request failures.
'CONN_MAX_AGE': _get_conn_max_age(),
'CONN_HEALTH_CHECKS': get_boolean_setting(
'INVENTREE_DB_CONN_HEALTH_CHECKS', 'database.conn_health_checks', False
),
}
# Check for required keys
@ -113,9 +133,9 @@ def set_postgres_options(db_options: dict):
if 'connect_timeout' not in db_options:
# The DB server is in the same data center, it should not take very
# long to connect to the database server
# # seconds, 2 is minimum allowed by libpq
db_options['connect_timeout'] = int(
get_setting('INVENTREE_DB_TIMEOUT', 'database.timeout', 2)
# Note: 2 seconds is minimum allowed by libpq
db_options['connect_timeout'] = max(
2, int(get_setting('INVENTREE_DB_TIMEOUT', 'database.timeout', 10))
)
# Setup TCP keepalive
@ -133,7 +153,7 @@ def set_postgres_options(db_options: dict):
if 'keepalives_idle' not in db_options:
db_options['keepalives_idle'] = int(
get_setting(
'INVENTREE_DB_TCP_KEEPALIVES_IDLE', 'database.tcp_keepalives_idle', 1
'INVENTREE_DB_TCP_KEEPALIVES_IDLE', 'database.tcp_keepalives_idle', 5
)
)
@ -143,7 +163,7 @@ def set_postgres_options(db_options: dict):
get_setting(
'INVENTREE_DB_TCP_KEEPALIVES_INTERVAL',
'database.tcp_keepalives_interval',
'1',
'5',
)
)
@ -157,10 +177,13 @@ def set_postgres_options(db_options: dict):
)
)
# # Milliseconds for how long pending data should remain unacked
# by the remote server
# TODO: Supported starting in PSQL 11
# "tcp_user_timeout": int(os.getenv("PGTCP_USER_TIMEOUT", "1000"),
# # Milliseconds for how long pending data should remain unacked by the remote server
if 'tcp_user_timeout' not in db_options:
db_options['tcp_user_timeout'] = int(
get_setting(
'INVENTREE_DB_TCP_USER_TIMEOUT', 'database.tcp_user_timeout', '2000'
)
)
# Postgres's default isolation level is Read Committed which is
# normally fine, but most developers think the database server is
@ -178,10 +201,21 @@ def set_postgres_options(db_options: dict):
else IsolationLevel.READ_COMMITTED
)
# Specify the application name for the database connection
# This can be useful for debugging and monitoring purposes
if 'application_name' not in db_options:
db_options['application_name'] = (
'inventree-worker' if isInWorkerThread() else 'inventree-server'
)
def set_mysql_options(db_options: dict):
"""Set database options specific to mysql backend."""
# TODO TCP time outs and keepalives
# Timeout values
if 'connect_timeout' not in db_options:
db_options['connect_timeout'] = int(
get_setting('INVENTREE_DB_TIMEOUT', 'database.timeout', 10)
)
# MariaDB's default isolation level is Repeatable Read which is
# normally fine, but most developers think the database server is

View File

@ -56,7 +56,11 @@ INVENTREE_BASE_URL = 'https://inventree.org'
INVENTREE_NEWS_URL = f'{INVENTREE_BASE_URL}/news/feed.atom'
# Determine if we are running in "test" mode e.g. "manage.py test"
TESTING = 'test' in sys.argv or 'TESTING' in os.environ or 'pytest' in sys.argv
TESTING = (
'test' in sys.argv
or 'TESTING' in os.environ
or any('pytest' in arg for arg in sys.argv)
)
if TESTING:
# Use a weaker password hasher for testing (improves testing speed)

View File

@ -282,40 +282,31 @@ def offload_task(
# function was passed - use that
_func = taskname
else:
# Split path
# Split on the last dot: everything before is the module path,
# everything after is the function name. rsplit handles any depth
# (e.g. 'app.module.func' or 'app.sub.module.func').
try:
app, mod, func = taskname.split('.')
app_mod = app + '.' + mod
module_path, func_name = taskname.rsplit('.', 1)
except ValueError:
raise_warning(
f"WARNING: '{taskname}' not started - Malformed function path"
)
return False
# Import module from app
try:
_mod = importlib.import_module(app_mod)
_mod = importlib.import_module(module_path)
except ModuleNotFoundError:
log_error('offload_task', scope='worker')
raise_warning(
f"WARNING: '{taskname}' not started - No module named '{app_mod}'"
f"WARNING: '{taskname}' not started - No module named '{module_path}'"
)
return False
# Retrieve function
try:
_func = getattr(_mod, func)
except AttributeError: # pragma: no cover
# getattr does not work for local import
_func = None
try:
if not _func:
_func = eval(func) # pragma: no cover
except NameError:
_func = getattr(_mod, func_name, None)
if _func is None:
log_error('offload_task', scope='worker')
raise_warning(
f"WARNING: '{taskname}' not started - No function named '{func}'"
f"WARNING: '{taskname}' not started - No function named '{func_name}'"
)
return False

View File

@ -0,0 +1,6 @@
"""Sample task functions used by offload_task unit tests."""
def get_result():
"""Return a fixed value; used to verify 4-part module paths resolve correctly."""
return 'abc'

View File

@ -67,26 +67,72 @@ class InvenTreeTaskTests(PluginRegistryMixin, TestCase):
# Run with string ref
InvenTree.tasks.offload_task('InvenTree.test_tasks.get_result')
# Error runs
# The error-path tests below all use force_sync=True so that the sync
# resolution code is exercised regardless of whether background workers
# happen to be running in this environment.
# Malformed taskname
with self.assertWarnsMessage(
UserWarning, "WARNING: 'InvenTree' not started - Malformed function path"
):
InvenTree.tasks.offload_task('InvenTree')
InvenTree.tasks.offload_task('InvenTree', force_sync=True)
# Non existent app
with self.assertWarnsMessage(
UserWarning,
"WARNING: 'InvenTreeABC.test_tasks.doesnotmatter' not started - No module named 'InvenTreeABC.test_tasks'",
):
InvenTree.tasks.offload_task('InvenTreeABC.test_tasks.doesnotmatter')
InvenTree.tasks.offload_task(
'InvenTreeABC.test_tasks.doesnotmatter', force_sync=True
)
# Non existent function
with self.assertWarnsMessage(
UserWarning,
"WARNING: 'InvenTree.test_tasks.doesnotexist' not started - No function named 'doesnotexist'",
):
InvenTree.tasks.offload_task('InvenTree.test_tasks.doesnotexist')
InvenTree.tasks.offload_task(
'InvenTree.test_tasks.doesnotexist', force_sync=True
)
def test_offloading_deep_path(self):
"""Test that task paths with more than 3 components are resolved correctly.
Previously the sync fallback split on '.' requiring exactly 3 parts,
so a 4-part path raised ValueError and returned False with no warning.
Now rsplit('.', 1) is used, so any depth works.
"""
# 4-part valid path: module = 'InvenTree.test_helpers.sample_tasks', func = 'get_result'
InvenTree.tasks.offload_task(
'InvenTree.test_helpers.sample_tasks.get_result', force_sync=True
)
# 4-part path with a non-existent module — should warn cleanly rather than crash
with self.assertWarnsMessage(
UserWarning,
"WARNING: 'InvenTreeABC.sub.tasks.doesnotmatter' not started - No module named 'InvenTreeABC.sub.tasks'",
):
InvenTree.tasks.offload_task(
'InvenTreeABC.sub.tasks.doesnotmatter', force_sync=True
)
def test_offloading_no_eval_bypass(self):
"""Verify that names in tasks.py scope cannot be resolved via an eval bypass.
The previous implementation fell back to eval(func) when getattr returned
None, evaluating the function name in the local scope of offload_task rather
than in the target module. This meant a Python builtin like 'eval', or any
name imported into tasks.py, could be invoked via a crafted taskname. The
replacement uses only getattr on the imported module and rejects anything
not found there.
"""
# 'eval' is a Python builtin; it is not an attribute of InvenTree.test_tasks.
# Previously eval('eval') in tasks.py's scope would resolve it; now it fails.
with self.assertWarnsMessage(
UserWarning,
"WARNING: 'InvenTree.test_tasks.eval' not started - No function named 'eval'",
):
InvenTree.tasks.offload_task('InvenTree.test_tasks.eval', force_sync=True)
def test_task_heartbeat(self):
"""Test the task heartbeat."""

View File

@ -1488,7 +1488,7 @@ class TestOffloadTask(InvenTreeTestCase):
offload_task('dummy_task.numbers', 1, 1, 1, force_sync=True)
)
self.assertIn('Malformed function path', str(log.output))
self.assertIn("No module named \\'dummy_task\\'", str(log.output))
# Offload dummy task with a Part instance
# This should succeed, ensuring that the Part instance is correctly pickled

View File

@ -50,12 +50,17 @@ def setup_tracing(
"""
if InvenTree.ready.isImportingData() or InvenTree.ready.isRunningMigrations():
return
if endpoint is None or headers is None:
print(
'Tracing endpoint or headers not specified - skipping tracing setup'
) # pragma: no cover
return # pragma: no cover
# check if trace is already set up - if so, skip
if trace.get_tracer_provider() is not None:
return
# Logger configuration
logger = logging.getLogger('inventree')
@ -163,29 +168,34 @@ def setup_tracing(
def setup_instruments(db_engine: str): # pragma: no cover
"""Run auto-instrumentation for OpenTelemetry tracing."""
DjangoInstrumentor().instrument()
RedisInstrumentor().instrument()
RequestsInstrumentor().instrument()
SystemMetricsInstrumentor().instrument()
if not DjangoInstrumentor()._is_instrumented_by_opentelemetry:
DjangoInstrumentor().instrument()
RedisInstrumentor().instrument()
RequestsInstrumentor().instrument()
SystemMetricsInstrumentor().instrument()
db_engine = str(db_engine).lower().strip()
# DBs
if 'sqlite' in db_engine:
SQLite3Instrumentor().instrument()
inst = SQLite3Instrumentor()
if not inst._is_instrumented_by_opentelemetry:
inst.instrument()
elif 'postgresql' in db_engine:
try:
from opentelemetry.instrumentation.psycopg import PsycopgInstrumentor
PsycopgInstrumentor().instrument(
enable_commenter=False, commenter_options={}
)
inst = PsycopgInstrumentor()
if not inst._is_instrumented_by_opentelemetry:
inst.instrument(enable_commenter=False, commenter_options={})
except ModuleNotFoundError:
pass
elif 'mysql' in db_engine:
try:
from opentelemetry.instrumentation.pymysql import PyMySQLInstrumentor
PyMySQLInstrumentor().instrument()
inst = PyMySQLInstrumentor()
if not inst._is_instrumented_by_opentelemetry:
inst.instrument()
except ModuleNotFoundError:
pass

View File

@ -32,6 +32,7 @@ from common.settings import get_global_setting
from generic.states.fields import InvenTreeCustomStatusSerializerMixin
from InvenTree.mixins import DataImportExportSerializerMixin
from InvenTree.serializers import (
CustomStatusSerializerMixin,
FilterableSerializerMixin,
InvenTreeDecimalField,
InvenTreeModelSerializer,
@ -53,6 +54,7 @@ from .status_codes import BuildStatus
class BuildSerializer(
CustomStatusSerializerMixin,
FilterableSerializerMixin,
NotesFieldMixin,
DataImportExportSerializerMixin,
@ -116,8 +118,6 @@ class BuildSerializer(
level = serializers.IntegerField(label=_('Build Level'), read_only=True)
status_text = serializers.CharField(source='get_status_display', read_only=True)
part_detail = OptionalField(
serializer_class=part_serializers.PartBriefSerializer,
serializer_kwargs={'source': 'part', 'many': False, 'read_only': True},

View File

@ -1817,6 +1817,97 @@ class BuildConsumeTest(BuildAPITest):
self.assertEqual(line.consumed, 100)
class BuildCustomStatusTest(BuildAPITest):
"""Tests for custom status values on Build orders."""
url = reverse('api-build-list')
def test_custom_status_query_count(self):
"""Test that listing Build orders with custom statuses does not cause N+1 queries.
Ensures that resolving 'status_text' for custom status values is O(1)
in database queries, not O(N) relative to the number of results.
"""
from django.contrib.contenttypes.models import ContentType
from common.models import InvenTreeCustomUserStateModel
build_content_type = ContentType.objects.get_for_model(Build)
# 10 custom status values - different keys, labels, and logical_keys
logical_keys = [
BuildStatus.PENDING.value,
BuildStatus.PRODUCTION.value,
BuildStatus.ON_HOLD.value,
BuildStatus.CANCELLED.value,
BuildStatus.COMPLETE.value,
BuildStatus.PENDING.value,
BuildStatus.PRODUCTION.value,
BuildStatus.ON_HOLD.value,
BuildStatus.CANCELLED.value,
BuildStatus.COMPLETE.value,
]
custom_statuses = [
InvenTreeCustomUserStateModel.objects.create(
key=3000 + i,
name=f'BuildCustomStatus{i}',
label=f'Build Custom Status Label {i}',
color='secondary',
logical_key=logical_keys[i],
model=build_content_type,
reference_status='BuildStatus',
)
for i in range(10)
]
part = Part.objects.filter(assembly=True).first()
# Build is an MPTT tree model; bulk_create requires tree fields to be
# populated manually. All new orders are root nodes (no parent) so each
# gets its own unique tree_id.
from django.db.models import Max
next_tree_id = (Build.objects.aggregate(m=Max('tree_id'))['m'] or 0) + 1
# Create 100 build orders, cycling through the 10 custom statuses
Build.objects.bulk_create([
Build(
part=part,
reference=f'BO-QTEST-{i}',
quantity=1,
status=custom_statuses[i % 10].logical_key,
status_custom_key=custom_statuses[i % 10].key,
lft=1,
rght=2,
level=0,
tree_id=next_tree_id + i,
)
for i in range(100)
])
# Lookup: custom_key -> custom_status_object, for quick per-row assertions
custom_lookup = {cs.key: cs for cs in custom_statuses}
# Query count must stay below the fixed threshold regardless of limit.
# An N+1 bug would push limit=50 or limit=100 well over the threshold.
for limit in [1, 10, 50, 100]:
response = self.get(
self.url, data={'limit': limit}, expected_code=200, max_query_count=50
)
for result in response.data['results']:
cs = custom_lookup.get(result['status_custom_key'])
if cs is None:
# Build from fixtures - no custom status assigned
continue
self.assertEqual(result['status'], cs.logical_key)
self.assertEqual(result['status_custom_key'], cs.key)
self.assertEqual(result['status_text'], cs.label)
class BuildAutoAllocateAPITest(InvenTreeAPITestCase):
"""API integration tests for BuildAutoAllocate endpoint back-ports (stock_sort_by, build_lines)."""

View File

@ -79,21 +79,37 @@ class AttachmentAdmin(admin.ModelAdmin):
@admin.register(common.models.DataOutput)
class DataOutputAdmin(admin.ModelAdmin):
"""Admin interface for DataOutput objects."""
"""Admin interface for DataOutput objects - view and delete only."""
list_display = ('user', 'created', 'output_type', 'output')
list_filter = ('user', 'output_type')
def has_add_permission(self, request):
"""Prevent addition of new DataOutput objects via the admin interface."""
return False
def has_change_permission(self, request, obj=None):
"""Prevent modification of DataOutput objects via the admin interface."""
return False
@admin.register(common.models.BarcodeScanResult)
class BarcodeScanResultAdmin(admin.ModelAdmin):
"""Admin interface for BarcodeScanResult objects."""
"""Admin interface for BarcodeScanResult objects - read-only audit log."""
list_display = ('data', 'timestamp', 'user', 'endpoint', 'result')
list_filter = ('user', 'endpoint', 'result')
def has_add_permission(self, request):
"""Prevent addition of new BarcodeScanResult objects via the admin interface."""
return False
def has_change_permission(self, request, obj=None):
"""Prevent modification of BarcodeScanResult objects via the admin interface."""
return False
@admin.register(common.models.ProjectCode)
class ProjectCodeAdmin(admin.ModelAdmin):
@ -139,14 +155,22 @@ class WebhookAdmin(admin.ModelAdmin):
@admin.register(common.models.NotificationEntry)
class NotificationEntryAdmin(admin.ModelAdmin):
"""Admin settings for NotificationEntry."""
"""Admin settings for NotificationEntry - view and delete only."""
list_display = ('key', 'uid', 'updated')
def has_add_permission(self, request):
"""Prevent addition of new NotificationEntry objects via the admin interface."""
return False
def has_change_permission(self, request, obj=None):
"""Prevent modification of NotificationEntry objects via the admin interface."""
return False
@admin.register(common.models.NotificationMessage)
class NotificationMessageAdmin(admin.ModelAdmin):
"""Admin settings for NotificationMessage."""
"""Admin settings for NotificationMessage - view and delete only."""
list_display = (
'age_human',
@ -162,14 +186,46 @@ class NotificationMessageAdmin(admin.ModelAdmin):
search_fields = ('name', 'category', 'message')
def has_add_permission(self, request):
"""Prevent addition of new NotificationMessage objects via the admin interface."""
return False
def has_change_permission(self, request, obj=None):
"""Prevent modification of NotificationMessage objects via the admin interface."""
return False
@admin.register(common.models.NewsFeedEntry)
class NewsFeedEntryAdmin(admin.ModelAdmin):
"""Admin settings for NewsFeedEntry."""
"""Admin settings for NewsFeedEntry - view and delete only."""
list_display = ('title', 'author', 'published', 'summary')
def has_add_permission(self, request):
"""Prevent addition of new NewsFeedEntry objects via the admin interface."""
return False
admin.site.register(common.models.WebhookMessage, admin.ModelAdmin)
admin.site.register(common.models.EmailMessage, admin.ModelAdmin)
admin.site.register(common.models.EmailThread, admin.ModelAdmin)
def has_change_permission(self, request, obj=None):
"""Prevent modification of NewsFeedEntry objects via the admin interface."""
return False
class ReadOnlyAdmin(admin.ModelAdmin):
"""Base admin class that prevents all modifications."""
def has_add_permission(self, request):
"""Prevent addition of new objects via the admin interface."""
return False
def has_change_permission(self, request, obj=None):
"""Prevent modification of objects via the admin interface."""
return False
def has_delete_permission(self, request, obj=None):
"""Prevent deletion of objects via the admin interface."""
return False
admin.site.register(common.models.WebhookMessage, ReadOnlyAdmin)
admin.site.register(common.models.EmailMessage, ReadOnlyAdmin)
admin.site.register(common.models.EmailThread, ReadOnlyAdmin)

View File

@ -3355,6 +3355,11 @@ class EmailThread(InvenTree.models.InvenTreeMetadataModel):
unique_together = [['key', 'global_id']]
ordering = ['-updated']
@staticmethod
def get_api_url():
"""Return the API URL associated with the EmailThread model."""
return reverse('api-email-list')
key = models.CharField(
max_length=250,
verbose_name=_('Key'),

View File

@ -235,6 +235,12 @@ USER_SETTINGS: dict[str, InvenTreeSettingsKeyType] = {
'default': False,
'validator': bool,
},
'SHOW_EXTRA_MODEL_INFO': {
'name': _('Show Extra Model Information'),
'description': _('Display extra information in model selection dropdowns'),
'default': False,
'validator': bool,
},
'SHOW_FULL_LOCATION_IN_TABLES': {
'name': _('Show full stock location in tables'),
'description': _(

View File

@ -121,3 +121,7 @@ class TransitionTests(InvenTreeTestCase):
self.assertIn(
"ValueError('This is a broken transition plugin!')", str(cm.output[0])
)
# Ensure the plugin is now disabled
registry.set_plugin_state('sample-transition', False)
registry.set_plugin_state('sample-broken-transition', False)

View File

@ -60,7 +60,7 @@ class TransitionMethod:
**kwargs: Additional keyword arguments for custom logic.
Returns:
result: bool - True if the transition method was successful, False otherwise.
result: bool - True if the transition method was successful (and no further transitions are attempted), False otherwise.
Raises:
ValidationError: Alert the user that the transition failed

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

View File

@ -31,6 +31,7 @@ from importer.registry import register_importer
from InvenTree.helpers import extract_serial_numbers, hash_barcode, normalize, str2bool
from InvenTree.mixins import DataImportExportSerializerMixin
from InvenTree.serializers import (
CustomStatusSerializerMixin,
FilterableSerializerMixin,
InvenTreeCurrencySerializer,
InvenTreeDecimalField,
@ -100,7 +101,10 @@ class DuplicateOrderSerializer(serializers.Serializer):
class AbstractOrderSerializer(
DataImportExportSerializerMixin, FilterableSerializerMixin, serializers.Serializer
CustomStatusSerializerMixin,
DataImportExportSerializerMixin,
FilterableSerializerMixin,
serializers.Serializer,
):
"""Abstract serializer class which provides fields common to all order types."""
@ -118,9 +122,6 @@ class AbstractOrderSerializer(
read_only=True, allow_null=True, label=_('Completed Lines')
)
# Human-readable status text (read-only)
status_text = serializers.CharField(source='get_status_display', read_only=True)
# status field cannot be set directly
status = serializers.IntegerField(read_only=True, label=_('Order Status'))

View File

@ -16,7 +16,7 @@ from icalendar import Calendar
from rest_framework import status
from common.currency import currency_codes
from common.models import InvenTreeSetting
from common.models import InvenTreeCustomUserStateModel, InvenTreeSetting
from common.settings import set_global_setting
from company.models import Company, SupplierPart, SupplierPriceBreak
from InvenTree.unit_test import InvenTreeAPITestCase
@ -737,6 +737,69 @@ class PurchaseOrderTest(OrderTest):
)
self.assertEqual(response.status_code, 200)
def test_po_custom_status_query_count(self):
"""Test that listing PurchaseOrders with custom statuses does not cause N+1 queries.
Ensures that resolving the 'status_text' field for custom status values
is O(1) in database queries, not O(N) relative to the number of results.
"""
from django.contrib.contenttypes.models import ContentType
po_content_type = ContentType.objects.get_for_model(models.PurchaseOrder)
# 10 custom status values - different keys, labels, and logical_keys
logical_keys = [
PurchaseOrderStatus.PENDING.value,
PurchaseOrderStatus.PLACED.value,
PurchaseOrderStatus.ON_HOLD.value,
PurchaseOrderStatus.COMPLETE.value,
PurchaseOrderStatus.CANCELLED.value,
PurchaseOrderStatus.LOST.value,
PurchaseOrderStatus.RETURNED.value,
PurchaseOrderStatus.PENDING.value,
PurchaseOrderStatus.PLACED.value,
PurchaseOrderStatus.ON_HOLD.value,
]
custom_statuses = [
InvenTreeCustomUserStateModel.objects.create(
key=1000 + i,
name=f'PoCustomStatus{i}',
label=f'PO Custom Status Label {i}',
color='secondary',
logical_key=logical_keys[i],
model=po_content_type,
reference_status='PurchaseOrderStatus',
)
for i in range(10)
]
# Create 100 purchase orders, cycling through the custom statuses
supplier = Company.objects.filter(is_supplier=True).first()
models.PurchaseOrder.objects.bulk_create([
models.PurchaseOrder(
supplier=supplier,
reference=f'PO-QTEST-{i}',
status=custom_statuses[i % 10].logical_key,
status_custom_key=custom_statuses[i % 10].key,
)
for i in range(100)
])
# Query count must stay below the fixed threshold for all limit values.
# An N+1 bug would push limit=50 or limit=100 well over the threshold.
for limit in [1, 5, 10, 25, 50, 100]:
response = self.get(
self.LIST_URL,
data={'limit': limit},
expected_code=200,
max_query_count=50,
)
for result in response.data['results']:
self.assertIn('status_text', result)
self.assertIsNotNone(result['status_text'])
class PurchaseOrderLineItemTest(OrderTest):
"""Unit tests for PurchaseOrderLineItems."""
@ -771,7 +834,7 @@ class PurchaseOrderLineItemTest(OrderTest):
# Try to delete a set of line items via their IDs
self.delete(url, {'items': [1, 2]}, expected_code=200)
# We should have 2 less PurchaseOrderLineItems after deletign them
# We should have 2 less PurchaseOrderLineItems after deleting them
self.assertEqual(models.PurchaseOrderLineItem.objects.count(), n - 2)
def test_po_line_merge_pricing(self):

View File

@ -533,10 +533,23 @@ class BarcodePOReceive(BarcodeView):
# Now, look just for "supplier-barcode" plugins
plugins = registry.with_mixin(PluginMixinEnum.SUPPLIER_BARCODE)
plugin_slug = None
plugin_response = None
plugin_error = None
no_supplier_plugin_error = []
supplier_purchase_order = None
plugin_supplier = None
supplier_part = None
for current_plugin in plugins:
try:
# Will either Output Debugresponse if No_Match is True or return the regular response if No_Match is False
result = current_plugin.scan_receive_item(
barcode,
request.user,
@ -546,12 +559,58 @@ class BarcodePOReceive(BarcodeView):
line_item=line_item,
auto_allocate=auto_allocate,
)
except Exception:
log_error('BarcodePOReceive.handle_barcode', plugin=current_plugin.slug)
continue
if result is None:
continue
no_match = result.get('no_match', True)
# No_Match Determines if it found a exact match for all the required fields from scan_recieve_item
if no_match is True:
supplier_found = False
try:
plugin_slug = current_plugin.slug
supplier_purchase_order = result.get('PO')
plugin_supplier = result.get('supplier')
supplier_part = result.get('supplier_part')
except KeyError as e:
log_error(
f'BarcodePOReceive.handle_barcode debugresponse: KeyError {e}'
)
continue
# Supplier does not have associated Supplier ID
if plugin_supplier is None:
no_supplier_plugin_error.append(plugin_slug)
continue
# No Purchase Order or Supplier Part Found
if supplier_purchase_order is None and supplier_part is None:
continue
# Purchase Order exists and is found but Supplier part does not exist
if supplier_purchase_order != None and supplier_part is None:
# Supplier was Found
supplier_found = True
plugin_error = _('Purchase order Found\rNo supplier Part Match')
# Supplier Part is Found but Purchase Order does not exist
elif supplier_purchase_order is None and supplier_part != None:
# Supplier was Found
supplier_found = True
plugin_error = _('Supplier Part Found\rNo Purchase Order Match')
# Supplier for PO or Supplier part in barcode was found
if supplier_found is True:
# Adds info on for what was found in the barcode
response['supplier_matches'] = {
'purchase_order': supplier_purchase_order,
'no_match': no_match,
'supplier': plugin_supplier,
'supplier_part': supplier_part,
}
if 'error' in result:
logger.info(
@ -569,13 +628,20 @@ class BarcodePOReceive(BarcodeView):
response['plugin'] = plugin.name if plugin else None
if plugin_response:
# If there is a plugin response, and there is a match (no_match = false), combine the dictionaries
if plugin_response and plugin_response.get('no_match') is False:
response = {**response, **plugin_response}
elif no_supplier_plugin_error:
response['no_supplier_plugin_error'] = no_supplier_plugin_error
# A plugin has not been found!
if plugin is None:
response['error'] = _('No plugin match for supplier barcode')
# A plugin was found, with a Error
elif plugin_error:
response['error'] = plugin_error
self.log_scan(request, response, 'success' in response)
if 'error' in response:
@ -787,11 +853,11 @@ class BarcodeScanResultList(BarcodeScanResultMixin, BulkDeleteMixin, ListAPI):
filterset_class = BarcodeScanResultFilter
filter_backends = SEARCH_ORDER_FILTER
ordering_fields = ['user', 'plugin', 'timestamp', 'endpoint', 'result']
ordering_fields = ['user', 'timestamp', 'endpoint', 'result']
ordering = '-timestamp'
search_fields = ['plugin']
search_fields = ['data']
class BarcodeScanResultDetail(BarcodeScanResultMixin, RetrieveDestroyAPI):

View File

@ -320,7 +320,7 @@ class SupplierBarcodeMixin(BarcodeMixin):
location=None,
auto_allocate: bool = True,
**kwargs,
) -> dict | None:
) -> dict:
"""Attempt to receive an item against a PurchaseOrder via barcode scanning.
Arguments:
@ -344,32 +344,50 @@ class SupplierBarcodeMixin(BarcodeMixin):
# Extract supplier information
supplier = supplier or self.get_supplier(cache=True)
if not supplier:
"""Construct Debug Response
This is returned if a perfect match is not found with the info provided from the barcode
Response Info:
'supplier': get supplier ID
'PO': Represented for "Purchase Order", find PO number to supplier
'supplier_part': find supplier part info to supplier
'no_match': Boolean, did we find a perfect match with info given? False is Yes, True is No
"""
debug_response = {}
if supplier is None:
# No supplier information available
return None
debug_response['supplier'] = None
else:
debug_response['supplier'] = supplier.name
# Extract purchase order information
purchase_order = purchase_order or self.get_purchase_order()
if not purchase_order or purchase_order.supplier != supplier:
if purchase_order is None or purchase_order.supplier != supplier:
# Purchase order does not match supplier
return None
debug_response['PO'] = None
else:
debug_response['PO'] = purchase_order.reference
supplier_part = self.get_supplier_part()
if not supplier_part:
if supplier_part is None:
# No supplier part information available
return None
debug_response['supplier_part'] = None
else:
debug_response['supplier_part'] = str(supplier_part.part)
# Attempt to find matching line item
if not line_item:
if not line_item and purchase_order != None:
line_items = purchase_order.lines.filter(part=supplier_part)
if line_items.count() == 1:
line_item = line_items.first()
if not line_item:
# No line item information available
return None
# If Purchase Order or Supplier Part does not exist, throw debug response
if debug_response['PO'] is None or debug_response['supplier_part'] is None:
debug_response['no_match'] = True
return debug_response
if line_item.part != supplier_part:
return {'error': _('Supplier part does not match line item')}
@ -406,7 +424,8 @@ class SupplierBarcodeMixin(BarcodeMixin):
'supplier_part': supplier_part.pk,
'purchase_order': purchase_order.pk,
'location': location.pk if location else None,
}
},
'no_match': False,
}
if action_required:

View File

@ -51,7 +51,9 @@ class AppMixin:
"""
from common.settings import get_global_setting
if settings.PLUGIN_TESTING or get_global_setting('ENABLE_PLUGINS_APP'):
if settings.PLUGIN_TESTING or get_global_setting(
'ENABLE_PLUGINS_APP', create=False
):
logger.info('Registering IntegrationPlugin apps')
apps_changed = False

View File

@ -428,6 +428,95 @@ class SupplierBarcodePOReceiveTests(InvenTreeAPITestCase):
# Quantity should be pre-filled with the remaining quantity
self.assertEqual(5, response.data['lineitem']['quantity'])
def test_receive_includes_no_match_false(self):
"""Successful receive merges no_match=False from the plugin response."""
self.purchase_order1.place_order()
url = reverse('api-barcode-po-receive')
result = self.post(url, data={'barcode': DIGIKEY_BARCODE}, expected_code=200)
self.assertIn('success', result.data)
self.assertIn('no_match', result.data)
self.assertFalse(result.data['no_match'])
def test_partial_match_po_found_no_supplier_part(self):
"""Barcode references a known PO but the SKU has no matching supplier part."""
url = reverse('api-barcode-po-receive')
# '1K72991337' matches purchase_order1 via supplier_reference; SKU is unknown
result = self.post(
url, data={'barcode': DIGIKEY_BARCODE_PO_NO_PART}, expected_code=400
)
self.assertIn('error', result.data)
self.assertIn('Purchase order Found', result.data['error'])
self.assertIn('No supplier Part Match', result.data['error'])
self.assertIn('supplier_matches', result.data)
matches = result.data['supplier_matches']
self.assertIsNotNone(matches['purchase_order'])
# DRF's ValidationError converts None leaves to ErrorDetail('None')
self.assertEqual(str(matches['supplier_part']), 'None')
self.assertIsNotNone(matches['supplier'])
self.assertTrue(matches['no_match'])
def test_partial_match_supplier_part_found_no_po(self):
"""Barcode references a known supplier part but the PO reference has no match."""
url = reverse('api-barcode-po-receive')
# 'P296-LM358BIDDFRCT-ND' matches the existing supplier part; PO ref is unknown
result = self.post(
url, data={'barcode': DIGIKEY_BARCODE_PART_NO_PO}, expected_code=400
)
self.assertIn('error', result.data)
self.assertIn('Supplier Part Found', result.data['error'])
self.assertIn('No Purchase Order Match', result.data['error'])
self.assertIn('supplier_matches', result.data)
matches = result.data['supplier_matches']
# DRF's ValidationError converts None leaves to ErrorDetail('None')
self.assertEqual(str(matches['purchase_order']), 'None')
self.assertIsNotNone(matches['supplier_part'])
self.assertIsNotNone(matches['supplier'])
self.assertTrue(matches['no_match'])
def test_no_supplier_plugin_error(self):
"""Plugins that cannot resolve a supplier are listed in no_supplier_plugin_error."""
url = reverse('api-barcode-po-receive')
digikey_plugin = registry.get_plugin('digikeyplugin')
original_supplier_id = digikey_plugin.get_setting('SUPPLIER_ID')
# Use a non-existent PK so get_supplier() returns None
digikey_plugin.set_setting('SUPPLIER_ID', 99999)
if hasattr(digikey_plugin, '_supplier'):
del digikey_plugin._supplier
try:
result = self.post(
url, data={'barcode': DIGIKEY_BARCODE}, expected_code=400
)
self.assertIn('no_supplier_plugin_error', result.data)
self.assertIn('digikeyplugin', result.data['no_supplier_plugin_error'])
finally:
digikey_plugin.set_setting('SUPPLIER_ID', original_supplier_id)
if hasattr(digikey_plugin, '_supplier'):
del digikey_plugin._supplier
def test_no_supplier_matches_when_both_missing(self):
"""When neither PO nor supplier part can be resolved, no supplier_matches is set."""
url = reverse('api-barcode-po-receive')
# Completely unknown barcode — no PO, no part, no supplier
result = self.post(
url, data={'barcode': 'COMPLETELY-UNKNOWN-BARCODE-XYZ'}, expected_code=400
)
self.assertIn('error', result.data)
self.assertNotIn('supplier_matches', result.data)
DIGIKEY_BARCODE = (
'[)>\x1e06\x1dP296-LM358BIDDFRCT-ND\x1d1PLM358BIDDFR\x1dK\x1d1K72991337\x1d'
@ -476,3 +565,17 @@ TME_QRCODE = (
)
TME_DATAMATRIX_CODE = 'PWBP-302 1PMPNWBP-302 Q1 K19361337/1'
# DigiKey barcode: '1K72991337' matches purchase_order1 via supplier_reference,
# but 'PNONEXISTENT-SKU' has no matching supplier part in the test database.
DIGIKEY_BARCODE_PO_NO_PART = (
'[)>\x1e06\x1dPNONEXISTENT-SKU-XXXX\x1d1PNONEXISTENT-MPN\x1dK\x1d1K72991337\x1d'
'10K85781337\x1d11K1\x1d4LPH\x1dQ10\x1d11ZPICK'
)
# DigiKey barcode: 'P296-LM358BIDDFRCT-ND' matches the existing supplier part,
# but '1KBADORDER-XXXXX' matches no purchase order in the test database.
DIGIKEY_BARCODE_PART_NO_PO = (
'[)>\x1e06\x1dP296-LM358BIDDFRCT-ND\x1d1PLM358BIDDFR\x1dK\x1d1KBADORDER-XXXXX\x1d'
'10K85781337\x1d11K1\x1d4LPH\x1dQ10\x1d11ZPICK'
)

View File

@ -77,12 +77,24 @@ class StockItemAdmin(admin.ModelAdmin):
@admin.register(StockItemTracking)
class StockTrackingAdmin(admin.ModelAdmin):
"""Admin class for StockTracking."""
"""Admin class for StockTracking - read-only to preserve audit trail integrity."""
list_display = ('item', 'date', 'label')
autocomplete_fields = ['item']
def has_add_permission(self, request):
"""Prevent addition of new tracking entries via the admin interface."""
return False
def has_change_permission(self, request, obj=None):
"""Prevent modification of tracking entries via the admin interface."""
return False
def has_delete_permission(self, request, obj=None):
"""Prevent deletion of tracking entries via the admin interface."""
return False
@admin.register(StockItemTestResult)
class StockItemTestResultAdmin(admin.ModelAdmin):

View File

@ -33,6 +33,7 @@ from generic.states.fields import InvenTreeCustomStatusSerializerMixin
from importer.registry import register_importer
from InvenTree.mixins import DataImportExportSerializerMixin
from InvenTree.serializers import (
CustomStatusSerializerMixin,
InvenTreeCurrencySerializer,
InvenTreeDecimalField,
OptionalField,
@ -308,6 +309,7 @@ class StockItemTestResultSerializer(
@register_importer()
class StockItemSerializer(
CustomStatusSerializerMixin,
InvenTree.serializers.FilterableSerializerMixin,
DataImportExportSerializerMixin,
InvenTreeCustomStatusSerializerMixin,
@ -565,10 +567,6 @@ class StockItemSerializer(
return queryset
status_text = serializers.CharField(
source='get_status_display', read_only=True, label=_('Status')
)
SKU = serializers.CharField(
source='supplier_part.SKU',
read_only=True,

View File

@ -1411,6 +1411,82 @@ class CustomStockItemStatusTest(StockAPITestCase):
self.assertEqual(status['value'], self.status.key)
self.assertEqual(status['display_name'], self.status.label)
def test_custom_status_query_count(self):
"""Test that listing StockItems with custom statuses does not cause N+1 queries.
Ensures that resolving 'status_text' for custom status values is O(1)
in database queries, not O(N) relative to the number of results.
"""
stock_content_type = ContentType.objects.get(model='stockitem')
# 10 custom status values - different keys, labels, and logical_keys
logical_keys = [
StockStatus.OK.value,
StockStatus.ATTENTION.value,
StockStatus.DAMAGED.value,
StockStatus.DESTROYED.value,
StockStatus.REJECTED.value,
StockStatus.LOST.value,
StockStatus.QUARANTINED.value,
StockStatus.RETURNED.value,
StockStatus.OK.value,
StockStatus.ATTENTION.value,
]
custom_statuses = [
InvenTreeCustomUserStateModel.objects.create(
key=2000 + i,
name=f'StockCustomStatus{i}',
label=f'Stock Custom Status Label {i}',
color='secondary',
logical_key=logical_keys[i],
model=stock_content_type,
reference_status='StockStatus',
)
for i in range(10)
]
part = Part.objects.filter(active=True, virtual=False).first()
# Create 500 stock items, cycling through the 10 custom statuses
StockItem.objects.bulk_create([
StockItem(
part=part,
quantity=1,
level=0,
tree_id=0,
lft=0,
rght=0,
status=custom_statuses[i % 10].logical_key,
status_custom_key=custom_statuses[i % 10].key,
)
for i in range(500)
])
# Lookup: custom_key -> custom_status_object, for quick per-row assertions
custom_lookup = {cs.key: cs for cs in custom_statuses}
# Query count must stay below the fixed threshold regardless of limit.
# An N+1 bug would push limit=100 or limit=500 well over the threshold.
for limit in [50, 100, 500]:
response = self.get(
self.list_url,
data={'limit': limit},
expected_code=200,
max_query_count=50,
)
for result in response.data['results']:
cs = custom_lookup.get(result['status_custom_key'])
if cs is None:
# Item from fixtures - no custom status assigned
continue
self.assertEqual(result['status'], cs.logical_key)
self.assertEqual(result['status_custom_key'], cs.key)
self.assertEqual(result['status_text'], cs.label)
class StockItemTest(StockAPITestCase):
"""Series of API tests for the StockItem API."""

View File

@ -118,10 +118,8 @@ class InvenTreeGroupAdminForm(forms.ModelForm):
class InvenTreeUserAdmin(UserAdmin):
"""Custom admin page for the User model.
Hides the "permissions" view as this is now handled
entirely by groups and RuleSets.
(And it's confusing!)
- Restrict user creation and editing to superuser accounts
- Hides the "permissions" view as this is handled by RuleSets
"""
list_display = (
@ -132,6 +130,7 @@ class InvenTreeUserAdmin(UserAdmin):
'is_staff',
'last_login',
) # display last connection for each user in user admin panel.
fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
@ -142,6 +141,15 @@ class InvenTreeUserAdmin(UserAdmin):
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
)
def get_readonly_fields(self, request, obj=None):
"""Make all fields read-only for non-superusers."""
fields = super().get_readonly_fields(request, obj)
if not request.user.is_superuser:
fields += ('is_staff', 'is_superuser')
return fields
@admin.register(Owner)
class OwnerAdmin(admin.ModelAdmin):

View File

@ -95,22 +95,22 @@ bleach==4.1.0 \
# via
# -c src/backend/requirements.txt
# django-markdownify
blessed==1.39.0 \
--hash=sha256:666e7e3fd0a4e38c3a262eaaf1e22a4ce2c81337aa17593c3f60ea136ec24fe1 \
--hash=sha256:b04fc7141a20a3b2ade6cad741051f1e3ac59cc1e7e90915ed1f9e521332bea4
blessed==1.42.0 \
--hash=sha256:34b460b77562ed21f807cfd7c527b983b0cc300c98810c8076f283b7bcd45ba7 \
--hash=sha256:f96c4a6dc664b48e0b832fa732acc16df67abd30f0ec35babf99025982f21852
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
boto3==1.43.8 \
--hash=sha256:1894497c383e3cdf50e210f1f57a43e9f4047a5d3accc73ffdb7eacc3b0f011b \
--hash=sha256:d1235602d715c727c1923ef4bcdb5612a20575a9a5e4f2db00d571e0ea1f85fc
boto3==1.43.14 \
--hash=sha256:574335744656cfed0b362a0a0467aaf2eb2bf15526edcd02d31d3c661f4b09e4 \
--hash=sha256:5c0a994b3182061ee101812e721100717a4d664f9f4ceaf4a86b6d032ce9fc2d
# via
# -c src/backend/requirements.txt
# django-anymail
# django-storages
botocore==1.43.8 \
--hash=sha256:611ad8b1f60661373cd39d9391ff16f1eaf8f5cb1d0a691563a4201d1a2603ce \
--hash=sha256:6257d2655c3abe75eaa49e218b7d883cdc7cea64652b451e5feb08a6c169da3c
botocore==1.43.14 \
--hash=sha256:1f4a2a95ea78c10398e78431e98c1fe47adb54a7b10a32975144c1f541186658 \
--hash=sha256:b9e500737e43d2f147c9d4e23b54360335e77d4c0ba90a318f51b65e06cb8516
# via
# -c src/backend/requirements.txt
# boto3
@ -219,9 +219,9 @@ brotli==1.2.0 \
# via
# -c src/backend/requirements.txt
# fonttools
certifi==2026.4.22 \
--hash=sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a \
--hash=sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580
certifi==2026.5.20 \
--hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \
--hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d
# via
# -c src/backend/requirements.txt
# requests
@ -762,49 +762,49 @@ drf-spectacular==0.29.0 \
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
dulwich==1.2.1 \
--hash=sha256:011d37afa0922b500c938d6417317e3e6d29d33a6fbaf12b3696fe2a216aa170 \
--hash=sha256:01f43a1b6953c93b4e957c0902f7de80267285e0c59c9b44f4801f7da9f11107 \
--hash=sha256:04215befa00c82accda97d0e3ce760d9344a67f081d5b92ade8ad00007fb38d8 \
--hash=sha256:09ca37d7a9086f96e535a6474644588e4502f69e2b202c39d92901e3606f6501 \
--hash=sha256:0b1181f4ae225bbde373fc16279c4e61fdf04aedd30ec21fab387be642567453 \
--hash=sha256:1073c75db1f5da845d219d1b5b601aeaaa7be5f999f3c77f4966f9ca2327bf44 \
--hash=sha256:11a51dba8454b4c64bf242a918ca4c4300097f7cec84a13164845e638a26f9de \
--hash=sha256:121d2d181407cd7bb051922dc3bc00841ca0959b8299880529525db93dfbdca9 \
--hash=sha256:1961e0b6c0b1f2920f4ab05821652d8eb12f19ddb5a4c167c385902391c08dd3 \
--hash=sha256:21f48480bb1e6501bdec7f0877639cdfc64a16e0dd97c24947582fbeed3baf21 \
--hash=sha256:23560643c1cc85737c87985761666a59b35b06a1cbb06db5ba642fa35c67be93 \
--hash=sha256:3183b7db09282842459fbeb37e77e498c8339e6db4d0cb6b5d3685c97ce79dac \
--hash=sha256:3412eca24fd79d5413ac7003f8f29b2e01008d7c9d6fce159f77602f70058aea \
--hash=sha256:3a95dd2649ef3c6e59095d5bd3de08470b3ba908dc41343f5823666a10a326f8 \
--hash=sha256:4198f79ff04b07f6f6dc81047041f341b35e774cf0207f496b35f34dd2e1bb9c \
--hash=sha256:41dfe1b1cb0f0202102e90f9f47f54458f27da6b746939a6ed85362b68e3e3a5 \
--hash=sha256:4b6cd8104bf4adf2824221ba4f3b399c641a7a6a026dfa193c2f516531971315 \
--hash=sha256:55c8415fb19068a5956e0d5bd83b0fb6e06b291d3b096d8fb6a604c105fc6ca0 \
--hash=sha256:5a0245dccd69fba9dfe2ed88eff9212bad3e04107bc41676fffbc7f706428cdb \
--hash=sha256:5e875729df04991732959f52e29c1de96d00eaf62feddfc60f0b2b08c6e38870 \
--hash=sha256:6349b475a1b17b224191a4801687a2486574c9526e00835db2ba9ed081124b11 \
--hash=sha256:674b21a9025c00a00495fa03a9bb5f2da67a13ee513c74f16bd91c56678d0a77 \
--hash=sha256:74b01505b8e2ba81a715213101c790e0794f5c0b8021f8ce1100131a1e8cfa55 \
--hash=sha256:879abd15550b6abcebf97823905a167623f331eeca5e12dc0cac62bcd9e70bcd \
--hash=sha256:96846ef600378739a64e347412c22d5f87f1b67d68526c633b465589335e9387 \
--hash=sha256:96c160d68173ae55f68bf734e7fddae04ea025f3541996b0b5f28dc2025b42dc \
--hash=sha256:9d0ea88273a7ee6fd3b1d75e231cc6fc614774e19bdd7c1de5df274b4b492dde \
--hash=sha256:9f7249c1090be7e7e841edda369d843617314092bb05f2a6a8b7f88660bbb2a6 \
--hash=sha256:a65527eda5f6a463168c6338d781b05d325ba85dc05c6f8217ddd8454911bb53 \
--hash=sha256:ba43bfb3a7cad40d9607170561e8c3be42e7083b4b57af89a5f54e01577ff791 \
--hash=sha256:bad94416de9a76ad36dc19f0e65a829b89fc845282bdcc8c43285a4addc1ad2e \
--hash=sha256:cedc717cf9ee973df63293bcbaa51bb03ab6f87ff631d1dd329078b3f48712a7 \
--hash=sha256:cf3aa983ca4907c96a0b98b357a3cac7381192786495522a65944da1b284bc02 \
--hash=sha256:d0ddda4e165ea14c70e1d0531aa97e527332f1133ea0c65eebaa5afb8786029e \
--hash=sha256:da2fdcc9e4f178b2b4ec1194d8e17d111d572f111fdaa7c6c5a3f46bbe686eaf \
--hash=sha256:e2f14d20aea48dd1d48714d637c70399b140825d930b4f5aa6fbb62199429740 \
--hash=sha256:e45a68da922a4abe8fd015ed020ec0123ee58176a6984a34d2a2c74c959e45d3 \
--hash=sha256:e88fd960a9327d87556a3bad76ad84b6346d3a07409fe8f081877a775977c86b \
--hash=sha256:efd394fcb926ec8e9ed63fe0883784abf8ef7bb1d30a492e950ca8c2f369ce7b \
--hash=sha256:f944b742962d9933e60863f577491743328b287251ae57a1e7cd84c289acfc23 \
--hash=sha256:fb855620d04d0c286058db941c106cfc60bc1e4882ea20b6dd499f3668a1afbe \
--hash=sha256:fd774825510350b614121bfa84d95c6eb08f7e93c0fe7faf8760423a43dbad8b
dulwich==1.2.5 \
--hash=sha256:00b54a1d56ddbacdd8eadd6d4787a51b3a05fefa30eadbf9165fd283a00b90ed \
--hash=sha256:0395b2c8924c3424bafe2d9c1edd5348cc4b21ce9c1d6655bf01f9a5c47164c8 \
--hash=sha256:07cb75b58216440e2c170fff4f3d55a5f387358d9489863af8cb11f24ee37121 \
--hash=sha256:1679b376433a0fc7f36586afda1d4ed7427afa7a79d4bf17e5014474eea69fa4 \
--hash=sha256:1699a4cf8d44c174408325a9594a1498d05786cea34e3004c8732420ee1b8182 \
--hash=sha256:1ba83ec3cfb4c506c277400357a51523c8258fa07b841ee06e8e1071da4cfed1 \
--hash=sha256:1c151a7f3995ccf9d433a603b747e76141a7ebe7c385c8909e9f7e7a6422c28f \
--hash=sha256:2f10dafa1ef5660b1331364bc8d68446448608a8d8f493ed0e260eaf5133e71c \
--hash=sha256:2f90d68bfa97c4ca71de7507984365aefe27b6d248cb28dc99644d0f3ae8c60b \
--hash=sha256:41ccffb0521f3f9ad73fac78772f321d731607336cee48911e7c26963459481c \
--hash=sha256:46db47394ba8a95748ae739f5d3a5a3e1724a2f857bf2437bc71bfc0baaed91d \
--hash=sha256:5108acead814d1de8b6262d6d8fb90af7e82f5a4d83788b6b48e39d01800a92f \
--hash=sha256:517fb7e20f91d2bd48dc5de9edc90ff8974a5512ce7f243284b191f8be6344c3 \
--hash=sha256:53599909d54a2fae49fcd50047f1daf4b8b9eda6a5500a08b71da689f5431c24 \
--hash=sha256:556593fd11637f80f6018bee1916b1a84f5b420423b470ebb3f1a782ad6ef081 \
--hash=sha256:5e067b7feceb7034bc99e7c7143a704f1d97d4be7027d9a0aa5a83c0657ff091 \
--hash=sha256:66aded7d364341b55941973a1562323f25bd205f0809692b687ec36ccd31242c \
--hash=sha256:6c683c0f4a062894b6826c61102d415dae86ade61a10003c82ccc2b91858d5fb \
--hash=sha256:701a9ecf7a8a44f5e2459e46befa93530cf36a8b1ae3140aefc007db1d7d0207 \
--hash=sha256:77d2d2e43ad975459491de1ebf47990c74ff17f12586c8561e9890239bc422db \
--hash=sha256:827366331603150de5976d72dd456a3fd5fc91e856471dc1d10fd64758c05f02 \
--hash=sha256:8929134acf4ff967203df7600b38535f9b5b590462067a7e30dbce01acb97af9 \
--hash=sha256:8e55f36a7f52ba0976dd72100273523908b16fb9dda6ce96d9aa9df9cceed4cc \
--hash=sha256:9008ef25cabd379cda4fa86000fc38ca14b72afe17db798a8c85c0b2b7ce4d1e \
--hash=sha256:93d2d87acf75d60c5a2b8c5c8a45aff17bbbd00c17bdccb4ba013d3ab590a65f \
--hash=sha256:9693d2c9e226b2ea855c1dc3a87e2f4d972f7523fc0f7924e5997e9f4c23d97f \
--hash=sha256:9b6d234f1f91335e9f01d9daac42ddc2d2e5c2fdbe285d8eeef50353b283648a \
--hash=sha256:9f3c98f5fa90a842c1f545463834f712aa2eed785fc3d5e42836c0df2d691bb6 \
--hash=sha256:9fc113c1348c7eb22c4e8790f68b562bb4f42a721fafb813e89a57e9cd632040 \
--hash=sha256:a5549f4afc973e0a15ea6b0244d57f848d3f3ee13dac557eb311024aebebf128 \
--hash=sha256:a6620963196c49212c511cd909f367dacf771f199a27d116f357cc671ea956c7 \
--hash=sha256:a70477c991e96cfe8fdd7c866e7251faf71b38bfeb51d6f27554c9cce1caabf3 \
--hash=sha256:ade416833214f3ee13af9b0199fff4de00fa6e0fde3deced776532fd91df5515 \
--hash=sha256:c65230abaa52c72093b70d3b499d5689d1d8f9627e88ad3c3b4f8154e86ac0c8 \
--hash=sha256:ccc58f26a1b94bef255316311678b03854f7192069bdf11cf501a6c85f61b83c \
--hash=sha256:d33bae2b3292ed0235522682316251658187f43b1ebad6cd2b127069b94afb3b \
--hash=sha256:d46e35c473646efb3b2ff8032f37ac5b6d48da52a669577187d3796a6d5987a3 \
--hash=sha256:d8f7ea8f47e38e5b0de3fab97e07e9c9161ffddc90b3964512cab2b7749df4e6 \
--hash=sha256:dd9569bc26174a3437d749114d36c81fc6c7478b55370ae50125e34e9629e4fe \
--hash=sha256:df4ac3746099562c8160d78d55bb2fa10c9ada7ef970af3e2536bd133cb7830e \
--hash=sha256:e128cddeccae4146b556684a0d5426454fff5bfe7306862e5a8ce6b471568af4 \
--hash=sha256:fa37da7ad16c47391016b5f984fb60e175e1ab0b478f04920fd6d1f61123ce4d
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
@ -971,25 +971,19 @@ gunicorn==26.0.0 \
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
icalendar==7.1.0 \
--hash=sha256:10cd223c792fcc43bee4c3ebe3149d4cf32406c85cfef146624df5a0d414260f \
--hash=sha256:6de875370d22fc4aff172ad7c439b39fb109dc2eab9ce358fcb95e8689ad7b56
icalendar==7.1.2 \
--hash=sha256:01c76243c76c549f58bb51510a8f0a4edb7c539726adda1356dfd0dc04fb7a53 \
--hash=sha256:ebc43ebeb357be98984b573d975118008dee3410d8df28b054ef2943cf3e367e
# via
# -c src/backend/requirements.txt
# django-ical
idna==3.15 \
--hash=sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8 \
--hash=sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc
idna==3.16 \
--hash=sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5 \
--hash=sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d
# via
# -c src/backend/requirements.txt
# django-anymail
# requests
importlib-metadata==8.7.1 \
--hash=sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb \
--hash=sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151
# via
# -c src/backend/requirements.txt
# opentelemetry-api
inflection==0.5.1 \
--hash=sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417 \
--hash=sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2
@ -1014,6 +1008,12 @@ jinja2==3.1.6 \
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
jinxed==2.0.4 \
--hash=sha256:58ae0a4a2930b51e9de162208c356b382f5e25b3bc0ad73e2013b02a62d84e8b \
--hash=sha256:a92ac48923433c0a88577bb0479191788813fd5055ef17ff2b04a701a1b83f19
# via
# -c src/backend/requirements.txt
# blessed
jmespath==1.1.0 \
--hash=sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d \
--hash=sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64
@ -1039,141 +1039,141 @@ jwcrypto==1.5.7 \
# via
# -c src/backend/requirements.txt
# django-oauth-toolkit
lxml==6.1.0 \
--hash=sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2 \
--hash=sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773 \
--hash=sha256:045e387d1f4f42a418380930fa3f45c73c9b392faf67e495e58902e68e8f44a7 \
--hash=sha256:05b9b8787e35bec69e68daf4952b2e6dfcfb0db7ecf1a06f8cdfbbac4eb71aad \
--hash=sha256:07f98f5496f96bf724b1e3c933c107f0cbf2745db18c03d2e13a291c3afd2635 \
--hash=sha256:08950a23f296b3f83521577274e3d3b0f3d739bf2e68d01a752e4288bc50d286 \
--hash=sha256:0d082495c5fcf426e425a6e28daaba1fcb6d8f854a4ff01effb1f1f381203eb9 \
--hash=sha256:0f0f08beb0182e3e9a86fae124b3c47a7b41b7b69b225e1377db983802404e54 \
--hash=sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c \
--hash=sha256:11a873c77a181b4fef9c2e357d08ed399542c2af1390101da66720a19c7c9618 \
--hash=sha256:183bfb45a493081943be7ea2b5adfc2b611e1cf377cefa8b8a8be404f45ef9a7 \
--hash=sha256:19f4164243fc206d12ed3d866e80e74f5bc3627966520da1a5f97e42c32a3f39 \
--hash=sha256:1ae225f66e5938f4fa29d37e009a3bb3b13032ac57eb4eb42afa44f6e4054e69 \
--hash=sha256:1bc4cc83fb7f66ffb16f74d6dd0162e144333fc36ebcce32246f80c8735b2551 \
--hash=sha256:1dd6a1c3ad4cb674f44525d9957f3e9c209bb6dd9213245195167a281fcc2bdc \
--hash=sha256:20cf4d0651987c906a2f5cba4e3a8d6ba4bfdf973cfe2a96c0d6053888ea2ecd \
--hash=sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd \
--hash=sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405 \
--hash=sha256:23a5dc68e08ed13331d61815c08f260f46b4a60fdd1640bbeb82cf89a9d90289 \
--hash=sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b \
--hash=sha256:2593a0a6621545b9095b71ad74ed4226eba438a7d9fc3712a99bdb15508cf93a \
--hash=sha256:264c605ab9c0e4aa1a679636f4582c4d3313700009fac3ec9c3412ed0d8f3e1d \
--hash=sha256:26c5272c6a4bf4cf32d3f5a7890c942b0e04438691157d341616d02cca74d4bd \
--hash=sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac \
--hash=sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62 \
--hash=sha256:29f5c00cb7d752bce2c70ebd2d31b0a42f9499ffdd3ecb2f31a5b73ee43031ad \
--hash=sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f \
--hash=sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad \
--hash=sha256:363e47283bde87051b821826e71dde47f107e08614e1aa312ba0c5711e77738c \
--hash=sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292 \
--hash=sha256:37448bf9c7d7adfc5254763901e2bbd6bb876228dfc1fc7f66e58c06368a7544 \
--hash=sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491 \
--hash=sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120 \
--hash=sha256:3ae5d8d5427f3cc317e7950f2da7ad276df0cfa37b8de2f5658959e618ea8512 \
--hash=sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb \
--hash=sha256:40d9189f80075f2e1f88db21ef815a2b17b28adf8e50aaf5c789bfe737027f32 \
--hash=sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2 \
--hash=sha256:41dcc4c7b10484257cbd6c37b83ddb26df2b0e5aff5ac00d095689015af868ec \
--hash=sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88 \
--hash=sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43 \
--hash=sha256:4642e04449a1e164b5ff71ffd901ddb772dfabf5c9adf1b7be5dffe1212bc037 \
--hash=sha256:468479e52ecf3ec23799c863336d02c05fc2f7ffd1a1424eeeb9a28d4eb69d13 \
--hash=sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03 \
--hash=sha256:481d6e2104285d9add34f41b42b247b76b61c5b5c26c303c2e9707bbf8bd9a64 \
--hash=sha256:4937460dc5df0cdd2f06a86c285c28afda06aefa3af949f9477d3e8df430c485 \
--hash=sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16 \
--hash=sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb \
--hash=sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9 \
--hash=sha256:4e2c54d6b47361d0f1d3bc8d4e082ad87201e56ccdcca4d3b9ee3644ff595ec8 \
--hash=sha256:52b0ac6903cf74ebf997eb8c682d2fbac7d1ab7e4c552413eec55868a9b73f39 \
--hash=sha256:546b66c0dd1bb8d9fa89d7123e5fa19a8aff3a1f2141eb22df96112afb17b842 \
--hash=sha256:56971379bc5ee8037c5a0f09fa88f66cdb7d37c3e38af3e45cf539f41131ac1f \
--hash=sha256:5715e0e28736a070f3f34a7ccc09e2fdcba0e3060abbcf61a1a5718ff6d6b105 \
--hash=sha256:5cfa1a34df366d9dc0d5eaf420f4cf2bb1e1bebe1066d1c2fc28c179f8a4004c \
--hash=sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7 \
--hash=sha256:6262b87f9e5c1e5fe501d6c153247289af42eb44ad7660b9b3de17baaf92d6f6 \
--hash=sha256:63aeafc26aac0be8aff14af7871249e87ea1319be92090bfd632ec68e03b16a5 \
--hash=sha256:690022c7fae793b0489aa68a658822cea83e0d5933781811cabbf5ea3bcfe73d \
--hash=sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9 \
--hash=sha256:73becf6d8c81d4c76b1014dbd3584cb26d904492dcf73ca85dc8bff08dcd6d2d \
--hash=sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb \
--hash=sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f \
--hash=sha256:76b958b4ea3104483c20f74866d55aa056546e15ebe83dd7aecd63698f43b755 \
--hash=sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb \
--hash=sha256:7ba11752e346bd804ea312ec2eea2532dfa8b8d3261d81a32ef9e6ab16256280 \
--hash=sha256:7da13bb6fbadfafb474e0226a30570a3445cfd47c86296f2446dafbd77079ace \
--hash=sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e \
--hash=sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33 \
--hash=sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5 \
--hash=sha256:81ff55c70b67d19d52b6fd118a114c0a4c97d799cd3089ff9bd9e2ff4b414ee2 \
--hash=sha256:857efde87d365706590847b916baff69c0bc9252dc5af030e378c9800c0b10e3 \
--hash=sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585 \
--hash=sha256:8c11b984b5ce6add4dccc7144c7be5d364d298f15b0c6a57da1991baedc750ce \
--hash=sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946 \
--hash=sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e \
--hash=sha256:9147d8e386ec3b82c3b15d88927f734f565b0aaadef7def562b853adca45784a \
--hash=sha256:920354904d1cb86577d4b3cfe2830c2dbe81d6f4449e57ada428f1609b5985f7 \
--hash=sha256:942454ff253da14218f972b23dc72fa4edf6c943f37edd19cd697618b626fac5 \
--hash=sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28 \
--hash=sha256:976a6b39b1b13e8c354ad8d3f261f3a4ac6609518af91bdb5094760a08f132c4 \
--hash=sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c \
--hash=sha256:9c03e048b6ce8e77b09c734e931584894ecd58d08296804ca2d0b184c933ce50 \
--hash=sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9 \
--hash=sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495 \
--hash=sha256:9f93d5b8b07f73e8c77e3c6556a3db269918390c804b5e5fcdd4858232cc8f16 \
--hash=sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45 \
--hash=sha256:a02ca8fe48815bddcfca3248efe54451abb9dbf2f7d1c5744c8aa4142d476919 \
--hash=sha256:a1d9b99e5b2597e4f5aed2484fef835256fa1b68a19e4265c97628ef4bf8bcf4 \
--hash=sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc \
--hash=sha256:a31286dbb5e74c8e9a5344465b77ab4c5bd511a253b355b5ca2fae7e579fafec \
--hash=sha256:a86f06f059e22a0d574990ee2df24ede03f7f3c68c1336293eee9536c4c776cd \
--hash=sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8 \
--hash=sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f \
--hash=sha256:b6c2f225662bc5ad416bdd06f72ca301b31b39ce4261f0e0097017fc2891b940 \
--hash=sha256:bb40648d96157f9081886defe13eac99253e663be969ff938a9289eff6e47b72 \
--hash=sha256:bba078de0031c219e5dd06cf3e6bf8fb8e6e64a77819b358f53bb132e3e03366 \
--hash=sha256:bc783ee3147e60a25aa0445ea82b3e8aabb83b240f2b95d32cb75587ff781814 \
--hash=sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690 \
--hash=sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13 \
--hash=sha256:c08da09dc003c9e8c70e06b53a11db6fb3b250c21c4236b03c7d7b443c318e7a \
--hash=sha256:c3592631e652afa34999a088f98ba7dfc7d6aff0d535c410bea77a71743f3819 \
--hash=sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c \
--hash=sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86 \
--hash=sha256:c53fa3a5a52122d590e847a57ccf955557b9634a7f99ff5a35131321b0a85317 \
--hash=sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180 \
--hash=sha256:c748ebcb6877de89f48ab90ca96642ac458fff5dec291a2b9337cd4d0934e383 \
--hash=sha256:c871299c595ee004d186f61840f0bfc4941aa3f17c8ba4a565ead7e4f4f820ee \
--hash=sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe \
--hash=sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181 \
--hash=sha256:cec05be8c876f92a5aa07b01d60bbb4d11cfbdd654cad0561c0d7b5c043a61b9 \
--hash=sha256:d036ee7b99d5148072ac7c9b847193decdfeac633db350363f7bce4fff108f0e \
--hash=sha256:d0d799ff958655781296ec870d5e2448e75150da2b3d07f13ff5b0c2c35beefd \
--hash=sha256:d1392c569c032f78a11a25d1de1c43fff13294c793b39e19d84fade3045cbbc3 \
--hash=sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d \
--hash=sha256:d3829a6e6fd550a219564912d4002c537f65da4c6ae4e093cc34462f4fa027ad \
--hash=sha256:d43aa26dcda363f21e79afa0668f5029ed7394b3bb8c92a6927a3d34e8b610ea \
--hash=sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24 \
--hash=sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d \
--hash=sha256:db88156fcf544cdbf0d95588051515cfdfd4c876fc66444eb98bceb5d6db76de \
--hash=sha256:de550d129f18d8ab819651ffe4f38b1b713c7e116707de3c0c6400d0ef34fbc1 \
--hash=sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d \
--hash=sha256:e3c4f84b24a1fcba435157d111c4b755099c6ff00a3daee1ad281817de75ed11 \
--hash=sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9 \
--hash=sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2 \
--hash=sha256:e80807d72f96b96ad5588cb85c75616e4f2795a7737d4630784c51497beb7776 \
--hash=sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f \
--hash=sha256:f0cea5b1d3e6e77d71bd2b9972eb2446221a69dc52bb0b9c3c6f6e5700592d93 \
--hash=sha256:f15401d8d3dbf239e23c818afc10c7207f7b95f9a307e092122b6f86dd43209a \
--hash=sha256:f504d861d9f2a8f94020130adac88d66de93841707a23a86244263d1e54682f5 \
--hash=sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d \
--hash=sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d \
--hash=sha256:fcf3da95e93349e0647d48d4b36a12783105bcc74cb0c416952f9988410846a3 \
--hash=sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086 \
--hash=sha256:ffb34ea45a82dd637c2c97ae1bbb920850c1e59bcae79ce1c15af531d83e7215
lxml==6.1.1 \
--hash=sha256:05a82eb6e1530a64f26225b55cbd178113bd0b5af1c2b625f25e5296742c26d2 \
--hash=sha256:07a4a68e286ee7a1ed7dfb8af83e615757c0ccfe9f18c6b4ea6771388d9ba8c9 \
--hash=sha256:09dd5b7075dc2f7709654a46543ba1ea3c2e217b2ed8fbd413a8a945a0f40f60 \
--hash=sha256:0b7e8a14c8634bf6f7a568634cb395305a6d964aeb5b7ee32248094bed3a7e2c \
--hash=sha256:104c09bda8d2a562824c0e319d0768ce26a779b7601e0931d33b09b53c392ef7 \
--hash=sha256:126c93f7f56f0eda92f6d8c619edc463a4f23d9252f1c9d0405a76f25fa9f11a \
--hash=sha256:162af1091cd785f2f27e62d3547ae9bc58ec5c86dd314d67021fd02463708d83 \
--hash=sha256:17e0e18d4ad8adbd0399291bc44845b69d9dd68439a3cdebdf35ff902ec05072 \
--hash=sha256:18b73c339ae29b90fd2d06e58ebd555a751bde9cd6bbd36cc0281b9a2c94e9d8 \
--hash=sha256:19607c6bbff2a44cf3fe8250abccd20942d3462473e0a721d01d379ed017e462 \
--hash=sha256:19b7ab10b210b0b3ad7985d9ac4eb66ab09a90b20fe6e2f7ba55d01a234345d0 \
--hash=sha256:1d4962d4c66bf830a7e59ed6cfc17d148149898a3aefa8ec6e59763e6e3ed085 \
--hash=sha256:1db753c9115ec7100d073b744d17e25e88a8f90f5c39b2f5dd878149af59671f \
--hash=sha256:1dde6131244bba38a17c745836ba190bc753fd73c9291666287fd0a3fa3dcf30 \
--hash=sha256:25c6997a9a534e016695a0ba06b2f07945de682731ff01065b6d5a4474179da1 \
--hash=sha256:26e6eda8d38c1fcab1090dd196ee87cbd13788e531937610e2589085de074e77 \
--hash=sha256:27acc820660aaffa4f7c087f29120e12980f7779d56d8492d263170111284740 \
--hash=sha256:2a0217714657e023ef4293500f65aa20fce6164c8fd6b08fa5bd4a859fb14b9b \
--hash=sha256:2c8daa471358dc2d6fcf02165e80ec68f77871a286df95bc5cc3816153b0fd2c \
--hash=sha256:30a89d3ac8faec007453fb541f3f46807eeec88edd5826f6e3fe001752a2c621 \
--hash=sha256:31033dc34636ea6b7d5cc11b1ddbda78a14de858ba9d3e1ed4b69a3085bc521e \
--hash=sha256:32ab449a5486f6c758e849bb86710d0e45edc24a04e250c01555f8f5653958f8 \
--hash=sha256:3483644525531e1d5762b0c44a8e18b6efba321b6dcf8a8952de10b037618bca \
--hash=sha256:34c2d737beabfe35baada43941ed519251e9a12e779031496bcd5d539fcfd730 \
--hash=sha256:3779def59032b81e44a5f70096ef6bf2082f8d901937dca354474ba09782e245 \
--hash=sha256:37a58976370f36d9329d118ad0b953c5aeb9119ac9c6a4e258942a225d0573a1 \
--hash=sha256:3893c14c4b6ac5b2d54ba8cf03e99fe5104e592de491f19bd6b82756c09f8004 \
--hash=sha256:3a12689be69a28ddaa0ab99a5a1137da2afd5f8f16df7b5680b66f616d3eda1d \
--hash=sha256:3ab541146f1f6968c462d6c2ac495148e8cdba2f8347700b2141b6ec5a75bf52 \
--hash=sha256:3abf332af33a74288675d936fe861fd4344da0dd6622193fbc4f2bfbb35536b5 \
--hash=sha256:3fd9728a2735fda14f4e8235830c86b539e9661e849665bf926d3f867943b4bf \
--hash=sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc \
--hash=sha256:441dd227fa0690eb9fc81edabc63cdcefc212bba99b906dcf6e32cc1a9d3e533 \
--hash=sha256:469e3618338bd7ab5beb412d2439825479fcf0dab99e394ca563dbc4eaf6c834 \
--hash=sha256:47402e62c52ff5988c1e8c6c63177f5708bccf48e366dea4e3dcf1e645e04947 \
--hash=sha256:4f0dd2f01f9f8a89f565d000e03abcf0a13d692a346c8d22f628d49af098777a \
--hash=sha256:53b7d2b7a10b1c35c0a5e21e9224accf60c1bbfba523990732e521b2b73adef2 \
--hash=sha256:53c909b62a0532183542fed00c5a7218258c56292d409bc789886fe1cb04c438 \
--hash=sha256:54a7f95e4de5fb94e2f9f4b9055c6ba33bf3d628fd77a1d647c5923caa2cdcdc \
--hash=sha256:556e94a63c9b04716f8e4de2abb65775061f846e89331b6c5be79183a24f98ea \
--hash=sha256:55b03549819867ea141c0202242c4816c82e52ec36e7e648db9d8da5a3dc3ed6 \
--hash=sha256:581d4c8ae690a6609e64862dd6b7c2489635c2d13907fc2b20f2bc200ff1d21e \
--hash=sha256:58bb955caba94e467d2a96da17660d2d704e0675894cba21ab8a775b8621fd1c \
--hash=sha256:5b7328b46d49fc9477d91ae8f6d55340347d827b7734ba3ea33faae0efef1383 \
--hash=sha256:5ba186ad207446c65d3bb3d3e0412b032b1d9f595e59861e2354798c5703d955 \
--hash=sha256:5bec7d03d78d853597d6107854c2310ce3f761fd218fe9fe91d5101fcf6c2efe \
--hash=sha256:5c6bf403fbb3b3e348a561a5f4f0b9961835657981c802a1df03653eef8a9074 \
--hash=sha256:5f6994074ebae6ffb04447268e37dc16edc304f9859cf91acb86e0af6c1b395c \
--hash=sha256:62aeb7e85b5d60320b9d77eef2e773994e2c0ce10121b277e0a19804e1654a5a \
--hash=sha256:63876be28efefa04a1df615b46770e82042cce445cfdce55160522f57b231ccb \
--hash=sha256:639f6c857d91d9be29bd7502348d6736dab168b54b5158cd899abf11684dc186 \
--hash=sha256:640f97d43d867bcb9c75b3af013b64850756b746cb6bce8ace83b70da3abba9d \
--hash=sha256:649dda677cf3bd6ac9ae14007ba0c824ded8ce5808b53fc7431d9140399118c1 \
--hash=sha256:6540377fbd53fe1b629172288c464fb18db11ce1fa7dc15891da10aa9dcc3e7f \
--hash=sha256:6689e828a94eee4f139408c337bb198e014724bb8a8c26d3cfac49d119ed69a6 \
--hash=sha256:68a9198d0fc122d14bb76837de9aa80cf84caed990b5b237f532ed87d3706736 \
--hash=sha256:6b1761fbf9ec984e2e9d9c589ef5f5fd684b7c19f92aadd567a26c5224958db6 \
--hash=sha256:70cdfd80589d59e43e18005dd7244e8895e93db8ab6a620b7e23df5445a4e3d2 \
--hash=sha256:70ef8a7e102a1508f8121aae5b0867abd663f72c14f0a9c937e6554cb4587b7b \
--hash=sha256:73bc2086f141224ebddb7fc5c6a36ca58b31b94b561e1dfe8e073e3270fad1e7 \
--hash=sha256:74a9717fd0d82effef5c2854f0d917231d5324b5a3eb7275c43ac9fa32f97a14 \
--hash=sha256:752d3bbfe874715ccd0aec7f88d7fc623c0f1fd7aa7b3238a084e017bad2a009 \
--hash=sha256:762ff394d5bd56da0cf034a23dcce4e13923f15321a2adfa2ac00201dc6d3fca \
--hash=sha256:76447f65250ed2501ead1a1552f5ce8edff159a86f308348e6a9c4acb5e1f1b4 \
--hash=sha256:766b010012d59470072c1816b5b6c69f1d243e5db36ea5968e94accf430a4635 \
--hash=sha256:787b2496d0dbe8cd180984e8d29e3a6f76e7ea34db781cb3bd55e4ba1ef8b4ee \
--hash=sha256:793033d6c5cdf33a573f910d9bea14ef8f5771820411d118da8e1182edb53d5e \
--hash=sha256:7d47866cb32fb503450b6edc9df355d10dc49836af2e89901bd6ac6b0896d9d9 \
--hash=sha256:7f7a92e8583f06b1fd49d01158143b8461cfcd135dcb10ec807270a3051bd603 \
--hash=sha256:80c2dfadb855da477cf73373ad29a333535dedb9b12bad02c9814c8e2b43bf08 \
--hash=sha256:83b6b30eb131da7a75b601f28c5d6971e6ed3e887919bf6b6a1ad3c2df289080 \
--hash=sha256:86281fbdd6a8162756f8d603f37e3435bfa38043adb79c6dc6a2dfee065e7525 \
--hash=sha256:86c89b9d55ebf820ad7c90bc533410f0d098054f293351f10603c0c46ff598f5 \
--hash=sha256:876e1ff5930ed8bf295ec5ef9a8155e9b6b1876bbf1deed8b3a8069311875a8f \
--hash=sha256:88136950da4d13c318bde414ce10219931937851327f44328f2df4d2c4614067 \
--hash=sha256:88d8cb75b9d82858497a5393e3c63cfbf03035225e4b35a49ed7ccb151e4dc0e \
--hash=sha256:8be8ad51249698103d24b0571df35a10990fbe93dd043b6c024172189485f5e3 \
--hash=sha256:8d43ca737b20e106e4aebc42b2f3ae19f00ba63d7eb731698ee083d72d15646f \
--hash=sha256:8dadbe5b217ff35b6a8d16610dd710219b59b76d13f0e3f0d9f36786206e4485 \
--hash=sha256:9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13 \
--hash=sha256:96f2ec43df44b1f76249ee0a615334f9b5b060e1c8bd90e706dad2d14d02f383 \
--hash=sha256:98fc784c2c1440667aeedf8465bdfe10208acf0ead656a2c68627299f546b315 \
--hash=sha256:9e36f163528fc50cbef305f02a5fd66d404edf7049cdaff211dbc2cba5a7013e \
--hash=sha256:9eb9b5a968f6e0f6d640092a567e14529ff8cea2e29d00da6f78a79fa49f013c \
--hash=sha256:9f76acfb5f68ba982635a53fd985a8044be98a35b43232c2a1ee235ffab3e1dd \
--hash=sha256:a088f287f7d8275a33c07f2cac6c50b9319309a0200a39e7e75d80c707723099 \
--hash=sha256:a10bd2fd62e8ce916ececb342f348f190724a098c1faa056fdfb2a22ad5e8660 \
--hash=sha256:a4bbea04c97f6d78a48e3fbc1cb9116d2780b1b39e03a23f6eb9b603fd61f510 \
--hash=sha256:aa366a1e55b8ebfe8ca8ddc3cfe75c8ebade181aeb0f661d0cb05986b647f72a \
--hash=sha256:aa49e06d94aba782c6a02eecb7e507969e7e7a41b267f1b359bb35585f295d5b \
--hash=sha256:aad9aa39483ed8ec44d6d2e59e5b98a0d80676ef0d92f44bfc374836111f62f5 \
--hash=sha256:aae97dfdb60715c164419ac2532a76d013c3918a665eb6cb7288098b5f349aaf \
--hash=sha256:abbefa31eee84842140f67acef1c828e28bba8bbf0c3bc6e5492a9af88152c28 \
--hash=sha256:ac931cdc9442c1763b8a8f6cd62c0c938737eafc5be75eff88df55fc73bc0d00 \
--hash=sha256:acd7d70b64c0aae0c7922cca83d288a16f5f6da523637697872253415269baef \
--hash=sha256:add8cf6ddf9a65116119a28ece0f7886e30af27ba724a7594305f1d1b58a92a1 \
--hash=sha256:aee395f5d0927f947758b4ec119fd5fc8ec71f07a1c5c52077b30b04c0fa6955 \
--hash=sha256:b1b963fd8f5caa68e99dfae060d54de1fe9cba899b8718b44a00cdca53c3e590 \
--hash=sha256:b2d444f2e66624d68e9c6b211e28a76e22fff5fcabcfff4deac18b529b7d4137 \
--hash=sha256:b8d812c6011c08b8111a15e54dd990b8923692d80adf35488bee34026c35accf \
--hash=sha256:ba96ae44888e0185281e937633a743ea90d5a196c6000f82565ebb0580012d40 \
--hash=sha256:bdebcc8a75d38c7598dfb2c9ed852d7a9eb4a10d6e2d0764b919b802bf32ac88 \
--hash=sha256:c07da4cebf6889f03ebac8d238f62318e29f495de0aa18a51ea14e61ae907e2e \
--hash=sha256:c08e5c694306507275f2290073350c4f32e383db15213b2c69e7ff39c1193840 \
--hash=sha256:c4f469aebd783bb741c2ecb2a681008fd26bfe5c16a9a72ed5467f834e810df2 \
--hash=sha256:c5d7152ec39ca7c402d8fb9bad86140a15b9503bd0c54484e3f1bbe3dd37ceca \
--hash=sha256:c674693f055fa2495de12292cb45e9944199d8eaef5a2dec45175c7c61cb73e3 \
--hash=sha256:c6ed5141a5c7507cf3ee76bd363b0d6f801e3321adc35b5d825a23115faa5465 \
--hash=sha256:c921ba5c51e4e9f63b8b00267d06566e1f63407408a0496da2d1d0bfc819c7fc \
--hash=sha256:c9a4b821dc7055bf9e05ff5719e18ec501f75c0f0bbfabd573b277559780833d \
--hash=sha256:c9f79d5325907f13e1be0b3e4dacc1049d1dffc4aeee3c995284bea5fe0fab7d \
--hash=sha256:cd312b9692e831d2ffcad61eab31d91d4b4655a962e61de8fb410472cbcd37aa \
--hash=sha256:cea3f4c1af79af13cdb2da0c028111d8f8522d4f22a000c82385535f24e5cf3a \
--hash=sha256:cecdd5dfdc87b1fd87dbf81d4b037a544f47f4c744200a67013771682d67686a \
--hash=sha256:cf9d57306d848218f3601fee7601fab1a327c942d56e2e97610583cb4dd74206 \
--hash=sha256:d34bbf07dbc7ca5970671b1512e928991fb5e9d95365636c9b2d8b4f53af405e \
--hash=sha256:d49514be2f28d895c38cf9d2b72d7b9a07d00314519f456c0b50b53cfcf4c785 \
--hash=sha256:d680fbcb768404c601ecb43519ecd8461f6954cb11c06a78962f666832ccfca8 \
--hash=sha256:db1d75f6617a49c1c01bc7023713e0ff59ab32c9579ae62a7674c0e34f3b0b0a \
--hash=sha256:dcb292aa7fe485ceff7af4f92e46c5af397daec5dff64871a528f0fc47a3cc5b \
--hash=sha256:e07c65f443c887bbcf31cc1771d932ecc192a5273943589b3c7572b749f1ffb2 \
--hash=sha256:e902da4b04e6b52e5893900d4b8ab46068f75f3561f01bf1080957f9fd932ed6 \
--hash=sha256:e9308ff8241c532df3f3e570f9a5aeed6c853f888512ba4b75638d7c11c95ef6 \
--hash=sha256:eb7c9811bfaa8b1ed5ed319f5d370dfbcaa59d52ea64be2a5a85e18195930354 \
--hash=sha256:ebe6af670449830d6d9b752c256a983291c766a1365ba5d5460048f9e33a7818 \
--hash=sha256:ed21202aec73cda4d55d1ce57b389aadb90ffb044e6cd1080b8347efe1b1ec84 \
--hash=sha256:efe0374196335f93b53269acd811b944f2e6bdc88e8894f214bd636455484909 \
--hash=sha256:f64ec5397ea6a41fc1b4af0380d79b44a755b5531dcaccd9940fb260dca93038 \
--hash=sha256:f6ac4ef4d82dff54670227a69c67782ae0b811b5cf6b17954f1e8f7502fc0d1d \
--hash=sha256:f6f0ce10945fab9c4c06ce14e22af9059d1a87493a9af4501a5b0b9187e21cf2 \
--hash=sha256:f8844cd288697c6425c9beba919302241e3278871dc6519515e72b04e987abcf \
--hash=sha256:fe0306bd29505a9177aac19f1877174b0e7422c222a59f70b2cd41633448c3dc \
--hash=sha256:ff3f333630ab480244a1bff72043e511a91eb22e7595dead8653ee5612dd8f3d \
--hash=sha256:ffecec8eb889b58ba9be5b95fb1cc78e22ea8eedea38e8736a1568fe1979250e
# via
# -c src/backend/requirements.txt
# python3-saml
@ -1321,9 +1321,9 @@ openpyxl==3.1.5 \
# via
# -c src/backend/requirements.txt
# tablib
opentelemetry-api==1.41.1 \
--hash=sha256:0ad1814d73b875f84494387dae86ce0b12c68556331ce6ce8fe789197c949621 \
--hash=sha256:a22df900e75c76dc08440710e51f52f1aa6b451b429298896023e60db5b3139f
opentelemetry-api==1.42.1 \
--hash=sha256:51a69edacadbc03a8950ace1c4c21099cacc538820ac2c9e36277e78cebba714 \
--hash=sha256:56c63bea9f77b62856be8c47600474acad853b2924b99b1687c4cb6297166716
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
@ -1341,34 +1341,34 @@ opentelemetry-api==1.41.1 \
# opentelemetry-instrumentation-wsgi
# opentelemetry-sdk
# opentelemetry-semantic-conventions
opentelemetry-exporter-otlp==1.41.1 \
--hash=sha256:299a2f0541ca175df186f5ac58fd5db177ba1e9b72b0826049062f750d55b47f \
--hash=sha256:db276c5a80c02b063994e80950d00ca1bfddcf6520f608335b7dc2db0c0eb9c6
opentelemetry-exporter-otlp==1.42.1 \
--hash=sha256:2d9ebaed714377a67d224d46795ddcc11d2c877fa5de35fda70b6f3b010729a9 \
--hash=sha256:aedd54545bb0587cd45210abdc8be545af9c01413f3307786e276df1e3c83bee
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
opentelemetry-exporter-otlp-proto-common==1.41.1 \
--hash=sha256:0e253156ea9c36b0bd3d2440c5c9ba7dd1f3fb64ba7a08fc85fbac536b56e1fb \
--hash=sha256:10da74dad6a49344b9b7b21b6182e3060373a235fde1528616d5f01f92e66aa9
opentelemetry-exporter-otlp-proto-common==1.42.1 \
--hash=sha256:04f1f01fb597c4249dfcd7f8b861c902c2102369d376d9d346ff38de4469a2ee \
--hash=sha256:f48d395ab815b444da118868977e9798ea354c25737d5cf39578ae894011c140
# via
# -c src/backend/requirements.txt
# opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http
opentelemetry-exporter-otlp-proto-grpc==1.41.1 \
--hash=sha256:537926dcef951136992479af1d9cd88f25e33d56c530e9f020ed57774dca2f94 \
--hash=sha256:b05df8fa1333dc9a3fda36b676b96b5095ab6016d3f0c3296d430d629ba1443b
opentelemetry-exporter-otlp-proto-grpc==1.42.1 \
--hash=sha256:0ae1177e2038b18a929b3098215243631ef91136cba26b7e2b12790ceb7e87cc \
--hash=sha256:975c4461f167dd8ed8857d68d3b6b25f3d272eab896f6a9470d0f5b90e2faf15
# via
# -c src/backend/requirements.txt
# opentelemetry-exporter-otlp
opentelemetry-exporter-otlp-proto-http==1.41.1 \
--hash=sha256:1a21e8f49c7a946d935551e90947d6c3eb39236723c6624401da0f33d68edcb4 \
--hash=sha256:4747a9604c8550ab38c6fd6180e2fcb80de3267060bef2c306bad3cb443302bc
opentelemetry-exporter-otlp-proto-http==1.42.1 \
--hash=sha256:00a16da1b312a1d6c7233d600d557c91df71125af73020f3b9a7765bd699d59d \
--hash=sha256:bf142a21035d7571ac3a09cb2e5639f49886f243972883cfe777ed3bf02b734d
# via
# -c src/backend/requirements.txt
# opentelemetry-exporter-otlp
opentelemetry-instrumentation==0.62b1 \
--hash=sha256:90e92a905ba4f84db06ac3aec96701df6c079b2d66e9379f8739f0a1bdcc7f45 \
--hash=sha256:976fc6e640f2006599e97429c949e622c108d0c17c2059347d1e6c93c707f257
opentelemetry-instrumentation==0.63b1 \
--hash=sha256:32368d6ae52c8de20aa790a6ad86b10a76f09956092337ae37d675773990e541 \
--hash=sha256:f1986716d52cc316ea5f60189098726a9071d8ecc0eee96c9ed110be08bade9c
# via
# -c src/backend/requirements.txt
# opentelemetry-instrumentation-dbapi
@ -1380,82 +1380,82 @@ opentelemetry-instrumentation==0.62b1 \
# opentelemetry-instrumentation-sqlite3
# opentelemetry-instrumentation-system-metrics
# opentelemetry-instrumentation-wsgi
opentelemetry-instrumentation-dbapi==0.62b1 \
--hash=sha256:82755a0f013980b2c000a10452fb91ab5135b937a8afe37c45f34e62ecc7a2b8 \
--hash=sha256:dd2cc4085e832a708835218833b839a88ef0ace66851d14debefe6b2ef363a3c
opentelemetry-instrumentation-dbapi==0.63b1 \
--hash=sha256:1bc842ddcacb7ec3622ef306ffdb3c584bf97df79d381e03604ea6eb23c63781 \
--hash=sha256:406978ed56bcfc5fd246fd918e6b36d0f5de26fa396c78cf63326a7b530597c8
# via
# -c src/backend/requirements.txt
# opentelemetry-instrumentation-psycopg
# opentelemetry-instrumentation-pymysql
# opentelemetry-instrumentation-sqlite3
opentelemetry-instrumentation-django==0.62b1 \
--hash=sha256:268c9b3d234510ab835acea0524ba65361389f6e2c291b495ba8072c7bb59003 \
--hash=sha256:5ac96ef25019fbea5e2739c9b4b7ce17e4379e2b1ec9fdce6b696a5c09ceabbe
opentelemetry-instrumentation-django==0.63b1 \
--hash=sha256:909ea4afbb18f16eb811d7ac330ed0f6fda7765191d49bccb8cca38c4fbc005f \
--hash=sha256:f2071d2f92e4779c5a14dd452b0dfe426343599e6efa9d888304fb639a9f3101
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
opentelemetry-instrumentation-psycopg==0.62b1 \
--hash=sha256:61c8a1794d6e11c7bca1735c2b87ef8c04b5d7202e962409d66d204f84c01be1 \
--hash=sha256:f08e843e81656271ddc8c8194a54a5f3c8ebe0cb137e3166af9a2ff014978b5e
opentelemetry-instrumentation-psycopg==0.63b1 \
--hash=sha256:4d74ec2dda8e54ead9bd71f134af984792f77e1d8809591ae53edadddd4c8fc0 \
--hash=sha256:86819f4cccea21a47920807826495797b7261cc1893c8e855415e76fb7b8e2e8
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
opentelemetry-instrumentation-pymysql==0.62b1 \
--hash=sha256:12e42f891978a3a41bc564bb81e2299d3654659589f705064a4bb10e93e493f8 \
--hash=sha256:50d2efdde1bd8d826cbceffe5b3ce0ffd421349d609200eab0f3e632945cd304
opentelemetry-instrumentation-pymysql==0.63b1 \
--hash=sha256:2cf6a24e136b5b0c3f6e59675fc44b42ad2b97474b075a82859f9868a9252648 \
--hash=sha256:6b54b83c86ac6c2e8dae642bbee4894e2283ceb5da23d571f1abb7060f7b62f4
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
opentelemetry-instrumentation-redis==0.62b1 \
--hash=sha256:2d3c421d95e05ade075bee5becbe34e743b1cdf5bdee2085cb524f88c4f13dcb \
--hash=sha256:9aedd02c1acf631251d1d676634db47da9da04e0a626cd0c7d83fe0eb791d165
opentelemetry-instrumentation-redis==0.63b1 \
--hash=sha256:28d235159df43cc2bc8779af5c602afad1e08603fff75ac8ca34dd1bf30a9cb9 \
--hash=sha256:f0e51c4006f68e340abbf28a7995feff004de78649697cbdf3bac0072cacd082
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
opentelemetry-instrumentation-requests==0.62b1 \
--hash=sha256:67a79c4b67e2192445c1cf03d62126fa623065688d8bd1a9f87f858b0e5f0286 \
--hash=sha256:ca348f2f51b715c21e86d82106d784f7069ae849c3e636ab37e34dc0ba510b8c
opentelemetry-instrumentation-requests==0.63b1 \
--hash=sha256:513fcaa3d93debbdb359c00ce1a137a34a89ee908c51ac43beb7e8c18ac2b3cd \
--hash=sha256:935c980a11e33bfd7ed969c741e4bd7c84077045651469f10e163534368d87f7
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
opentelemetry-instrumentation-sqlite3==0.62b1 \
--hash=sha256:14ba18aed35a01e1d89bb814e49f7d6dd90bb68b2034ccf047801ca4659dba27 \
--hash=sha256:3ab0d614bab9956126ce81a22315bf58d56d653a65675a66e8b525f880ec3016
opentelemetry-instrumentation-sqlite3==0.63b1 \
--hash=sha256:3d61afda8358dc32135fabd27e5934bd25ea0eed68d64c34508f24ba8d723efc \
--hash=sha256:cc11c68cccde061dc8bcc5f80fe00bb0105a0fbb11b390859a23ca91ccd4e3b6
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
opentelemetry-instrumentation-system-metrics==0.62b1 \
--hash=sha256:260130cb63e3028290305d8f25b744fb3a279677769dd65287ce60086cc11d32 \
--hash=sha256:b8097e0d3747102fa545723bb67f50f572a7618b6479359fa6533007d2f621aa
opentelemetry-instrumentation-system-metrics==0.63b1 \
--hash=sha256:995051f47876d79461aed8b7aa205d4584d90794ef864342cc748929c389bb42 \
--hash=sha256:d6d4d7a1a854be4165143cf6420ee5894188762eb367d7bf9da5be4a83a4b632
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
opentelemetry-instrumentation-wsgi==0.62b1 \
--hash=sha256:02a364fd9c940a46b19c825c5bfe386b007d5292ef91573894164836953fe831 \
--hash=sha256:a2df11de0113f504043e2b0fa0288238a93ee49ff607bd5100cb2d3a75bc771f
opentelemetry-instrumentation-wsgi==0.63b1 \
--hash=sha256:03d61c4678ce82402e7f37b6a3dbd84cb97b85b3cb416a78c2e74c7c6d9451fa \
--hash=sha256:86779715262227d3436bdfb16aabd1c524b0f236725a69e8754ceda76c6d79dc
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
# opentelemetry-instrumentation-django
opentelemetry-proto==1.41.1 \
--hash=sha256:0496713b804d127a4147e32849fbaf5683fac8ee98550e8e7679cd706c289720 \
--hash=sha256:4b9d2eb631237ea43b80e16c073af438554e32bc7e9e3f8ca4a9582f900020e5
opentelemetry-proto==1.42.1 \
--hash=sha256:c6a51e6b4f05ae63565f3a113217f3d2bfaec68f78c02d7a6c85f9010d1cfca6 \
--hash=sha256:dedb74cba2886c59c7789b227a7a670613025a07489040050aedff6e5c0fb43c
# via
# -c src/backend/requirements.txt
# opentelemetry-exporter-otlp-proto-common
# opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http
opentelemetry-sdk==1.41.1 \
--hash=sha256:724b615e1215b5aeacda0abb8a6a8922c9a1853068948bd0bd225a56d0c792e6 \
--hash=sha256:edee379c126c1bce952b0c812b48fe8ff35b30df0eecf17e98afa4d598b7d85d
opentelemetry-sdk==1.42.1 \
--hash=sha256:083cd4bbfaa5aa7b5a9e552430d9951219967cfb27aa61feb13a77aba1fc839d \
--hash=sha256:8c834e8f8c9ba4171d4ec843d0cb8a67e4c7394d3f9e9297e582cbd9456ddbf7
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
# opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http
opentelemetry-semantic-conventions==0.62b1 \
--hash=sha256:c5cc6e04a7f8c7cdd30be2ed81499fa4e75bfbd52c9cb70d40af1f9cd3619802 \
--hash=sha256:cf506938103d331fbb78eded0d9788095f7fd59016f2bda813c3324e5a74a93c
opentelemetry-semantic-conventions==0.63b1 \
--hash=sha256:3daf963611334b365e98a57438183eb012d3bfb40b2d931a9af613476b8701a9 \
--hash=sha256:dfe5ef4dee82586b746f522b818ceb298d00b3d59f660042bd79404bff8d0682
# via
# -c src/backend/requirements.txt
# opentelemetry-instrumentation
@ -1463,11 +1463,12 @@ opentelemetry-semantic-conventions==0.62b1 \
# opentelemetry-instrumentation-django
# opentelemetry-instrumentation-redis
# opentelemetry-instrumentation-requests
# opentelemetry-instrumentation-system-metrics
# opentelemetry-instrumentation-wsgi
# opentelemetry-sdk
opentelemetry-util-http==0.62b1 \
--hash=sha256:adf6facbb89aef8f8bc566e2f04624942ba08a7b678b3479a91051a8f4dc70a3 \
--hash=sha256:c57e8a6c19fc422c288e6074e882f506f85030b69b7376182f74f9257b9261f0
opentelemetry-util-http==0.63b1 \
--hash=sha256:6284194028c59cd439f8acfe388145069a6127f11dc077e1344a2094adacc3f8 \
--hash=sha256:ba1268f00922ee522dba2ae38458060f99486e7385a8056985901ca9685adfff
# via
# -c src/backend/requirements.txt
# opentelemetry-instrumentation-django
@ -1681,9 +1682,9 @@ pydyf==0.12.1 \
# via
# -c src/backend/requirements.txt
# weasyprint
pyjwt[crypto]==2.12.1 \
--hash=sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c \
--hash=sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b
pyjwt[crypto]==2.13.0 \
--hash=sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423 \
--hash=sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728
# via
# -c src/backend/requirements.txt
# django-allauth
@ -1717,9 +1718,9 @@ pynacl==1.6.2 \
# via
# -c src/backend/requirements.txt
# paramiko
pypdf==6.11.0 \
--hash=sha256:062b51c81b0910e6d2755e99e1c5547a0a23b7d0a32322af66240d8edcfabe87 \
--hash=sha256:769394d5756d5b304c9b6bef88b54b1816b328e7e6fc9254e625529a15ed4ab8
pypdf==6.12.1 \
--hash=sha256:3953a097b9f26d4e0ead5ff95943d9971377557662a91d8872186053cd71d30a \
--hash=sha256:8fa2a2321cf16247ed848bd7c97f193a60c08670d04abed5b0138327e51c43b0
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
@ -2383,12 +2384,6 @@ xmlsec==1.3.17 \
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
# python3-saml
zipp==3.23.1 \
--hash=sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc \
--hash=sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110
# via
# -c src/backend/requirements.txt
# importlib-metadata
zopfli==0.4.1 \
--hash=sha256:02086247dd12fda929f9bfe8b3962b6bcdbfc8c82e99255aebcf367867cf0760 \
--hash=sha256:07a5cdc5d1aaa6c288c5d9f5a5383042ba743641abf8e2fd898dcad622d8a38e \

View File

@ -11,9 +11,9 @@ build==1.5.0 \
--hash=sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f \
--hash=sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647
# via pip-tools
certifi==2026.4.22 \
--hash=sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a \
--hash=sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580
certifi==2026.5.20 \
--hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \
--hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d
# via
# -c src/backend/requirements-3.14.txt
# -c src/backend/requirements.txt
@ -242,9 +242,9 @@ charset-normalizer==3.4.7 \
# -c src/backend/requirements.txt
# pdfminer-six
# requests
click==8.3.3 \
--hash=sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2 \
--hash=sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613
click==8.4.1 \
--hash=sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2 \
--hash=sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96
# via pip-tools
coverage[toml]==7.14.0 \
--hash=sha256:057a6af2f160a85384cde4ab36f0d2777bae1057bae255f95413cdd382aa5c74 \
@ -448,9 +448,9 @@ gprof2dot==2025.4.14 \
--hash=sha256:0742e4c0b4409a5e8777e739388a11e1ed3750be86895655312ea7c20bd0090e \
--hash=sha256:35743e2d2ca027bf48fa7cba37021aaf4a27beeae1ae8e05a50b55f1f921a6ce
# via django-silk
idna==3.15 \
--hash=sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8 \
--hash=sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc
idna==3.16 \
--hash=sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5 \
--hash=sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d
# via
# -c src/backend/requirements-3.14.txt
# -c src/backend/requirements.txt
@ -496,24 +496,24 @@ pluggy==1.6.0 \
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \
--hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
# via pytest
prek==0.4.0 \
--hash=sha256:01d69684306c67917ed69497d0f523216f13f2306723b62e1ace454209477b45 \
--hash=sha256:0d19efff4537e4a68b5bd61dcbf5ccb2dd8343df3ea9482aabe37d241ff18ddf \
--hash=sha256:0d9483404e5b8bf65111ee6b821e6cf7a5ba9c3a2065e7a0b7a39a17daaffcc9 \
--hash=sha256:44e3716a3e2983add2c094775d564536342011e84f770132389819c965513d6d \
--hash=sha256:473ce0262e36b8e77b327941ad6d5747ac451b4c441cdc86dab95640b68fff72 \
--hash=sha256:47f42477c8453c7440e4e656e5ab0c2a1e4c25daa5ed441a9ac1a2b7634abc12 \
--hash=sha256:6e3a0e43d7f345a5e32962736c335b5fc466d499f2556fb6f24028dd08108618 \
--hash=sha256:7203bff21e622a5482e956384c43990b0092faab02f72118275839a77a45e939 \
--hash=sha256:944670565dfb3800465355f299effa31572822566d10c6210e6f76bf399ddf53 \
--hash=sha256:a8272f32e698eae514556086ad73d02026ec00bbd4d26c420f761ea857cfd795 \
--hash=sha256:a860fda0f27f872622689358a583e5f2a5771241331848233274f4cfeb8ae9bb \
--hash=sha256:b218d92ad5d2ff0b59240d7d837fa9edc61849894958acb3a225efff7a2faa65 \
--hash=sha256:b60bd4fdc323896a5bada2708734d16ff59ee5e32b8c390ae2ebe328426964a3 \
--hash=sha256:ce2a8feb5dc1f1e748879d0e14c2145353059b830ac5e3f64d92127ab16efc78 \
--hash=sha256:e46277b577991ccdc7b13e2cfa7d260ae13e77f5af543e50e188799f649f0ad8 \
--hash=sha256:f4cba2132e038349b4b0a00a73b300e4192bae9f78fff8df0365c00bd19140e7 \
--hash=sha256:ff1f1fa311023418d40a443bca6928a9f5ce073d0b9130b641954183aa31736d
prek==0.4.1 \
--hash=sha256:051c442b570b53756225410240577bee1aeace6be52955dfacf45a9783223b36 \
--hash=sha256:10e7e78ffe65dfba7d687a8c71b2f473554d1ba60f43c742105da4c0030feed9 \
--hash=sha256:17ffa9d8dd40791b9b99cafe558c5cc28e78e5be57607b280b15f0dab90264e9 \
--hash=sha256:2ab3460641762edf128b1ec8e833ce7e9ae015d1268a894560cb90d3393a7527 \
--hash=sha256:2d1805123ab5d730629de588bf319ea39e7078b589b3288c95740f1b4780a1d4 \
--hash=sha256:5b4a348537924b20e208cbd87ef58e96ec37d691c5bec2969209c40de0ecf72e \
--hash=sha256:60928d1dad45ff3e491d3083a50643cc213aa2d54f1dbd8d702d7193773c020e \
--hash=sha256:622a8812bda87cf4ddcae2dab5ccecc55b88d70c677129dbe25e975d923179f0 \
--hash=sha256:6e69a9c02ead38706a5d2a4ae209dccba08ccb5d0026e1d08e723c66ab964750 \
--hash=sha256:76663998827a2cbc94f5e209319809655489b5bd1f8e70568a623372e80253f0 \
--hash=sha256:7c515492ef3585e6bcd7b83f1bb1cb131038abc88ed2c843de1e4c3ceb865b19 \
--hash=sha256:8fa707971465d8ad021c907e43691aad7bb98942943e61e294ece5f95d9fbc78 \
--hash=sha256:b25807e0aa57d2118747e127b58e7a1bf41d5d7b3323f5f3f1f3cb10031245cc \
--hash=sha256:c0877e82c52359d655fe1072b3a5228639184d1d5f03c6803b6530cd6da1ef20 \
--hash=sha256:cdf4503a240369f66321213d9c4bc6f925014b64ff7121de9e9920c9b9838ce2 \
--hash=sha256:dc744fedf98df8a00a9e3bcd629b163fee5e9f9e22bce66029d9945241586165 \
--hash=sha256:efa95331c4c171a867c0064c19d8a4abc94a1c1c920c8b8092f2d7d87f4b90a8
# via -r src/backend/requirements-dev.in
pycparser==3.0 \
--hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \
@ -540,36 +540,36 @@ pytest==9.0.3 \
# via
# pytest-codspeed
# pytest-django
pytest-codspeed==5.0.2 \
--hash=sha256:0621a458c52e77aa113c8d6e14037b90ce3cb5a8dd10a7656b71641999baef8c \
--hash=sha256:0fd7db3e6fb6bd28abbf0059dd54ee6233f5faf5c08597b1e9624821417e8d99 \
--hash=sha256:1b87b6a5e3c0e05ea043790aae08791dd6b3e7f487b18ec1bce145a60c78a130 \
--hash=sha256:1ee33ac4c3bd7317b6956c0b6cb250f759e02072bb14fd0324de0df71d5d488f \
--hash=sha256:22332fefae895fc80a36ac8a6d5b314663efcad9e833aed8452388441b95c50f \
--hash=sha256:33245c1fd96b1a4299604f6791e7fded376605c140ad778db7032dcd46a74d1c \
--hash=sha256:3658d3b42a15c6f40fa385629a8a8655dbedadd5d7bb5a01bc342b47f73da252 \
--hash=sha256:3799ca9e54d6958d1b388371d00f928fcc4e1e68427d312348dd413a1bba5e0b \
--hash=sha256:3a5ce30d2bcfbeb329b61f3435369720ed122caa1dd898464acbcd7edc63cf04 \
--hash=sha256:3d15eaa6ca380d0d7cb5b7b8692f362a8aac3832dff6867a0c7068fb8c7a4ef1 \
--hash=sha256:40b12cbf88eb69583d7063a4f5c986a7eed14f750a49764ef39a565ffa33d540 \
--hash=sha256:439cd9d87ad449b7db327724b8fdc4a1ae79090b166b77c4e5e15102a371f6c7 \
--hash=sha256:53473907ee2a7569b5ce6ffbfd2ba1793d284a37ff5c8670ed3149133c3ed37b \
--hash=sha256:687e5aa0fd101adbfe98f36dc253cd4e3b77d90ad96260e6e7e78bde4319c357 \
--hash=sha256:793d423dc76fd52b67495318681be18c541a7cfe30432ab2f272cd393422c56b \
--hash=sha256:7b033d25f40c47733234f29c10629f14d004540c743a5c30718e2aa768d7cbb3 \
--hash=sha256:82d3c9db57ccaef5177e1096b4dbbf8f3fde8d25c568e38d31a259474c94e5b4 \
--hash=sha256:93fea30b2d7266343dd505a182bdf1eb47f96f5fa2929f1d9aff01d3b60e1589 \
--hash=sha256:a14a6515cd315745b4b5b4739a72b287782c00a35f2927e55c310499b79d6bc2 \
--hash=sha256:a88fcddd08bdb1afe043ac4f992e032baee92c88990a611111e0c00d77927cfe \
--hash=sha256:b42d2aae3ac94192b8843fa7578eae584223bcb6334c50ca9f0e9ebafd40053b \
--hash=sha256:c20756925af58ad9d5b584d66a9b8dc709f9b243e6d8fd377e2a1b5a99bf9229 \
--hash=sha256:c2c09ec82a2def144816c6ffb311252c6ff0624189b3b5e674d889920b6d926c \
--hash=sha256:cd07e12c38f6974c969e76d070aba448c92fad66601cda4fd289afa52c81ef13 \
--hash=sha256:d223b0fe74625e633c86934a1da3ed1607f694fb3981a598bcfc02811e54808e \
--hash=sha256:d394d0d27ead72d0b00906e3832f4dcb9aadb81887a4f379c534c32c0ab965b7 \
--hash=sha256:d6d24532a8fee7018b9a33df51e1a14e27ae6b2b0772e6ad477ce5c561ab06a5 \
--hash=sha256:dedc9e4542832a3487aedf0b448217f34fdc794676b9e0daeaf408a343322c2b \
--hash=sha256:fbd1e86900e7ebbbf3cdf5a48124412d2b75283ab1378994ac27ba3308e262fc
pytest-codspeed==5.0.3 \
--hash=sha256:005348ea52ace3ede2e2f595913912ad2564cca7b124211a88dc78a9cb1fca63 \
--hash=sha256:0c383c9121deb58a69f174188e9e4488ffc0daced0ed276abf87747182511901 \
--hash=sha256:20eba63765be9d1b6cacbbfad84b87d49eb04b357a7045a0899880da181f81e3 \
--hash=sha256:25464363c7f9b9bd5022e969c0addba616fa40ac9b8f0fc9e030c4538863b32d \
--hash=sha256:2eeb25fb1ac3f73c4de50e739e78fea396b89782bdb740bf2a7cd2df21f8d4ee \
--hash=sha256:3a2097247985f5d915a94b80c5552d10979ca858c859fc3edef1bf2baa5c9b7a \
--hash=sha256:4c682f6645d4eb472f3bd95dbda1805e3af4243610572cb7d6bf94a88e8a0b6c \
--hash=sha256:51e192905a2230f9956e6160732f76577836953a4a1fb2b1e7be74e51ac7b2a0 \
--hash=sha256:6524c57fec279a22ffef6112af404036afc71b4704758ae9f0abda429b8478d4 \
--hash=sha256:73c5c9d98a3372a42611989ccfa437cce3842431ac6d6b9ab42c4f0e59c070f7 \
--hash=sha256:782f9985b6f6b45b8bc20152d206d3a52b56dd088ba81cb70a71f0b39841be9e \
--hash=sha256:7ac4344f34bbcdd17f6f8c30dbac3da2f80d223dd112e568fd7f7c2cd4cbc693 \
--hash=sha256:85505c96a3477c346ec2d2b7dced8478f4c651e2b1666ee102d53a832b511853 \
--hash=sha256:8df77b3409f54f4a268f77f3ff74992fe1d995cdbaf2cecf8ad74d32db217ce7 \
--hash=sha256:91afef90e6a96b013495e4702ef5d6358614a449e71008cdc194ef668778b92f \
--hash=sha256:9aa0815b90196f3c20d736ea8691381e97f12bbe8c7d87af10a351e434b452cb \
--hash=sha256:a2e0ab65df73e837666d12357280ca50ff6d6ac03ea5266703be518b68170edf \
--hash=sha256:a4bcdb4b6522738152885ef067e0c8524d5699828d780fb6f464cdb3db44369c \
--hash=sha256:a5d8695a227ea1c3a41d25db5b3fe720bf1b4808bd38862be811a4efd902c792 \
--hash=sha256:abe793da40f87295d33988673d34f06ea569848b44490b847552cd416816258a \
--hash=sha256:bce0a6ea93a5b19658f713312bb67554c19283ab15b454a1e3e55a13e78130f8 \
--hash=sha256:bf4cc4178cbace8f4d2bd240408276bc4da3850ac5fcb5fb5f8a74ab417615bb \
--hash=sha256:c3a9ed38dfa776443b86f4b49a982e8443d0953db4974bd2673d63cc904ae1ad \
--hash=sha256:dbe6a4a00b449b6ba2771f644cbc38bdf55acf5c812e60e5659110e19dd9f510 \
--hash=sha256:ec9fa6f0af0a9feb0e0bd517fb59ef28f806fbd50c0c6900ac26cbb4d080eba5 \
--hash=sha256:efd43f82ea03ced8488a767ded9473f050791ab7783ea8654107e1e0ac66af40 \
--hash=sha256:f56d0339cd98d26f6e561987be25bdd2761a5d53d8f73493b1ebe02d0d451093 \
--hash=sha256:f852bee785a7a124cb1720b1915670c6742af87747dc4d838f3ffdbd365ce9d9 \
--hash=sha256:fe2ea83c924c2250675b75686c3ee456b8cf0208d83d552e182a195fdf467378
# via -r src/backend/requirements-dev.in
pytest-django==4.12.0 \
--hash=sha256:3ff300c49f8350ba2953b90297d23bf5f589db69545f56f1ec5f8cff5da83e85 \
@ -626,13 +626,13 @@ ty==0.0.1a21 \
--hash=sha256:e941e9a9d1e54b03eeaf9c3197c26a19cf76009fd5e41e16e5657c1c827bd6d3 \
--hash=sha256:ecf41706b803827b0de8717f32a434dad1e67be9f4b8caf403e12013179ea06a
# via -r src/backend/requirements-dev.in
types-psycopg2==2.9.21.20260509 \
--hash=sha256:0422105f691a409e9d8048c2205aca9d694b70823248c6614393444017e9f088 \
--hash=sha256:69f6dae384bbea830dff23621936423035db152af901331e6f9c46f7c4f4b24f
types-psycopg2==2.9.21.20260518 \
--hash=sha256:2fd728a4fa3860db0a4a9813e5f49c30ed329b3d2e60e73530d9db01b2b98420 \
--hash=sha256:8b1f80d90d6799a4fcdac12198b382a8feee9ed4340d5f69b56fc5ffa0644143
# via django-types
types-pyyaml==6.0.12.20260510 \
--hash=sha256:09c1f1cb65a6eebea1e2e51ccf4918b8288e152909609a35cdb0d805efd125ad \
--hash=sha256:3492eb9ba4d9d833473214c4d5736cccf5f37d93f5854059721e1c84f785309d
types-pyyaml==6.0.12.20260518 \
--hash=sha256:d2150f75a231c9fe9c7463bd29487d93e60bac90400287351384bc2284eba7cd \
--hash=sha256:d917f83fb38462550338c1297faedd860b3ec83912b96b1e3d73255f7473e466
# via django-stubs
typing-extensions==4.15.0 \
--hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \

View File

@ -10,9 +10,9 @@ build==1.5.0 \
--hash=sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f \
--hash=sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647
# via pip-tools
certifi==2026.4.22 \
--hash=sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a \
--hash=sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580
certifi==2026.5.20 \
--hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \
--hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d
# via
# -c src/backend/requirements.txt
# requests
@ -104,7 +104,6 @@ cffi==2.0.0 \
# via
# -c src/backend/requirements.txt
# cryptography
# pytest-codspeed
charset-normalizer==3.4.7 \
--hash=sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc \
--hash=sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c \
@ -239,9 +238,9 @@ charset-normalizer==3.4.7 \
# -c src/backend/requirements.txt
# pdfminer-six
# requests
click==8.3.3 \
--hash=sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2 \
--hash=sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613
click==8.4.1 \
--hash=sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2 \
--hash=sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96
# via pip-tools
coverage[toml]==7.13.5 \
--hash=sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256 \
@ -443,9 +442,9 @@ gprof2dot==2025.4.14 \
--hash=sha256:0742e4c0b4409a5e8777e739388a11e1ed3750be86895655312ea7c20bd0090e \
--hash=sha256:35743e2d2ca027bf48fa7cba37021aaf4a27beeae1ae8e05a50b55f1f921a6ce
# via django-silk
idna==3.15 \
--hash=sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8 \
--hash=sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc
idna==3.16 \
--hash=sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5 \
--hash=sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d
# via
# -c src/backend/requirements.txt
# requests
@ -489,24 +488,24 @@ pluggy==1.6.0 \
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \
--hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
# via pytest
prek==0.3.13 \
--hash=sha256:0d89ac712c60e34d1550a606ad5fdfb8ad71d44ced8afa2fa5cbc106be4abd9e \
--hash=sha256:326fac2bdce00074ce6c5046b861d310638aee2b9de1ed241ba7eb32bdc83898 \
--hash=sha256:64b59a1550780af2bba37297c704b17f81d8e9df6288af1fab4017938e33b1db \
--hash=sha256:7ca8c526a23873177fb3b92013500b08ef5f8bedc7263f9f3a44dd2f49645a26 \
--hash=sha256:841049f89f5ec9f4035299283d11e566ac5a068e3742ead1055ea04f886831fc \
--hash=sha256:9ce6cd8f114ba9bbdbe97422103fd886101949b1c42e588a7543c4436ead2020 \
--hash=sha256:a5bbb175478438a871e3281d2c3c3f067288af73ad81707a9bdebfd769766c7d \
--hash=sha256:a65327a014d838341af757dfc05a706d10e8e33f039bc32bb3dbe2fa21c440c0 \
--hash=sha256:a9fd74e0aec550c6b8d41076fdcdd6ff121cd7d94d743c1338bd794784e3c775 \
--hash=sha256:b00d38f01235073c35aa5f48df57fefef45a6cec2ae0884d750345a2c7220370 \
--hash=sha256:b6a200843a36a5b0c41764ce7639ccb3471d48b097f1c5e3fc8f034219b42626 \
--hash=sha256:bdacaad8f35f343e063d251211fe34db1de9e5cc591795361ad69a6485202258 \
--hash=sha256:c48586ee3708bfbf3df80121f55583e9a7d0fa166b08172c091fe5971e92a0ac \
--hash=sha256:cc03e924a24d8d961f56195853c8b206cb196be6db4ad8312125dae847d718ac \
--hash=sha256:e5530a867bcf5b172b7513a64e71b06a337d1d184696227ae953845867376b8d \
--hash=sha256:f00328f1c520d8fefb910ab0d3c6764ee330d227952baa19b7e3de7242bd8b3b \
--hash=sha256:f9b5265863d18b5be4ea094fdce4fd6ca61a8c89a70ee3d8ee153b3e0ed6b272
prek==0.4.1 \
--hash=sha256:051c442b570b53756225410240577bee1aeace6be52955dfacf45a9783223b36 \
--hash=sha256:10e7e78ffe65dfba7d687a8c71b2f473554d1ba60f43c742105da4c0030feed9 \
--hash=sha256:17ffa9d8dd40791b9b99cafe558c5cc28e78e5be57607b280b15f0dab90264e9 \
--hash=sha256:2ab3460641762edf128b1ec8e833ce7e9ae015d1268a894560cb90d3393a7527 \
--hash=sha256:2d1805123ab5d730629de588bf319ea39e7078b589b3288c95740f1b4780a1d4 \
--hash=sha256:5b4a348537924b20e208cbd87ef58e96ec37d691c5bec2969209c40de0ecf72e \
--hash=sha256:60928d1dad45ff3e491d3083a50643cc213aa2d54f1dbd8d702d7193773c020e \
--hash=sha256:622a8812bda87cf4ddcae2dab5ccecc55b88d70c677129dbe25e975d923179f0 \
--hash=sha256:6e69a9c02ead38706a5d2a4ae209dccba08ccb5d0026e1d08e723c66ab964750 \
--hash=sha256:76663998827a2cbc94f5e209319809655489b5bd1f8e70568a623372e80253f0 \
--hash=sha256:7c515492ef3585e6bcd7b83f1bb1cb131038abc88ed2c843de1e4c3ceb865b19 \
--hash=sha256:8fa707971465d8ad021c907e43691aad7bb98942943e61e294ece5f95d9fbc78 \
--hash=sha256:b25807e0aa57d2118747e127b58e7a1bf41d5d7b3323f5f3f1f3cb10031245cc \
--hash=sha256:c0877e82c52359d655fe1072b3a5228639184d1d5f03c6803b6530cd6da1ef20 \
--hash=sha256:cdf4503a240369f66321213d9c4bc6f925014b64ff7121de9e9920c9b9838ce2 \
--hash=sha256:dc744fedf98df8a00a9e3bcd629b163fee5e9f9e22bce66029d9945241586165 \
--hash=sha256:efa95331c4c171a867c0064c19d8a4abc94a1c1c920c8b8092f2d7d87f4b90a8
# via -r src/backend/requirements-dev.in
pycparser==3.0 \
--hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \
@ -532,23 +531,36 @@ pytest==9.0.3 \
# via
# pytest-codspeed
# pytest-django
pytest-codspeed==4.4.0 \
--hash=sha256:06943110e7a8a4b54f4b13aaa3ff8db39caa02b2f61705916887649e36b9713a \
--hash=sha256:215170441e57bfcbefd179dfd86ccd54ed0ee235e0602a068ce4448b35f13cb2 \
--hash=sha256:29b1bf8a36e18d11641a5e610e23a94036b04185e3099978d81a873a5bd3635c \
--hash=sha256:33c38e0e797c74506004f231fc53eab0e412987de281755f714018334381aa3a \
--hash=sha256:340dbb1cc5a21434e0e29bd68ab03c7dc7ad9bfde09d1980b7161352c4c2f048 \
--hash=sha256:413666266762f9cef1321ba971a9e127b97a1f1dad40ddfd2184c2bc5ac157f9 \
--hash=sha256:4afa9455a9b198c5e898224c751182fcf53f67f11fb27c2c3346284da1baa018 \
--hash=sha256:56d5dd94dcb69460f916acb9c69865d0171b98acec3ce256645d0c0275b553d7 \
--hash=sha256:6a5c1d51e7ca72ffe247c99b9a97a54191185e8f7a27528e2200d7416da2a68b \
--hash=sha256:83479a6719598d2910969a60cc410c7283c262c876422a9157dca2f2ab42fa1d \
--hash=sha256:98524af4ddd6006ea064791bb15a43957d78fab040cb6f499ca73a369da373e6 \
--hash=sha256:a6aab2fa73523f538e7729c20ccf4a1e8e921324c9877a816b05334135950fd9 \
--hash=sha256:e258e6c3d5a8a02ae02a64831be3acd44c19210ffbf13321bdbb8c111c5c6fe4 \
--hash=sha256:edb7c101d9c50439a42cf02cfa9c0ac92da618841636bbebf87c3fa54669442a \
--hash=sha256:ee3e1964446011ca192eebf0350227df231a5b88af57e518f2a4328fc8ca5131 \
--hash=sha256:f3ae6f4053042c3a9ae3b05416fb42253c5e514e89391eb25e9c9e3ac8de8677
pytest-codspeed==5.0.3 \
--hash=sha256:005348ea52ace3ede2e2f595913912ad2564cca7b124211a88dc78a9cb1fca63 \
--hash=sha256:0c383c9121deb58a69f174188e9e4488ffc0daced0ed276abf87747182511901 \
--hash=sha256:20eba63765be9d1b6cacbbfad84b87d49eb04b357a7045a0899880da181f81e3 \
--hash=sha256:25464363c7f9b9bd5022e969c0addba616fa40ac9b8f0fc9e030c4538863b32d \
--hash=sha256:2eeb25fb1ac3f73c4de50e739e78fea396b89782bdb740bf2a7cd2df21f8d4ee \
--hash=sha256:3a2097247985f5d915a94b80c5552d10979ca858c859fc3edef1bf2baa5c9b7a \
--hash=sha256:4c682f6645d4eb472f3bd95dbda1805e3af4243610572cb7d6bf94a88e8a0b6c \
--hash=sha256:51e192905a2230f9956e6160732f76577836953a4a1fb2b1e7be74e51ac7b2a0 \
--hash=sha256:6524c57fec279a22ffef6112af404036afc71b4704758ae9f0abda429b8478d4 \
--hash=sha256:73c5c9d98a3372a42611989ccfa437cce3842431ac6d6b9ab42c4f0e59c070f7 \
--hash=sha256:782f9985b6f6b45b8bc20152d206d3a52b56dd088ba81cb70a71f0b39841be9e \
--hash=sha256:7ac4344f34bbcdd17f6f8c30dbac3da2f80d223dd112e568fd7f7c2cd4cbc693 \
--hash=sha256:85505c96a3477c346ec2d2b7dced8478f4c651e2b1666ee102d53a832b511853 \
--hash=sha256:8df77b3409f54f4a268f77f3ff74992fe1d995cdbaf2cecf8ad74d32db217ce7 \
--hash=sha256:91afef90e6a96b013495e4702ef5d6358614a449e71008cdc194ef668778b92f \
--hash=sha256:9aa0815b90196f3c20d736ea8691381e97f12bbe8c7d87af10a351e434b452cb \
--hash=sha256:a2e0ab65df73e837666d12357280ca50ff6d6ac03ea5266703be518b68170edf \
--hash=sha256:a4bcdb4b6522738152885ef067e0c8524d5699828d780fb6f464cdb3db44369c \
--hash=sha256:a5d8695a227ea1c3a41d25db5b3fe720bf1b4808bd38862be811a4efd902c792 \
--hash=sha256:abe793da40f87295d33988673d34f06ea569848b44490b847552cd416816258a \
--hash=sha256:bce0a6ea93a5b19658f713312bb67554c19283ab15b454a1e3e55a13e78130f8 \
--hash=sha256:bf4cc4178cbace8f4d2bd240408276bc4da3850ac5fcb5fb5f8a74ab417615bb \
--hash=sha256:c3a9ed38dfa776443b86f4b49a982e8443d0953db4974bd2673d63cc904ae1ad \
--hash=sha256:dbe6a4a00b449b6ba2771f644cbc38bdf55acf5c812e60e5659110e19dd9f510 \
--hash=sha256:ec9fa6f0af0a9feb0e0bd517fb59ef28f806fbd50c0c6900ac26cbb4d080eba5 \
--hash=sha256:efd43f82ea03ced8488a767ded9473f050791ab7783ea8654107e1e0ac66af40 \
--hash=sha256:f56d0339cd98d26f6e561987be25bdd2761a5d53d8f73493b1ebe02d0d451093 \
--hash=sha256:f852bee785a7a124cb1720b1915670c6742af87747dc4d838f3ffdbd365ce9d9 \
--hash=sha256:fe2ea83c924c2250675b75686c3ee456b8cf0208d83d552e182a195fdf467378
# via -r src/backend/requirements-dev.in
pytest-django==4.12.0 \
--hash=sha256:3ff300c49f8350ba2953b90297d23bf5f589db69545f56f1ec5f8cff5da83e85 \
@ -651,13 +663,13 @@ ty==0.0.1a21 \
--hash=sha256:e941e9a9d1e54b03eeaf9c3197c26a19cf76009fd5e41e16e5657c1c827bd6d3 \
--hash=sha256:ecf41706b803827b0de8717f32a434dad1e67be9f4b8caf403e12013179ea06a
# via -r src/backend/requirements-dev.in
types-psycopg2==2.9.21.20260422 \
--hash=sha256:ad7574fa8e25d9aa96ab96cd280c4dee20872725cd1fe6a6d3facc354f2644d4 \
--hash=sha256:e240684ac37946c5a2a058b04ea1f2fd0e4ee2655719b8c3ec9abf37f96da5ba
types-psycopg2==2.9.21.20260518 \
--hash=sha256:2fd728a4fa3860db0a4a9813e5f49c30ed329b3d2e60e73530d9db01b2b98420 \
--hash=sha256:8b1f80d90d6799a4fcdac12198b382a8feee9ed4340d5f69b56fc5ffa0644143
# via django-types
types-pyyaml==6.0.12.20260408 \
--hash=sha256:92a73f2b8d7f39ef392a38131f76b970f8c66e4c42b3125ae872b7c93b556307 \
--hash=sha256:fbc42037d12159d9c801ebfcc79ebd28335a7c13b08a4cfbc6916df78fee9384
types-pyyaml==6.0.12.20260518 \
--hash=sha256:d2150f75a231c9fe9c7463bd29487d93e60bac90400287351384bc2284eba7cd \
--hash=sha256:d917f83fb38462550338c1297faedd860b3ec83912b96b1e3d73255f7473e466
# via django-stubs
typing-extensions==4.15.0 \
--hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \

View File

@ -91,19 +91,19 @@ bleach==4.1.0 \
--hash=sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da \
--hash=sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994
# via django-markdownify
blessed==1.39.0 \
--hash=sha256:666e7e3fd0a4e38c3a262eaaf1e22a4ce2c81337aa17593c3f60ea136ec24fe1 \
--hash=sha256:b04fc7141a20a3b2ade6cad741051f1e3ac59cc1e7e90915ed1f9e521332bea4
blessed==1.42.0 \
--hash=sha256:34b460b77562ed21f807cfd7c527b983b0cc300c98810c8076f283b7bcd45ba7 \
--hash=sha256:f96c4a6dc664b48e0b832fa732acc16df67abd30f0ec35babf99025982f21852
# via -r src/backend/requirements.in
boto3==1.43.8 \
--hash=sha256:1894497c383e3cdf50e210f1f57a43e9f4047a5d3accc73ffdb7eacc3b0f011b \
--hash=sha256:d1235602d715c727c1923ef4bcdb5612a20575a9a5e4f2db00d571e0ea1f85fc
boto3==1.43.14 \
--hash=sha256:574335744656cfed0b362a0a0467aaf2eb2bf15526edcd02d31d3c661f4b09e4 \
--hash=sha256:5c0a994b3182061ee101812e721100717a4d664f9f4ceaf4a86b6d032ce9fc2d
# via
# django-anymail
# django-storages
botocore==1.43.8 \
--hash=sha256:611ad8b1f60661373cd39d9391ff16f1eaf8f5cb1d0a691563a4201d1a2603ce \
--hash=sha256:6257d2655c3abe75eaa49e218b7d883cdc7cea64652b451e5feb08a6c169da3c
botocore==1.43.14 \
--hash=sha256:1f4a2a95ea78c10398e78431e98c1fe47adb54a7b10a32975144c1f541186658 \
--hash=sha256:b9e500737e43d2f147c9d4e23b54360335e77d4c0ba90a318f51b65e06cb8516
# via
# boto3
# s3transfer
@ -209,9 +209,9 @@ brotli==1.2.0 \
--hash=sha256:fc1530af5c3c275b8524f2e24841cbe2599d74462455e9bae5109e9ff42e9361 \
--hash=sha256:ff09cd8c5eec3b9d02d2408db41be150d8891c5566addce57513bf546e3d6c6d
# via fonttools
certifi==2026.4.22 \
--hash=sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a \
--hash=sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580
certifi==2026.5.20 \
--hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \
--hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d
# via
# requests
# sentry-sdk
@ -673,49 +673,49 @@ drf-spectacular==0.29.0 \
--hash=sha256:0a069339ea390ce7f14a75e8b5af4a0860a46e833fd4af027411a3e94fc1a0cc \
--hash=sha256:d1ee7c9535d89848affb4427347f7c4a22c5d22530b8842ef133d7b72e19b41a
# via -r src/backend/requirements.in
dulwich==1.2.1 \
--hash=sha256:011d37afa0922b500c938d6417317e3e6d29d33a6fbaf12b3696fe2a216aa170 \
--hash=sha256:01f43a1b6953c93b4e957c0902f7de80267285e0c59c9b44f4801f7da9f11107 \
--hash=sha256:04215befa00c82accda97d0e3ce760d9344a67f081d5b92ade8ad00007fb38d8 \
--hash=sha256:09ca37d7a9086f96e535a6474644588e4502f69e2b202c39d92901e3606f6501 \
--hash=sha256:0b1181f4ae225bbde373fc16279c4e61fdf04aedd30ec21fab387be642567453 \
--hash=sha256:1073c75db1f5da845d219d1b5b601aeaaa7be5f999f3c77f4966f9ca2327bf44 \
--hash=sha256:11a51dba8454b4c64bf242a918ca4c4300097f7cec84a13164845e638a26f9de \
--hash=sha256:121d2d181407cd7bb051922dc3bc00841ca0959b8299880529525db93dfbdca9 \
--hash=sha256:1961e0b6c0b1f2920f4ab05821652d8eb12f19ddb5a4c167c385902391c08dd3 \
--hash=sha256:21f48480bb1e6501bdec7f0877639cdfc64a16e0dd97c24947582fbeed3baf21 \
--hash=sha256:23560643c1cc85737c87985761666a59b35b06a1cbb06db5ba642fa35c67be93 \
--hash=sha256:3183b7db09282842459fbeb37e77e498c8339e6db4d0cb6b5d3685c97ce79dac \
--hash=sha256:3412eca24fd79d5413ac7003f8f29b2e01008d7c9d6fce159f77602f70058aea \
--hash=sha256:3a95dd2649ef3c6e59095d5bd3de08470b3ba908dc41343f5823666a10a326f8 \
--hash=sha256:4198f79ff04b07f6f6dc81047041f341b35e774cf0207f496b35f34dd2e1bb9c \
--hash=sha256:41dfe1b1cb0f0202102e90f9f47f54458f27da6b746939a6ed85362b68e3e3a5 \
--hash=sha256:4b6cd8104bf4adf2824221ba4f3b399c641a7a6a026dfa193c2f516531971315 \
--hash=sha256:55c8415fb19068a5956e0d5bd83b0fb6e06b291d3b096d8fb6a604c105fc6ca0 \
--hash=sha256:5a0245dccd69fba9dfe2ed88eff9212bad3e04107bc41676fffbc7f706428cdb \
--hash=sha256:5e875729df04991732959f52e29c1de96d00eaf62feddfc60f0b2b08c6e38870 \
--hash=sha256:6349b475a1b17b224191a4801687a2486574c9526e00835db2ba9ed081124b11 \
--hash=sha256:674b21a9025c00a00495fa03a9bb5f2da67a13ee513c74f16bd91c56678d0a77 \
--hash=sha256:74b01505b8e2ba81a715213101c790e0794f5c0b8021f8ce1100131a1e8cfa55 \
--hash=sha256:879abd15550b6abcebf97823905a167623f331eeca5e12dc0cac62bcd9e70bcd \
--hash=sha256:96846ef600378739a64e347412c22d5f87f1b67d68526c633b465589335e9387 \
--hash=sha256:96c160d68173ae55f68bf734e7fddae04ea025f3541996b0b5f28dc2025b42dc \
--hash=sha256:9d0ea88273a7ee6fd3b1d75e231cc6fc614774e19bdd7c1de5df274b4b492dde \
--hash=sha256:9f7249c1090be7e7e841edda369d843617314092bb05f2a6a8b7f88660bbb2a6 \
--hash=sha256:a65527eda5f6a463168c6338d781b05d325ba85dc05c6f8217ddd8454911bb53 \
--hash=sha256:ba43bfb3a7cad40d9607170561e8c3be42e7083b4b57af89a5f54e01577ff791 \
--hash=sha256:bad94416de9a76ad36dc19f0e65a829b89fc845282bdcc8c43285a4addc1ad2e \
--hash=sha256:cedc717cf9ee973df63293bcbaa51bb03ab6f87ff631d1dd329078b3f48712a7 \
--hash=sha256:cf3aa983ca4907c96a0b98b357a3cac7381192786495522a65944da1b284bc02 \
--hash=sha256:d0ddda4e165ea14c70e1d0531aa97e527332f1133ea0c65eebaa5afb8786029e \
--hash=sha256:da2fdcc9e4f178b2b4ec1194d8e17d111d572f111fdaa7c6c5a3f46bbe686eaf \
--hash=sha256:e2f14d20aea48dd1d48714d637c70399b140825d930b4f5aa6fbb62199429740 \
--hash=sha256:e45a68da922a4abe8fd015ed020ec0123ee58176a6984a34d2a2c74c959e45d3 \
--hash=sha256:e88fd960a9327d87556a3bad76ad84b6346d3a07409fe8f081877a775977c86b \
--hash=sha256:efd394fcb926ec8e9ed63fe0883784abf8ef7bb1d30a492e950ca8c2f369ce7b \
--hash=sha256:f944b742962d9933e60863f577491743328b287251ae57a1e7cd84c289acfc23 \
--hash=sha256:fb855620d04d0c286058db941c106cfc60bc1e4882ea20b6dd499f3668a1afbe \
--hash=sha256:fd774825510350b614121bfa84d95c6eb08f7e93c0fe7faf8760423a43dbad8b
dulwich==1.2.5 \
--hash=sha256:00b54a1d56ddbacdd8eadd6d4787a51b3a05fefa30eadbf9165fd283a00b90ed \
--hash=sha256:0395b2c8924c3424bafe2d9c1edd5348cc4b21ce9c1d6655bf01f9a5c47164c8 \
--hash=sha256:07cb75b58216440e2c170fff4f3d55a5f387358d9489863af8cb11f24ee37121 \
--hash=sha256:1679b376433a0fc7f36586afda1d4ed7427afa7a79d4bf17e5014474eea69fa4 \
--hash=sha256:1699a4cf8d44c174408325a9594a1498d05786cea34e3004c8732420ee1b8182 \
--hash=sha256:1ba83ec3cfb4c506c277400357a51523c8258fa07b841ee06e8e1071da4cfed1 \
--hash=sha256:1c151a7f3995ccf9d433a603b747e76141a7ebe7c385c8909e9f7e7a6422c28f \
--hash=sha256:2f10dafa1ef5660b1331364bc8d68446448608a8d8f493ed0e260eaf5133e71c \
--hash=sha256:2f90d68bfa97c4ca71de7507984365aefe27b6d248cb28dc99644d0f3ae8c60b \
--hash=sha256:41ccffb0521f3f9ad73fac78772f321d731607336cee48911e7c26963459481c \
--hash=sha256:46db47394ba8a95748ae739f5d3a5a3e1724a2f857bf2437bc71bfc0baaed91d \
--hash=sha256:5108acead814d1de8b6262d6d8fb90af7e82f5a4d83788b6b48e39d01800a92f \
--hash=sha256:517fb7e20f91d2bd48dc5de9edc90ff8974a5512ce7f243284b191f8be6344c3 \
--hash=sha256:53599909d54a2fae49fcd50047f1daf4b8b9eda6a5500a08b71da689f5431c24 \
--hash=sha256:556593fd11637f80f6018bee1916b1a84f5b420423b470ebb3f1a782ad6ef081 \
--hash=sha256:5e067b7feceb7034bc99e7c7143a704f1d97d4be7027d9a0aa5a83c0657ff091 \
--hash=sha256:66aded7d364341b55941973a1562323f25bd205f0809692b687ec36ccd31242c \
--hash=sha256:6c683c0f4a062894b6826c61102d415dae86ade61a10003c82ccc2b91858d5fb \
--hash=sha256:701a9ecf7a8a44f5e2459e46befa93530cf36a8b1ae3140aefc007db1d7d0207 \
--hash=sha256:77d2d2e43ad975459491de1ebf47990c74ff17f12586c8561e9890239bc422db \
--hash=sha256:827366331603150de5976d72dd456a3fd5fc91e856471dc1d10fd64758c05f02 \
--hash=sha256:8929134acf4ff967203df7600b38535f9b5b590462067a7e30dbce01acb97af9 \
--hash=sha256:8e55f36a7f52ba0976dd72100273523908b16fb9dda6ce96d9aa9df9cceed4cc \
--hash=sha256:9008ef25cabd379cda4fa86000fc38ca14b72afe17db798a8c85c0b2b7ce4d1e \
--hash=sha256:93d2d87acf75d60c5a2b8c5c8a45aff17bbbd00c17bdccb4ba013d3ab590a65f \
--hash=sha256:9693d2c9e226b2ea855c1dc3a87e2f4d972f7523fc0f7924e5997e9f4c23d97f \
--hash=sha256:9b6d234f1f91335e9f01d9daac42ddc2d2e5c2fdbe285d8eeef50353b283648a \
--hash=sha256:9f3c98f5fa90a842c1f545463834f712aa2eed785fc3d5e42836c0df2d691bb6 \
--hash=sha256:9fc113c1348c7eb22c4e8790f68b562bb4f42a721fafb813e89a57e9cd632040 \
--hash=sha256:a5549f4afc973e0a15ea6b0244d57f848d3f3ee13dac557eb311024aebebf128 \
--hash=sha256:a6620963196c49212c511cd909f367dacf771f199a27d116f357cc671ea956c7 \
--hash=sha256:a70477c991e96cfe8fdd7c866e7251faf71b38bfeb51d6f27554c9cce1caabf3 \
--hash=sha256:ade416833214f3ee13af9b0199fff4de00fa6e0fde3deced776532fd91df5515 \
--hash=sha256:c65230abaa52c72093b70d3b499d5689d1d8f9627e88ad3c3b4f8154e86ac0c8 \
--hash=sha256:ccc58f26a1b94bef255316311678b03854f7192069bdf11cf501a6c85f61b83c \
--hash=sha256:d33bae2b3292ed0235522682316251658187f43b1ebad6cd2b127069b94afb3b \
--hash=sha256:d46e35c473646efb3b2ff8032f37ac5b6d48da52a669577187d3796a6d5987a3 \
--hash=sha256:d8f7ea8f47e38e5b0de3fab97e07e9c9161ffddc90b3964512cab2b7749df4e6 \
--hash=sha256:dd9569bc26174a3437d749114d36c81fc6c7478b55370ae50125e34e9629e4fe \
--hash=sha256:df4ac3746099562c8160d78d55bb2fa10c9ada7ef970af3e2536bd133cb7830e \
--hash=sha256:e128cddeccae4146b556684a0d5426454fff5bfe7306862e5a8ce6b471568af4 \
--hash=sha256:fa37da7ad16c47391016b5f984fb60e175e1ab0b478f04920fd6d1f61123ce4d
# via -r src/backend/requirements.in
et-xmlfile==2.0.0 \
--hash=sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa \
@ -864,20 +864,16 @@ gunicorn==26.0.0 \
--hash=sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc \
--hash=sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf
# via -r src/backend/requirements.in
icalendar==7.1.0 \
--hash=sha256:10cd223c792fcc43bee4c3ebe3149d4cf32406c85cfef146624df5a0d414260f \
--hash=sha256:6de875370d22fc4aff172ad7c439b39fb109dc2eab9ce358fcb95e8689ad7b56
icalendar==7.1.2 \
--hash=sha256:01c76243c76c549f58bb51510a8f0a4edb7c539726adda1356dfd0dc04fb7a53 \
--hash=sha256:ebc43ebeb357be98984b573d975118008dee3410d8df28b054ef2943cf3e367e
# via django-ical
idna==3.15 \
--hash=sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8 \
--hash=sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc
idna==3.16 \
--hash=sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5 \
--hash=sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d
# via
# django-anymail
# requests
importlib-metadata==8.7.1 \
--hash=sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb \
--hash=sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151
# via opentelemetry-api
inflection==0.5.1 \
--hash=sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417 \
--hash=sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2
@ -894,6 +890,10 @@ jinja2==3.1.6 \
--hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
--hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
# via -r src/backend/requirements.in
jinxed==2.0.4 \
--hash=sha256:58ae0a4a2930b51e9de162208c356b382f5e25b3bc0ad73e2013b02a62d84e8b \
--hash=sha256:a92ac48923433c0a88577bb0479191788813fd5055ef17ff2b04a701a1b83f19
# via blessed
jmespath==1.1.0 \
--hash=sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d \
--hash=sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64
@ -912,141 +912,141 @@ jwcrypto==1.5.7 \
--hash=sha256:70204d7cca406eda8c82352e3c41ba2d946610dafd19e54403f0a1f4f18633c6 \
--hash=sha256:729463fefe28b6de5cf1ebfda3e94f1a1b41d2799148ef98a01cb9678ebe2bb0
# via django-oauth-toolkit
lxml==6.1.0 \
--hash=sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2 \
--hash=sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773 \
--hash=sha256:045e387d1f4f42a418380930fa3f45c73c9b392faf67e495e58902e68e8f44a7 \
--hash=sha256:05b9b8787e35bec69e68daf4952b2e6dfcfb0db7ecf1a06f8cdfbbac4eb71aad \
--hash=sha256:07f98f5496f96bf724b1e3c933c107f0cbf2745db18c03d2e13a291c3afd2635 \
--hash=sha256:08950a23f296b3f83521577274e3d3b0f3d739bf2e68d01a752e4288bc50d286 \
--hash=sha256:0d082495c5fcf426e425a6e28daaba1fcb6d8f854a4ff01effb1f1f381203eb9 \
--hash=sha256:0f0f08beb0182e3e9a86fae124b3c47a7b41b7b69b225e1377db983802404e54 \
--hash=sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c \
--hash=sha256:11a873c77a181b4fef9c2e357d08ed399542c2af1390101da66720a19c7c9618 \
--hash=sha256:183bfb45a493081943be7ea2b5adfc2b611e1cf377cefa8b8a8be404f45ef9a7 \
--hash=sha256:19f4164243fc206d12ed3d866e80e74f5bc3627966520da1a5f97e42c32a3f39 \
--hash=sha256:1ae225f66e5938f4fa29d37e009a3bb3b13032ac57eb4eb42afa44f6e4054e69 \
--hash=sha256:1bc4cc83fb7f66ffb16f74d6dd0162e144333fc36ebcce32246f80c8735b2551 \
--hash=sha256:1dd6a1c3ad4cb674f44525d9957f3e9c209bb6dd9213245195167a281fcc2bdc \
--hash=sha256:20cf4d0651987c906a2f5cba4e3a8d6ba4bfdf973cfe2a96c0d6053888ea2ecd \
--hash=sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd \
--hash=sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405 \
--hash=sha256:23a5dc68e08ed13331d61815c08f260f46b4a60fdd1640bbeb82cf89a9d90289 \
--hash=sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b \
--hash=sha256:2593a0a6621545b9095b71ad74ed4226eba438a7d9fc3712a99bdb15508cf93a \
--hash=sha256:264c605ab9c0e4aa1a679636f4582c4d3313700009fac3ec9c3412ed0d8f3e1d \
--hash=sha256:26c5272c6a4bf4cf32d3f5a7890c942b0e04438691157d341616d02cca74d4bd \
--hash=sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac \
--hash=sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62 \
--hash=sha256:29f5c00cb7d752bce2c70ebd2d31b0a42f9499ffdd3ecb2f31a5b73ee43031ad \
--hash=sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f \
--hash=sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad \
--hash=sha256:363e47283bde87051b821826e71dde47f107e08614e1aa312ba0c5711e77738c \
--hash=sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292 \
--hash=sha256:37448bf9c7d7adfc5254763901e2bbd6bb876228dfc1fc7f66e58c06368a7544 \
--hash=sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491 \
--hash=sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120 \
--hash=sha256:3ae5d8d5427f3cc317e7950f2da7ad276df0cfa37b8de2f5658959e618ea8512 \
--hash=sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb \
--hash=sha256:40d9189f80075f2e1f88db21ef815a2b17b28adf8e50aaf5c789bfe737027f32 \
--hash=sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2 \
--hash=sha256:41dcc4c7b10484257cbd6c37b83ddb26df2b0e5aff5ac00d095689015af868ec \
--hash=sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88 \
--hash=sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43 \
--hash=sha256:4642e04449a1e164b5ff71ffd901ddb772dfabf5c9adf1b7be5dffe1212bc037 \
--hash=sha256:468479e52ecf3ec23799c863336d02c05fc2f7ffd1a1424eeeb9a28d4eb69d13 \
--hash=sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03 \
--hash=sha256:481d6e2104285d9add34f41b42b247b76b61c5b5c26c303c2e9707bbf8bd9a64 \
--hash=sha256:4937460dc5df0cdd2f06a86c285c28afda06aefa3af949f9477d3e8df430c485 \
--hash=sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16 \
--hash=sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb \
--hash=sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9 \
--hash=sha256:4e2c54d6b47361d0f1d3bc8d4e082ad87201e56ccdcca4d3b9ee3644ff595ec8 \
--hash=sha256:52b0ac6903cf74ebf997eb8c682d2fbac7d1ab7e4c552413eec55868a9b73f39 \
--hash=sha256:546b66c0dd1bb8d9fa89d7123e5fa19a8aff3a1f2141eb22df96112afb17b842 \
--hash=sha256:56971379bc5ee8037c5a0f09fa88f66cdb7d37c3e38af3e45cf539f41131ac1f \
--hash=sha256:5715e0e28736a070f3f34a7ccc09e2fdcba0e3060abbcf61a1a5718ff6d6b105 \
--hash=sha256:5cfa1a34df366d9dc0d5eaf420f4cf2bb1e1bebe1066d1c2fc28c179f8a4004c \
--hash=sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7 \
--hash=sha256:6262b87f9e5c1e5fe501d6c153247289af42eb44ad7660b9b3de17baaf92d6f6 \
--hash=sha256:63aeafc26aac0be8aff14af7871249e87ea1319be92090bfd632ec68e03b16a5 \
--hash=sha256:690022c7fae793b0489aa68a658822cea83e0d5933781811cabbf5ea3bcfe73d \
--hash=sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9 \
--hash=sha256:73becf6d8c81d4c76b1014dbd3584cb26d904492dcf73ca85dc8bff08dcd6d2d \
--hash=sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb \
--hash=sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f \
--hash=sha256:76b958b4ea3104483c20f74866d55aa056546e15ebe83dd7aecd63698f43b755 \
--hash=sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb \
--hash=sha256:7ba11752e346bd804ea312ec2eea2532dfa8b8d3261d81a32ef9e6ab16256280 \
--hash=sha256:7da13bb6fbadfafb474e0226a30570a3445cfd47c86296f2446dafbd77079ace \
--hash=sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e \
--hash=sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33 \
--hash=sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5 \
--hash=sha256:81ff55c70b67d19d52b6fd118a114c0a4c97d799cd3089ff9bd9e2ff4b414ee2 \
--hash=sha256:857efde87d365706590847b916baff69c0bc9252dc5af030e378c9800c0b10e3 \
--hash=sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585 \
--hash=sha256:8c11b984b5ce6add4dccc7144c7be5d364d298f15b0c6a57da1991baedc750ce \
--hash=sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946 \
--hash=sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e \
--hash=sha256:9147d8e386ec3b82c3b15d88927f734f565b0aaadef7def562b853adca45784a \
--hash=sha256:920354904d1cb86577d4b3cfe2830c2dbe81d6f4449e57ada428f1609b5985f7 \
--hash=sha256:942454ff253da14218f972b23dc72fa4edf6c943f37edd19cd697618b626fac5 \
--hash=sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28 \
--hash=sha256:976a6b39b1b13e8c354ad8d3f261f3a4ac6609518af91bdb5094760a08f132c4 \
--hash=sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c \
--hash=sha256:9c03e048b6ce8e77b09c734e931584894ecd58d08296804ca2d0b184c933ce50 \
--hash=sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9 \
--hash=sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495 \
--hash=sha256:9f93d5b8b07f73e8c77e3c6556a3db269918390c804b5e5fcdd4858232cc8f16 \
--hash=sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45 \
--hash=sha256:a02ca8fe48815bddcfca3248efe54451abb9dbf2f7d1c5744c8aa4142d476919 \
--hash=sha256:a1d9b99e5b2597e4f5aed2484fef835256fa1b68a19e4265c97628ef4bf8bcf4 \
--hash=sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc \
--hash=sha256:a31286dbb5e74c8e9a5344465b77ab4c5bd511a253b355b5ca2fae7e579fafec \
--hash=sha256:a86f06f059e22a0d574990ee2df24ede03f7f3c68c1336293eee9536c4c776cd \
--hash=sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8 \
--hash=sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f \
--hash=sha256:b6c2f225662bc5ad416bdd06f72ca301b31b39ce4261f0e0097017fc2891b940 \
--hash=sha256:bb40648d96157f9081886defe13eac99253e663be969ff938a9289eff6e47b72 \
--hash=sha256:bba078de0031c219e5dd06cf3e6bf8fb8e6e64a77819b358f53bb132e3e03366 \
--hash=sha256:bc783ee3147e60a25aa0445ea82b3e8aabb83b240f2b95d32cb75587ff781814 \
--hash=sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690 \
--hash=sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13 \
--hash=sha256:c08da09dc003c9e8c70e06b53a11db6fb3b250c21c4236b03c7d7b443c318e7a \
--hash=sha256:c3592631e652afa34999a088f98ba7dfc7d6aff0d535c410bea77a71743f3819 \
--hash=sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c \
--hash=sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86 \
--hash=sha256:c53fa3a5a52122d590e847a57ccf955557b9634a7f99ff5a35131321b0a85317 \
--hash=sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180 \
--hash=sha256:c748ebcb6877de89f48ab90ca96642ac458fff5dec291a2b9337cd4d0934e383 \
--hash=sha256:c871299c595ee004d186f61840f0bfc4941aa3f17c8ba4a565ead7e4f4f820ee \
--hash=sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe \
--hash=sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181 \
--hash=sha256:cec05be8c876f92a5aa07b01d60bbb4d11cfbdd654cad0561c0d7b5c043a61b9 \
--hash=sha256:d036ee7b99d5148072ac7c9b847193decdfeac633db350363f7bce4fff108f0e \
--hash=sha256:d0d799ff958655781296ec870d5e2448e75150da2b3d07f13ff5b0c2c35beefd \
--hash=sha256:d1392c569c032f78a11a25d1de1c43fff13294c793b39e19d84fade3045cbbc3 \
--hash=sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d \
--hash=sha256:d3829a6e6fd550a219564912d4002c537f65da4c6ae4e093cc34462f4fa027ad \
--hash=sha256:d43aa26dcda363f21e79afa0668f5029ed7394b3bb8c92a6927a3d34e8b610ea \
--hash=sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24 \
--hash=sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d \
--hash=sha256:db88156fcf544cdbf0d95588051515cfdfd4c876fc66444eb98bceb5d6db76de \
--hash=sha256:de550d129f18d8ab819651ffe4f38b1b713c7e116707de3c0c6400d0ef34fbc1 \
--hash=sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d \
--hash=sha256:e3c4f84b24a1fcba435157d111c4b755099c6ff00a3daee1ad281817de75ed11 \
--hash=sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9 \
--hash=sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2 \
--hash=sha256:e80807d72f96b96ad5588cb85c75616e4f2795a7737d4630784c51497beb7776 \
--hash=sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f \
--hash=sha256:f0cea5b1d3e6e77d71bd2b9972eb2446221a69dc52bb0b9c3c6f6e5700592d93 \
--hash=sha256:f15401d8d3dbf239e23c818afc10c7207f7b95f9a307e092122b6f86dd43209a \
--hash=sha256:f504d861d9f2a8f94020130adac88d66de93841707a23a86244263d1e54682f5 \
--hash=sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d \
--hash=sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d \
--hash=sha256:fcf3da95e93349e0647d48d4b36a12783105bcc74cb0c416952f9988410846a3 \
--hash=sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086 \
--hash=sha256:ffb34ea45a82dd637c2c97ae1bbb920850c1e59bcae79ce1c15af531d83e7215
lxml==6.1.1 \
--hash=sha256:05a82eb6e1530a64f26225b55cbd178113bd0b5af1c2b625f25e5296742c26d2 \
--hash=sha256:07a4a68e286ee7a1ed7dfb8af83e615757c0ccfe9f18c6b4ea6771388d9ba8c9 \
--hash=sha256:09dd5b7075dc2f7709654a46543ba1ea3c2e217b2ed8fbd413a8a945a0f40f60 \
--hash=sha256:0b7e8a14c8634bf6f7a568634cb395305a6d964aeb5b7ee32248094bed3a7e2c \
--hash=sha256:104c09bda8d2a562824c0e319d0768ce26a779b7601e0931d33b09b53c392ef7 \
--hash=sha256:126c93f7f56f0eda92f6d8c619edc463a4f23d9252f1c9d0405a76f25fa9f11a \
--hash=sha256:162af1091cd785f2f27e62d3547ae9bc58ec5c86dd314d67021fd02463708d83 \
--hash=sha256:17e0e18d4ad8adbd0399291bc44845b69d9dd68439a3cdebdf35ff902ec05072 \
--hash=sha256:18b73c339ae29b90fd2d06e58ebd555a751bde9cd6bbd36cc0281b9a2c94e9d8 \
--hash=sha256:19607c6bbff2a44cf3fe8250abccd20942d3462473e0a721d01d379ed017e462 \
--hash=sha256:19b7ab10b210b0b3ad7985d9ac4eb66ab09a90b20fe6e2f7ba55d01a234345d0 \
--hash=sha256:1d4962d4c66bf830a7e59ed6cfc17d148149898a3aefa8ec6e59763e6e3ed085 \
--hash=sha256:1db753c9115ec7100d073b744d17e25e88a8f90f5c39b2f5dd878149af59671f \
--hash=sha256:1dde6131244bba38a17c745836ba190bc753fd73c9291666287fd0a3fa3dcf30 \
--hash=sha256:25c6997a9a534e016695a0ba06b2f07945de682731ff01065b6d5a4474179da1 \
--hash=sha256:26e6eda8d38c1fcab1090dd196ee87cbd13788e531937610e2589085de074e77 \
--hash=sha256:27acc820660aaffa4f7c087f29120e12980f7779d56d8492d263170111284740 \
--hash=sha256:2a0217714657e023ef4293500f65aa20fce6164c8fd6b08fa5bd4a859fb14b9b \
--hash=sha256:2c8daa471358dc2d6fcf02165e80ec68f77871a286df95bc5cc3816153b0fd2c \
--hash=sha256:30a89d3ac8faec007453fb541f3f46807eeec88edd5826f6e3fe001752a2c621 \
--hash=sha256:31033dc34636ea6b7d5cc11b1ddbda78a14de858ba9d3e1ed4b69a3085bc521e \
--hash=sha256:32ab449a5486f6c758e849bb86710d0e45edc24a04e250c01555f8f5653958f8 \
--hash=sha256:3483644525531e1d5762b0c44a8e18b6efba321b6dcf8a8952de10b037618bca \
--hash=sha256:34c2d737beabfe35baada43941ed519251e9a12e779031496bcd5d539fcfd730 \
--hash=sha256:3779def59032b81e44a5f70096ef6bf2082f8d901937dca354474ba09782e245 \
--hash=sha256:37a58976370f36d9329d118ad0b953c5aeb9119ac9c6a4e258942a225d0573a1 \
--hash=sha256:3893c14c4b6ac5b2d54ba8cf03e99fe5104e592de491f19bd6b82756c09f8004 \
--hash=sha256:3a12689be69a28ddaa0ab99a5a1137da2afd5f8f16df7b5680b66f616d3eda1d \
--hash=sha256:3ab541146f1f6968c462d6c2ac495148e8cdba2f8347700b2141b6ec5a75bf52 \
--hash=sha256:3abf332af33a74288675d936fe861fd4344da0dd6622193fbc4f2bfbb35536b5 \
--hash=sha256:3fd9728a2735fda14f4e8235830c86b539e9661e849665bf926d3f867943b4bf \
--hash=sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc \
--hash=sha256:441dd227fa0690eb9fc81edabc63cdcefc212bba99b906dcf6e32cc1a9d3e533 \
--hash=sha256:469e3618338bd7ab5beb412d2439825479fcf0dab99e394ca563dbc4eaf6c834 \
--hash=sha256:47402e62c52ff5988c1e8c6c63177f5708bccf48e366dea4e3dcf1e645e04947 \
--hash=sha256:4f0dd2f01f9f8a89f565d000e03abcf0a13d692a346c8d22f628d49af098777a \
--hash=sha256:53b7d2b7a10b1c35c0a5e21e9224accf60c1bbfba523990732e521b2b73adef2 \
--hash=sha256:53c909b62a0532183542fed00c5a7218258c56292d409bc789886fe1cb04c438 \
--hash=sha256:54a7f95e4de5fb94e2f9f4b9055c6ba33bf3d628fd77a1d647c5923caa2cdcdc \
--hash=sha256:556e94a63c9b04716f8e4de2abb65775061f846e89331b6c5be79183a24f98ea \
--hash=sha256:55b03549819867ea141c0202242c4816c82e52ec36e7e648db9d8da5a3dc3ed6 \
--hash=sha256:581d4c8ae690a6609e64862dd6b7c2489635c2d13907fc2b20f2bc200ff1d21e \
--hash=sha256:58bb955caba94e467d2a96da17660d2d704e0675894cba21ab8a775b8621fd1c \
--hash=sha256:5b7328b46d49fc9477d91ae8f6d55340347d827b7734ba3ea33faae0efef1383 \
--hash=sha256:5ba186ad207446c65d3bb3d3e0412b032b1d9f595e59861e2354798c5703d955 \
--hash=sha256:5bec7d03d78d853597d6107854c2310ce3f761fd218fe9fe91d5101fcf6c2efe \
--hash=sha256:5c6bf403fbb3b3e348a561a5f4f0b9961835657981c802a1df03653eef8a9074 \
--hash=sha256:5f6994074ebae6ffb04447268e37dc16edc304f9859cf91acb86e0af6c1b395c \
--hash=sha256:62aeb7e85b5d60320b9d77eef2e773994e2c0ce10121b277e0a19804e1654a5a \
--hash=sha256:63876be28efefa04a1df615b46770e82042cce445cfdce55160522f57b231ccb \
--hash=sha256:639f6c857d91d9be29bd7502348d6736dab168b54b5158cd899abf11684dc186 \
--hash=sha256:640f97d43d867bcb9c75b3af013b64850756b746cb6bce8ace83b70da3abba9d \
--hash=sha256:649dda677cf3bd6ac9ae14007ba0c824ded8ce5808b53fc7431d9140399118c1 \
--hash=sha256:6540377fbd53fe1b629172288c464fb18db11ce1fa7dc15891da10aa9dcc3e7f \
--hash=sha256:6689e828a94eee4f139408c337bb198e014724bb8a8c26d3cfac49d119ed69a6 \
--hash=sha256:68a9198d0fc122d14bb76837de9aa80cf84caed990b5b237f532ed87d3706736 \
--hash=sha256:6b1761fbf9ec984e2e9d9c589ef5f5fd684b7c19f92aadd567a26c5224958db6 \
--hash=sha256:70cdfd80589d59e43e18005dd7244e8895e93db8ab6a620b7e23df5445a4e3d2 \
--hash=sha256:70ef8a7e102a1508f8121aae5b0867abd663f72c14f0a9c937e6554cb4587b7b \
--hash=sha256:73bc2086f141224ebddb7fc5c6a36ca58b31b94b561e1dfe8e073e3270fad1e7 \
--hash=sha256:74a9717fd0d82effef5c2854f0d917231d5324b5a3eb7275c43ac9fa32f97a14 \
--hash=sha256:752d3bbfe874715ccd0aec7f88d7fc623c0f1fd7aa7b3238a084e017bad2a009 \
--hash=sha256:762ff394d5bd56da0cf034a23dcce4e13923f15321a2adfa2ac00201dc6d3fca \
--hash=sha256:76447f65250ed2501ead1a1552f5ce8edff159a86f308348e6a9c4acb5e1f1b4 \
--hash=sha256:766b010012d59470072c1816b5b6c69f1d243e5db36ea5968e94accf430a4635 \
--hash=sha256:787b2496d0dbe8cd180984e8d29e3a6f76e7ea34db781cb3bd55e4ba1ef8b4ee \
--hash=sha256:793033d6c5cdf33a573f910d9bea14ef8f5771820411d118da8e1182edb53d5e \
--hash=sha256:7d47866cb32fb503450b6edc9df355d10dc49836af2e89901bd6ac6b0896d9d9 \
--hash=sha256:7f7a92e8583f06b1fd49d01158143b8461cfcd135dcb10ec807270a3051bd603 \
--hash=sha256:80c2dfadb855da477cf73373ad29a333535dedb9b12bad02c9814c8e2b43bf08 \
--hash=sha256:83b6b30eb131da7a75b601f28c5d6971e6ed3e887919bf6b6a1ad3c2df289080 \
--hash=sha256:86281fbdd6a8162756f8d603f37e3435bfa38043adb79c6dc6a2dfee065e7525 \
--hash=sha256:86c89b9d55ebf820ad7c90bc533410f0d098054f293351f10603c0c46ff598f5 \
--hash=sha256:876e1ff5930ed8bf295ec5ef9a8155e9b6b1876bbf1deed8b3a8069311875a8f \
--hash=sha256:88136950da4d13c318bde414ce10219931937851327f44328f2df4d2c4614067 \
--hash=sha256:88d8cb75b9d82858497a5393e3c63cfbf03035225e4b35a49ed7ccb151e4dc0e \
--hash=sha256:8be8ad51249698103d24b0571df35a10990fbe93dd043b6c024172189485f5e3 \
--hash=sha256:8d43ca737b20e106e4aebc42b2f3ae19f00ba63d7eb731698ee083d72d15646f \
--hash=sha256:8dadbe5b217ff35b6a8d16610dd710219b59b76d13f0e3f0d9f36786206e4485 \
--hash=sha256:9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13 \
--hash=sha256:96f2ec43df44b1f76249ee0a615334f9b5b060e1c8bd90e706dad2d14d02f383 \
--hash=sha256:98fc784c2c1440667aeedf8465bdfe10208acf0ead656a2c68627299f546b315 \
--hash=sha256:9e36f163528fc50cbef305f02a5fd66d404edf7049cdaff211dbc2cba5a7013e \
--hash=sha256:9eb9b5a968f6e0f6d640092a567e14529ff8cea2e29d00da6f78a79fa49f013c \
--hash=sha256:9f76acfb5f68ba982635a53fd985a8044be98a35b43232c2a1ee235ffab3e1dd \
--hash=sha256:a088f287f7d8275a33c07f2cac6c50b9319309a0200a39e7e75d80c707723099 \
--hash=sha256:a10bd2fd62e8ce916ececb342f348f190724a098c1faa056fdfb2a22ad5e8660 \
--hash=sha256:a4bbea04c97f6d78a48e3fbc1cb9116d2780b1b39e03a23f6eb9b603fd61f510 \
--hash=sha256:aa366a1e55b8ebfe8ca8ddc3cfe75c8ebade181aeb0f661d0cb05986b647f72a \
--hash=sha256:aa49e06d94aba782c6a02eecb7e507969e7e7a41b267f1b359bb35585f295d5b \
--hash=sha256:aad9aa39483ed8ec44d6d2e59e5b98a0d80676ef0d92f44bfc374836111f62f5 \
--hash=sha256:aae97dfdb60715c164419ac2532a76d013c3918a665eb6cb7288098b5f349aaf \
--hash=sha256:abbefa31eee84842140f67acef1c828e28bba8bbf0c3bc6e5492a9af88152c28 \
--hash=sha256:ac931cdc9442c1763b8a8f6cd62c0c938737eafc5be75eff88df55fc73bc0d00 \
--hash=sha256:acd7d70b64c0aae0c7922cca83d288a16f5f6da523637697872253415269baef \
--hash=sha256:add8cf6ddf9a65116119a28ece0f7886e30af27ba724a7594305f1d1b58a92a1 \
--hash=sha256:aee395f5d0927f947758b4ec119fd5fc8ec71f07a1c5c52077b30b04c0fa6955 \
--hash=sha256:b1b963fd8f5caa68e99dfae060d54de1fe9cba899b8718b44a00cdca53c3e590 \
--hash=sha256:b2d444f2e66624d68e9c6b211e28a76e22fff5fcabcfff4deac18b529b7d4137 \
--hash=sha256:b8d812c6011c08b8111a15e54dd990b8923692d80adf35488bee34026c35accf \
--hash=sha256:ba96ae44888e0185281e937633a743ea90d5a196c6000f82565ebb0580012d40 \
--hash=sha256:bdebcc8a75d38c7598dfb2c9ed852d7a9eb4a10d6e2d0764b919b802bf32ac88 \
--hash=sha256:c07da4cebf6889f03ebac8d238f62318e29f495de0aa18a51ea14e61ae907e2e \
--hash=sha256:c08e5c694306507275f2290073350c4f32e383db15213b2c69e7ff39c1193840 \
--hash=sha256:c4f469aebd783bb741c2ecb2a681008fd26bfe5c16a9a72ed5467f834e810df2 \
--hash=sha256:c5d7152ec39ca7c402d8fb9bad86140a15b9503bd0c54484e3f1bbe3dd37ceca \
--hash=sha256:c674693f055fa2495de12292cb45e9944199d8eaef5a2dec45175c7c61cb73e3 \
--hash=sha256:c6ed5141a5c7507cf3ee76bd363b0d6f801e3321adc35b5d825a23115faa5465 \
--hash=sha256:c921ba5c51e4e9f63b8b00267d06566e1f63407408a0496da2d1d0bfc819c7fc \
--hash=sha256:c9a4b821dc7055bf9e05ff5719e18ec501f75c0f0bbfabd573b277559780833d \
--hash=sha256:c9f79d5325907f13e1be0b3e4dacc1049d1dffc4aeee3c995284bea5fe0fab7d \
--hash=sha256:cd312b9692e831d2ffcad61eab31d91d4b4655a962e61de8fb410472cbcd37aa \
--hash=sha256:cea3f4c1af79af13cdb2da0c028111d8f8522d4f22a000c82385535f24e5cf3a \
--hash=sha256:cecdd5dfdc87b1fd87dbf81d4b037a544f47f4c744200a67013771682d67686a \
--hash=sha256:cf9d57306d848218f3601fee7601fab1a327c942d56e2e97610583cb4dd74206 \
--hash=sha256:d34bbf07dbc7ca5970671b1512e928991fb5e9d95365636c9b2d8b4f53af405e \
--hash=sha256:d49514be2f28d895c38cf9d2b72d7b9a07d00314519f456c0b50b53cfcf4c785 \
--hash=sha256:d680fbcb768404c601ecb43519ecd8461f6954cb11c06a78962f666832ccfca8 \
--hash=sha256:db1d75f6617a49c1c01bc7023713e0ff59ab32c9579ae62a7674c0e34f3b0b0a \
--hash=sha256:dcb292aa7fe485ceff7af4f92e46c5af397daec5dff64871a528f0fc47a3cc5b \
--hash=sha256:e07c65f443c887bbcf31cc1771d932ecc192a5273943589b3c7572b749f1ffb2 \
--hash=sha256:e902da4b04e6b52e5893900d4b8ab46068f75f3561f01bf1080957f9fd932ed6 \
--hash=sha256:e9308ff8241c532df3f3e570f9a5aeed6c853f888512ba4b75638d7c11c95ef6 \
--hash=sha256:eb7c9811bfaa8b1ed5ed319f5d370dfbcaa59d52ea64be2a5a85e18195930354 \
--hash=sha256:ebe6af670449830d6d9b752c256a983291c766a1365ba5d5460048f9e33a7818 \
--hash=sha256:ed21202aec73cda4d55d1ce57b389aadb90ffb044e6cd1080b8347efe1b1ec84 \
--hash=sha256:efe0374196335f93b53269acd811b944f2e6bdc88e8894f214bd636455484909 \
--hash=sha256:f64ec5397ea6a41fc1b4af0380d79b44a755b5531dcaccd9940fb260dca93038 \
--hash=sha256:f6ac4ef4d82dff54670227a69c67782ae0b811b5cf6b17954f1e8f7502fc0d1d \
--hash=sha256:f6f0ce10945fab9c4c06ce14e22af9059d1a87493a9af4501a5b0b9187e21cf2 \
--hash=sha256:f8844cd288697c6425c9beba919302241e3278871dc6519515e72b04e987abcf \
--hash=sha256:fe0306bd29505a9177aac19f1877174b0e7422c222a59f70b2cd41633448c3dc \
--hash=sha256:ff3f333630ab480244a1bff72043e511a91eb22e7595dead8653ee5612dd8f3d \
--hash=sha256:ffecec8eb889b58ba9be5b95fb1cc78e22ea8eedea38e8736a1568fe1979250e
# via
# python3-saml
# xmlsec
@ -1184,9 +1184,9 @@ openpyxl==3.1.5 \
--hash=sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2 \
--hash=sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050
# via tablib
opentelemetry-api==1.41.1 \
--hash=sha256:0ad1814d73b875f84494387dae86ce0b12c68556331ce6ce8fe789197c949621 \
--hash=sha256:a22df900e75c76dc08440710e51f52f1aa6b451b429298896023e60db5b3139f
opentelemetry-api==1.42.1 \
--hash=sha256:51a69edacadbc03a8950ace1c4c21099cacc538820ac2c9e36277e78cebba714 \
--hash=sha256:56c63bea9f77b62856be8c47600474acad853b2924b99b1687c4cb6297166716
# via
# -r src/backend/requirements.in
# opentelemetry-exporter-otlp-proto-grpc
@ -1203,27 +1203,27 @@ opentelemetry-api==1.41.1 \
# opentelemetry-instrumentation-wsgi
# opentelemetry-sdk
# opentelemetry-semantic-conventions
opentelemetry-exporter-otlp==1.41.1 \
--hash=sha256:299a2f0541ca175df186f5ac58fd5db177ba1e9b72b0826049062f750d55b47f \
--hash=sha256:db276c5a80c02b063994e80950d00ca1bfddcf6520f608335b7dc2db0c0eb9c6
opentelemetry-exporter-otlp==1.42.1 \
--hash=sha256:2d9ebaed714377a67d224d46795ddcc11d2c877fa5de35fda70b6f3b010729a9 \
--hash=sha256:aedd54545bb0587cd45210abdc8be545af9c01413f3307786e276df1e3c83bee
# via -r src/backend/requirements.in
opentelemetry-exporter-otlp-proto-common==1.41.1 \
--hash=sha256:0e253156ea9c36b0bd3d2440c5c9ba7dd1f3fb64ba7a08fc85fbac536b56e1fb \
--hash=sha256:10da74dad6a49344b9b7b21b6182e3060373a235fde1528616d5f01f92e66aa9
opentelemetry-exporter-otlp-proto-common==1.42.1 \
--hash=sha256:04f1f01fb597c4249dfcd7f8b861c902c2102369d376d9d346ff38de4469a2ee \
--hash=sha256:f48d395ab815b444da118868977e9798ea354c25737d5cf39578ae894011c140
# via
# opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http
opentelemetry-exporter-otlp-proto-grpc==1.41.1 \
--hash=sha256:537926dcef951136992479af1d9cd88f25e33d56c530e9f020ed57774dca2f94 \
--hash=sha256:b05df8fa1333dc9a3fda36b676b96b5095ab6016d3f0c3296d430d629ba1443b
opentelemetry-exporter-otlp-proto-grpc==1.42.1 \
--hash=sha256:0ae1177e2038b18a929b3098215243631ef91136cba26b7e2b12790ceb7e87cc \
--hash=sha256:975c4461f167dd8ed8857d68d3b6b25f3d272eab896f6a9470d0f5b90e2faf15
# via opentelemetry-exporter-otlp
opentelemetry-exporter-otlp-proto-http==1.41.1 \
--hash=sha256:1a21e8f49c7a946d935551e90947d6c3eb39236723c6624401da0f33d68edcb4 \
--hash=sha256:4747a9604c8550ab38c6fd6180e2fcb80de3267060bef2c306bad3cb443302bc
opentelemetry-exporter-otlp-proto-http==1.42.1 \
--hash=sha256:00a16da1b312a1d6c7233d600d557c91df71125af73020f3b9a7765bd699d59d \
--hash=sha256:bf142a21035d7571ac3a09cb2e5639f49886f243972883cfe777ed3bf02b734d
# via opentelemetry-exporter-otlp
opentelemetry-instrumentation==0.62b1 \
--hash=sha256:90e92a905ba4f84db06ac3aec96701df6c079b2d66e9379f8739f0a1bdcc7f45 \
--hash=sha256:976fc6e640f2006599e97429c949e622c108d0c17c2059347d1e6c93c707f257
opentelemetry-instrumentation==0.63b1 \
--hash=sha256:32368d6ae52c8de20aa790a6ad86b10a76f09956092337ae37d675773990e541 \
--hash=sha256:f1986716d52cc316ea5f60189098726a9071d8ecc0eee96c9ed110be08bade9c
# via
# opentelemetry-instrumentation-dbapi
# opentelemetry-instrumentation-django
@ -1234,75 +1234,76 @@ opentelemetry-instrumentation==0.62b1 \
# opentelemetry-instrumentation-sqlite3
# opentelemetry-instrumentation-system-metrics
# opentelemetry-instrumentation-wsgi
opentelemetry-instrumentation-dbapi==0.62b1 \
--hash=sha256:82755a0f013980b2c000a10452fb91ab5135b937a8afe37c45f34e62ecc7a2b8 \
--hash=sha256:dd2cc4085e832a708835218833b839a88ef0ace66851d14debefe6b2ef363a3c
opentelemetry-instrumentation-dbapi==0.63b1 \
--hash=sha256:1bc842ddcacb7ec3622ef306ffdb3c584bf97df79d381e03604ea6eb23c63781 \
--hash=sha256:406978ed56bcfc5fd246fd918e6b36d0f5de26fa396c78cf63326a7b530597c8
# via
# opentelemetry-instrumentation-psycopg
# opentelemetry-instrumentation-pymysql
# opentelemetry-instrumentation-sqlite3
opentelemetry-instrumentation-django==0.62b1 \
--hash=sha256:268c9b3d234510ab835acea0524ba65361389f6e2c291b495ba8072c7bb59003 \
--hash=sha256:5ac96ef25019fbea5e2739c9b4b7ce17e4379e2b1ec9fdce6b696a5c09ceabbe
opentelemetry-instrumentation-django==0.63b1 \
--hash=sha256:909ea4afbb18f16eb811d7ac330ed0f6fda7765191d49bccb8cca38c4fbc005f \
--hash=sha256:f2071d2f92e4779c5a14dd452b0dfe426343599e6efa9d888304fb639a9f3101
# via -r src/backend/requirements.in
opentelemetry-instrumentation-psycopg==0.62b1 \
--hash=sha256:61c8a1794d6e11c7bca1735c2b87ef8c04b5d7202e962409d66d204f84c01be1 \
--hash=sha256:f08e843e81656271ddc8c8194a54a5f3c8ebe0cb137e3166af9a2ff014978b5e
opentelemetry-instrumentation-psycopg==0.63b1 \
--hash=sha256:4d74ec2dda8e54ead9bd71f134af984792f77e1d8809591ae53edadddd4c8fc0 \
--hash=sha256:86819f4cccea21a47920807826495797b7261cc1893c8e855415e76fb7b8e2e8
# via -r src/backend/requirements.in
opentelemetry-instrumentation-pymysql==0.62b1 \
--hash=sha256:12e42f891978a3a41bc564bb81e2299d3654659589f705064a4bb10e93e493f8 \
--hash=sha256:50d2efdde1bd8d826cbceffe5b3ce0ffd421349d609200eab0f3e632945cd304
opentelemetry-instrumentation-pymysql==0.63b1 \
--hash=sha256:2cf6a24e136b5b0c3f6e59675fc44b42ad2b97474b075a82859f9868a9252648 \
--hash=sha256:6b54b83c86ac6c2e8dae642bbee4894e2283ceb5da23d571f1abb7060f7b62f4
# via -r src/backend/requirements.in
opentelemetry-instrumentation-redis==0.62b1 \
--hash=sha256:2d3c421d95e05ade075bee5becbe34e743b1cdf5bdee2085cb524f88c4f13dcb \
--hash=sha256:9aedd02c1acf631251d1d676634db47da9da04e0a626cd0c7d83fe0eb791d165
opentelemetry-instrumentation-redis==0.63b1 \
--hash=sha256:28d235159df43cc2bc8779af5c602afad1e08603fff75ac8ca34dd1bf30a9cb9 \
--hash=sha256:f0e51c4006f68e340abbf28a7995feff004de78649697cbdf3bac0072cacd082
# via -r src/backend/requirements.in
opentelemetry-instrumentation-requests==0.62b1 \
--hash=sha256:67a79c4b67e2192445c1cf03d62126fa623065688d8bd1a9f87f858b0e5f0286 \
--hash=sha256:ca348f2f51b715c21e86d82106d784f7069ae849c3e636ab37e34dc0ba510b8c
opentelemetry-instrumentation-requests==0.63b1 \
--hash=sha256:513fcaa3d93debbdb359c00ce1a137a34a89ee908c51ac43beb7e8c18ac2b3cd \
--hash=sha256:935c980a11e33bfd7ed969c741e4bd7c84077045651469f10e163534368d87f7
# via -r src/backend/requirements.in
opentelemetry-instrumentation-sqlite3==0.62b1 \
--hash=sha256:14ba18aed35a01e1d89bb814e49f7d6dd90bb68b2034ccf047801ca4659dba27 \
--hash=sha256:3ab0d614bab9956126ce81a22315bf58d56d653a65675a66e8b525f880ec3016
opentelemetry-instrumentation-sqlite3==0.63b1 \
--hash=sha256:3d61afda8358dc32135fabd27e5934bd25ea0eed68d64c34508f24ba8d723efc \
--hash=sha256:cc11c68cccde061dc8bcc5f80fe00bb0105a0fbb11b390859a23ca91ccd4e3b6
# via -r src/backend/requirements.in
opentelemetry-instrumentation-system-metrics==0.62b1 \
--hash=sha256:260130cb63e3028290305d8f25b744fb3a279677769dd65287ce60086cc11d32 \
--hash=sha256:b8097e0d3747102fa545723bb67f50f572a7618b6479359fa6533007d2f621aa
opentelemetry-instrumentation-system-metrics==0.63b1 \
--hash=sha256:995051f47876d79461aed8b7aa205d4584d90794ef864342cc748929c389bb42 \
--hash=sha256:d6d4d7a1a854be4165143cf6420ee5894188762eb367d7bf9da5be4a83a4b632
# via -r src/backend/requirements.in
opentelemetry-instrumentation-wsgi==0.62b1 \
--hash=sha256:02a364fd9c940a46b19c825c5bfe386b007d5292ef91573894164836953fe831 \
--hash=sha256:a2df11de0113f504043e2b0fa0288238a93ee49ff607bd5100cb2d3a75bc771f
opentelemetry-instrumentation-wsgi==0.63b1 \
--hash=sha256:03d61c4678ce82402e7f37b6a3dbd84cb97b85b3cb416a78c2e74c7c6d9451fa \
--hash=sha256:86779715262227d3436bdfb16aabd1c524b0f236725a69e8754ceda76c6d79dc
# via
# -r src/backend/requirements.in
# opentelemetry-instrumentation-django
opentelemetry-proto==1.41.1 \
--hash=sha256:0496713b804d127a4147e32849fbaf5683fac8ee98550e8e7679cd706c289720 \
--hash=sha256:4b9d2eb631237ea43b80e16c073af438554e32bc7e9e3f8ca4a9582f900020e5
opentelemetry-proto==1.42.1 \
--hash=sha256:c6a51e6b4f05ae63565f3a113217f3d2bfaec68f78c02d7a6c85f9010d1cfca6 \
--hash=sha256:dedb74cba2886c59c7789b227a7a670613025a07489040050aedff6e5c0fb43c
# via
# opentelemetry-exporter-otlp-proto-common
# opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http
opentelemetry-sdk==1.41.1 \
--hash=sha256:724b615e1215b5aeacda0abb8a6a8922c9a1853068948bd0bd225a56d0c792e6 \
--hash=sha256:edee379c126c1bce952b0c812b48fe8ff35b30df0eecf17e98afa4d598b7d85d
opentelemetry-sdk==1.42.1 \
--hash=sha256:083cd4bbfaa5aa7b5a9e552430d9951219967cfb27aa61feb13a77aba1fc839d \
--hash=sha256:8c834e8f8c9ba4171d4ec843d0cb8a67e4c7394d3f9e9297e582cbd9456ddbf7
# via
# -r src/backend/requirements.in
# opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http
opentelemetry-semantic-conventions==0.62b1 \
--hash=sha256:c5cc6e04a7f8c7cdd30be2ed81499fa4e75bfbd52c9cb70d40af1f9cd3619802 \
--hash=sha256:cf506938103d331fbb78eded0d9788095f7fd59016f2bda813c3324e5a74a93c
opentelemetry-semantic-conventions==0.63b1 \
--hash=sha256:3daf963611334b365e98a57438183eb012d3bfb40b2d931a9af613476b8701a9 \
--hash=sha256:dfe5ef4dee82586b746f522b818ceb298d00b3d59f660042bd79404bff8d0682
# via
# opentelemetry-instrumentation
# opentelemetry-instrumentation-dbapi
# opentelemetry-instrumentation-django
# opentelemetry-instrumentation-redis
# opentelemetry-instrumentation-requests
# opentelemetry-instrumentation-system-metrics
# opentelemetry-instrumentation-wsgi
# opentelemetry-sdk
opentelemetry-util-http==0.62b1 \
--hash=sha256:adf6facbb89aef8f8bc566e2f04624942ba08a7b678b3479a91051a8f4dc70a3 \
--hash=sha256:c57e8a6c19fc422c288e6074e882f506f85030b69b7376182f74f9257b9261f0
opentelemetry-util-http==0.63b1 \
--hash=sha256:6284194028c59cd439f8acfe388145069a6127f11dc077e1344a2094adacc3f8 \
--hash=sha256:ba1268f00922ee522dba2ae38458060f99486e7385a8056985901ca9685adfff
# via
# opentelemetry-instrumentation-django
# opentelemetry-instrumentation-requests
@ -1490,9 +1491,9 @@ pydyf==0.12.1 \
--hash=sha256:ea25b4e1fe7911195cb57067560daaa266639184e8335365cc3ee5214e7eaadc \
--hash=sha256:fbd7e759541ac725c29c506612003de393249b94310ea78ae44cb1d04b220095
# via weasyprint
pyjwt[crypto]==2.12.1 \
--hash=sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c \
--hash=sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b
pyjwt[crypto]==2.13.0 \
--hash=sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423 \
--hash=sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728
# via
# django-allauth
# djangorestframework-simplejwt
@ -1523,9 +1524,9 @@ pynacl==1.6.2 \
--hash=sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2 \
--hash=sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9
# via paramiko
pypdf==6.11.0 \
--hash=sha256:062b51c81b0910e6d2755e99e1c5547a0a23b7d0a32322af66240d8edcfabe87 \
--hash=sha256:769394d5756d5b304c9b6bef88b54b1816b328e7e6fc9254e625529a15ed4ab8
pypdf==6.12.1 \
--hash=sha256:3953a097b9f26d4e0ead5ff95943d9971377557662a91d8872186053cd71d30a \
--hash=sha256:8fa2a2321cf16247ed848bd7c97f193a60c08670d04abed5b0138327e51c43b0
# via -r src/backend/requirements.in
pyphen==0.17.2 \
--hash=sha256:3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd \
@ -2130,10 +2131,6 @@ xmlsec==1.3.17 \
# via
# -r src/backend/requirements.in
# python3-saml
zipp==3.23.1 \
--hash=sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc \
--hash=sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110
# via importlib-metadata
zopfli==0.4.1 \
--hash=sha256:02086247dd12fda929f9bfe8b3962b6bcdbfc8c82e99255aebcf367867cf0760 \
--hash=sha256:07a5cdc5d1aaa6c288c5d9f5a5383042ba743641abf8e2fd898dcad622d8a38e \

View File

@ -10,6 +10,7 @@ export interface ModelInformationInterface {
url_detail?: string;
api_endpoint: ApiEndpoints;
admin_url?: string;
pk_field?: string;
supports_barcode?: boolean;
icon: keyof InvenTreeIconType;
}

View File

@ -2,6 +2,7 @@ import type { DefaultMantineColor, MantineStyleProp } from '@mantine/core';
import type { UseFormReturnType } from '@mantine/form';
import type { JSX, ReactNode } from 'react';
import type { FieldValues, UseFormReturn } from 'react-hook-form';
import type { NavigateFunction } from 'react-router-dom';
import type { ApiEndpoints } from '../enums/ApiEndpoints';
import type { ModelType } from '../enums/ModelType';
import type { PathParams, UiSizeType } from './Core';
@ -160,6 +161,7 @@ export type ApiFormFieldSet = Record<string, ApiFormFieldType>;
* @param modelType : Define a model type for this form
* @param follow : Boolean, follow the result of the form (if possible)
* @param table : Table to update on success (if provided)
* @param navigate : Optional navigate function to use for following results (if follow is true)
*/
export interface ApiFormProps {
url: ApiEndpoints | string;
@ -189,6 +191,7 @@ export interface ApiFormProps {
follow?: boolean;
actions?: ApiFormAction[];
timeout?: number;
navigate?: NavigateFunction;
keepOpenOption?: boolean;
onKeepOpenChange?: (keepOpen: boolean) => void;
}

View File

@ -15,6 +15,7 @@ export interface InstanceRenderInterface {
link?: boolean;
navigate?: any;
showSecondary?: boolean;
showHover?: boolean;
extra?: Record<string, any>;
}

View File

@ -115,7 +115,7 @@
"@lingui/babel-plugin-lingui-macro": "^5.9.2",
"@lingui/cli": "^5.9.2",
"@lingui/macro": "^5.9.2",
"@playwright/test": "^1.58.2",
"@playwright/test": "^1.16.0",
"@types/node": "^25.5.0",
"@types/qrcode": "^1.5.5",
"@types/react": "^19.2.14",

View File

@ -5,6 +5,7 @@ import { IconX } from '@tabler/icons-react';
import { Boundary } from '@lib/components/Boundary';
import type { ModelType } from '@lib/index';
import type { InvenTreeIconType } from '@lib/types/Icons';
import type { JSX } from 'react';
/**
@ -22,6 +23,7 @@ export interface DashboardWidgetProps {
minWidth?: number;
minHeight?: number;
modelType?: ModelType;
icon?: keyof InvenTreeIconType;
render: () => JSX.Element;
visible?: () => boolean;
}

View File

@ -12,10 +12,11 @@ import {
Tooltip
} from '@mantine/core';
import { useDebouncedValue } from '@mantine/hooks';
import { IconBackspace, IconLayoutGridAdd } from '@tabler/icons-react';
import { IconBackspace } from '@tabler/icons-react';
import { useMemo, useState } from 'react';
import { StylishText } from '@lib/components/StylishText';
import { InvenTreeIcon } from '../../functions/icons';
import { useDashboardItems } from '../../hooks/UseDashboardItems';
/**
@ -105,7 +106,7 @@ export default function DashboardWidgetDrawer({
onAddWidget(widget.label);
}}
>
<IconLayoutGridAdd />
<InvenTreeIcon icon={widget.icon ?? 'dashboard'} />
</ActionIcon>
</Tooltip>
</Table.Td>

View File

@ -8,6 +8,7 @@ import ColorToggleDashboardWidget from './widgets/ColorToggleWidget';
import GetStartedWidget from './widgets/GetStartedWidget';
import LanguageSelectDashboardWidget from './widgets/LanguageSelectWidget';
import NewsWidget from './widgets/NewsWidget';
import OrderHistoryWidget from './widgets/OrderHistoryWidget';
import QueryCountDashboardWidget from './widgets/QueryCountDashboardWidget';
import QueryDashboardWidget from './widgets/QueryDashboardWidget';
import StocktakeDashboardWidget from './widgets/StocktakeDashboardWidget';
@ -43,6 +44,7 @@ function BuiltinQueryCountWidgets(): DashboardWidgetProps[] {
title: t`Invalid BOMs`,
description: t`Assemblies requiring bill of materials validation`,
modelType: ModelType.part,
icon: 'exclamation',
params: {
active: true, // Only show active parts
assembly: true, // Only show parts which are assemblies
@ -94,7 +96,8 @@ function BuiltinQueryCountWidgets(): DashboardWidgetProps[] {
description: t`Show the number of stock items which have expired`,
modelType: ModelType.stockitem,
params: { expired: true },
enabled: globalSettings.isSet('STOCK_ENABLE_EXPIRY')
enabled: globalSettings.isSet('STOCK_ENABLE_EXPIRY'),
icon: 'overdue'
}),
QueryCountDashboardWidget({
title: t`Stale Stock Items`,
@ -116,14 +119,16 @@ function BuiltinQueryCountWidgets(): DashboardWidgetProps[] {
label: 'ovr-bo',
description: t`Show the number of build orders which are overdue`,
modelType: ModelType.build,
params: { overdue: true }
params: { overdue: true },
icon: 'overdue'
}),
QueryCountDashboardWidget({
title: t`Assigned Build Orders`,
label: 'asn-bo',
description: t`Show the number of build orders which are assigned to you`,
modelType: ModelType.build,
params: { assigned_to_me: true, outstanding: true }
params: { assigned_to_me: true, outstanding: true },
icon: 'responsible'
}),
QueryCountDashboardWidget({
title: t`Active Sales Orders`,
@ -137,14 +142,16 @@ function BuiltinQueryCountWidgets(): DashboardWidgetProps[] {
label: 'ovr-so',
description: t`Show the number of sales orders which are overdue`,
modelType: ModelType.salesorder,
params: { overdue: true }
params: { overdue: true },
icon: 'overdue'
}),
QueryCountDashboardWidget({
title: t`Assigned Sales Orders`,
label: 'asn-so',
description: t`Show the number of sales orders which are assigned to you`,
modelType: ModelType.salesorder,
params: { assigned_to_me: true, outstanding: true }
params: { assigned_to_me: true, outstanding: true },
icon: 'responsible'
}),
QueryCountDashboardWidget({
title: t`Pending Shipments`,
@ -165,14 +172,16 @@ function BuiltinQueryCountWidgets(): DashboardWidgetProps[] {
label: 'ovr-po',
description: t`Show the number of purchase orders which are overdue`,
modelType: ModelType.purchaseorder,
params: { overdue: true }
params: { overdue: true },
icon: 'overdue'
}),
QueryCountDashboardWidget({
title: t`Assigned Purchase Orders`,
label: 'asn-po',
description: t`Show the number of purchase orders which are assigned to you`,
modelType: ModelType.purchaseorder,
params: { assigned_to_me: true, outstanding: true }
params: { assigned_to_me: true, outstanding: true },
icon: 'responsible'
}),
QueryCountDashboardWidget({
title: t`Active Return Orders`,
@ -186,14 +195,16 @@ function BuiltinQueryCountWidgets(): DashboardWidgetProps[] {
label: 'ovr-ro',
description: t`Show the number of return orders which are overdue`,
modelType: ModelType.returnorder,
params: { overdue: true }
params: { overdue: true },
icon: 'overdue'
}),
QueryCountDashboardWidget({
title: t`Assigned Return Orders`,
label: 'asn-ro',
description: t`Show the number of return orders which are assigned to you`,
modelType: ModelType.returnorder,
params: { assigned_to_me: true, outstanding: true }
params: { assigned_to_me: true, outstanding: true },
icon: 'responsible'
})
];
@ -207,6 +218,26 @@ function BuiltinQueryCountWidgets(): DashboardWidgetProps[] {
});
}
function BuiltinHistoryWidgets(): DashboardWidgetProps[] {
return [
OrderHistoryWidget({
modelType: ModelType.build
}),
OrderHistoryWidget({
modelType: ModelType.salesorder
}),
OrderHistoryWidget({
modelType: ModelType.purchaseorder
}),
OrderHistoryWidget({
modelType: ModelType.returnorder
}),
OrderHistoryWidget({
modelType: ModelType.transferorder
})
];
}
function BuiltinGettingStartedWidgets(): DashboardWidgetProps[] {
return [
{
@ -215,6 +246,7 @@ function BuiltinGettingStartedWidgets(): DashboardWidgetProps[] {
description: t`Getting started with InvenTree`,
minWidth: 5,
minHeight: 4,
icon: 'info',
render: () => <GetStartedWidget />
},
{
@ -223,6 +255,7 @@ function BuiltinGettingStartedWidgets(): DashboardWidgetProps[] {
description: t`The latest news from InvenTree`,
minWidth: 5,
minHeight: 4,
icon: 'news',
render: () => <NewsWidget />
}
];
@ -243,6 +276,7 @@ function BuiltinActionWidgets(): DashboardWidgetProps[] {
export default function DashboardWidgetLibrary(): DashboardWidgetProps[] {
return [
...BuiltinQueryCountWidgets(),
...BuiltinHistoryWidgets(),
...BuiltinGettingStartedWidgets(),
...BuiltinSettingsWidgets(),
...BuiltinActionWidgets()

View File

@ -0,0 +1,131 @@
import { ModelInformationDict } from '@lib/enums/ModelInformation';
import type { ModelType } from '@lib/enums/ModelType';
import { apiUrl } from '@lib/functions/Api';
import { StylishText } from '@lib/index';
import { t } from '@lingui/core/macro';
import { BarChart } from '@mantine/charts';
import { Stack } from '@mantine/core';
import { useDocumentVisibility } from '@mantine/hooks';
import { useQuery } from '@tanstack/react-query';
import dayjs from 'dayjs';
import { useMemo } from 'react';
import { useApi } from '../../../contexts/ApiContext';
import { useUserState } from '../../../states/UserState';
import type { DashboardWidgetProps } from '../DashboardWidget';
function OrderHistoryComponent({
modelType,
title
}: {
modelType: ModelType;
title: string;
}) {
const modelInfo = useMemo(() => {
return ModelInformationDict[modelType];
}, [modelType]);
const url = useMemo(() => {
return apiUrl(modelInfo.api_endpoint);
}, [modelInfo]);
const api = useApi();
const visibility = useDocumentVisibility();
const endDate = dayjs().add(1, 'day').format('YYYY-MM-DD');
const startDate = dayjs()
.subtract(12, 'month')
.subtract(1, 'day')
.format('YYYY-MM-DD');
const params = {
completed_after: startDate,
completed_before: endDate
};
const query = useQuery({
queryKey: ['dashboard-order-summary', modelType, params, visibility],
enabled: visibility === 'visible',
refetchOnWindowFocus: true,
refetchOnMount: true,
refetchInterval: 10 * 60 * 1000, // 10 minute refetch interval
staleTime: 5 * 60 * 1000, // 5 minute stale time
queryFn: () => {
if (visibility !== 'visible') {
return [];
}
return api.get(url, { params }).then((res) => {
return res.data ?? [];
});
}
});
const chartData = useMemo(() => {
const months = Array.from({ length: 13 }, (_, i) => ({
month: dayjs()
.subtract(12 - i, 'month')
.format('MMM YY'),
count: 0
}));
for (const order of query.data || []) {
// Build orders use `completion_date`; all other order types use `complete_date`
const dateStr =
order.complete_date ?? order.completion_date ?? order.shipment_date;
if (!dateStr) continue;
const label = dayjs(dateStr).format('MMM YY');
const entry = months.find((m) => m.month === label);
if (entry) entry.count++;
}
return months;
}, [query.data]);
return (
<Stack gap='xs'>
<StylishText size='md'>{title}</StylishText>
<BarChart
h={200}
data={chartData}
dataKey='month'
series={[{ name: 'count', label: t`Completed`, color: 'blue.6' }]}
withYAxis={false}
/>
</Stack>
);
}
/**
* Display a simple chart of the number of completed orders per month, for the last 12 months.
*/
export default function OrderHistoryWidget({
modelType
}: {
modelType: ModelType;
}): DashboardWidgetProps {
const user = useUserState();
const modelInfo = useMemo(() => {
return ModelInformationDict[modelType];
}, [modelType]);
// Extract translated model labels
const models = modelInfo.label_multiple();
return {
label: `${modelType}-history`,
title: t`Completed ${models}`,
description: t`Display number of completed ${models} per month`,
minHeight: 2,
minWidth: 3,
modelType: modelType,
icon: 'chart_bar',
visible: () => user.hasViewPermission(modelType),
render: () => (
<OrderHistoryComponent
modelType={modelType}
title={t`Completed ${models}`}
/>
)
};
}

View File

@ -129,6 +129,7 @@ export default function QueryCountDashboardWidget({
title,
description,
modelType,
icon,
enabled = true,
params
}: {
@ -136,6 +137,7 @@ export default function QueryCountDashboardWidget({
title: string;
description: string;
modelType: ModelType;
icon?: keyof InvenTreeIconType;
enabled?: boolean;
params: any;
}): DashboardWidgetProps {
@ -147,6 +149,7 @@ export default function QueryCountDashboardWidget({
modelType: modelType,
minWidth: 2,
minHeight: 1,
icon: icon,
render: () => (
<QueryCountWidget modelType={modelType} title={title} params={params} />
)

View File

@ -66,6 +66,7 @@ export default function StocktakeDashboardWidget(): DashboardWidgetProps {
minHeight: 1,
minWidth: 2,
render: () => <StocktakeWidget />,
icon: 'stocktake',
enabled: user.hasAddRole(UserRoles.part)
};
}

View File

@ -20,7 +20,7 @@ import {
type SubmitHandler,
useForm
} from 'react-hook-form';
import { type NavigateFunction, useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { Boundary } from '@lib/components/Boundary';
import { isTrue } from '@lib/functions/Conversion';
@ -176,14 +176,15 @@ export function ApiForm({
props.onKeepOpenChange?.(v);
};
// Accessor for the navigation function (which is used to redirect the user)
let navigate: NavigateFunction | null = null;
let navigate = props.navigate || null;
try {
navigate = useNavigate();
} catch (_error) {
// Note: If we launch a form within a plugin context, useNavigate() may not be available
navigate = null;
if (!navigate) {
try {
navigate = useNavigate();
} catch (_error) {
// Note: If we launch a form within a plugin context, useNavigate() may not be available
navigate = null;
}
}
const [fields, setFields] = useState<ApiFormFieldSet>(
@ -658,6 +659,7 @@ export function ApiForm({
definition={field}
control={form.control}
url={url}
navigate={navigate}
setFields={setFields}
onKeyDown={(value) => {
if (

View File

@ -6,6 +6,7 @@ import { type Control, type FieldValues, useController } from 'react-hook-form';
import type { ApiFormFieldSet, ApiFormFieldType } from '@lib/types/Forms';
import { IconFileUpload } from '@tabler/icons-react';
import type { NavigateFunction } from 'react-router-dom';
import DateTimeField from '../DateTimeField';
import { BooleanField } from './BooleanField';
import { ChoiceField } from './ChoiceField';
@ -26,6 +27,7 @@ export function ApiFormField({
definition,
control,
hideLabels,
navigate,
url,
setFields,
onKeyDown
@ -34,6 +36,7 @@ export function ApiFormField({
definition: ApiFormFieldType;
control: Control<FieldValues, any>;
hideLabels?: boolean;
navigate?: NavigateFunction | null;
url?: string;
setFields?: React.Dispatch<React.SetStateAction<ApiFormFieldSet>>;
onKeyDown?: (value: any) => void;
@ -119,9 +122,10 @@ export function ApiFormField({
case 'related field':
return (
<RelatedModelField
controller={controller}
definition={fieldDefinition}
controller={controller}
fieldName={fieldName}
navigate={navigate}
/>
);
case 'email':

View File

@ -32,6 +32,7 @@ import {
import { apiUrl } from '@lib/functions/Api';
import type { ApiFormFieldType } from '@lib/types/Forms';
import { IconPlus } from '@tabler/icons-react';
import type { NavigateFunction } from 'react-router-dom';
import { useApi } from '../../../contexts/ApiContext';
import { useCreateApiFormModal } from '../../../hooks/UseForm';
import {
@ -50,11 +51,13 @@ export function RelatedModelField({
controller,
fieldName,
definition,
navigate,
limit = 10
}: Readonly<{
controller: UseControllerReturn<FieldValues, any>;
definition: ApiFormFieldType;
fieldName: string;
navigate?: NavigateFunction | null;
limit?: number;
}>) {
const api = useApi();

View File

@ -271,7 +271,7 @@ function NavTabs() {
// static content
mainNavTabs.forEach((tab) => {
if (tab.role && !user.hasViewRole(tab.role)) {
if (tab.visible === false) {
return;
}

View File

@ -1,16 +1,20 @@
import { t } from '@lingui/core/macro';
import {
ActionIcon,
Alert,
Anchor,
Box,
Group,
HoverCard,
type MantineSize,
Paper,
Skeleton,
Space,
Stack,
Text
} from '@mantine/core';
import { useQuery } from '@tanstack/react-query';
import { type ReactNode, useCallback } from 'react';
import { type ReactNode, useCallback, useMemo } from 'react';
import { ModelInformationDict } from '@lib/enums/ModelInformation';
import { ModelType } from '@lib/enums/ModelType';
@ -24,9 +28,16 @@ import type {
} from '@lib/types/Rendering';
export type { InstanceRenderInterface } from '@lib/types/Rendering';
import { getBaseUrl, navigateToLink, shortenString } from '@lib/index';
import {
getBaseUrl,
getDetailUrl,
navigateToLink,
shortenString
} from '@lib/index';
import { IconLink } from '@tabler/icons-react';
import { useApi } from '../../contexts/ApiContext';
import { usePluginState } from '../../states/PluginState';
import { useUserSettingsState } from '../../states/SettingsStates';
import { Thumbnail } from '../images/Thumbnail';
import { RenderBuildItem, RenderBuildLine, RenderBuildOrder } from './Build';
import {
@ -125,11 +136,98 @@ export function RenderInstance(props: RenderInstanceProps): ReactNode {
props.custom_model ?? props.model ?? ''
);
// provider component
if (!RenderComponent) {
return <UnknownRenderer model={props.model} />;
}
return <RenderComponent {...props} />;
const userSettings = useUserSettingsState();
// Extract model information from the defined model type
const modelInfo = useMemo(() => {
if (!props.model) {
return undefined;
}
return ModelInformationDict[
props.model.toString().toLowerCase() as ModelType
];
}, [props.model]);
const showHover: boolean = useMemo(() => {
if (!modelInfo) {
return false;
}
// Override with the props.showHover attribute
if (props.showHover !== undefined) {
return props.showHover;
}
// If not specified, fall back to the user configured setting
return userSettings.isSet('SHOW_EXTRA_MODEL_INFO');
}, [props.showHover, modelInfo, userSettings]);
// Extract model ID from the provided instance data, using the defined primary key field (or 'pk' as a fallback)
const modelId = useMemo(() => {
if (!modelInfo || !props.instance) {
return undefined;
}
return props.instance[modelInfo.pk_field ?? 'pk'];
}, [modelInfo, props.instance]);
const detailUrl = useMemo(() => {
if (!props.model) {
return undefined;
}
return getDetailUrl(props.model, modelId, true);
}, [props.model]);
return (
<HoverCard
disabled={!showHover}
position='top-end'
withinPortal
openDelay={500}
closeDelay={100}
zIndex={99999}
>
<HoverCard.Target>
<Box>
{!!RenderComponent ? (
<RenderComponent {...props} />
) : (
<UnknownRenderer model={props.model} />
)}
</Box>
</HoverCard.Target>
<HoverCard.Dropdown>
<Stack gap='xs'>
<Group justify='space-between'>
<Text size='sm' fw='bold'>
{modelInfo?.label()}
</Text>
{modelId && <Text size='xs'>{`[${t`ID`}: ${modelId}]`}</Text>}
</Group>
{detailUrl && (
<Anchor
href={detailUrl}
target='_blank'
onClick={(event) => {
if (props.navigate) {
navigateToLink(detailUrl, props.navigate, event);
}
}}
>
<Group gap='xs' wrap='nowrap'>
<ActionIcon variant='transparent' size='xs'>
<IconLink />
</ActionIcon>
<Text size='sm'>{t`View details`}</Text>
</Group>
</Anchor>
)}
</Stack>
</HoverCard.Dropdown>
</HoverCard>
);
}
export function RenderRemoteInstance({

View File

@ -124,6 +124,17 @@ export function getActions(navigate: NavigateFunction) {
leftSection: <IconLink size='1.2rem' />
});
globalSettings.isSet('TRANSFERORDER_ENABLED') &&
user?.hasViewRole(UserRoles.transfer_order) &&
_actions.push({
id: 'transfer-orders',
label: t`Transfer Orders`,
description: t`Go to Transfer Orders`,
onClick: () =>
navigate(ModelInformationDict['transferorder'].url_overview!),
leftSection: <IconLink size='1.2rem' />
});
globalSettings.isSet('RETURNORDER_ENABLED') &&
user?.hasViewRole(UserRoles.return_order) &&
_actions.push({
@ -172,6 +183,17 @@ export function getActions(navigate: NavigateFunction) {
});
staff &&
user?.hasViewPermission(ModelType.error) &&
_actions.push({
id: 'error-logs',
label: t`Error Logs`,
description: t`View error logs for this instance`,
onClick: () => navigate('/settings/admin/errors'),
leftSection: <IconReport size='1.2rem' />
});
staff &&
user?.hasViewPermission(ModelType.pluginconfig) &&
_actions.push({
id: 'plugin-settings',
label: t`Plugins`,

View File

@ -16,15 +16,18 @@ import {
} from '@tabler/icons-react';
import type { ReactNode } from 'react';
import type { MenuLinkItem } from '../components/items/MenuLinks';
import { useGlobalSettingsState } from '../states/SettingsStates';
type NavTab = {
name: string;
title: string;
icon: ReactNode;
role?: UserRoles;
visible?: boolean;
};
export function getNavTabs(user: UserStateProps): NavTab[] {
const globalSettings = useGlobalSettingsState.getState();
const navTabs: NavTab[] = [
{
name: 'home',
@ -35,37 +38,45 @@ export function getNavTabs(user: UserStateProps): NavTab[] {
name: 'part',
title: t`Parts`,
icon: <IconBox />,
role: UserRoles.part
visible:
user.hasViewRole(UserRoles.part) ||
user.hasViewRole(UserRoles.part_category)
},
{
name: 'stock',
title: t`Stock`,
icon: <IconPackages />,
role: UserRoles.stock
visible:
user.hasViewRole(UserRoles.stock) ||
user.hasViewRole(UserRoles.stock_location) ||
(globalSettings.isSet('TRANSFERORDER_ENABLED') &&
user.hasViewRole(UserRoles.transfer_order))
},
{
name: 'manufacturing',
title: t`Manufacturing`,
icon: <IconBuildingFactory2 />,
role: UserRoles.build
visible: user.hasViewRole(UserRoles.build)
},
{
name: 'purchasing',
title: t`Purchasing`,
icon: <IconShoppingCart />,
role: UserRoles.purchase_order
visible: user.hasViewRole(UserRoles.purchase_order)
},
{
name: 'sales',
title: t`Sales`,
icon: <IconTruckDelivery />,
role: UserRoles.sales_order
visible:
user.hasViewRole(UserRoles.sales_order) ||
(globalSettings.isSet('RETURNORDER_ENABLED') &&
user.hasViewRole(UserRoles.return_order))
}
];
return navTabs.filter((tab) => {
if (!tab.role) return true;
return user.hasViewRole(tab.role);
return tab.visible !== false;
});
}

View File

@ -1,7 +1,7 @@
import type { ApiFormFieldSet } from '@lib/types/Forms';
import { t } from '@lingui/core/macro';
import { IconBuildingStore, IconCopy, IconPackages } from '@tabler/icons-react';
import { useMemo, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useGlobalSettingsState } from '../states/SettingsStates';
/**
@ -23,6 +23,12 @@ export function usePartFields({
undefined
);
// Set the initial state for the tracked fields based on the global settings
useEffect(() => {
setVirtual(globalSettings.isSet('PART_VIRTUAL'));
setPurchaseable(globalSettings.isSet('PART_PURCHASEABLE'));
}, [partId, create]);
return useMemo(() => {
const fields: ApiFormFieldSet = {
category: {

View File

@ -1,3 +1,16 @@
import { ActionButton } from '@lib/components/ActionButton';
import { StylishText } from '@lib/components/StylishText';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType';
import { apiUrl } from '@lib/functions/Api';
import { getDetailUrl } from '@lib/functions/Navigation';
import type {
ApiFormAdjustFilterType,
ApiFormFieldChoice,
ApiFormFieldSet,
ApiFormModalProps,
StockOperationProps
} from '@lib/types/Forms';
import { t } from '@lingui/core/macro';
import {
Alert,
@ -20,27 +33,13 @@ import {
IconUsersGroup
} from '@tabler/icons-react';
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
import { type JSX, Suspense, useEffect, useMemo, useState } from 'react';
import { ActionButton } from '@lib/components/ActionButton';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType';
import dayjs from 'dayjs';
import { type JSX, Suspense, useEffect, useMemo, useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import { api } from '../App';
import RemoveRowButton from '../components/buttons/RemoveRowButton';
import { StandaloneField } from '../components/forms/StandaloneField';
import { StylishText } from '@lib/components/StylishText';
import { apiUrl } from '@lib/functions/Api';
import { getDetailUrl } from '@lib/functions/Navigation';
import type {
ApiFormAdjustFilterType,
ApiFormFieldChoice,
ApiFormFieldSet,
ApiFormModalProps,
StockOperationProps
} from '@lib/types/Forms';
import {
TableFieldExtraRow,
type TableFieldRowProps
@ -490,12 +489,31 @@ function StockItemDefaultMove({
function moveToDefault(
stockItem: any,
value: StockItemQuantity,
refresh: () => void
refresh: () => void,
options?: {
title?: string;
onConfirm?: (location: number) => void;
}
) {
const location =
stockItem.part_detail?.default_location ??
stockItem.part_detail?.category_default_location;
modals.openConfirmModal({
title: <StylishText>{t`Confirm Stock Transfer`}</StylishText>,
title: (
<StylishText>{options?.title ?? t`Confirm Stock Transfer`}</StylishText>
),
children: <StockItemDefaultMove stockItem={stockItem} value={value} />,
onConfirm: () => {
if (!location) {
return;
}
if (options?.onConfirm) {
options.onConfirm(location);
return;
}
if (
stockItem.location === stockItem.part_detail?.default_location ||
stockItem.location === stockItem.part_detail?.category_default_location
@ -512,9 +530,7 @@ function moveToDefault(
status: stockItem.status
}
],
location:
stockItem.part_detail?.default_location ??
stockItem.part_detail?.category_default_location
location: location
})
.then((response) => {
refresh();
@ -548,6 +564,7 @@ function StockOperationsRow({
add = false,
setMax = false,
merge = false,
returnStock = false,
record
}: {
props: TableFieldRowProps;
@ -556,8 +573,11 @@ function StockOperationsRow({
add?: boolean;
setMax?: boolean;
merge?: boolean;
returnStock?: boolean;
record?: any;
}) {
const form = useFormContext();
const statusOptions: ApiFormFieldChoice[] = useMemo(() => {
return (
StatusFilterOptions(ModelType.stockitem)()?.map((choice) => {
@ -676,7 +696,22 @@ function StockOperationsRow({
{transfer && (
<ActionButton
onClick={() =>
moveToDefault(record, props.item.quantity, removeAndRefresh)
moveToDefault(
record,
props.item.quantity,
removeAndRefresh,
returnStock
? {
title: t`Confirm Stock Return`,
onConfirm: (location: number) => {
form.setValue('location', location, {
shouldDirty: true,
shouldValidate: true
});
}
}
: undefined
)
}
icon={<InvenTreeIcon icon='default_location' />}
tooltip={t`Move to default location`}
@ -840,6 +875,7 @@ function stockReturnFields(items: any[]): ApiFormFieldSet {
key={record.pk}
record={record}
transfer
returnStock
changeStatus
/>
);
@ -854,12 +890,25 @@ function stockReturnFields(items: any[]): ApiFormFieldSet {
]
},
location: {
field_type: 'related field',
api_url: apiUrl(ApiEndpoints.stock_location_list),
model: ModelType.stocklocation,
required: true,
filters: {
structural: false
}
},
merge: {},
notes: {}
merge: {
field_type: 'boolean',
label: t`Merge into existing stock`,
description: t`Merge returned items into existing stock items if possible`,
value: false
},
notes: {
field_type: 'string',
label: t`Notes`,
description: t`Stock transaction notes`
}
};
return fields;
@ -1593,11 +1642,7 @@ export function useTestResultFields({
/**
* Modal form for finding a particular stock item by serial number
*/
export function useFindSerialNumberForm({
partId
}: {
partId: number;
}) {
export function useFindSerialNumberForm({ partId }: { partId: number }) {
const navigate = useNavigate();
return useApiFormModal({

View File

@ -15,10 +15,13 @@ import {
IconCalendar,
IconCalendarCheck,
IconCalendarDot,
IconCalendarExclamation,
IconCalendarStats,
IconCalendarTime,
IconCalendarX,
IconCancel,
IconChartBar,
IconChartLine,
IconCheck,
IconCircleCheck,
IconCircleDashedCheck,
@ -63,6 +66,7 @@ import {
IconMapPin,
IconMapPinHeart,
IconMinusVertical,
IconNews,
IconNotes,
IconNumber123,
IconNumbers,
@ -158,6 +162,7 @@ const icons: InvenTreeIconType = {
transfer_orders: IconTransfer,
sales_orders: IconTruckDelivery,
scheduling: IconCalendarStats,
overdue: IconCalendarExclamation,
scrap: IconCircleX,
shipment: IconCubeSend,
test_templates: IconTestPipe,
@ -267,7 +272,11 @@ const icons: InvenTreeIconType = {
plugin: IconPlug,
history: IconHistory,
dashboard: IconLayoutDashboard,
search: IconSearch
search: IconSearch,
chart_bar: IconChartBar,
chart_line: IconChartLine,
news: IconNews
};
/**

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