Merge commit '956468eb847754e701d207bae2a51a6a00ca7d56' into block-notes

This commit is contained in:
Oliver Walters
2026-06-01 09:16:12 +00:00
134 changed files with 51167 additions and 44760 deletions
+14 -4
View File
@@ -165,7 +165,7 @@ jobs:
pip install --require-hashes -r docs/requirements.txt pip install --require-hashes -r docs/requirements.txt
python docs/ci/check_mkdocs_config.py python docs/ci/check_mkdocs_config.py
- name: Check Links - 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: with:
folder-path: docs folder-path: docs
config-file: docs/mlc_config.json config-file: docs/mlc_config.json
@@ -713,9 +713,19 @@ jobs:
invoke dev.setup-test -iv invoke dev.setup-test -iv
invoke int.rebuild-thumbnails invoke int.rebuild-thumbnails
- name: Install dependencies - name: Install dependencies
run: | run: invoke int.frontend-compile --extract
invoke int.frontend-compile --extract - name: Cache Playwright browsers
cd src/frontend && npx playwright install --with-deps 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: Run Playwright tests - name: Run Playwright tests
id: tests id: tests
run: | run: |
+10 -2
View File
@@ -227,7 +227,7 @@ jobs:
INVENTREE_PLUGIN_FILE=/opt/inventree/plugins.txt INVENTREE_PLUGIN_FILE=/opt/inventree/plugins.txt
INVENTREE_CONFIG_FILE=/opt/inventree/config.yaml INVENTREE_CONFIG_FILE=/opt/inventree/config.yaml
APP_REPO=inventree/InvenTree 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 uses: pkgr/action/publish@3bce081ae512c5020856e237d37b3f5479d4aa71 # pin@main
with: with:
target: ${{ matrix.target }} target: ${{ matrix.target }}
@@ -235,10 +235,18 @@ jobs:
repository: inventree/InvenTree repository: inventree/InvenTree
channel: ${{ env.pkg_channel }} channel: ${{ env.pkg_channel }}
file: ${{ steps.package.outputs.package_path }} 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 - name: Publish to artifact
run: gh release upload ${REF} ${PACKAGE_PATH}#${PACKAGE_NAME} run: gh release upload ${REF} ${PACKAGE_PATH}#${PACKAGE_NAME}
env: env:
REF: ${{ github.ref_name }} REF: ${{ github.ref_name }}
PACKAGE_PATH: ${{ steps.package.outputs.package_path }} 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 }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1
View File
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- [#12019](https://github.com/inventree/InvenTree/pull/12019) adds a "location" field to the StockCount API endpoint, allowing users to specify a location when performing a stock count. This field is optional, and if not provided, the stock count will be performed without changing the location of the stock item. If a location is provided, the stock item(s) will be moved to the specified location as part of the stock count operation.
- [#12011](https://github.com/inventree/InvenTree/pull/12011) adds a "creation_date" field to the StockItem API endpoint, allowing users to track when each stock item was created. This field is read-only and is automatically set to the current date and time when a new stock item is created. - [#12011](https://github.com/inventree/InvenTree/pull/12011) adds a "creation_date" field to the StockItem API endpoint, allowing users to track when each stock item was created. This field is read-only and is automatically set to the current date and time when a new stock item is created.
- [#12000](https://github.com/inventree/InvenTree/pull/12000) adds support for auto-allocation of stock items against sales orders. This includes both backend and frontend changes, allowing users to trigger auto-allocation via the API or through the UI. The auto-allocation process will attempt to allocate available stock items to the sales order line items, based on the specified stock sorting and allocation rules. - [#12000](https://github.com/inventree/InvenTree/pull/12000) adds support for auto-allocation of stock items against sales orders. This includes both backend and frontend changes, allowing users to trigger auto-allocation via the API or through the UI. The auto-allocation process will attempt to allocate available stock items to the sales order line items, based on the specified stock sorting and allocation rules.
- [#11920](https://github.com/inventree/InvenTree/pull/11920) adds support for renaming attachments after they have been uploaded. This includes both backend and frontend changes, allowing users to rename attachments via the API or through the UI. - [#11920](https://github.com/inventree/InvenTree/pull/11920) adds support for renaming attachments after they have been uploaded. This includes both backend and frontend changes, allowing users to rename attachments via the API or through the UI.
+8 -2
View File
@@ -19,7 +19,7 @@ threads = 4
# Worker timeout (default = 90 seconds) # 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 # Number of worker processes
workers = os.environ.get('INVENTREE_GUNICORN_WORKERS', None) workers = os.environ.get('INVENTREE_GUNICORN_WORKERS', None)
@@ -43,7 +43,13 @@ preload_app = True
def post_fork(server, worker): 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 from django.conf import settings
if not settings.TRACING_ENABLED: if not settings.TRACING_ENABLED:
+6 -6
View File
@@ -1,8 +1,8 @@
# This file was autogenerated by uv via the following command: # 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 # uv pip compile contrib/dev_reqs/requirements.in -o contrib/dev_reqs/requirements.txt -c src/backend/requirements.txt
certifi==2026.4.22 \ certifi==2026.5.20 \
--hash=sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a \ --hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \
--hash=sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580 --hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# requests # requests
@@ -139,9 +139,9 @@ charset-normalizer==3.4.7 \
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# requests # requests
idna==3.15 \ idna==3.16 \
--hash=sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8 \ --hash=sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5 \
--hash=sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc --hash=sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# requests # requests
+4
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. 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 #### 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. 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. 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 ### Sales Order Actions
The following barcode actions are available for [Sales Orders](./so.md): The following barcode actions are available for [Sales Orders](./so.md):
+1
View File
@@ -29,6 +29,7 @@ The *Display Settings* screen shows general display configuration options:
{{ usersetting("SHOW_FULL_CATEGORY_IN_TABLES")}} {{ usersetting("SHOW_FULL_CATEGORY_IN_TABLES")}}
{{ usersetting("SHOW_BOM_SUBASSEMBLY_LEVELS")}} {{ usersetting("SHOW_BOM_SUBASSEMBLY_LEVELS")}}
{{ usersetting("ENABLE_LAST_BREADCRUMB") }} {{ usersetting("ENABLE_LAST_BREADCRUMB") }}
{{ usersetting("SHOW_EXTRA_MODEL_INFO") }}
{{ usersetting("SHOW_FULL_LOCATION_IN_TABLES") }} {{ usersetting("SHOW_FULL_LOCATION_IN_TABLES") }}
{{ usersetting("DISPLAY_ITEMS_FINAL_LEVEL") }} {{ usersetting("DISPLAY_ITEMS_FINAL_LEVEL") }}
+8 -3
View File
@@ -256,6 +256,9 @@ The following database options can be configured:
{{ configsetting("INVENTREE_DB_PASSWORD") }} Database password (if required) | {{ configsetting("INVENTREE_DB_PASSWORD") }} Database password (if required) |
{{ configsetting("INVENTREE_DB_HOST") }} Database host address (if required) | {{ configsetting("INVENTREE_DB_HOST") }} Database host address (if required) |
{{ configsetting("INVENTREE_DB_PORT") }} Database host port (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) | {{ configsetting("INVENTREE_DB_OPTIONS") }} Additional database options (as a JSON object) |
!!! tip "Database Password" !!! 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: If running with a PostgreSQL database backend, the following additional options are available:
{{ configtable() }} {{ 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` | database.tcp_keepalives | 1 | TCP keepalive |
| `INVENTREE_DB_TCP_KEEPALIVES_IDLE` | database.tcp_keepalives_idle | 1 | Idle TCP keepalive | | `INVENTREE_DB_TCP_KEEPALIVES_IDLE` | database.tcp_keepalives_idle | 5 | Idle TCP keepalive |
| `INVENTREE_DB_TCP_KEEPALIVES_INTERVAL` | database.tcp_keepalives_interval | 1| TCP keepalive interval | | `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_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" | | `INVENTREE_DB_ISOLATION_SERIALIZABLE` | database.serializable | False | Database isolation level configured to "serializable" |
### MySQL Settings ### 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: If running with a MySQL database backend, the following additional options are available:
{{ configtable() }} {{ configtable() }}
{{ configsetting("INVENTREE_DB_TIMEOUT", default="10") }} Database connection timeout (s) |
| `INVENTREE_DB_ISOLATION_SERIALIZABLE` | database.serializable | False | Database isolation level configured to "serializable" | | `INVENTREE_DB_ISOLATION_SERIALIZABLE` | database.serializable | False | Database isolation level configured to "serializable" |
### SQLite Settings ### SQLite Settings
+2
View File
@@ -32,6 +32,8 @@ Remove parts from a stock item record - for example taking parts from stock for
Count stock items (stocktake) to record the number of items in stock at a given point of time. The quantity for each part is pre-filled with the current quantity based on stock item history. Count stock items (stocktake) to record the number of items in stock at a given point of time. The quantity for each part is pre-filled with the current quantity based on stock item history.
An optional **Location** field allows all counted items to be moved to a new location in the same operation. If left blank, each item retains its current location.
{{ image("stock/stock_count.png", "Stock Count") }} {{ image("stock/stock_count.png", "Stock Count") }}
### Merge Stock ### Merge Stock
+9 -9
View File
@@ -28,9 +28,9 @@ bracex==2.6 \
--hash=sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952 \ --hash=sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952 \
--hash=sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7 --hash=sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7
# via wcmatch # via wcmatch
certifi==2026.4.22 \ certifi==2026.5.20 \
--hash=sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a \ --hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \
--hash=sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580 --hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# httpcore # httpcore
@@ -169,9 +169,9 @@ charset-normalizer==3.4.7 \
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# requests # requests
click==8.3.3 \ click==8.4.1 \
--hash=sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2 \ --hash=sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2 \
--hash=sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613 --hash=sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96
# via # via
# mkdocs # mkdocs
# neoteroi-mkdocs # neoteroi-mkdocs
@@ -227,9 +227,9 @@ httpx==0.28.1 \
--hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \ --hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \
--hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad --hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad
# via neoteroi-mkdocs # via neoteroi-mkdocs
idna==3.15 \ idna==3.16 \
--hash=sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8 \ --hash=sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5 \
--hash=sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc --hash=sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# anyio # anyio
+53
View File
@@ -2,7 +2,12 @@
from django.contrib import admin from django.contrib import admin
from django.http.request import HttpRequest 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.admin import RateAdmin
from djmoney.contrib.exchange.models import Rate from djmoney.contrib.exchange.models import Rate
@@ -17,3 +22,51 @@ class CustomRateAdmin(RateAdmin):
admin.site.unregister(Rate) admin.site.unregister(Rate)
admin.site.register(Rate, CustomRateAdmin) 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)
@@ -1,11 +1,17 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 496 INVENTREE_API_VERSION = 498
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" """Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """ INVENTREE_API_TEXT = """
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
v496 -> 2026-05-26 : https://github.com/inventree/InvenTree/pull/12011 v496 -> 2026-05-26 : https://github.com/inventree/InvenTree/pull/12011
- Add "creation_date" field to the StockItem API endpoint - Add "creation_date" field to the StockItem API endpoint
+12 -1
View File
@@ -64,9 +64,20 @@ class InvenTreeConfig(AppConfig):
self.collect_tasks() self.collect_tasks()
self.start_background_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 # Let the background worker 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) 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 # Update exchange rates
InvenTree.tasks.offload_task( InvenTree.tasks.offload_task(
InvenTree.tasks.update_exchange_rates, force_async=True InvenTree.tasks.update_exchange_rates, force_async=True
@@ -763,6 +763,56 @@ class InvenTreeDecimalField(serializers.FloatField):
raise serializers.ValidationError(_('Invalid value')) 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 ContentTypeField(serializers.ChoiceField): class ContentTypeField(serializers.ChoiceField):
"""Serializer field which represents a ContentType as 'app_label.model_name'. """Serializer field which represents a ContentType as 'app_label.model_name'.
@@ -5,10 +5,23 @@ from pathlib import Path
import structlog import structlog
from InvenTree.config import get_boolean_setting, get_config_value, get_setting from InvenTree.config import get_boolean_setting, get_config_value, get_setting
from InvenTree.ready import isInWorkerThread
logger = structlog.get_logger('inventree') 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(): def get_db_backend():
"""Return the database backend configuration.""" """Return the database backend configuration."""
# For the core database configuration values, we test for UPPERCASE configuration values as a backup, # 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 get_config_value('database.OPTIONS')
or {}, 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 # Check for required keys
@@ -113,9 +133,9 @@ def set_postgres_options(db_options: dict):
if 'connect_timeout' not in db_options: if 'connect_timeout' not in db_options:
# The DB server is in the same data center, it should not take very # The DB server is in the same data center, it should not take very
# long to connect to the database server # long to connect to the database server
# # seconds, 2 is minimum allowed by libpq # Note: 2 seconds is minimum allowed by libpq
db_options['connect_timeout'] = int( db_options['connect_timeout'] = max(
get_setting('INVENTREE_DB_TIMEOUT', 'database.timeout', 2) 2, int(get_setting('INVENTREE_DB_TIMEOUT', 'database.timeout', 10))
) )
# Setup TCP keepalive # Setup TCP keepalive
@@ -133,7 +153,7 @@ def set_postgres_options(db_options: dict):
if 'keepalives_idle' not in db_options: if 'keepalives_idle' not in db_options:
db_options['keepalives_idle'] = int( db_options['keepalives_idle'] = int(
get_setting( 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( get_setting(
'INVENTREE_DB_TCP_KEEPALIVES_INTERVAL', 'INVENTREE_DB_TCP_KEEPALIVES_INTERVAL',
'database.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 # # Milliseconds for how long pending data should remain unacked by the remote server
# by the remote server if 'tcp_user_timeout' not in db_options:
# TODO: Supported starting in PSQL 11 db_options['tcp_user_timeout'] = int(
# "tcp_user_timeout": int(os.getenv("PGTCP_USER_TIMEOUT", "1000"), get_setting(
'INVENTREE_DB_TCP_USER_TIMEOUT', 'database.tcp_user_timeout', '2000'
)
)
# Postgres's default isolation level is Read Committed which is # Postgres's default isolation level is Read Committed which is
# normally fine, but most developers think the database server 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 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): def set_mysql_options(db_options: dict):
"""Set database options specific to mysql backend.""" """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 # MariaDB's default isolation level is Repeatable Read which is
# normally fine, but most developers think the database server is # normally fine, but most developers think the database server is
+5 -1
View File
@@ -56,7 +56,11 @@ INVENTREE_BASE_URL = 'https://inventree.org'
INVENTREE_NEWS_URL = f'{INVENTREE_BASE_URL}/news/feed.atom' INVENTREE_NEWS_URL = f'{INVENTREE_BASE_URL}/news/feed.atom'
# Determine if we are running in "test" mode e.g. "manage.py test" # 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: if TESTING:
# Use a weaker password hasher for testing (improves testing speed) # Use a weaker password hasher for testing (improves testing speed)
+9 -18
View File
@@ -282,40 +282,31 @@ def offload_task(
# function was passed - use that # function was passed - use that
_func = taskname _func = taskname
else: 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: try:
app, mod, func = taskname.split('.') module_path, func_name = taskname.rsplit('.', 1)
app_mod = app + '.' + mod
except ValueError: except ValueError:
raise_warning( raise_warning(
f"WARNING: '{taskname}' not started - Malformed function path" f"WARNING: '{taskname}' not started - Malformed function path"
) )
return False return False
# Import module from app
try: try:
_mod = importlib.import_module(app_mod) _mod = importlib.import_module(module_path)
except ModuleNotFoundError: except ModuleNotFoundError:
log_error('offload_task', scope='worker') log_error('offload_task', scope='worker')
raise_warning( raise_warning(
f"WARNING: '{taskname}' not started - No module named '{app_mod}'" f"WARNING: '{taskname}' not started - No module named '{module_path}'"
) )
return False return False
# Retrieve function _func = getattr(_mod, func_name, None)
try: if _func is None:
_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:
log_error('offload_task', scope='worker') log_error('offload_task', scope='worker')
raise_warning( raise_warning(
f"WARNING: '{taskname}' not started - No function named '{func}'" f"WARNING: '{taskname}' not started - No function named '{func_name}'"
) )
return False return False
@@ -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'
+50 -4
View File
@@ -67,26 +67,72 @@ class InvenTreeTaskTests(PluginRegistryMixin, TestCase):
# Run with string ref # Run with string ref
InvenTree.tasks.offload_task('InvenTree.test_tasks.get_result') 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 # Malformed taskname
with self.assertWarnsMessage( with self.assertWarnsMessage(
UserWarning, "WARNING: 'InvenTree' not started - Malformed function path" UserWarning, "WARNING: 'InvenTree' not started - Malformed function path"
): ):
InvenTree.tasks.offload_task('InvenTree') InvenTree.tasks.offload_task('InvenTree', force_sync=True)
# Non existent app # Non existent app
with self.assertWarnsMessage( with self.assertWarnsMessage(
UserWarning, UserWarning,
"WARNING: 'InvenTreeABC.test_tasks.doesnotmatter' not started - No module named 'InvenTreeABC.test_tasks'", "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 # Non existent function
with self.assertWarnsMessage( with self.assertWarnsMessage(
UserWarning, UserWarning,
"WARNING: 'InvenTree.test_tasks.doesnotexist' not started - No function named 'doesnotexist'", "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): def test_task_heartbeat(self):
"""Test the task heartbeat.""" """Test the task heartbeat."""
+1 -1
View File
@@ -1488,7 +1488,7 @@ class TestOffloadTask(InvenTreeTestCase):
offload_task('dummy_task.numbers', 1, 1, 1, force_sync=True) 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 # Offload dummy task with a Part instance
# This should succeed, ensuring that the Part instance is correctly pickled # This should succeed, ensuring that the Part instance is correctly pickled
+15 -5
View File
@@ -50,12 +50,17 @@ def setup_tracing(
""" """
if InvenTree.ready.isImportingData() or InvenTree.ready.isRunningMigrations(): if InvenTree.ready.isImportingData() or InvenTree.ready.isRunningMigrations():
return return
if endpoint is None or headers is None: if endpoint is None or headers is None:
print( print(
'Tracing endpoint or headers not specified - skipping tracing setup' 'Tracing endpoint or headers not specified - skipping tracing setup'
) # pragma: no cover ) # pragma: no cover
return # 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 configuration
logger = logging.getLogger('inventree') logger = logging.getLogger('inventree')
@@ -163,6 +168,7 @@ def setup_tracing(
def setup_instruments(db_engine: str): # pragma: no cover def setup_instruments(db_engine: str): # pragma: no cover
"""Run auto-instrumentation for OpenTelemetry tracing.""" """Run auto-instrumentation for OpenTelemetry tracing."""
if not DjangoInstrumentor()._is_instrumented_by_opentelemetry:
DjangoInstrumentor().instrument() DjangoInstrumentor().instrument()
RedisInstrumentor().instrument() RedisInstrumentor().instrument()
RequestsInstrumentor().instrument() RequestsInstrumentor().instrument()
@@ -172,20 +178,24 @@ def setup_instruments(db_engine: str): # pragma: no cover
# DBs # DBs
if 'sqlite' in db_engine: if 'sqlite' in db_engine:
SQLite3Instrumentor().instrument() inst = SQLite3Instrumentor()
if not inst._is_instrumented_by_opentelemetry:
inst.instrument()
elif 'postgresql' in db_engine: elif 'postgresql' in db_engine:
try: try:
from opentelemetry.instrumentation.psycopg import PsycopgInstrumentor from opentelemetry.instrumentation.psycopg import PsycopgInstrumentor
PsycopgInstrumentor().instrument( inst = PsycopgInstrumentor()
enable_commenter=False, commenter_options={} if not inst._is_instrumented_by_opentelemetry:
) inst.instrument(enable_commenter=False, commenter_options={})
except ModuleNotFoundError: except ModuleNotFoundError:
pass pass
elif 'mysql' in db_engine: elif 'mysql' in db_engine:
try: try:
from opentelemetry.instrumentation.pymysql import PyMySQLInstrumentor from opentelemetry.instrumentation.pymysql import PyMySQLInstrumentor
PyMySQLInstrumentor().instrument() inst = PyMySQLInstrumentor()
if not inst._is_instrumented_by_opentelemetry:
inst.instrument()
except ModuleNotFoundError: except ModuleNotFoundError:
pass pass
+2 -2
View File
@@ -32,6 +32,7 @@ from common.settings import get_global_setting
from generic.states.fields import InvenTreeCustomStatusSerializerMixin from generic.states.fields import InvenTreeCustomStatusSerializerMixin
from InvenTree.mixins import DataImportExportSerializerMixin from InvenTree.mixins import DataImportExportSerializerMixin
from InvenTree.serializers import ( from InvenTree.serializers import (
CustomStatusSerializerMixin,
FilterableSerializerMixin, FilterableSerializerMixin,
InvenTreeDecimalField, InvenTreeDecimalField,
InvenTreeModelSerializer, InvenTreeModelSerializer,
@@ -52,6 +53,7 @@ from .status_codes import BuildStatus
class BuildSerializer( class BuildSerializer(
CustomStatusSerializerMixin,
FilterableSerializerMixin, FilterableSerializerMixin,
DataImportExportSerializerMixin, DataImportExportSerializerMixin,
InvenTreeCustomStatusSerializerMixin, InvenTreeCustomStatusSerializerMixin,
@@ -113,8 +115,6 @@ class BuildSerializer(
level = serializers.IntegerField(label=_('Build Level'), read_only=True) level = serializers.IntegerField(label=_('Build Level'), read_only=True)
status_text = serializers.CharField(source='get_status_display', read_only=True)
part_detail = OptionalField( part_detail = OptionalField(
serializer_class=part_serializers.PartBriefSerializer, serializer_class=part_serializers.PartBriefSerializer,
serializer_kwargs={'source': 'part', 'many': False, 'read_only': True}, serializer_kwargs={'source': 'part', 'many': False, 'read_only': True},
+91
View File
@@ -1817,6 +1817,97 @@ class BuildConsumeTest(BuildAPITest):
self.assertEqual(line.consumed, 100) 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): class BuildAutoAllocateAPITest(InvenTreeAPITestCase):
"""API integration tests for BuildAutoAllocate endpoint back-ports (stock_sort_by, build_lines).""" """API integration tests for BuildAutoAllocate endpoint back-ports (stock_sort_by, build_lines)."""
+64 -8
View File
@@ -89,21 +89,37 @@ class AttachmentAdmin(admin.ModelAdmin):
@admin.register(common.models.DataOutput) @admin.register(common.models.DataOutput)
class DataOutputAdmin(admin.ModelAdmin): 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_display = ('user', 'created', 'output_type', 'output')
list_filter = ('user', 'output_type') 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) @admin.register(common.models.BarcodeScanResult)
class BarcodeScanResultAdmin(admin.ModelAdmin): 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_display = ('data', 'timestamp', 'user', 'endpoint', 'result')
list_filter = ('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) @admin.register(common.models.ProjectCode)
class ProjectCodeAdmin(admin.ModelAdmin): class ProjectCodeAdmin(admin.ModelAdmin):
@@ -149,14 +165,22 @@ class WebhookAdmin(admin.ModelAdmin):
@admin.register(common.models.NotificationEntry) @admin.register(common.models.NotificationEntry)
class NotificationEntryAdmin(admin.ModelAdmin): class NotificationEntryAdmin(admin.ModelAdmin):
"""Admin settings for NotificationEntry.""" """Admin settings for NotificationEntry - view and delete only."""
list_display = ('key', 'uid', 'updated') 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) @admin.register(common.models.NotificationMessage)
class NotificationMessageAdmin(admin.ModelAdmin): class NotificationMessageAdmin(admin.ModelAdmin):
"""Admin settings for NotificationMessage.""" """Admin settings for NotificationMessage - view and delete only."""
list_display = ( list_display = (
'age_human', 'age_human',
@@ -172,14 +196,46 @@ class NotificationMessageAdmin(admin.ModelAdmin):
search_fields = ('name', 'category', 'message') 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) @admin.register(common.models.NewsFeedEntry)
class NewsFeedEntryAdmin(admin.ModelAdmin): class NewsFeedEntryAdmin(admin.ModelAdmin):
"""Admin settings for NewsFeedEntry.""" """Admin settings for NewsFeedEntry - view and delete only."""
list_display = ('title', 'author', 'published', 'summary') 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) def has_change_permission(self, request, obj=None):
admin.site.register(common.models.EmailMessage, admin.ModelAdmin) """Prevent modification of NewsFeedEntry objects via the admin interface."""
admin.site.register(common.models.EmailThread, admin.ModelAdmin) 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)
@@ -235,6 +235,12 @@ USER_SETTINGS: dict[str, InvenTreeSettingsKeyType] = {
'default': False, 'default': False,
'validator': bool, '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': { 'SHOW_FULL_LOCATION_IN_TABLES': {
'name': _('Show full stock location in tables'), 'name': _('Show full stock location in tables'),
'description': _( 'description': _(
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
+5 -4
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.helpers import extract_serial_numbers, hash_barcode, normalize, str2bool
from InvenTree.mixins import DataImportExportSerializerMixin from InvenTree.mixins import DataImportExportSerializerMixin
from InvenTree.serializers import ( from InvenTree.serializers import (
CustomStatusSerializerMixin,
FilterableSerializerMixin, FilterableSerializerMixin,
InvenTreeCurrencySerializer, InvenTreeCurrencySerializer,
InvenTreeDecimalField, InvenTreeDecimalField,
@@ -99,7 +100,10 @@ class DuplicateOrderSerializer(serializers.Serializer):
class AbstractOrderSerializer( class AbstractOrderSerializer(
DataImportExportSerializerMixin, FilterableSerializerMixin, serializers.Serializer CustomStatusSerializerMixin,
DataImportExportSerializerMixin,
FilterableSerializerMixin,
serializers.Serializer,
): ):
"""Abstract serializer class which provides fields common to all order types.""" """Abstract serializer class which provides fields common to all order types."""
@@ -117,9 +121,6 @@ class AbstractOrderSerializer(
read_only=True, allow_null=True, label=_('Completed Lines') 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 field cannot be set directly
status = serializers.IntegerField(read_only=True, label=_('Order Status')) status = serializers.IntegerField(read_only=True, label=_('Order Status'))
+65 -2
View File
@@ -16,7 +16,7 @@ from icalendar import Calendar
from rest_framework import status from rest_framework import status
from common.currency import currency_codes 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 common.settings import set_global_setting
from company.models import Company, SupplierPart, SupplierPriceBreak from company.models import Company, SupplierPart, SupplierPriceBreak
from InvenTree.unit_test import InvenTreeAPITestCase from InvenTree.unit_test import InvenTreeAPITestCase
@@ -737,6 +737,69 @@ class PurchaseOrderTest(OrderTest):
) )
self.assertEqual(response.status_code, 200) 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): class PurchaseOrderLineItemTest(OrderTest):
"""Unit tests for PurchaseOrderLineItems.""" """Unit tests for PurchaseOrderLineItems."""
@@ -771,7 +834,7 @@ class PurchaseOrderLineItemTest(OrderTest):
# Try to delete a set of line items via their IDs # Try to delete a set of line items via their IDs
self.delete(url, {'items': [1, 2]}, expected_code=200) 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) self.assertEqual(models.PurchaseOrderLineItem.objects.count(), n - 2)
def test_po_line_merge_pricing(self): def test_po_line_merge_pricing(self):
@@ -533,10 +533,23 @@ class BarcodePOReceive(BarcodeView):
# Now, look just for "supplier-barcode" plugins # Now, look just for "supplier-barcode" plugins
plugins = registry.with_mixin(PluginMixinEnum.SUPPLIER_BARCODE) plugins = registry.with_mixin(PluginMixinEnum.SUPPLIER_BARCODE)
plugin_slug = None
plugin_response = 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: for current_plugin in plugins:
try: 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( result = current_plugin.scan_receive_item(
barcode, barcode,
request.user, request.user,
@@ -546,13 +559,59 @@ class BarcodePOReceive(BarcodeView):
line_item=line_item, line_item=line_item,
auto_allocate=auto_allocate, auto_allocate=auto_allocate,
) )
except Exception: except Exception:
log_error('BarcodePOReceive.handle_barcode', plugin=current_plugin.slug) log_error('BarcodePOReceive.handle_barcode', plugin=current_plugin.slug)
continue continue
if result is None: 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 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: if 'error' in result:
logger.info( logger.info(
'%s.scan_receive_item(...) returned an error: %s', '%s.scan_receive_item(...) returned an error: %s',
@@ -569,13 +628,20 @@ class BarcodePOReceive(BarcodeView):
response['plugin'] = plugin.name if plugin else None 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} response = {**response, **plugin_response}
elif no_supplier_plugin_error:
response['no_supplier_plugin_error'] = no_supplier_plugin_error
# A plugin has not been found! # A plugin has not been found!
if plugin is None: if plugin is None:
response['error'] = _('No plugin match for supplier barcode') 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) self.log_scan(request, response, 'success' in response)
if 'error' in response: if 'error' in response:
@@ -320,7 +320,7 @@ class SupplierBarcodeMixin(BarcodeMixin):
location=None, location=None,
auto_allocate: bool = True, auto_allocate: bool = True,
**kwargs, **kwargs,
) -> dict | None: ) -> dict:
"""Attempt to receive an item against a PurchaseOrder via barcode scanning. """Attempt to receive an item against a PurchaseOrder via barcode scanning.
Arguments: Arguments:
@@ -344,32 +344,50 @@ class SupplierBarcodeMixin(BarcodeMixin):
# Extract supplier information # Extract supplier information
supplier = supplier or self.get_supplier(cache=True) 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 # No supplier information available
return None debug_response['supplier'] = None
else:
debug_response['supplier'] = supplier.name
# Extract purchase order information # Extract purchase order information
purchase_order = purchase_order or self.get_purchase_order() 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 # 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() supplier_part = self.get_supplier_part()
if not supplier_part: if supplier_part is None:
# No supplier part information available # 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 # 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) line_items = purchase_order.lines.filter(part=supplier_part)
if line_items.count() == 1: if line_items.count() == 1:
line_item = line_items.first() line_item = line_items.first()
if not line_item: # If Purchase Order or Supplier Part does not exist, throw debug response
# No line item information available if debug_response['PO'] is None or debug_response['supplier_part'] is None:
return None debug_response['no_match'] = True
return debug_response
if line_item.part != supplier_part: if line_item.part != supplier_part:
return {'error': _('Supplier part does not match line item')} return {'error': _('Supplier part does not match line item')}
@@ -406,7 +424,8 @@ class SupplierBarcodeMixin(BarcodeMixin):
'supplier_part': supplier_part.pk, 'supplier_part': supplier_part.pk,
'purchase_order': purchase_order.pk, 'purchase_order': purchase_order.pk,
'location': location.pk if location else None, 'location': location.pk if location else None,
} },
'no_match': False,
} }
if action_required: if action_required:
@@ -428,6 +428,95 @@ class SupplierBarcodePOReceiveTests(InvenTreeAPITestCase):
# Quantity should be pre-filled with the remaining quantity # Quantity should be pre-filled with the remaining quantity
self.assertEqual(5, response.data['lineitem']['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 = ( DIGIKEY_BARCODE = (
'[)>\x1e06\x1dP296-LM358BIDDFRCT-ND\x1d1PLM358BIDDFR\x1dK\x1d1K72991337\x1d' '[)>\x1e06\x1dP296-LM358BIDDFRCT-ND\x1d1PLM358BIDDFR\x1dK\x1d1K72991337\x1d'
@@ -476,3 +565,17 @@ TME_QRCODE = (
) )
TME_DATAMATRIX_CODE = 'PWBP-302 1PMPNWBP-302 Q1 K19361337/1' 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'
)
+13 -1
View File
@@ -77,12 +77,24 @@ class StockItemAdmin(admin.ModelAdmin):
@admin.register(StockItemTracking) @admin.register(StockItemTracking)
class StockTrackingAdmin(admin.ModelAdmin): class StockTrackingAdmin(admin.ModelAdmin):
"""Admin class for StockTracking.""" """Admin class for StockTracking - read-only to preserve audit trail integrity."""
list_display = ('item', 'date', 'label') list_display = ('item', 'date', 'label')
autocomplete_fields = ['item'] 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) @admin.register(StockItemTestResult)
class StockItemTestResultAdmin(admin.ModelAdmin): class StockItemTestResultAdmin(admin.ModelAdmin):
+9
View File
@@ -2624,6 +2624,7 @@ class StockItem(
Keyword Arguments: Keyword Arguments:
notes: Optional notes for the stocktake notes: Optional notes for the stocktake
status: Optionally adjust the stock status status: Optionally adjust the stock status
location: Optionally set the stock location
""" """
try: try:
count = Decimal(count) count = Decimal(count)
@@ -2635,6 +2636,14 @@ class StockItem(
tracking_info = {} tracking_info = {}
location = kwargs.pop('location', None)
if location and location != self.location:
old_location = self.location
self.location = location
tracking_info['location'] = location.pk
tracking_info['old_location'] = old_location.pk if old_location else None
status = kwargs.pop('status', None) or kwargs.pop('status_custom_key', None) status = kwargs.pop('status', None) or kwargs.pop('status_custom_key', None)
if status and not self.compare_status(status): if status and not self.compare_status(status):
+20 -4
View File
@@ -33,6 +33,7 @@ from generic.states.fields import InvenTreeCustomStatusSerializerMixin
from importer.registry import register_importer from importer.registry import register_importer
from InvenTree.mixins import DataImportExportSerializerMixin from InvenTree.mixins import DataImportExportSerializerMixin
from InvenTree.serializers import ( from InvenTree.serializers import (
CustomStatusSerializerMixin,
InvenTreeCurrencySerializer, InvenTreeCurrencySerializer,
InvenTreeDecimalField, InvenTreeDecimalField,
OptionalField, OptionalField,
@@ -308,6 +309,7 @@ class StockItemTestResultSerializer(
@register_importer() @register_importer()
class StockItemSerializer( class StockItemSerializer(
CustomStatusSerializerMixin,
InvenTree.serializers.FilterableSerializerMixin, InvenTree.serializers.FilterableSerializerMixin,
DataImportExportSerializerMixin, DataImportExportSerializerMixin,
InvenTreeCustomStatusSerializerMixin, InvenTreeCustomStatusSerializerMixin,
@@ -564,10 +566,6 @@ class StockItemSerializer(
return queryset return queryset
status_text = serializers.CharField(
source='get_status_display', read_only=True, label=_('Status')
)
SKU = serializers.CharField( SKU = serializers.CharField(
source='supplier_part.SKU', source='supplier_part.SKU',
read_only=True, read_only=True,
@@ -1743,6 +1741,20 @@ class StockAdjustmentSerializer(serializers.Serializer):
class StockCountSerializer(StockAdjustmentSerializer): class StockCountSerializer(StockAdjustmentSerializer):
"""Serializer for counting stock items.""" """Serializer for counting stock items."""
class Meta:
"""Metaclass options."""
fields = ['items', 'notes', 'location']
location = serializers.PrimaryKeyRelatedField(
queryset=StockLocation.objects.filter(structural=False),
many=False,
required=False,
allow_null=True,
label=_('Location'),
help_text=_('Set stock location for counted items (optional)'),
)
def save(self): def save(self):
"""Count stock.""" """Count stock."""
request = self.context['request'] request = self.context['request']
@@ -1750,6 +1762,7 @@ class StockCountSerializer(StockAdjustmentSerializer):
data = self.validated_data data = self.validated_data
items = data['items'] items = data['items']
notes = data.get('notes', '') notes = data.get('notes', '')
location = data.get('location', None)
with transaction.atomic(): with transaction.atomic():
for item in items: for item in items:
@@ -1763,6 +1776,9 @@ class StockCountSerializer(StockAdjustmentSerializer):
if field_value := item.get(field_name, None): if field_value := item.get(field_name, None):
extra[field_name] = field_value extra[field_name] = field_value
if location is not None:
extra['location'] = location
stock_item.stocktake(quantity, request.user, notes=notes, **extra) stock_item.stocktake(quantity, request.user, notes=notes, **extra)
+158
View File
@@ -1411,6 +1411,82 @@ class CustomStockItemStatusTest(StockAPITestCase):
self.assertEqual(status['value'], self.status.key) self.assertEqual(status['value'], self.status.key)
self.assertEqual(status['display_name'], self.status.label) 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): class StockItemTest(StockAPITestCase):
"""Series of API tests for the StockItem API.""" """Series of API tests for the StockItem API."""
@@ -2253,6 +2329,88 @@ class StocktakeTest(StockAPITestCase):
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
) )
def test_count_with_location(self):
"""Test that the stock count endpoint correctly handles the optional location field."""
url = reverse('api-stock-count')
# Stock item pk=1234 starts at location 5; pk=1 starts at location 3
item_a = StockItem.objects.get(pk=1234)
item_b = StockItem.objects.get(pk=1)
self.assertEqual(item_a.location.pk, 5)
self.assertEqual(item_b.location.pk, 3)
# --- location is updated when provided (single item) ---
response = self.post(
url,
{'items': [{'pk': item_a.pk, 'quantity': 10}], 'location': 1},
expected_code=201,
)
self.assertEqual(response.data['items'][0]['pk'], item_a.pk)
item_a.refresh_from_db()
self.assertEqual(item_a.location.pk, 1)
# Tracking entry records the location change
entry = StockItemTracking.objects.filter(
item=item_a, tracking_type=StockHistoryCode.STOCK_COUNT
).latest('date')
self.assertEqual(entry.deltas.get('location'), 1)
self.assertEqual(entry.deltas.get('old_location'), 5)
# --- location is updated for multiple items simultaneously ---
response = self.post(
url,
{
'items': [
{'pk': item_a.pk, 'quantity': 5},
{'pk': item_b.pk, 'quantity': 20},
],
'location': 2,
},
expected_code=201,
)
self.assertEqual(len(response.data['items']), 2)
item_a.refresh_from_db()
item_b.refresh_from_db()
self.assertEqual(item_a.location.pk, 2)
self.assertEqual(item_b.location.pk, 2)
# Both items have a tracking entry with the new location
for item, old_loc in [(item_a, 1), (item_b, 3)]:
entry = StockItemTracking.objects.filter(
item=item, tracking_type=StockHistoryCode.STOCK_COUNT
).latest('date')
self.assertEqual(entry.deltas.get('location'), 2)
self.assertEqual(entry.deltas.get('old_location'), old_loc)
# --- location is unchanged when not provided ---
response = self.post(
url, {'items': [{'pk': item_a.pk, 'quantity': 7}]}, expected_code=201
)
item_a.refresh_from_db()
# Location should still be 2 (unchanged from the previous count)
self.assertEqual(item_a.location.pk, 2)
# Tracking entry has no location delta when location was not provided
entry = StockItemTracking.objects.filter(
item=item_a, tracking_type=StockHistoryCode.STOCK_COUNT
).latest('date')
self.assertNotIn('location', entry.deltas)
self.assertNotIn('old_location', entry.deltas)
# --- structural location is rejected ---
structural = StockLocation.objects.create(name='Structural', structural=True)
response = self.post(
url,
{'items': [{'pk': item_a.pk, 'quantity': 1}], 'location': structural.pk},
expected_code=400,
)
self.assertIn('does not exist', str(response.data['location']))
class StockItemDeletionTest(StockAPITestCase): class StockItemDeletionTest(StockAPITestCase):
"""Tests for stock item deletion via the API.""" """Tests for stock item deletion via the API."""
+12 -4
View File
@@ -118,10 +118,8 @@ class InvenTreeGroupAdminForm(forms.ModelForm):
class InvenTreeUserAdmin(UserAdmin): class InvenTreeUserAdmin(UserAdmin):
"""Custom admin page for the User model. """Custom admin page for the User model.
Hides the "permissions" view as this is now handled - Restrict user creation and editing to superuser accounts
entirely by groups and RuleSets. - Hides the "permissions" view as this is handled by RuleSets
(And it's confusing!)
""" """
list_display = ( list_display = (
@@ -132,6 +130,7 @@ class InvenTreeUserAdmin(UserAdmin):
'is_staff', 'is_staff',
'last_login', 'last_login',
) # display last connection for each user in user admin panel. ) # display last connection for each user in user admin panel.
fieldsets = ( fieldsets = (
(None, {'fields': ('username', 'password')}), (None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}), (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
@@ -142,6 +141,15 @@ class InvenTreeUserAdmin(UserAdmin):
(_('Important dates'), {'fields': ('last_login', 'date_joined')}), (_('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) @admin.register(Owner)
class OwnerAdmin(admin.ModelAdmin): class OwnerAdmin(admin.ModelAdmin):
+266 -271
View File
@@ -95,22 +95,22 @@ bleach==4.1.0 \
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# django-markdownify # django-markdownify
blessed==1.39.0 \ blessed==1.42.0 \
--hash=sha256:666e7e3fd0a4e38c3a262eaaf1e22a4ce2c81337aa17593c3f60ea136ec24fe1 \ --hash=sha256:34b460b77562ed21f807cfd7c527b983b0cc300c98810c8076f283b7bcd45ba7 \
--hash=sha256:b04fc7141a20a3b2ade6cad741051f1e3ac59cc1e7e90915ed1f9e521332bea4 --hash=sha256:f96c4a6dc664b48e0b832fa732acc16df67abd30f0ec35babf99025982f21852
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -r src/backend/requirements.in
boto3==1.43.8 \ boto3==1.43.14 \
--hash=sha256:1894497c383e3cdf50e210f1f57a43e9f4047a5d3accc73ffdb7eacc3b0f011b \ --hash=sha256:574335744656cfed0b362a0a0467aaf2eb2bf15526edcd02d31d3c661f4b09e4 \
--hash=sha256:d1235602d715c727c1923ef4bcdb5612a20575a9a5e4f2db00d571e0ea1f85fc --hash=sha256:5c0a994b3182061ee101812e721100717a4d664f9f4ceaf4a86b6d032ce9fc2d
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# django-anymail # django-anymail
# django-storages # django-storages
botocore==1.43.8 \ botocore==1.43.14 \
--hash=sha256:611ad8b1f60661373cd39d9391ff16f1eaf8f5cb1d0a691563a4201d1a2603ce \ --hash=sha256:1f4a2a95ea78c10398e78431e98c1fe47adb54a7b10a32975144c1f541186658 \
--hash=sha256:6257d2655c3abe75eaa49e218b7d883cdc7cea64652b451e5feb08a6c169da3c --hash=sha256:b9e500737e43d2f147c9d4e23b54360335e77d4c0ba90a318f51b65e06cb8516
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# boto3 # boto3
@@ -219,9 +219,9 @@ brotli==1.2.0 \
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# fonttools # fonttools
certifi==2026.4.22 \ certifi==2026.5.20 \
--hash=sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a \ --hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \
--hash=sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580 --hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# requests # requests
@@ -762,49 +762,49 @@ drf-spectacular==0.29.0 \
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -r src/backend/requirements.in
dulwich==1.2.1 \ dulwich==1.2.5 \
--hash=sha256:011d37afa0922b500c938d6417317e3e6d29d33a6fbaf12b3696fe2a216aa170 \ --hash=sha256:00b54a1d56ddbacdd8eadd6d4787a51b3a05fefa30eadbf9165fd283a00b90ed \
--hash=sha256:01f43a1b6953c93b4e957c0902f7de80267285e0c59c9b44f4801f7da9f11107 \ --hash=sha256:0395b2c8924c3424bafe2d9c1edd5348cc4b21ce9c1d6655bf01f9a5c47164c8 \
--hash=sha256:04215befa00c82accda97d0e3ce760d9344a67f081d5b92ade8ad00007fb38d8 \ --hash=sha256:07cb75b58216440e2c170fff4f3d55a5f387358d9489863af8cb11f24ee37121 \
--hash=sha256:09ca37d7a9086f96e535a6474644588e4502f69e2b202c39d92901e3606f6501 \ --hash=sha256:1679b376433a0fc7f36586afda1d4ed7427afa7a79d4bf17e5014474eea69fa4 \
--hash=sha256:0b1181f4ae225bbde373fc16279c4e61fdf04aedd30ec21fab387be642567453 \ --hash=sha256:1699a4cf8d44c174408325a9594a1498d05786cea34e3004c8732420ee1b8182 \
--hash=sha256:1073c75db1f5da845d219d1b5b601aeaaa7be5f999f3c77f4966f9ca2327bf44 \ --hash=sha256:1ba83ec3cfb4c506c277400357a51523c8258fa07b841ee06e8e1071da4cfed1 \
--hash=sha256:11a51dba8454b4c64bf242a918ca4c4300097f7cec84a13164845e638a26f9de \ --hash=sha256:1c151a7f3995ccf9d433a603b747e76141a7ebe7c385c8909e9f7e7a6422c28f \
--hash=sha256:121d2d181407cd7bb051922dc3bc00841ca0959b8299880529525db93dfbdca9 \ --hash=sha256:2f10dafa1ef5660b1331364bc8d68446448608a8d8f493ed0e260eaf5133e71c \
--hash=sha256:1961e0b6c0b1f2920f4ab05821652d8eb12f19ddb5a4c167c385902391c08dd3 \ --hash=sha256:2f90d68bfa97c4ca71de7507984365aefe27b6d248cb28dc99644d0f3ae8c60b \
--hash=sha256:21f48480bb1e6501bdec7f0877639cdfc64a16e0dd97c24947582fbeed3baf21 \ --hash=sha256:41ccffb0521f3f9ad73fac78772f321d731607336cee48911e7c26963459481c \
--hash=sha256:23560643c1cc85737c87985761666a59b35b06a1cbb06db5ba642fa35c67be93 \ --hash=sha256:46db47394ba8a95748ae739f5d3a5a3e1724a2f857bf2437bc71bfc0baaed91d \
--hash=sha256:3183b7db09282842459fbeb37e77e498c8339e6db4d0cb6b5d3685c97ce79dac \ --hash=sha256:5108acead814d1de8b6262d6d8fb90af7e82f5a4d83788b6b48e39d01800a92f \
--hash=sha256:3412eca24fd79d5413ac7003f8f29b2e01008d7c9d6fce159f77602f70058aea \ --hash=sha256:517fb7e20f91d2bd48dc5de9edc90ff8974a5512ce7f243284b191f8be6344c3 \
--hash=sha256:3a95dd2649ef3c6e59095d5bd3de08470b3ba908dc41343f5823666a10a326f8 \ --hash=sha256:53599909d54a2fae49fcd50047f1daf4b8b9eda6a5500a08b71da689f5431c24 \
--hash=sha256:4198f79ff04b07f6f6dc81047041f341b35e774cf0207f496b35f34dd2e1bb9c \ --hash=sha256:556593fd11637f80f6018bee1916b1a84f5b420423b470ebb3f1a782ad6ef081 \
--hash=sha256:41dfe1b1cb0f0202102e90f9f47f54458f27da6b746939a6ed85362b68e3e3a5 \ --hash=sha256:5e067b7feceb7034bc99e7c7143a704f1d97d4be7027d9a0aa5a83c0657ff091 \
--hash=sha256:4b6cd8104bf4adf2824221ba4f3b399c641a7a6a026dfa193c2f516531971315 \ --hash=sha256:66aded7d364341b55941973a1562323f25bd205f0809692b687ec36ccd31242c \
--hash=sha256:55c8415fb19068a5956e0d5bd83b0fb6e06b291d3b096d8fb6a604c105fc6ca0 \ --hash=sha256:6c683c0f4a062894b6826c61102d415dae86ade61a10003c82ccc2b91858d5fb \
--hash=sha256:5a0245dccd69fba9dfe2ed88eff9212bad3e04107bc41676fffbc7f706428cdb \ --hash=sha256:701a9ecf7a8a44f5e2459e46befa93530cf36a8b1ae3140aefc007db1d7d0207 \
--hash=sha256:5e875729df04991732959f52e29c1de96d00eaf62feddfc60f0b2b08c6e38870 \ --hash=sha256:77d2d2e43ad975459491de1ebf47990c74ff17f12586c8561e9890239bc422db \
--hash=sha256:6349b475a1b17b224191a4801687a2486574c9526e00835db2ba9ed081124b11 \ --hash=sha256:827366331603150de5976d72dd456a3fd5fc91e856471dc1d10fd64758c05f02 \
--hash=sha256:674b21a9025c00a00495fa03a9bb5f2da67a13ee513c74f16bd91c56678d0a77 \ --hash=sha256:8929134acf4ff967203df7600b38535f9b5b590462067a7e30dbce01acb97af9 \
--hash=sha256:74b01505b8e2ba81a715213101c790e0794f5c0b8021f8ce1100131a1e8cfa55 \ --hash=sha256:8e55f36a7f52ba0976dd72100273523908b16fb9dda6ce96d9aa9df9cceed4cc \
--hash=sha256:879abd15550b6abcebf97823905a167623f331eeca5e12dc0cac62bcd9e70bcd \ --hash=sha256:9008ef25cabd379cda4fa86000fc38ca14b72afe17db798a8c85c0b2b7ce4d1e \
--hash=sha256:96846ef600378739a64e347412c22d5f87f1b67d68526c633b465589335e9387 \ --hash=sha256:93d2d87acf75d60c5a2b8c5c8a45aff17bbbd00c17bdccb4ba013d3ab590a65f \
--hash=sha256:96c160d68173ae55f68bf734e7fddae04ea025f3541996b0b5f28dc2025b42dc \ --hash=sha256:9693d2c9e226b2ea855c1dc3a87e2f4d972f7523fc0f7924e5997e9f4c23d97f \
--hash=sha256:9d0ea88273a7ee6fd3b1d75e231cc6fc614774e19bdd7c1de5df274b4b492dde \ --hash=sha256:9b6d234f1f91335e9f01d9daac42ddc2d2e5c2fdbe285d8eeef50353b283648a \
--hash=sha256:9f7249c1090be7e7e841edda369d843617314092bb05f2a6a8b7f88660bbb2a6 \ --hash=sha256:9f3c98f5fa90a842c1f545463834f712aa2eed785fc3d5e42836c0df2d691bb6 \
--hash=sha256:a65527eda5f6a463168c6338d781b05d325ba85dc05c6f8217ddd8454911bb53 \ --hash=sha256:9fc113c1348c7eb22c4e8790f68b562bb4f42a721fafb813e89a57e9cd632040 \
--hash=sha256:ba43bfb3a7cad40d9607170561e8c3be42e7083b4b57af89a5f54e01577ff791 \ --hash=sha256:a5549f4afc973e0a15ea6b0244d57f848d3f3ee13dac557eb311024aebebf128 \
--hash=sha256:bad94416de9a76ad36dc19f0e65a829b89fc845282bdcc8c43285a4addc1ad2e \ --hash=sha256:a6620963196c49212c511cd909f367dacf771f199a27d116f357cc671ea956c7 \
--hash=sha256:cedc717cf9ee973df63293bcbaa51bb03ab6f87ff631d1dd329078b3f48712a7 \ --hash=sha256:a70477c991e96cfe8fdd7c866e7251faf71b38bfeb51d6f27554c9cce1caabf3 \
--hash=sha256:cf3aa983ca4907c96a0b98b357a3cac7381192786495522a65944da1b284bc02 \ --hash=sha256:ade416833214f3ee13af9b0199fff4de00fa6e0fde3deced776532fd91df5515 \
--hash=sha256:d0ddda4e165ea14c70e1d0531aa97e527332f1133ea0c65eebaa5afb8786029e \ --hash=sha256:c65230abaa52c72093b70d3b499d5689d1d8f9627e88ad3c3b4f8154e86ac0c8 \
--hash=sha256:da2fdcc9e4f178b2b4ec1194d8e17d111d572f111fdaa7c6c5a3f46bbe686eaf \ --hash=sha256:ccc58f26a1b94bef255316311678b03854f7192069bdf11cf501a6c85f61b83c \
--hash=sha256:e2f14d20aea48dd1d48714d637c70399b140825d930b4f5aa6fbb62199429740 \ --hash=sha256:d33bae2b3292ed0235522682316251658187f43b1ebad6cd2b127069b94afb3b \
--hash=sha256:e45a68da922a4abe8fd015ed020ec0123ee58176a6984a34d2a2c74c959e45d3 \ --hash=sha256:d46e35c473646efb3b2ff8032f37ac5b6d48da52a669577187d3796a6d5987a3 \
--hash=sha256:e88fd960a9327d87556a3bad76ad84b6346d3a07409fe8f081877a775977c86b \ --hash=sha256:d8f7ea8f47e38e5b0de3fab97e07e9c9161ffddc90b3964512cab2b7749df4e6 \
--hash=sha256:efd394fcb926ec8e9ed63fe0883784abf8ef7bb1d30a492e950ca8c2f369ce7b \ --hash=sha256:dd9569bc26174a3437d749114d36c81fc6c7478b55370ae50125e34e9629e4fe \
--hash=sha256:f944b742962d9933e60863f577491743328b287251ae57a1e7cd84c289acfc23 \ --hash=sha256:df4ac3746099562c8160d78d55bb2fa10c9ada7ef970af3e2536bd133cb7830e \
--hash=sha256:fb855620d04d0c286058db941c106cfc60bc1e4882ea20b6dd499f3668a1afbe \ --hash=sha256:e128cddeccae4146b556684a0d5426454fff5bfe7306862e5a8ce6b471568af4 \
--hash=sha256:fd774825510350b614121bfa84d95c6eb08f7e93c0fe7faf8760423a43dbad8b --hash=sha256:fa37da7ad16c47391016b5f984fb60e175e1ab0b478f04920fd6d1f61123ce4d
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -r src/backend/requirements.in
@@ -971,25 +971,19 @@ gunicorn==26.0.0 \
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -r src/backend/requirements.in
icalendar==7.1.0 \ icalendar==7.1.2 \
--hash=sha256:10cd223c792fcc43bee4c3ebe3149d4cf32406c85cfef146624df5a0d414260f \ --hash=sha256:01c76243c76c549f58bb51510a8f0a4edb7c539726adda1356dfd0dc04fb7a53 \
--hash=sha256:6de875370d22fc4aff172ad7c439b39fb109dc2eab9ce358fcb95e8689ad7b56 --hash=sha256:ebc43ebeb357be98984b573d975118008dee3410d8df28b054ef2943cf3e367e
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# django-ical # django-ical
idna==3.15 \ idna==3.16 \
--hash=sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8 \ --hash=sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5 \
--hash=sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc --hash=sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# django-anymail # django-anymail
# requests # requests
importlib-metadata==8.7.1 \
--hash=sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb \
--hash=sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151
# via
# -c src/backend/requirements.txt
# opentelemetry-api
inflection==0.5.1 \ inflection==0.5.1 \
--hash=sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417 \ --hash=sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417 \
--hash=sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2 --hash=sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2
@@ -1014,6 +1008,12 @@ jinja2==3.1.6 \
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -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 \ jmespath==1.1.0 \
--hash=sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d \ --hash=sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d \
--hash=sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64 --hash=sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64
@@ -1039,141 +1039,141 @@ jwcrypto==1.5.7 \
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# django-oauth-toolkit # django-oauth-toolkit
lxml==6.1.0 \ lxml==6.1.1 \
--hash=sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2 \ --hash=sha256:05a82eb6e1530a64f26225b55cbd178113bd0b5af1c2b625f25e5296742c26d2 \
--hash=sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773 \ --hash=sha256:07a4a68e286ee7a1ed7dfb8af83e615757c0ccfe9f18c6b4ea6771388d9ba8c9 \
--hash=sha256:045e387d1f4f42a418380930fa3f45c73c9b392faf67e495e58902e68e8f44a7 \ --hash=sha256:09dd5b7075dc2f7709654a46543ba1ea3c2e217b2ed8fbd413a8a945a0f40f60 \
--hash=sha256:05b9b8787e35bec69e68daf4952b2e6dfcfb0db7ecf1a06f8cdfbbac4eb71aad \ --hash=sha256:0b7e8a14c8634bf6f7a568634cb395305a6d964aeb5b7ee32248094bed3a7e2c \
--hash=sha256:07f98f5496f96bf724b1e3c933c107f0cbf2745db18c03d2e13a291c3afd2635 \ --hash=sha256:104c09bda8d2a562824c0e319d0768ce26a779b7601e0931d33b09b53c392ef7 \
--hash=sha256:08950a23f296b3f83521577274e3d3b0f3d739bf2e68d01a752e4288bc50d286 \ --hash=sha256:126c93f7f56f0eda92f6d8c619edc463a4f23d9252f1c9d0405a76f25fa9f11a \
--hash=sha256:0d082495c5fcf426e425a6e28daaba1fcb6d8f854a4ff01effb1f1f381203eb9 \ --hash=sha256:162af1091cd785f2f27e62d3547ae9bc58ec5c86dd314d67021fd02463708d83 \
--hash=sha256:0f0f08beb0182e3e9a86fae124b3c47a7b41b7b69b225e1377db983802404e54 \ --hash=sha256:17e0e18d4ad8adbd0399291bc44845b69d9dd68439a3cdebdf35ff902ec05072 \
--hash=sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c \ --hash=sha256:18b73c339ae29b90fd2d06e58ebd555a751bde9cd6bbd36cc0281b9a2c94e9d8 \
--hash=sha256:11a873c77a181b4fef9c2e357d08ed399542c2af1390101da66720a19c7c9618 \ --hash=sha256:19607c6bbff2a44cf3fe8250abccd20942d3462473e0a721d01d379ed017e462 \
--hash=sha256:183bfb45a493081943be7ea2b5adfc2b611e1cf377cefa8b8a8be404f45ef9a7 \ --hash=sha256:19b7ab10b210b0b3ad7985d9ac4eb66ab09a90b20fe6e2f7ba55d01a234345d0 \
--hash=sha256:19f4164243fc206d12ed3d866e80e74f5bc3627966520da1a5f97e42c32a3f39 \ --hash=sha256:1d4962d4c66bf830a7e59ed6cfc17d148149898a3aefa8ec6e59763e6e3ed085 \
--hash=sha256:1ae225f66e5938f4fa29d37e009a3bb3b13032ac57eb4eb42afa44f6e4054e69 \ --hash=sha256:1db753c9115ec7100d073b744d17e25e88a8f90f5c39b2f5dd878149af59671f \
--hash=sha256:1bc4cc83fb7f66ffb16f74d6dd0162e144333fc36ebcce32246f80c8735b2551 \ --hash=sha256:1dde6131244bba38a17c745836ba190bc753fd73c9291666287fd0a3fa3dcf30 \
--hash=sha256:1dd6a1c3ad4cb674f44525d9957f3e9c209bb6dd9213245195167a281fcc2bdc \ --hash=sha256:25c6997a9a534e016695a0ba06b2f07945de682731ff01065b6d5a4474179da1 \
--hash=sha256:20cf4d0651987c906a2f5cba4e3a8d6ba4bfdf973cfe2a96c0d6053888ea2ecd \ --hash=sha256:26e6eda8d38c1fcab1090dd196ee87cbd13788e531937610e2589085de074e77 \
--hash=sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd \ --hash=sha256:27acc820660aaffa4f7c087f29120e12980f7779d56d8492d263170111284740 \
--hash=sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405 \ --hash=sha256:2a0217714657e023ef4293500f65aa20fce6164c8fd6b08fa5bd4a859fb14b9b \
--hash=sha256:23a5dc68e08ed13331d61815c08f260f46b4a60fdd1640bbeb82cf89a9d90289 \ --hash=sha256:2c8daa471358dc2d6fcf02165e80ec68f77871a286df95bc5cc3816153b0fd2c \
--hash=sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b \ --hash=sha256:30a89d3ac8faec007453fb541f3f46807eeec88edd5826f6e3fe001752a2c621 \
--hash=sha256:2593a0a6621545b9095b71ad74ed4226eba438a7d9fc3712a99bdb15508cf93a \ --hash=sha256:31033dc34636ea6b7d5cc11b1ddbda78a14de858ba9d3e1ed4b69a3085bc521e \
--hash=sha256:264c605ab9c0e4aa1a679636f4582c4d3313700009fac3ec9c3412ed0d8f3e1d \ --hash=sha256:32ab449a5486f6c758e849bb86710d0e45edc24a04e250c01555f8f5653958f8 \
--hash=sha256:26c5272c6a4bf4cf32d3f5a7890c942b0e04438691157d341616d02cca74d4bd \ --hash=sha256:3483644525531e1d5762b0c44a8e18b6efba321b6dcf8a8952de10b037618bca \
--hash=sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac \ --hash=sha256:34c2d737beabfe35baada43941ed519251e9a12e779031496bcd5d539fcfd730 \
--hash=sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62 \ --hash=sha256:3779def59032b81e44a5f70096ef6bf2082f8d901937dca354474ba09782e245 \
--hash=sha256:29f5c00cb7d752bce2c70ebd2d31b0a42f9499ffdd3ecb2f31a5b73ee43031ad \ --hash=sha256:37a58976370f36d9329d118ad0b953c5aeb9119ac9c6a4e258942a225d0573a1 \
--hash=sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f \ --hash=sha256:3893c14c4b6ac5b2d54ba8cf03e99fe5104e592de491f19bd6b82756c09f8004 \
--hash=sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad \ --hash=sha256:3a12689be69a28ddaa0ab99a5a1137da2afd5f8f16df7b5680b66f616d3eda1d \
--hash=sha256:363e47283bde87051b821826e71dde47f107e08614e1aa312ba0c5711e77738c \ --hash=sha256:3ab541146f1f6968c462d6c2ac495148e8cdba2f8347700b2141b6ec5a75bf52 \
--hash=sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292 \ --hash=sha256:3abf332af33a74288675d936fe861fd4344da0dd6622193fbc4f2bfbb35536b5 \
--hash=sha256:37448bf9c7d7adfc5254763901e2bbd6bb876228dfc1fc7f66e58c06368a7544 \ --hash=sha256:3fd9728a2735fda14f4e8235830c86b539e9661e849665bf926d3f867943b4bf \
--hash=sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491 \ --hash=sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc \
--hash=sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120 \ --hash=sha256:441dd227fa0690eb9fc81edabc63cdcefc212bba99b906dcf6e32cc1a9d3e533 \
--hash=sha256:3ae5d8d5427f3cc317e7950f2da7ad276df0cfa37b8de2f5658959e618ea8512 \ --hash=sha256:469e3618338bd7ab5beb412d2439825479fcf0dab99e394ca563dbc4eaf6c834 \
--hash=sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb \ --hash=sha256:47402e62c52ff5988c1e8c6c63177f5708bccf48e366dea4e3dcf1e645e04947 \
--hash=sha256:40d9189f80075f2e1f88db21ef815a2b17b28adf8e50aaf5c789bfe737027f32 \ --hash=sha256:4f0dd2f01f9f8a89f565d000e03abcf0a13d692a346c8d22f628d49af098777a \
--hash=sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2 \ --hash=sha256:53b7d2b7a10b1c35c0a5e21e9224accf60c1bbfba523990732e521b2b73adef2 \
--hash=sha256:41dcc4c7b10484257cbd6c37b83ddb26df2b0e5aff5ac00d095689015af868ec \ --hash=sha256:53c909b62a0532183542fed00c5a7218258c56292d409bc789886fe1cb04c438 \
--hash=sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88 \ --hash=sha256:54a7f95e4de5fb94e2f9f4b9055c6ba33bf3d628fd77a1d647c5923caa2cdcdc \
--hash=sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43 \ --hash=sha256:556e94a63c9b04716f8e4de2abb65775061f846e89331b6c5be79183a24f98ea \
--hash=sha256:4642e04449a1e164b5ff71ffd901ddb772dfabf5c9adf1b7be5dffe1212bc037 \ --hash=sha256:55b03549819867ea141c0202242c4816c82e52ec36e7e648db9d8da5a3dc3ed6 \
--hash=sha256:468479e52ecf3ec23799c863336d02c05fc2f7ffd1a1424eeeb9a28d4eb69d13 \ --hash=sha256:581d4c8ae690a6609e64862dd6b7c2489635c2d13907fc2b20f2bc200ff1d21e \
--hash=sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03 \ --hash=sha256:58bb955caba94e467d2a96da17660d2d704e0675894cba21ab8a775b8621fd1c \
--hash=sha256:481d6e2104285d9add34f41b42b247b76b61c5b5c26c303c2e9707bbf8bd9a64 \ --hash=sha256:5b7328b46d49fc9477d91ae8f6d55340347d827b7734ba3ea33faae0efef1383 \
--hash=sha256:4937460dc5df0cdd2f06a86c285c28afda06aefa3af949f9477d3e8df430c485 \ --hash=sha256:5ba186ad207446c65d3bb3d3e0412b032b1d9f595e59861e2354798c5703d955 \
--hash=sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16 \ --hash=sha256:5bec7d03d78d853597d6107854c2310ce3f761fd218fe9fe91d5101fcf6c2efe \
--hash=sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb \ --hash=sha256:5c6bf403fbb3b3e348a561a5f4f0b9961835657981c802a1df03653eef8a9074 \
--hash=sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9 \ --hash=sha256:5f6994074ebae6ffb04447268e37dc16edc304f9859cf91acb86e0af6c1b395c \
--hash=sha256:4e2c54d6b47361d0f1d3bc8d4e082ad87201e56ccdcca4d3b9ee3644ff595ec8 \ --hash=sha256:62aeb7e85b5d60320b9d77eef2e773994e2c0ce10121b277e0a19804e1654a5a \
--hash=sha256:52b0ac6903cf74ebf997eb8c682d2fbac7d1ab7e4c552413eec55868a9b73f39 \ --hash=sha256:63876be28efefa04a1df615b46770e82042cce445cfdce55160522f57b231ccb \
--hash=sha256:546b66c0dd1bb8d9fa89d7123e5fa19a8aff3a1f2141eb22df96112afb17b842 \ --hash=sha256:639f6c857d91d9be29bd7502348d6736dab168b54b5158cd899abf11684dc186 \
--hash=sha256:56971379bc5ee8037c5a0f09fa88f66cdb7d37c3e38af3e45cf539f41131ac1f \ --hash=sha256:640f97d43d867bcb9c75b3af013b64850756b746cb6bce8ace83b70da3abba9d \
--hash=sha256:5715e0e28736a070f3f34a7ccc09e2fdcba0e3060abbcf61a1a5718ff6d6b105 \ --hash=sha256:649dda677cf3bd6ac9ae14007ba0c824ded8ce5808b53fc7431d9140399118c1 \
--hash=sha256:5cfa1a34df366d9dc0d5eaf420f4cf2bb1e1bebe1066d1c2fc28c179f8a4004c \ --hash=sha256:6540377fbd53fe1b629172288c464fb18db11ce1fa7dc15891da10aa9dcc3e7f \
--hash=sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7 \ --hash=sha256:6689e828a94eee4f139408c337bb198e014724bb8a8c26d3cfac49d119ed69a6 \
--hash=sha256:6262b87f9e5c1e5fe501d6c153247289af42eb44ad7660b9b3de17baaf92d6f6 \ --hash=sha256:68a9198d0fc122d14bb76837de9aa80cf84caed990b5b237f532ed87d3706736 \
--hash=sha256:63aeafc26aac0be8aff14af7871249e87ea1319be92090bfd632ec68e03b16a5 \ --hash=sha256:6b1761fbf9ec984e2e9d9c589ef5f5fd684b7c19f92aadd567a26c5224958db6 \
--hash=sha256:690022c7fae793b0489aa68a658822cea83e0d5933781811cabbf5ea3bcfe73d \ --hash=sha256:70cdfd80589d59e43e18005dd7244e8895e93db8ab6a620b7e23df5445a4e3d2 \
--hash=sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9 \ --hash=sha256:70ef8a7e102a1508f8121aae5b0867abd663f72c14f0a9c937e6554cb4587b7b \
--hash=sha256:73becf6d8c81d4c76b1014dbd3584cb26d904492dcf73ca85dc8bff08dcd6d2d \ --hash=sha256:73bc2086f141224ebddb7fc5c6a36ca58b31b94b561e1dfe8e073e3270fad1e7 \
--hash=sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb \ --hash=sha256:74a9717fd0d82effef5c2854f0d917231d5324b5a3eb7275c43ac9fa32f97a14 \
--hash=sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f \ --hash=sha256:752d3bbfe874715ccd0aec7f88d7fc623c0f1fd7aa7b3238a084e017bad2a009 \
--hash=sha256:76b958b4ea3104483c20f74866d55aa056546e15ebe83dd7aecd63698f43b755 \ --hash=sha256:762ff394d5bd56da0cf034a23dcce4e13923f15321a2adfa2ac00201dc6d3fca \
--hash=sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb \ --hash=sha256:76447f65250ed2501ead1a1552f5ce8edff159a86f308348e6a9c4acb5e1f1b4 \
--hash=sha256:7ba11752e346bd804ea312ec2eea2532dfa8b8d3261d81a32ef9e6ab16256280 \ --hash=sha256:766b010012d59470072c1816b5b6c69f1d243e5db36ea5968e94accf430a4635 \
--hash=sha256:7da13bb6fbadfafb474e0226a30570a3445cfd47c86296f2446dafbd77079ace \ --hash=sha256:787b2496d0dbe8cd180984e8d29e3a6f76e7ea34db781cb3bd55e4ba1ef8b4ee \
--hash=sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e \ --hash=sha256:793033d6c5cdf33a573f910d9bea14ef8f5771820411d118da8e1182edb53d5e \
--hash=sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33 \ --hash=sha256:7d47866cb32fb503450b6edc9df355d10dc49836af2e89901bd6ac6b0896d9d9 \
--hash=sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5 \ --hash=sha256:7f7a92e8583f06b1fd49d01158143b8461cfcd135dcb10ec807270a3051bd603 \
--hash=sha256:81ff55c70b67d19d52b6fd118a114c0a4c97d799cd3089ff9bd9e2ff4b414ee2 \ --hash=sha256:80c2dfadb855da477cf73373ad29a333535dedb9b12bad02c9814c8e2b43bf08 \
--hash=sha256:857efde87d365706590847b916baff69c0bc9252dc5af030e378c9800c0b10e3 \ --hash=sha256:83b6b30eb131da7a75b601f28c5d6971e6ed3e887919bf6b6a1ad3c2df289080 \
--hash=sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585 \ --hash=sha256:86281fbdd6a8162756f8d603f37e3435bfa38043adb79c6dc6a2dfee065e7525 \
--hash=sha256:8c11b984b5ce6add4dccc7144c7be5d364d298f15b0c6a57da1991baedc750ce \ --hash=sha256:86c89b9d55ebf820ad7c90bc533410f0d098054f293351f10603c0c46ff598f5 \
--hash=sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946 \ --hash=sha256:876e1ff5930ed8bf295ec5ef9a8155e9b6b1876bbf1deed8b3a8069311875a8f \
--hash=sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e \ --hash=sha256:88136950da4d13c318bde414ce10219931937851327f44328f2df4d2c4614067 \
--hash=sha256:9147d8e386ec3b82c3b15d88927f734f565b0aaadef7def562b853adca45784a \ --hash=sha256:88d8cb75b9d82858497a5393e3c63cfbf03035225e4b35a49ed7ccb151e4dc0e \
--hash=sha256:920354904d1cb86577d4b3cfe2830c2dbe81d6f4449e57ada428f1609b5985f7 \ --hash=sha256:8be8ad51249698103d24b0571df35a10990fbe93dd043b6c024172189485f5e3 \
--hash=sha256:942454ff253da14218f972b23dc72fa4edf6c943f37edd19cd697618b626fac5 \ --hash=sha256:8d43ca737b20e106e4aebc42b2f3ae19f00ba63d7eb731698ee083d72d15646f \
--hash=sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28 \ --hash=sha256:8dadbe5b217ff35b6a8d16610dd710219b59b76d13f0e3f0d9f36786206e4485 \
--hash=sha256:976a6b39b1b13e8c354ad8d3f261f3a4ac6609518af91bdb5094760a08f132c4 \ --hash=sha256:9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13 \
--hash=sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c \ --hash=sha256:96f2ec43df44b1f76249ee0a615334f9b5b060e1c8bd90e706dad2d14d02f383 \
--hash=sha256:9c03e048b6ce8e77b09c734e931584894ecd58d08296804ca2d0b184c933ce50 \ --hash=sha256:98fc784c2c1440667aeedf8465bdfe10208acf0ead656a2c68627299f546b315 \
--hash=sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9 \ --hash=sha256:9e36f163528fc50cbef305f02a5fd66d404edf7049cdaff211dbc2cba5a7013e \
--hash=sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495 \ --hash=sha256:9eb9b5a968f6e0f6d640092a567e14529ff8cea2e29d00da6f78a79fa49f013c \
--hash=sha256:9f93d5b8b07f73e8c77e3c6556a3db269918390c804b5e5fcdd4858232cc8f16 \ --hash=sha256:9f76acfb5f68ba982635a53fd985a8044be98a35b43232c2a1ee235ffab3e1dd \
--hash=sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45 \ --hash=sha256:a088f287f7d8275a33c07f2cac6c50b9319309a0200a39e7e75d80c707723099 \
--hash=sha256:a02ca8fe48815bddcfca3248efe54451abb9dbf2f7d1c5744c8aa4142d476919 \ --hash=sha256:a10bd2fd62e8ce916ececb342f348f190724a098c1faa056fdfb2a22ad5e8660 \
--hash=sha256:a1d9b99e5b2597e4f5aed2484fef835256fa1b68a19e4265c97628ef4bf8bcf4 \ --hash=sha256:a4bbea04c97f6d78a48e3fbc1cb9116d2780b1b39e03a23f6eb9b603fd61f510 \
--hash=sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc \ --hash=sha256:aa366a1e55b8ebfe8ca8ddc3cfe75c8ebade181aeb0f661d0cb05986b647f72a \
--hash=sha256:a31286dbb5e74c8e9a5344465b77ab4c5bd511a253b355b5ca2fae7e579fafec \ --hash=sha256:aa49e06d94aba782c6a02eecb7e507969e7e7a41b267f1b359bb35585f295d5b \
--hash=sha256:a86f06f059e22a0d574990ee2df24ede03f7f3c68c1336293eee9536c4c776cd \ --hash=sha256:aad9aa39483ed8ec44d6d2e59e5b98a0d80676ef0d92f44bfc374836111f62f5 \
--hash=sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8 \ --hash=sha256:aae97dfdb60715c164419ac2532a76d013c3918a665eb6cb7288098b5f349aaf \
--hash=sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f \ --hash=sha256:abbefa31eee84842140f67acef1c828e28bba8bbf0c3bc6e5492a9af88152c28 \
--hash=sha256:b6c2f225662bc5ad416bdd06f72ca301b31b39ce4261f0e0097017fc2891b940 \ --hash=sha256:ac931cdc9442c1763b8a8f6cd62c0c938737eafc5be75eff88df55fc73bc0d00 \
--hash=sha256:bb40648d96157f9081886defe13eac99253e663be969ff938a9289eff6e47b72 \ --hash=sha256:acd7d70b64c0aae0c7922cca83d288a16f5f6da523637697872253415269baef \
--hash=sha256:bba078de0031c219e5dd06cf3e6bf8fb8e6e64a77819b358f53bb132e3e03366 \ --hash=sha256:add8cf6ddf9a65116119a28ece0f7886e30af27ba724a7594305f1d1b58a92a1 \
--hash=sha256:bc783ee3147e60a25aa0445ea82b3e8aabb83b240f2b95d32cb75587ff781814 \ --hash=sha256:aee395f5d0927f947758b4ec119fd5fc8ec71f07a1c5c52077b30b04c0fa6955 \
--hash=sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690 \ --hash=sha256:b1b963fd8f5caa68e99dfae060d54de1fe9cba899b8718b44a00cdca53c3e590 \
--hash=sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13 \ --hash=sha256:b2d444f2e66624d68e9c6b211e28a76e22fff5fcabcfff4deac18b529b7d4137 \
--hash=sha256:c08da09dc003c9e8c70e06b53a11db6fb3b250c21c4236b03c7d7b443c318e7a \ --hash=sha256:b8d812c6011c08b8111a15e54dd990b8923692d80adf35488bee34026c35accf \
--hash=sha256:c3592631e652afa34999a088f98ba7dfc7d6aff0d535c410bea77a71743f3819 \ --hash=sha256:ba96ae44888e0185281e937633a743ea90d5a196c6000f82565ebb0580012d40 \
--hash=sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c \ --hash=sha256:bdebcc8a75d38c7598dfb2c9ed852d7a9eb4a10d6e2d0764b919b802bf32ac88 \
--hash=sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86 \ --hash=sha256:c07da4cebf6889f03ebac8d238f62318e29f495de0aa18a51ea14e61ae907e2e \
--hash=sha256:c53fa3a5a52122d590e847a57ccf955557b9634a7f99ff5a35131321b0a85317 \ --hash=sha256:c08e5c694306507275f2290073350c4f32e383db15213b2c69e7ff39c1193840 \
--hash=sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180 \ --hash=sha256:c4f469aebd783bb741c2ecb2a681008fd26bfe5c16a9a72ed5467f834e810df2 \
--hash=sha256:c748ebcb6877de89f48ab90ca96642ac458fff5dec291a2b9337cd4d0934e383 \ --hash=sha256:c5d7152ec39ca7c402d8fb9bad86140a15b9503bd0c54484e3f1bbe3dd37ceca \
--hash=sha256:c871299c595ee004d186f61840f0bfc4941aa3f17c8ba4a565ead7e4f4f820ee \ --hash=sha256:c674693f055fa2495de12292cb45e9944199d8eaef5a2dec45175c7c61cb73e3 \
--hash=sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe \ --hash=sha256:c6ed5141a5c7507cf3ee76bd363b0d6f801e3321adc35b5d825a23115faa5465 \
--hash=sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181 \ --hash=sha256:c921ba5c51e4e9f63b8b00267d06566e1f63407408a0496da2d1d0bfc819c7fc \
--hash=sha256:cec05be8c876f92a5aa07b01d60bbb4d11cfbdd654cad0561c0d7b5c043a61b9 \ --hash=sha256:c9a4b821dc7055bf9e05ff5719e18ec501f75c0f0bbfabd573b277559780833d \
--hash=sha256:d036ee7b99d5148072ac7c9b847193decdfeac633db350363f7bce4fff108f0e \ --hash=sha256:c9f79d5325907f13e1be0b3e4dacc1049d1dffc4aeee3c995284bea5fe0fab7d \
--hash=sha256:d0d799ff958655781296ec870d5e2448e75150da2b3d07f13ff5b0c2c35beefd \ --hash=sha256:cd312b9692e831d2ffcad61eab31d91d4b4655a962e61de8fb410472cbcd37aa \
--hash=sha256:d1392c569c032f78a11a25d1de1c43fff13294c793b39e19d84fade3045cbbc3 \ --hash=sha256:cea3f4c1af79af13cdb2da0c028111d8f8522d4f22a000c82385535f24e5cf3a \
--hash=sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d \ --hash=sha256:cecdd5dfdc87b1fd87dbf81d4b037a544f47f4c744200a67013771682d67686a \
--hash=sha256:d3829a6e6fd550a219564912d4002c537f65da4c6ae4e093cc34462f4fa027ad \ --hash=sha256:cf9d57306d848218f3601fee7601fab1a327c942d56e2e97610583cb4dd74206 \
--hash=sha256:d43aa26dcda363f21e79afa0668f5029ed7394b3bb8c92a6927a3d34e8b610ea \ --hash=sha256:d34bbf07dbc7ca5970671b1512e928991fb5e9d95365636c9b2d8b4f53af405e \
--hash=sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24 \ --hash=sha256:d49514be2f28d895c38cf9d2b72d7b9a07d00314519f456c0b50b53cfcf4c785 \
--hash=sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d \ --hash=sha256:d680fbcb768404c601ecb43519ecd8461f6954cb11c06a78962f666832ccfca8 \
--hash=sha256:db88156fcf544cdbf0d95588051515cfdfd4c876fc66444eb98bceb5d6db76de \ --hash=sha256:db1d75f6617a49c1c01bc7023713e0ff59ab32c9579ae62a7674c0e34f3b0b0a \
--hash=sha256:de550d129f18d8ab819651ffe4f38b1b713c7e116707de3c0c6400d0ef34fbc1 \ --hash=sha256:dcb292aa7fe485ceff7af4f92e46c5af397daec5dff64871a528f0fc47a3cc5b \
--hash=sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d \ --hash=sha256:e07c65f443c887bbcf31cc1771d932ecc192a5273943589b3c7572b749f1ffb2 \
--hash=sha256:e3c4f84b24a1fcba435157d111c4b755099c6ff00a3daee1ad281817de75ed11 \ --hash=sha256:e902da4b04e6b52e5893900d4b8ab46068f75f3561f01bf1080957f9fd932ed6 \
--hash=sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9 \ --hash=sha256:e9308ff8241c532df3f3e570f9a5aeed6c853f888512ba4b75638d7c11c95ef6 \
--hash=sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2 \ --hash=sha256:eb7c9811bfaa8b1ed5ed319f5d370dfbcaa59d52ea64be2a5a85e18195930354 \
--hash=sha256:e80807d72f96b96ad5588cb85c75616e4f2795a7737d4630784c51497beb7776 \ --hash=sha256:ebe6af670449830d6d9b752c256a983291c766a1365ba5d5460048f9e33a7818 \
--hash=sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f \ --hash=sha256:ed21202aec73cda4d55d1ce57b389aadb90ffb044e6cd1080b8347efe1b1ec84 \
--hash=sha256:f0cea5b1d3e6e77d71bd2b9972eb2446221a69dc52bb0b9c3c6f6e5700592d93 \ --hash=sha256:efe0374196335f93b53269acd811b944f2e6bdc88e8894f214bd636455484909 \
--hash=sha256:f15401d8d3dbf239e23c818afc10c7207f7b95f9a307e092122b6f86dd43209a \ --hash=sha256:f64ec5397ea6a41fc1b4af0380d79b44a755b5531dcaccd9940fb260dca93038 \
--hash=sha256:f504d861d9f2a8f94020130adac88d66de93841707a23a86244263d1e54682f5 \ --hash=sha256:f6ac4ef4d82dff54670227a69c67782ae0b811b5cf6b17954f1e8f7502fc0d1d \
--hash=sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d \ --hash=sha256:f6f0ce10945fab9c4c06ce14e22af9059d1a87493a9af4501a5b0b9187e21cf2 \
--hash=sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d \ --hash=sha256:f8844cd288697c6425c9beba919302241e3278871dc6519515e72b04e987abcf \
--hash=sha256:fcf3da95e93349e0647d48d4b36a12783105bcc74cb0c416952f9988410846a3 \ --hash=sha256:fe0306bd29505a9177aac19f1877174b0e7422c222a59f70b2cd41633448c3dc \
--hash=sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086 \ --hash=sha256:ff3f333630ab480244a1bff72043e511a91eb22e7595dead8653ee5612dd8f3d \
--hash=sha256:ffb34ea45a82dd637c2c97ae1bbb920850c1e59bcae79ce1c15af531d83e7215 --hash=sha256:ffecec8eb889b58ba9be5b95fb1cc78e22ea8eedea38e8736a1568fe1979250e
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# python3-saml # python3-saml
@@ -1321,9 +1321,9 @@ openpyxl==3.1.5 \
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# tablib # tablib
opentelemetry-api==1.41.1 \ opentelemetry-api==1.42.1 \
--hash=sha256:0ad1814d73b875f84494387dae86ce0b12c68556331ce6ce8fe789197c949621 \ --hash=sha256:51a69edacadbc03a8950ace1c4c21099cacc538820ac2c9e36277e78cebba714 \
--hash=sha256:a22df900e75c76dc08440710e51f52f1aa6b451b429298896023e60db5b3139f --hash=sha256:56c63bea9f77b62856be8c47600474acad853b2924b99b1687c4cb6297166716
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -r src/backend/requirements.in
@@ -1341,34 +1341,34 @@ opentelemetry-api==1.41.1 \
# opentelemetry-instrumentation-wsgi # opentelemetry-instrumentation-wsgi
# opentelemetry-sdk # opentelemetry-sdk
# opentelemetry-semantic-conventions # opentelemetry-semantic-conventions
opentelemetry-exporter-otlp==1.41.1 \ opentelemetry-exporter-otlp==1.42.1 \
--hash=sha256:299a2f0541ca175df186f5ac58fd5db177ba1e9b72b0826049062f750d55b47f \ --hash=sha256:2d9ebaed714377a67d224d46795ddcc11d2c877fa5de35fda70b6f3b010729a9 \
--hash=sha256:db276c5a80c02b063994e80950d00ca1bfddcf6520f608335b7dc2db0c0eb9c6 --hash=sha256:aedd54545bb0587cd45210abdc8be545af9c01413f3307786e276df1e3c83bee
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -r src/backend/requirements.in
opentelemetry-exporter-otlp-proto-common==1.41.1 \ opentelemetry-exporter-otlp-proto-common==1.42.1 \
--hash=sha256:0e253156ea9c36b0bd3d2440c5c9ba7dd1f3fb64ba7a08fc85fbac536b56e1fb \ --hash=sha256:04f1f01fb597c4249dfcd7f8b861c902c2102369d376d9d346ff38de4469a2ee \
--hash=sha256:10da74dad6a49344b9b7b21b6182e3060373a235fde1528616d5f01f92e66aa9 --hash=sha256:f48d395ab815b444da118868977e9798ea354c25737d5cf39578ae894011c140
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http # opentelemetry-exporter-otlp-proto-http
opentelemetry-exporter-otlp-proto-grpc==1.41.1 \ opentelemetry-exporter-otlp-proto-grpc==1.42.1 \
--hash=sha256:537926dcef951136992479af1d9cd88f25e33d56c530e9f020ed57774dca2f94 \ --hash=sha256:0ae1177e2038b18a929b3098215243631ef91136cba26b7e2b12790ceb7e87cc \
--hash=sha256:b05df8fa1333dc9a3fda36b676b96b5095ab6016d3f0c3296d430d629ba1443b --hash=sha256:975c4461f167dd8ed8857d68d3b6b25f3d272eab896f6a9470d0f5b90e2faf15
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# opentelemetry-exporter-otlp # opentelemetry-exporter-otlp
opentelemetry-exporter-otlp-proto-http==1.41.1 \ opentelemetry-exporter-otlp-proto-http==1.42.1 \
--hash=sha256:1a21e8f49c7a946d935551e90947d6c3eb39236723c6624401da0f33d68edcb4 \ --hash=sha256:00a16da1b312a1d6c7233d600d557c91df71125af73020f3b9a7765bd699d59d \
--hash=sha256:4747a9604c8550ab38c6fd6180e2fcb80de3267060bef2c306bad3cb443302bc --hash=sha256:bf142a21035d7571ac3a09cb2e5639f49886f243972883cfe777ed3bf02b734d
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# opentelemetry-exporter-otlp # opentelemetry-exporter-otlp
opentelemetry-instrumentation==0.62b1 \ opentelemetry-instrumentation==0.63b1 \
--hash=sha256:90e92a905ba4f84db06ac3aec96701df6c079b2d66e9379f8739f0a1bdcc7f45 \ --hash=sha256:32368d6ae52c8de20aa790a6ad86b10a76f09956092337ae37d675773990e541 \
--hash=sha256:976fc6e640f2006599e97429c949e622c108d0c17c2059347d1e6c93c707f257 --hash=sha256:f1986716d52cc316ea5f60189098726a9071d8ecc0eee96c9ed110be08bade9c
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# opentelemetry-instrumentation-dbapi # opentelemetry-instrumentation-dbapi
@@ -1380,82 +1380,82 @@ opentelemetry-instrumentation==0.62b1 \
# opentelemetry-instrumentation-sqlite3 # opentelemetry-instrumentation-sqlite3
# opentelemetry-instrumentation-system-metrics # opentelemetry-instrumentation-system-metrics
# opentelemetry-instrumentation-wsgi # opentelemetry-instrumentation-wsgi
opentelemetry-instrumentation-dbapi==0.62b1 \ opentelemetry-instrumentation-dbapi==0.63b1 \
--hash=sha256:82755a0f013980b2c000a10452fb91ab5135b937a8afe37c45f34e62ecc7a2b8 \ --hash=sha256:1bc842ddcacb7ec3622ef306ffdb3c584bf97df79d381e03604ea6eb23c63781 \
--hash=sha256:dd2cc4085e832a708835218833b839a88ef0ace66851d14debefe6b2ef363a3c --hash=sha256:406978ed56bcfc5fd246fd918e6b36d0f5de26fa396c78cf63326a7b530597c8
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# opentelemetry-instrumentation-psycopg # opentelemetry-instrumentation-psycopg
# opentelemetry-instrumentation-pymysql # opentelemetry-instrumentation-pymysql
# opentelemetry-instrumentation-sqlite3 # opentelemetry-instrumentation-sqlite3
opentelemetry-instrumentation-django==0.62b1 \ opentelemetry-instrumentation-django==0.63b1 \
--hash=sha256:268c9b3d234510ab835acea0524ba65361389f6e2c291b495ba8072c7bb59003 \ --hash=sha256:909ea4afbb18f16eb811d7ac330ed0f6fda7765191d49bccb8cca38c4fbc005f \
--hash=sha256:5ac96ef25019fbea5e2739c9b4b7ce17e4379e2b1ec9fdce6b696a5c09ceabbe --hash=sha256:f2071d2f92e4779c5a14dd452b0dfe426343599e6efa9d888304fb639a9f3101
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -r src/backend/requirements.in
opentelemetry-instrumentation-psycopg==0.62b1 \ opentelemetry-instrumentation-psycopg==0.63b1 \
--hash=sha256:61c8a1794d6e11c7bca1735c2b87ef8c04b5d7202e962409d66d204f84c01be1 \ --hash=sha256:4d74ec2dda8e54ead9bd71f134af984792f77e1d8809591ae53edadddd4c8fc0 \
--hash=sha256:f08e843e81656271ddc8c8194a54a5f3c8ebe0cb137e3166af9a2ff014978b5e --hash=sha256:86819f4cccea21a47920807826495797b7261cc1893c8e855415e76fb7b8e2e8
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -r src/backend/requirements.in
opentelemetry-instrumentation-pymysql==0.62b1 \ opentelemetry-instrumentation-pymysql==0.63b1 \
--hash=sha256:12e42f891978a3a41bc564bb81e2299d3654659589f705064a4bb10e93e493f8 \ --hash=sha256:2cf6a24e136b5b0c3f6e59675fc44b42ad2b97474b075a82859f9868a9252648 \
--hash=sha256:50d2efdde1bd8d826cbceffe5b3ce0ffd421349d609200eab0f3e632945cd304 --hash=sha256:6b54b83c86ac6c2e8dae642bbee4894e2283ceb5da23d571f1abb7060f7b62f4
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -r src/backend/requirements.in
opentelemetry-instrumentation-redis==0.62b1 \ opentelemetry-instrumentation-redis==0.63b1 \
--hash=sha256:2d3c421d95e05ade075bee5becbe34e743b1cdf5bdee2085cb524f88c4f13dcb \ --hash=sha256:28d235159df43cc2bc8779af5c602afad1e08603fff75ac8ca34dd1bf30a9cb9 \
--hash=sha256:9aedd02c1acf631251d1d676634db47da9da04e0a626cd0c7d83fe0eb791d165 --hash=sha256:f0e51c4006f68e340abbf28a7995feff004de78649697cbdf3bac0072cacd082
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -r src/backend/requirements.in
opentelemetry-instrumentation-requests==0.62b1 \ opentelemetry-instrumentation-requests==0.63b1 \
--hash=sha256:67a79c4b67e2192445c1cf03d62126fa623065688d8bd1a9f87f858b0e5f0286 \ --hash=sha256:513fcaa3d93debbdb359c00ce1a137a34a89ee908c51ac43beb7e8c18ac2b3cd \
--hash=sha256:ca348f2f51b715c21e86d82106d784f7069ae849c3e636ab37e34dc0ba510b8c --hash=sha256:935c980a11e33bfd7ed969c741e4bd7c84077045651469f10e163534368d87f7
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -r src/backend/requirements.in
opentelemetry-instrumentation-sqlite3==0.62b1 \ opentelemetry-instrumentation-sqlite3==0.63b1 \
--hash=sha256:14ba18aed35a01e1d89bb814e49f7d6dd90bb68b2034ccf047801ca4659dba27 \ --hash=sha256:3d61afda8358dc32135fabd27e5934bd25ea0eed68d64c34508f24ba8d723efc \
--hash=sha256:3ab0d614bab9956126ce81a22315bf58d56d653a65675a66e8b525f880ec3016 --hash=sha256:cc11c68cccde061dc8bcc5f80fe00bb0105a0fbb11b390859a23ca91ccd4e3b6
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -r src/backend/requirements.in
opentelemetry-instrumentation-system-metrics==0.62b1 \ opentelemetry-instrumentation-system-metrics==0.63b1 \
--hash=sha256:260130cb63e3028290305d8f25b744fb3a279677769dd65287ce60086cc11d32 \ --hash=sha256:995051f47876d79461aed8b7aa205d4584d90794ef864342cc748929c389bb42 \
--hash=sha256:b8097e0d3747102fa545723bb67f50f572a7618b6479359fa6533007d2f621aa --hash=sha256:d6d4d7a1a854be4165143cf6420ee5894188762eb367d7bf9da5be4a83a4b632
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -r src/backend/requirements.in
opentelemetry-instrumentation-wsgi==0.62b1 \ opentelemetry-instrumentation-wsgi==0.63b1 \
--hash=sha256:02a364fd9c940a46b19c825c5bfe386b007d5292ef91573894164836953fe831 \ --hash=sha256:03d61c4678ce82402e7f37b6a3dbd84cb97b85b3cb416a78c2e74c7c6d9451fa \
--hash=sha256:a2df11de0113f504043e2b0fa0288238a93ee49ff607bd5100cb2d3a75bc771f --hash=sha256:86779715262227d3436bdfb16aabd1c524b0f236725a69e8754ceda76c6d79dc
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -r src/backend/requirements.in
# opentelemetry-instrumentation-django # opentelemetry-instrumentation-django
opentelemetry-proto==1.41.1 \ opentelemetry-proto==1.42.1 \
--hash=sha256:0496713b804d127a4147e32849fbaf5683fac8ee98550e8e7679cd706c289720 \ --hash=sha256:c6a51e6b4f05ae63565f3a113217f3d2bfaec68f78c02d7a6c85f9010d1cfca6 \
--hash=sha256:4b9d2eb631237ea43b80e16c073af438554e32bc7e9e3f8ca4a9582f900020e5 --hash=sha256:dedb74cba2886c59c7789b227a7a670613025a07489040050aedff6e5c0fb43c
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# opentelemetry-exporter-otlp-proto-common # opentelemetry-exporter-otlp-proto-common
# opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http # opentelemetry-exporter-otlp-proto-http
opentelemetry-sdk==1.41.1 \ opentelemetry-sdk==1.42.1 \
--hash=sha256:724b615e1215b5aeacda0abb8a6a8922c9a1853068948bd0bd225a56d0c792e6 \ --hash=sha256:083cd4bbfaa5aa7b5a9e552430d9951219967cfb27aa61feb13a77aba1fc839d \
--hash=sha256:edee379c126c1bce952b0c812b48fe8ff35b30df0eecf17e98afa4d598b7d85d --hash=sha256:8c834e8f8c9ba4171d4ec843d0cb8a67e4c7394d3f9e9297e582cbd9456ddbf7
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -r src/backend/requirements.in
# opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http # opentelemetry-exporter-otlp-proto-http
opentelemetry-semantic-conventions==0.62b1 \ opentelemetry-semantic-conventions==0.63b1 \
--hash=sha256:c5cc6e04a7f8c7cdd30be2ed81499fa4e75bfbd52c9cb70d40af1f9cd3619802 \ --hash=sha256:3daf963611334b365e98a57438183eb012d3bfb40b2d931a9af613476b8701a9 \
--hash=sha256:cf506938103d331fbb78eded0d9788095f7fd59016f2bda813c3324e5a74a93c --hash=sha256:dfe5ef4dee82586b746f522b818ceb298d00b3d59f660042bd79404bff8d0682
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# opentelemetry-instrumentation # opentelemetry-instrumentation
@@ -1463,11 +1463,12 @@ opentelemetry-semantic-conventions==0.62b1 \
# opentelemetry-instrumentation-django # opentelemetry-instrumentation-django
# opentelemetry-instrumentation-redis # opentelemetry-instrumentation-redis
# opentelemetry-instrumentation-requests # opentelemetry-instrumentation-requests
# opentelemetry-instrumentation-system-metrics
# opentelemetry-instrumentation-wsgi # opentelemetry-instrumentation-wsgi
# opentelemetry-sdk # opentelemetry-sdk
opentelemetry-util-http==0.62b1 \ opentelemetry-util-http==0.63b1 \
--hash=sha256:adf6facbb89aef8f8bc566e2f04624942ba08a7b678b3479a91051a8f4dc70a3 \ --hash=sha256:6284194028c59cd439f8acfe388145069a6127f11dc077e1344a2094adacc3f8 \
--hash=sha256:c57e8a6c19fc422c288e6074e882f506f85030b69b7376182f74f9257b9261f0 --hash=sha256:ba1268f00922ee522dba2ae38458060f99486e7385a8056985901ca9685adfff
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# opentelemetry-instrumentation-django # opentelemetry-instrumentation-django
@@ -1681,9 +1682,9 @@ pydyf==0.12.1 \
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# weasyprint # weasyprint
pyjwt[crypto]==2.12.1 \ pyjwt[crypto]==2.13.0 \
--hash=sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c \ --hash=sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423 \
--hash=sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b --hash=sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# django-allauth # django-allauth
@@ -1717,9 +1718,9 @@ pynacl==1.6.2 \
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# paramiko # paramiko
pypdf==6.11.0 \ pypdf==6.12.1 \
--hash=sha256:062b51c81b0910e6d2755e99e1c5547a0a23b7d0a32322af66240d8edcfabe87 \ --hash=sha256:3953a097b9f26d4e0ead5ff95943d9971377557662a91d8872186053cd71d30a \
--hash=sha256:769394d5756d5b304c9b6bef88b54b1816b328e7e6fc9254e625529a15ed4ab8 --hash=sha256:8fa2a2321cf16247ed848bd7c97f193a60c08670d04abed5b0138327e51c43b0
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -r src/backend/requirements.in
@@ -2383,12 +2384,6 @@ xmlsec==1.3.17 \
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -r src/backend/requirements.in
# python3-saml # python3-saml
zipp==3.23.1 \
--hash=sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc \
--hash=sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110
# via
# -c src/backend/requirements.txt
# importlib-metadata
zopfli==0.4.1 \ zopfli==0.4.1 \
--hash=sha256:02086247dd12fda929f9bfe8b3962b6bcdbfc8c82e99255aebcf367867cf0760 \ --hash=sha256:02086247dd12fda929f9bfe8b3962b6bcdbfc8c82e99255aebcf367867cf0760 \
--hash=sha256:07a5cdc5d1aaa6c288c5d9f5a5383042ba743641abf8e2fd898dcad622d8a38e \ --hash=sha256:07a5cdc5d1aaa6c288c5d9f5a5383042ba743641abf8e2fd898dcad622d8a38e \
+63 -63
View File
@@ -11,9 +11,9 @@ build==1.5.0 \
--hash=sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f \ --hash=sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f \
--hash=sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647 --hash=sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647
# via pip-tools # via pip-tools
certifi==2026.4.22 \ certifi==2026.5.20 \
--hash=sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a \ --hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \
--hash=sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580 --hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d
# via # via
# -c src/backend/requirements-3.14.txt # -c src/backend/requirements-3.14.txt
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
@@ -242,9 +242,9 @@ charset-normalizer==3.4.7 \
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# pdfminer-six # pdfminer-six
# requests # requests
click==8.3.3 \ click==8.4.1 \
--hash=sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2 \ --hash=sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2 \
--hash=sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613 --hash=sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96
# via pip-tools # via pip-tools
coverage[toml]==7.14.0 \ coverage[toml]==7.14.0 \
--hash=sha256:057a6af2f160a85384cde4ab36f0d2777bae1057bae255f95413cdd382aa5c74 \ --hash=sha256:057a6af2f160a85384cde4ab36f0d2777bae1057bae255f95413cdd382aa5c74 \
@@ -448,9 +448,9 @@ gprof2dot==2025.4.14 \
--hash=sha256:0742e4c0b4409a5e8777e739388a11e1ed3750be86895655312ea7c20bd0090e \ --hash=sha256:0742e4c0b4409a5e8777e739388a11e1ed3750be86895655312ea7c20bd0090e \
--hash=sha256:35743e2d2ca027bf48fa7cba37021aaf4a27beeae1ae8e05a50b55f1f921a6ce --hash=sha256:35743e2d2ca027bf48fa7cba37021aaf4a27beeae1ae8e05a50b55f1f921a6ce
# via django-silk # via django-silk
idna==3.15 \ idna==3.16 \
--hash=sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8 \ --hash=sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5 \
--hash=sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc --hash=sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d
# via # via
# -c src/backend/requirements-3.14.txt # -c src/backend/requirements-3.14.txt
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
@@ -496,24 +496,24 @@ pluggy==1.6.0 \
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \
--hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
# via pytest # via pytest
prek==0.4.0 \ prek==0.4.1 \
--hash=sha256:01d69684306c67917ed69497d0f523216f13f2306723b62e1ace454209477b45 \ --hash=sha256:051c442b570b53756225410240577bee1aeace6be52955dfacf45a9783223b36 \
--hash=sha256:0d19efff4537e4a68b5bd61dcbf5ccb2dd8343df3ea9482aabe37d241ff18ddf \ --hash=sha256:10e7e78ffe65dfba7d687a8c71b2f473554d1ba60f43c742105da4c0030feed9 \
--hash=sha256:0d9483404e5b8bf65111ee6b821e6cf7a5ba9c3a2065e7a0b7a39a17daaffcc9 \ --hash=sha256:17ffa9d8dd40791b9b99cafe558c5cc28e78e5be57607b280b15f0dab90264e9 \
--hash=sha256:44e3716a3e2983add2c094775d564536342011e84f770132389819c965513d6d \ --hash=sha256:2ab3460641762edf128b1ec8e833ce7e9ae015d1268a894560cb90d3393a7527 \
--hash=sha256:473ce0262e36b8e77b327941ad6d5747ac451b4c441cdc86dab95640b68fff72 \ --hash=sha256:2d1805123ab5d730629de588bf319ea39e7078b589b3288c95740f1b4780a1d4 \
--hash=sha256:47f42477c8453c7440e4e656e5ab0c2a1e4c25daa5ed441a9ac1a2b7634abc12 \ --hash=sha256:5b4a348537924b20e208cbd87ef58e96ec37d691c5bec2969209c40de0ecf72e \
--hash=sha256:6e3a0e43d7f345a5e32962736c335b5fc466d499f2556fb6f24028dd08108618 \ --hash=sha256:60928d1dad45ff3e491d3083a50643cc213aa2d54f1dbd8d702d7193773c020e \
--hash=sha256:7203bff21e622a5482e956384c43990b0092faab02f72118275839a77a45e939 \ --hash=sha256:622a8812bda87cf4ddcae2dab5ccecc55b88d70c677129dbe25e975d923179f0 \
--hash=sha256:944670565dfb3800465355f299effa31572822566d10c6210e6f76bf399ddf53 \ --hash=sha256:6e69a9c02ead38706a5d2a4ae209dccba08ccb5d0026e1d08e723c66ab964750 \
--hash=sha256:a8272f32e698eae514556086ad73d02026ec00bbd4d26c420f761ea857cfd795 \ --hash=sha256:76663998827a2cbc94f5e209319809655489b5bd1f8e70568a623372e80253f0 \
--hash=sha256:a860fda0f27f872622689358a583e5f2a5771241331848233274f4cfeb8ae9bb \ --hash=sha256:7c515492ef3585e6bcd7b83f1bb1cb131038abc88ed2c843de1e4c3ceb865b19 \
--hash=sha256:b218d92ad5d2ff0b59240d7d837fa9edc61849894958acb3a225efff7a2faa65 \ --hash=sha256:8fa707971465d8ad021c907e43691aad7bb98942943e61e294ece5f95d9fbc78 \
--hash=sha256:b60bd4fdc323896a5bada2708734d16ff59ee5e32b8c390ae2ebe328426964a3 \ --hash=sha256:b25807e0aa57d2118747e127b58e7a1bf41d5d7b3323f5f3f1f3cb10031245cc \
--hash=sha256:ce2a8feb5dc1f1e748879d0e14c2145353059b830ac5e3f64d92127ab16efc78 \ --hash=sha256:c0877e82c52359d655fe1072b3a5228639184d1d5f03c6803b6530cd6da1ef20 \
--hash=sha256:e46277b577991ccdc7b13e2cfa7d260ae13e77f5af543e50e188799f649f0ad8 \ --hash=sha256:cdf4503a240369f66321213d9c4bc6f925014b64ff7121de9e9920c9b9838ce2 \
--hash=sha256:f4cba2132e038349b4b0a00a73b300e4192bae9f78fff8df0365c00bd19140e7 \ --hash=sha256:dc744fedf98df8a00a9e3bcd629b163fee5e9f9e22bce66029d9945241586165 \
--hash=sha256:ff1f1fa311023418d40a443bca6928a9f5ce073d0b9130b641954183aa31736d --hash=sha256:efa95331c4c171a867c0064c19d8a4abc94a1c1c920c8b8092f2d7d87f4b90a8
# via -r src/backend/requirements-dev.in # via -r src/backend/requirements-dev.in
pycparser==3.0 \ pycparser==3.0 \
--hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \ --hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \
@@ -540,36 +540,36 @@ pytest==9.0.3 \
# via # via
# pytest-codspeed # pytest-codspeed
# pytest-django # pytest-django
pytest-codspeed==5.0.2 \ pytest-codspeed==5.0.3 \
--hash=sha256:0621a458c52e77aa113c8d6e14037b90ce3cb5a8dd10a7656b71641999baef8c \ --hash=sha256:005348ea52ace3ede2e2f595913912ad2564cca7b124211a88dc78a9cb1fca63 \
--hash=sha256:0fd7db3e6fb6bd28abbf0059dd54ee6233f5faf5c08597b1e9624821417e8d99 \ --hash=sha256:0c383c9121deb58a69f174188e9e4488ffc0daced0ed276abf87747182511901 \
--hash=sha256:1b87b6a5e3c0e05ea043790aae08791dd6b3e7f487b18ec1bce145a60c78a130 \ --hash=sha256:20eba63765be9d1b6cacbbfad84b87d49eb04b357a7045a0899880da181f81e3 \
--hash=sha256:1ee33ac4c3bd7317b6956c0b6cb250f759e02072bb14fd0324de0df71d5d488f \ --hash=sha256:25464363c7f9b9bd5022e969c0addba616fa40ac9b8f0fc9e030c4538863b32d \
--hash=sha256:22332fefae895fc80a36ac8a6d5b314663efcad9e833aed8452388441b95c50f \ --hash=sha256:2eeb25fb1ac3f73c4de50e739e78fea396b89782bdb740bf2a7cd2df21f8d4ee \
--hash=sha256:33245c1fd96b1a4299604f6791e7fded376605c140ad778db7032dcd46a74d1c \ --hash=sha256:3a2097247985f5d915a94b80c5552d10979ca858c859fc3edef1bf2baa5c9b7a \
--hash=sha256:3658d3b42a15c6f40fa385629a8a8655dbedadd5d7bb5a01bc342b47f73da252 \ --hash=sha256:4c682f6645d4eb472f3bd95dbda1805e3af4243610572cb7d6bf94a88e8a0b6c \
--hash=sha256:3799ca9e54d6958d1b388371d00f928fcc4e1e68427d312348dd413a1bba5e0b \ --hash=sha256:51e192905a2230f9956e6160732f76577836953a4a1fb2b1e7be74e51ac7b2a0 \
--hash=sha256:3a5ce30d2bcfbeb329b61f3435369720ed122caa1dd898464acbcd7edc63cf04 \ --hash=sha256:6524c57fec279a22ffef6112af404036afc71b4704758ae9f0abda429b8478d4 \
--hash=sha256:3d15eaa6ca380d0d7cb5b7b8692f362a8aac3832dff6867a0c7068fb8c7a4ef1 \ --hash=sha256:73c5c9d98a3372a42611989ccfa437cce3842431ac6d6b9ab42c4f0e59c070f7 \
--hash=sha256:40b12cbf88eb69583d7063a4f5c986a7eed14f750a49764ef39a565ffa33d540 \ --hash=sha256:782f9985b6f6b45b8bc20152d206d3a52b56dd088ba81cb70a71f0b39841be9e \
--hash=sha256:439cd9d87ad449b7db327724b8fdc4a1ae79090b166b77c4e5e15102a371f6c7 \ --hash=sha256:7ac4344f34bbcdd17f6f8c30dbac3da2f80d223dd112e568fd7f7c2cd4cbc693 \
--hash=sha256:53473907ee2a7569b5ce6ffbfd2ba1793d284a37ff5c8670ed3149133c3ed37b \ --hash=sha256:85505c96a3477c346ec2d2b7dced8478f4c651e2b1666ee102d53a832b511853 \
--hash=sha256:687e5aa0fd101adbfe98f36dc253cd4e3b77d90ad96260e6e7e78bde4319c357 \ --hash=sha256:8df77b3409f54f4a268f77f3ff74992fe1d995cdbaf2cecf8ad74d32db217ce7 \
--hash=sha256:793d423dc76fd52b67495318681be18c541a7cfe30432ab2f272cd393422c56b \ --hash=sha256:91afef90e6a96b013495e4702ef5d6358614a449e71008cdc194ef668778b92f \
--hash=sha256:7b033d25f40c47733234f29c10629f14d004540c743a5c30718e2aa768d7cbb3 \ --hash=sha256:9aa0815b90196f3c20d736ea8691381e97f12bbe8c7d87af10a351e434b452cb \
--hash=sha256:82d3c9db57ccaef5177e1096b4dbbf8f3fde8d25c568e38d31a259474c94e5b4 \ --hash=sha256:a2e0ab65df73e837666d12357280ca50ff6d6ac03ea5266703be518b68170edf \
--hash=sha256:93fea30b2d7266343dd505a182bdf1eb47f96f5fa2929f1d9aff01d3b60e1589 \ --hash=sha256:a4bcdb4b6522738152885ef067e0c8524d5699828d780fb6f464cdb3db44369c \
--hash=sha256:a14a6515cd315745b4b5b4739a72b287782c00a35f2927e55c310499b79d6bc2 \ --hash=sha256:a5d8695a227ea1c3a41d25db5b3fe720bf1b4808bd38862be811a4efd902c792 \
--hash=sha256:a88fcddd08bdb1afe043ac4f992e032baee92c88990a611111e0c00d77927cfe \ --hash=sha256:abe793da40f87295d33988673d34f06ea569848b44490b847552cd416816258a \
--hash=sha256:b42d2aae3ac94192b8843fa7578eae584223bcb6334c50ca9f0e9ebafd40053b \ --hash=sha256:bce0a6ea93a5b19658f713312bb67554c19283ab15b454a1e3e55a13e78130f8 \
--hash=sha256:c20756925af58ad9d5b584d66a9b8dc709f9b243e6d8fd377e2a1b5a99bf9229 \ --hash=sha256:bf4cc4178cbace8f4d2bd240408276bc4da3850ac5fcb5fb5f8a74ab417615bb \
--hash=sha256:c2c09ec82a2def144816c6ffb311252c6ff0624189b3b5e674d889920b6d926c \ --hash=sha256:c3a9ed38dfa776443b86f4b49a982e8443d0953db4974bd2673d63cc904ae1ad \
--hash=sha256:cd07e12c38f6974c969e76d070aba448c92fad66601cda4fd289afa52c81ef13 \ --hash=sha256:dbe6a4a00b449b6ba2771f644cbc38bdf55acf5c812e60e5659110e19dd9f510 \
--hash=sha256:d223b0fe74625e633c86934a1da3ed1607f694fb3981a598bcfc02811e54808e \ --hash=sha256:ec9fa6f0af0a9feb0e0bd517fb59ef28f806fbd50c0c6900ac26cbb4d080eba5 \
--hash=sha256:d394d0d27ead72d0b00906e3832f4dcb9aadb81887a4f379c534c32c0ab965b7 \ --hash=sha256:efd43f82ea03ced8488a767ded9473f050791ab7783ea8654107e1e0ac66af40 \
--hash=sha256:d6d24532a8fee7018b9a33df51e1a14e27ae6b2b0772e6ad477ce5c561ab06a5 \ --hash=sha256:f56d0339cd98d26f6e561987be25bdd2761a5d53d8f73493b1ebe02d0d451093 \
--hash=sha256:dedc9e4542832a3487aedf0b448217f34fdc794676b9e0daeaf408a343322c2b \ --hash=sha256:f852bee785a7a124cb1720b1915670c6742af87747dc4d838f3ffdbd365ce9d9 \
--hash=sha256:fbd1e86900e7ebbbf3cdf5a48124412d2b75283ab1378994ac27ba3308e262fc --hash=sha256:fe2ea83c924c2250675b75686c3ee456b8cf0208d83d552e182a195fdf467378
# via -r src/backend/requirements-dev.in # via -r src/backend/requirements-dev.in
pytest-django==4.12.0 \ pytest-django==4.12.0 \
--hash=sha256:3ff300c49f8350ba2953b90297d23bf5f589db69545f56f1ec5f8cff5da83e85 \ --hash=sha256:3ff300c49f8350ba2953b90297d23bf5f589db69545f56f1ec5f8cff5da83e85 \
@@ -626,13 +626,13 @@ ty==0.0.1a21 \
--hash=sha256:e941e9a9d1e54b03eeaf9c3197c26a19cf76009fd5e41e16e5657c1c827bd6d3 \ --hash=sha256:e941e9a9d1e54b03eeaf9c3197c26a19cf76009fd5e41e16e5657c1c827bd6d3 \
--hash=sha256:ecf41706b803827b0de8717f32a434dad1e67be9f4b8caf403e12013179ea06a --hash=sha256:ecf41706b803827b0de8717f32a434dad1e67be9f4b8caf403e12013179ea06a
# via -r src/backend/requirements-dev.in # via -r src/backend/requirements-dev.in
types-psycopg2==2.9.21.20260509 \ types-psycopg2==2.9.21.20260518 \
--hash=sha256:0422105f691a409e9d8048c2205aca9d694b70823248c6614393444017e9f088 \ --hash=sha256:2fd728a4fa3860db0a4a9813e5f49c30ed329b3d2e60e73530d9db01b2b98420 \
--hash=sha256:69f6dae384bbea830dff23621936423035db152af901331e6f9c46f7c4f4b24f --hash=sha256:8b1f80d90d6799a4fcdac12198b382a8feee9ed4340d5f69b56fc5ffa0644143
# via django-types # via django-types
types-pyyaml==6.0.12.20260510 \ types-pyyaml==6.0.12.20260518 \
--hash=sha256:09c1f1cb65a6eebea1e2e51ccf4918b8288e152909609a35cdb0d805efd125ad \ --hash=sha256:d2150f75a231c9fe9c7463bd29487d93e60bac90400287351384bc2284eba7cd \
--hash=sha256:3492eb9ba4d9d833473214c4d5736cccf5f37d93f5854059721e1c84f785309d --hash=sha256:d917f83fb38462550338c1297faedd860b3ec83912b96b1e3d73255f7473e466
# via django-stubs # via django-stubs
typing-extensions==4.15.0 \ typing-extensions==4.15.0 \
--hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
+63 -51
View File
@@ -10,9 +10,9 @@ build==1.5.0 \
--hash=sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f \ --hash=sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f \
--hash=sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647 --hash=sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647
# via pip-tools # via pip-tools
certifi==2026.4.22 \ certifi==2026.5.20 \
--hash=sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a \ --hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \
--hash=sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580 --hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# requests # requests
@@ -104,7 +104,6 @@ cffi==2.0.0 \
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# cryptography # cryptography
# pytest-codspeed
charset-normalizer==3.4.7 \ charset-normalizer==3.4.7 \
--hash=sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc \ --hash=sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc \
--hash=sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c \ --hash=sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c \
@@ -239,9 +238,9 @@ charset-normalizer==3.4.7 \
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# pdfminer-six # pdfminer-six
# requests # requests
click==8.3.3 \ click==8.4.1 \
--hash=sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2 \ --hash=sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2 \
--hash=sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613 --hash=sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96
# via pip-tools # via pip-tools
coverage[toml]==7.13.5 \ coverage[toml]==7.13.5 \
--hash=sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256 \ --hash=sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256 \
@@ -443,9 +442,9 @@ gprof2dot==2025.4.14 \
--hash=sha256:0742e4c0b4409a5e8777e739388a11e1ed3750be86895655312ea7c20bd0090e \ --hash=sha256:0742e4c0b4409a5e8777e739388a11e1ed3750be86895655312ea7c20bd0090e \
--hash=sha256:35743e2d2ca027bf48fa7cba37021aaf4a27beeae1ae8e05a50b55f1f921a6ce --hash=sha256:35743e2d2ca027bf48fa7cba37021aaf4a27beeae1ae8e05a50b55f1f921a6ce
# via django-silk # via django-silk
idna==3.15 \ idna==3.16 \
--hash=sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8 \ --hash=sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5 \
--hash=sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc --hash=sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# requests # requests
@@ -489,24 +488,24 @@ pluggy==1.6.0 \
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \
--hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
# via pytest # via pytest
prek==0.3.13 \ prek==0.4.1 \
--hash=sha256:0d89ac712c60e34d1550a606ad5fdfb8ad71d44ced8afa2fa5cbc106be4abd9e \ --hash=sha256:051c442b570b53756225410240577bee1aeace6be52955dfacf45a9783223b36 \
--hash=sha256:326fac2bdce00074ce6c5046b861d310638aee2b9de1ed241ba7eb32bdc83898 \ --hash=sha256:10e7e78ffe65dfba7d687a8c71b2f473554d1ba60f43c742105da4c0030feed9 \
--hash=sha256:64b59a1550780af2bba37297c704b17f81d8e9df6288af1fab4017938e33b1db \ --hash=sha256:17ffa9d8dd40791b9b99cafe558c5cc28e78e5be57607b280b15f0dab90264e9 \
--hash=sha256:7ca8c526a23873177fb3b92013500b08ef5f8bedc7263f9f3a44dd2f49645a26 \ --hash=sha256:2ab3460641762edf128b1ec8e833ce7e9ae015d1268a894560cb90d3393a7527 \
--hash=sha256:841049f89f5ec9f4035299283d11e566ac5a068e3742ead1055ea04f886831fc \ --hash=sha256:2d1805123ab5d730629de588bf319ea39e7078b589b3288c95740f1b4780a1d4 \
--hash=sha256:9ce6cd8f114ba9bbdbe97422103fd886101949b1c42e588a7543c4436ead2020 \ --hash=sha256:5b4a348537924b20e208cbd87ef58e96ec37d691c5bec2969209c40de0ecf72e \
--hash=sha256:a5bbb175478438a871e3281d2c3c3f067288af73ad81707a9bdebfd769766c7d \ --hash=sha256:60928d1dad45ff3e491d3083a50643cc213aa2d54f1dbd8d702d7193773c020e \
--hash=sha256:a65327a014d838341af757dfc05a706d10e8e33f039bc32bb3dbe2fa21c440c0 \ --hash=sha256:622a8812bda87cf4ddcae2dab5ccecc55b88d70c677129dbe25e975d923179f0 \
--hash=sha256:a9fd74e0aec550c6b8d41076fdcdd6ff121cd7d94d743c1338bd794784e3c775 \ --hash=sha256:6e69a9c02ead38706a5d2a4ae209dccba08ccb5d0026e1d08e723c66ab964750 \
--hash=sha256:b00d38f01235073c35aa5f48df57fefef45a6cec2ae0884d750345a2c7220370 \ --hash=sha256:76663998827a2cbc94f5e209319809655489b5bd1f8e70568a623372e80253f0 \
--hash=sha256:b6a200843a36a5b0c41764ce7639ccb3471d48b097f1c5e3fc8f034219b42626 \ --hash=sha256:7c515492ef3585e6bcd7b83f1bb1cb131038abc88ed2c843de1e4c3ceb865b19 \
--hash=sha256:bdacaad8f35f343e063d251211fe34db1de9e5cc591795361ad69a6485202258 \ --hash=sha256:8fa707971465d8ad021c907e43691aad7bb98942943e61e294ece5f95d9fbc78 \
--hash=sha256:c48586ee3708bfbf3df80121f55583e9a7d0fa166b08172c091fe5971e92a0ac \ --hash=sha256:b25807e0aa57d2118747e127b58e7a1bf41d5d7b3323f5f3f1f3cb10031245cc \
--hash=sha256:cc03e924a24d8d961f56195853c8b206cb196be6db4ad8312125dae847d718ac \ --hash=sha256:c0877e82c52359d655fe1072b3a5228639184d1d5f03c6803b6530cd6da1ef20 \
--hash=sha256:e5530a867bcf5b172b7513a64e71b06a337d1d184696227ae953845867376b8d \ --hash=sha256:cdf4503a240369f66321213d9c4bc6f925014b64ff7121de9e9920c9b9838ce2 \
--hash=sha256:f00328f1c520d8fefb910ab0d3c6764ee330d227952baa19b7e3de7242bd8b3b \ --hash=sha256:dc744fedf98df8a00a9e3bcd629b163fee5e9f9e22bce66029d9945241586165 \
--hash=sha256:f9b5265863d18b5be4ea094fdce4fd6ca61a8c89a70ee3d8ee153b3e0ed6b272 --hash=sha256:efa95331c4c171a867c0064c19d8a4abc94a1c1c920c8b8092f2d7d87f4b90a8
# via -r src/backend/requirements-dev.in # via -r src/backend/requirements-dev.in
pycparser==3.0 \ pycparser==3.0 \
--hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \ --hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \
@@ -532,23 +531,36 @@ pytest==9.0.3 \
# via # via
# pytest-codspeed # pytest-codspeed
# pytest-django # pytest-django
pytest-codspeed==4.4.0 \ pytest-codspeed==5.0.3 \
--hash=sha256:06943110e7a8a4b54f4b13aaa3ff8db39caa02b2f61705916887649e36b9713a \ --hash=sha256:005348ea52ace3ede2e2f595913912ad2564cca7b124211a88dc78a9cb1fca63 \
--hash=sha256:215170441e57bfcbefd179dfd86ccd54ed0ee235e0602a068ce4448b35f13cb2 \ --hash=sha256:0c383c9121deb58a69f174188e9e4488ffc0daced0ed276abf87747182511901 \
--hash=sha256:29b1bf8a36e18d11641a5e610e23a94036b04185e3099978d81a873a5bd3635c \ --hash=sha256:20eba63765be9d1b6cacbbfad84b87d49eb04b357a7045a0899880da181f81e3 \
--hash=sha256:33c38e0e797c74506004f231fc53eab0e412987de281755f714018334381aa3a \ --hash=sha256:25464363c7f9b9bd5022e969c0addba616fa40ac9b8f0fc9e030c4538863b32d \
--hash=sha256:340dbb1cc5a21434e0e29bd68ab03c7dc7ad9bfde09d1980b7161352c4c2f048 \ --hash=sha256:2eeb25fb1ac3f73c4de50e739e78fea396b89782bdb740bf2a7cd2df21f8d4ee \
--hash=sha256:413666266762f9cef1321ba971a9e127b97a1f1dad40ddfd2184c2bc5ac157f9 \ --hash=sha256:3a2097247985f5d915a94b80c5552d10979ca858c859fc3edef1bf2baa5c9b7a \
--hash=sha256:4afa9455a9b198c5e898224c751182fcf53f67f11fb27c2c3346284da1baa018 \ --hash=sha256:4c682f6645d4eb472f3bd95dbda1805e3af4243610572cb7d6bf94a88e8a0b6c \
--hash=sha256:56d5dd94dcb69460f916acb9c69865d0171b98acec3ce256645d0c0275b553d7 \ --hash=sha256:51e192905a2230f9956e6160732f76577836953a4a1fb2b1e7be74e51ac7b2a0 \
--hash=sha256:6a5c1d51e7ca72ffe247c99b9a97a54191185e8f7a27528e2200d7416da2a68b \ --hash=sha256:6524c57fec279a22ffef6112af404036afc71b4704758ae9f0abda429b8478d4 \
--hash=sha256:83479a6719598d2910969a60cc410c7283c262c876422a9157dca2f2ab42fa1d \ --hash=sha256:73c5c9d98a3372a42611989ccfa437cce3842431ac6d6b9ab42c4f0e59c070f7 \
--hash=sha256:98524af4ddd6006ea064791bb15a43957d78fab040cb6f499ca73a369da373e6 \ --hash=sha256:782f9985b6f6b45b8bc20152d206d3a52b56dd088ba81cb70a71f0b39841be9e \
--hash=sha256:a6aab2fa73523f538e7729c20ccf4a1e8e921324c9877a816b05334135950fd9 \ --hash=sha256:7ac4344f34bbcdd17f6f8c30dbac3da2f80d223dd112e568fd7f7c2cd4cbc693 \
--hash=sha256:e258e6c3d5a8a02ae02a64831be3acd44c19210ffbf13321bdbb8c111c5c6fe4 \ --hash=sha256:85505c96a3477c346ec2d2b7dced8478f4c651e2b1666ee102d53a832b511853 \
--hash=sha256:edb7c101d9c50439a42cf02cfa9c0ac92da618841636bbebf87c3fa54669442a \ --hash=sha256:8df77b3409f54f4a268f77f3ff74992fe1d995cdbaf2cecf8ad74d32db217ce7 \
--hash=sha256:ee3e1964446011ca192eebf0350227df231a5b88af57e518f2a4328fc8ca5131 \ --hash=sha256:91afef90e6a96b013495e4702ef5d6358614a449e71008cdc194ef668778b92f \
--hash=sha256:f3ae6f4053042c3a9ae3b05416fb42253c5e514e89391eb25e9c9e3ac8de8677 --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 # via -r src/backend/requirements-dev.in
pytest-django==4.12.0 \ pytest-django==4.12.0 \
--hash=sha256:3ff300c49f8350ba2953b90297d23bf5f589db69545f56f1ec5f8cff5da83e85 \ --hash=sha256:3ff300c49f8350ba2953b90297d23bf5f589db69545f56f1ec5f8cff5da83e85 \
@@ -651,13 +663,13 @@ ty==0.0.1a21 \
--hash=sha256:e941e9a9d1e54b03eeaf9c3197c26a19cf76009fd5e41e16e5657c1c827bd6d3 \ --hash=sha256:e941e9a9d1e54b03eeaf9c3197c26a19cf76009fd5e41e16e5657c1c827bd6d3 \
--hash=sha256:ecf41706b803827b0de8717f32a434dad1e67be9f4b8caf403e12013179ea06a --hash=sha256:ecf41706b803827b0de8717f32a434dad1e67be9f4b8caf403e12013179ea06a
# via -r src/backend/requirements-dev.in # via -r src/backend/requirements-dev.in
types-psycopg2==2.9.21.20260422 \ types-psycopg2==2.9.21.20260518 \
--hash=sha256:ad7574fa8e25d9aa96ab96cd280c4dee20872725cd1fe6a6d3facc354f2644d4 \ --hash=sha256:2fd728a4fa3860db0a4a9813e5f49c30ed329b3d2e60e73530d9db01b2b98420 \
--hash=sha256:e240684ac37946c5a2a058b04ea1f2fd0e4ee2655719b8c3ec9abf37f96da5ba --hash=sha256:8b1f80d90d6799a4fcdac12198b382a8feee9ed4340d5f69b56fc5ffa0644143
# via django-types # via django-types
types-pyyaml==6.0.12.20260408 \ types-pyyaml==6.0.12.20260518 \
--hash=sha256:92a73f2b8d7f39ef392a38131f76b970f8c66e4c42b3125ae872b7c93b556307 \ --hash=sha256:d2150f75a231c9fe9c7463bd29487d93e60bac90400287351384bc2284eba7cd \
--hash=sha256:fbc42037d12159d9c801ebfcc79ebd28335a7c13b08a4cfbc6916df78fee9384 --hash=sha256:d917f83fb38462550338c1297faedd860b3ec83912b96b1e3d73255f7473e466
# via django-stubs # via django-stubs
typing-extensions==4.15.0 \ typing-extensions==4.15.0 \
--hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
+264 -267
View File
@@ -91,19 +91,19 @@ bleach==4.1.0 \
--hash=sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da \ --hash=sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da \
--hash=sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994 --hash=sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994
# via django-markdownify # via django-markdownify
blessed==1.39.0 \ blessed==1.42.0 \
--hash=sha256:666e7e3fd0a4e38c3a262eaaf1e22a4ce2c81337aa17593c3f60ea136ec24fe1 \ --hash=sha256:34b460b77562ed21f807cfd7c527b983b0cc300c98810c8076f283b7bcd45ba7 \
--hash=sha256:b04fc7141a20a3b2ade6cad741051f1e3ac59cc1e7e90915ed1f9e521332bea4 --hash=sha256:f96c4a6dc664b48e0b832fa732acc16df67abd30f0ec35babf99025982f21852
# via -r src/backend/requirements.in # via -r src/backend/requirements.in
boto3==1.43.8 \ boto3==1.43.14 \
--hash=sha256:1894497c383e3cdf50e210f1f57a43e9f4047a5d3accc73ffdb7eacc3b0f011b \ --hash=sha256:574335744656cfed0b362a0a0467aaf2eb2bf15526edcd02d31d3c661f4b09e4 \
--hash=sha256:d1235602d715c727c1923ef4bcdb5612a20575a9a5e4f2db00d571e0ea1f85fc --hash=sha256:5c0a994b3182061ee101812e721100717a4d664f9f4ceaf4a86b6d032ce9fc2d
# via # via
# django-anymail # django-anymail
# django-storages # django-storages
botocore==1.43.8 \ botocore==1.43.14 \
--hash=sha256:611ad8b1f60661373cd39d9391ff16f1eaf8f5cb1d0a691563a4201d1a2603ce \ --hash=sha256:1f4a2a95ea78c10398e78431e98c1fe47adb54a7b10a32975144c1f541186658 \
--hash=sha256:6257d2655c3abe75eaa49e218b7d883cdc7cea64652b451e5feb08a6c169da3c --hash=sha256:b9e500737e43d2f147c9d4e23b54360335e77d4c0ba90a318f51b65e06cb8516
# via # via
# boto3 # boto3
# s3transfer # s3transfer
@@ -209,9 +209,9 @@ brotli==1.2.0 \
--hash=sha256:fc1530af5c3c275b8524f2e24841cbe2599d74462455e9bae5109e9ff42e9361 \ --hash=sha256:fc1530af5c3c275b8524f2e24841cbe2599d74462455e9bae5109e9ff42e9361 \
--hash=sha256:ff09cd8c5eec3b9d02d2408db41be150d8891c5566addce57513bf546e3d6c6d --hash=sha256:ff09cd8c5eec3b9d02d2408db41be150d8891c5566addce57513bf546e3d6c6d
# via fonttools # via fonttools
certifi==2026.4.22 \ certifi==2026.5.20 \
--hash=sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a \ --hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \
--hash=sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580 --hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d
# via # via
# requests # requests
# sentry-sdk # sentry-sdk
@@ -673,49 +673,49 @@ drf-spectacular==0.29.0 \
--hash=sha256:0a069339ea390ce7f14a75e8b5af4a0860a46e833fd4af027411a3e94fc1a0cc \ --hash=sha256:0a069339ea390ce7f14a75e8b5af4a0860a46e833fd4af027411a3e94fc1a0cc \
--hash=sha256:d1ee7c9535d89848affb4427347f7c4a22c5d22530b8842ef133d7b72e19b41a --hash=sha256:d1ee7c9535d89848affb4427347f7c4a22c5d22530b8842ef133d7b72e19b41a
# via -r src/backend/requirements.in # via -r src/backend/requirements.in
dulwich==1.2.1 \ dulwich==1.2.5 \
--hash=sha256:011d37afa0922b500c938d6417317e3e6d29d33a6fbaf12b3696fe2a216aa170 \ --hash=sha256:00b54a1d56ddbacdd8eadd6d4787a51b3a05fefa30eadbf9165fd283a00b90ed \
--hash=sha256:01f43a1b6953c93b4e957c0902f7de80267285e0c59c9b44f4801f7da9f11107 \ --hash=sha256:0395b2c8924c3424bafe2d9c1edd5348cc4b21ce9c1d6655bf01f9a5c47164c8 \
--hash=sha256:04215befa00c82accda97d0e3ce760d9344a67f081d5b92ade8ad00007fb38d8 \ --hash=sha256:07cb75b58216440e2c170fff4f3d55a5f387358d9489863af8cb11f24ee37121 \
--hash=sha256:09ca37d7a9086f96e535a6474644588e4502f69e2b202c39d92901e3606f6501 \ --hash=sha256:1679b376433a0fc7f36586afda1d4ed7427afa7a79d4bf17e5014474eea69fa4 \
--hash=sha256:0b1181f4ae225bbde373fc16279c4e61fdf04aedd30ec21fab387be642567453 \ --hash=sha256:1699a4cf8d44c174408325a9594a1498d05786cea34e3004c8732420ee1b8182 \
--hash=sha256:1073c75db1f5da845d219d1b5b601aeaaa7be5f999f3c77f4966f9ca2327bf44 \ --hash=sha256:1ba83ec3cfb4c506c277400357a51523c8258fa07b841ee06e8e1071da4cfed1 \
--hash=sha256:11a51dba8454b4c64bf242a918ca4c4300097f7cec84a13164845e638a26f9de \ --hash=sha256:1c151a7f3995ccf9d433a603b747e76141a7ebe7c385c8909e9f7e7a6422c28f \
--hash=sha256:121d2d181407cd7bb051922dc3bc00841ca0959b8299880529525db93dfbdca9 \ --hash=sha256:2f10dafa1ef5660b1331364bc8d68446448608a8d8f493ed0e260eaf5133e71c \
--hash=sha256:1961e0b6c0b1f2920f4ab05821652d8eb12f19ddb5a4c167c385902391c08dd3 \ --hash=sha256:2f90d68bfa97c4ca71de7507984365aefe27b6d248cb28dc99644d0f3ae8c60b \
--hash=sha256:21f48480bb1e6501bdec7f0877639cdfc64a16e0dd97c24947582fbeed3baf21 \ --hash=sha256:41ccffb0521f3f9ad73fac78772f321d731607336cee48911e7c26963459481c \
--hash=sha256:23560643c1cc85737c87985761666a59b35b06a1cbb06db5ba642fa35c67be93 \ --hash=sha256:46db47394ba8a95748ae739f5d3a5a3e1724a2f857bf2437bc71bfc0baaed91d \
--hash=sha256:3183b7db09282842459fbeb37e77e498c8339e6db4d0cb6b5d3685c97ce79dac \ --hash=sha256:5108acead814d1de8b6262d6d8fb90af7e82f5a4d83788b6b48e39d01800a92f \
--hash=sha256:3412eca24fd79d5413ac7003f8f29b2e01008d7c9d6fce159f77602f70058aea \ --hash=sha256:517fb7e20f91d2bd48dc5de9edc90ff8974a5512ce7f243284b191f8be6344c3 \
--hash=sha256:3a95dd2649ef3c6e59095d5bd3de08470b3ba908dc41343f5823666a10a326f8 \ --hash=sha256:53599909d54a2fae49fcd50047f1daf4b8b9eda6a5500a08b71da689f5431c24 \
--hash=sha256:4198f79ff04b07f6f6dc81047041f341b35e774cf0207f496b35f34dd2e1bb9c \ --hash=sha256:556593fd11637f80f6018bee1916b1a84f5b420423b470ebb3f1a782ad6ef081 \
--hash=sha256:41dfe1b1cb0f0202102e90f9f47f54458f27da6b746939a6ed85362b68e3e3a5 \ --hash=sha256:5e067b7feceb7034bc99e7c7143a704f1d97d4be7027d9a0aa5a83c0657ff091 \
--hash=sha256:4b6cd8104bf4adf2824221ba4f3b399c641a7a6a026dfa193c2f516531971315 \ --hash=sha256:66aded7d364341b55941973a1562323f25bd205f0809692b687ec36ccd31242c \
--hash=sha256:55c8415fb19068a5956e0d5bd83b0fb6e06b291d3b096d8fb6a604c105fc6ca0 \ --hash=sha256:6c683c0f4a062894b6826c61102d415dae86ade61a10003c82ccc2b91858d5fb \
--hash=sha256:5a0245dccd69fba9dfe2ed88eff9212bad3e04107bc41676fffbc7f706428cdb \ --hash=sha256:701a9ecf7a8a44f5e2459e46befa93530cf36a8b1ae3140aefc007db1d7d0207 \
--hash=sha256:5e875729df04991732959f52e29c1de96d00eaf62feddfc60f0b2b08c6e38870 \ --hash=sha256:77d2d2e43ad975459491de1ebf47990c74ff17f12586c8561e9890239bc422db \
--hash=sha256:6349b475a1b17b224191a4801687a2486574c9526e00835db2ba9ed081124b11 \ --hash=sha256:827366331603150de5976d72dd456a3fd5fc91e856471dc1d10fd64758c05f02 \
--hash=sha256:674b21a9025c00a00495fa03a9bb5f2da67a13ee513c74f16bd91c56678d0a77 \ --hash=sha256:8929134acf4ff967203df7600b38535f9b5b590462067a7e30dbce01acb97af9 \
--hash=sha256:74b01505b8e2ba81a715213101c790e0794f5c0b8021f8ce1100131a1e8cfa55 \ --hash=sha256:8e55f36a7f52ba0976dd72100273523908b16fb9dda6ce96d9aa9df9cceed4cc \
--hash=sha256:879abd15550b6abcebf97823905a167623f331eeca5e12dc0cac62bcd9e70bcd \ --hash=sha256:9008ef25cabd379cda4fa86000fc38ca14b72afe17db798a8c85c0b2b7ce4d1e \
--hash=sha256:96846ef600378739a64e347412c22d5f87f1b67d68526c633b465589335e9387 \ --hash=sha256:93d2d87acf75d60c5a2b8c5c8a45aff17bbbd00c17bdccb4ba013d3ab590a65f \
--hash=sha256:96c160d68173ae55f68bf734e7fddae04ea025f3541996b0b5f28dc2025b42dc \ --hash=sha256:9693d2c9e226b2ea855c1dc3a87e2f4d972f7523fc0f7924e5997e9f4c23d97f \
--hash=sha256:9d0ea88273a7ee6fd3b1d75e231cc6fc614774e19bdd7c1de5df274b4b492dde \ --hash=sha256:9b6d234f1f91335e9f01d9daac42ddc2d2e5c2fdbe285d8eeef50353b283648a \
--hash=sha256:9f7249c1090be7e7e841edda369d843617314092bb05f2a6a8b7f88660bbb2a6 \ --hash=sha256:9f3c98f5fa90a842c1f545463834f712aa2eed785fc3d5e42836c0df2d691bb6 \
--hash=sha256:a65527eda5f6a463168c6338d781b05d325ba85dc05c6f8217ddd8454911bb53 \ --hash=sha256:9fc113c1348c7eb22c4e8790f68b562bb4f42a721fafb813e89a57e9cd632040 \
--hash=sha256:ba43bfb3a7cad40d9607170561e8c3be42e7083b4b57af89a5f54e01577ff791 \ --hash=sha256:a5549f4afc973e0a15ea6b0244d57f848d3f3ee13dac557eb311024aebebf128 \
--hash=sha256:bad94416de9a76ad36dc19f0e65a829b89fc845282bdcc8c43285a4addc1ad2e \ --hash=sha256:a6620963196c49212c511cd909f367dacf771f199a27d116f357cc671ea956c7 \
--hash=sha256:cedc717cf9ee973df63293bcbaa51bb03ab6f87ff631d1dd329078b3f48712a7 \ --hash=sha256:a70477c991e96cfe8fdd7c866e7251faf71b38bfeb51d6f27554c9cce1caabf3 \
--hash=sha256:cf3aa983ca4907c96a0b98b357a3cac7381192786495522a65944da1b284bc02 \ --hash=sha256:ade416833214f3ee13af9b0199fff4de00fa6e0fde3deced776532fd91df5515 \
--hash=sha256:d0ddda4e165ea14c70e1d0531aa97e527332f1133ea0c65eebaa5afb8786029e \ --hash=sha256:c65230abaa52c72093b70d3b499d5689d1d8f9627e88ad3c3b4f8154e86ac0c8 \
--hash=sha256:da2fdcc9e4f178b2b4ec1194d8e17d111d572f111fdaa7c6c5a3f46bbe686eaf \ --hash=sha256:ccc58f26a1b94bef255316311678b03854f7192069bdf11cf501a6c85f61b83c \
--hash=sha256:e2f14d20aea48dd1d48714d637c70399b140825d930b4f5aa6fbb62199429740 \ --hash=sha256:d33bae2b3292ed0235522682316251658187f43b1ebad6cd2b127069b94afb3b \
--hash=sha256:e45a68da922a4abe8fd015ed020ec0123ee58176a6984a34d2a2c74c959e45d3 \ --hash=sha256:d46e35c473646efb3b2ff8032f37ac5b6d48da52a669577187d3796a6d5987a3 \
--hash=sha256:e88fd960a9327d87556a3bad76ad84b6346d3a07409fe8f081877a775977c86b \ --hash=sha256:d8f7ea8f47e38e5b0de3fab97e07e9c9161ffddc90b3964512cab2b7749df4e6 \
--hash=sha256:efd394fcb926ec8e9ed63fe0883784abf8ef7bb1d30a492e950ca8c2f369ce7b \ --hash=sha256:dd9569bc26174a3437d749114d36c81fc6c7478b55370ae50125e34e9629e4fe \
--hash=sha256:f944b742962d9933e60863f577491743328b287251ae57a1e7cd84c289acfc23 \ --hash=sha256:df4ac3746099562c8160d78d55bb2fa10c9ada7ef970af3e2536bd133cb7830e \
--hash=sha256:fb855620d04d0c286058db941c106cfc60bc1e4882ea20b6dd499f3668a1afbe \ --hash=sha256:e128cddeccae4146b556684a0d5426454fff5bfe7306862e5a8ce6b471568af4 \
--hash=sha256:fd774825510350b614121bfa84d95c6eb08f7e93c0fe7faf8760423a43dbad8b --hash=sha256:fa37da7ad16c47391016b5f984fb60e175e1ab0b478f04920fd6d1f61123ce4d
# via -r src/backend/requirements.in # via -r src/backend/requirements.in
et-xmlfile==2.0.0 \ et-xmlfile==2.0.0 \
--hash=sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa \ --hash=sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa \
@@ -864,20 +864,16 @@ gunicorn==26.0.0 \
--hash=sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc \ --hash=sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc \
--hash=sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf --hash=sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf
# via -r src/backend/requirements.in # via -r src/backend/requirements.in
icalendar==7.1.0 \ icalendar==7.1.2 \
--hash=sha256:10cd223c792fcc43bee4c3ebe3149d4cf32406c85cfef146624df5a0d414260f \ --hash=sha256:01c76243c76c549f58bb51510a8f0a4edb7c539726adda1356dfd0dc04fb7a53 \
--hash=sha256:6de875370d22fc4aff172ad7c439b39fb109dc2eab9ce358fcb95e8689ad7b56 --hash=sha256:ebc43ebeb357be98984b573d975118008dee3410d8df28b054ef2943cf3e367e
# via django-ical # via django-ical
idna==3.15 \ idna==3.16 \
--hash=sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8 \ --hash=sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5 \
--hash=sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc --hash=sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d
# via # via
# django-anymail # django-anymail
# requests # requests
importlib-metadata==8.7.1 \
--hash=sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb \
--hash=sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151
# via opentelemetry-api
inflection==0.5.1 \ inflection==0.5.1 \
--hash=sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417 \ --hash=sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417 \
--hash=sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2 --hash=sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2
@@ -894,6 +890,10 @@ jinja2==3.1.6 \
--hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
--hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
# via -r src/backend/requirements.in # via -r src/backend/requirements.in
jinxed==2.0.4 \
--hash=sha256:58ae0a4a2930b51e9de162208c356b382f5e25b3bc0ad73e2013b02a62d84e8b \
--hash=sha256:a92ac48923433c0a88577bb0479191788813fd5055ef17ff2b04a701a1b83f19
# via blessed
jmespath==1.1.0 \ jmespath==1.1.0 \
--hash=sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d \ --hash=sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d \
--hash=sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64 --hash=sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64
@@ -912,141 +912,141 @@ jwcrypto==1.5.7 \
--hash=sha256:70204d7cca406eda8c82352e3c41ba2d946610dafd19e54403f0a1f4f18633c6 \ --hash=sha256:70204d7cca406eda8c82352e3c41ba2d946610dafd19e54403f0a1f4f18633c6 \
--hash=sha256:729463fefe28b6de5cf1ebfda3e94f1a1b41d2799148ef98a01cb9678ebe2bb0 --hash=sha256:729463fefe28b6de5cf1ebfda3e94f1a1b41d2799148ef98a01cb9678ebe2bb0
# via django-oauth-toolkit # via django-oauth-toolkit
lxml==6.1.0 \ lxml==6.1.1 \
--hash=sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2 \ --hash=sha256:05a82eb6e1530a64f26225b55cbd178113bd0b5af1c2b625f25e5296742c26d2 \
--hash=sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773 \ --hash=sha256:07a4a68e286ee7a1ed7dfb8af83e615757c0ccfe9f18c6b4ea6771388d9ba8c9 \
--hash=sha256:045e387d1f4f42a418380930fa3f45c73c9b392faf67e495e58902e68e8f44a7 \ --hash=sha256:09dd5b7075dc2f7709654a46543ba1ea3c2e217b2ed8fbd413a8a945a0f40f60 \
--hash=sha256:05b9b8787e35bec69e68daf4952b2e6dfcfb0db7ecf1a06f8cdfbbac4eb71aad \ --hash=sha256:0b7e8a14c8634bf6f7a568634cb395305a6d964aeb5b7ee32248094bed3a7e2c \
--hash=sha256:07f98f5496f96bf724b1e3c933c107f0cbf2745db18c03d2e13a291c3afd2635 \ --hash=sha256:104c09bda8d2a562824c0e319d0768ce26a779b7601e0931d33b09b53c392ef7 \
--hash=sha256:08950a23f296b3f83521577274e3d3b0f3d739bf2e68d01a752e4288bc50d286 \ --hash=sha256:126c93f7f56f0eda92f6d8c619edc463a4f23d9252f1c9d0405a76f25fa9f11a \
--hash=sha256:0d082495c5fcf426e425a6e28daaba1fcb6d8f854a4ff01effb1f1f381203eb9 \ --hash=sha256:162af1091cd785f2f27e62d3547ae9bc58ec5c86dd314d67021fd02463708d83 \
--hash=sha256:0f0f08beb0182e3e9a86fae124b3c47a7b41b7b69b225e1377db983802404e54 \ --hash=sha256:17e0e18d4ad8adbd0399291bc44845b69d9dd68439a3cdebdf35ff902ec05072 \
--hash=sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c \ --hash=sha256:18b73c339ae29b90fd2d06e58ebd555a751bde9cd6bbd36cc0281b9a2c94e9d8 \
--hash=sha256:11a873c77a181b4fef9c2e357d08ed399542c2af1390101da66720a19c7c9618 \ --hash=sha256:19607c6bbff2a44cf3fe8250abccd20942d3462473e0a721d01d379ed017e462 \
--hash=sha256:183bfb45a493081943be7ea2b5adfc2b611e1cf377cefa8b8a8be404f45ef9a7 \ --hash=sha256:19b7ab10b210b0b3ad7985d9ac4eb66ab09a90b20fe6e2f7ba55d01a234345d0 \
--hash=sha256:19f4164243fc206d12ed3d866e80e74f5bc3627966520da1a5f97e42c32a3f39 \ --hash=sha256:1d4962d4c66bf830a7e59ed6cfc17d148149898a3aefa8ec6e59763e6e3ed085 \
--hash=sha256:1ae225f66e5938f4fa29d37e009a3bb3b13032ac57eb4eb42afa44f6e4054e69 \ --hash=sha256:1db753c9115ec7100d073b744d17e25e88a8f90f5c39b2f5dd878149af59671f \
--hash=sha256:1bc4cc83fb7f66ffb16f74d6dd0162e144333fc36ebcce32246f80c8735b2551 \ --hash=sha256:1dde6131244bba38a17c745836ba190bc753fd73c9291666287fd0a3fa3dcf30 \
--hash=sha256:1dd6a1c3ad4cb674f44525d9957f3e9c209bb6dd9213245195167a281fcc2bdc \ --hash=sha256:25c6997a9a534e016695a0ba06b2f07945de682731ff01065b6d5a4474179da1 \
--hash=sha256:20cf4d0651987c906a2f5cba4e3a8d6ba4bfdf973cfe2a96c0d6053888ea2ecd \ --hash=sha256:26e6eda8d38c1fcab1090dd196ee87cbd13788e531937610e2589085de074e77 \
--hash=sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd \ --hash=sha256:27acc820660aaffa4f7c087f29120e12980f7779d56d8492d263170111284740 \
--hash=sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405 \ --hash=sha256:2a0217714657e023ef4293500f65aa20fce6164c8fd6b08fa5bd4a859fb14b9b \
--hash=sha256:23a5dc68e08ed13331d61815c08f260f46b4a60fdd1640bbeb82cf89a9d90289 \ --hash=sha256:2c8daa471358dc2d6fcf02165e80ec68f77871a286df95bc5cc3816153b0fd2c \
--hash=sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b \ --hash=sha256:30a89d3ac8faec007453fb541f3f46807eeec88edd5826f6e3fe001752a2c621 \
--hash=sha256:2593a0a6621545b9095b71ad74ed4226eba438a7d9fc3712a99bdb15508cf93a \ --hash=sha256:31033dc34636ea6b7d5cc11b1ddbda78a14de858ba9d3e1ed4b69a3085bc521e \
--hash=sha256:264c605ab9c0e4aa1a679636f4582c4d3313700009fac3ec9c3412ed0d8f3e1d \ --hash=sha256:32ab449a5486f6c758e849bb86710d0e45edc24a04e250c01555f8f5653958f8 \
--hash=sha256:26c5272c6a4bf4cf32d3f5a7890c942b0e04438691157d341616d02cca74d4bd \ --hash=sha256:3483644525531e1d5762b0c44a8e18b6efba321b6dcf8a8952de10b037618bca \
--hash=sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac \ --hash=sha256:34c2d737beabfe35baada43941ed519251e9a12e779031496bcd5d539fcfd730 \
--hash=sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62 \ --hash=sha256:3779def59032b81e44a5f70096ef6bf2082f8d901937dca354474ba09782e245 \
--hash=sha256:29f5c00cb7d752bce2c70ebd2d31b0a42f9499ffdd3ecb2f31a5b73ee43031ad \ --hash=sha256:37a58976370f36d9329d118ad0b953c5aeb9119ac9c6a4e258942a225d0573a1 \
--hash=sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f \ --hash=sha256:3893c14c4b6ac5b2d54ba8cf03e99fe5104e592de491f19bd6b82756c09f8004 \
--hash=sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad \ --hash=sha256:3a12689be69a28ddaa0ab99a5a1137da2afd5f8f16df7b5680b66f616d3eda1d \
--hash=sha256:363e47283bde87051b821826e71dde47f107e08614e1aa312ba0c5711e77738c \ --hash=sha256:3ab541146f1f6968c462d6c2ac495148e8cdba2f8347700b2141b6ec5a75bf52 \
--hash=sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292 \ --hash=sha256:3abf332af33a74288675d936fe861fd4344da0dd6622193fbc4f2bfbb35536b5 \
--hash=sha256:37448bf9c7d7adfc5254763901e2bbd6bb876228dfc1fc7f66e58c06368a7544 \ --hash=sha256:3fd9728a2735fda14f4e8235830c86b539e9661e849665bf926d3f867943b4bf \
--hash=sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491 \ --hash=sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc \
--hash=sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120 \ --hash=sha256:441dd227fa0690eb9fc81edabc63cdcefc212bba99b906dcf6e32cc1a9d3e533 \
--hash=sha256:3ae5d8d5427f3cc317e7950f2da7ad276df0cfa37b8de2f5658959e618ea8512 \ --hash=sha256:469e3618338bd7ab5beb412d2439825479fcf0dab99e394ca563dbc4eaf6c834 \
--hash=sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb \ --hash=sha256:47402e62c52ff5988c1e8c6c63177f5708bccf48e366dea4e3dcf1e645e04947 \
--hash=sha256:40d9189f80075f2e1f88db21ef815a2b17b28adf8e50aaf5c789bfe737027f32 \ --hash=sha256:4f0dd2f01f9f8a89f565d000e03abcf0a13d692a346c8d22f628d49af098777a \
--hash=sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2 \ --hash=sha256:53b7d2b7a10b1c35c0a5e21e9224accf60c1bbfba523990732e521b2b73adef2 \
--hash=sha256:41dcc4c7b10484257cbd6c37b83ddb26df2b0e5aff5ac00d095689015af868ec \ --hash=sha256:53c909b62a0532183542fed00c5a7218258c56292d409bc789886fe1cb04c438 \
--hash=sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88 \ --hash=sha256:54a7f95e4de5fb94e2f9f4b9055c6ba33bf3d628fd77a1d647c5923caa2cdcdc \
--hash=sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43 \ --hash=sha256:556e94a63c9b04716f8e4de2abb65775061f846e89331b6c5be79183a24f98ea \
--hash=sha256:4642e04449a1e164b5ff71ffd901ddb772dfabf5c9adf1b7be5dffe1212bc037 \ --hash=sha256:55b03549819867ea141c0202242c4816c82e52ec36e7e648db9d8da5a3dc3ed6 \
--hash=sha256:468479e52ecf3ec23799c863336d02c05fc2f7ffd1a1424eeeb9a28d4eb69d13 \ --hash=sha256:581d4c8ae690a6609e64862dd6b7c2489635c2d13907fc2b20f2bc200ff1d21e \
--hash=sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03 \ --hash=sha256:58bb955caba94e467d2a96da17660d2d704e0675894cba21ab8a775b8621fd1c \
--hash=sha256:481d6e2104285d9add34f41b42b247b76b61c5b5c26c303c2e9707bbf8bd9a64 \ --hash=sha256:5b7328b46d49fc9477d91ae8f6d55340347d827b7734ba3ea33faae0efef1383 \
--hash=sha256:4937460dc5df0cdd2f06a86c285c28afda06aefa3af949f9477d3e8df430c485 \ --hash=sha256:5ba186ad207446c65d3bb3d3e0412b032b1d9f595e59861e2354798c5703d955 \
--hash=sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16 \ --hash=sha256:5bec7d03d78d853597d6107854c2310ce3f761fd218fe9fe91d5101fcf6c2efe \
--hash=sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb \ --hash=sha256:5c6bf403fbb3b3e348a561a5f4f0b9961835657981c802a1df03653eef8a9074 \
--hash=sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9 \ --hash=sha256:5f6994074ebae6ffb04447268e37dc16edc304f9859cf91acb86e0af6c1b395c \
--hash=sha256:4e2c54d6b47361d0f1d3bc8d4e082ad87201e56ccdcca4d3b9ee3644ff595ec8 \ --hash=sha256:62aeb7e85b5d60320b9d77eef2e773994e2c0ce10121b277e0a19804e1654a5a \
--hash=sha256:52b0ac6903cf74ebf997eb8c682d2fbac7d1ab7e4c552413eec55868a9b73f39 \ --hash=sha256:63876be28efefa04a1df615b46770e82042cce445cfdce55160522f57b231ccb \
--hash=sha256:546b66c0dd1bb8d9fa89d7123e5fa19a8aff3a1f2141eb22df96112afb17b842 \ --hash=sha256:639f6c857d91d9be29bd7502348d6736dab168b54b5158cd899abf11684dc186 \
--hash=sha256:56971379bc5ee8037c5a0f09fa88f66cdb7d37c3e38af3e45cf539f41131ac1f \ --hash=sha256:640f97d43d867bcb9c75b3af013b64850756b746cb6bce8ace83b70da3abba9d \
--hash=sha256:5715e0e28736a070f3f34a7ccc09e2fdcba0e3060abbcf61a1a5718ff6d6b105 \ --hash=sha256:649dda677cf3bd6ac9ae14007ba0c824ded8ce5808b53fc7431d9140399118c1 \
--hash=sha256:5cfa1a34df366d9dc0d5eaf420f4cf2bb1e1bebe1066d1c2fc28c179f8a4004c \ --hash=sha256:6540377fbd53fe1b629172288c464fb18db11ce1fa7dc15891da10aa9dcc3e7f \
--hash=sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7 \ --hash=sha256:6689e828a94eee4f139408c337bb198e014724bb8a8c26d3cfac49d119ed69a6 \
--hash=sha256:6262b87f9e5c1e5fe501d6c153247289af42eb44ad7660b9b3de17baaf92d6f6 \ --hash=sha256:68a9198d0fc122d14bb76837de9aa80cf84caed990b5b237f532ed87d3706736 \
--hash=sha256:63aeafc26aac0be8aff14af7871249e87ea1319be92090bfd632ec68e03b16a5 \ --hash=sha256:6b1761fbf9ec984e2e9d9c589ef5f5fd684b7c19f92aadd567a26c5224958db6 \
--hash=sha256:690022c7fae793b0489aa68a658822cea83e0d5933781811cabbf5ea3bcfe73d \ --hash=sha256:70cdfd80589d59e43e18005dd7244e8895e93db8ab6a620b7e23df5445a4e3d2 \
--hash=sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9 \ --hash=sha256:70ef8a7e102a1508f8121aae5b0867abd663f72c14f0a9c937e6554cb4587b7b \
--hash=sha256:73becf6d8c81d4c76b1014dbd3584cb26d904492dcf73ca85dc8bff08dcd6d2d \ --hash=sha256:73bc2086f141224ebddb7fc5c6a36ca58b31b94b561e1dfe8e073e3270fad1e7 \
--hash=sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb \ --hash=sha256:74a9717fd0d82effef5c2854f0d917231d5324b5a3eb7275c43ac9fa32f97a14 \
--hash=sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f \ --hash=sha256:752d3bbfe874715ccd0aec7f88d7fc623c0f1fd7aa7b3238a084e017bad2a009 \
--hash=sha256:76b958b4ea3104483c20f74866d55aa056546e15ebe83dd7aecd63698f43b755 \ --hash=sha256:762ff394d5bd56da0cf034a23dcce4e13923f15321a2adfa2ac00201dc6d3fca \
--hash=sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb \ --hash=sha256:76447f65250ed2501ead1a1552f5ce8edff159a86f308348e6a9c4acb5e1f1b4 \
--hash=sha256:7ba11752e346bd804ea312ec2eea2532dfa8b8d3261d81a32ef9e6ab16256280 \ --hash=sha256:766b010012d59470072c1816b5b6c69f1d243e5db36ea5968e94accf430a4635 \
--hash=sha256:7da13bb6fbadfafb474e0226a30570a3445cfd47c86296f2446dafbd77079ace \ --hash=sha256:787b2496d0dbe8cd180984e8d29e3a6f76e7ea34db781cb3bd55e4ba1ef8b4ee \
--hash=sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e \ --hash=sha256:793033d6c5cdf33a573f910d9bea14ef8f5771820411d118da8e1182edb53d5e \
--hash=sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33 \ --hash=sha256:7d47866cb32fb503450b6edc9df355d10dc49836af2e89901bd6ac6b0896d9d9 \
--hash=sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5 \ --hash=sha256:7f7a92e8583f06b1fd49d01158143b8461cfcd135dcb10ec807270a3051bd603 \
--hash=sha256:81ff55c70b67d19d52b6fd118a114c0a4c97d799cd3089ff9bd9e2ff4b414ee2 \ --hash=sha256:80c2dfadb855da477cf73373ad29a333535dedb9b12bad02c9814c8e2b43bf08 \
--hash=sha256:857efde87d365706590847b916baff69c0bc9252dc5af030e378c9800c0b10e3 \ --hash=sha256:83b6b30eb131da7a75b601f28c5d6971e6ed3e887919bf6b6a1ad3c2df289080 \
--hash=sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585 \ --hash=sha256:86281fbdd6a8162756f8d603f37e3435bfa38043adb79c6dc6a2dfee065e7525 \
--hash=sha256:8c11b984b5ce6add4dccc7144c7be5d364d298f15b0c6a57da1991baedc750ce \ --hash=sha256:86c89b9d55ebf820ad7c90bc533410f0d098054f293351f10603c0c46ff598f5 \
--hash=sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946 \ --hash=sha256:876e1ff5930ed8bf295ec5ef9a8155e9b6b1876bbf1deed8b3a8069311875a8f \
--hash=sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e \ --hash=sha256:88136950da4d13c318bde414ce10219931937851327f44328f2df4d2c4614067 \
--hash=sha256:9147d8e386ec3b82c3b15d88927f734f565b0aaadef7def562b853adca45784a \ --hash=sha256:88d8cb75b9d82858497a5393e3c63cfbf03035225e4b35a49ed7ccb151e4dc0e \
--hash=sha256:920354904d1cb86577d4b3cfe2830c2dbe81d6f4449e57ada428f1609b5985f7 \ --hash=sha256:8be8ad51249698103d24b0571df35a10990fbe93dd043b6c024172189485f5e3 \
--hash=sha256:942454ff253da14218f972b23dc72fa4edf6c943f37edd19cd697618b626fac5 \ --hash=sha256:8d43ca737b20e106e4aebc42b2f3ae19f00ba63d7eb731698ee083d72d15646f \
--hash=sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28 \ --hash=sha256:8dadbe5b217ff35b6a8d16610dd710219b59b76d13f0e3f0d9f36786206e4485 \
--hash=sha256:976a6b39b1b13e8c354ad8d3f261f3a4ac6609518af91bdb5094760a08f132c4 \ --hash=sha256:9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13 \
--hash=sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c \ --hash=sha256:96f2ec43df44b1f76249ee0a615334f9b5b060e1c8bd90e706dad2d14d02f383 \
--hash=sha256:9c03e048b6ce8e77b09c734e931584894ecd58d08296804ca2d0b184c933ce50 \ --hash=sha256:98fc784c2c1440667aeedf8465bdfe10208acf0ead656a2c68627299f546b315 \
--hash=sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9 \ --hash=sha256:9e36f163528fc50cbef305f02a5fd66d404edf7049cdaff211dbc2cba5a7013e \
--hash=sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495 \ --hash=sha256:9eb9b5a968f6e0f6d640092a567e14529ff8cea2e29d00da6f78a79fa49f013c \
--hash=sha256:9f93d5b8b07f73e8c77e3c6556a3db269918390c804b5e5fcdd4858232cc8f16 \ --hash=sha256:9f76acfb5f68ba982635a53fd985a8044be98a35b43232c2a1ee235ffab3e1dd \
--hash=sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45 \ --hash=sha256:a088f287f7d8275a33c07f2cac6c50b9319309a0200a39e7e75d80c707723099 \
--hash=sha256:a02ca8fe48815bddcfca3248efe54451abb9dbf2f7d1c5744c8aa4142d476919 \ --hash=sha256:a10bd2fd62e8ce916ececb342f348f190724a098c1faa056fdfb2a22ad5e8660 \
--hash=sha256:a1d9b99e5b2597e4f5aed2484fef835256fa1b68a19e4265c97628ef4bf8bcf4 \ --hash=sha256:a4bbea04c97f6d78a48e3fbc1cb9116d2780b1b39e03a23f6eb9b603fd61f510 \
--hash=sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc \ --hash=sha256:aa366a1e55b8ebfe8ca8ddc3cfe75c8ebade181aeb0f661d0cb05986b647f72a \
--hash=sha256:a31286dbb5e74c8e9a5344465b77ab4c5bd511a253b355b5ca2fae7e579fafec \ --hash=sha256:aa49e06d94aba782c6a02eecb7e507969e7e7a41b267f1b359bb35585f295d5b \
--hash=sha256:a86f06f059e22a0d574990ee2df24ede03f7f3c68c1336293eee9536c4c776cd \ --hash=sha256:aad9aa39483ed8ec44d6d2e59e5b98a0d80676ef0d92f44bfc374836111f62f5 \
--hash=sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8 \ --hash=sha256:aae97dfdb60715c164419ac2532a76d013c3918a665eb6cb7288098b5f349aaf \
--hash=sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f \ --hash=sha256:abbefa31eee84842140f67acef1c828e28bba8bbf0c3bc6e5492a9af88152c28 \
--hash=sha256:b6c2f225662bc5ad416bdd06f72ca301b31b39ce4261f0e0097017fc2891b940 \ --hash=sha256:ac931cdc9442c1763b8a8f6cd62c0c938737eafc5be75eff88df55fc73bc0d00 \
--hash=sha256:bb40648d96157f9081886defe13eac99253e663be969ff938a9289eff6e47b72 \ --hash=sha256:acd7d70b64c0aae0c7922cca83d288a16f5f6da523637697872253415269baef \
--hash=sha256:bba078de0031c219e5dd06cf3e6bf8fb8e6e64a77819b358f53bb132e3e03366 \ --hash=sha256:add8cf6ddf9a65116119a28ece0f7886e30af27ba724a7594305f1d1b58a92a1 \
--hash=sha256:bc783ee3147e60a25aa0445ea82b3e8aabb83b240f2b95d32cb75587ff781814 \ --hash=sha256:aee395f5d0927f947758b4ec119fd5fc8ec71f07a1c5c52077b30b04c0fa6955 \
--hash=sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690 \ --hash=sha256:b1b963fd8f5caa68e99dfae060d54de1fe9cba899b8718b44a00cdca53c3e590 \
--hash=sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13 \ --hash=sha256:b2d444f2e66624d68e9c6b211e28a76e22fff5fcabcfff4deac18b529b7d4137 \
--hash=sha256:c08da09dc003c9e8c70e06b53a11db6fb3b250c21c4236b03c7d7b443c318e7a \ --hash=sha256:b8d812c6011c08b8111a15e54dd990b8923692d80adf35488bee34026c35accf \
--hash=sha256:c3592631e652afa34999a088f98ba7dfc7d6aff0d535c410bea77a71743f3819 \ --hash=sha256:ba96ae44888e0185281e937633a743ea90d5a196c6000f82565ebb0580012d40 \
--hash=sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c \ --hash=sha256:bdebcc8a75d38c7598dfb2c9ed852d7a9eb4a10d6e2d0764b919b802bf32ac88 \
--hash=sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86 \ --hash=sha256:c07da4cebf6889f03ebac8d238f62318e29f495de0aa18a51ea14e61ae907e2e \
--hash=sha256:c53fa3a5a52122d590e847a57ccf955557b9634a7f99ff5a35131321b0a85317 \ --hash=sha256:c08e5c694306507275f2290073350c4f32e383db15213b2c69e7ff39c1193840 \
--hash=sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180 \ --hash=sha256:c4f469aebd783bb741c2ecb2a681008fd26bfe5c16a9a72ed5467f834e810df2 \
--hash=sha256:c748ebcb6877de89f48ab90ca96642ac458fff5dec291a2b9337cd4d0934e383 \ --hash=sha256:c5d7152ec39ca7c402d8fb9bad86140a15b9503bd0c54484e3f1bbe3dd37ceca \
--hash=sha256:c871299c595ee004d186f61840f0bfc4941aa3f17c8ba4a565ead7e4f4f820ee \ --hash=sha256:c674693f055fa2495de12292cb45e9944199d8eaef5a2dec45175c7c61cb73e3 \
--hash=sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe \ --hash=sha256:c6ed5141a5c7507cf3ee76bd363b0d6f801e3321adc35b5d825a23115faa5465 \
--hash=sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181 \ --hash=sha256:c921ba5c51e4e9f63b8b00267d06566e1f63407408a0496da2d1d0bfc819c7fc \
--hash=sha256:cec05be8c876f92a5aa07b01d60bbb4d11cfbdd654cad0561c0d7b5c043a61b9 \ --hash=sha256:c9a4b821dc7055bf9e05ff5719e18ec501f75c0f0bbfabd573b277559780833d \
--hash=sha256:d036ee7b99d5148072ac7c9b847193decdfeac633db350363f7bce4fff108f0e \ --hash=sha256:c9f79d5325907f13e1be0b3e4dacc1049d1dffc4aeee3c995284bea5fe0fab7d \
--hash=sha256:d0d799ff958655781296ec870d5e2448e75150da2b3d07f13ff5b0c2c35beefd \ --hash=sha256:cd312b9692e831d2ffcad61eab31d91d4b4655a962e61de8fb410472cbcd37aa \
--hash=sha256:d1392c569c032f78a11a25d1de1c43fff13294c793b39e19d84fade3045cbbc3 \ --hash=sha256:cea3f4c1af79af13cdb2da0c028111d8f8522d4f22a000c82385535f24e5cf3a \
--hash=sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d \ --hash=sha256:cecdd5dfdc87b1fd87dbf81d4b037a544f47f4c744200a67013771682d67686a \
--hash=sha256:d3829a6e6fd550a219564912d4002c537f65da4c6ae4e093cc34462f4fa027ad \ --hash=sha256:cf9d57306d848218f3601fee7601fab1a327c942d56e2e97610583cb4dd74206 \
--hash=sha256:d43aa26dcda363f21e79afa0668f5029ed7394b3bb8c92a6927a3d34e8b610ea \ --hash=sha256:d34bbf07dbc7ca5970671b1512e928991fb5e9d95365636c9b2d8b4f53af405e \
--hash=sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24 \ --hash=sha256:d49514be2f28d895c38cf9d2b72d7b9a07d00314519f456c0b50b53cfcf4c785 \
--hash=sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d \ --hash=sha256:d680fbcb768404c601ecb43519ecd8461f6954cb11c06a78962f666832ccfca8 \
--hash=sha256:db88156fcf544cdbf0d95588051515cfdfd4c876fc66444eb98bceb5d6db76de \ --hash=sha256:db1d75f6617a49c1c01bc7023713e0ff59ab32c9579ae62a7674c0e34f3b0b0a \
--hash=sha256:de550d129f18d8ab819651ffe4f38b1b713c7e116707de3c0c6400d0ef34fbc1 \ --hash=sha256:dcb292aa7fe485ceff7af4f92e46c5af397daec5dff64871a528f0fc47a3cc5b \
--hash=sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d \ --hash=sha256:e07c65f443c887bbcf31cc1771d932ecc192a5273943589b3c7572b749f1ffb2 \
--hash=sha256:e3c4f84b24a1fcba435157d111c4b755099c6ff00a3daee1ad281817de75ed11 \ --hash=sha256:e902da4b04e6b52e5893900d4b8ab46068f75f3561f01bf1080957f9fd932ed6 \
--hash=sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9 \ --hash=sha256:e9308ff8241c532df3f3e570f9a5aeed6c853f888512ba4b75638d7c11c95ef6 \
--hash=sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2 \ --hash=sha256:eb7c9811bfaa8b1ed5ed319f5d370dfbcaa59d52ea64be2a5a85e18195930354 \
--hash=sha256:e80807d72f96b96ad5588cb85c75616e4f2795a7737d4630784c51497beb7776 \ --hash=sha256:ebe6af670449830d6d9b752c256a983291c766a1365ba5d5460048f9e33a7818 \
--hash=sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f \ --hash=sha256:ed21202aec73cda4d55d1ce57b389aadb90ffb044e6cd1080b8347efe1b1ec84 \
--hash=sha256:f0cea5b1d3e6e77d71bd2b9972eb2446221a69dc52bb0b9c3c6f6e5700592d93 \ --hash=sha256:efe0374196335f93b53269acd811b944f2e6bdc88e8894f214bd636455484909 \
--hash=sha256:f15401d8d3dbf239e23c818afc10c7207f7b95f9a307e092122b6f86dd43209a \ --hash=sha256:f64ec5397ea6a41fc1b4af0380d79b44a755b5531dcaccd9940fb260dca93038 \
--hash=sha256:f504d861d9f2a8f94020130adac88d66de93841707a23a86244263d1e54682f5 \ --hash=sha256:f6ac4ef4d82dff54670227a69c67782ae0b811b5cf6b17954f1e8f7502fc0d1d \
--hash=sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d \ --hash=sha256:f6f0ce10945fab9c4c06ce14e22af9059d1a87493a9af4501a5b0b9187e21cf2 \
--hash=sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d \ --hash=sha256:f8844cd288697c6425c9beba919302241e3278871dc6519515e72b04e987abcf \
--hash=sha256:fcf3da95e93349e0647d48d4b36a12783105bcc74cb0c416952f9988410846a3 \ --hash=sha256:fe0306bd29505a9177aac19f1877174b0e7422c222a59f70b2cd41633448c3dc \
--hash=sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086 \ --hash=sha256:ff3f333630ab480244a1bff72043e511a91eb22e7595dead8653ee5612dd8f3d \
--hash=sha256:ffb34ea45a82dd637c2c97ae1bbb920850c1e59bcae79ce1c15af531d83e7215 --hash=sha256:ffecec8eb889b58ba9be5b95fb1cc78e22ea8eedea38e8736a1568fe1979250e
# via # via
# python3-saml # python3-saml
# xmlsec # xmlsec
@@ -1184,9 +1184,9 @@ openpyxl==3.1.5 \
--hash=sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2 \ --hash=sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2 \
--hash=sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050 --hash=sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050
# via tablib # via tablib
opentelemetry-api==1.41.1 \ opentelemetry-api==1.42.1 \
--hash=sha256:0ad1814d73b875f84494387dae86ce0b12c68556331ce6ce8fe789197c949621 \ --hash=sha256:51a69edacadbc03a8950ace1c4c21099cacc538820ac2c9e36277e78cebba714 \
--hash=sha256:a22df900e75c76dc08440710e51f52f1aa6b451b429298896023e60db5b3139f --hash=sha256:56c63bea9f77b62856be8c47600474acad853b2924b99b1687c4cb6297166716
# via # via
# -r src/backend/requirements.in # -r src/backend/requirements.in
# opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-grpc
@@ -1203,27 +1203,27 @@ opentelemetry-api==1.41.1 \
# opentelemetry-instrumentation-wsgi # opentelemetry-instrumentation-wsgi
# opentelemetry-sdk # opentelemetry-sdk
# opentelemetry-semantic-conventions # opentelemetry-semantic-conventions
opentelemetry-exporter-otlp==1.41.1 \ opentelemetry-exporter-otlp==1.42.1 \
--hash=sha256:299a2f0541ca175df186f5ac58fd5db177ba1e9b72b0826049062f750d55b47f \ --hash=sha256:2d9ebaed714377a67d224d46795ddcc11d2c877fa5de35fda70b6f3b010729a9 \
--hash=sha256:db276c5a80c02b063994e80950d00ca1bfddcf6520f608335b7dc2db0c0eb9c6 --hash=sha256:aedd54545bb0587cd45210abdc8be545af9c01413f3307786e276df1e3c83bee
# via -r src/backend/requirements.in # via -r src/backend/requirements.in
opentelemetry-exporter-otlp-proto-common==1.41.1 \ opentelemetry-exporter-otlp-proto-common==1.42.1 \
--hash=sha256:0e253156ea9c36b0bd3d2440c5c9ba7dd1f3fb64ba7a08fc85fbac536b56e1fb \ --hash=sha256:04f1f01fb597c4249dfcd7f8b861c902c2102369d376d9d346ff38de4469a2ee \
--hash=sha256:10da74dad6a49344b9b7b21b6182e3060373a235fde1528616d5f01f92e66aa9 --hash=sha256:f48d395ab815b444da118868977e9798ea354c25737d5cf39578ae894011c140
# via # via
# opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http # opentelemetry-exporter-otlp-proto-http
opentelemetry-exporter-otlp-proto-grpc==1.41.1 \ opentelemetry-exporter-otlp-proto-grpc==1.42.1 \
--hash=sha256:537926dcef951136992479af1d9cd88f25e33d56c530e9f020ed57774dca2f94 \ --hash=sha256:0ae1177e2038b18a929b3098215243631ef91136cba26b7e2b12790ceb7e87cc \
--hash=sha256:b05df8fa1333dc9a3fda36b676b96b5095ab6016d3f0c3296d430d629ba1443b --hash=sha256:975c4461f167dd8ed8857d68d3b6b25f3d272eab896f6a9470d0f5b90e2faf15
# via opentelemetry-exporter-otlp # via opentelemetry-exporter-otlp
opentelemetry-exporter-otlp-proto-http==1.41.1 \ opentelemetry-exporter-otlp-proto-http==1.42.1 \
--hash=sha256:1a21e8f49c7a946d935551e90947d6c3eb39236723c6624401da0f33d68edcb4 \ --hash=sha256:00a16da1b312a1d6c7233d600d557c91df71125af73020f3b9a7765bd699d59d \
--hash=sha256:4747a9604c8550ab38c6fd6180e2fcb80de3267060bef2c306bad3cb443302bc --hash=sha256:bf142a21035d7571ac3a09cb2e5639f49886f243972883cfe777ed3bf02b734d
# via opentelemetry-exporter-otlp # via opentelemetry-exporter-otlp
opentelemetry-instrumentation==0.62b1 \ opentelemetry-instrumentation==0.63b1 \
--hash=sha256:90e92a905ba4f84db06ac3aec96701df6c079b2d66e9379f8739f0a1bdcc7f45 \ --hash=sha256:32368d6ae52c8de20aa790a6ad86b10a76f09956092337ae37d675773990e541 \
--hash=sha256:976fc6e640f2006599e97429c949e622c108d0c17c2059347d1e6c93c707f257 --hash=sha256:f1986716d52cc316ea5f60189098726a9071d8ecc0eee96c9ed110be08bade9c
# via # via
# opentelemetry-instrumentation-dbapi # opentelemetry-instrumentation-dbapi
# opentelemetry-instrumentation-django # opentelemetry-instrumentation-django
@@ -1234,75 +1234,76 @@ opentelemetry-instrumentation==0.62b1 \
# opentelemetry-instrumentation-sqlite3 # opentelemetry-instrumentation-sqlite3
# opentelemetry-instrumentation-system-metrics # opentelemetry-instrumentation-system-metrics
# opentelemetry-instrumentation-wsgi # opentelemetry-instrumentation-wsgi
opentelemetry-instrumentation-dbapi==0.62b1 \ opentelemetry-instrumentation-dbapi==0.63b1 \
--hash=sha256:82755a0f013980b2c000a10452fb91ab5135b937a8afe37c45f34e62ecc7a2b8 \ --hash=sha256:1bc842ddcacb7ec3622ef306ffdb3c584bf97df79d381e03604ea6eb23c63781 \
--hash=sha256:dd2cc4085e832a708835218833b839a88ef0ace66851d14debefe6b2ef363a3c --hash=sha256:406978ed56bcfc5fd246fd918e6b36d0f5de26fa396c78cf63326a7b530597c8
# via # via
# opentelemetry-instrumentation-psycopg # opentelemetry-instrumentation-psycopg
# opentelemetry-instrumentation-pymysql # opentelemetry-instrumentation-pymysql
# opentelemetry-instrumentation-sqlite3 # opentelemetry-instrumentation-sqlite3
opentelemetry-instrumentation-django==0.62b1 \ opentelemetry-instrumentation-django==0.63b1 \
--hash=sha256:268c9b3d234510ab835acea0524ba65361389f6e2c291b495ba8072c7bb59003 \ --hash=sha256:909ea4afbb18f16eb811d7ac330ed0f6fda7765191d49bccb8cca38c4fbc005f \
--hash=sha256:5ac96ef25019fbea5e2739c9b4b7ce17e4379e2b1ec9fdce6b696a5c09ceabbe --hash=sha256:f2071d2f92e4779c5a14dd452b0dfe426343599e6efa9d888304fb639a9f3101
# via -r src/backend/requirements.in # via -r src/backend/requirements.in
opentelemetry-instrumentation-psycopg==0.62b1 \ opentelemetry-instrumentation-psycopg==0.63b1 \
--hash=sha256:61c8a1794d6e11c7bca1735c2b87ef8c04b5d7202e962409d66d204f84c01be1 \ --hash=sha256:4d74ec2dda8e54ead9bd71f134af984792f77e1d8809591ae53edadddd4c8fc0 \
--hash=sha256:f08e843e81656271ddc8c8194a54a5f3c8ebe0cb137e3166af9a2ff014978b5e --hash=sha256:86819f4cccea21a47920807826495797b7261cc1893c8e855415e76fb7b8e2e8
# via -r src/backend/requirements.in # via -r src/backend/requirements.in
opentelemetry-instrumentation-pymysql==0.62b1 \ opentelemetry-instrumentation-pymysql==0.63b1 \
--hash=sha256:12e42f891978a3a41bc564bb81e2299d3654659589f705064a4bb10e93e493f8 \ --hash=sha256:2cf6a24e136b5b0c3f6e59675fc44b42ad2b97474b075a82859f9868a9252648 \
--hash=sha256:50d2efdde1bd8d826cbceffe5b3ce0ffd421349d609200eab0f3e632945cd304 --hash=sha256:6b54b83c86ac6c2e8dae642bbee4894e2283ceb5da23d571f1abb7060f7b62f4
# via -r src/backend/requirements.in # via -r src/backend/requirements.in
opentelemetry-instrumentation-redis==0.62b1 \ opentelemetry-instrumentation-redis==0.63b1 \
--hash=sha256:2d3c421d95e05ade075bee5becbe34e743b1cdf5bdee2085cb524f88c4f13dcb \ --hash=sha256:28d235159df43cc2bc8779af5c602afad1e08603fff75ac8ca34dd1bf30a9cb9 \
--hash=sha256:9aedd02c1acf631251d1d676634db47da9da04e0a626cd0c7d83fe0eb791d165 --hash=sha256:f0e51c4006f68e340abbf28a7995feff004de78649697cbdf3bac0072cacd082
# via -r src/backend/requirements.in # via -r src/backend/requirements.in
opentelemetry-instrumentation-requests==0.62b1 \ opentelemetry-instrumentation-requests==0.63b1 \
--hash=sha256:67a79c4b67e2192445c1cf03d62126fa623065688d8bd1a9f87f858b0e5f0286 \ --hash=sha256:513fcaa3d93debbdb359c00ce1a137a34a89ee908c51ac43beb7e8c18ac2b3cd \
--hash=sha256:ca348f2f51b715c21e86d82106d784f7069ae849c3e636ab37e34dc0ba510b8c --hash=sha256:935c980a11e33bfd7ed969c741e4bd7c84077045651469f10e163534368d87f7
# via -r src/backend/requirements.in # via -r src/backend/requirements.in
opentelemetry-instrumentation-sqlite3==0.62b1 \ opentelemetry-instrumentation-sqlite3==0.63b1 \
--hash=sha256:14ba18aed35a01e1d89bb814e49f7d6dd90bb68b2034ccf047801ca4659dba27 \ --hash=sha256:3d61afda8358dc32135fabd27e5934bd25ea0eed68d64c34508f24ba8d723efc \
--hash=sha256:3ab0d614bab9956126ce81a22315bf58d56d653a65675a66e8b525f880ec3016 --hash=sha256:cc11c68cccde061dc8bcc5f80fe00bb0105a0fbb11b390859a23ca91ccd4e3b6
# via -r src/backend/requirements.in # via -r src/backend/requirements.in
opentelemetry-instrumentation-system-metrics==0.62b1 \ opentelemetry-instrumentation-system-metrics==0.63b1 \
--hash=sha256:260130cb63e3028290305d8f25b744fb3a279677769dd65287ce60086cc11d32 \ --hash=sha256:995051f47876d79461aed8b7aa205d4584d90794ef864342cc748929c389bb42 \
--hash=sha256:b8097e0d3747102fa545723bb67f50f572a7618b6479359fa6533007d2f621aa --hash=sha256:d6d4d7a1a854be4165143cf6420ee5894188762eb367d7bf9da5be4a83a4b632
# via -r src/backend/requirements.in # via -r src/backend/requirements.in
opentelemetry-instrumentation-wsgi==0.62b1 \ opentelemetry-instrumentation-wsgi==0.63b1 \
--hash=sha256:02a364fd9c940a46b19c825c5bfe386b007d5292ef91573894164836953fe831 \ --hash=sha256:03d61c4678ce82402e7f37b6a3dbd84cb97b85b3cb416a78c2e74c7c6d9451fa \
--hash=sha256:a2df11de0113f504043e2b0fa0288238a93ee49ff607bd5100cb2d3a75bc771f --hash=sha256:86779715262227d3436bdfb16aabd1c524b0f236725a69e8754ceda76c6d79dc
# via # via
# -r src/backend/requirements.in # -r src/backend/requirements.in
# opentelemetry-instrumentation-django # opentelemetry-instrumentation-django
opentelemetry-proto==1.41.1 \ opentelemetry-proto==1.42.1 \
--hash=sha256:0496713b804d127a4147e32849fbaf5683fac8ee98550e8e7679cd706c289720 \ --hash=sha256:c6a51e6b4f05ae63565f3a113217f3d2bfaec68f78c02d7a6c85f9010d1cfca6 \
--hash=sha256:4b9d2eb631237ea43b80e16c073af438554e32bc7e9e3f8ca4a9582f900020e5 --hash=sha256:dedb74cba2886c59c7789b227a7a670613025a07489040050aedff6e5c0fb43c
# via # via
# opentelemetry-exporter-otlp-proto-common # opentelemetry-exporter-otlp-proto-common
# opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http # opentelemetry-exporter-otlp-proto-http
opentelemetry-sdk==1.41.1 \ opentelemetry-sdk==1.42.1 \
--hash=sha256:724b615e1215b5aeacda0abb8a6a8922c9a1853068948bd0bd225a56d0c792e6 \ --hash=sha256:083cd4bbfaa5aa7b5a9e552430d9951219967cfb27aa61feb13a77aba1fc839d \
--hash=sha256:edee379c126c1bce952b0c812b48fe8ff35b30df0eecf17e98afa4d598b7d85d --hash=sha256:8c834e8f8c9ba4171d4ec843d0cb8a67e4c7394d3f9e9297e582cbd9456ddbf7
# via # via
# -r src/backend/requirements.in # -r src/backend/requirements.in
# opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http # opentelemetry-exporter-otlp-proto-http
opentelemetry-semantic-conventions==0.62b1 \ opentelemetry-semantic-conventions==0.63b1 \
--hash=sha256:c5cc6e04a7f8c7cdd30be2ed81499fa4e75bfbd52c9cb70d40af1f9cd3619802 \ --hash=sha256:3daf963611334b365e98a57438183eb012d3bfb40b2d931a9af613476b8701a9 \
--hash=sha256:cf506938103d331fbb78eded0d9788095f7fd59016f2bda813c3324e5a74a93c --hash=sha256:dfe5ef4dee82586b746f522b818ceb298d00b3d59f660042bd79404bff8d0682
# via # via
# opentelemetry-instrumentation # opentelemetry-instrumentation
# opentelemetry-instrumentation-dbapi # opentelemetry-instrumentation-dbapi
# opentelemetry-instrumentation-django # opentelemetry-instrumentation-django
# opentelemetry-instrumentation-redis # opentelemetry-instrumentation-redis
# opentelemetry-instrumentation-requests # opentelemetry-instrumentation-requests
# opentelemetry-instrumentation-system-metrics
# opentelemetry-instrumentation-wsgi # opentelemetry-instrumentation-wsgi
# opentelemetry-sdk # opentelemetry-sdk
opentelemetry-util-http==0.62b1 \ opentelemetry-util-http==0.63b1 \
--hash=sha256:adf6facbb89aef8f8bc566e2f04624942ba08a7b678b3479a91051a8f4dc70a3 \ --hash=sha256:6284194028c59cd439f8acfe388145069a6127f11dc077e1344a2094adacc3f8 \
--hash=sha256:c57e8a6c19fc422c288e6074e882f506f85030b69b7376182f74f9257b9261f0 --hash=sha256:ba1268f00922ee522dba2ae38458060f99486e7385a8056985901ca9685adfff
# via # via
# opentelemetry-instrumentation-django # opentelemetry-instrumentation-django
# opentelemetry-instrumentation-requests # opentelemetry-instrumentation-requests
@@ -1490,9 +1491,9 @@ pydyf==0.12.1 \
--hash=sha256:ea25b4e1fe7911195cb57067560daaa266639184e8335365cc3ee5214e7eaadc \ --hash=sha256:ea25b4e1fe7911195cb57067560daaa266639184e8335365cc3ee5214e7eaadc \
--hash=sha256:fbd7e759541ac725c29c506612003de393249b94310ea78ae44cb1d04b220095 --hash=sha256:fbd7e759541ac725c29c506612003de393249b94310ea78ae44cb1d04b220095
# via weasyprint # via weasyprint
pyjwt[crypto]==2.12.1 \ pyjwt[crypto]==2.13.0 \
--hash=sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c \ --hash=sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423 \
--hash=sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b --hash=sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728
# via # via
# django-allauth # django-allauth
# djangorestframework-simplejwt # djangorestframework-simplejwt
@@ -1523,9 +1524,9 @@ pynacl==1.6.2 \
--hash=sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2 \ --hash=sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2 \
--hash=sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9 --hash=sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9
# via paramiko # via paramiko
pypdf==6.11.0 \ pypdf==6.12.1 \
--hash=sha256:062b51c81b0910e6d2755e99e1c5547a0a23b7d0a32322af66240d8edcfabe87 \ --hash=sha256:3953a097b9f26d4e0ead5ff95943d9971377557662a91d8872186053cd71d30a \
--hash=sha256:769394d5756d5b304c9b6bef88b54b1816b328e7e6fc9254e625529a15ed4ab8 --hash=sha256:8fa2a2321cf16247ed848bd7c97f193a60c08670d04abed5b0138327e51c43b0
# via -r src/backend/requirements.in # via -r src/backend/requirements.in
pyphen==0.17.2 \ pyphen==0.17.2 \
--hash=sha256:3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd \ --hash=sha256:3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd \
@@ -2130,10 +2131,6 @@ xmlsec==1.3.17 \
# via # via
# -r src/backend/requirements.in # -r src/backend/requirements.in
# python3-saml # python3-saml
zipp==3.23.1 \
--hash=sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc \
--hash=sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110
# via importlib-metadata
zopfli==0.4.1 \ zopfli==0.4.1 \
--hash=sha256:02086247dd12fda929f9bfe8b3962b6bcdbfc8c82e99255aebcf367867cf0760 \ --hash=sha256:02086247dd12fda929f9bfe8b3962b6bcdbfc8c82e99255aebcf367867cf0760 \
--hash=sha256:07a5cdc5d1aaa6c288c5d9f5a5383042ba743641abf8e2fd898dcad622d8a38e \ --hash=sha256:07a5cdc5d1aaa6c288c5d9f5a5383042ba743641abf8e2fd898dcad622d8a38e \
@@ -10,6 +10,7 @@ export interface ModelInformationInterface {
url_detail?: string; url_detail?: string;
api_endpoint: ApiEndpoints; api_endpoint: ApiEndpoints;
admin_url?: string; admin_url?: string;
pk_field?: string;
supports_barcode?: boolean; supports_barcode?: boolean;
icon: keyof InvenTreeIconType; icon: keyof InvenTreeIconType;
} }
+1
View File
@@ -15,6 +15,7 @@ export interface InstanceRenderInterface {
link?: boolean; link?: boolean;
navigate?: any; navigate?: any;
showSecondary?: boolean; showSecondary?: boolean;
showHover?: boolean;
extra?: Record<string, any>; extra?: Record<string, any>;
} }
+1 -1
View File
@@ -127,7 +127,7 @@
"@lingui/babel-plugin-lingui-macro": "^5.9.2", "@lingui/babel-plugin-lingui-macro": "^5.9.2",
"@lingui/cli": "^5.9.2", "@lingui/cli": "^5.9.2",
"@lingui/macro": "^5.9.2", "@lingui/macro": "^5.9.2",
"@playwright/test": "^1.58.2", "@playwright/test": "^1.16.0",
"@types/node": "^25.5.0", "@types/node": "^25.5.0",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
+1 -1
View File
@@ -271,7 +271,7 @@ function NavTabs() {
// static content // static content
mainNavTabs.forEach((tab) => { mainNavTabs.forEach((tab) => {
if (tab.role && !user.hasViewRole(tab.role)) { if (tab.visible === false) {
return; return;
} }
@@ -1,16 +1,20 @@
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
import { import {
ActionIcon,
Alert, Alert,
Anchor, Anchor,
Box,
Group, Group,
HoverCard,
type MantineSize, type MantineSize,
Paper, Paper,
Skeleton, Skeleton,
Space, Space,
Stack,
Text Text
} from '@mantine/core'; } from '@mantine/core';
import { useQuery } from '@tanstack/react-query'; 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 { ModelInformationDict } from '@lib/enums/ModelInformation';
import { ModelType } from '@lib/enums/ModelType'; import { ModelType } from '@lib/enums/ModelType';
@@ -25,8 +29,11 @@ import type {
export type { InstanceRenderInterface } from '@lib/types/Rendering'; export type { InstanceRenderInterface } from '@lib/types/Rendering';
import { getBaseUrl, navigateToLink, shortenString } from '@lib/index'; import { getBaseUrl, navigateToLink, shortenString } from '@lib/index';
import { IconLink } from '@tabler/icons-react';
import { useNavigate } from 'react-router-dom';
import { useApi } from '../../contexts/ApiContext'; import { useApi } from '../../contexts/ApiContext';
import { usePluginState } from '../../states/PluginState'; import { usePluginState } from '../../states/PluginState';
import { useUserSettingsState } from '../../states/SettingsStates';
import { Thumbnail } from '../images/Thumbnail'; import { Thumbnail } from '../images/Thumbnail';
import { RenderBuildItem, RenderBuildLine, RenderBuildOrder } from './Build'; import { RenderBuildItem, RenderBuildLine, RenderBuildOrder } from './Build';
import { import {
@@ -125,11 +132,95 @@ export function RenderInstance(props: RenderInstanceProps): ReactNode {
props.custom_model ?? props.model ?? '' props.custom_model ?? props.model ?? ''
); );
// provider component const navigate = useNavigate();
if (!RenderComponent) { const userSettings = useUserSettingsState();
return <UnknownRenderer model={props.model} />;
// Extract model information from the defined model type
const modelInfo = useMemo(() => {
if (!props.model) {
return undefined;
} }
return <RenderComponent {...props} />;
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 (!modelInfo || !modelId || !modelInfo.url_detail) {
return undefined;
}
return modelInfo.url_detail.replace(':pk', modelId.toString());
}, [modelInfo, modelId]);
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) => navigateToLink(detailUrl, 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({ export function RenderRemoteInstance({
+22
View File
@@ -124,6 +124,17 @@ export function getActions(navigate: NavigateFunction) {
leftSection: <IconLink size='1.2rem' /> 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') && globalSettings.isSet('RETURNORDER_ENABLED') &&
user?.hasViewRole(UserRoles.return_order) && user?.hasViewRole(UserRoles.return_order) &&
_actions.push({ _actions.push({
@@ -172,6 +183,17 @@ export function getActions(navigate: NavigateFunction) {
}); });
staff && 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({ _actions.push({
id: 'plugin-settings', id: 'plugin-settings',
label: t`Plugins`, label: t`Plugins`,
+19 -8
View File
@@ -16,15 +16,18 @@ import {
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import type { MenuLinkItem } from '../components/items/MenuLinks'; import type { MenuLinkItem } from '../components/items/MenuLinks';
import { useGlobalSettingsState } from '../states/SettingsStates';
type NavTab = { type NavTab = {
name: string; name: string;
title: string; title: string;
icon: ReactNode; icon: ReactNode;
role?: UserRoles; visible?: boolean;
}; };
export function getNavTabs(user: UserStateProps): NavTab[] { export function getNavTabs(user: UserStateProps): NavTab[] {
const globalSettings = useGlobalSettingsState.getState();
const navTabs: NavTab[] = [ const navTabs: NavTab[] = [
{ {
name: 'home', name: 'home',
@@ -35,37 +38,45 @@ export function getNavTabs(user: UserStateProps): NavTab[] {
name: 'part', name: 'part',
title: t`Parts`, title: t`Parts`,
icon: <IconBox />, icon: <IconBox />,
role: UserRoles.part visible:
user.hasViewRole(UserRoles.part) ||
user.hasViewRole(UserRoles.part_category)
}, },
{ {
name: 'stock', name: 'stock',
title: t`Stock`, title: t`Stock`,
icon: <IconPackages />, 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', name: 'manufacturing',
title: t`Manufacturing`, title: t`Manufacturing`,
icon: <IconBuildingFactory2 />, icon: <IconBuildingFactory2 />,
role: UserRoles.build visible: user.hasViewRole(UserRoles.build)
}, },
{ {
name: 'purchasing', name: 'purchasing',
title: t`Purchasing`, title: t`Purchasing`,
icon: <IconShoppingCart />, icon: <IconShoppingCart />,
role: UserRoles.purchase_order visible: user.hasViewRole(UserRoles.purchase_order)
}, },
{ {
name: 'sales', name: 'sales',
title: t`Sales`, title: t`Sales`,
icon: <IconTruckDelivery />, 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) => { return navTabs.filter((tab) => {
if (!tab.role) return true; return tab.visible !== false;
return user.hasViewRole(tab.role);
}); });
} }
+7 -1
View File
@@ -1,7 +1,7 @@
import type { ApiFormFieldSet } from '@lib/types/Forms'; import type { ApiFormFieldSet } from '@lib/types/Forms';
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
import { IconBuildingStore, IconCopy, IconPackages } from '@tabler/icons-react'; import { IconBuildingStore, IconCopy, IconPackages } from '@tabler/icons-react';
import { useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { useGlobalSettingsState } from '../states/SettingsStates'; import { useGlobalSettingsState } from '../states/SettingsStates';
/** /**
@@ -23,6 +23,12 @@ export function usePartFields({
undefined 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(() => { return useMemo(() => {
const fields: ApiFormFieldSet = { const fields: ApiFormFieldSet = {
category: { category: {
+83 -29
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 { t } from '@lingui/core/macro';
import { import {
Alert, Alert,
@@ -20,27 +33,13 @@ import {
IconUsersGroup IconUsersGroup
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { useQuery, useSuspenseQuery } from '@tanstack/react-query'; 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 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 { useNavigate } from 'react-router-dom';
import { api } from '../App'; import { api } from '../App';
import RemoveRowButton from '../components/buttons/RemoveRowButton'; import RemoveRowButton from '../components/buttons/RemoveRowButton';
import { StandaloneField } from '../components/forms/StandaloneField'; 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 { import {
TableFieldExtraRow, TableFieldExtraRow,
type TableFieldRowProps type TableFieldRowProps
@@ -490,12 +489,31 @@ function StockItemDefaultMove({
function moveToDefault( function moveToDefault(
stockItem: any, stockItem: any,
value: StockItemQuantity, 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({ modals.openConfirmModal({
title: <StylishText>{t`Confirm Stock Transfer`}</StylishText>, title: (
<StylishText>{options?.title ?? t`Confirm Stock Transfer`}</StylishText>
),
children: <StockItemDefaultMove stockItem={stockItem} value={value} />, children: <StockItemDefaultMove stockItem={stockItem} value={value} />,
onConfirm: () => { onConfirm: () => {
if (!location) {
return;
}
if (options?.onConfirm) {
options.onConfirm(location);
return;
}
if ( if (
stockItem.location === stockItem.part_detail?.default_location || stockItem.location === stockItem.part_detail?.default_location ||
stockItem.location === stockItem.part_detail?.category_default_location stockItem.location === stockItem.part_detail?.category_default_location
@@ -512,9 +530,7 @@ function moveToDefault(
status: stockItem.status status: stockItem.status
} }
], ],
location: location: location
stockItem.part_detail?.default_location ??
stockItem.part_detail?.category_default_location
}) })
.then((response) => { .then((response) => {
refresh(); refresh();
@@ -548,6 +564,7 @@ function StockOperationsRow({
add = false, add = false,
setMax = false, setMax = false,
merge = false, merge = false,
returnStock = false,
record record
}: { }: {
props: TableFieldRowProps; props: TableFieldRowProps;
@@ -556,8 +573,11 @@ function StockOperationsRow({
add?: boolean; add?: boolean;
setMax?: boolean; setMax?: boolean;
merge?: boolean; merge?: boolean;
returnStock?: boolean;
record?: any; record?: any;
}) { }) {
const form = useFormContext();
const statusOptions: ApiFormFieldChoice[] = useMemo(() => { const statusOptions: ApiFormFieldChoice[] = useMemo(() => {
return ( return (
StatusFilterOptions(ModelType.stockitem)()?.map((choice) => { StatusFilterOptions(ModelType.stockitem)()?.map((choice) => {
@@ -676,7 +696,22 @@ function StockOperationsRow({
{transfer && ( {transfer && (
<ActionButton <ActionButton
onClick={() => 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' />} icon={<InvenTreeIcon icon='default_location' />}
tooltip={t`Move to default location`} tooltip={t`Move to default location`}
@@ -840,6 +875,7 @@ function stockReturnFields(items: any[]): ApiFormFieldSet {
key={record.pk} key={record.pk}
record={record} record={record}
transfer transfer
returnStock
changeStatus changeStatus
/> />
); );
@@ -854,12 +890,25 @@ function stockReturnFields(items: any[]): ApiFormFieldSet {
] ]
}, },
location: { location: {
field_type: 'related field',
api_url: apiUrl(ApiEndpoints.stock_location_list),
model: ModelType.stocklocation,
required: true,
filters: { filters: {
structural: false structural: false
} }
}, },
merge: {}, merge: {
notes: {} 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; return fields;
@@ -967,6 +1016,9 @@ function stockCountFields(items: any[]): ApiFormFieldSet {
const initialValue = mapAdjustmentItems(items); const initialValue = mapAdjustmentItems(items);
// Extract all location values from the items
const locations = [...new Set(items.map((item) => item.location))];
const fields: ApiFormFieldSet = { const fields: ApiFormFieldSet = {
items: { items: {
field_type: 'table', field_type: 'table',
@@ -990,6 +1042,12 @@ function stockCountFields(items: any[]): ApiFormFieldSet {
{ title: t`Actions` } { title: t`Actions` }
] ]
}, },
location: {
value: locations.length === 1 ? locations[0] : undefined,
filters: {
structural: false
}
},
notes: {} notes: {}
}; };
@@ -1584,11 +1642,7 @@ export function useTestResultFields({
/** /**
* Modal form for finding a particular stock item by serial number * Modal form for finding a particular stock item by serial number
*/ */
export function useFindSerialNumberForm({ export function useFindSerialNumberForm({ partId }: { partId: number }) {
partId
}: {
partId: number;
}) {
const navigate = useNavigate(); const navigate = useNavigate();
return useApiFormModal({ return useApiFormModal({
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

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