Merge branch 'master' of https://github.com/inventree/InvenTree into matmair/issue11460

This commit is contained in:
Matthias Mair 2026-06-06 17:46:51 +02:00
commit c6cfc1856c
No known key found for this signature in database
GPG Key ID: A593429DDA23B66A
219 changed files with 35114 additions and 30096 deletions

View File

@ -106,7 +106,7 @@ runs:
- name: Run invoke update
if: ${{ inputs.update == 'true' }}
shell: bash
run: invoke update --skip-backup --skip-static
run: invoke update --skip-backup --skip-static --no-frontend
- name: Collect static files
if: ${{ inputs.static == 'true' }}
shell: bash

View File

@ -168,10 +168,10 @@ jobs:
echo "git_commit_date=$(git show -s --format=%ci)" >> $GITHUB_ENV
- name: Set up QEMU
if: github.event_name != 'pull_request'
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # pin@v4.0.0
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # pin@v4.1.0
- name: Set up Docker Buildx
if: github.event_name != 'pull_request'
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # pin@v4.0.0
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # pin@v4.1.0
- name: Set up cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # pin@v4.1.2
@ -185,14 +185,14 @@ jobs:
fi
- name: Login to Dockerhub
if: github.event_name != 'pull_request' && steps.docker_login.outputs.skip_dockerhub_login != 'true'
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # pin@v4.1.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # pin@v4.2.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log into registry ghcr.io
if: github.event_name != 'pull_request'
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # pin@v4.1.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # pin@v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@ -201,7 +201,7 @@ jobs:
- name: Extract Docker metadata
if: github.event_name != 'pull_request'
id: meta
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # pin@v6.0.0
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # pin@v6.1.0
with:
images: |
inventree/inventree
@ -210,7 +210,7 @@ jobs:
- name: Push Docker Images
id: push-docker
if: github.event_name != 'pull_request'
uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # pin@v1
uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # pin@v1
with:
project: jczzbjkk68
context: .

306
.github/workflows/frontend.yaml vendored Normal file
View File

@ -0,0 +1,306 @@
# Playwright testing for frontend code
# Runs the following tests:
# - Playwright tests in Firefox (against compiled frontend code)
# - Playwright tests in Chromium (coverage enabled, against vite frontend code)
# - Build frontend code and upload as artifact
name: Frontend
on:
push:
branches-ignore: ["l10*"]
pull_request:
branches-ignore: ["l10*"]
env:
python_version: 3.11
node_version: 24
# The OS version must be set per job
server_start_sleep: 60
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INVENTREE_DB_ENGINE: postgresql
INVENTREE_DB_NAME: inventree
INVENTREE_DB_HOST: "127.0.0.1"
INVENTREE_DB_PORT: 5432
INVENTREE_DB_USER: inventree_user
INVENTREE_DB_PASSWORD: inventree_password
INVENTREE_DEBUG: true
INVENTREE_PLUGINS_ENABLED: false
INVENTREE_MEDIA_ROOT: /home/runner/work/InvenTree/test_inventree_media
INVENTREE_STATIC_ROOT: /home/runner/work/InvenTree/test_inventree_static
INVENTREE_BACKUP_DIR: /home/runner/work/InvenTree/test_inventree_backup
INVENTREE_SITE_URL: http://localhost:8000
permissions:
contents: read
jobs:
paths-filter:
name: Filter
runs-on: ubuntu-latest
outputs:
frontend: ${{ steps.filter.outputs.frontend }}
force: ${{ steps.force.outputs.force }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2
with:
persist-credentials: false
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # pin@v4.0.1
id: filter
with:
filters: |
frontend:
- 'src/frontend/**'
- name: Is CI being forced?
run: echo "force=true" >> $GITHUB_OUTPUT
id: force
if: |
contains(github.event.pull_request.labels.*.name, 'dependency') ||
contains(github.event.pull_request.labels.*.name, 'full-run')
build:
name: Build
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2
with:
persist-credentials: false
- name: Environment Setup
uses: ./.github/actions/setup
with:
npm: true
- name: Install dependencies
run: cd src/frontend && yarn install
- name: Build frontend
run: cd src/frontend && yarn run compile && yarn run lib && yarn run build
- name: Write version file - SHA
run: cd src/backend/InvenTree/web/static/web/.vite && echo "$GITHUB_SHA" > sha.txt
- name: Zip frontend
run: |
cd src/backend/InvenTree/web/static
zip -r frontend-build.zip web/ web/.vite
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # pin@v7.0.1
with:
name: frontend-build
path: src/backend/InvenTree/web/static/web
include-hidden-files: true
firefox:
name: Tests [Firefox ${{ matrix.shard }} / 2]
runs-on: ubuntu-24.04
timeout-minutes: 60
needs: ["build", "paths-filter"]
if: github.ref == 'refs/heads/master' || needs.paths-filter.outputs.frontend == 'true' || needs.paths-filter.outputs.force == 'true'
strategy:
fail-fast: false
matrix:
shard: [1, 2]
services:
postgres:
image: postgres:17
env:
POSTGRES_DB: inventree
POSTGRES_USER: inventree_user
POSTGRES_PASSWORD: inventree_password
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U testuser"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
VITE_COVERAGE: false
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2
with:
persist-credentials: false
- name: Environment Setup
uses: ./.github/actions/setup
with:
npm: true
install: true
update: true
apt-dependency: gettext postgresql-client libpq-dev
pip-dependency: psycopg2
- name: Set up test data
run: |
invoke dev.setup-test -iv
invoke int.rebuild-thumbnails
- name: Install dependencies
run: invoke int.frontend-compile --extract
- name: Cache Playwright browsers
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # pin@v4.3.0
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('src/frontend/yarn.lock') }}
- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: cd src/frontend && npx playwright install --with-deps
- name: Install Playwright OS dependencies
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: cd src/frontend && npx playwright install-deps
- name: Install Sample Plugin
run: |
pip install -U inventree-plugin-creator
create-inventree-plugin --default
cd MyCustomPlugin && pip install -e . && cd frontend && npm install && npm run translate && npm run build
- name: Run Playwright tests
id: tests
run: |
cd src/frontend
cp ./tests/fixtures/playwright_custom_logo.png ../backend/InvenTree/InvenTree/static/img/playwright_custom_logo.png
cp ./tests/fixtures/playwright_custom_splash.png ../backend/InvenTree/InvenTree/static/img/playwright_custom_splash.png
invoke static
env INVENTREE_CUSTOM_SPLASH="img/playwright_custom_splash.png" INVENTREE_CUSTOM_LOGO="img/playwright_custom_logo.png" PLAYWRIGHT_BASE_URL=http://localhost:8000 npx playwright test --project=firefox --shard=${{ matrix.shard }}/2
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # pin@v7.0.1
if: ${{ !cancelled() && steps.tests.outcome == 'failure' }}
with:
name: playwright-report-firefox-${{ matrix.shard }}
path: src/frontend/playwright-report/
retention-days: 14
chromium:
name: Tests [Chromium ${{ matrix.shard }} / 4]
runs-on: ubuntu-24.04
timeout-minutes: 60
needs: ["build", "paths-filter"]
if: github.ref == 'refs/heads/master' || needs.paths-filter.outputs.frontend == 'true' || needs.paths-filter.outputs.force == 'true'
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
services:
postgres:
image: postgres:17
env:
POSTGRES_DB: inventree
POSTGRES_USER: inventree_user
POSTGRES_PASSWORD: inventree_password
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U testuser"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
COVERAGE: true
VITE_COVERAGE: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2
with:
persist-credentials: false
- name: Environment Setup
uses: ./.github/actions/setup
with:
npm: true
install: true
update: true
apt-dependency: gettext postgresql-client libpq-dev
pip-dependency: psycopg2
- name: Set up test data
run: |
invoke dev.setup-test -iv
invoke int.rebuild-thumbnails
- name: Install dependencies
run: invoke int.frontend-compile --extract
- name: Cache Playwright browsers
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # pin@v4.3.0
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('src/frontend/yarn.lock') }}
- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: cd src/frontend && npx playwright install --with-deps
- name: Install Playwright OS dependencies
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: cd src/frontend && npx playwright install-deps
- name: Install Sample Plugin
run: |
pip install -U inventree-plugin-creator
create-inventree-plugin --default
cd MyCustomPlugin && pip install -e . && cd frontend && npm install && npm run translate && npm run build
- name: Playwright [${{ matrix.shard }} / 4]
id: tests
run: |
cd src/frontend
npx nyc playwright test --project=chromium --shard=${{ matrix.shard }}/4
- name: Playwright Report [${{ matrix.shard }} / 4]
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # pin@v7.0.1
if: ${{ !cancelled() && steps.tests.outcome == 'failure' }}
with:
name: playwright-report-chromium-${{ matrix.shard }}
path: src/frontend/playwright-report/
if-no-files-found: error
retention-days: 7
- name: Upload Coverage Artifact [${{ matrix.shard }} / 4]
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # pin@v7.0.1
id: coverage-upload
if: ${{ !cancelled() && steps.tests.outcome != 'failure' }}
with:
name: coverage-${{ matrix.shard }}
path: src/frontend/.nyc_output/
if-no-files-found: error
include-hidden-files: true
retention-days: 1
# Recombine the coverage reports from the 4 shards, and upload to Codecov
coverage:
name: Merge coverage reports and upload to Codecov
runs-on: ubuntu-latest
needs: chromium
timeout-minutes: 30
if: always()
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2
with:
persist-credentials: false
- name: Environment Setup
uses: ./.github/actions/setup
with:
npm: true
install: false
update: false
- name: Download Coverage Artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # pin@v8.0.1
with:
pattern: coverage-*
path: all-coverage/
merge-multiple: true
- name: Merge Coverage Reports
run: |
mkdir -p .nyc_output
cp all-coverage/*.json .nyc_output/ 2>/dev/null || true
npx nyc merge .nyc_output merged-coverage.json
npx nyc report \
--tempdir .nyc_output \
--reporter=lcov \
--reporter=text-summary \
--report-dir ./coverage \
- name: Upload coverage reports to Codecov
if: ${{ !cancelled() && github.ref == 'refs/heads/master' }}
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # pin@v6.0.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
slug: inventree/InvenTree
flags: web
files: ./coverage/lcov.info

View File

@ -222,7 +222,7 @@ jobs:
echo "Downloaded api.yaml"
- name: Running OpenAPI Spec diff action
id: breaking_changes
uses: oasdiff/oasdiff-action/diff@6147a58e5d1249a12f42fc864ab791d571a30015 # pin@main
uses: oasdiff/oasdiff-action/diff@50e6a3413e5aa9c3ae4d8393c34745be44288b46 # pin@main
with:
base: "api.yaml"
revision: "src/backend/InvenTree/schema.yml"
@ -664,133 +664,6 @@ jobs:
chmod +rw /home/runner/work/InvenTree/db.sqlite3
invoke migrate
web_ui:
name: Tests - Web UI
runs-on: ubuntu-24.04
timeout-minutes: 60
needs: ["code-style", "paths-filter"]
if: needs.paths-filter.outputs.frontend == 'true' || needs.paths-filter.outputs.force == 'true'
services:
postgres:
image: postgres:17
env:
POSTGRES_DB: inventree
POSTGRES_USER: inventree_user
POSTGRES_PASSWORD: inventree_password
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U testuser"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
INVENTREE_DB_ENGINE: postgresql
INVENTREE_DB_NAME: inventree
INVENTREE_DB_HOST: "127.0.0.1"
INVENTREE_DB_PORT: 5432
INVENTREE_DB_USER: inventree_user
INVENTREE_DB_PASSWORD: inventree_password
INVENTREE_DEBUG: true
INVENTREE_PLUGINS_ENABLED: false
VITE_COVERAGE_BUILD: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2
with:
persist-credentials: false
- name: Environment Setup
uses: ./.github/actions/setup
with:
npm: true
install: true
update: true
apt-dependency: gettext postgresql-client libpq-dev
pip-dependency: psycopg2
- name: Set up test data
run: |
invoke dev.setup-test -iv
invoke int.rebuild-thumbnails
- name: Install dependencies
run: invoke int.frontend-compile --extract
- name: Cache Playwright browsers
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # pin@v4.3.0
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('src/frontend/yarn.lock') }}
- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: cd src/frontend && npx playwright install --with-deps
- name: Install Playwright OS dependencies
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: cd src/frontend && npx playwright install-deps
- name: Install Sample Plugin
run: |
pip install -U inventree-plugin-creator
create-inventree-plugin --default
cd MyCustomPlugin && pip install -e . && cd frontend && npm install && npm run translate && npm run build
- name: Run Playwright tests
id: tests
run: |
cd src/frontend
cp ./tests/fixtures/playwright_custom_logo.png ../backend/InvenTree/InvenTree/static/img/playwright_custom_logo.png
cp ./tests/fixtures/playwright_custom_splash.png ../backend/InvenTree/InvenTree/static/img/playwright_custom_splash.png
invoke static
env INVENTREE_CUSTOM_SPLASH="img/playwright_custom_splash.png" INVENTREE_CUSTOM_LOGO="img/playwright_custom_logo.png" npx nyc playwright test --project=customization
npx nyc playwright test --project=chromium --project=firefox
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # pin@v7.0.1
if: ${{ !cancelled() && steps.tests.outcome == 'failure' }}
with:
name: playwright-report
path: src/frontend/playwright-report/
retention-days: 14
- name: Report coverage
run: cd src/frontend && npx nyc report --report-dir ./coverage --temp-dir .nyc_output --reporter=lcov --exclude-after-remap false
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # pin@v6.0.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
slug: inventree/InvenTree
flags: web
- name: Upload bundler info
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: |
cd src/frontend
yarn install
yarn run build
web_ui_build:
name: Build - Web UI
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2
with:
persist-credentials: false
- name: Environment Setup
uses: ./.github/actions/setup
with:
npm: true
- name: Install dependencies
run: cd src/frontend && yarn install
- name: Build frontend
run: cd src/frontend && yarn run compile && yarn run lib && yarn run build
- name: Write version file - SHA
run: cd src/backend/InvenTree/web/static/web/.vite && echo "$GITHUB_SHA" > sha.txt
- name: Zip frontend
run: |
cd src/backend/InvenTree/web/static
zip -r frontend-build.zip web/ web/.vite
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # pin@v7.0.1
with:
name: frontend-build
path: src/backend/InvenTree/web/static/web
include-hidden-files: true
zizmor:
name: Security [Zizmor]
runs-on: ubuntu-24.04

View File

@ -28,7 +28,7 @@ jobs:
pip install --require-hashes -r contrib/dev_reqs/requirements.txt
python3 .github/scripts/version_check.py
- name: Push to Stable Branch
uses: ad-m/github-push-action@4cc74773234f74829a8c21bc4d69dd4be9cfa599 # pin@v1.1.0
uses: ad-m/github-push-action@881a6320fdb16eb5318c5054f31c218aec2b324c # pin@v1.3.0
if: env.stable_release == 'true'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -67,6 +67,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
with:
sarif_file: results.sarif

View File

@ -16,7 +16,7 @@ jobs:
pull-requests: write
steps:
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # pin@v10.2.0
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # pin@v10.3.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: "This issue seems stale. Please react to show this is still important."

View File

@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- [#12093](https://github.com/inventree/InvenTree/pull/12093) adds "read_only" attribute to PluginSetting API endpoint, which indicates whether a particular plugin setting is read-only (i.e. cannot be modified via the API)
- [#12079](https://github.com/inventree/InvenTree/pull/12079) adds the ability to save filter groups for table and calendar views in the user interface. This allows users to save and reuse commonly used filter configurations, improving the usability and efficiency of the interface.
- [#12077](https://github.com/inventree/InvenTree/pull/12077) adds "tags" fields to multiple new model types and a /api/tag/ endpoint for fetching tags. Also adds the ability to filter various model types by tags.
- [#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.
- [#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.
@ -37,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed
- [#12071](https://github.com/inventree/InvenTree/pull/12071) removes the "review_needed" field on the StockItem model. Note that this field was never exposed to the API or any business logic, and is essentially a no-op field which is not used for anything. This field was removed to simplify the StockItem model and reduce confusion around its purpose.
- [#11962](https://github.com/inventree/InvenTree/pull/11962) removes the "remote_image" field from the Part API endpoint, which (previously) allowed the user to specify a remote URL for an image to be downloaded and associated with the part. This field was removed due to security concerns around downloading images from arbitrary URLs. If you were using this field in an external client application, you will need to update your application to use the new "download_image_from_url" API endpoint instead.
## 1.3.0 - 2026-04-11

View File

@ -43,8 +43,10 @@ component_management:
name: Backend Apps
paths:
- src/backend/InvenTree/build/**
- src/backend/InvenTree/common/**
- src/backend/InvenTree/company/**
- src/backend/InvenTree/data_exporter/**
- src/backend/InvenTree/generic/**
- src/backend/InvenTree/importer/**
- src/backend/InvenTree/machine/**
- src/backend/InvenTree/order/**
@ -70,7 +72,8 @@ component_management:
- component_id: web
name: Frontend
paths:
- src/frontend/**
- src/frontend/lib/**
- src/frontend/src/**
statuses:
- type: project
target: 68% # 90%

View File

@ -6,9 +6,9 @@ asgiref==3.11.1 \
# via
# -c src/backend/requirements.txt
# django
django==5.2.14 \
--hash=sha256:58a63ba841662e5c686b57ba1fec52ddd68c0b93bd96ac3029d55728f00bf8a2 \
--hash=sha256:6f712143bd3064310d1f50fac859c3e9a274bdcfc9595339853be7779297fc76
django==5.2.15 \
--hash=sha256:0eb4a9bb1853a35b0286dbc6d916bd352c8c2687195a7f2d6f80cefd840e4970 \
--hash=sha256:5154a9bf84ac01dde011e367f355c07dbb329532e06810dcf3ef2af269e236e7
# via
# -c src/backend/requirements.txt
# -r contrib/container/requirements.in

View File

@ -139,9 +139,9 @@ charset-normalizer==3.4.7 \
# via
# -c src/backend/requirements.txt
# requests
idna==3.16 \
--hash=sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5 \
--hash=sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d
idna==3.17 \
--hash=sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c \
--hash=sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f
# via
# -c src/backend/requirements.txt
# requests

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -0,0 +1,82 @@
---
title: Tags
---
## Tags
*Tags* are short, arbitrary labels that can be attached to InvenTree objects to group or classify them in flexible ways that don't require changes to the underlying data model. Unlike [parameters](./parameters.md), tags carry no typed value — they are simply names. A tag can be applied to objects of any supported model type, and tags are shared across the entire InvenTree instance.
!!! note "Shared Tag Namespace"
Tags are global: a tag named `prototype` applied to a Part and the same tag applied to a Build Order refer to the same underlying tag record. Renaming or deleting a tag affects every object to which it is attached.
### Supported Models
Tags can be attached to the following InvenTree objects:
- [Parts](../part/index.md)
- [Supplier Parts](../purchasing/supplier.md#supplier-parts)
- [Manufacturer Parts](../purchasing/manufacturer.md#manufacturer-parts)
- [Companies](./company.md)
- [Stock Items](../stock/index.md#stock-item)
- [Stock Locations](../stock/index.md#stock-location)
- [Build Orders](../manufacturing/build.md)
- [Purchase Orders](../purchasing/purchase_order.md)
- [Sales Orders](../sales/sales_order.md)
- [Return Orders](../sales/return_order.md)
- [Sales Order Shipments](../sales/sales_order.md#shipments)
## Managing Tags
### Adding and Removing Tags
Any object that supports tags will expose a *Tags* field in its detail and edit forms. Tags are entered as a comma-separated list of names and can be freely added or removed at any time. Tag names are case-insensitive — `Prototype`, `prototype`, and `PROTOTYPE` all refer to the same tag.
### Tag Names
Tag names must be unique within the InvenTree instance (case-insensitively). If you type a name that already exists under a different capitalisation, the existing tag is assigned rather than a new one created. Tag names may contain spaces, but leading and trailing whitespace is stripped automatically.
## Filtering by Tags
Tables that support tags can be filtered by one or more tag names. When multiple tags are specified, only objects that carry **all** of the specified tags are returned (AND logic).
For example, filtering a Parts table by the tags `approved` and `prototype` returns only parts tagged with both.
## API Access
### Tag Endpoints
The tag list is available at `/api/tag/`. Individual tags can be retrieved, updated, or deleted at `/api/tag/<id>/`.
The `model_type` query parameter narrows the tag list to tags currently applied to a specific model type:
```
GET /api/tag/?model_type=part
```
### Tags on Model Endpoints
For models that support tags, the `tags` field is returned in the detail endpoint response as a list of tag name strings:
```json
{
"pk": 42,
"name": "Widget",
"tags": ["approved", "prototype"]
}
```
Tags can be updated via a `PATCH` or `POST` request by supplying a JSON-encoded list of tag name strings. The full list of tags replaces the previous set — omitting a tag removes it:
```json
{
"tags": ["approved", "production"]
}
```
Tags can also be used as a filter parameter on list endpoints. Supply a comma-separated list of tag names to the `tags` query parameter:
```
GET /api/part/?tags=approved,prototype
```
This returns only parts tagged with **both** `approved` and `prototype`.

View File

@ -158,6 +158,24 @@ Select the "table filters" button to open the filter selection menu
Table filters are saved across browser sessions, allowing users to maintain their preferred filter settings when returning to the particular table view.
#### Saved Filter Groups
Frequently used combinations of filters can be saved as a named *filter group*, allowing them to be quickly recalled later without having to re-add each filter individually.
The **Saved Filter Groups** panel is displayed at the bottom of the filter drawer. When one or more filters are active, a **Save current filters** button is available. Clicking it opens an inline name input — enter a name and press Enter (or click the confirm icon) to save the group. Press Escape or click the cancel icon to discard.
{{ image("concepts/ui_table_filter_group.png", "Filter Groups") }}
Previously saved filter groups are listed in the panel. Each entry shows the group name alongside two actions:
- **Load** (green reload icon): Replaces the current active filters with the filters stored in that group. The table immediately re-fetches data using the restored filters.
- **Delete** (red × icon): Permanently removes the saved filter group.
Saved filter groups are stored in the browser's local storage and are specific to each table or calendar view, so groups saved for one view are not available in another. They persist across local browser sessions until explicitly deleted. Filter groups are not shared to other devices.
!!! info "Loading a filter group replaces active filters"
Loading a saved filter group replaces all currently active filters with those stored in the group. Any unsaved active filters will be overwritten.
### Data Sorting
Some table columns support data sorting, allowing the dataset to be sorted in ascending or descending order based on the values in that column. To sort a column, click on the column header. Clicking the column header again will toggle the sort order between ascending and descending. The current sort order is indicated by an arrow icon in the column header.
@ -190,7 +208,7 @@ For tables which reference other objects within the system, clicking on a row wi
## Calendar Views
Some [table views](#table-views) can be switched to a calendar view, which provides a visual representation of data based on date fields. The calendar view allows users to easily see and interact with data that is organized by date, such as scheduled tasks, events, or deadlines.
Some [table views](#table-views) associated with various order types can be switched to a calendar view, which provides a visual representation of data based on date fields. The calendar view allows users to easily see and interact with data that is organized by date, such as scheduled tasks, events, or deadlines.
To switch to the "calendar view" (for a table which supports it), click on the "calendar view" button located above and to the right of the table view:
@ -200,6 +218,10 @@ This will display the data in a calendar format:
{{ image("concepts/ui_calendar_view.png", "Calendar View") }}
### Calendar Horizon
The calendar view provides a configurable "horizon" setting, which allows users to adjust the number of months displayed in the calendar view.
## Parametric Views
Some [table views](#table-views) can be switched to a parametric view, which provides a visual representation of data based on specific parameters or attributes. The parametric view allows users to easily see and interact with data that is organized by certain characteristics, such as categories, types, or other relevant attributes.

View File

@ -60,15 +60,17 @@ invoke dev.setup-dev
InvenTree roughly follow the [GitLab flow](https://about.gitlab.com/topics/version-control/what-are-gitlab-flow-best-practices/) branching style, to allow simple management of multiple tagged releases, short-lived branches, and development on the main branch.
There are nominally 5 active branches:
- `master` - The main development branch
- `stable` - The latest stable release
- `next-breaking` - The next breaking release (e.g. 2.0, 3.0) with all deprecated features removed
- `l10n` - Translation branch: Source to Crowdin
- `l10_crowdin` - Translation branch: Source from Crowdin
- `y.y.x` - Release branch for the currently supported version (e.g. `0.5.x`)
All other branches are removed periodically by maintainers or core team members. This includes old release branches.
Do not use them as base for feature development or forks as patches from them might not be accepted without rebasing.
| Branch | Description |
| --- | --- |
| `master` | The main development branch |
| `stable` | The latest stable release |
| `next-breaking` | The next breaking release (e.g. 2.0, 3.0) with all deprecated features removed |
| `l10n` | Translation branch: Source to Crowdin |
| `l10_crowdin` | Translation branch: Source from Crowdin |
| `y.y.x` | Release branch for the currently supported version (e.g. `0.5.x`) |
All other branches are removed periodically by maintainers or core team members. This includes old release branches. Do not use them as base for feature development or forks as patches from them might not be accepted without rebasing.
### Version Numbering

View File

@ -35,7 +35,7 @@ If the installed version of invoke is too old, users may see error messages duri
Make sure you are running a stable or production release of InvenTree. The frontend panel is not included in development releases.
More Information: [Error Codes - INVE-E1](./settings/error_codes.md#inve-e1)
### No module named <xxx>
### No module named xyz
During the install or update process, you may be presented with an error like:

View File

@ -214,10 +214,10 @@ The frontend code for your plugin is located in the `frontend/src` directory. Yo
Refer to the `./frontend/src/Panel.tsx` file as a starting point. This is where the custom panel for the part detail page is implemented. You can modify this file to change the content and behavior of the panel.
While the `npm dev` server is running, any changes you make to the frontend code will be automatically reloaded allowing for rapid development and testing of your plugin's frontend features. This avoids the need to rebuild the frontend code every time you make a change.
While the `npm dev` server is running, any changes to the frontend are reflected in the browser using React Fast Refresh, allowing for rapid development without rebuilding the frontend with every change.
!!! info "Page Reload"
Due to the way the InvenTree frontend is structured, you will need to manually refresh the page in your browser to see changes to the frontend code. The development server will automatically reload the frontend code, but the InvenTree server needs to be aware of the changes.
All exports in plugin modules that export React components must start with a capital letter. Otherwise, React Fast Refresh will fall back to a full page reload instead of performing a component-level update. Additionally, any render functions referenced from Python must also be capitalized.
## Build Plugin

View File

@ -119,7 +119,7 @@ function AttachmentCarouselPanel({context}: {context: InvenTreePluginContext;})
}
// This is the function which is called by InvenTree to render the actual panel component
export function renderAttachmentCarouselPanel(context: InvenTreePluginContext) {
export function RenderAttachmentCarouselPanel(context: InvenTreePluginContext) {
checkPluginVersion(context);
return <AttachmentCarouselPanel context={context} />;
}
@ -300,7 +300,7 @@ Back to the walkthrough, open `core.py` in the `attachment_carousel` folder and
'title': 'Attachment Carousel',
'description': 'Custom panel description',
'icon': 'ti:carousel-horizontal:outline',
'source': self.plugin_static_file('Panel.js:renderAttachmentCarouselPanel'),
'source': self.plugin_static_file('Panel.js:RenderAttachmentCarouselPanel'),
'context': {
# Provide additional context data to the panel
'settings': self.get_settings_dict(),
@ -385,7 +385,7 @@ panels.append({
'description': 'Custom panel description',
- 'icon': 'ti:mood-smile:outline',
+ 'icon': 'ti:carousel-horizontal:outline',
'source': self.plugin_static_file('Panel.js:renderAttachmentCarouselPanel'),
'source': self.plugin_static_file('Panel.js:RenderAttachmentCarouselPanel'),
'context': {
# Provide additional context data to the panel
'settings': self.get_settings_dict(),
@ -519,7 +519,7 @@ panels.append({
'title': 'Attachment Carousel',
'description': 'Custom panel description',
'icon': 'ti:carousel-horizontal:outline',
'source': self.plugin_static_file('Panel.js:renderAttachmentCarouselPanel'),
'source': self.plugin_static_file('Panel.js:RenderAttachmentCarouselPanel'),
'context': {
# Provide additional context data to the panel
'settings': self.get_settings_dict(),

View File

@ -33,6 +33,7 @@ Configuration of basic server settings:
{{ globalsetting("DISPLAY_FULL_NAMES") }}
{{ globalsetting("DISPLAY_PROFILE_INFO") }}
{{ globalsetting("WEEK_STARTS_ON") }}
{{ globalsetting("CALENDAR_HORIZON_MONTHS") }}
{{ globalsetting("INVENTREE_UPLOAD_MAX_SIZE") }}
{{ globalsetting("INVENTREE_STRICT_URLS") }}

View File

@ -555,6 +555,15 @@ To override global settings, provide a "dictionary" of settings overrides in the
{{ configtable() }}
{{ configsetting("INVENTREE_GLOBAL_SETTINGS") }} JSON object containing global settings overrides |
## Override Plugin Settings
If you have plugins installed which require configuration, you can provide plugin settings overrides in the configuration file, or via an environment variable.
{{ configtable() }}
{{ configsetting("INVENTREE_PLUGIN_SETTINGS") }} JSON object containing plugin settings overrides |
Note that plugin settings overrides require knowledge of the plugin "slug" and the particular settings which are being overridden. You should refer to the plugin documentation for more information on available settings. If plugin overrides are specified, but no matching plugin is found, the overrides will be ignored.
## Other Settings
Other available settings, not categorized above, are detailed in the table below:

View File

@ -102,6 +102,7 @@ nav:
- Project Codes: concepts/project_codes.md
- Attachments: concepts/attachments.md
- Parameters: concepts/parameters.md
- Tags: concepts/tags.md
- Barcodes:
- Barcode Support: barcodes/index.md
- Internal Barcodes: barcodes/internal.md

View File

@ -227,9 +227,9 @@ httpx==0.28.1 \
--hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \
--hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad
# via neoteroi-mkdocs
idna==3.16 \
--hash=sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5 \
--hash=sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d
idna==3.17 \
--hash=sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c \
--hash=sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f
# via
# -c src/backend/requirements.txt
# anyio
@ -463,9 +463,9 @@ pathspec==1.0.4 \
# mkdocs
# mkdocs-macros-plugin
# properdocs
platformdirs==4.9.6 \
--hash=sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a \
--hash=sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917
platformdirs==4.10.0 \
--hash=sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7 \
--hash=sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a
# via
# -c src/backend/requirements.txt
# mkdocs-get-deps

View File

@ -1,11 +1,19 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 499
INVENTREE_API_VERSION = 501
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v501 -> 2026-06-05 : https://github.com/inventree/InvenTree/pull/12093
- Adds "read_only" attribute to PluginSetting API endpoint, which indicates whether a particular plugin setting is read-only (i.e. cannot be modified via the API)
v500 -> 2026-06-03 : https://github.com/inventree/InvenTree/pull/12077
- Adds "tags" fields to multiple new model types
- Adds /api/tag/ endpoint for fetching tags
- Enable filtering various model types by tags
v499 -> 2026-06-01 : https://github.com/inventree/InvenTree/pull/12057
- Fixes search field issues on the BarcodeScanHistory API endpoint

View File

@ -114,6 +114,9 @@ def reload_unit_registry():
reg.define('@alias degF = Fahrenheit')
reg.define('@alias degK = Kelvin')
# Override R as ohm (pint defines R as an SI prefix by default)
reg.define('R = ohm')
# Define some "standard" additional units
reg.define('piece = 1')
reg.define('each = 1 = ea')

View File

@ -24,6 +24,7 @@ from error_report.models import Error
from mptt.exceptions import InvalidMove
from mptt.models import MPTTModel, TreeForeignKey
from stdimage.models import StdImageField
from taggit.managers import TaggableManager
import common.settings
import InvenTree.exceptions
@ -1250,6 +1251,25 @@ class InvenTreeNotesMixin(models.Model):
)
class InvenTreeTagsMixin(models.Model):
"""A mixin class for adding tag functionality to a model class.
The following fields are added to any model which implements this mixin:
- tags : A text field for storing comma-separated tags
"""
class Meta:
"""Metaclass options for this mixin.
Note: abstract must be true, as this is only a mixin, not a separate table
"""
abstract = True
tags = TaggableManager(blank=True)
class InvenTreeBarcodeMixin(models.Model):
"""A mixin class for adding barcode functionality to a model class.

View File

@ -699,10 +699,6 @@ class InvenTreeTaggitSerializer(TaggitSerializer):
return self._save_tags(tag_object, to_be_tagged)
class InvenTreeTagModelSerializer(InvenTreeTaggitSerializer, InvenTreeModelSerializer):
"""Combination of InvenTreeTaggitSerializer and InvenTreeModelSerializer."""
class InvenTreeAttachmentSerializerField(serializers.FileField):
"""Override the DRF native FileField serializer, to remove the leading server path.

View File

@ -1100,6 +1100,21 @@ if len(GLOBAL_SETTINGS_OVERRIDES) > 0:
# Set the global setting
logger.debug('- Override value for %s = ********', key)
# Plugin settings overrides
# If provided, these values will override any plugin settings (and prevent them from being changed)
# Specified as a nested dict: {plugin_slug: {SETTING_KEY: value}}
PLUGIN_SETTING_OVERRIDES = get_setting(
'INVENTREE_PLUGIN_SETTINGS', 'plugin_settings', typecast=dict
)
if len(PLUGIN_SETTING_OVERRIDES) > 0:
logger.info(
'INVE-I1: Plugin settings overrides: %s', list(PLUGIN_SETTING_OVERRIDES.keys())
)
for slug, overrides in PLUGIN_SETTING_OVERRIDES.items():
for key in overrides:
logger.debug('- Override value for %s.%s = ********', slug, key)
# User interface customization values
CUSTOM_LOGO = get_setting('INVENTREE_CUSTOM_LOGO', 'customize.logo', typecast=str)
@ -1169,3 +1184,6 @@ if 'dbbackup' not in STORAGES:
if _media:
MEDIA_URL = _media
PRESIGNED_URL_EXPIRATION = 600
# Taggit settings
TAGGIT_CASE_INSENSITIVE = True

View File

@ -293,6 +293,16 @@ class ConversionTest(TestCase):
)
self.assertAlmostEqual(output, expected, 12)
# Test that 'R' is interpreted as ohms
# Ref: https://github.com/inventree/InvenTree/issues/12063
r_tests = [('8R6', 8.6), ('10R', 10), ('4R7', 4.7), ('100R', 100)]
for val, expected in r_tests:
output = InvenTree.conversion.convert_physical_value(
val, 'ohm', strip_units=True
)
self.assertAlmostEqual(output, expected, 6)
def test_scientific_notation(self):
"""Test that scientific notation is handled correctly."""
tests = [

View File

@ -16,6 +16,7 @@ from rest_framework.response import Response
import build.models as build_models
import build.serializers
import common.filters
import common.models
import common.serializers
import part.models as part_models
@ -307,6 +308,8 @@ class BuildFilter(FilterSet):
return queryset
tags = common.filters.TagsFilter()
class BuildMixin:
"""Mixin class for Build API endpoints."""

View File

@ -0,0 +1,29 @@
# Generated by Django 5.2.14 on 2026-06-03 10:07
import taggit.managers
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("build", "0058_buildline_consumed"),
(
"taggit",
"0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx",
),
]
operations = [
migrations.AddField(
model_name="build",
name="tags",
field=taggit.managers.TaggableManager(
blank=True,
help_text="A comma-separated list of tags.",
through="taggit.TaggedItem",
to="taggit.Tag",
verbose_name="Tags",
),
),
]

View File

@ -79,6 +79,7 @@ class Build(
InvenTree.models.InvenTreeParameterMixin,
InvenTree.models.InvenTreeAttachmentMixin,
InvenTree.models.InvenTreeBarcodeMixin,
InvenTree.models.InvenTreeTagsMixin,
InvenTree.models.InvenTreeNotesMixin,
InvenTree.models.ReferenceIndexingMixin,
StateTransitionMixin,

View File

@ -36,6 +36,7 @@ from InvenTree.serializers import (
FilterableSerializerMixin,
InvenTreeDecimalField,
InvenTreeModelSerializer,
InvenTreeTaggitSerializer,
NotesFieldMixin,
OptionalField,
)
@ -57,6 +58,7 @@ class BuildSerializer(
CustomStatusSerializerMixin,
FilterableSerializerMixin,
NotesFieldMixin,
InvenTreeTaggitSerializer,
DataImportExportSerializerMixin,
InvenTreeCustomStatusSerializerMixin,
InvenTreeModelSerializer,
@ -103,6 +105,7 @@ class BuildSerializer(
'parameters',
'priority',
'level',
'tags',
]
read_only_fields = [
'completed',
@ -127,6 +130,8 @@ class BuildSerializer(
parameters = common.filters.enable_parameters_filter()
tags = common.filters.enable_tags_filter()
part_name = serializers.CharField(
source='part.name', read_only=True, label=_('Part Name')
)

View File

@ -36,6 +36,7 @@ from rest_framework.permissions import IsAdminUser, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from sql_util.utils import SubqueryCount
from taggit.models import Tag
import common.filters
import common.models
@ -504,6 +505,48 @@ class ProjectCodeDetail(RetrieveUpdateDestroyAPI):
permission_classes = [IsStaffOrReadOnlyScope]
class TagFilter(FilterSet):
"""Custom filters for the TagList API endpoint."""
class Meta:
"""Metaclass options for the filterset."""
model = Tag
fields = []
model_type = rest_filters.CharFilter(method='filter_model_type', label='Model Type')
def filter_model_type(self, queryset, name, value):
"""Filter to tags which have been applied to the given model type."""
ct = common.filters.determine_content_type(value)
if ct is None:
raise ValidationError({'model_type': f'Invalid model type: {value}'})
return queryset.filter(taggit_taggeditem_items__content_type=ct).distinct()
class TagMixin:
"""Mixin class for Tag views."""
serializer_class = common.serializers.TagSerializer
queryset = Tag.objects.all()
permission_classes = [IsStaffOrReadOnlyScope]
class TagList(TagMixin, ListCreateAPI):
"""List view for all tags."""
filterset_class = TagFilter
filter_backends = SEARCH_ORDER_FILTER
ordering_fields = ['name']
search_fields = ['name']
class TagDetail(TagMixin, RetrieveUpdateDestroyAPI):
"""Detail view for a particular tag."""
class CustomUnitViewset(DataExportViewMixin, viewsets.ModelViewSet):
"""List view for custom units."""
@ -745,6 +788,8 @@ class AttachmentFilter(FilterSet):
return queryset.exclude(attachment=None).exclude(attachment='')
return queryset.filter(Q(attachment=None) | Q(attachment='')).distinct()
tags = common.filters.TagsFilter()
class AttachmentMixin:
"""Mixin class for Attachment views."""
@ -1554,6 +1599,14 @@ common_api_urls = [
path('', ProjectCodeList.as_view(), name='api-project-code-list'),
]),
),
# Tags (via django-taggit)
path(
'tag/',
include([
path('<int:pk>/', TagDetail.as_view(), name='api-tag-detail'),
path('', TagList.as_view(), name='api-tag-list'),
]),
),
# Flags
path(
'flags/',

View File

@ -19,6 +19,7 @@ from django.db.models import (
from django.db.models.query import QuerySet
from django.utils.translation import gettext_lazy as _
import django_filters.rest_framework.filters as rest_filters
from rest_framework import serializers
from taggit.serializers import TagListSerializerField
@ -93,6 +94,36 @@ def filter_content_type(
return queryset.filter(q)
class TagsFilter(rest_filters.CharFilter):
"""Filter which accepts a comma-separated list of tag names and returns only objects that have ALL of the specified tags.
Example usage in a FilterSet:
tags = TagsFilter(label=_('Tags'))
Example query:
?tags=apple,banana returns only items tagged with both 'apple' AND 'banana'
"""
def __init__(self, *args, **kwargs):
"""Initialize the filter."""
if 'label' not in kwargs:
kwargs['label'] = _('Tags')
super().__init__(*args, **kwargs)
def filter(self, qs, value):
"""Filter queryset to items matching all provided tag names."""
if not value:
return qs
tag_names = [t.strip() for t in value.split(',') if t.strip()]
for tag in tag_names:
qs = qs.filter(tags__name__iexact=tag)
return qs.distinct()
"""A list of valid operators for filtering part parameters."""
PARAMETER_FILTER_OPERATORS: list[str] = ['gt', 'gte', 'lt', 'lte', 'ne', 'icontains']

View File

@ -50,7 +50,6 @@ from djmoney.contrib.exchange.models import convert_money
from opentelemetry import trace
from PIL import Image
from rest_framework.exceptions import PermissionDenied
from taggit.managers import TaggableManager
import common.validators
import InvenTree.conversion
@ -1923,7 +1922,11 @@ def rename_attachment(instance, filename: str):
)
class Attachment(InvenTree.models.MetadataMixin, InvenTree.models.InvenTreeModel):
class Attachment(
InvenTree.models.MetadataMixin,
InvenTree.models.InvenTreeTagsMixin,
InvenTree.models.InvenTreeModel,
):
"""Class which represents an uploaded file attachment.
An attachment can be either an uploaded file, or an external URL.
@ -2156,8 +2159,6 @@ class Attachment(InvenTree.models.MetadataMixin, InvenTree.models.InvenTreeModel
default=0, verbose_name=_('File size'), help_text=_('File size in bytes')
)
tags = TaggableManager(blank=True)
@property
def basename(self):
"""Base name/path for attachment."""

View File

@ -12,6 +12,7 @@ from error_report.models import Error
from flags.state import flag_state
from rest_framework import serializers
from rest_framework.exceptions import PermissionDenied
from taggit.models import Tag
import common.filters
import common.models as common_models
@ -28,6 +29,7 @@ from InvenTree.serializers import (
InvenTreeAttachmentSerializerField,
InvenTreeImageSerializerField,
InvenTreeModelSerializer,
InvenTreeTaggitSerializer,
OptionalField,
)
from plugin import registry as plugin_registry
@ -422,6 +424,29 @@ class ProjectCodeSerializer(DataImportExportSerializerMixin, InvenTreeModelSeria
)
@register_importer()
class TagSerializer(DataImportExportSerializerMixin, InvenTreeModelSerializer):
"""Serializer for the Tag model."""
class Meta:
"""Meta options for TagSerializer."""
model = Tag
fields = ['pk', 'name', 'slug']
read_only_fields = ['pk', 'slug']
def validate(self, data):
"""Slugify the received name to generate the slug."""
from django.utils.text import slugify
name = data.get('name', None)
if name is not None:
data['slug'] = slugify(name)
return data
@register_importer()
class CustomStateSerializer(DataImportExportSerializerMixin, InvenTreeModelSerializer):
"""Serializer for the custom state model."""
@ -720,7 +745,9 @@ class FailedTaskSerializer(InvenTreeModelSerializer):
result = serializers.CharField()
class AttachmentSerializer(FilterableSerializerMixin, InvenTreeModelSerializer):
class AttachmentSerializer(
FilterableSerializerMixin, InvenTreeTaggitSerializer, InvenTreeModelSerializer
):
"""Serializer class for the Attachment model."""
class Meta:

View File

@ -1243,6 +1243,15 @@ SYSTEM_SETTINGS: dict[str, InvenTreeSettingsKeyType] = {
('6', _('Saturday')),
],
},
'CALENDAR_HORIZON_MONTHS': {
'name': _('Calendar Horizon'),
'description': _(
'Number of months into the future to display in calendar views'
),
'default': 12,
'validator': [int, MinValueValidator(1)],
'units': _('months'),
},
'TEST_STATION_DATA': {
'name': _('Enable Test Station Data'),
'description': _('Enable test station data collection for test results'),

View File

@ -13,6 +13,19 @@ import InvenTree.ready
logger = structlog.get_logger('inventree')
def plugin_setting_overrides(slug: str) -> dict:
"""Return a dictionary of plugin setting overrides for the given plugin slug.
These values are set via environment variables or configuration file.
"""
from django.conf import settings
if hasattr(settings, 'PLUGIN_SETTING_OVERRIDES'):
return settings.PLUGIN_SETTING_OVERRIDES.get(slug, {}) or {}
return {}
def global_setting_overrides() -> dict:
"""Return a dictionary of global settings overrides.

View File

@ -8,6 +8,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
from django.urls import reverse
from PIL import Image
from taggit.models import Tag
import common.models
from InvenTree.unit_test import InvenTreeAPITestCase
@ -999,3 +1000,170 @@ class AttachmentThumbnailAPITests(InvenTreeAPITestCase):
set_global_setting(
'INVENTREE_UPLOAD_MAX_SIZE', original_limit, change_user=None
)
class TagAPITests(InvenTreeAPITestCase):
"""Tests for the Tag API endpoints and tag-based filtering."""
roles = 'all'
LIST_URL = 'api-tag-list'
DETAIL_URL = 'api-tag-detail'
def setUp(self):
"""Create a small set of tagged objects for filter testing."""
super().setUp()
from part.models import Part
self.part_a = Part.objects.create(
name='Tagged Part A', description='Part with apple and banana tags'
)
self.part_b = Part.objects.create(
name='Tagged Part B', description='Part with apple tag only'
)
self.part_c = Part.objects.create(
name='Untagged Part C', description='Part with no tags'
)
self.part_a.tags.add('apple', 'banana')
self.part_b.tags.add('apple')
# ------------------------------------------------------------------
# Tag list / CRUD
# ------------------------------------------------------------------
def test_tag_list(self):
"""Tag list endpoint should return all existing tags."""
url = reverse(self.LIST_URL)
response = self.get(url)
names = {t['name'] for t in response.data}
self.assertIn('apple', names)
self.assertIn('banana', names)
def test_tag_create(self):
"""Staff users should be able to create tags via POST."""
url = reverse(self.LIST_URL)
n = Tag.objects.count()
response = self.post(url, {'name': 'cherry'}, expected_code=201)
self.assertEqual(response.data['name'], 'cherry')
self.assertEqual(Tag.objects.count(), n + 1)
def test_tag_create_non_staff(self):
"""Non-staff users must not be able to create tags."""
self.user.is_staff = False
self.user.save()
url = reverse(self.LIST_URL)
self.post(url, {'name': 'forbidden'}, expected_code=403)
def test_tag_edit(self):
"""Staff users should be able to rename a tag via PATCH."""
tag = Tag.objects.get(name='banana')
url = reverse(self.DETAIL_URL, kwargs={'pk': tag.pk})
response = self.patch(url, {'name': 'blueberry'}, expected_code=200)
self.assertEqual(response.data['name'], 'blueberry')
tag.refresh_from_db()
self.assertEqual(tag.name, 'blueberry')
def test_tag_delete(self):
"""Staff users should be able to delete a tag."""
tag = Tag.objects.get(name='banana')
url = reverse(self.DETAIL_URL, kwargs={'pk': tag.pk})
self.delete(url, expected_code=204)
self.assertFalse(Tag.objects.filter(name='banana').exists())
def test_tag_search(self):
"""The list endpoint should support free-text search."""
url = reverse(self.LIST_URL)
response = self.get(url, data={'search': 'app'})
names = [t['name'] for t in response.data]
self.assertIn('apple', names)
self.assertNotIn('banana', names)
# ------------------------------------------------------------------
# Filter by model type
# ------------------------------------------------------------------
def test_tag_filter_model_type(self):
"""Tags applied to a given model type should be returned when filtering by model_type."""
url = reverse(self.LIST_URL)
# Filter for tags applied to Part objects
response = self.get(url, data={'model_type': 'part.part'})
names = {t['name'] for t in response.data}
self.assertIn('apple', names)
self.assertIn('banana', names)
def test_tag_filter_model_type_unrelated(self):
"""Filtering by a model type that has no tagged objects should return an empty list."""
url = reverse(self.LIST_URL)
# StockItem has no tagged objects in this test
response = self.get(url, data={'model_type': 'stock.stockitem'})
self.assertEqual(len(response.data), 0)
def test_tag_filter_model_type_invalid(self):
"""An unrecognised model_type value should return a 400 error."""
url = reverse(self.LIST_URL)
self.get(url, data={'model_type': 'notanapp.notamodel'}, expected_code=400)
# ------------------------------------------------------------------
# Filter Part list by tags
# ------------------------------------------------------------------
def test_part_filter_single_tag(self):
"""Filtering parts by a single tag should return only parts with that tag."""
url = reverse('api-part-list')
response = self.get(url, data={'tags': 'apple'})
pks = {p['pk'] for p in response.data}
self.assertIn(self.part_a.pk, pks)
self.assertIn(self.part_b.pk, pks)
self.assertNotIn(self.part_c.pk, pks)
def test_part_filter_multiple_tags_and(self):
"""Filtering by comma-separated tags should return only parts that have ALL tags."""
url = reverse('api-part-list')
response = self.get(url, data={'tags': 'apple,banana'})
pks = {p['pk'] for p in response.data}
self.assertIn(self.part_a.pk, pks)
self.assertNotIn(self.part_b.pk, pks) # only has 'apple'
self.assertNotIn(self.part_c.pk, pks) # no tags at all
def test_part_filter_tag_case_insensitive(self):
"""Tag filtering should be case-insensitive."""
url = reverse('api-part-list')
response = self.get(url, data={'tags': 'APPLE'})
pks = {p['pk'] for p in response.data}
self.assertIn(self.part_a.pk, pks)
self.assertIn(self.part_b.pk, pks)
def test_part_filter_nonexistent_tag(self):
"""Filtering by a tag that no part has should return an empty result set."""
url = reverse('api-part-list')
response = self.get(url, data={'tags': 'doesnotexist'})
self.assertEqual(len(response.data), 0)
def test_part_filter_tag_whitespace(self):
"""Whitespace around comma-separated tag names should be ignored."""
url = reverse('api-part-list')
response = self.get(url, data={'tags': ' apple , banana '})
pks = {p['pk'] for p in response.data}
self.assertIn(self.part_a.pk, pks)
self.assertNotIn(self.part_b.pk, pks)

View File

@ -7,6 +7,7 @@ from django.utils.translation import gettext_lazy as _
import django_filters.rest_framework.filters as rest_filters
from django_filters.rest_framework.filterset import FilterSet
import common.filters
import part.models
from data_exporter.mixins import DataExportViewMixin
from InvenTree.api import ListCreateDestroyAPIView, ParameterListMixin, meta_path
@ -37,6 +38,18 @@ from .serializers import (
)
class CompanyFilter(FilterSet):
"""Custom API filters for the CompanyList endpoint."""
class Meta:
"""Metaclass options."""
model = Company
fields = ['is_customer', 'is_manufacturer', 'is_supplier', 'name', 'active']
tags = common.filters.TagsFilter()
class CompanyMixin(OutputOptionsMixin):
"""Mixin class for Company API endpoints."""
@ -62,13 +75,7 @@ class CompanyList(CompanyMixin, ParameterListMixin, DataExportViewMixin, ListCre
filter_backends = SEARCH_ORDER_FILTER
filterset_fields = [
'is_customer',
'is_manufacturer',
'is_supplier',
'name',
'active',
]
filterset_class = CompanyFilter
search_fields = ['name', 'description', 'website', 'tax_id']
@ -145,6 +152,8 @@ class ManufacturerPartFilter(FilterSet):
field_name='manufacturer__active', label=_('Manufacturer is Active')
)
tags = common.filters.TagsFilter(label=_('Tags'))
class ManufacturerOutputOptions(OutputConfiguration):
"""Available output options for the ManufacturerPart endpoints."""
@ -299,6 +308,8 @@ class SupplierPartFilter(FilterSet):
else:
return queryset.exclude(in_stock__gt=0)
tags = common.filters.TagsFilter(label=_('Tags'))
class SupplierPartOutputOptions(OutputConfiguration):
"""Available output options for the SupplierPart endpoints."""

View File

@ -0,0 +1,29 @@
# Generated by Django 5.2.14 on 2026-06-03 10:07
import taggit.managers
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("company", "0079_auto_20260212_1054"),
(
"taggit",
"0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx",
),
]
operations = [
migrations.AddField(
model_name="company",
name="tags",
field=taggit.managers.TaggableManager(
blank=True,
help_text="A comma-separated list of tags.",
through="taggit.TaggedItem",
to="taggit.Tag",
verbose_name="Tags",
),
),
]

View File

@ -17,7 +17,6 @@ from django.utils.translation import gettext_lazy as _
from django.utils.translation import pgettext_lazy as __
from moneyed import CURRENCIES
from taggit.managers import TaggableManager
import common.currency
import common.models
@ -80,6 +79,7 @@ class Company(
InvenTree.models.InvenTreeAttachmentMixin,
InvenTree.models.InvenTreeParameterMixin,
InvenTree.models.InvenTreeNotesMixin,
InvenTree.models.InvenTreeTagsMixin,
report.mixins.InvenTreeReportMixin,
InvenTree.models.InvenTreeImageMixin,
InvenTree.models.InvenTreeMetadataModel,
@ -487,6 +487,7 @@ class ManufacturerPart(
InvenTree.models.InvenTreeParameterMixin,
InvenTree.models.InvenTreeBarcodeMixin,
InvenTree.models.InvenTreeNotesMixin,
InvenTree.models.InvenTreeTagsMixin,
InvenTree.models.InvenTreeMetadataModel,
):
"""Represents a unique part as provided by a Manufacturer Each ManufacturerPart is identified by a MPN (Manufacturer Part Number) Each ManufacturerPart is also linked to a Part object. A Part may be available from multiple manufacturers.
@ -559,8 +560,6 @@ class ManufacturerPart(
help_text=_('Manufacturer part description'),
)
tags = TaggableManager(blank=True)
@classmethod
def create(cls, part, manufacturer, mpn, description, link=None):
"""Check if ManufacturerPart instance does not already exist then create it."""
@ -603,6 +602,7 @@ class SupplierPart(
InvenTree.models.InvenTreeParameterMixin,
InvenTree.models.MetadataMixin,
InvenTree.models.InvenTreeBarcodeMixin,
InvenTree.models.InvenTreeTagsMixin,
InvenTree.models.InvenTreeNotesMixin,
common.models.MetaMixin,
InvenTree.models.InvenTreeModel,
@ -640,8 +640,6 @@ class SupplierPart(
# This model was moved from the 'Part' app
db_table = 'part_supplierpart'
tags = TaggableManager(blank=True)
@staticmethod
def get_api_url():
"""Return the API URL associated with the SupplierPart model."""

View File

@ -20,7 +20,7 @@ from InvenTree.serializers import (
InvenTreeImageSerializerField,
InvenTreeModelSerializer,
InvenTreeMoneySerializer,
InvenTreeTagModelSerializer,
InvenTreeTaggitSerializer,
NotesFieldMixin,
OptionalField,
)
@ -108,6 +108,7 @@ class AddressBriefSerializer(InvenTreeModelSerializer):
class CompanySerializer(
FilterableSerializerMixin,
DataImportExportSerializerMixin,
InvenTreeTaggitSerializer,
NotesFieldMixin,
InvenTreeModelSerializer,
):
@ -143,6 +144,7 @@ class CompanySerializer(
'primary_address',
'tax_id',
'parameters',
'tags',
]
@staticmethod
@ -185,6 +187,8 @@ class CompanySerializer(
help_text=_('Default currency used for this supplier'), required=True
)
tags = common.filters.enable_tags_filter()
parameters = common.filters.enable_parameters_filter()
@ -207,8 +211,9 @@ class ContactSerializer(DataImportExportSerializerMixin, InvenTreeModelSerialize
class ManufacturerPartSerializer(
FilterableSerializerMixin,
DataImportExportSerializerMixin,
InvenTreeTagModelSerializer,
InvenTreeTaggitSerializer,
NotesFieldMixin,
InvenTreeModelSerializer,
):
"""Serializer for ManufacturerPart object."""
@ -308,8 +313,9 @@ class SupplierPriceBreakBriefSerializer(
class SupplierPartSerializer(
FilterableSerializerMixin,
DataImportExportSerializerMixin,
InvenTreeTagModelSerializer,
InvenTreeTaggitSerializer,
NotesFieldMixin,
InvenTreeModelSerializer,
):
"""Serializer for SupplierPart object."""

View File

@ -236,6 +236,12 @@ ldap:
# INVENTREE_DEFAULT_CURRENCY: 'CNY'
# INVENTREE_RESTRICT_ABOUT: true
# Override plugin settings (by plugin slug)
# Settings specified here will be locked and cannot be changed via the UI
#plugin_settings:
# plugin-slug:
# SETTING_KEY: 'value'
# Storage configuration
# Ref: https://docs.inventree.org/en/stable/start/config/#storage-backends
storage:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,7 @@ from rest_framework.exceptions import NotFound
from rest_framework.response import Response
import build.models
import common.filters
import common.models
import common.serializers
import common.settings
@ -277,6 +278,8 @@ class OrderFilter(FilterSet):
return queryset.filter(q1 | q2 | q3 | q4).distinct()
tags = common.filters.TagsFilter()
class LineItemFilter(FilterSet):
"""Base class for custom API filters for order line item list(s)."""
@ -1448,6 +1451,8 @@ class SalesOrderShipmentFilter(FilterSet):
return queryset.filter(q1 | q2).distinct()
tags = common.filters.TagsFilter()
class SalesOrderShipmentMixin:
"""Mixin class for SalesOrderShipment endpoints."""

View File

@ -0,0 +1,73 @@
# Generated by Django 5.2.14 on 2026-06-03 10:07
import taggit.managers
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("order", "0119_transferorderlineitem_line_int"),
(
"taggit",
"0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx",
),
]
operations = [
migrations.AddField(
model_name="purchaseorder",
name="tags",
field=taggit.managers.TaggableManager(
blank=True,
help_text="A comma-separated list of tags.",
through="taggit.TaggedItem",
to="taggit.Tag",
verbose_name="Tags",
),
),
migrations.AddField(
model_name="returnorder",
name="tags",
field=taggit.managers.TaggableManager(
blank=True,
help_text="A comma-separated list of tags.",
through="taggit.TaggedItem",
to="taggit.Tag",
verbose_name="Tags",
),
),
migrations.AddField(
model_name="salesorder",
name="tags",
field=taggit.managers.TaggableManager(
blank=True,
help_text="A comma-separated list of tags.",
through="taggit.TaggedItem",
to="taggit.Tag",
verbose_name="Tags",
),
),
migrations.AddField(
model_name="salesordershipment",
name="tags",
field=taggit.managers.TaggableManager(
blank=True,
help_text="A comma-separated list of tags.",
through="taggit.TaggedItem",
to="taggit.Tag",
verbose_name="Tags",
),
),
migrations.AddField(
model_name="transferorder",
name="tags",
field=taggit.managers.TaggableManager(
blank=True,
help_text="A comma-separated list of tags.",
through="taggit.TaggedItem",
to="taggit.Tag",
verbose_name="Tags",
),
),
]

View File

@ -300,6 +300,7 @@ class Order(
InvenTree.models.InvenTreeAttachmentMixin,
InvenTree.models.InvenTreeBarcodeMixin,
InvenTree.models.InvenTreeNotesMixin,
InvenTree.models.InvenTreeTagsMixin,
report.mixins.InvenTreeReportMixin,
InvenTree.models.MetadataMixin,
InvenTree.models.ReferenceIndexingMixin,
@ -2465,6 +2466,7 @@ class SalesOrderShipment(
InvenTree.models.InvenTreeParameterMixin,
InvenTree.models.InvenTreeAttachmentMixin,
InvenTree.models.InvenTreeBarcodeMixin,
InvenTree.models.InvenTreeTagsMixin,
InvenTree.models.InvenTreeNotesMixin,
report.mixins.InvenTreeReportMixin,
InvenTree.models.MetadataMixin,

View File

@ -37,6 +37,7 @@ from InvenTree.serializers import (
InvenTreeDecimalField,
InvenTreeModelSerializer,
InvenTreeMoneySerializer,
InvenTreeTaggitSerializer,
NotesFieldMixin,
OptionalField,
)
@ -103,6 +104,7 @@ class DuplicateOrderSerializer(serializers.Serializer):
class AbstractOrderSerializer(
CustomStatusSerializerMixin,
DataImportExportSerializerMixin,
InvenTreeTaggitSerializer,
FilterableSerializerMixin,
serializers.Serializer,
):
@ -172,6 +174,8 @@ class AbstractOrderSerializer(
parameters = common.filters.enable_parameters_filter()
tags = common.filters.enable_tags_filter()
# Boolean field indicating if this order is overdue (Note: must be annotated)
overdue = serializers.BooleanField(read_only=True, allow_null=True)
@ -236,6 +240,7 @@ class AbstractOrderSerializer(
'project_code_label',
'responsible_detail',
'parameters',
'tags',
*extra_fields,
]
@ -1065,6 +1070,7 @@ class SalesOrderSerializer(
TotalPriceMixin,
InvenTreeCustomStatusSerializerMixin,
AbstractOrderSerializer,
InvenTreeTaggitSerializer,
InvenTreeModelSerializer,
):
"""Serializer for the SalesOrder model class."""
@ -1084,6 +1090,7 @@ class SalesOrderSerializer(
'completed_shipments_count',
'allocated_lines',
'updated_at',
'tags',
])
read_only_fields = ['status', 'creation_date', 'shipment_date', 'updated_at']
extra_kwargs = {'order_currency': {'required': False}}
@ -1165,6 +1172,8 @@ class SalesOrderSerializer(
read_only=True, allow_null=True, label=_('Allocated Lines')
)
tags = common.filters.enable_tags_filter()
class SalesOrderIssueSerializer(OrderAdjustSerializer):
"""Serializer for issuing a SalesOrder."""
@ -1363,6 +1372,7 @@ class SalesOrderLineItemSerializer(
class SalesOrderShipmentSerializer(
DataImportExportSerializerMixin,
FilterableSerializerMixin,
InvenTreeTaggitSerializer,
NotesFieldMixin,
InvenTreeModelSerializer,
):
@ -1392,6 +1402,7 @@ class SalesOrderShipmentSerializer(
'customer_detail',
'order_detail',
'shipment_address_detail',
'tags',
]
@staticmethod
@ -1461,6 +1472,8 @@ class SalesOrderShipmentSerializer(
parameters = common.filters.enable_parameters_filter()
tags = common.filters.enable_tags_filter()
class SalesOrderAllocationSerializer(
FilterableSerializerMixin, InvenTreeModelSerializer

View File

@ -12,6 +12,7 @@ from drf_spectacular.utils import extend_schema, extend_schema_field
from rest_framework import serializers
from rest_framework.response import Response
import common.filters
import common.serializers
import part.tasks as part_tasks
from data_exporter.mixins import DataExportViewMixin
@ -910,9 +911,7 @@ class PartFilter(FilterSet):
virtual = rest_filters.BooleanFilter()
tags_name = rest_filters.CharFilter(field_name='tags__name', lookup_expr='iexact')
tags_slug = rest_filters.CharFilter(field_name='tags__slug', lookup_expr='iexact')
tags = common.filters.TagsFilter()
# Created date filters
created_before = InvenTreeDateFilter(

View File

@ -32,7 +32,6 @@ from djmoney.contrib.exchange.models import convert_money
from djmoney.money import Money
from mptt.managers import TreeManager
from mptt.models import TreeForeignKey
from taggit.managers import TaggableManager
import common.currency
import common.models
@ -465,6 +464,7 @@ class Part(
InvenTree.models.InvenTreeParameterMixin,
InvenTree.models.InvenTreeAttachmentMixin,
InvenTree.models.InvenTreeBarcodeMixin,
InvenTree.models.InvenTreeTagsMixin,
InvenTree.models.InvenTreeNotesMixin,
report.mixins.InvenTreeReportMixin,
InvenTree.models.InvenTreeImageMixin,
@ -520,8 +520,6 @@ class Part(
objects = TreeManager()
tags = TaggableManager(blank=True)
class Meta:
"""Metaclass defines extra model properties."""

View File

@ -302,6 +302,43 @@ class PluginSetting(common.models.BaseInvenTreeSetting):
on_delete=models.CASCADE,
)
def save(self, *args, **kwargs):
"""When saving a plugin setting, enforce any config-level overrides."""
from common.settings import plugin_setting_overrides
if self.plugin_id and self.plugin:
overrides = plugin_setting_overrides(self.plugin.key)
if self.key in overrides:
self.value = str(overrides[self.key])
super().save(*args, **kwargs)
@classmethod
def get_setting_default(cls, key, **kwargs):
"""Return the default value for a plugin setting, respecting config overrides."""
from common.settings import plugin_setting_overrides
plugin = kwargs.get('plugin')
if plugin:
overrides = plugin_setting_overrides(plugin.key)
if key in overrides:
return overrides[key]
return super().get_setting_default(key, **kwargs)
@classmethod
def get_setting(cls, key, backup_value=None, **kwargs):
"""Get the value of a plugin setting, respecting config overrides."""
from common.settings import plugin_setting_overrides
plugin = kwargs.get('plugin')
if plugin:
overrides = plugin_setting_overrides(plugin.key)
if key in overrides:
return overrides[key]
return super().get_setting(key, backup_value=backup_value, **kwargs)
@classmethod
def get_setting_definition(cls, key, **kwargs):
"""In the BaseInvenTreeSetting class, we have a class attribute named 'SETTINGS', which is a dict object that fully defines all the setting parameters.

View File

@ -283,10 +283,23 @@ class PluginSettingSerializer(GenericReferencedSettingSerializer):
"""Serializer for the PluginSetting model."""
MODEL = PluginSetting
EXTRA_FIELDS = ['plugin']
EXTRA_FIELDS = ['plugin', 'read_only']
plugin = serializers.CharField(source='plugin.key', read_only=True)
read_only = serializers.SerializerMethodField(
read_only=True,
help_text=_('Indicates if the setting is overridden by configuration'),
label=_('Override'),
)
def get_read_only(self, obj) -> bool:
"""Return True if this plugin setting is locked by configuration."""
from common.settings import plugin_setting_overrides
overrides = plugin_setting_overrides(obj.plugin.key)
return obj.key in overrides
class PluginUserSettingSerializer(GenericReferencedSettingSerializer):
"""Serializer for the PluginUserSetting model."""

View File

@ -7,7 +7,7 @@ from rest_framework.exceptions import NotFound
from InvenTree.unit_test import InvenTreeAPITestCase, PluginMixin
from plugin.api import check_plugin
from plugin.models import PluginConfig
from plugin.models import PluginConfig, PluginSetting
class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
@ -719,3 +719,147 @@ class PluginFullAPITest(PluginMixin, InvenTreeAPITestCase):
# max_query_count=350,
# )
# self.assertEqual(response.status_code, 200)
class PluginLockedSettingsTest(PluginMixin, InvenTreeAPITestCase):
"""Tests for locked plugin settings (overridden via configuration).
When a plugin setting is specified in PLUGIN_SETTING_OVERRIDES:
- The API reports read_only=True for that setting
- The stored value always reflects the override, even after a PATCH attempt
- Other settings on the same plugin remain editable (read_only=False)
"""
superuser = True
PLUGIN_SLUG = 'sample'
# Two settings from the sample plugin used across these tests
LOCKED_KEY = 'API_KEY'
LOCKED_VALUE = 'locked-api-key-value'
LOCKED_NUMERIC_KEY = 'NUMERICAL_SETTING'
LOCKED_NUMERIC_VALUE = 42
UNLOCKED_KEY = 'CHOICE_SETTING'
def setUp(self):
"""Activate the sample plugin and confirm it is ready."""
super().setUp()
from plugin.registry import registry
registry.set_plugin_state(self.PLUGIN_SLUG, True)
self.cfg = PluginConfig.objects.filter(key=self.PLUGIN_SLUG).first()
self.assertIsNotNone(self.cfg)
def _setting_url(self, key):
return reverse(
'api-plugin-setting-detail', kwargs={'plugin': self.PLUGIN_SLUG, 'key': key}
)
# ------------------------------------------------------------------
# Helper that supplies the override context for all locked-setting tests
# ------------------------------------------------------------------
def _overrides(self):
return {
self.PLUGIN_SLUG: {
self.LOCKED_KEY: self.LOCKED_VALUE,
self.LOCKED_NUMERIC_KEY: self.LOCKED_NUMERIC_VALUE,
}
}
def test_locked_setting_read_only_flag(self):
"""Locked settings must be reported as read_only=True via the API."""
with self.settings(PLUGIN_SETTING_OVERRIDES=self._overrides()):
response = self.get(self._setting_url(self.LOCKED_KEY), expected_code=200)
self.assertTrue(response.data['read_only'])
def test_unlocked_setting_not_read_only(self):
"""Settings that are NOT overridden must be reported as read_only=False."""
with self.settings(PLUGIN_SETTING_OVERRIDES=self._overrides()):
response = self.get(self._setting_url(self.UNLOCKED_KEY), expected_code=200)
self.assertFalse(response.data['read_only'])
def test_locked_setting_returns_override_value(self):
"""GET on a locked setting must return the configured override value."""
with self.settings(PLUGIN_SETTING_OVERRIDES=self._overrides()):
response = self.get(self._setting_url(self.LOCKED_KEY), expected_code=200)
self.assertEqual(response.data['value'], self.LOCKED_VALUE)
def test_locked_setting_model_get(self):
"""PluginSetting.get_setting() must return the override value directly."""
with self.settings(PLUGIN_SETTING_OVERRIDES=self._overrides()):
value = PluginSetting.get_setting(self.LOCKED_KEY, plugin=self.cfg)
self.assertEqual(value, self.LOCKED_VALUE)
numeric = PluginSetting.get_setting(
self.LOCKED_NUMERIC_KEY, plugin=self.cfg
)
self.assertEqual(numeric, self.LOCKED_NUMERIC_VALUE)
def test_locked_setting_patch_ignored(self):
"""PATCH to a locked setting must not change the value; override is re-applied."""
with self.settings(PLUGIN_SETTING_OVERRIDES=self._overrides()):
response = self.patch(
self._setting_url(self.LOCKED_KEY),
{'value': 'attacker-supplied-value'},
expected_code=200,
)
# The response value must reflect the override, not the submitted value
self.assertEqual(response.data['value'], self.LOCKED_VALUE)
# Confirm persistence: a subsequent GET also returns the locked value
response = self.get(self._setting_url(self.LOCKED_KEY), expected_code=200)
self.assertEqual(response.data['value'], self.LOCKED_VALUE)
def test_locked_numeric_setting_patch_ignored(self):
"""PATCH to a locked numeric setting must not change the value."""
with self.settings(PLUGIN_SETTING_OVERRIDES=self._overrides()):
response = self.patch(
self._setting_url(self.LOCKED_NUMERIC_KEY),
{'value': 999},
expected_code=200,
)
self.assertEqual(int(response.data['value']), self.LOCKED_NUMERIC_VALUE)
def test_unlocked_setting_can_be_changed(self):
"""Settings not in the override dict must still be freely editable."""
with self.settings(PLUGIN_SETTING_OVERRIDES=self._overrides()):
# Set an initial known value
self.patch(
self._setting_url(self.UNLOCKED_KEY), {'value': 'A'}, expected_code=200
)
response = self.get(self._setting_url(self.UNLOCKED_KEY), expected_code=200)
self.assertEqual(response.data['value'], 'A')
# Change it again to confirm mutability
self.patch(
self._setting_url(self.UNLOCKED_KEY), {'value': 'B'}, expected_code=200
)
response = self.get(self._setting_url(self.UNLOCKED_KEY), expected_code=200)
self.assertEqual(response.data['value'], 'B')
def test_locked_setting_model_save_enforced(self):
"""Saving a PluginSetting instance directly must enforce the override value."""
with self.settings(PLUGIN_SETTING_OVERRIDES=self._overrides()):
# Retrieve (or create) the DB object and try to write a different value
obj = PluginSetting.get_setting_object(self.LOCKED_KEY, plugin=self.cfg)
self.assertIsNotNone(obj)
obj.value = 'should-be-overwritten'
obj.save()
# Re-fetch from DB to confirm the override was enforced
obj.refresh_from_db()
self.assertEqual(obj.value, self.LOCKED_VALUE)
def test_no_overrides_settings_are_editable(self):
"""Without any PLUGIN_SETTING_OVERRIDES, all settings default to read_only=False."""
with self.settings(PLUGIN_SETTING_OVERRIDES={}):
for key in [self.LOCKED_KEY, self.LOCKED_NUMERIC_KEY, self.UNLOCKED_KEY]:
response = self.get(self._setting_url(key), expected_code=200)
self.assertFalse(
response.data['read_only'], msg=f'{key} should not be read_only'
)

View File

@ -373,6 +373,8 @@ class StockLocationFilter(FilterSet):
return queryset
tags = common.filters.TagsFilter(label=_('Tags'))
class StockLocationMixin(SerializerContextMixin):
"""Mixin class for StockLocation API endpoints."""
@ -1041,6 +1043,8 @@ class StockFilter(FilterSet):
children = loc_obj.getUniqueChildren()
return queryset.filter(location__in=children)
tags = common.filters.TagsFilter(label=_('Tags'))
class StockApiMixin(SerializerContextMixin):
"""Mixin class for StockItem API endpoints."""

View File

@ -0,0 +1,17 @@
# Generated by Django 5.2.14 on 2026-06-02 06:22
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("stock", "0122_alter_stockitem_creation_date"),
]
operations = [
migrations.RemoveField(
model_name="stockitem",
name="review_needed",
),
]

View File

@ -23,7 +23,6 @@ import structlog
from djmoney.contrib.exchange.models import convert_money
from mptt.managers import TreeManager
from mptt.models import TreeForeignKey
from taggit.managers import TaggableManager
import build.models
import common.models
@ -126,6 +125,7 @@ class StockLocation(
InvenTree.models.PluginValidationMixin,
InvenTree.models.InvenTreeParameterMixin,
InvenTree.models.InvenTreeBarcodeMixin,
InvenTree.models.InvenTreeTagsMixin,
report.mixins.InvenTreeReportMixin,
InvenTree.models.PathStringMixin,
InvenTree.models.MetadataMixin,
@ -149,8 +149,6 @@ class StockLocation(
verbose_name = _('Stock Location')
verbose_name_plural = _('Stock Locations')
tags = TaggableManager(blank=True)
def delete(self, *args, **kwargs):
"""Custom model deletion routine, which updates any child locations or items.
@ -426,6 +424,7 @@ class StockItem(
InvenTree.models.InvenTreeAttachmentMixin,
InvenTree.models.InvenTreeBarcodeMixin,
InvenTree.models.InvenTreeNotesMixin,
InvenTree.models.InvenTreeTagsMixin,
StatusCodeMixin,
report.mixins.InvenTreeReportMixin,
common.models.MetaMixin,
@ -448,7 +447,6 @@ class StockItem(
expiry_date: Expiry date of the StockItem (optional)
stocktake_date: Date of last stocktake for this item
stocktake_user: User that performed the most recent stocktake
review_needed: Flag if StockItem needs review
delete_on_deplete: If True, StockItem will be deleted when the stock level gets to zero
status: Status of this StockItem (ref: stock.status_codes.StockStatus)
notes: Extra notes field
@ -620,8 +618,6 @@ class StockItem(
'test_templates': self.part.getTestTemplateMap(),
}
tags = TaggableManager(blank=True)
# A Query filter which will be reused in multiple places to determine if a StockItem is actually "in stock"
# See also: StockItem.in_stock() method
IN_STOCK_FILTER = Q(
@ -1238,8 +1234,6 @@ class StockItem(
help_text=_('Date that this stock item was created'),
)
review_needed = models.BooleanField(default=False)
delete_on_deplete = models.BooleanField(
default=default_delete_on_deplete,
verbose_name=_('Delete on deplete'),

View File

@ -313,7 +313,8 @@ class StockItemSerializer(
InvenTree.serializers.FilterableSerializerMixin,
DataImportExportSerializerMixin,
InvenTreeCustomStatusSerializerMixin,
InvenTree.serializers.InvenTreeTagModelSerializer,
InvenTree.serializers.InvenTreeTaggitSerializer,
InvenTree.serializers.InvenTreeModelSerializer,
):
"""Serializer for a StockItem.
@ -1194,7 +1195,8 @@ class LocationDeleteSerializer(serializers.Serializer):
class LocationSerializer(
InvenTree.serializers.FilterableSerializerMixin,
DataImportExportSerializerMixin,
InvenTree.serializers.InvenTreeTagModelSerializer,
InvenTree.serializers.InvenTreeTaggitSerializer,
InvenTree.serializers.InvenTreeModelSerializer,
):
"""Detailed information about a stock location."""

View File

@ -89,28 +89,28 @@ bcrypt==5.0.0 \
# via
# -c src/backend/requirements.txt
# paramiko
bleach==4.1.0 \
--hash=sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da \
--hash=sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994
bleach[css]==6.4.0 \
--hash=sha256:4202482733d85cedd04e59fcb2f89f4e4c7c385a78d3c3c23c30446843a37452 \
--hash=sha256:4b6b6a54fff2e69a3dde9d21cc6301220bee3c3cb792187d11403fd795031081
# via
# -c src/backend/requirements.txt
# django-markdownify
blessed==1.42.0 \
--hash=sha256:34b460b77562ed21f807cfd7c527b983b0cc300c98810c8076f283b7bcd45ba7 \
--hash=sha256:f96c4a6dc664b48e0b832fa732acc16df67abd30f0ec35babf99025982f21852
blessed==1.44.0 \
--hash=sha256:d3b5037f0143eb7c80b2000be5bcdf86161af68aed231b91deadbce4e7379785 \
--hash=sha256:e1d2ed93d3d90d0a1494a8b134d188c83fb89ae25590af638b921e8c1c8fa223
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
boto3==1.43.14 \
--hash=sha256:574335744656cfed0b362a0a0467aaf2eb2bf15526edcd02d31d3c661f4b09e4 \
--hash=sha256:5c0a994b3182061ee101812e721100717a4d664f9f4ceaf4a86b6d032ce9fc2d
boto3==1.43.17 \
--hash=sha256:8cf48babdd52ff0e2d891dc661143780b361d3776a3be06cd719da0696995074 \
--hash=sha256:f6b3862a0b14e237f9323223ee76b0563e87a6bbe6d94a42e7b008a901ba8950
# via
# -c src/backend/requirements.txt
# django-anymail
# django-storages
botocore==1.43.14 \
--hash=sha256:1f4a2a95ea78c10398e78431e98c1fe47adb54a7b10a32975144c1f541186658 \
--hash=sha256:b9e500737e43d2f147c9d4e23b54360335e77d4c0ba90a318f51b65e06cb8516
botocore==1.43.17 \
--hash=sha256:27f4ecb80cf1e5be70415fc4a4d3db3907d41ef8178c9df822364f275427d375 \
--hash=sha256:499af7c942ecfd404322974e82c6b5d05a8ea16e9f19320b353e16f401adc5b4
# via
# -c src/backend/requirements.txt
# boto3
@ -520,9 +520,9 @@ defusedxml==0.7.1 \
# via
# -c src/backend/requirements.txt
# python3-openid
django==5.2.14 \
--hash=sha256:58a63ba841662e5c686b57ba1fec52ddd68c0b93bd96ac3029d55728f00bf8a2 \
--hash=sha256:6f712143bd3064310d1f50fac859c3e9a274bdcfc9595339853be7779297fc76
django==5.2.15 \
--hash=sha256:0eb4a9bb1853a35b0286dbc6d916bd352c8c2687195a7f2d6f80cefd840e4970 \
--hash=sha256:5154a9bf84ac01dde011e367f355c07dbb329532e06810dcf3ef2af269e236e7
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
@ -631,9 +631,9 @@ django-maintenance-mode==0.22.0 \
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
django-markdownify==0.9.1 \
--hash=sha256:06ff2994ff09ce030b50de8c6fc5b89b9c25a66796948aff55370716ca1233af \
--hash=sha256:24ba68b8a5996b6ec9632d11a3fd2e7159cb7e6becd3104e0a9372b5a2a148ef
django-markdownify==0.9.7 \
--hash=sha256:2e59dfd445ad6362288f7bc1574e45d8356848463e261b878fd70df6a272c47f \
--hash=sha256:ca3a6361f952d1c1b661c13229f69219a36e745358aa0f377212aef8f09e4cf3
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
@ -649,9 +649,9 @@ django-mptt==0.18.0 \
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
django-oauth-toolkit==3.2.0 \
--hash=sha256:bd2cd2719b010231a2f370f927dbcc740454fb1d0dd7e7f4138f36227363dc26 \
--hash=sha256:c36761ae6810083d95a652e9c820046cde0d45a2e2a5574bbe7202656ec20bb6
django-oauth-toolkit==3.3.0 \
--hash=sha256:2b375d14b1c0ff86e5df5e5de5d1a6d9868873304f54aca37c50158552d5b922 \
--hash=sha256:7af1365cd80735211454b0dca810bc9e860c631e61fd17768ad0fa331737954f
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
@ -750,9 +750,9 @@ djangorestframework-simplejwt[crypto]==5.5.1 \
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
docutils==0.22.4 \
--hash=sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968 \
--hash=sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de
docutils==0.23 \
--hash=sha256:25d013af9bf23bc1c7b2b093dff4208166c53a94786c9e447808335ef1185fea \
--hash=sha256:746f5060322511280a1e50eb76846ed6bf2342984b2ac04dc42caa1a8d78799e
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
@ -977,9 +977,9 @@ icalendar==7.1.2 \
# via
# -c src/backend/requirements.txt
# django-ical
idna==3.16 \
--hash=sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5 \
--hash=sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d
idna==3.17 \
--hash=sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c \
--hash=sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f
# via
# -c src/backend/requirements.txt
# django-anymail
@ -1479,7 +1479,6 @@ packaging==26.2 \
--hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661
# via
# -c src/backend/requirements.txt
# bleach
# gunicorn
# opentelemetry-instrumentation
paramiko==5.0.0 \
@ -1606,9 +1605,9 @@ pip-licenses==5.5.5 \
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
platformdirs==4.9.6 \
--hash=sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a \
--hash=sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917
platformdirs==4.10.0 \
--hash=sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7 \
--hash=sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a
# via
# -c src/backend/requirements.txt
# pint
@ -1718,9 +1717,9 @@ pynacl==1.6.2 \
# via
# -c src/backend/requirements.txt
# paramiko
pypdf==6.12.1 \
--hash=sha256:3953a097b9f26d4e0ead5ff95943d9971377557662a91d8872186053cd71d30a \
--hash=sha256:8fa2a2321cf16247ed848bd7c97f193a60c08670d04abed5b0138327e51c43b0
pypdf==6.12.2 \
--hash=sha256:111669eb6680c04495ae0c113a1476e3bf93a95761d23c7406b591c80a6490b1 \
--hash=sha256:67b2699357a1f3f4c945940ea80826349ee507c9e2577724a14b4941982c104d
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
@ -1948,9 +1947,9 @@ rapidfuzz==3.14.5 \
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
redis==7.4.0 \
--hash=sha256:64a6ea7bf567ad43c964d2c30d82853f8df927c5c9017766c55a1d1ed95d18ad \
--hash=sha256:a9c74a5c893a5ef8455a5adb793a31bb70feb821c86eccb62eebef5a19c429ec
redis==8.0.0 \
--hash=sha256:a00c5355432051ac14e593b8b197fc76c887ee12d55a0984f69328a1115fdc49 \
--hash=sha256:c938c18338585009f0bc310f4c7e4e4b4d37639356c4ac072cedf3af570c8dc7
# via
# -c src/backend/requirements.txt
# django-redis
@ -1970,135 +1969,150 @@ requests==2.34.2 \
# django-anymail
# django-oauth-toolkit
# opentelemetry-exporter-otlp-proto-http
rpds-py==0.30.0 \
--hash=sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f \
--hash=sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136 \
--hash=sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3 \
--hash=sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7 \
--hash=sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65 \
--hash=sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4 \
--hash=sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169 \
--hash=sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf \
--hash=sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4 \
--hash=sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2 \
--hash=sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c \
--hash=sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4 \
--hash=sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3 \
--hash=sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6 \
--hash=sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7 \
--hash=sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89 \
--hash=sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85 \
--hash=sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6 \
--hash=sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa \
--hash=sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb \
--hash=sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6 \
--hash=sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87 \
--hash=sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856 \
--hash=sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4 \
--hash=sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f \
--hash=sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53 \
--hash=sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229 \
--hash=sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad \
--hash=sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23 \
--hash=sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db \
--hash=sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038 \
--hash=sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27 \
--hash=sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00 \
--hash=sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18 \
--hash=sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083 \
--hash=sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c \
--hash=sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738 \
--hash=sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898 \
--hash=sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e \
--hash=sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7 \
--hash=sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08 \
--hash=sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6 \
--hash=sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551 \
--hash=sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e \
--hash=sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288 \
--hash=sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df \
--hash=sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0 \
--hash=sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2 \
--hash=sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05 \
--hash=sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0 \
--hash=sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464 \
--hash=sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5 \
--hash=sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404 \
--hash=sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7 \
--hash=sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139 \
--hash=sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394 \
--hash=sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb \
--hash=sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15 \
--hash=sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff \
--hash=sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed \
--hash=sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6 \
--hash=sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e \
--hash=sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95 \
--hash=sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d \
--hash=sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950 \
--hash=sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3 \
--hash=sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5 \
--hash=sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97 \
--hash=sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e \
--hash=sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e \
--hash=sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b \
--hash=sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd \
--hash=sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad \
--hash=sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8 \
--hash=sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425 \
--hash=sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221 \
--hash=sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d \
--hash=sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825 \
--hash=sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51 \
--hash=sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e \
--hash=sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f \
--hash=sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8 \
--hash=sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f \
--hash=sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d \
--hash=sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07 \
--hash=sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877 \
--hash=sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31 \
--hash=sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58 \
--hash=sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94 \
--hash=sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28 \
--hash=sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000 \
--hash=sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1 \
--hash=sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1 \
--hash=sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7 \
--hash=sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7 \
--hash=sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40 \
--hash=sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d \
--hash=sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0 \
--hash=sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84 \
--hash=sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f \
--hash=sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a \
--hash=sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7 \
--hash=sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419 \
--hash=sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8 \
--hash=sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a \
--hash=sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9 \
--hash=sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be \
--hash=sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed \
--hash=sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a \
--hash=sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d \
--hash=sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324 \
--hash=sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f \
--hash=sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2 \
--hash=sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f \
--hash=sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5
rpds-py==2026.5.1 \
--hash=sha256:01d17b29c0c23d82b1f4751147ec49cf451f1fc2554eb9ef5f957e55d2656ead \
--hash=sha256:036a36a87fb1cd3b214d11c4b3c4f7d2ddad933625dca1c900b56a057c07740a \
--hash=sha256:0408a24e44feb919423dc6d9da677cb5cddb894d2ca9e763967d156d9c60fab4 \
--hash=sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256 \
--hash=sha256:0957cf3c2b8632ec7aaebffebea8005b353cc2a237b6e2ae3c2cac0820704cfb \
--hash=sha256:0a5ae4dbe43c1076983b72616496919872ae7bbe7a1e21cc48336bc3154d130b \
--hash=sha256:0a7d1eec967df0e9b22614a5e177622e0c89611d03727fa0cb48e45028907870 \
--hash=sha256:0b35217adefe87f2fe4db7e9766cabe84744bfe9616d9667be18988928c7f2dc \
--hash=sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08 \
--hash=sha256:141c9498daf2ace9eda35d2b0e376f9ea8b058d84f2aef4f96fccfd449a2f251 \
--hash=sha256:1841d067089e117142d79b98aa0df2f08b52f2ecc1819dd2700636c0db74a473 \
--hash=sha256:19cb09fab7b7fc96b2a6e28f2e34b72a3705ff27b37edb77455316e5d3f3dc9b \
--hash=sha256:1c27c5f6102eac8c03e7595a00827a53b271ba40a53b59ff8709170e0855ea4a \
--hash=sha256:1ebb2f0ab7e16132995a72de805170e0203df0c3dd22e1ef1cd1fdd90bd7a131 \
--hash=sha256:1f2c391c3059798093b65df23aca2cac150460ae9c630d99dec83d703d9485b9 \
--hash=sha256:205dde846f24332ab0c1188699a043b8d165b79bb84529ce272c45048ff6be01 \
--hash=sha256:21846aac0ed2e0589f38c12dc44e77bb64e494b771eadbcf169cba00566ba7ba \
--hash=sha256:21942f52dbbd5f8758bf021213d28bd45c39e873e65e2407faf5f1846f5761ad \
--hash=sha256:277f6c82f0580848796c7ecc8a7173aa3bfb928e4ff831261c2f60a81dc270db \
--hash=sha256:27b74c10ed6a8f190f4287f53bcfea348b92a84a9c9f70d30183d1e6172d580d \
--hash=sha256:296c799becfa849c779c8725494fe9ed94959ed886787df4364b058465bad7f0 \
--hash=sha256:2c595a1d9255dce0599e13130d1440ab2506654f2b50294226ee06402f8fef63 \
--hash=sha256:2c817a189d4ee14290420e5ff051e4dd6baa13f3edf84685071dee07a6d538ee \
--hash=sha256:2d88621d6a7d4dfa633d21abe90f280bb205274e16b1d1e61c6ad4640b2453b7 \
--hash=sha256:3350ec808fb538fe71a1f94dfaa0e29c598dfad805ce49f0caec5ae3183c652b \
--hash=sha256:3397a5ed7174dc2786bb214030232fc36fe8e5584fec43a9952cc542b1a12036 \
--hash=sha256:3574b55c604b8f75dacb007136508bbc0db406e626301778096a133327e7f2fb \
--hash=sha256:3609e9939a8a76cd904cf98a3f1f13b5dc7e150adeaee89e0ea09652ea213e16 \
--hash=sha256:3684a59b158a7683aaeb8e25352e9a9dd2122cec78f2d8530266e4f91b4c7b3f \
--hash=sha256:3966b82dd563176396df030f3dd52a6e54cb69b718e95e78bd555ed3d1e0185d \
--hash=sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d \
--hash=sha256:40ff257542e04796880e011e15cd4dc21c2599975df2aaa8f2c8495ca574e1a5 \
--hash=sha256:413b424f7c4ee65ab5e5be91f5731be0f8b41a1ee2b12dfe810d716312e95a78 \
--hash=sha256:42d0f20e85e549c870749d0e247f0c10d318a45b7e9676d575d2dcb04a1b2e66 \
--hash=sha256:43bca78665423cabae77146f2fe7ce55272b6c8d55d82cca83effd42c7e13972 \
--hash=sha256:453895624ecf7db7063b1004e44037522bbaef9ff6a945e59bc71662d7a03abd \
--hash=sha256:4860b603ddda0475a8885499b3729e90229d480105b42651962a5397d995fa89 \
--hash=sha256:4be8b1d2a705cc37d08256004e1d07de143fa0075c8e85a3df020b776f62b732 \
--hash=sha256:4e237e139f94d3c036fd28eb9f564c99055476ff4ff05cd42be55ce349b5aa02 \
--hash=sha256:4fb8d2e7cb2f850b169806d61d1b991738acec96500a75c30f49caf064ce7cef \
--hash=sha256:55d8f9b7b78c9538fc9e04e82ec0e888ff0c3cffcfad152c77e57cd09351a98a \
--hash=sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c \
--hash=sha256:5d333a7127d4b307601ac37792bee01bb95c867cbfacf21b6375b804d6bbd723 \
--hash=sha256:613fc4ee9eaef26dc5840666214dd6fbcebcf32f46e76f4abc473059f4e13dda \
--hash=sha256:6142dbd80c4df62a5d899f0d616d417f84e0bc8d32526c8e5589019d75d028a7 \
--hash=sha256:62ae3853454fe9ef283a03c96c2d835d39e84b14643a9d62c82ef0fb87d702ca \
--hash=sha256:63c2c4c213f1a4e3f3de28ecab029dbdee976324e729c0d7a55211be72576b02 \
--hash=sha256:656a042550878f12d45752452d47094b7cfe5ad1e9d7b87b5a22ad3ae5ff8015 \
--hash=sha256:66c93681c4729e4e3ecba31b8179fae083ff3118841672835140338b4b9867c1 \
--hash=sha256:6736718bd4fc49cbcb538ba30516fdbef161522acefb739657d48b97bd864fed \
--hash=sha256:68700371c5d7ae1412862ddfa719090925c93ecf351c566d66f09d04b136ea00 \
--hash=sha256:6c3d771a46ec18b12af06ce36243a9a80b07a5d0515236332d90863ca8bb326a \
--hash=sha256:6c7fcf61d44cacecaf3aea542b0e053db77972a4573e7ceda16fb2b399161195 \
--hash=sha256:6f249f8b860a200ad35193af961183ebe9132710484e6f6ce0cf89fd83c63a9a \
--hash=sha256:73c4bd4f70294737b5206a3e8e30ccadbf8a60301831c8ea23eec5dbeea1ecfa \
--hash=sha256:7559f72b94ae52659086c595dfa017cde03155f7832071d30959049052cb3ece \
--hash=sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df \
--hash=sha256:77c004fdc7b891967106f78ddfd7b076bfe6813c6139c6fff6aed3bcaa960b26 \
--hash=sha256:7818f8d0a415be74d2be3590b0a1c1f463a642f4d0217e7d10602dceef5b79aa \
--hash=sha256:7944270ae71383f6e2657dd7d5ce4eeb4ac2d0059a6738f0510583d462ab4842 \
--hash=sha256:7bd530e6a530bb3ea892f194fafa455f3516ac25ecf7143fd33c09be62b0470a \
--hash=sha256:8213afbe8a3a906fb9acb2014423fe3359ee783d0bf90995f70623a3217bfa6c \
--hash=sha256:83bcf894486c9d78dd290d3c0124ff6dd8875d3025e2090a8ec49fcc37c55fdd \
--hash=sha256:85264a90ff4c05c1568dd65f5921c837614b67c60358fb4c17df3b7f2e90690a \
--hash=sha256:88647f43a73c4e01be19b04ceef0c8d3a1958153604d13c773becd8016f2a0cf \
--hash=sha256:8895840ac4809e5f60c88fd07617cd71326e73d6e5a8aa783c5c0f7c24985de2 \
--hash=sha256:8ba264fa49be666cd9cc56bf34ec7002fb3d27a4aee5bcb4d43d0d18feb1bb6f \
--hash=sha256:8bff7073db3899158fff55ebf57b113a67030af26f80a18978f9f0aa60250ddf \
--hash=sha256:8c43a8a973270fd173bf48cdf80bbe66312421cba68d40845034f174f2389049 \
--hash=sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3 \
--hash=sha256:90f628283be835db980c941767d41c9a27b5239e54ba0a9c1335247e82406964 \
--hash=sha256:94068eb3ae6d43f5a786b7db96a406a34e6d5c24489feef32fd6e8946ea7b291 \
--hash=sha256:980450826cf22e133c57e0835070bdd0dd3f73b9b708c3ce223def2cb9469e14 \
--hash=sha256:99ab6ba7bfa2cb0f96a04e3652355bf04e3f51aceb1e943b8541dab7ba4828cc \
--hash=sha256:9af8905b8f854990e40d5206aa5ac58d9b0fe0b7f351ff2bb086c20f6c8c6a47 \
--hash=sha256:9baffb505aff33acc69b422a19f77806680f3c8632227d79f48de8a810d1c2c5 \
--hash=sha256:9cdddb6c1207d284d94fd1530adf57fbd797fe7c4b8704ba85f49414f2557e7d \
--hash=sha256:9e25b7088f9ccbfc0dfcaa52bf969300ca229e10ecf758974ebcbb080a4b37bb \
--hash=sha256:a04df86b3f0fade39ec8fd0e0aab089b1da9fbd2b48df778a57ef96f5e7d38df \
--hash=sha256:a05fa4f41f37ec97c9c260441a940450a192f78d774d2b097eee1379f1e1246a \
--hash=sha256:a2999883eedf72fdfb7520b92c7d4ec2572a71ff40239377aa604cc529eecafc \
--hash=sha256:aad1bff7f666b9598e573815affd666aac6a13a585dde336f843e33350c7fadc \
--hash=sha256:abe76bcdba31e576cb83eeb8797aa0d882b738fef6dc65d0601fc753806a5b46 \
--hash=sha256:ad3773236e95f7f33991eb125224b7da66f206504d032a253a02da7e134519fb \
--hash=sha256:af03e34e860047bc7a352b842856fcf78798fbb81132cc98bd2f907ab4eb9cd2 \
--hash=sha256:b1b964e3ab599e718dc46c018d104b1ebc007cbc6567d827c94a687fca56d77e \
--hash=sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb \
--hash=sha256:b317c87a13f769a4e787819bd508aaa5d69aa09b0880de9af6d3a8a54571cdec \
--hash=sha256:b3cc20c0d800af78fd0fac68086e28c1856cec51ea528bb81ea851aa40d39325 \
--hash=sha256:b4e4bc98639ec915f512fde3aa7a95e0041d95d9c3cc86eea841fa63cb1e8600 \
--hash=sha256:b5c30f3f04eef4fbd362226a6f31d7c8895ca4fbb6e0b790f6890a98d8da8559 \
--hash=sha256:b5f077b44a4f7808520f66dae234988d867deb9aed9be5da057ce9ba831b2a41 \
--hash=sha256:b6825cc329b290e93c5f6a9be2393118a763f6ccf6abd83704e0c102ca583644 \
--hash=sha256:b8d2f912928d426e8cfa396f7f3f8d29a59e6689c86dcca3c420730c1096322b \
--hash=sha256:b95d5e11fc712b752081183a55a244c03cd00570489edd7014d8899f8ceb8162 \
--hash=sha256:b9a6528956191c48c52294a592dbd4a8386d7048bdb25c0efcb6b966466c6d83 \
--hash=sha256:ba05adbf15d994c38ec0b7ab32e858e5110c21e9009a00a86545fd220f84e038 \
--hash=sha256:c0f920015df2a504bebaba6d4c31ccf3fcf942f92655c086da30b671aad19aa6 \
--hash=sha256:c396c1304de421050b3681ea70f371874b54d41b0151e96109758144c231e30b \
--hash=sha256:c39f5b67a8a2e67179ada2a954227d670fe65fa9098457f698f56ddf248709b3 \
--hash=sha256:c3df104083952a0e0c6f10de33e440eabe98fb6317d23e1a58c68f6df08d01b9 \
--hash=sha256:c74005a7bb87752acf351c93897ec63ad77a07a0da7ecad9c050e32e7286ba34 \
--hash=sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6 \
--hash=sha256:ca653c6546386227cd9800d1bef6a348099acf8db4250341da6d90f663d6dfcb \
--hash=sha256:cacedb7a6e167680acba45ad5716e89067d225dc80da0d7040cae8c81d4572fa \
--hash=sha256:cc68e231a77a5f0d774ae278a1f8e55c0456501820847c1e4efb3829f3441df6 \
--hash=sha256:ce87129d9f2c14fa6c4a8601fb80eb4488c80d38a20cd13758ef11123e14995d \
--hash=sha256:cea68bcd53467561ae2f96a6bdad1544299ba97b5b0ddcd5ac3d376e5c781c24 \
--hash=sha256:cef8ac28d26f4dda3533060c20fbf80a325458fa9fd23ea72a73cdfa8e978838 \
--hash=sha256:d0efbe45632665e53e3db8fe1e5692db58fc5cb9bab4459d570b83efefe11164 \
--hash=sha256:d3858b908218ee108d0bbfb2095ccc237648053c9bf98affad7cb079acaf1d97 \
--hash=sha256:de42116e69cb53b911cc34aee5ab98f36c597b822545045d49e938818b99e5e4 \
--hash=sha256:df1d2a1996755b24b9ecee92cb4d36c28f86f464a6a173349c26bab41e94b8c2 \
--hash=sha256:e07be2a9d7122bd6e82dea89814ef8dc893feb1aae97fec1630f3263bbb30e55 \
--hash=sha256:e0b360f316d966b048b085857630b3cc51f3db2f07b06f440eac8f695374d1e3 \
--hash=sha256:e10464d17df3b582745c25cec695cb9558bca2cb6ddb631aee1787fc72c767b2 \
--hash=sha256:e3a8ae58895ac107ed934a6bf51e5846f95c53b9b940c2c6d310838fd5846358 \
--hash=sha256:e4abbf391a70be864920858bf360f4fb380577c9a0f732438a1996726e2c195b \
--hash=sha256:eaaea962c68cdc68d4a533ba985ab8e9484277910bbfaa2ab3ef7732667bfed8 \
--hash=sha256:ed0954b524873214369184a9c82b0eaa45a3fbb9a798cd95b17e0d98499e7ea0 \
--hash=sha256:edf2765d84e42447f112ad877af8fe1db0089aaec5b28e88d6eab45e7fe99cea \
--hash=sha256:ef1013a8625c74043210190b246f5b1551e09757c1f356c6e4160ef96c5bc081 \
--hash=sha256:efef4ac29c6ff495531eb17ee705b62841ecaa291b7c7077e848ea03e237164d \
--hash=sha256:f3a5b10e8ce894825f380a8f1b6444cf73c294dfea62afbb2d13e3a9e630cec1 \
--hash=sha256:f3df3d16ded76f1f8c9cdebd0e1ea55fdf4c23b812de189814da7cf229c22a81 \
--hash=sha256:f414556f6e3958300ff941e40c9f97e3dc9774ddd1b3434c475d73dd354bbed3 \
--hash=sha256:fc09f82e63d4bcd58149572f857a431bae851dc747e313c3b5bdf7abb907fda8 \
--hash=sha256:fc0c0f878ea770a0a8a462456c5ad36fc9fe6358e6b76fdadc7f17575e0b8bf1 \
--hash=sha256:fe71bca7d547acb17027c7fd1624ff8aae623499c498d3e7011182c4de5c25e0 \
--hash=sha256:fea6e836d10abbe191d557d33bd58bd5987725fe63aa1eefe557d230209855bd
# via
# -c src/backend/requirements.txt
# jsonschema
# referencing
s3transfer==0.17.0 \
--hash=sha256:9edeb6d1c3c2f89d6050348548834ad8289610d886e5bf7b7207728bd43ce33a \
--hash=sha256:ce3801712acf4ad3e89fb9990df97b4972e93f4b3b0004d214be5bce12814c20
s3transfer==0.18.0 \
--hash=sha256:239c13b09e65ad0346e1be7348b8a202dcad44ac7ea7c6eb858fc881dce739b6 \
--hash=sha256:3760b8b7ec1315da54048b2d626276732bee4300d054d492d4e1d43e20d4ecbd
# via
# -c src/backend/requirements.txt
# boto3
sentry-sdk==2.60.0 \
--hash=sha256:0bd25e54e78ca02d0be512529fa644bbbf9e8470d7b26371294012d4ca93c978 \
--hash=sha256:28a536c03291c8bcb363cf35c611b32738ec118ff64d8d6383b096448ac4c803
sentry-sdk==2.61.0 \
--hash=sha256:1ca9b4bb777eb5be67004edab7eb894f21c6301f1d05ed64966719ad5d1764ce \
--hash=sha256:ec4d30273909cb1d198e03208b16ee70e2bc5d90a16fd9f1fb2fc6a72e1f03dc
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
@ -2119,7 +2133,6 @@ six==1.17.0 \
--hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
# via
# -c src/backend/requirements.txt
# bleach
# python-dateutil
sqlparse==0.5.5 \
--hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \
@ -2145,6 +2158,7 @@ tinycss2==1.5.1 \
--hash=sha256:d339d2b616ba90ccce58da8495a78f46e55d4d25f9fd71dfd526f07e7d53f957
# via
# -c src/backend/requirements.txt
# bleach
# cssselect2
# weasyprint
tinyhtml5==2.1.0 \
@ -2384,19 +2398,19 @@ xmlsec==1.3.17 \
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
# python3-saml
zopfli==0.4.1 \
--hash=sha256:02086247dd12fda929f9bfe8b3962b6bcdbfc8c82e99255aebcf367867cf0760 \
--hash=sha256:07a5cdc5d1aaa6c288c5d9f5a5383042ba743641abf8e2fd898dcad622d8a38e \
--hash=sha256:27823dc1161a4031d1c25925fd45d9868ec0cbc7692341830a7dcfa25063662c \
--hash=sha256:2f992ac7d83cbddd889e1813ace576cbc91a05d5d7a0a21b366e2e5f492e7707 \
--hash=sha256:4238d4d746d1095e29c9125490985e0c12ffd3654f54a24af551e2391e936d54 \
--hash=sha256:5a4c22b6161f47f5bd34637dbaee6735abd287cd64e0d1ce28ef1871bf625f4b \
--hash=sha256:84a31ba9edc921b1d3a4449929394a993888f32d70de3a3617800c428a947b9b \
--hash=sha256:a899eca405662a23ae75054affa3517a060362eae1185d3d791c86a50153c4dd \
--hash=sha256:a93c2ecafff372de6c0aa2212eff18a75f6c71a100372fee7b4b129cc0b6f9a7 \
--hash=sha256:cb136a74d14a4ecfae29cb0fdecece58a6c115abc9a74c12bc6ac62e80f229d7 \
--hash=sha256:d7bcee1b189d64ec33d1e05cfa1b6a1268c29329c382f6ca1bd6245b04925c57 \
--hash=sha256:fdfb7ce9f5de37a5b2f75dd2642fd7717956ef2a72e0387302a36d382440db07
zopfli==0.4.2 \
--hash=sha256:18af6d2f778552d9bfdaa8e6caaf38c2bd57d0efba34a47860fcc4f6ba899696 \
--hash=sha256:1fc2fd1725c986f535ea323139ec1b974fa294ef9574e0fad840967d52dd38a9 \
--hash=sha256:65a847b1f234df456584f0267fb0f270a95772e675a98ae9c21f3b4839077b2f \
--hash=sha256:70c20595aa30aae246ac71a9fa471e2aa768318150ab814aeea2f651c59c6a32 \
--hash=sha256:92e30df03a00c08b3265c362566573b50a3af3b113b0dbe755071376635ce93d \
--hash=sha256:932083f7ca190fbd73cdf8ad7d32ec7231ecd25e1b3fade547cfb11b823ff91f \
--hash=sha256:a75e646fed3a2a42a82e69a81644009189c8ab4271691f020d52da8630d0580e \
--hash=sha256:bc25a3b3568910afb4bbd951d99835de29cade95417bca215339eac172eb381d \
--hash=sha256:d036b7179c225ce6bd73e68b5a176319ce8835739f494650e4124cd9c383b826 \
--hash=sha256:d1972b58083b85af460d8936f623d849b487c151b26eae925b25ec921ae6cd22 \
--hash=sha256:d3bc0b483575ab8849d9c15b349f78ae0141eb4dfe2ba260c8fdc0b3ba5c6c42 \
--hash=sha256:e679896fde39930dec1f46e5f4e1b1900378686168064966209ffb7b80e41743
# via
# -c src/backend/requirements.txt
# fonttools

View File

@ -246,113 +246,113 @@ click==8.4.1 \
--hash=sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2 \
--hash=sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96
# via pip-tools
coverage[toml]==7.14.0 \
--hash=sha256:057a6af2f160a85384cde4ab36f0d2777bae1057bae255f95413cdd382aa5c74 \
--hash=sha256:0773d8329cf32b6fd222e4b52622c61fe8d503eb966cfc8d3c3c10c96266d50e \
--hash=sha256:0a951308cde22cf77f953955a754d04dccb57fe3bb8e345d685778ed9fc1632a \
--hash=sha256:0c451757d3fa2603354fdc789b5e58a0e327a117c370a40e3476ba4eabab228c \
--hash=sha256:0f162bc9a15b82d947b02651b0c7e1609d6f7a8735ca330cfadec8481dd97d5a \
--hash=sha256:15228a6800ce7bdf1b74800595e56db7138cecb338fdbf044806e10dcf182dfe \
--hash=sha256:1733198802d71ec4c524f322e2867ee05c62e9e75df86bdca545407a221827d1 \
--hash=sha256:1a0abc7342ea9711c469dd8b821c6c311e6bc6aac1442e5fbd6b27fae0a8f3db \
--hash=sha256:1b23b0c6f0b1db6ad769b7050c8b641c0bf215ded26c1816955b17b7f26edfa9 \
--hash=sha256:1c9ed6ef99f88fb8c14aa8e2bf8eb0fe55fa2edfea68f8675d78741df1a5ac0e \
--hash=sha256:22a7e06a5f11a757cdfe79018e9095f9f69ae283c5cd8123774c788deec8717b \
--hash=sha256:23b81107f46d3f21d0cbce30664fcec0f5d9f585638a67081750f99738f6bf66 \
--hash=sha256:29943e552fdc08e082eb51400fb2f58e118a83b5542bd06531214e084399b644 \
--hash=sha256:29fe3da551dface75deb2ccbf87b6b66e2e7ef38f6d89050b428be94afff3490 \
--hash=sha256:2fb73254ff43c911c967a899e1359bc5049b4b115d6e8fbdde4937d0a2246cd5 \
--hash=sha256:3485a836550b303d006d57cc06e3d5afaabc642c77050b7c985a97b13e3776b8 \
--hash=sha256:362cb78e01a5dc82009d88004cf60f2e6b6d6fcbfdec05b05af73b0abf40118f \
--hash=sha256:3a5d8e876dfa2f102e970b183863d6dedd023d3c0eeca1fe7a9787bc5f28b212 \
--hash=sha256:3e7e88110bae996d199d1693ca8ec3fd52441d426401ae963437598667b4c5eb \
--hash=sha256:3f5549365af25d770e06b1f8f5682d9a5637d06eb494db91c6fa75d3950cc917 \
--hash=sha256:3fd43f0616e765ab78d069cf8358def7363957a45cee446d65c502dcfeea7893 \
--hash=sha256:454a380af72c6adada298ed270d38c7a391288198dbfb8467f786f588751a90c \
--hash=sha256:45899ec2138a4346ed34d601dedf5076fb74edf2d1dd9dc76a78e82397edee90 \
--hash=sha256:45e0f79d8351fa76e256716df91eab12890d32678b9590df7ae1042e4bd4cf5d \
--hash=sha256:49c005cba1e2f9677fb2845dcdf9a2e72a52a17d63e8231aaaae35d9f50215ef \
--hash=sha256:4b899594a8b2d81e5cc064a0d7f9cac2081fed91049456cae7676787e41549c9 \
--hash=sha256:55d3089079ce181a4566b1065ab28d2575eb76d8ac8f81f4fcda2bf037fee087 \
--hash=sha256:5904abf7e18cddc463219b17552229650c6b79e061d31a1059283051169cf7d5 \
--hash=sha256:5ac83957a80d0701310e96d8bec68cdcf4f90a7674b7d13f15a344315b41ab27 \
--hash=sha256:5d4a51aad8ba8bdcd2b8bd8f03d4aca19693fa2327a3470e4718a25b03481020 \
--hash=sha256:5ebb8f4614a3787d567e610bbfdf96a4798dd69a1afb1bd8ad228d4111fe6ff3 \
--hash=sha256:63df0fe568e698e1045792399f8ab6da3a6c2dce3182813fb92afa2641087b47 \
--hash=sha256:65c86fb646d2bd2972e96bd1a8b45817ed907cee68655d6295fe7ec031d04cca \
--hash=sha256:65f267ca1370726ec2c1aa38bbe4df9a71a740f22878d2d4bf59d71a4cd8d323 \
--hash=sha256:664123feb0929d7affc135717dbd70d61d98688a08ab1e5ba464739620c6252d \
--hash=sha256:668b92e6958c4db7cf92e81caac328dfbbdbb215db2850ad28f0cbe1eea0bfbd \
--hash=sha256:68af363c07ecd8d4b7d4043d85cb376d7d227eceb54e5323ee45da73dbd3e426 \
--hash=sha256:6a6516b02a6101398e19a3f44820f69bab2590697f7def4331f668b14adaf828 \
--hash=sha256:6a78e2a9d9c5e3b8d4ab9b9d28c985ea66fced0a7d7c2aec1f216e03a2011480 \
--hash=sha256:6b9bf47223dd8db3d4c4b2e443b02bace480d428f0822c3f991600448a176c97 \
--hash=sha256:6d160217ec6fe890f16ad3a9531761589443749e448f91986c972714fad361c8 \
--hash=sha256:6e57054a583da8ac55edf24117ea4c9133032cfc4cf72aa2d48c1e5d4b52f899 \
--hash=sha256:70390b0da32cb90b501953716302906e8bcce087cb283e70d8c97729f22e92b2 \
--hash=sha256:72a305291fa8ee01332f1aaf38b348ca34097f6aa0b0ef627eef2837e57bbba5 \
--hash=sha256:731dc15b385ac52289743d476245b61e1a2927e803bef655b52bc3b2a75a21f3 \
--hash=sha256:731e535b1498b27d13594a0527a79b0510867b0ad891532be41cb883f2128e20 \
--hash=sha256:7333cd944ee4393b9b3d3c1b598c936d4fc8d70573a4c7dacfec5590dd50e436 \
--hash=sha256:741f57cddc9004a8c81b084660215f33a6b597dbe62c31386b983ee26310e327 \
--hash=sha256:742a73ea621953b012f2c4c2219b512180dd84489acf5b1596b0aafc55b9100b \
--hash=sha256:7b2bb6c9d7e769360d0f20a0f219603fd64f0c8f97de17ab25853261602be0fb \
--hash=sha256:7b79d646cf46d5cf9a9f40281d4441df5849e445726e369006d2b117710b33fe \
--hash=sha256:7bf43e000d24012599b879791cff41589af90674722421ef11b11a5431920bab \
--hash=sha256:7c843572c605ab51cfdb5c6b5f2586e2a8467c0d28eca4bdef4ec70c5fecbd82 \
--hash=sha256:7ebb1c6df9f78046a1b1e0a89674cd4bf73b7c648914eebcf976a57fd99a5627 \
--hash=sha256:7ffd19fc8aed057fd686a17a4935eef5f9859d69208f96310e893e64b9b6ccf5 \
--hash=sha256:8231ade007f37959fbf58acc677f26b922c02eda6f0428ea307da0fd39681bf3 \
--hash=sha256:827d6397dbd95144939b18f89edf31f63e1f99633e8d5f32f22ba8bdda567477 \
--hash=sha256:829994cfe1aeb773ca27bf246d4badc1e764893e3bfb98fff820fcecd1ca4662 \
--hash=sha256:84c32d90bf4537f0e7b4dec9aaa9a938fb8205136b9d2ecf4d7629d5262dc075 \
--hash=sha256:8767486808c436f05b23ab98eb963fb29185e32a9357a166971685cb3459900f \
--hash=sha256:8de5b61163aee3d05c8a2beab6f47913df7981dad1baf82c414d99158c286ab1 \
--hash=sha256:90c1a51bcfddf645b3bb7ec333d9e94393a8e94f55642380fa8a9a5a9e636cb7 \
--hash=sha256:9117377b823daa28aa8635fbb08cda1cd6be3d7143257345459559aeef852d52 \
--hash=sha256:91b993743d959b8be85b4abf9d5478216a69329c321efe5be0433c1a841d691d \
--hash=sha256:92af52828e7f29d827346b0294e5a0853fa206db77db0395b282918d41e28db9 \
--hash=sha256:9336e23e8bb3a3925398261385e2a1533957d3e760e91070dcb0e98bfa514eed \
--hash=sha256:953f521ca9445300397e65fda3dca58b2dbd68fee983777420b57ac3c77e9f90 \
--hash=sha256:98af83fd65ae24b1fdd03aaead967a9f523bcd2f1aab2d4f3ffda65bb568a6f1 \
--hash=sha256:9aed9fa983514ca032790f3fe0d1c0e42ca7e16b42432af1706b50a9a46bef5d \
--hash=sha256:9cd1169b2230f9cbe9c638ba38022ed7a2b1e641cc07f7cea0365e4be2a74980 \
--hash=sha256:9d1aa57a1dc8e05bdc42e81c5d671d849577aeedf279f4c449d6d286f9ed88ca \
--hash=sha256:9d26ac7f5398bafc5b57421ad994e8a4749e8a7a0e62d05ec7d53014d5963bfa \
--hash=sha256:9f323af3e1e4f68b60b7b247e37b8515563a61375518fa59de1af48ba28a3db6 \
--hash=sha256:9fbd898551762dea00d3fef2b1c4f99afd2c6a3ff952ea07d60a9bd5ed4f34bc \
--hash=sha256:a1816c505187592dcd1c5a5f226601a549f70365fbd00930ac88b0c225b76bb4 \
--hash=sha256:a2bd259c442cd43c49b30fbafc51776eb19ea396faf159d26a83e6a0a5f13b0c \
--hash=sha256:a3b5ddfd6aa7ddad53ee3edb231e88a2151507a43229b7d71b953916deca127d \
--hash=sha256:a706b908dfa85538863504c624b237a3cc34232bf403c057414ebfdb3b4d9f84 \
--hash=sha256:a841fae2fadcae4f438d43b6ccc4aac2ad609f47cdb6cfdce60cbb3fe5ca7bc2 \
--hash=sha256:a93bac2cb577ef60074999ed56d8a1535894398e2ed920d4185c3ec0c8864742 \
--hash=sha256:a9f864ef57b7172e2db87a096642dd51e179e085ab6b2c371c29e885f65c8fb2 \
--hash=sha256:acebd068fca5512c3a6fde9c045f901613478781a73f0e82b307b214daef23fb \
--hash=sha256:b34ece8065914f938ed7f2c5872bb865336977a52919149846eac3744327267a \
--hash=sha256:b4cc4fce8672fffcb09b0eafc167b396b3ba53c4a7230f54b7aaffbf6c835fa9 \
--hash=sha256:b4e26a0f1b696faf283bffe5b8569e44e336c582439df5d53281ab89ee0cba96 \
--hash=sha256:b4f07cf7edcb7ec39431a5074d7ea83b29a9f71fcfc494f0f40af4e65180420f \
--hash=sha256:b812eb847b19876ebf33fb6c4f11819af05ab6050b0bfa1bc53412ae81779adb \
--hash=sha256:ba3b8390db29296dbbf49e91b6fe08f990743a90c8f447ba4c2ffc29670dfa63 \
--hash=sha256:bcb2e855b87321259a037429288ae85216d191c74de3e79bf57cd2bc0761992c \
--hash=sha256:bfb0ed8ec5d25e93face268115d7964db9df8b9aae8edcde9ec6b16c726a7cc1 \
--hash=sha256:c7492f2d493b976941c7ca050f273cbda2f43c381124f7586a3e3c16d1804fec \
--hash=sha256:c79d2319cabef1fe8e86df73371126931550804738f78ad7d31e3aad85a67367 \
--hash=sha256:c83d2399a51bbec8429266905d33616f04bc5726b1138c35844d5fcd896b2e20 \
--hash=sha256:ca3d9cf2c32b521bd9518385608787fa86f38daf993695307531822c3430ed67 \
--hash=sha256:cc3499459bbcdd51a65b64c35ab7ed2764eaf3cba826e0df3f1d7fe2e102b70b \
--hash=sha256:d128b1bba9361fbaaf6a19e179e6cfd6a9103ce0c0555876f72780acc93efd85 \
--hash=sha256:d1bb3543b58fea74d2cd1abc4054cc927e4724687cb4560cd2ed88d2c7d820c0 \
--hash=sha256:d8b013632cc1ce1d09dbe4f32667b4d320ec2f54fc326ebeffcd0b0bcc2bb6c4 \
--hash=sha256:d8e1762f0e9cbc26ec315471e7b47855218e833cd5a032d706fbf43845d878c7 \
--hash=sha256:d9c8ef6ed820c433de075657d72dda1f89a2984955e58b8a75feb3f184250218 \
--hash=sha256:dc38367eaa2abb1b766ac333142bce7655335a73537f5c8b75aaa89c2b987757 \
--hash=sha256:f2bbb8254370eb4c628ff3d6fa8a7f74ddc40565394d4f7ab791d1fe568e37ef \
--hash=sha256:f580f8c80acd94ac72e863efe2cab791d8c38d153e0b463b92dfa000d5c84cd1 \
--hash=sha256:fab3877e4ebb06bd9d4d4d00ee53309ee5478e66873c66a382272e3ee33eb7ea \
--hash=sha256:fb609b3658479e33f9516d46f1a89dbb9b6c261366e3a11844a96ec487533dae \
--hash=sha256:fcaba850dd317c65423a9d63d88f9573c53b00354d6dd95724576cc98a131595
coverage[toml]==7.14.1 \
--hash=sha256:0177614a0370f227888b4e436a7c55686d6a9f90eb1ade2b624ba685a1686e86 \
--hash=sha256:01b7733daad0237daa01ef80fe2dfceffc911e6a17fa7b55d14aa8214eaaaecd \
--hash=sha256:03a6f93c1ec3b7f2e77b5dbcc5573a2c21f12529a5c6bbe0f16f72303cc2fa4d \
--hash=sha256:042c46ded7c288aeb07cf14a28b6c1e10b78fcba40171c3fa1e939377eeef0b5 \
--hash=sha256:06144cd511cf2624873a035c5069cf297144f6e77a73ee3d7a55b605ec5efb42 \
--hash=sha256:07c6290b1697b862c0478eab545eec949a0d0e4d6d03497f446d706da3b4f2de \
--hash=sha256:10274a1fbeb8ec5d72966e17bb198a3104257aca4ac09d98667c5f8aca8c8548 \
--hash=sha256:1101a5ebb083aecb625ebb6209d4105b58f647b093cb2dc8122d7b33f743cfe1 \
--hash=sha256:114c95ef29302423b87d159075805f4ab973254a2638a5d7d046c94887cc87d7 \
--hash=sha256:1238cb94638e610e972c60dac68e813f868dc7d6e982535270558443058d9d59 \
--hash=sha256:12c42ec1e14f553c4f817e989365982e646e27211f10a0f717855b94a79c8906 \
--hash=sha256:145986fe66647eb489f18d9a997567a3fd358584c4b5a808769113abc07466af \
--hash=sha256:17a5a241e5997621a956a7f402a7433ef4221e5152809b785bec79e2323799f1 \
--hash=sha256:1896f5e19ff3f0431c7ce2172adc54890fd97f86b59ced8ca1649145d9ffe35d \
--hash=sha256:196a13319ad88d6d8ef5ab489ec4f44ddde2143c0c7d5b27786f6c3ffd56a7e1 \
--hash=sha256:221c70f316241a78e77e607c227cefc8808d4e08f28d99c04f35694690e940be \
--hash=sha256:2222be86d0b54f5dd5a38f45f17f315f737245e857bf0bdedc70734f84a13c02 \
--hash=sha256:2224f89ffd0c5605ccce1ed7a584da162bc7c55f601ab1c946bc9de31a486b42 \
--hash=sha256:23bf7fa51ac02e07fc7c96849b82946da47ae862dc8f86d183b2a4864fc38129 \
--hash=sha256:2d69af5dea2de76fc485a83032a630523f985198b7e25be901ec60181587b01e \
--hash=sha256:30c08f7d90415aa98b3c990385dea2939b0da55f38515e5b369b83655f8523be \
--hash=sha256:357d4e32935c36588aaba057d734fa32428c360c9fc2e4442afbf1b646beee6e \
--hash=sha256:35ab22d91de736e8966b980dc355cbcdd2c6dbbcfe275f9a2991bc8a91b3df65 \
--hash=sha256:370c5afae3fa0658e11694a32b24c2778f6bc2d17718121f94ee185e69f26b54 \
--hash=sha256:3758dd0a7f1fa57365ef2e781df0f0731d38b6e3772259d13dae4bd8a958d4b1 \
--hash=sha256:39b21e212c55af06fa375e3dbf90a8a8e38792f3a910c580066d23563830ddd5 \
--hash=sha256:3a56abc20a472baf0304c455721bc601477440d28ecfde8a03dde79ede07e0df \
--hash=sha256:3c18ebc343e15be53049b3a2dce38fe82d58f37e20ab9094b3a39c0aa4f6bb47 \
--hash=sha256:3d452fd08b5c72c5167c93e6867b5c08500bd40f2a21e1e854a500550b6cc36f \
--hash=sha256:3e3680291c4a1d0dadfa84a2c459576a4af5133abb617905714339a0c73138cf \
--hash=sha256:442cc9c952b2df400cda54bb04ab87330cf2cd08a8692cbbea36773531eb6f37 \
--hash=sha256:46f714d2fb8ae2f4f29f23ada7f1e79b759fff5a70f94a1dac23af204c3ec9e4 \
--hash=sha256:478b5bcd63c2e1357c5c7e16c070690df7b07f676b1c114d7b93e533c664309f \
--hash=sha256:48b283b1dd6372e8de2a7a9a4c4d5dc06f4d4fd209b876f3c88a7a205a0c8f84 \
--hash=sha256:4a28fd227808366b196a75476dced2eb35b351d6766ba9c858dc93319e87f4f1 \
--hash=sha256:4ea1c034f95c9b056e856b794630b17f9fa3d57e4800ff1e503d3be0f9c9078c \
--hash=sha256:51bd64741cc6fa065abd300ede1afe5a5291ece9c31da8b24884deda48bcc3f8 \
--hash=sha256:54acdb6674a4661768d7bf7db32dfb9f46ab1d764f8aba6df75ce1a6a088724e \
--hash=sha256:59baf88468dbc8d63b1887afd92bda52e40bb1561696e5819670601403810cec \
--hash=sha256:5a1c5215be81035e629d5bc756650634d0bf31991038db7a0eccb90f025ce16d \
--hash=sha256:5b0c99ba93a07d56f6df340bb79be53202a082b2fdb81bfe6190b741a3470d54 \
--hash=sha256:5ea0c297e27133853b4d8a3eb799bff5a2dbd9f2f41537a240d337ac9b4df890 \
--hash=sha256:5f0cfc27c539f07cf5c0a4cfe211d0b6cae039f8f40526dbaa71944e64b50a7b \
--hash=sha256:6223a72fd0e4c7156353ec0f08a5f93623e1d3034d0e2683b9bb8ea674131b1d \
--hash=sha256:62a9f70b52e0b5a95cfef4a5c5641b06983cadc5e538a3feeb5c00211f523ac2 \
--hash=sha256:62fd185ef9df3c33d1c8178c5af105f762afbad96038de9a4ae100aa6297ca33 \
--hash=sha256:6a3cb83d1552c0cd1b4906655b6a33fd4a8473229633a901c6b73bf86914dee9 \
--hash=sha256:6adc5a36984624a70bf11d7184e20fa0a49aa7c47ffab43804106a1a695ea22e \
--hash=sha256:6b6b0853b895fe0e98cbfc580d1ec3393d9302b4b1e96a77b3f5c91fdab899e6 \
--hash=sha256:6ff665fb023a77386fe11685190cee1f60a7d635994a30d9b0a061533d470fce \
--hash=sha256:7279d2110a28cebc738b6459ecda2771735a4c18465fbbd36b3288fe5ed92247 \
--hash=sha256:76a085d7005236a767e3426148b2c407e53ad61695c562f8a81da2d373324901 \
--hash=sha256:7771b601718fdde84832c3a434ca9bbf4ae9adbc49d84198b4110700c3c77c36 \
--hash=sha256:79058c47dae6788504b5effb319961bcd72d7240551464b91d474bc0ed186d69 \
--hash=sha256:7af486dabe8954d03b087f0021540897afe084f04e16ff5579e08cc46f871416 \
--hash=sha256:7f02d09f70776579b926d889a4c9c235070a1f47c40458aeaca563fae5acfdb5 \
--hash=sha256:8011224a62280e50dab346960c03cf47aca1a1e09e608c0fb33fd6e0cc8e9500 \
--hash=sha256:8270544c361ed405a27a060dbc9ed2c124b084d96dfdc2d9a2510482aef981ad \
--hash=sha256:84ac9499e48700399a5dd0ea7085b5091961fec52c68d66b4ec0d3cf7f4441b1 \
--hash=sha256:84b535f00655ecafe1d929d1fb00ed5d6fa3051ea643ab2c161a3887b86f294b \
--hash=sha256:851b9e1e4e8a4608e77c79714b2e77c0970d2ed7202a05e92ae407817481887b \
--hash=sha256:85e85586565842f6932abebd4c18bcb1074223dc0b3576e7d173ca710622813a \
--hash=sha256:87ebdf787d4888e3f3f2d523eadc6e18c6d18c6d0eb173801a189641627fb37e \
--hash=sha256:8a3ce026d73290f42f08dafecbd82c193a74df280461fbf97300fec51fd133ee \
--hash=sha256:9132cd363a68a4c3daa7c8704a654b1e39d3360f6f5b8ddd470608a945236c07 \
--hash=sha256:99cd41ff91afd94896fea3bc002706b6ae4ce95727d06e4a0f39c0a8d8bd8b1a \
--hash=sha256:9eeb3fcbc13ba40dfbdb22d01d196a28e9cef9ed4c29b60061a1e0e823a9929d \
--hash=sha256:a06c76364a9360e33d6d23769aefdf7f66f38e2ffb60ceb1baaa4989d83b695c \
--hash=sha256:a07891c3f4805442b31b71e84ba3cf29ed1aa9a428284e06deeb4b23e5b46343 \
--hash=sha256:a24a81f9715ee42ef59a316cc11611c98fe23920f7c81861315c9f3ff4a230f4 \
--hash=sha256:a252f21c27e38347e60111a3266b03827422a7d5525951aceee313aa68bab1d2 \
--hash=sha256:a311d8e1da24be5c1ccf85cbfb06315dbaa1703d5a1eab3f6432c72b837917c8 \
--hash=sha256:a5274669f37f2343635a347b91a60777621341ab3378e9c6ac9335eee704bddf \
--hash=sha256:aa5e304a873fabddc11e484e9b6b738bd38bd7bed17b09aa84eecf5332e8b8bb \
--hash=sha256:ab4af6352741a604c431c6072fce5bee33bf0f20dc7a56618d6bf6bb89e9810c \
--hash=sha256:b553d04b5e778a8e56d57eb134aff42a92718ecba45e79c4764ecfa40efd92ff \
--hash=sha256:b84800013769a78ccb9ef4659402e26d06867e337b61ec365f77ad008adea80e \
--hash=sha256:b84ffdf877644e7096aa936991efeed873f7f3df57b9cd001312b7668ab08550 \
--hash=sha256:bcaa50684dcaadfa599ac48f81103c756d791cfd85c97203d2217c593d48b860 \
--hash=sha256:be9f2c802dcfce3f71298303aa5dad0dce440a76c52f2f60dacd8656dab78793 \
--hash=sha256:c643734307300234fafa36bf2a040a7235f8f177ea1fd6ec1423aea6fb7b929f \
--hash=sha256:c79cead5b5bc584d9c71451cb984d0e3a84e0c0937379c8efcbf27c8d661b851 \
--hash=sha256:c7e057326434e441306226fbeb5d1aaf14a2637efe97ba668306635835f32ad7 \
--hash=sha256:c912c259304cfb5ee584481cfb7ce1ff932b4d61e6c9140b8f19cb7b5ed82332 \
--hash=sha256:ce66d8e46da2bb5ee313a745cbd2e391d319176c1f7a9451bfcd3a2fb920859b \
--hash=sha256:ced2f09ef276fd58611a1ef502164ad266d2b75174e5a40cabbdb4033f9f6cf2 \
--hash=sha256:cfe5a5fec635799ef33428f1e5e61bafa45a92a96190ba731561ba558ccc214d \
--hash=sha256:d13e6725992e2d2fd7d81d4f5241952d13740121dfd501da09201be39b2c003a \
--hash=sha256:d34d75f892b3ab73ba11cab5442cce7b3e168fd64162b16f0e1e0d09c508edef \
--hash=sha256:d5b89cdfb2ee051b71e8c3c70bd81a9eff81100f736a269136fe1a68efe00474 \
--hash=sha256:d5ed429d0b8edaac649e889b4ffcedb6c80b06629a3f93050e3dddfb99235bee \
--hash=sha256:da028256b04ec30e5e0114b6f76172938c313991f0a2d3d894271315cf5d5e43 \
--hash=sha256:dcbf65f1f66a26cdd88c35cf68fb4729c5d1cd2e88added72420541dfb212034 \
--hash=sha256:dd34767fa19848d35659ffc0a75314f58c7af3f1cd87ec521e8292a1238398a3 \
--hash=sha256:ddf799247318f34dbcd2efa8c95a8d0642674e926bb1774cf9b63dfd2a389d1c \
--hash=sha256:de286598cc65d2b489411174b1faec2f5a7775fb3201fd925db2a76b4030f37d \
--hash=sha256:e471bc5769ff073b058cfadb0d736b56ce067c8560eabeb0da88462df98c23e7 \
--hash=sha256:e854312c4103f2ad4c0dc023b69b77ebfd2c89db5f86c4c94dc2353f9a92167e \
--hash=sha256:ea8cd6ca0ee9f616aaef3afc6882e32c2cbf18b00d96313ffd76af650574034d \
--hash=sha256:f2302660e32562a532b442480121aef8aa61a5bdb20b30bf0adab29f10a5a4b4 \
--hash=sha256:f497a1ea81d4cd7c10ddcaa685135b9aabd291af3d55775a9ddf3cb7a364cdd9 \
--hash=sha256:f4ddbe407477f04c45115d1a4e5bc480f753553b534d338d4c3358b1cdd0ea52 \
--hash=sha256:f747dc8edcfe740130f28f32f3995e955494285717e86ee25af51db2219df08a \
--hash=sha256:fad54e871165f6ec2f536063ac74c3104508a12963e64072ba44bd822de52b0c \
--hash=sha256:fc459e5d73be2d6332fcfe8dbf3d8994671fe33c700f4565988ecfa511547253 \
--hash=sha256:fd86572566fb40189a8260446158235159bc7a82dfbc87a3b39cf4fb57fcec1c
# via -r src/backend/requirements-dev.in
cryptography==48.0.0 \
--hash=sha256:0890f502ddf7d9c6426129c3f49f5c0a39278ed7cd6322c8755ffca6ee675a13 \
@ -408,9 +408,9 @@ cryptography==48.0.0 \
# -c src/backend/requirements-3.14.txt
# -c src/backend/requirements.txt
# pdfminer-six
django==5.2.14 \
--hash=sha256:58a63ba841662e5c686b57ba1fec52ddd68c0b93bd96ac3029d55728f00bf8a2 \
--hash=sha256:6f712143bd3064310d1f50fac859c3e9a274bdcfc9595339853be7779297fc76
django==5.2.15 \
--hash=sha256:0eb4a9bb1853a35b0286dbc6d916bd352c8c2687195a7f2d6f80cefd840e4970 \
--hash=sha256:5154a9bf84ac01dde011e367f355c07dbb329532e06810dcf3ef2af269e236e7
# via
# -c src/backend/requirements-3.14.txt
# -c src/backend/requirements.txt
@ -428,13 +428,13 @@ django-silk==5.5.0 \
django-slowtests==1.1.1 \
--hash=sha256:3c6936d420c9df444ac03625b41d97de043c662bbde61fbcd33e4cd407d0c247
# via -r src/backend/requirements-dev.in
django-stubs==6.0.4 \
--hash=sha256:7aee77e8de9c14c0d9cf84988befe826d93cbc15a87e0ade2943f14d553451cf \
--hash=sha256:e991c68f77239663577a5f4fc75e99c84f867f378cafc97cbf4acc5aff378279
django-stubs==6.0.5 \
--hash=sha256:7742b8e60afc68a8545158e2bdb103376d5a092f7902c17f370920a9c08eb957 \
--hash=sha256:9fb9eede9b2fc47b36c3dc4a93652eb959dff45466ec4a580e4a22782192d746
# via -r src/backend/requirements-dev.in
django-stubs-ext==6.0.4 \
--hash=sha256:0434a912bb08a370afcac9e90305c53e6f4eed3c1d1d46962559da5f8dbb8f27 \
--hash=sha256:ff21f7b4362928b56e18cda0595f296e33c665f3019f4e3e4231977385e76cac
django-stubs-ext==6.0.5 \
--hash=sha256:1cc325e991303849bce70e19748981b225ef08b37256f263e113caf97cd3bcc0 \
--hash=sha256:a9932c8233d6dd4e34ae0b192026379c1a9be8f0b7c27aa1fa09ae215169773e
# via django-stubs
django-test-migrations==1.5.0 \
--hash=sha256:1cbff04b1e82c5564a6f635284907b381cc11a2ff883adff46776d9126824f07 \
@ -448,9 +448,9 @@ gprof2dot==2025.4.14 \
--hash=sha256:0742e4c0b4409a5e8777e739388a11e1ed3750be86895655312ea7c20bd0090e \
--hash=sha256:35743e2d2ca027bf48fa7cba37021aaf4a27beeae1ae8e05a50b55f1f921a6ce
# via django-silk
idna==3.16 \
--hash=sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5 \
--hash=sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d
idna==3.17 \
--hash=sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c \
--hash=sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f
# via
# -c src/backend/requirements-3.14.txt
# -c src/backend/requirements.txt
@ -496,24 +496,24 @@ pluggy==1.6.0 \
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \
--hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
# via pytest
prek==0.4.1 \
--hash=sha256:051c442b570b53756225410240577bee1aeace6be52955dfacf45a9783223b36 \
--hash=sha256:10e7e78ffe65dfba7d687a8c71b2f473554d1ba60f43c742105da4c0030feed9 \
--hash=sha256:17ffa9d8dd40791b9b99cafe558c5cc28e78e5be57607b280b15f0dab90264e9 \
--hash=sha256:2ab3460641762edf128b1ec8e833ce7e9ae015d1268a894560cb90d3393a7527 \
--hash=sha256:2d1805123ab5d730629de588bf319ea39e7078b589b3288c95740f1b4780a1d4 \
--hash=sha256:5b4a348537924b20e208cbd87ef58e96ec37d691c5bec2969209c40de0ecf72e \
--hash=sha256:60928d1dad45ff3e491d3083a50643cc213aa2d54f1dbd8d702d7193773c020e \
--hash=sha256:622a8812bda87cf4ddcae2dab5ccecc55b88d70c677129dbe25e975d923179f0 \
--hash=sha256:6e69a9c02ead38706a5d2a4ae209dccba08ccb5d0026e1d08e723c66ab964750 \
--hash=sha256:76663998827a2cbc94f5e209319809655489b5bd1f8e70568a623372e80253f0 \
--hash=sha256:7c515492ef3585e6bcd7b83f1bb1cb131038abc88ed2c843de1e4c3ceb865b19 \
--hash=sha256:8fa707971465d8ad021c907e43691aad7bb98942943e61e294ece5f95d9fbc78 \
--hash=sha256:b25807e0aa57d2118747e127b58e7a1bf41d5d7b3323f5f3f1f3cb10031245cc \
--hash=sha256:c0877e82c52359d655fe1072b3a5228639184d1d5f03c6803b6530cd6da1ef20 \
--hash=sha256:cdf4503a240369f66321213d9c4bc6f925014b64ff7121de9e9920c9b9838ce2 \
--hash=sha256:dc744fedf98df8a00a9e3bcd629b163fee5e9f9e22bce66029d9945241586165 \
--hash=sha256:efa95331c4c171a867c0064c19d8a4abc94a1c1c920c8b8092f2d7d87f4b90a8
prek==0.4.3 \
--hash=sha256:03a4ac3c3023a76faa52ad7775720599b10241930be8902c471085b22572b4b0 \
--hash=sha256:1b8d99ee3277f8f3a3453a953120ee5c6c52f7ad89e459a25425cf62135f47b1 \
--hash=sha256:2d2b0c12e3d1c6d90646f9faa2d4c66f9861f3c6e577d7dbd25e733ed095ac56 \
--hash=sha256:40173425ab82bf0a7267d672b3e3aae9dd425eaee3a3641c6a5f040da3ff95e4 \
--hash=sha256:70d9da5fc14ef41565ff7ba9f476fb53166bf719a954339b2e9f42ed494a2f71 \
--hash=sha256:798d04437d30d6b4e6c1d520fe6ca800c340c9246f0dc8900d8b365df54b71b6 \
--hash=sha256:8607d636ef9232675507d97d252e1dcca5628bff79cb069fa945fff09d7bbb43 \
--hash=sha256:89484765304a779780f83489eb3aed5de5366f47fce7713fa5a917ebc281baa0 \
--hash=sha256:a46862d81078d2c8caa286c392f965ed72fb72eb1fed171910ba54fe8d546ed0 \
--hash=sha256:b43a85f5ddf7827a75491e79ca068a49c5e4efde8dbac844ecb89622a78458e4 \
--hash=sha256:c67109de8d9766c2afd6e7e64feb9e1a0d3eceb3b4123280c28344660c1a97cd \
--hash=sha256:ca6802eaf191acb6166e9e013dd277ea193ba27c1dca896ab7debf6dca758b6d \
--hash=sha256:e08595fe96d24c1fe13486b00d55ce73a7b37040a16e82365942606594c67a6b \
--hash=sha256:e486307ea649e7300b3535fac52fe0ba0b80aebe23143b662659d16e6a7c8b47 \
--hash=sha256:e99ee90a7b6e84dabef891ff7521eb59dae38953467bdb482f004ea522d3a64c \
--hash=sha256:f514ec0d95cd4578d74d4601058bd259f5baf91c937f2aaae942d4b070b8077f \
--hash=sha256:f78e343584cfff106fc3c361109b87949ad8028dc5aa667e0fccd26db8170d7d
# via -r src/backend/requirements-dev.in
pycparser==3.0 \
--hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \

View File

@ -403,9 +403,9 @@ cryptography==48.0.0 \
# via
# -c src/backend/requirements.txt
# pdfminer-six
django==5.2.14 \
--hash=sha256:58a63ba841662e5c686b57ba1fec52ddd68c0b93bd96ac3029d55728f00bf8a2 \
--hash=sha256:6f712143bd3064310d1f50fac859c3e9a274bdcfc9595339853be7779297fc76
django==5.2.15 \
--hash=sha256:0eb4a9bb1853a35b0286dbc6d916bd352c8c2687195a7f2d6f80cefd840e4970 \
--hash=sha256:5154a9bf84ac01dde011e367f355c07dbb329532e06810dcf3ef2af269e236e7
# via
# -c src/backend/requirements.txt
# django-silk
@ -442,9 +442,9 @@ gprof2dot==2025.4.14 \
--hash=sha256:0742e4c0b4409a5e8777e739388a11e1ed3750be86895655312ea7c20bd0090e \
--hash=sha256:35743e2d2ca027bf48fa7cba37021aaf4a27beeae1ae8e05a50b55f1f921a6ce
# via django-silk
idna==3.16 \
--hash=sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5 \
--hash=sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d
idna==3.17 \
--hash=sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c \
--hash=sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f
# via
# -c src/backend/requirements.txt
# requests

View File

@ -87,23 +87,23 @@ bcrypt==5.0.0 \
--hash=sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822 \
--hash=sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b
# via paramiko
bleach==4.1.0 \
--hash=sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da \
--hash=sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994
bleach[css]==6.4.0 \
--hash=sha256:4202482733d85cedd04e59fcb2f89f4e4c7c385a78d3c3c23c30446843a37452 \
--hash=sha256:4b6b6a54fff2e69a3dde9d21cc6301220bee3c3cb792187d11403fd795031081
# via django-markdownify
blessed==1.42.0 \
--hash=sha256:34b460b77562ed21f807cfd7c527b983b0cc300c98810c8076f283b7bcd45ba7 \
--hash=sha256:f96c4a6dc664b48e0b832fa732acc16df67abd30f0ec35babf99025982f21852
blessed==1.44.0 \
--hash=sha256:d3b5037f0143eb7c80b2000be5bcdf86161af68aed231b91deadbce4e7379785 \
--hash=sha256:e1d2ed93d3d90d0a1494a8b134d188c83fb89ae25590af638b921e8c1c8fa223
# via -r src/backend/requirements.in
boto3==1.43.14 \
--hash=sha256:574335744656cfed0b362a0a0467aaf2eb2bf15526edcd02d31d3c661f4b09e4 \
--hash=sha256:5c0a994b3182061ee101812e721100717a4d664f9f4ceaf4a86b6d032ce9fc2d
boto3==1.43.17 \
--hash=sha256:8cf48babdd52ff0e2d891dc661143780b361d3776a3be06cd719da0696995074 \
--hash=sha256:f6b3862a0b14e237f9323223ee76b0563e87a6bbe6d94a42e7b008a901ba8950
# via
# django-anymail
# django-storages
botocore==1.43.14 \
--hash=sha256:1f4a2a95ea78c10398e78431e98c1fe47adb54a7b10a32975144c1f541186658 \
--hash=sha256:b9e500737e43d2f147c9d4e23b54360335e77d4c0ba90a318f51b65e06cb8516
botocore==1.43.17 \
--hash=sha256:27f4ecb80cf1e5be70415fc4a4d3db3907d41ef8178c9df822364f275427d375 \
--hash=sha256:499af7c942ecfd404322974e82c6b5d05a8ea16e9f19320b353e16f401adc5b4
# via
# boto3
# s3transfer
@ -501,9 +501,9 @@ defusedxml==0.7.1 \
--hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \
--hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61
# via python3-openid
django==5.2.14 \
--hash=sha256:58a63ba841662e5c686b57ba1fec52ddd68c0b93bd96ac3029d55728f00bf8a2 \
--hash=sha256:6f712143bd3064310d1f50fac859c3e9a274bdcfc9595339853be7779297fc76
django==5.2.15 \
--hash=sha256:0eb4a9bb1853a35b0286dbc6d916bd352c8c2687195a7f2d6f80cefd840e4970 \
--hash=sha256:5154a9bf84ac01dde011e367f355c07dbb329532e06810dcf3ef2af269e236e7
# via
# -r src/backend/requirements.in
# django-allauth
@ -585,9 +585,9 @@ django-maintenance-mode==0.22.0 \
--hash=sha256:502f04f845d6996e8add321186b3b9236c3702de7cb0ab14952890af6523b9e5 \
--hash=sha256:a9cf2ba79c9945bd67f98755a6cfd281869d39b3745bbb5d1f571d058657aa85
# via -r src/backend/requirements.in
django-markdownify==0.9.1 \
--hash=sha256:06ff2994ff09ce030b50de8c6fc5b89b9c25a66796948aff55370716ca1233af \
--hash=sha256:24ba68b8a5996b6ec9632d11a3fd2e7159cb7e6becd3104e0a9372b5a2a148ef
django-markdownify==0.9.7 \
--hash=sha256:2e59dfd445ad6362288f7bc1574e45d8356848463e261b878fd70df6a272c47f \
--hash=sha256:ca3a6361f952d1c1b661c13229f69219a36e745358aa0f377212aef8f09e4cf3
# via -r src/backend/requirements.in
django-money==3.6.0 \
--hash=sha256:94402f2831f2726b94ef2da35b4059441b4c0aedfc47b312472200d4ffdf8d73 \
@ -597,9 +597,9 @@ django-mptt==0.18.0 \
--hash=sha256:bfa3f01627e3966a1df901aeca74570a3e933e66809ebf58d9df673e63627afb \
--hash=sha256:cf5661357ff22bc64e20d3341c26e538aa54583aea0763cfe6aaec0ab8e3a8ee
# via -r src/backend/requirements.in
django-oauth-toolkit==3.2.0 \
--hash=sha256:bd2cd2719b010231a2f370f927dbcc740454fb1d0dd7e7f4138f36227363dc26 \
--hash=sha256:c36761ae6810083d95a652e9c820046cde0d45a2e2a5574bbe7202656ec20bb6
django-oauth-toolkit==3.3.0 \
--hash=sha256:2b375d14b1c0ff86e5df5e5de5d1a6d9868873304f54aca37c50158552d5b922 \
--hash=sha256:7af1365cd80735211454b0dca810bc9e860c631e61fd17768ad0fa331737954f
# via -r src/backend/requirements.in
django-otp==1.3.0 \
--hash=sha256:5277731bc05b6cdbf96aa84ac46018e30ed5fb248086053b0146f925de059060 \
@ -665,9 +665,9 @@ djangorestframework-simplejwt[crypto]==5.5.1 \
--hash=sha256:2c30f3707053d384e9f315d11c2daccfcb548d4faa453111ca19a542b732e469 \
--hash=sha256:e72c5572f51d7803021288e2057afcbd03f17fe11d484096f40a460abc76e87f
# via -r src/backend/requirements.in
docutils==0.22.4 \
--hash=sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968 \
--hash=sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de
docutils==0.23 \
--hash=sha256:25d013af9bf23bc1c7b2b093dff4208166c53a94786c9e447808335ef1185fea \
--hash=sha256:746f5060322511280a1e50eb76846ed6bf2342984b2ac04dc42caa1a8d78799e
# via -r src/backend/requirements.in
drf-spectacular==0.29.0 \
--hash=sha256:0a069339ea390ce7f14a75e8b5af4a0860a46e833fd4af027411a3e94fc1a0cc \
@ -868,9 +868,9 @@ icalendar==7.1.2 \
--hash=sha256:01c76243c76c549f58bb51510a8f0a4edb7c539726adda1356dfd0dc04fb7a53 \
--hash=sha256:ebc43ebeb357be98984b573d975118008dee3410d8df28b054ef2943cf3e367e
# via django-ical
idna==3.16 \
--hash=sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5 \
--hash=sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d
idna==3.17 \
--hash=sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c \
--hash=sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f
# via
# django-anymail
# requests
@ -1312,7 +1312,6 @@ packaging==26.2 \
--hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \
--hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661
# via
# bleach
# gunicorn
# opentelemetry-instrumentation
paramiko==5.0.0 \
@ -1430,9 +1429,9 @@ pip-licenses==5.5.5 \
--hash=sha256:60750c006adf7a0910347b726e8ee9fee3bc8d2e7c8307a5c4ec0776c8e2a276 \
--hash=sha256:f4c4c6d9e6a03612cf59f29f19dc8ab54904d82e055b8e191498f2279a224e14
# via -r src/backend/requirements.in
platformdirs==4.9.6 \
--hash=sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a \
--hash=sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917
platformdirs==4.10.0 \
--hash=sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7 \
--hash=sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a
# via pint
ppf-datamatrix==0.2 \
--hash=sha256:819be65eae444b760e178d5761853f78f8e5fca14fec2809b5e3369978fa9244 \
@ -1524,9 +1523,9 @@ pynacl==1.6.2 \
--hash=sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2 \
--hash=sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9
# via paramiko
pypdf==6.12.1 \
--hash=sha256:3953a097b9f26d4e0ead5ff95943d9971377557662a91d8872186053cd71d30a \
--hash=sha256:8fa2a2321cf16247ed848bd7c97f193a60c08670d04abed5b0138327e51c43b0
pypdf==6.12.2 \
--hash=sha256:111669eb6680c04495ae0c113a1476e3bf93a95761d23c7406b591c80a6490b1 \
--hash=sha256:67b2699357a1f3f4c945940ea80826349ee507c9e2577724a14b4941982c104d
# via -r src/backend/requirements.in
pyphen==0.17.2 \
--hash=sha256:3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd \
@ -1733,9 +1732,9 @@ rapidfuzz==3.14.5 \
--hash=sha256:fbf1b8bb2695415b347f3727da1addca2acb82c9b97ac86bebf8b1bead1eb12d \
--hash=sha256:feedf219672eef83ea6be6f3bb093bba396a8560fc75be85ba225f082903df0a
# via -r src/backend/requirements.in
redis==7.4.0 \
--hash=sha256:64a6ea7bf567ad43c964d2c30d82853f8df927c5c9017766c55a1d1ed95d18ad \
--hash=sha256:a9c74a5c893a5ef8455a5adb793a31bb70feb821c86eccb62eebef5a19c429ec
redis==8.0.0 \
--hash=sha256:a00c5355432051ac14e593b8b197fc76c887ee12d55a0984f69328a1115fdc49 \
--hash=sha256:c938c18338585009f0bc310f4c7e4e4b4d37639356c4ac072cedf3af570c8dc7
# via django-redis
referencing==0.37.0 \
--hash=sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231 \
@ -1751,132 +1750,147 @@ requests==2.34.2 \
# django-anymail
# django-oauth-toolkit
# opentelemetry-exporter-otlp-proto-http
rpds-py==0.30.0 \
--hash=sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f \
--hash=sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136 \
--hash=sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3 \
--hash=sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7 \
--hash=sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65 \
--hash=sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4 \
--hash=sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169 \
--hash=sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf \
--hash=sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4 \
--hash=sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2 \
--hash=sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c \
--hash=sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4 \
--hash=sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3 \
--hash=sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6 \
--hash=sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7 \
--hash=sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89 \
--hash=sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85 \
--hash=sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6 \
--hash=sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa \
--hash=sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb \
--hash=sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6 \
--hash=sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87 \
--hash=sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856 \
--hash=sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4 \
--hash=sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f \
--hash=sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53 \
--hash=sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229 \
--hash=sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad \
--hash=sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23 \
--hash=sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db \
--hash=sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038 \
--hash=sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27 \
--hash=sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00 \
--hash=sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18 \
--hash=sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083 \
--hash=sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c \
--hash=sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738 \
--hash=sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898 \
--hash=sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e \
--hash=sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7 \
--hash=sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08 \
--hash=sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6 \
--hash=sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551 \
--hash=sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e \
--hash=sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288 \
--hash=sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df \
--hash=sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0 \
--hash=sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2 \
--hash=sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05 \
--hash=sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0 \
--hash=sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464 \
--hash=sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5 \
--hash=sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404 \
--hash=sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7 \
--hash=sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139 \
--hash=sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394 \
--hash=sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb \
--hash=sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15 \
--hash=sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff \
--hash=sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed \
--hash=sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6 \
--hash=sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e \
--hash=sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95 \
--hash=sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d \
--hash=sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950 \
--hash=sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3 \
--hash=sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5 \
--hash=sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97 \
--hash=sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e \
--hash=sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e \
--hash=sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b \
--hash=sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd \
--hash=sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad \
--hash=sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8 \
--hash=sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425 \
--hash=sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221 \
--hash=sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d \
--hash=sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825 \
--hash=sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51 \
--hash=sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e \
--hash=sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f \
--hash=sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8 \
--hash=sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f \
--hash=sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d \
--hash=sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07 \
--hash=sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877 \
--hash=sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31 \
--hash=sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58 \
--hash=sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94 \
--hash=sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28 \
--hash=sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000 \
--hash=sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1 \
--hash=sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1 \
--hash=sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7 \
--hash=sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7 \
--hash=sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40 \
--hash=sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d \
--hash=sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0 \
--hash=sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84 \
--hash=sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f \
--hash=sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a \
--hash=sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7 \
--hash=sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419 \
--hash=sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8 \
--hash=sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a \
--hash=sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9 \
--hash=sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be \
--hash=sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed \
--hash=sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a \
--hash=sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d \
--hash=sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324 \
--hash=sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f \
--hash=sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2 \
--hash=sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f \
--hash=sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5
rpds-py==2026.5.1 \
--hash=sha256:01d17b29c0c23d82b1f4751147ec49cf451f1fc2554eb9ef5f957e55d2656ead \
--hash=sha256:036a36a87fb1cd3b214d11c4b3c4f7d2ddad933625dca1c900b56a057c07740a \
--hash=sha256:0408a24e44feb919423dc6d9da677cb5cddb894d2ca9e763967d156d9c60fab4 \
--hash=sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256 \
--hash=sha256:0957cf3c2b8632ec7aaebffebea8005b353cc2a237b6e2ae3c2cac0820704cfb \
--hash=sha256:0a5ae4dbe43c1076983b72616496919872ae7bbe7a1e21cc48336bc3154d130b \
--hash=sha256:0a7d1eec967df0e9b22614a5e177622e0c89611d03727fa0cb48e45028907870 \
--hash=sha256:0b35217adefe87f2fe4db7e9766cabe84744bfe9616d9667be18988928c7f2dc \
--hash=sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08 \
--hash=sha256:141c9498daf2ace9eda35d2b0e376f9ea8b058d84f2aef4f96fccfd449a2f251 \
--hash=sha256:1841d067089e117142d79b98aa0df2f08b52f2ecc1819dd2700636c0db74a473 \
--hash=sha256:19cb09fab7b7fc96b2a6e28f2e34b72a3705ff27b37edb77455316e5d3f3dc9b \
--hash=sha256:1c27c5f6102eac8c03e7595a00827a53b271ba40a53b59ff8709170e0855ea4a \
--hash=sha256:1ebb2f0ab7e16132995a72de805170e0203df0c3dd22e1ef1cd1fdd90bd7a131 \
--hash=sha256:1f2c391c3059798093b65df23aca2cac150460ae9c630d99dec83d703d9485b9 \
--hash=sha256:205dde846f24332ab0c1188699a043b8d165b79bb84529ce272c45048ff6be01 \
--hash=sha256:21846aac0ed2e0589f38c12dc44e77bb64e494b771eadbcf169cba00566ba7ba \
--hash=sha256:21942f52dbbd5f8758bf021213d28bd45c39e873e65e2407faf5f1846f5761ad \
--hash=sha256:277f6c82f0580848796c7ecc8a7173aa3bfb928e4ff831261c2f60a81dc270db \
--hash=sha256:27b74c10ed6a8f190f4287f53bcfea348b92a84a9c9f70d30183d1e6172d580d \
--hash=sha256:296c799becfa849c779c8725494fe9ed94959ed886787df4364b058465bad7f0 \
--hash=sha256:2c595a1d9255dce0599e13130d1440ab2506654f2b50294226ee06402f8fef63 \
--hash=sha256:2c817a189d4ee14290420e5ff051e4dd6baa13f3edf84685071dee07a6d538ee \
--hash=sha256:2d88621d6a7d4dfa633d21abe90f280bb205274e16b1d1e61c6ad4640b2453b7 \
--hash=sha256:3350ec808fb538fe71a1f94dfaa0e29c598dfad805ce49f0caec5ae3183c652b \
--hash=sha256:3397a5ed7174dc2786bb214030232fc36fe8e5584fec43a9952cc542b1a12036 \
--hash=sha256:3574b55c604b8f75dacb007136508bbc0db406e626301778096a133327e7f2fb \
--hash=sha256:3609e9939a8a76cd904cf98a3f1f13b5dc7e150adeaee89e0ea09652ea213e16 \
--hash=sha256:3684a59b158a7683aaeb8e25352e9a9dd2122cec78f2d8530266e4f91b4c7b3f \
--hash=sha256:3966b82dd563176396df030f3dd52a6e54cb69b718e95e78bd555ed3d1e0185d \
--hash=sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d \
--hash=sha256:40ff257542e04796880e011e15cd4dc21c2599975df2aaa8f2c8495ca574e1a5 \
--hash=sha256:413b424f7c4ee65ab5e5be91f5731be0f8b41a1ee2b12dfe810d716312e95a78 \
--hash=sha256:42d0f20e85e549c870749d0e247f0c10d318a45b7e9676d575d2dcb04a1b2e66 \
--hash=sha256:43bca78665423cabae77146f2fe7ce55272b6c8d55d82cca83effd42c7e13972 \
--hash=sha256:453895624ecf7db7063b1004e44037522bbaef9ff6a945e59bc71662d7a03abd \
--hash=sha256:4860b603ddda0475a8885499b3729e90229d480105b42651962a5397d995fa89 \
--hash=sha256:4be8b1d2a705cc37d08256004e1d07de143fa0075c8e85a3df020b776f62b732 \
--hash=sha256:4e237e139f94d3c036fd28eb9f564c99055476ff4ff05cd42be55ce349b5aa02 \
--hash=sha256:4fb8d2e7cb2f850b169806d61d1b991738acec96500a75c30f49caf064ce7cef \
--hash=sha256:55d8f9b7b78c9538fc9e04e82ec0e888ff0c3cffcfad152c77e57cd09351a98a \
--hash=sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c \
--hash=sha256:5d333a7127d4b307601ac37792bee01bb95c867cbfacf21b6375b804d6bbd723 \
--hash=sha256:613fc4ee9eaef26dc5840666214dd6fbcebcf32f46e76f4abc473059f4e13dda \
--hash=sha256:6142dbd80c4df62a5d899f0d616d417f84e0bc8d32526c8e5589019d75d028a7 \
--hash=sha256:62ae3853454fe9ef283a03c96c2d835d39e84b14643a9d62c82ef0fb87d702ca \
--hash=sha256:63c2c4c213f1a4e3f3de28ecab029dbdee976324e729c0d7a55211be72576b02 \
--hash=sha256:656a042550878f12d45752452d47094b7cfe5ad1e9d7b87b5a22ad3ae5ff8015 \
--hash=sha256:66c93681c4729e4e3ecba31b8179fae083ff3118841672835140338b4b9867c1 \
--hash=sha256:6736718bd4fc49cbcb538ba30516fdbef161522acefb739657d48b97bd864fed \
--hash=sha256:68700371c5d7ae1412862ddfa719090925c93ecf351c566d66f09d04b136ea00 \
--hash=sha256:6c3d771a46ec18b12af06ce36243a9a80b07a5d0515236332d90863ca8bb326a \
--hash=sha256:6c7fcf61d44cacecaf3aea542b0e053db77972a4573e7ceda16fb2b399161195 \
--hash=sha256:6f249f8b860a200ad35193af961183ebe9132710484e6f6ce0cf89fd83c63a9a \
--hash=sha256:73c4bd4f70294737b5206a3e8e30ccadbf8a60301831c8ea23eec5dbeea1ecfa \
--hash=sha256:7559f72b94ae52659086c595dfa017cde03155f7832071d30959049052cb3ece \
--hash=sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df \
--hash=sha256:77c004fdc7b891967106f78ddfd7b076bfe6813c6139c6fff6aed3bcaa960b26 \
--hash=sha256:7818f8d0a415be74d2be3590b0a1c1f463a642f4d0217e7d10602dceef5b79aa \
--hash=sha256:7944270ae71383f6e2657dd7d5ce4eeb4ac2d0059a6738f0510583d462ab4842 \
--hash=sha256:7bd530e6a530bb3ea892f194fafa455f3516ac25ecf7143fd33c09be62b0470a \
--hash=sha256:8213afbe8a3a906fb9acb2014423fe3359ee783d0bf90995f70623a3217bfa6c \
--hash=sha256:83bcf894486c9d78dd290d3c0124ff6dd8875d3025e2090a8ec49fcc37c55fdd \
--hash=sha256:85264a90ff4c05c1568dd65f5921c837614b67c60358fb4c17df3b7f2e90690a \
--hash=sha256:88647f43a73c4e01be19b04ceef0c8d3a1958153604d13c773becd8016f2a0cf \
--hash=sha256:8895840ac4809e5f60c88fd07617cd71326e73d6e5a8aa783c5c0f7c24985de2 \
--hash=sha256:8ba264fa49be666cd9cc56bf34ec7002fb3d27a4aee5bcb4d43d0d18feb1bb6f \
--hash=sha256:8bff7073db3899158fff55ebf57b113a67030af26f80a18978f9f0aa60250ddf \
--hash=sha256:8c43a8a973270fd173bf48cdf80bbe66312421cba68d40845034f174f2389049 \
--hash=sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3 \
--hash=sha256:90f628283be835db980c941767d41c9a27b5239e54ba0a9c1335247e82406964 \
--hash=sha256:94068eb3ae6d43f5a786b7db96a406a34e6d5c24489feef32fd6e8946ea7b291 \
--hash=sha256:980450826cf22e133c57e0835070bdd0dd3f73b9b708c3ce223def2cb9469e14 \
--hash=sha256:99ab6ba7bfa2cb0f96a04e3652355bf04e3f51aceb1e943b8541dab7ba4828cc \
--hash=sha256:9af8905b8f854990e40d5206aa5ac58d9b0fe0b7f351ff2bb086c20f6c8c6a47 \
--hash=sha256:9baffb505aff33acc69b422a19f77806680f3c8632227d79f48de8a810d1c2c5 \
--hash=sha256:9cdddb6c1207d284d94fd1530adf57fbd797fe7c4b8704ba85f49414f2557e7d \
--hash=sha256:9e25b7088f9ccbfc0dfcaa52bf969300ca229e10ecf758974ebcbb080a4b37bb \
--hash=sha256:a04df86b3f0fade39ec8fd0e0aab089b1da9fbd2b48df778a57ef96f5e7d38df \
--hash=sha256:a05fa4f41f37ec97c9c260441a940450a192f78d774d2b097eee1379f1e1246a \
--hash=sha256:a2999883eedf72fdfb7520b92c7d4ec2572a71ff40239377aa604cc529eecafc \
--hash=sha256:aad1bff7f666b9598e573815affd666aac6a13a585dde336f843e33350c7fadc \
--hash=sha256:abe76bcdba31e576cb83eeb8797aa0d882b738fef6dc65d0601fc753806a5b46 \
--hash=sha256:ad3773236e95f7f33991eb125224b7da66f206504d032a253a02da7e134519fb \
--hash=sha256:af03e34e860047bc7a352b842856fcf78798fbb81132cc98bd2f907ab4eb9cd2 \
--hash=sha256:b1b964e3ab599e718dc46c018d104b1ebc007cbc6567d827c94a687fca56d77e \
--hash=sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb \
--hash=sha256:b317c87a13f769a4e787819bd508aaa5d69aa09b0880de9af6d3a8a54571cdec \
--hash=sha256:b3cc20c0d800af78fd0fac68086e28c1856cec51ea528bb81ea851aa40d39325 \
--hash=sha256:b4e4bc98639ec915f512fde3aa7a95e0041d95d9c3cc86eea841fa63cb1e8600 \
--hash=sha256:b5c30f3f04eef4fbd362226a6f31d7c8895ca4fbb6e0b790f6890a98d8da8559 \
--hash=sha256:b5f077b44a4f7808520f66dae234988d867deb9aed9be5da057ce9ba831b2a41 \
--hash=sha256:b6825cc329b290e93c5f6a9be2393118a763f6ccf6abd83704e0c102ca583644 \
--hash=sha256:b8d2f912928d426e8cfa396f7f3f8d29a59e6689c86dcca3c420730c1096322b \
--hash=sha256:b95d5e11fc712b752081183a55a244c03cd00570489edd7014d8899f8ceb8162 \
--hash=sha256:b9a6528956191c48c52294a592dbd4a8386d7048bdb25c0efcb6b966466c6d83 \
--hash=sha256:ba05adbf15d994c38ec0b7ab32e858e5110c21e9009a00a86545fd220f84e038 \
--hash=sha256:c0f920015df2a504bebaba6d4c31ccf3fcf942f92655c086da30b671aad19aa6 \
--hash=sha256:c396c1304de421050b3681ea70f371874b54d41b0151e96109758144c231e30b \
--hash=sha256:c39f5b67a8a2e67179ada2a954227d670fe65fa9098457f698f56ddf248709b3 \
--hash=sha256:c3df104083952a0e0c6f10de33e440eabe98fb6317d23e1a58c68f6df08d01b9 \
--hash=sha256:c74005a7bb87752acf351c93897ec63ad77a07a0da7ecad9c050e32e7286ba34 \
--hash=sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6 \
--hash=sha256:ca653c6546386227cd9800d1bef6a348099acf8db4250341da6d90f663d6dfcb \
--hash=sha256:cacedb7a6e167680acba45ad5716e89067d225dc80da0d7040cae8c81d4572fa \
--hash=sha256:cc68e231a77a5f0d774ae278a1f8e55c0456501820847c1e4efb3829f3441df6 \
--hash=sha256:ce87129d9f2c14fa6c4a8601fb80eb4488c80d38a20cd13758ef11123e14995d \
--hash=sha256:cea68bcd53467561ae2f96a6bdad1544299ba97b5b0ddcd5ac3d376e5c781c24 \
--hash=sha256:cef8ac28d26f4dda3533060c20fbf80a325458fa9fd23ea72a73cdfa8e978838 \
--hash=sha256:d0efbe45632665e53e3db8fe1e5692db58fc5cb9bab4459d570b83efefe11164 \
--hash=sha256:d3858b908218ee108d0bbfb2095ccc237648053c9bf98affad7cb079acaf1d97 \
--hash=sha256:de42116e69cb53b911cc34aee5ab98f36c597b822545045d49e938818b99e5e4 \
--hash=sha256:df1d2a1996755b24b9ecee92cb4d36c28f86f464a6a173349c26bab41e94b8c2 \
--hash=sha256:e07be2a9d7122bd6e82dea89814ef8dc893feb1aae97fec1630f3263bbb30e55 \
--hash=sha256:e0b360f316d966b048b085857630b3cc51f3db2f07b06f440eac8f695374d1e3 \
--hash=sha256:e10464d17df3b582745c25cec695cb9558bca2cb6ddb631aee1787fc72c767b2 \
--hash=sha256:e3a8ae58895ac107ed934a6bf51e5846f95c53b9b940c2c6d310838fd5846358 \
--hash=sha256:e4abbf391a70be864920858bf360f4fb380577c9a0f732438a1996726e2c195b \
--hash=sha256:eaaea962c68cdc68d4a533ba985ab8e9484277910bbfaa2ab3ef7732667bfed8 \
--hash=sha256:ed0954b524873214369184a9c82b0eaa45a3fbb9a798cd95b17e0d98499e7ea0 \
--hash=sha256:edf2765d84e42447f112ad877af8fe1db0089aaec5b28e88d6eab45e7fe99cea \
--hash=sha256:ef1013a8625c74043210190b246f5b1551e09757c1f356c6e4160ef96c5bc081 \
--hash=sha256:efef4ac29c6ff495531eb17ee705b62841ecaa291b7c7077e848ea03e237164d \
--hash=sha256:f3a5b10e8ce894825f380a8f1b6444cf73c294dfea62afbb2d13e3a9e630cec1 \
--hash=sha256:f3df3d16ded76f1f8c9cdebd0e1ea55fdf4c23b812de189814da7cf229c22a81 \
--hash=sha256:f414556f6e3958300ff941e40c9f97e3dc9774ddd1b3434c475d73dd354bbed3 \
--hash=sha256:fc09f82e63d4bcd58149572f857a431bae851dc747e313c3b5bdf7abb907fda8 \
--hash=sha256:fc0c0f878ea770a0a8a462456c5ad36fc9fe6358e6b76fdadc7f17575e0b8bf1 \
--hash=sha256:fe71bca7d547acb17027c7fd1624ff8aae623499c498d3e7011182c4de5c25e0 \
--hash=sha256:fea6e836d10abbe191d557d33bd58bd5987725fe63aa1eefe557d230209855bd
# via
# jsonschema
# referencing
s3transfer==0.17.0 \
--hash=sha256:9edeb6d1c3c2f89d6050348548834ad8289610d886e5bf7b7207728bd43ce33a \
--hash=sha256:ce3801712acf4ad3e89fb9990df97b4972e93f4b3b0004d214be5bce12814c20
s3transfer==0.18.0 \
--hash=sha256:239c13b09e65ad0346e1be7348b8a202dcad44ac7ea7c6eb858fc881dce739b6 \
--hash=sha256:3760b8b7ec1315da54048b2d626276732bee4300d054d492d4e1d43e20d4ecbd
# via boto3
sentry-sdk==2.60.0 \
--hash=sha256:0bd25e54e78ca02d0be512529fa644bbbf9e8470d7b26371294012d4ca93c978 \
--hash=sha256:28a536c03291c8bcb363cf35c611b32738ec118ff64d8d6383b096448ac4c803
sentry-sdk==2.61.0 \
--hash=sha256:1ca9b4bb777eb5be67004edab7eb894f21c6301f1d05ed64966719ad5d1764ce \
--hash=sha256:ec4d30273909cb1d198e03208b16ee70e2bc5d90a16fd9f1fb2fc6a72e1f03dc
# via
# -r src/backend/requirements.in
# django-q-sentry
@ -1890,9 +1904,7 @@ sgmllib3k==1.0.0 \
six==1.17.0 \
--hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \
--hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
# via
# bleach
# python-dateutil
# via python-dateutil
sqlparse==0.5.5 \
--hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \
--hash=sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e
@ -1911,6 +1923,7 @@ tinycss2==1.5.1 \
--hash=sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661 \
--hash=sha256:d339d2b616ba90ccce58da8495a78f46e55d4d25f9fd71dfd526f07e7d53f957
# via
# bleach
# cssselect2
# weasyprint
tinyhtml5==2.1.0 \
@ -2131,17 +2144,17 @@ xmlsec==1.3.17 \
# via
# -r src/backend/requirements.in
# python3-saml
zopfli==0.4.1 \
--hash=sha256:02086247dd12fda929f9bfe8b3962b6bcdbfc8c82e99255aebcf367867cf0760 \
--hash=sha256:07a5cdc5d1aaa6c288c5d9f5a5383042ba743641abf8e2fd898dcad622d8a38e \
--hash=sha256:27823dc1161a4031d1c25925fd45d9868ec0cbc7692341830a7dcfa25063662c \
--hash=sha256:2f992ac7d83cbddd889e1813ace576cbc91a05d5d7a0a21b366e2e5f492e7707 \
--hash=sha256:4238d4d746d1095e29c9125490985e0c12ffd3654f54a24af551e2391e936d54 \
--hash=sha256:5a4c22b6161f47f5bd34637dbaee6735abd287cd64e0d1ce28ef1871bf625f4b \
--hash=sha256:84a31ba9edc921b1d3a4449929394a993888f32d70de3a3617800c428a947b9b \
--hash=sha256:a899eca405662a23ae75054affa3517a060362eae1185d3d791c86a50153c4dd \
--hash=sha256:a93c2ecafff372de6c0aa2212eff18a75f6c71a100372fee7b4b129cc0b6f9a7 \
--hash=sha256:cb136a74d14a4ecfae29cb0fdecece58a6c115abc9a74c12bc6ac62e80f229d7 \
--hash=sha256:d7bcee1b189d64ec33d1e05cfa1b6a1268c29329c382f6ca1bd6245b04925c57 \
--hash=sha256:fdfb7ce9f5de37a5b2f75dd2642fd7717956ef2a72e0387302a36d382440db07
zopfli==0.4.2 \
--hash=sha256:18af6d2f778552d9bfdaa8e6caaf38c2bd57d0efba34a47860fcc4f6ba899696 \
--hash=sha256:1fc2fd1725c986f535ea323139ec1b974fa294ef9574e0fad840967d52dd38a9 \
--hash=sha256:65a847b1f234df456584f0267fb0f270a95772e675a98ae9c21f3b4839077b2f \
--hash=sha256:70c20595aa30aae246ac71a9fa471e2aa768318150ab814aeea2f651c59c6a32 \
--hash=sha256:92e30df03a00c08b3265c362566573b50a3af3b113b0dbe755071376635ce93d \
--hash=sha256:932083f7ca190fbd73cdf8ad7d32ec7231ecd25e1b3fade547cfb11b823ff91f \
--hash=sha256:a75e646fed3a2a42a82e69a81644009189c8ab4271691f020d52da8630d0580e \
--hash=sha256:bc25a3b3568910afb4bbd951d99835de29cade95417bca215339eac172eb381d \
--hash=sha256:d036b7179c225ce6bd73e68b5a176319ce8835739f494650e4124cd9c383b826 \
--hash=sha256:d1972b58083b85af460d8936f623d849b487c151b26eae925b25ec921ae6cd22 \
--hash=sha256:d3bc0b483575ab8849d9c15b349f78ae0141eb4dfe2ba260c8fdc0b3ba5c6c42 \
--hash=sha256:e679896fde39930dec1f46e5f4e1b1900378686168064966209ffb7b80e41743
# via fonttools

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