diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 124fbcbfd2..df3770b803 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -84,6 +84,16 @@ jobs: docker run --rm inventree-test test -f /home/inventree/gunicorn.conf.py docker run --rm inventree-test test -f /home/inventree/src/backend/requirements.txt docker run --rm inventree-test test -f /home/inventree/src/backend/InvenTree/manage.py + # Check that backend translations were compiled into the image (a few representative languages) + docker run --rm inventree-test test -f /home/inventree/src/backend/InvenTree/locale/de/LC_MESSAGES/django.mo + docker run --rm inventree-test test -f /home/inventree/src/backend/InvenTree/locale/fr/LC_MESSAGES/django.mo + docker run --rm inventree-test test -f /home/inventree/src/backend/InvenTree/locale/ru/LC_MESSAGES/django.mo + docker run --rm inventree-test test -f /home/inventree/src/backend/InvenTree/locale/zh_Hans/LC_MESSAGES/django.mo + # Check that frontend translations (lingui) were compiled into the static build output (a few representative languages) + docker run --rm inventree-test grep -q '"src/locales/de/messages.ts"' /home/inventree/src/backend/InvenTree/web/static/web/.vite/manifest.json + docker run --rm inventree-test grep -q '"src/locales/fr/messages.ts"' /home/inventree/src/backend/InvenTree/web/static/web/.vite/manifest.json + docker run --rm inventree-test grep -q '"src/locales/ru/messages.ts"' /home/inventree/src/backend/InvenTree/web/static/web/.vite/manifest.json + docker run --rm inventree-test grep -q '"src/locales/zh_Hans/messages.ts"' /home/inventree/src/backend/InvenTree/web/static/web/.vite/manifest.json - name: Build Docker Image # Build the development docker image (using docker-compose.yml) run: docker compose --project-directory . -f contrib/container/dev-docker-compose.yml build --no-cache diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index e894fc30d4..82a261c5dd 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -195,6 +195,9 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 5 + permissions: + contents: read # Required for actions/checkout + id-token: write # Required for GitHub OIDC env: COVERAGE: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b140056f45..e56b3f2f3d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: files: src/backend/requirements-dev\.(in|txt)$ - id: pip-compile name: pip-compile requirements-dev.in 3.14 - args: [src/backend/requirements-dev.in, -o, src/backend/requirements-dev-3.14.txt, -c, src/backend/requirements-3.14.txt, --python-version=3.14, -c, src/backend/requirements.txt] + args: [src/backend/requirements-dev.in, -o, src/backend/requirements-dev-3.14.txt, -c, src/backend/requirements-3.14.txt, --python-version=3.14, -c, src/backend/requirements.txt, -c, src/backend/requirements-dev.txt] files: src/backend/requirements-dev\.(in|txt)$ - id: pip-compile name: pip-compile requirements.txt diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..90afc6d2bc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,5 @@ +See [CONTRIBUTING.md](CONTRIBUTING.md) for codebase guidance. +Security reports must follow the security policy in [SECURITY.md](docs/docs/SECURITY.md) and provide how they interact with the [threat model](docs/docs/concepts/threat_model.md). + +Do not open a pull request without manual review by a human. Do not file AI generated issues under any circumstances. +Using AI generated content in issue/PR discussions might lead to code of conduct violations and/or bans. diff --git a/CHANGELOG.md b/CHANGELOG.md index 10a4265b85..930b77ad24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- [#12208](https://github.com/inventree/InvenTree/pull/12208) adds custom locale support for rendering currencies, dates and numbers within reports. This allows users to specify a custom locale for report rendering, which can be used to control the formatting of dates, numbers and currency values in the generated reports. - [#12204](https://github.com/inventree/InvenTree/pull/12204) adds new filtering options to PartCategoryTree and StockLocationTree API endpoints, allowing tree data to be fetched dynamically - [#12165](https://github.com/inventree/InvenTree/pull/12165) adds support for parameters against the PartCategory model - [#12103](https://github.com/inventree/InvenTree/pull/12103) adds column-based filtering to table views in the user interface. This extends the existing table filtering functionality by allowing users to apply filters directly to individual columns. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..90afc6d2bc --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +See [CONTRIBUTING.md](CONTRIBUTING.md) for codebase guidance. +Security reports must follow the security policy in [SECURITY.md](docs/docs/SECURITY.md) and provide how they interact with the [threat model](docs/docs/concepts/threat_model.md). + +Do not open a pull request without manual review by a human. Do not file AI generated issues under any circumstances. +Using AI generated content in issue/PR discussions might lead to code of conduct violations and/or bans. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c589902e9b..5fdb18c218 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,13 +1,27 @@ -### Contributing to InvenTree +# Contributing to InvenTree Hi there, thank you for your interest in contributing! Please read our contribution guidelines, before submitting your first pull request to the InvenTree codebase. -### Project File Structure +## About InvenTree + +InvenTree is an open-source inventory management system designed for makers, engineers, and small manufacturers. + +The codebase is split into two independently deployable layers: + +**Backend — Django (Python)** +A REST API built with Django and Django REST Framework. It owns all business logic, the database schema, background task processing (Django-Q2), plugin execution, and report generation. The backend can be used standalone via the API without the frontend. + +**Frontend — React (TypeScript)** +A single-page application built with React 19, Mantine 9, and Vite. It communicates exclusively through the backend REST API. It has its own build pipeline, test suite (Playwright), and i18n system (Lingui). The frontend lives entirely in `src/frontend/` and has no Django awareness. + +When making changes, be clear about which layer you are working in — they have separate toolchains, test runners, and linting rules. + +## Project File Structure The InvenTree project is split into two main components: frontend and backend. This source is located in the `src` directory. All other files are used for project management, documentation, and testing. -```bash +``` InvenTree/ ├─ .devops/ # Files for Azure DevOps ├─ .github/ # Files for GitHub @@ -39,11 +53,209 @@ InvenTree/ │ │ ├─ tsconfig.json # Settings for frontend compilation ├─ .pkgr.yml # Build definition for Debian/Ubuntu packages ├─ .pre-commit-config.yaml # Code formatter/linter configuration +├─ biome.json # JS/TS linting and formatting config ├─ CONTRIBUTING.md # Contribution guidelines and overview ├─ Procfile # Process definition for Debian/Ubuntu packages +├─ pyproject.toml # Python tooling config (Ruff, pytest, coverage, djLint, ty) ├─ README.md # General project information and overview ├─ SECURITY.md # Project security policy ├─ tasks.py # Action definitions for development, testing and deployment ``` +### Backend app domains + +The following Django apps are defined in `src/backend/InvenTree/`: + +| Directory | Domain | +|-----------|--------| +| `build/` | Build (manufacturing/assembly) orders | +| `common/` | Shared models, settings, utilities | +| `company/` | Suppliers, customers, manufacturers | +| `data_exporter/` | Data export functionality | +| `generic/` | Shared enums, events, and state machine utilities | +| `importer/` | Data import functionality | +| `InvenTree/` | Core configuration (settings, URLs, middleware) | +| `machine/` | Support for external machines and devices | +| `order/` | Purchase orders and sales orders | +| `part/` | Parts catalogue and categories | +| `stock/` | Stock items and locations | +| `report/` | Report templates and generation | +| `plugin/` | Plugin system | +| `users/` | User accounts and permissions | +| `web/` | Serves the React frontend static build to Django | + +### Frontend layout + +``` +src/frontend/src/ + components/ # Reusable React components + pages/ # Page-level route components + tables/ # Data table components + forms/ # Form components + hooks/ # Custom React hooks + states/ # Zustand global state + locales/ # i18n translation catalogues (Lingui) +``` + +## Development Setup + +The project uses [Invoke](https://www.pyinvoketasks.com/) (`tasks.py`) as the task runner. + +```bash +# One-time setup: creates venv at dev/venv/, installs deps, sets up pre-commit hooks +invoke dev.setup-dev + +# Apply database migrations +invoke migrate + +# Create an admin account (required to log in) +invoke superuser + +# Terminal 1 — Django dev server (port 8000) +invoke dev.server + +# Terminal 2 — Vite dev server with HMR (port 5173) +invoke dev.frontend-server + +# Terminal 3 (optional) — Background task worker +invoke worker +``` + +A VS Code Dev Container configuration is available at `.devcontainer/` and includes PostgreSQL 15 and Redis 7 sidecar services. + +To see all available tasks, run `invoke --list`. + +## Running Backend Tests + +Always pass `--keepdb` to backend test commands. It reuses the existing test database instead of rebuilding it from scratch on every run, which is significantly faster. + +```bash +# All backend tests +invoke dev.test --keepdb + +# Specific test module +invoke dev.test --keepdb --runtest=company.test_api + +# With coverage +invoke dev.test --keepdb --coverage + +# Migration tests only (skip --keepdb here — migrations must run against a fresh DB) +invoke dev.test --migrations +``` + +## Running Frontend Tests + +Frontend tests use [Playwright](https://playwright.dev/) and target Chromium and Firefox. The test runner automatically starts the Vite dev server (port 5173), the Django backend (port 8000), and the background worker — no manual server startup is needed. + +```bash +# Open Playwright's interactive UI (recommended for local development) +invoke dev.frontend-test + +# Run tests in headless mode from the frontend directory +cd src/frontend && npx playwright test + +# Run a specific test file +cd src/frontend && npx playwright test tests/pui_login.spec.ts + +# Run tests in a specific browser only +cd src/frontend && npx playwright test --project=chromium +``` + +Test files live in `src/frontend/tests/` and follow the `pui_*.spec.ts` naming convention. + +Locally, tests run with a single worker (required for Vite HMR compatibility). In CI they run with multiple workers against a production build for speed. + +## Code Style and Linting + +All formatting and linting runs automatically on commit via pre-commit hooks. Never skip them. + +### Python (Backend) + +Tool: **Ruff** (replaces Black, isort, flake8, and others). + +```bash +ruff check src/backend/ # Lint +ruff format src/backend/ # Format +``` + +- Google-style docstrings enforced +- Enabled rule sets: A, B, C, D, F, I, N, SIM, PIE, PLE, PLW, RUF, UP, W +- Type checking: `ty` (configured in `pyproject.toml`) + +### Django Templates + +Tool: **djLint** + +```bash +djlint src/backend/ --check +``` + +### JavaScript / TypeScript (Frontend) + +Tool: **Biome** (replaces ESLint + Prettier). + +```bash +cd src/frontend +yarn biome check . # Lint + format check +yarn biome format . # Format only +``` + +- Single quotes, space indentation +- `noUnusedImports` is an error + +## Making Changes + +### Backend + +- Each Django app has its own `models.py`, `serializers.py`, `views.py`, `urls.py`, and `filters.py`. +- API endpoints use Django REST Framework; document them with drf-spectacular decorators. +- After changing models, create a migration: `invoke dev.makemigrations`. +- Tests live in `test_*.py` files within each app directory. +- Background tasks go through Django-Q2; define them in the app's `tasks.py`. + +### Frontend + +- Pages register themselves in `src/frontend/src/router.tsx`. +- Server state fetching uses TanStack Query (React Query); avoid raw `useEffect` for data fetching. +- Global UI state uses Zustand stores in `states/`. +- UI components come from Mantine 9.x; use the Mantine component library before writing custom CSS. +- Add i18n strings with Lingui (`t` macro / `Trans` component); run `invoke int.frontend-trans` to extract from source and compile. (`invoke dev.translate` is for the full translation pipeline and is not intended for local use.) +- Playwright tests live in `src/frontend/tests/`. + +### Dependencies + +**Python:** Add packages to `src/backend/requirements.in` (or `requirements-dev.in` for dev-only tools). The pre-commit hook runs `pip-compile` automatically on commit to regenerate `requirements.txt`. Never edit `requirements.txt` directly. + +**Frontend:** Add packages with `yarn add ` from `src/frontend/`. + +### Migrations + +- Never edit existing migration files; always generate new ones. +- Keep migrations reversible where possible. +- Migration tests run in CI under the tag `migration_test`. + +## CI / CD + +GitHub Actions workflows live in `.github/workflows/`. Key workflows: + +| Workflow | Purpose | +|----------|---------| +| `qc_checks.yaml` | Lint, type-check, backend tests, API schema | +| `frontend.yaml` | Playwright E2E tests, frontend build | +| `docker.yaml` | Docker image builds | +| `translations.yaml` | Crowdin i18n sync | + +CI uses path-based filtering — only affected jobs run per PR. Coverage tracked via Codecov; quality analysis via SonarCloud. + +## Key Conventions + +- **REST API versioning:** Endpoint changes must not break the published API schema. Run `invoke dev.schema` and check the diff before opening a PR. +- **Plugin safety:** The plugin system (`plugin/`) is a public extension point; avoid breaking its interfaces. +- **No hardcoded secrets:** gitleaks runs in pre-commit and CI — any credential-shaped string will block merges. +- **Database portability:** Code must work on PostgreSQL, MySQL/MariaDB, and SQLite. Avoid database-specific SQL or ORM features. +- **Frontend translations:** Every user-visible string must be wrapped in a Lingui `t` macro or `` component. +- **Test tagging:** Tag slow or special tests (`@tag('migration_test')`, `@tag('performance_test')`) so CI can filter them selectively. + +--- + Refer to our [contribution guidelines](https://docs.inventree.org/en/latest/develop/contributing/) for more information! diff --git a/contrib/container/Dockerfile b/contrib/container/Dockerfile index 0427dd15ed..16418375e6 100644 --- a/contrib/container/Dockerfile +++ b/contrib/container/Dockerfile @@ -145,6 +145,9 @@ ENV PATH=/root/.local/bin:$PATH COPY --from=builder_stage ${INVENTREE_BACKEND_DIR}/InvenTree/web/static/web ${INVENTREE_BACKEND_DIR}/InvenTree/web/static/web COPY --from=builder_stage /root/.local /root/.local +# Compile Django backend translations during image build +RUN bash -c "cd '${INVENTREE_HOME}' && invoke int.backend-compilemessages" + # Launch the production server CMD ["sh", "-c", "exec gunicorn -c ./gunicorn.conf.py InvenTree.wsgi -b ${INVENTREE_WEB_ADDR}:${INVENTREE_WEB_PORT} --chdir ${INVENTREE_BACKEND_DIR}/InvenTree"] diff --git a/contrib/container/requirements.txt b/contrib/container/requirements.txt index b183af8118..066208a24c 100644 --- a/contrib/container/requirements.txt +++ b/contrib/container/requirements.txt @@ -236,26 +236,26 @@ typing-extensions==4.15.0 \ # via # -c src/backend/requirements.txt # psycopg-pool -uv==0.11.19 \ - --hash=sha256:28b0d612a766eb25756dbaa315433b726e93affa467d29a2682cc317547952ba \ - --hash=sha256:2d663bacb97e2e8412d1c26eace28c7ebbde9d6f5d7d78760fafd114d693817f \ - --hash=sha256:2e0e0b8ad59ec56f1440d6e4313b64a1d8119275dcec73d19eef33c43f99428c \ - --hash=sha256:301fd78309fc545c2cec2bfcc61a6bbdde876856c6d2041502737cf44085c178 \ - --hash=sha256:32d7988c0dfb6f90941f201c871a4478e96e4f2a32bdb2256d62a78ee20593fc \ - --hash=sha256:480fc34a8d0967af6a90b3f99a6e5687cd5c6e29528de96bec04d6e305a59363 \ - --hash=sha256:50e4d4796ca1a6da359a4f723a0fea86640c381d3ff4fa759a41badd7cb52dee \ - --hash=sha256:574f5dd4f31666661ea6386d3b91c5f0e8b84a8cae98ebba447c4674f2e6a4c7 \ - --hash=sha256:62b0b35a51d3034ff30ecd0f381e9bbc20d5b335754f54b098da29424d551ceb \ - --hash=sha256:65e932720daed1af1f720a0ff5f9b33ee5f7ad97488dcceceb85154fc1323b82 \ - --hash=sha256:7222f45b5541551057bfc2e3021f113800704f665c119fdf3ea700c6c4859b21 \ - --hash=sha256:731d9fab8db5d41590af64236d03f8069c8da665fd0f9493b85985f19c86cd90 \ - --hash=sha256:7fdd881cd6d80782afcf8c1d446dd15a42985167fd812b763d38ba1e4a8d944d \ - --hash=sha256:8f90b6687a480d154595aa619fb836a9a20d00ce37293db8099aad924f2b18f9 \ - --hash=sha256:a98495b9dd67287d8c1a0786f98cb037a50f0ee6c3d648572edaa7137aabc277 \ - --hash=sha256:aa6a7e8d07b33ad22f4732848ebb1d9486503973c248d6e632c06ce4339fe347 \ - --hash=sha256:c729f56ffef9b945053412c839695e8a0b13758aa15b7763e95a7dd539a6f522 \ - --hash=sha256:f4aa17ffd719daf37b7a6265efd3ee4922a8ddaabaf0406d2b28c7e5ce2f20ff \ - --hash=sha256:f56f5bf853626a30423052d7ee00bf5cc940a08347d6ee7ede96862d084054a5 +uv==0.11.23 \ + --hash=sha256:03fbb0a1c7b6d15e96778bdd79e8d1826c6259fea17fc13337fb0744136953f2 \ + --hash=sha256:3a900c8fd757f8c3da9dc5532d9a22d30540e91fdefd63c93909fedbfd756655 \ + --hash=sha256:576d776a1ca62e3d8aba99f0d8ec607db91a5ebaf52feaff820f28ed820e1665 \ + --hash=sha256:61e6bd7e7f0fe24f103540ba19516443bea6e689022c787217310a1e64558e3f \ + --hash=sha256:6d7cea7d9ade3c1c3e3db1dfcc23d335bceaabf38f51e442b6f57f8f7885a9a6 \ + --hash=sha256:7a85330de0a7eb0d5c6cf03c80edfb86facad19df367a0b52fc906db1ab15ce9 \ + --hash=sha256:91d19c4249d7437b69b91c385134360d7ed9d0ee2e2e83e81d369867151e78c2 \ + --hash=sha256:985aa93c9d6223e32fc747e09662537c4073c9ebef59c0a4fd7c6949d1d24fb3 \ + --hash=sha256:9dd412127cbe0e115bd3fc5c6cbe9cf59f593273fafab9f7dc6b2ac95efcc7c1 \ + --hash=sha256:ac337dacd640aab1ef97cf00f8c01e2889f0fd0ef8460a0f4e816bf12bb5988b \ + --hash=sha256:b3f515fd6b43068f241467496bced62cb2ed36d52d4c0877cfe61a1240713d32 \ + --hash=sha256:b8abe7d6f5e0d92bd41a9c000bbd9c8387af7886df4790c0451a34e781b8a075 \ + --hash=sha256:bbc41182d655f92cd380ecdf378da7fc1598c6b19057208f450f0ee9c259f46a \ + --hash=sha256:c2089b992919858dabae89d410cbb5cecf9034d26bbb04f14e6da52dffced290 \ + --hash=sha256:ca1d37e851fb9323250385403d8512a71c0d1b6162c729ff4909f37cfd067920 \ + --hash=sha256:d256f90513d01ff6cbc2f17d88c0ccde65d138500df547ece214e6a50731c4b7 \ + --hash=sha256:d62410e5f60a961cfda00ead8a1cc5fd37d052afda021099e488e90c15419beb \ + --hash=sha256:e7e215d69ea21fd5824a63edf8fef933bee2c028a0c2930651cfa6b88ca4ff8e \ + --hash=sha256:f2476dda35866ea3ded3a5905759da2d32dfac36dfd5b3428191a99a8ce15b02 # via -r contrib/container/requirements.in wheel==0.47.0 \ --hash=sha256:212281cab4dff978f6cedd499cd893e1f620791ca6ff7107cf270781e587eced \ diff --git a/contrib/dev_reqs/requirements.txt b/contrib/dev_reqs/requirements.txt index a8768a8b2a..916e1453b7 100644 --- a/contrib/dev_reqs/requirements.txt +++ b/contrib/dev_reqs/requirements.txt @@ -1,8 +1,8 @@ # This file was autogenerated by uv via the following command: # uv pip compile contrib/dev_reqs/requirements.in -o contrib/dev_reqs/requirements.txt -c src/backend/requirements.txt -certifi==2026.5.20 \ - --hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \ - --hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d +certifi==2026.6.17 \ + --hash=sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432 \ + --hash=sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db # via # -c src/backend/requirements.txt # requests diff --git a/docs/docs/concepts/pricing.md b/docs/docs/concepts/pricing.md index 037387e019..8e1e5a5345 100644 --- a/docs/docs/concepts/pricing.md +++ b/docs/docs/concepts/pricing.md @@ -63,3 +63,9 @@ Currency exchange rates are updated periodically, using the configured currency ## Pricing Settings Refer to the [global settings](../settings/global.md#pricing-and-currency) documentation for more information on available currency settings. + +## Rendering Currencies in Reports + +Currency values can be rendered in report templates using the [`render_currency`](../report/helpers.md#render_currency) helper function. This function formats a currency amount according to a locale, and supports currency conversion within the template. + +See the [report helpers documentation](../report/helpers.md#currency-formatting) for full details and examples. diff --git a/docs/docs/report/helpers.md b/docs/docs/report/helpers.md index c8811f561c..1cde507797 100644 --- a/docs/docs/report/helpers.md +++ b/docs/docs/report/helpers.md @@ -320,15 +320,86 @@ The helper function `format_number` allows for some common number formatting opt show_docstring_description: false show_source: False -#### Example +#### Examples ```html {% raw %} {% load report %} -{% format_number 3.14159265359 decimal_places=5, leading=3 %} - + + +{% format_number 3.14159265359 decimal_places=5 %} + + + +{% format_number 3.14159265359 decimal_places=5 leading=3 %} + + + {% format_number 3.14159265359 integer=True %} + + +{% format_number 9988776.5 decimal_places=2 separator=True %} + + + +{% format_number 9988776.5 decimal_places=2 separator=True locale='de-de' %} + + + +{% format_number 0.175 multiplier=100 decimal_places=1 %} + + + +{% format_number 1234.5 decimal_places=2 max_decimal_places=6 %} + + +{% endraw %} +``` + +#### Custom Format Strings + +The `fmt` argument accepts a [Unicode number pattern](https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns) string (the same syntax used by [Babel](https://babel.pocoo.org/en/latest/numbers.html)). When `fmt` is provided it takes complete priority over the `decimal_places`, `max_decimal_places`, `leading`, and `separator` arguments — those arguments are silently ignored. + +The `integer` and `multiplier` arguments **are** still applied to the number before the format string is used. + +| Symbol | Meaning | +| --- | --- | +| `0` | Required digit — always rendered, even if zero | +| `#` | Optional digit — suppressed when not significant | +| `,` | Grouping separator (position defines group size) | +| `.` | Decimal separator | + +Common patterns: + +| Pattern | Example output | +| --- | --- | +| `0` | `1235` | +| `#,##0` | `1,235` | +| `0.00` | `1234.57` | +| `#,##0.00` | `1,234.57` | +| `000` | `007` | + +```html +{% raw %} +{% load report %} + + +{% format_number 1234.5678 fmt='0.00' %} + + + +{% format_number 1234.5678 fmt='#,##0.00' %} + + + +{% format_number 1234.5678 fmt='#,##0.00' locale='de-de' %} + + + +{% format_number 9988776655.4321 fmt='#,##0' integer=True %} + + {% endraw %} ``` @@ -336,6 +407,25 @@ The helper function `format_number` allows for some common number formatting opt For rendering date and datetime information, the following helper functions are available: +Both functions resolve their output using the following priority order: + +1. **`fmt=` argument** — a [strftime format string](https://docs.python.org/3/library/datetime.html#format-codes). When provided, this takes full priority; `locale` and `date_format` are ignored. +2. **`locale=` argument** — when no `fmt` is given, Babel formats the value using the style set by `date_format` (default `medium`). +3. **Server `LANGUAGE_CODE`** — used as the locale when no `locale=` argument is supplied. + +#### Date Format Styles + +The `date_format` argument controls how Babel renders the date when locale-aware formatting is used. The four named styles are: + +| Style | `format_date` example (en-us, 2025-01-12) | `format_datetime` example (en-us, 2025-01-12 14:30) | +| --- | --- | --- | +| `full` | `Sunday, January 12, 2025` | `Sunday, January 12, 2025 at 2:30:00 PM UTC` | +| `long` | `January 12, 2025` | `January 12, 2025 at 2:30:00 PM UTC` | +| `medium` *(default)* | `Jan 12, 2025` | `Jan 12, 2025, 2:30:00 PM` | +| `short` | `1/12/25` | `1/12/25, 2:30 PM` | + +The exact output varies by locale — the table above uses `en-us`. + ### format_date ::: report.templatetags.report.format_date @@ -343,6 +433,35 @@ For rendering date and datetime information, the following helper functions are show_docstring_description: false show_source: False +#### Examples + +```html +{% raw %} +{% load report %} + + +{% format_date my_date %} + + + +{% format_date my_date fmt="%d/%m/%Y" %} + + + +{% format_date my_date locale='en-us' %} + + + +{% format_date my_date locale='en-us' date_format='short' %} + + + +{% format_date my_date locale='en-us' date_format='full' %} + + +{% endraw %} +``` + ### format_datetime ::: report.templatetags.report.format_datetime @@ -350,20 +469,31 @@ For rendering date and datetime information, the following helper functions are show_docstring_description: false show_source: False -### Date Formatting - -If not specified, these methods return a result which uses ISO formatting. Refer to the [datetime format codes](https://docs.python.org/3/library/datetime.html#format-codes) for more information! | - - -### Example - -A simple example of using the date formatting helper functions: +#### Examples ```html {% raw %} {% load report %} -Date: {% format_date my_date timezone="Australia/Sydney" %} -Datetime: {% format_datetime my_datetime format="%d-%m-%Y %H:%M%S" %} + + +{% format_datetime my_datetime %} + + + +{% format_datetime my_datetime fmt="%d-%m-%Y %H:%M" %} + + + +{% format_datetime my_datetime locale='en-us' %} + + + +{% format_datetime my_datetime locale='de-de' date_format='short' %} + + + +{% format_datetime my_datetime timezone="Australia/Sydney" locale='en-au' %} + {% endraw %} ``` @@ -373,11 +503,115 @@ Datetime: {% format_datetime my_datetime format="%d-%m-%Y %H:%M%S" %} The helper function `render_currency` allows for simple rendering of currency data. This function can also convert the specified amount of currency into a different target currency: -::: InvenTree.helpers_model.render_currency +::: report.templatetags.report.render_currency options: show_docstring_description: false show_source: False +#### Decimal Places + +When no decimal place arguments are provided, the locale/currency standard is used (e.g. 2 places for USD, 0 for JPY). + +`decimal_places` and `max_decimal_places` work the same way as in [`format_number`](#format_number): + +| Argument | Effect | +| --- | --- | +| `decimal_places=N` | Forces exactly N decimal digits (zero-padded) | +| `max_decimal_places=M` | Allows up to M decimal digits, suppressing trailing zeros beyond `decimal_places` | +| Both set | Forced minimum of `decimal_places`, optional up to `max_decimal_places` | +| Neither set | Locale/currency default (e.g. 2 for USD) | + +```html +{% raw %} +{% load report %} + + +{% render_currency order.total_price currency='USD' %} + + + +{% render_currency order.total_price currency='USD' decimal_places=3 %} + + + +{% render_currency order.total_price currency='USD' decimal_places=2 max_decimal_places=4 %} + + +{% endraw %} +``` + +#### Locale and Symbol Rendering + +The locale controls how the currency symbol and separators are rendered. For example, `USD 1234.56` with various locales: + +| Locale | Output | +| --- | --- | +| `en-us` | `$1,234.56` | +| `en-gb` | `US$1,234.56` | +| `en-au` | `USD1,234.56` | +| `de-de` | `1.234,56 $` | + +The locale is resolved in the following priority order: + +1. **Explicit `locale=` argument** — highest priority, always wins +2. **Server `LANGUAGE_CODE`** — fallback + +#### Leading Digits + +The `leading` argument specifies the minimum number of digits to render before the decimal point (zero-padded). This works identically to `leading` in [`format_number`](#format_number): + +```html +{% raw %} +{% load report %} + + +{% render_currency order.total_price currency='USD' %} + + + +{% render_currency order.total_price currency='USD' leading=4 %} + + +{% endraw %} +``` + +#### Custom Format Strings + +The `fmt` argument accepts a [Unicode number pattern](https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns) string (same syntax as [`format_number`](#custom-format-strings)). **When `fmt` is provided, it takes complete priority over `decimal_places`, `max_decimal_places`, and `leading`** — those arguments are ignored. + +The `locale`, `currency`, `multiplier`, and `include_symbol` arguments are still applied when `fmt` is set. + +To include the currency symbol in a `fmt` pattern, use the `¤` placeholder. Without it, no symbol appears regardless of `include_symbol`. + +| Pattern | Example output (en-us, USD) | +| --- | --- | +| `#,##0.00` | `1,234.56` (no symbol) | +| `¤#,##0.00` | `$1,234.56` | +| `¤#,##0.0000` | `$1,234.5600` | +| `¤ #,##0.00` | `$ 1,234.56` | + +```html +{% raw %} +{% load report %} + + +{% render_currency order.total_price currency='USD' fmt='#,##0.00' %} + + + +{% render_currency order.total_price currency='USD' fmt='¤#,##0.0000' locale='en-us' %} + + + +{% render_currency order.total_price currency='USD' fmt='#,##0.00' locale='de-de' %} + + + +{% render_currency order.total_price currency='USD' fmt='0.0000' decimal_places=2 %} + + +{% endraw %} +``` #### Example @@ -392,8 +626,12 @@ The helper function `render_currency` allows for simple rendering of currency da {% endfor %} + Total Price: {% render_currency order.total_price currency='NZD' decimal_places=2 %} + +Total Price: {% render_currency order.total_price currency='USD' locale='en-us' %} + {% endraw %} ``` diff --git a/docs/requirements.txt b/docs/requirements.txt index b0e38b0b6c..fd0a15017e 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -28,9 +28,9 @@ bracex==2.6 \ --hash=sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952 \ --hash=sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7 # via wcmatch -certifi==2026.5.20 \ - --hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \ - --hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d +certifi==2026.6.17 \ + --hash=sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432 \ + --hash=sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db # via # -c src/backend/requirements.txt # httpcore diff --git a/pyproject.toml b/pyproject.toml index 169e5a1f89..e1216eaaf6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,20 +102,20 @@ no-strip-extras=true generate-hashes=true [tool.ty.environment] -root = ["src/backend/InvenTree"] +extra-paths = ["src/backend/InvenTree"] [tool.ty.rules] unresolved-reference="ignore" # 21 # see https://github.com/astral-sh/ty/issues/220 unresolved-attribute="ignore" # 505 # need Plugin Mixin typing call-non-callable="ignore" # 8 ## invalid-assignment="ignore" # 17 # need to wait for better django field stubs -# invalid-method-override="ignore" # 99 +invalid-method-override="ignore" # 104 invalid-return-type="ignore" # 22 ## # possibly-missing-attribute="ignore" # 25 # https://github.com/astral-sh/ty/issues/164 unknown-argument="ignore" # 3 # need to wait for better django field stubs invalid-argument-type="ignore" # 49 no-matching-overload="ignore" # 3 # need to wait for betterdjango field stubs -possibly-unbound-attribute="ignore" # 21 +# possibly-unbound-attribute="ignore" # 21 [tool.coverage.run] source = ["src/backend/InvenTree", "InvenTree"] diff --git a/src/backend/InvenTree/InvenTree/api.py b/src/backend/InvenTree/InvenTree/api.py index a85498596f..1dfc53fa5c 100644 --- a/src/backend/InvenTree/InvenTree/api.py +++ b/src/backend/InvenTree/InvenTree/api.py @@ -494,7 +494,7 @@ class BulkCreateMixin: if unique_create_fields := getattr(self, 'unique_create_fields', None): existing = collections.defaultdict(list) for idx, item in enumerate(data): - key = tuple(item[v] for v in list(unique_create_fields)) # type: ignore[not-subscriptable] + key = tuple(item[v] for v in list(unique_create_fields)) # ty: ignore[not-subscriptable] existing[key].append(idx) unique_errors = [[] for _ in range(len(data))] diff --git a/src/backend/InvenTree/InvenTree/auth_overrides.py b/src/backend/InvenTree/InvenTree/auth_overrides.py index 9a223dd2f9..32de0107bd 100644 --- a/src/backend/InvenTree/InvenTree/auth_overrides.py +++ b/src/backend/InvenTree/InvenTree/auth_overrides.py @@ -114,9 +114,12 @@ class RegistrationMixin: """ if registration_enabled(self.REGISTRATION_SETTING): return True - logger.warning( - f'INVE-W12: Signup attempt blocked, because registration is disabled via setting {self.REGISTRATION_SETTING}.' - ) + # Only warn when this is an actual signup submission, not when called as + # a feature-availability check during login or other auth flows. + if request and request.method == 'POST' and 'signup' in request.path: + logger.warning( + f'INVE-W12: Signup attempt blocked, because registration is disabled via setting {self.REGISTRATION_SETTING}.' + ) return False def clean_email(self, email): diff --git a/src/backend/InvenTree/InvenTree/exceptions.py b/src/backend/InvenTree/InvenTree/exceptions.py index 161860339d..61bde3b275 100644 --- a/src/backend/InvenTree/InvenTree/exceptions.py +++ b/src/backend/InvenTree/InvenTree/exceptions.py @@ -19,10 +19,10 @@ logger = structlog.get_logger('inventree') def log_error( - path, - error_name=None, - error_info=None, - error_data=None, + path: Optional[str] = None, + error_name: Optional[str] = None, + error_info: Optional[str] = None, + error_data: Optional[str] = None, scope: Optional[str] = None, plugin: Optional[str] = None, ): diff --git a/src/backend/InvenTree/InvenTree/format.py b/src/backend/InvenTree/InvenTree/format.py index 3ee674b216..848c7e07ae 100644 --- a/src/backend/InvenTree/InvenTree/format.py +++ b/src/backend/InvenTree/InvenTree/format.py @@ -2,16 +2,9 @@ import re import string -from typing import Optional -from django.conf import settings -from django.utils import translation from django.utils.translation import gettext_lazy as _ -from babel import Locale -from babel.numbers import parse_pattern -from djmoney.money import Money - def parse_format_string(fmt_string: str) -> dict: """Extract formatting information from the provided format string. @@ -180,43 +173,3 @@ def extract_named_group(name: str, value: str, fmt_string: str) -> str: # And return the value we are interested in # Note: This will raise an IndexError if the named group was not matched return result.group(name) - - -def format_money( - money: Money, - decimal_places: Optional[int] = None, - fmt: Optional[str] = None, - include_symbol: bool = True, -) -> str: - """Format money object according to the currently set local. - - Args: - money (Money): The money object to format - decimal_places (int): Number of decimal places to use - fmt (str): Format pattern according LDML / the babel format pattern syntax (https://babel.pocoo.org/en/latest/numbers.html) - include_symbol (bool): Whether to include the currency symbol in the formatted output - - Returns: - str: The formatted string - - Raises: - ValueError: format string is incorrectly specified - """ - language = (None) or settings.LANGUAGE_CODE - locale = Locale.parse(translation.to_locale(language)) - if fmt: - pattern = parse_pattern(fmt) - else: - pattern = locale.currency_formats['standard'] - if decimal_places is not None: - pattern.frac_prec = (decimal_places, decimal_places) - - result = pattern.apply( - money.amount, - locale, - currency=money.currency.code if include_symbol else '', - currency_digits=decimal_places is None, - decimal_quantization=decimal_places is not None, - ) - - return result diff --git a/src/backend/InvenTree/InvenTree/helpers_model.py b/src/backend/InvenTree/InvenTree/helpers_model.py index ccd1cfd963..925046aef1 100644 --- a/src/backend/InvenTree/InvenTree/helpers_model.py +++ b/src/backend/InvenTree/InvenTree/helpers_model.py @@ -3,12 +3,10 @@ import io import ipaddress import socket -from decimal import Decimal from typing import Optional, cast from urllib.parse import urljoin, urlparse from django.conf import settings -from django.core.exceptions import ValidationError from django.core.validators import URLValidator from django.db.utils import OperationalError, ProgrammingError from django.utils.translation import gettext_lazy as _ @@ -16,8 +14,6 @@ from django.utils.translation import gettext_lazy as _ import requests import requests.exceptions import structlog -from djmoney.contrib.exchange.models import convert_money -from djmoney.money import Money from PIL import Image from common.notifications import ( @@ -31,7 +27,6 @@ from InvenTree.cache import ( get_session_cache, set_session_cache, ) -from InvenTree.format import format_money from InvenTree.ready import ignore_ready_warning logger = structlog.get_logger('inventree') @@ -252,85 +247,6 @@ def download_image_from_url( return img -def render_currency( - money: Money, - decimal_places: Optional[int] = None, - currency: Optional[str] = None, - multiplier: Optional[Decimal] = None, - min_decimal_places: Optional[int] = None, - max_decimal_places: Optional[int] = None, - include_symbol: bool = True, -): - """Render a currency / Money object to a formatted string (e.g. for reports). - - Arguments: - money: The Money instance to be rendered - decimal_places: The number of decimal places to render to. If unspecified, uses the PRICING_DECIMAL_PLACES setting. - currency: Optionally convert to the specified currency - multiplier: An optional multiplier to apply to the money amount before rendering - min_decimal_places: The minimum number of decimal places to render to. If unspecified, uses the PRICING_DECIMAL_PLACES_MIN setting. - max_decimal_places: The maximum number of decimal places to render to. If unspecified, uses the PRICING_DECIMAL_PLACES setting. - include_symbol: If True, include the currency symbol in the output - """ - if money in [None, '']: - return '-' - - if type(money) is not Money: - # Try to convert to a Money object - try: - money = Money( - Decimal(str(money)), - currency or get_global_setting('INVENTREE_DEFAULT_CURRENCY'), - ) - except Exception: - raise ValidationError( - f"render_currency: {_('Invalid money value')}: '{money}' ({type(money).__name__})" - ) - - if currency is not None: - # Attempt to convert to the provided currency - # If cannot be done, leave the original - try: - money = convert_money(money, currency) - except Exception: - pass - - if multiplier is not None: - try: - money *= Decimal(str(multiplier).strip()) - except Exception: - raise ValidationError( - f"render_currency: {_('Invalid multiplier value')}: '{multiplier}' ({type(multiplier).__name__})" - ) - - if min_decimal_places is None or not isinstance(min_decimal_places, (int, float)): - min_decimal_places = get_global_setting('PRICING_DECIMAL_PLACES_MIN', 0) - - if max_decimal_places is None or not isinstance(max_decimal_places, (int, float)): - max_decimal_places = get_global_setting('PRICING_DECIMAL_PLACES', 6) - - value = Decimal(str(money.amount)).normalize() - value = str(value) - - if decimal_places is not None and isinstance(decimal_places, (int, float)): - # Decimal place count is provided, use it - pass - elif '.' in value: - # If the value has a decimal point, use the number of decimal places in the value - decimal_places = len(value.split('.')[-1]) - else: - # No decimal point, use 2 as a default - decimal_places = 2 - - # Clip the decimal places to the specified range - decimal_places = max(decimal_places, min_decimal_places) - decimal_places = min(decimal_places, max_decimal_places) - - return format_money( - money, decimal_places=decimal_places, include_symbol=include_symbol - ) - - @ignore_ready_warning def getModelsWithMixin(mixin_class) -> list: """Return a list of database models that inherit from the given mixin class. diff --git a/src/backend/InvenTree/InvenTree/middleware.py b/src/backend/InvenTree/InvenTree/middleware.py index 98ffc08c22..0c6ff3c94b 100644 --- a/src/backend/InvenTree/InvenTree/middleware.py +++ b/src/backend/InvenTree/InvenTree/middleware.py @@ -106,6 +106,32 @@ apps_mfa_bypass = [ """App namespaces that bypass MFA enforcement - normal security model is still enforced.""" +def csrf_failure(request, reason=''): + """Custom CSRF failure handler. + + Returns a JSON response for API/headless requests so the frontend can + provide a meaningful error message to the user + """ + from django.views.csrf import csrf_failure as django_default + + if ( + request.path.startswith('/_allauth/') + or request.path.startswith('/api/') + or 'application/json' in request.headers.get('Accept', '') + or 'application/json' in request.headers.get('Content-Type', '') + ): + return JsonResponse( + { + 'detail': _( + 'CSRF verification failed. Ensure INVENTREE_SITE_URL and INVENTREE_TRUSTED_ORIGINS are configured correctly.' + ) + }, + status=403, + ) + + return django_default(request, reason=reason) + + class AuthRequiredMiddleware: """Check for user to be authenticated.""" diff --git a/src/backend/InvenTree/InvenTree/schema.py b/src/backend/InvenTree/InvenTree/schema.py index 55fa008349..edea87bfef 100644 --- a/src/backend/InvenTree/InvenTree/schema.py +++ b/src/backend/InvenTree/InvenTree/schema.py @@ -282,7 +282,7 @@ def postprocess_print_stats(result, generator, request, public): scopes = {} for path, details in rlt_dict.items(): if details['oauth']: - for scope in details['oauth']: + for scope in list(details['oauth']): if scope not in scopes: scopes[scope] = [] scopes[scope].append(path) @@ -343,7 +343,7 @@ def schema_for_view_output_options(view_class): return extended_view -def exclude_from_schema(klass: type, alternative_path: str) -> type: +def exclude_from_schema(klass: type[Any], alternative_path: str) -> type[Any]: """Decorator to exclude a view from the OpenAPI schema. This is used to hide legacy endpoints from the schema, while still retaining them for backwards compatibility. diff --git a/src/backend/InvenTree/InvenTree/setting/ldap.py b/src/backend/InvenTree/InvenTree/setting/ldap.py index 6e6f57989f..3b39179701 100644 --- a/src/backend/InvenTree/InvenTree/setting/ldap.py +++ b/src/backend/InvenTree/InvenTree/setting/ldap.py @@ -9,8 +9,8 @@ def get_ldap_config(debug: bool = False) -> dict: The returned settings will be updated into the globals() object, and will be used to configure the LDAP authentication backend. """ - import django_auth_ldap.config # type: ignore[unresolved-import] - import ldap # type: ignore[unresolved-import] + import django_auth_ldap.config # ty: ignore[unresolved-import] + import ldap # ty: ignore[unresolved-import] # get global options from dict and use ldap.OPT_* as keys and values global_options_dict = get_setting( diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index d5f643594c..06ba9dfc64 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -851,6 +851,7 @@ valid_cookie_modes = ['lax', 'strict', 'none'] COOKIE_MODE = COOKIE_MODE.capitalize() if COOKIE_MODE in valid_cookie_modes else False # Additional CSRF settings +CSRF_FAILURE_VIEW = 'InvenTree.middleware.csrf_failure' CSRF_HEADER_NAME = 'HTTP_X_CSRFTOKEN' CSRF_COOKIE_NAME = 'csrftoken' diff --git a/src/backend/InvenTree/InvenTree/test_middleware.py b/src/backend/InvenTree/InvenTree/test_middleware.py index 410822d3cf..b6b89cf31d 100644 --- a/src/backend/InvenTree/InvenTree/test_middleware.py +++ b/src/backend/InvenTree/InvenTree/test_middleware.py @@ -2,8 +2,7 @@ from unittest.mock import patch -from django.conf import settings -from django.http import Http404 +from django.http import Http404, HttpRequest from django.urls import reverse from error_report.models import Error @@ -289,18 +288,63 @@ class MiddlewareTests(InvenTreeTestCase): response, 'window.INVENTREE_SETTINGS', status_code=500 ) - # Log stuff # TODO remove - print( - '###DBG-TST###', - 'site', - settings.SITE_URL, - 'trusted', - settings.CSRF_TRUSTED_ORIGINS, - ) - # Check that the correct step triggers the error message self.assertContains( response, 'INVE-E7: The visited path `http://testserver` does not match', status_code=500, ) + + def test_csrf_failure(self): + """Test the custom CSRF failure handler.""" + from InvenTree.middleware import csrf_failure + + EXPECTED_DETAIL = 'CSRF verification failed. Ensure INVENTREE_SITE_URL and INVENTREE_TRUSTED_ORIGINS are configured correctly.' + + def make_request(path, headers=None): + request = HttpRequest() + request.path = path + request.META['SERVER_NAME'] = 'testserver' + request.META['SERVER_PORT'] = '80' + for key, value in (headers or {}).items(): + request.META[f'HTTP_{key.upper().replace("-", "_")}'] = value + return request + + # API path -> JSON 403 with meaningful message + response = csrf_failure( + make_request('/api/part/'), reason='origin check failed' + ) + self.assertEqual(response.status_code, 403) + import json + + data = json.loads(response.content) + self.assertEqual(data['detail'], EXPECTED_DETAIL) + + # allauth headless path -> JSON 403 + response = csrf_failure(make_request('/_allauth/browser/v1/auth/login')) + self.assertEqual(response.status_code, 403) + data = json.loads(response.content) + self.assertEqual(data['detail'], EXPECTED_DETAIL) + + # Accept: application/json header -> JSON 403 + response = csrf_failure( + make_request('/some/other/path/', {'Accept': 'application/json'}), + reason='origin check failed', + ) + self.assertEqual(response.status_code, 403) + data = json.loads(response.content) + self.assertEqual(data['detail'], EXPECTED_DETAIL) + + # Content-Type: application/json header -> JSON 403 + response = csrf_failure( + make_request('/some/other/path/', {'Content-Type': 'application/json'}), + reason='origin check failed', + ) + self.assertEqual(response.status_code, 403) + data = json.loads(response.content) + self.assertEqual(data['detail'], EXPECTED_DETAIL) + + # Plain browser request -> falls back to Django default CSRF page (403 HTML, not JSON) + response = csrf_failure(make_request('/web/'), reason='origin check failed') + self.assertEqual(response.status_code, 403) + self.assertNotIn(b'application/json', response.get('Content-Type', '').encode()) diff --git a/src/backend/InvenTree/InvenTree/tests.py b/src/backend/InvenTree/InvenTree/tests.py index 38062926d5..ea2f6d4171 100644 --- a/src/backend/InvenTree/InvenTree/tests.py +++ b/src/backend/InvenTree/InvenTree/tests.py @@ -608,35 +608,6 @@ class FormatTest(TestCase): with self.assertRaises(ValueError): InvenTree.format.extract_named_group('test', 'PO-ABC-xyz', 'PO-###-{test}') - def test_currency_formatting(self): - """Test that currency formatting works correctly for multiple currencies.""" - test_data = ( - (Money(3651.285718, 'USD'), 4, True, '$3,651.2857'), - (Money(487587.849178, 'CAD'), 5, True, 'CA$487,587.84918'), - (Money(0.348102, 'EUR'), 1, False, '0.3'), - (Money(0.916530, 'GBP'), 1, True, '£0.9'), - (Money(61.031024, 'JPY'), 3, False, '61.031'), - (Money(49609.694602, 'JPY'), 1, True, '¥49,609.7'), - (Money(155565.264777, 'AUD'), 2, False, '155,565.26'), - (Money(0.820437, 'CNY'), 4, True, 'CN¥0.8204'), - (Money(7587.849178, 'EUR'), 0, True, '€7,588'), - (Money(0.348102, 'GBP'), 3, False, '0.348'), - (Money(0.652923, 'CHF'), 0, True, 'CHF1'), - (Money(0.820437, 'CNY'), 1, True, 'CN¥0.8'), - (Money(98789.5295680, 'CHF'), 0, False, '98,790'), - (Money(0.585787, 'USD'), 1, True, '$0.6'), - (Money(0.690541, 'CAD'), 3, True, 'CA$0.691'), - (Money(427.814104, 'AUD'), 5, True, 'A$427.81410'), - ) - - with self.settings(LANGUAGE_CODE='en-us'): - for value, decimal_places, include_symbol, expected_result in test_data: - result = InvenTree.format.format_money( - value, decimal_places=decimal_places, include_symbol=include_symbol - ) - - self.assertEqual(result, expected_result) - class TestHelpers(TestCase): """Tests for InvenTree helper functions.""" diff --git a/src/backend/InvenTree/common/api.py b/src/backend/InvenTree/common/api.py index afe36c285e..e6c8b576c0 100644 --- a/src/backend/InvenTree/common/api.py +++ b/src/backend/InvenTree/common/api.py @@ -598,7 +598,7 @@ class CustomUnitViewset(DataExportViewMixin, viewsets.ModelViewSet): return { 'name': k, 'is_alias': reg.get_name(k) == k, - 'compatible_units': [str(a) for a in unit.compatible_units()], + 'compatible_units': [str(a) for a in unit.compatible_units()], # ty:ignore[missing-argument] 'isdimensionless': unit.dimensionless, } diff --git a/src/backend/InvenTree/common/validators.py b/src/backend/InvenTree/common/validators.py index c53a85ee5c..419c8c515d 100644 --- a/src/backend/InvenTree/common/validators.py +++ b/src/backend/InvenTree/common/validators.py @@ -4,6 +4,7 @@ import re from django.core.exceptions import SuspiciousFileOperation, ValidationError from django.core.files.storage import default_storage +from django.utils import translation from django.utils.translation import gettext_lazy as _ import common.icons @@ -166,3 +167,17 @@ def validate_variable_string(value: str): """The passed value must be a valid variable identifier string.""" if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', value): raise ValidationError(_('Value must be a valid variable identifier')) + + +def validate_locale(value: str): + """Validate that the provided value is a valid locale string.""" + from babel import Locale + from babel.core import UnknownLocaleError + + if not value: + return + + try: + Locale.parse(translation.to_locale(value)) + except (UnknownLocaleError, ValueError) as e: + raise ValidationError(f"Invalid locale value: '{value}' - {e}") diff --git a/src/backend/InvenTree/company/migrations/0019_auto_20200413_0642.py b/src/backend/InvenTree/company/migrations/0019_auto_20200413_0642.py index 0fc852354b..d4d33f9697 100644 --- a/src/backend/InvenTree/company/migrations/0019_auto_20200413_0642.py +++ b/src/backend/InvenTree/company/migrations/0019_auto_20200413_0642.py @@ -15,7 +15,7 @@ TESTING = 'test' in sys.argv def clear(): if not TESTING: # pragma: no cover - os.system('cls' if os.name == 'nt' else 'clear') + os.system('cls' if os.name == 'nt' else 'clear') # ty:ignore[deprecated] def reverse_association(apps, schema_editor): # pragma: no cover diff --git a/src/backend/InvenTree/order/models.py b/src/backend/InvenTree/order/models.py index 5ea4c9eaef..8c3ee0990f 100644 --- a/src/backend/InvenTree/order/models.py +++ b/src/backend/InvenTree/order/models.py @@ -272,7 +272,7 @@ class ReturnOrderReportContext(report.mixins.BaseReportContext, TypedDict): customer: Optional[Company] -class TransferOrderReportContext(report.mixins.BaseReportContext, TypedDict): +class TransferOrderReportContext(BaseOrderReportContext, TypedDict): """Context for the transfer order model. Attributes: @@ -639,7 +639,8 @@ class PurchaseOrder(TotalPriceMixin, Order): def report_context(self) -> PurchaseOrderReportContext: """Return report context data for this PurchaseOrder.""" return_ctx = super().report_context() - return_ctx.update({'supplier': self.supplier}) + + return_ctx.update({'supplier': self.supplier}) # ty:ignore[invalid-key] return return_ctx def get_absolute_url(self) -> str: @@ -1372,7 +1373,8 @@ class SalesOrder(TotalPriceMixin, Order): def report_context(self) -> SalesOrderReportContext: """Generate report context data for this SalesOrder.""" return_ctx = super().report_context() - return_ctx.update({'customer': self.customer}) + + return_ctx.update({'customer': self.customer}) # ty:ignore[invalid-key] return return_ctx def get_absolute_url(self) -> str: @@ -2930,7 +2932,8 @@ class ReturnOrder(TotalPriceMixin, Order): def report_context(self) -> ReturnOrderReportContext: """Generate report context data for this ReturnOrder.""" return_ctx = super().report_context() - return_ctx.update({'customer': self.customer}) + + return_ctx.update({'customer': self.customer}) # ty:ignore[invalid-key] return return_ctx def get_absolute_url(self): diff --git a/src/backend/InvenTree/plugin/samples/supplier/supplier_sample.py b/src/backend/InvenTree/plugin/samples/supplier/supplier_sample.py index 4ea7d761b8..4500da8c7c 100644 --- a/src/backend/InvenTree/plugin/samples/supplier/supplier_sample.py +++ b/src/backend/InvenTree/plugin/samples/supplier/supplier_sample.py @@ -66,7 +66,7 @@ class SampleSupplierPlugin(SupplierMixin, InvenTreePlugin): name=p['name'], description=p['description'], exact=p['sku'] == term, - price=f'{p["price"][1][0]:.2f}€', + price=f'{p["price"][1][0]:.2f}€', # ty:ignore[not-subscriptable] link=p['link'], image_url=p['image_url'], existing_part=getattr( diff --git a/src/backend/InvenTree/report/apps.py b/src/backend/InvenTree/report/apps.py index e72ea19145..1dfc507553 100644 --- a/src/backend/InvenTree/report/apps.py +++ b/src/backend/InvenTree/report/apps.py @@ -73,7 +73,9 @@ class ReportConfig(AppConfig): def cleanup(self): """Cleanup old label and report outputs.""" try: - from report.tasks import cleanup_old_report_outputs # type: ignore[import] + from report.tasks import ( + cleanup_old_report_outputs, # type: ignore[import] # ty: ignore[unresolved-import] + ) cleanup_old_report_outputs() except Exception: diff --git a/src/backend/InvenTree/report/templatetags/report.py b/src/backend/InvenTree/report/templatetags/report.py index da10ce29c2..de527d669b 100644 --- a/src/backend/InvenTree/report/templatetags/report.py +++ b/src/backend/InvenTree/report/templatetags/report.py @@ -1,6 +1,7 @@ """Custom template tags for report generation.""" import base64 +import copy import logging import mimetypes from datetime import date, datetime @@ -11,16 +12,23 @@ from typing import Any, Optional from django import template from django.apps.registry import apps -from django.conf import settings as django_settings +from django.conf import settings from django.contrib.staticfiles.storage import staticfiles_storage from django.core.exceptions import SuspiciousFileOperation, ValidationError from django.core.files.storage import default_storage from django.db.models import Model from django.db.models.query import QuerySet +from django.utils import translation from django.utils.safestring import SafeString, mark_safe from django.utils.translation import gettext_lazy as _ import lxml.html +from babel import Locale +from babel.core import UnknownLocaleError +from babel.dates import format_date as babel_format_date +from babel.dates import format_datetime as babel_format_datetime +from babel.numbers import format_decimal as babel_format_decimal +from babel.numbers import parse_pattern from djmoney.contrib.exchange.exceptions import MissingRate from djmoney.contrib.exchange.models import convert_money from djmoney.money import Money @@ -42,6 +50,22 @@ register = template.Library() logger = logging.getLogger('inventree') +def get_locale(locale: Optional[str] = None) -> Locale: + """Resolve and return a babel Locale. + + Args: + locale: Optional locale string (e.g. 'en-us'). Falls back to LANGUAGE_CODE. + + Raises: + ValidationError: If the locale string is invalid. + """ + language = locale or settings.LANGUAGE_CODE + try: + return Locale.parse(translation.to_locale(language)) + except (UnknownLocaleError, ValueError) as e: + raise ValidationError(f"Invalid locale '{language}' - {e}") + + @register.simple_tag() def order_queryset(queryset: QuerySet, *args) -> QuerySet: """Order a database queryset based on the provided arguments. @@ -531,7 +555,7 @@ def note(instance: Model, title: Optional[str] = None) -> str: return '' content = note.content - media_prefix = django_settings.MEDIA_URL + media_prefix = settings.MEDIA_URL # Replace any embedded image references with the actual image data root = lxml.html.fragment_fromstring(content, create_parent='div') @@ -862,9 +886,98 @@ def modulo(x: Any, y: Any, cast: Optional[type] = None) -> Any: @register.simple_tag -def render_currency(money, **kwargs): - """Render a currency / Money object.""" - return InvenTree.helpers_model.render_currency(money, **kwargs) +def render_currency( + money: Money | str | int | float | Decimal, + decimal_places: Optional[int] = None, + currency: Optional[str] = None, + multiplier: Optional[Decimal] = None, + max_decimal_places: Optional[int] = None, + include_symbol: bool = True, + leading: Optional[int] = None, + fmt: Optional[str] = None, + locale: Optional[str] = None, + **kwargs, +) -> str: + """Render a currency / Money object to a formatted string. + + Arguments: + money: The Money instance to be rendered + currency: Optionally convert to the specified currency before rendering + multiplier: Optional multiplier to apply to the amount before rendering + decimal_places: Minimum (forced) decimal places, e.g. decimal_places=2 gives '.00'. Defaults to the locale/currency standard. + max_decimal_places: Maximum decimal places (optional digits beyond decimal_places), e.g. max_decimal_places=4 allows up to 4. + include_symbol: If True, include the currency symbol in the output + leading: Minimum number of leading digits to render before the decimal point (default = 1) + fmt: Optional Babel number pattern string. When provided, takes priority over all other formatting options. + locale: Optional locale override (e.g. 'en-us', 'de-de'). Defaults to server LANGUAGE_CODE. + """ + if money in [None, '']: + return '-' + + # If the supplied value is *not* a Money instance, attempt to convert it into one + if not isinstance(money, Money): + try: + money = Money( + Decimal(str(money)), + currency or get_global_setting('INVENTREE_DEFAULT_CURRENCY'), + ) + except Exception: + raise ValidationError(f'render_currency: invalid money value - {money!r}') + + if currency is not None: + try: + money = convert_money(money, currency) + except Exception: + pass + + if multiplier is not None: + try: + money *= Decimal(str(multiplier).strip()) + except Exception: + raise ValidationError( + f'render_currency: invalid multiplier value - {multiplier!r}' + ) + + locale = get_locale(locale) + + # If a custom fmt pattern is applied, that overrides other formatting options + if fmt: + pattern = parse_pattern(fmt) + return pattern.apply( + money.amount, + locale, + currency=money.currency.code if include_symbol else '', + currency_digits=False, + decimal_quantization=True, + ) + + pattern = copy.copy(locale.currency_formats['standard']) + + if decimal_places is None or not isinstance(decimal_places, (int, float)): + decimal_places = get_global_setting('PRICING_DECIMAL_PLACES_MIN', 0) + + if max_decimal_places is None or not isinstance(max_decimal_places, (int, float)): + max_decimal_places = get_global_setting('PRICING_DECIMAL_PLACES', 6) + + pattern.frac_prec = (decimal_places, max(decimal_places, max_decimal_places)) + + if leading is not None: + try: + leading = int(leading) or 0 + except (ValueError, TypeError): + leading = 0 + if leading > 0: + min_int, max_int = pattern.int_prec + pattern.int_prec = (max(leading, min_int), max(leading, max_int)) + + return pattern.apply( + money.amount, + locale, + currency=money.currency.code if include_symbol else '', + currency_digits=decimal_places is None and max_decimal_places is None, + decimal_quantization=decimal_places is not None + or max_decimal_places is not None, + ) @register.simple_tag @@ -961,82 +1074,109 @@ def render_html_text(text: str, **kwargs): @register.simple_tag def format_number( number: int | float | Decimal, - decimal_places: Optional[int] = None, multiplier: Optional[int | float | Decimal] = None, integer: bool = False, - leading: int = 0, - separator: Optional[str] = None, + separator: bool = False, + leading: Optional[int] = None, + decimal_places: Optional[int] = None, + max_decimal_places: Optional[int] = None, + fmt: Optional[str] = None, + locale: Optional[str] = None, + **kwargs, ) -> str: """Render a number with optional formatting options. Arguments: number: The number to be formatted - decimal_places: Number of decimal places to render multiplier: Optional multiplier to apply to the number before formatting integer: Boolean, whether to render the number as an integer - leading: Number of leading zeros (default = 0) - separator: Character to use as a thousands separator (default = None) + separator: Boolean, whether to include a thousands separator + leading: Minimum number of leading digits to render (default = 1) + decimal_places: Number of decimal places to render (default = 0) + max_decimal_places: Maximum number of decimal places to render (default = 0) + separator: + fmt: Optional format string for the number - if provided, takes priority over 'decimal_places' and 'leading' + locale: Optional locale override (e.g. 'en-us', 'de-de'). When set, babel controls decimal and thousands separators. """ check_nulls('format_number', number) + # Check that the provided number is valid try: number = Decimal(str(number).strip()) except Exception: # If the number cannot be converted to a Decimal, just return the original value return str(number) + number = float(number) + if multiplier is not None: - number *= Decimal(str(multiplier).strip()) + number *= float(multiplier) if integer: - # Convert to integer - number = Decimal(int(number)) + number = int(number) - # Normalize the number (remove trailing zeroes) - number = number.normalize() + # Construct a formatting string for the number, based on the provided options + if not fmt: + fmt = '###,###,###,###,##0' # Default format string - this will be modified based on the provided options - if decimal_places is not None: - try: - decimal_places = int(decimal_places) - number = round(number, decimal_places) - except ValueError: - pass + # The 'leading' option specifies the minimum number of leading digits to render (not including decimal places) + if leading is not None: + try: + leading = int(leading) or 0 + except (ValueError, TypeError): + leading = 0 - # Re-encode, and normalize again - # Ensure that the output never uses scientific notation - value = Decimal(number) - value = ( - value.quantize(Decimal(1)) - if value == value.to_integral() - else value.normalize() + if leading > 1: + fmt = fmt[::-1].replace('#', '0', (leading - 1))[::-1] + + if not bool(separator): + fmt = fmt.replace(',', '') + + if decimal_places is not None or max_decimal_places is not None: + # Account for decimal places, if provided + + try: + decimal_places = int(decimal_places) or 0 + except (ValueError, TypeError): + decimal_places = 0 + + try: + max_decimal_places = int(max_decimal_places) or 0 + except (ValueError, TypeError): + max_decimal_places = 0 + + fmt += '.' + '0' * decimal_places + + if max_decimal_places > decimal_places: + fmt += '#' * (max_decimal_places - decimal_places) + elif not integer: + # No decimal places specified, allow any number of decimal places (up to the precision of the Decimal) + fmt += '.####################' + + babel_locale = get_locale(locale) + + return babel_format_decimal( + number, format=fmt, locale=babel_locale, numbering_system='latn' ) - if separator: - value = f'{value:,}' - value = value.replace(',', separator) - else: - value = f'{value}' - - if leading is not None: - try: - leading = int(leading) - value = '0' * leading + value - except ValueError: - pass - - return value - @register.simple_tag def format_datetime( - dt: datetime, timezone: Optional[str] = None, fmt: Optional[str] = None + dt: datetime, + timezone: Optional[str] = None, + fmt: Optional[str] = None, + locale: Optional[str] = None, + date_format: str = 'medium', + **kwargs, ): """Format a datetime object for display. Arguments: dt: The datetime object to format timezone: The timezone to use for the date (defaults to the server timezone) - fmt: The format string to use (defaults to ISO formatting) + fmt: The strftime format string to use. When provided, takes priority over locale and date_format. + locale: Optional locale override (e.g. 'en-us', 'de-de'). Used for locale-aware formatting when no fmt is given. + date_format: Babel date format style. One of 'full', 'long', 'medium' (default), 'short'. """ check_nulls('format_datetime', dt) @@ -1044,18 +1184,27 @@ def format_datetime( if fmt: return dt.strftime(fmt) - else: - return dt.isoformat() + + return babel_format_datetime(dt, format=date_format, locale=get_locale(locale)) @register.simple_tag -def format_date(dt: date, timezone: Optional[str] = None, fmt: Optional[str] = None): +def format_date( + dt: date, + timezone: Optional[str] = None, + fmt: Optional[str] = None, + locale: Optional[str] = None, + date_format: str = 'medium', + **kwargs, +): """Format a date object for display. Arguments: dt: The date to format timezone: The timezone to use for the date (defaults to the server timezone) - fmt: The format string to use (defaults to ISO formatting) + fmt: The strftime format string to use. When provided, takes priority over locale and date_format. + locale: Optional locale override (e.g. 'en-us', 'de-de'). Used for locale-aware formatting when no fmt is given. + date_format: Babel date format style. One of 'full', 'long', 'medium' (default), 'short'. """ check_nulls('format_date', dt) @@ -1066,8 +1215,8 @@ def format_date(dt: date, timezone: Optional[str] = None, fmt: Optional[str] = N if fmt: return dt.strftime(fmt) - else: - return dt.isoformat() + + return babel_format_date(dt, format=date_format, locale=get_locale(locale)) @register.simple_tag() diff --git a/src/backend/InvenTree/report/test_tags.py b/src/backend/InvenTree/report/test_tags.py index cf7c20ec4d..b5bf18f806 100644 --- a/src/backend/InvenTree/report/test_tags.py +++ b/src/backend/InvenTree/report/test_tags.py @@ -14,6 +14,7 @@ from djmoney.money import Money from PIL import Image from common.models import InvenTreeSetting, Parameter, ParameterTemplate +from common.settings import set_global_setting from InvenTree.unit_test import InvenTreeTestCase from part.models import Part from part.test_api import PartImageTestMixin @@ -359,26 +360,60 @@ class ReportTagTest(PartImageTestMixin, InvenTreeTestCase): for x in ['10.000000', ' 10 ', 10.000000, 10]: self.assertEqual(fn(x), '10') + # Test with various formatting options self.assertEqual(fn(1234), '1234') self.assertEqual(fn(1234.5678, decimal_places=0), '1235') + self.assertEqual(fn(1234.5678, decimal_places=0, separator=True), '1,235') self.assertEqual(fn(1234.5678, decimal_places=1), '1234.6') self.assertEqual(fn(1234.5678, decimal_places=2), '1234.57') - self.assertEqual(fn(1234.5678, decimal_places=3), '1234.568') - self.assertEqual(fn(-9999.5678, decimal_places=2, separator=','), '-9,999.57') self.assertEqual( - fn(9988776655.4321, integer=True, separator=' '), '9 988 776 655' + fn(1234.5678, decimal_places=2, max_decimal_places=10), '1234.5678' ) + self.assertEqual(fn(1234.5678, decimal_places=3), '1234.568') + self.assertEqual(fn(-9999.5678, decimal_places=2, locale='fr-fr'), '-9999,57') + self.assertEqual( + fn(-9999.5678, decimal_places=2, locale='fr-fr', separator=True), + '-9\u202f999,57', + ) + self.assertEqual( + fn(9988776655.4321, integer=True, locale='de-de', separator=True), + '9.988.776.655', + ) + + # Test with 'leading' option + self.assertEqual(fn(5, leading=3), '005') + self.assertEqual(fn(123, leading=5), '00123') + self.assertEqual(fn(1234, leading=2, decimal_places=4), '1234.0000') + + # Test with custom 'fmt' format string (takes priority over decimal_places / separator) + self.assertEqual(fn(1234.5678, fmt='0.00'), '1234.57') + self.assertEqual(fn(1234.5678, fmt='#,##0.00'), '1,234.57') + self.assertEqual(fn(1234.5678, fmt='0.00', locale='de-de'), '1234,57') + self.assertEqual(fn(1234.5678, fmt='#,##0.00', locale='de-de'), '1.234,57') + # fmt bypasses decimal_places and separator options + self.assertEqual( + fn(1234.5678, fmt='0.00', decimal_places=4, separator=True), '1234.57' + ) + # integer conversion still applies before fmt is used + self.assertEqual( + fn(9988776655.4321, fmt='#,##0', integer=True), '9,988,776,655' + ) + # multiplier is applied before fmt is used + self.assertEqual(fn(100, fmt='0.00', multiplier=1.5), '150.00') # Test with multiplier self.assertEqual(fn(1000, multiplier=1.5), '1500') # Failure cases self.assertEqual(fn('abc'), 'abc') - self.assertEqual(fn(1234.456, decimal_places='a'), '1234.456') + self.assertEqual(fn(1234.456, decimal_places='a'), '1234') + self.assertEqual( + fn(1234.456, decimal_places='a', separator=True, locale='en-au'), '1,234' + ) self.assertEqual(fn(1234.456, leading='a'), '1234.456') @override_settings(TIME_ZONE='America/New_York') - def test_date_tags(self): + def test_datetime_tags(self): """Test for date formatting tags. - Source timezone is Australia/Sydney @@ -394,18 +429,21 @@ class ReportTagTest(PartImageTestMixin, InvenTreeTestCase): tzinfo=ZoneInfo('Australia/Sydney'), ) - # Format a set of tests: timezone, format, expected + # Format a set of tests: timezone, format, locale, expected tests = [ - (None, None, '2024-03-12T21:30:00-04:00'), - (None, '%d-%m-%y', '12-03-24'), - ('UTC', None, '2024-03-13T01:30:00+00:00'), - ('UTC', '%d-%B-%Y', '13-March-2024'), - ('Europe/Amsterdam', None, '2024-03-13T02:30:00+01:00'), - ('Europe/Amsterdam', '%y-%m-%d %H:%M', '24-03-13 02:30'), + (None, None, 'en-us', 'Mar 12, 2024, 9:30:00 PM'), # noqa: RUF001 + (None, '%d-%m-%y', 'en-us', '12-03-24'), + ('UTC', None, 'en-us', 'Mar 13, 2024, 1:30:00 AM'), # noqa: RUF001 + ('UTC', '%d-%B-%Y', 'en-us', '13-March-2024'), + ('Europe/Amsterdam', None, 'de-de', '13.03.2024, 02:30:00'), + ('Europe/Amsterdam', '%y-%m-%d %H:%M', 'de-de', '24-03-13 02:30'), ] - for tz, fmt, expected in tests: - result = report_tags.format_datetime(time, tz, fmt) + for tz, fmt, locale, expected in tests: + print(tz, fmt, locale, expected) + result = report_tags.format_datetime( + time, timezone=tz, fmt=fmt, locale=locale + ) self.assertEqual(result, expected) def test_icon(self): @@ -519,21 +557,24 @@ class ReportTagTest(PartImageTestMixin, InvenTreeTestCase): self.assertEqual(report_tags.render_currency(m, decimal_places=3), '$1,234.560') self.assertEqual( report_tags.render_currency( - Money(1234, 'USD'), currency='EUR', min_decimal_places=3 + Money(1234, 'USD'), currency='EUR', decimal_places=3 ), '$1,234.000', ) + + set_global_setting('PRICING_DECIMAL_PLACES_MIN', 2) + self.assertEqual( report_tags.render_currency( Money(1234, 'USD'), currency='EUR', max_decimal_places=1 ), - '$1,234.0', + '$1,234.00', ) # Test with non-currency values self.assertEqual( - report_tags.render_currency(1234.45, currency='USD', decimal_places=2), - '$1,234.45', + report_tags.render_currency(1234.45, currency='USD', decimal_places=5), + '$1,234.45000', ) # Test with an invalid amount @@ -544,9 +585,101 @@ class ReportTagTest(PartImageTestMixin, InvenTreeTestCase): report_tags.render_currency(m, multiplier='quork') self.assertEqual(report_tags.render_currency(m, decimal_places='a'), exp_m) - self.assertEqual(report_tags.render_currency(m, min_decimal_places='a'), exp_m) self.assertEqual(report_tags.render_currency(m, max_decimal_places='a'), exp_m) + # Test locale override — different locales render USD differently + self.assertEqual(report_tags.render_currency(m, locale='en-us'), '$1,234.56') + self.assertEqual(report_tags.render_currency(m, locale='en-gb'), 'US$1,234.56') + self.assertEqual(report_tags.render_currency(m, locale='en-au'), 'USD1,234.56') + + # Test with custom fmt pattern + # Pattern without currency placeholder — no symbol in output + self.assertEqual(report_tags.render_currency(m, fmt='#,##0.00'), '1,234.56') + # Pattern with currency placeholder — symbol rendered per locale + self.assertEqual( + report_tags.render_currency(m, fmt='¤#,##0.0000', locale='en-us'), + '$1,234.5600', + ) + # fmt + locale: de-de uses dot thousands, comma decimal + self.assertEqual( + report_tags.render_currency(m, fmt='#,##0.00', locale='de-de'), '1.234,56' + ) + # fmt takes priority over decimal_places + self.assertEqual( + report_tags.render_currency(m, fmt='0.0000', decimal_places=2), '1234.5600' + ) + + # Test leading digits + m_small = Money(1.23, 'USD') + self.assertEqual( + report_tags.render_currency(m_small, leading=4, locale='en-us'), '$0,001.23' + ) + # leading=1 is the default — no change + self.assertEqual( + report_tags.render_currency(m_small, leading=1, locale='en-us'), '$1.23' + ) + # invalid leading falls back gracefully + self.assertEqual( + report_tags.render_currency(m_small, leading='x', locale='en-us'), '$1.23' + ) + # fmt takes priority over leading + self.assertEqual( + report_tags.render_currency(m_small, leading=6, fmt='#,##0.00'), '1.23' + ) + + # Test include_symbol + # Default (True) — symbol present + self.assertEqual(report_tags.render_currency(m, locale='en-us'), '$1,234.56') + # Explicit False — symbol suppressed + self.assertEqual( + report_tags.render_currency(m, include_symbol=False, locale='en-us'), + '1,234.56', + ) + # include_symbol=False with fmt containing ¤ — ¤ renders as empty string + self.assertEqual( + report_tags.render_currency( + m, include_symbol=False, fmt='¤#,##0.00', locale='en-us' + ), + '1,234.56', + ) + # include_symbol=False with fmt lacking ¤ — no symbol either way + self.assertEqual( + report_tags.render_currency( + m, include_symbol=False, fmt='#,##0.00', locale='en-us' + ), + '1,234.56', + ) + + def test_render_currency_locale_override(self): + """Explicit locale= kwarg takes priority over global setting and system locale.""" + m = Money(1234.56, 'USD') + + # Explicit locale overrides system LANGUAGE_CODE + with override_settings(LANGUAGE_CODE='en-au'): + self.assertEqual( + report_tags.render_currency(m, locale='en-us'), '$1,234.56' + ) + self.assertEqual( + report_tags.render_currency(m, locale='en-gb'), 'US$1,234.56' + ) + + # Invalid locale raises ValidationError regardless of other settings + with self.assertRaises(ValidationError): + report_tags.render_currency(m, locale='xx-zz') + + def test_render_currency_system_locale(self): + """render_currency uses system LANGUAGE_CODE when no explicit locale= is passed.""" + m = Money(1234.56, 'USD') + + with override_settings(LANGUAGE_CODE='en-us'): + self.assertEqual(report_tags.render_currency(m), '$1,234.56') + + with override_settings(LANGUAGE_CODE='en-gb'): + self.assertEqual(report_tags.render_currency(m), 'US$1,234.56') + + with override_settings(LANGUAGE_CODE='en-au'): + self.assertEqual(report_tags.render_currency(m), 'USD1,234.56') + def test_create_currency(self): """Test the create_currency template tag.""" m = report_tags.create_currency(1000, 'USD') @@ -618,14 +751,94 @@ class ReportTagTest(PartImageTestMixin, InvenTreeTestCase): def test_format_date(self): """Test the format_date template tag.""" - # Test with a valid date - date = timezone.datetime(year=2024, month=3, day=13) - self.assertEqual(report_tags.format_date(date), '2024-03-13') - self.assertEqual(report_tags.format_date(date, fmt='%d-%m-%y'), '13-03-24') + dt = timezone.datetime(year=2024, month=3, day=13) + self.assertEqual(report_tags.format_date(dt, locale='de-de'), '13.03.2024') + self.assertEqual(report_tags.format_date(dt, locale='en-us'), 'Mar 13, 2024') + self.assertEqual(report_tags.format_date(dt, locale='en-au'), '13 Mar 2024') + self.assertEqual(report_tags.format_date(dt, locale='fr-fr'), '13 mars 2024') + self.assertEqual(report_tags.format_date(dt, fmt='%d-%m-%y'), '13-03-24') # Test with an invalid date self.assertEqual(report_tags.format_date('abc'), 'abc') - self.assertEqual(report_tags.format_date(date, fmt='a'), 'a') + self.assertEqual(report_tags.format_date(dt, fmt='a'), 'a') + + # Explicit fmt always wins over locale + self.assertEqual( + report_tags.format_date(dt, fmt='%Y-%m-%d', locale='de-de'), '2024-03-13' + ) + + # Falls back to LANGUAGE_CODE when no locale= arg + with override_settings(LANGUAGE_CODE='en-us'): + self.assertEqual(report_tags.format_date(dt), 'Mar 13, 2024') + + # date_format controls the Babel style + self.assertEqual( + report_tags.format_date(dt, locale='en-us', date_format='short'), '3/13/24' + ) + self.assertEqual( + report_tags.format_date(dt, locale='en-us', date_format='long'), + 'March 13, 2024', + ) + self.assertEqual( + report_tags.format_date(dt, locale='en-us', date_format='full'), + 'Wednesday, March 13, 2024', + ) + + # fmt= wins over date_format= + self.assertEqual( + report_tags.format_date(dt, fmt='%Y', locale='en-us', date_format='full'), + '2024', + ) + + # Invalid locale raises ValidationError + with self.assertRaises(ValidationError): + report_tags.format_date(dt, locale='xx-zz') + + def test_format_datetime(self): + """Test that format_datetime renders locale-aware output.""" + from zoneinfo import ZoneInfo + + dt = timezone.datetime(2026, 6, 19, 15, 30, 0, tzinfo=ZoneInfo('UTC')) + + self.assertEqual( + report_tags.format_datetime(dt, locale='en-us'), + 'Jun 19, 2026, 3:30:00 PM', # noqa: RUF001 + ) + self.assertEqual( + report_tags.format_datetime(dt, locale='de-de'), '19.06.2026, 15:30:00' + ) + + # Explicit fmt still wins + self.assertEqual( + report_tags.format_datetime(dt, fmt='%Y-%m-%d', locale='de-de'), + '2026-06-19', + ) + + # Falls back to LANGUAGE_CODE when no locale= arg + with override_settings(LANGUAGE_CODE='de-de'): + self.assertEqual(report_tags.format_datetime(dt), '19.06.2026, 15:30:00') + + # date_format controls the Babel style + self.assertEqual( + report_tags.format_datetime(dt, locale='de-de', date_format='short'), + '19.06.26, 15:30', + ) + self.assertEqual( + report_tags.format_datetime(dt, locale='de-de', date_format='long'), + '19. Juni 2026, 15:30:00 UTC', # codespell:ignore "Juni" + ) + + # fmt= wins over date_format= + self.assertEqual( + report_tags.format_datetime( + dt, fmt='%H:%M', locale='en-us', date_format='full' + ), + '15:30', + ) + + # Invalid locale raises ValidationError + with self.assertRaises(ValidationError): + report_tags.format_datetime(dt, locale='xx-zz') class BarcodeTagTest(TestCase): diff --git a/src/backend/InvenTree/script/update_icons.py b/src/backend/InvenTree/script/update_icons.py index ea24479456..716ffeb878 100644 --- a/src/backend/InvenTree/script/update_icons.py +++ b/src/backend/InvenTree/script/update_icons.py @@ -19,7 +19,7 @@ if __name__ == '__main__': os.mkdir(STATIC_FOLDER) print('Downloading tabler icons...') - os.system(f'npm install --prefix {TMP_FOLDER} @tabler/icons @tabler/icons-webfont') + os.system(f'npm install --prefix {TMP_FOLDER} @tabler/icons @tabler/icons-webfont') # ty:ignore[deprecated] print(f'Copying tabler icons to {STATIC_FOLDER}...') diff --git a/src/backend/InvenTree/stock/api.py b/src/backend/InvenTree/stock/api.py index 31ef0f915a..6557761651 100644 --- a/src/backend/InvenTree/stock/api.py +++ b/src/backend/InvenTree/stock/api.py @@ -18,6 +18,7 @@ from rest_framework.generics import GenericAPIView from rest_framework.response import Response from rest_framework.serializers import ValidationError +import common.filters import common.models import common.settings import InvenTree.helpers diff --git a/src/backend/InvenTree/users/models.py b/src/backend/InvenTree/users/models.py index 54fb8ae2da..0e6e1eb316 100644 --- a/src/backend/InvenTree/users/models.py +++ b/src/backend/InvenTree/users/models.py @@ -47,9 +47,7 @@ User.add_to_class('__str__', user_model_str) # Overriding User.__str__ if settings.LDAP_AUTH: - from django_auth_ldap.backend import ( # type: ignore[unresolved-import] - populate_user, - ) + from django_auth_ldap.backend import populate_user # ty: ignore[unresolved-import] @receiver(populate_user) def create_email_address(user, **kwargs): diff --git a/src/backend/requirements-3.14.txt b/src/backend/requirements-3.14.txt index cf7e17b69f..55035af0c8 100644 --- a/src/backend/requirements-3.14.txt +++ b/src/backend/requirements-3.14.txt @@ -101,16 +101,16 @@ blessed==1.44.0 \ # via # -c src/backend/requirements.txt # -r src/backend/requirements.in -boto3==1.43.23 \ - --hash=sha256:5d26498702ffd021dc0d57d0eefcc7101cd995ea0ed08c057c9b631efccbaa48 \ - --hash=sha256:8afc058924ef8a5c62467fe2e1e2e0304c22018587a044714da89f9c602ba856 +boto3==1.43.34 \ + --hash=sha256:42595057324606928c6e2432b3093978e4d722e0d432bce942f2a385702c0a43 \ + --hash=sha256:444207c6c883d4df3ea3b2c36df43ad492b86e0b889eebd2fc1d5ea8db0a8a1a # via # -c src/backend/requirements.txt # django-anymail # django-storages -botocore==1.43.23 \ - --hash=sha256:69ff3d951cb644d1d84db646663c7eb919dc9c0c47e5768e947c8a71121b3d77 \ - --hash=sha256:a6737c598750f330bfa8ef2be2d9fa84b5d2d643b6bbb0d22e129e03b7535df1 +botocore==1.43.34 \ + --hash=sha256:238a0269f33c5914b9343900b44767e783b3e8b6dcb6e065eac8b4495601c5df \ + --hash=sha256:ccc973cf30c6445b30afe5760f6dc949a80f1f862cb23d9c45747f2c814ece77 # via # -c src/backend/requirements.txt # boto3 @@ -219,9 +219,9 @@ brotli==1.2.0 \ # via # -c src/backend/requirements.txt # fonttools -certifi==2026.5.20 \ - --hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \ - --hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d +certifi==2026.6.17 \ + --hash=sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432 \ + --hash=sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db # via # -c src/backend/requirements.txt # requests @@ -614,9 +614,9 @@ django-ipware==7.0.1 \ # via # -c src/backend/requirements.txt # django-structlog -django-js-asset==3.1.2 \ - --hash=sha256:1fc7584199ed1941ed7c8e7b87ca5524bb0f2ba941561d2a104e88ee9f07bedd \ - --hash=sha256:b5ffe376aebbd73b7af886d675ac9f43ca63b39540190fa8409c9f8e79145f68 +django-js-asset==4.0.1 \ + --hash=sha256:51ad1060115bf741bef88dc1287ca7f70e1d6ea7cf935f52a34f39eca9bd9db1 \ + --hash=sha256:967ac7d95ce094d9f65f358278bcd494201e2f9bbee936430150de83eac9f656 # via # -c src/backend/requirements.txt # django-mptt @@ -637,9 +637,9 @@ django-markdownify==0.9.7 \ # via # -c src/backend/requirements.txt # -r src/backend/requirements.in -django-money==3.6.0 \ - --hash=sha256:94402f2831f2726b94ef2da35b4059441b4c0aedfc47b312472200d4ffdf8d73 \ - --hash=sha256:a8d249bf3ce6ad7fb953530c920cc85ea7f1137c0fde747a74204ea24ec97ab1 +django-money==3.6.1 \ + --hash=sha256:32a8027a91dea969951a34a9cc4a4f101aaa6ec45e7f784f0b17ec7813818fb2 \ + --hash=sha256:9b25d992a457af932eef1e5fef0098dd3ff0c92f4255d2d6d452af772b2fdde5 # via # -c src/backend/requirements.txt # -r src/backend/requirements.in @@ -899,58 +899,58 @@ googleapis-common-protos==1.75.0 \ # -c src/backend/requirements.txt # opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-http -grpcio==1.81.0 \ - --hash=sha256:0fba53cb96004b2b7fb758b46b2288cb49d0b658316a4e73f3ef67230616ee65 \ - --hash=sha256:194eddfacc84d80f50512e9fd4ee851d5f2499f18f299c95aa8fb4748f0537e0 \ - --hash=sha256:19f201da7b4e5c0559198abe5a97157e726f3abe6e8f5e832d4a50740f6dcc22 \ - --hash=sha256:21ec30b9ea320c8207ea7cd05873ad64aa69fdd0e81b6758b3347983ba20b50a \ - --hash=sha256:275144b0115353339dbb8a6f28a9cf8997b5bf40e37f8f66ac0b0ea57e95b43f \ - --hash=sha256:300f3337b6425fd16ead9a4f9b2ac25801acb64aa5bc0b99eb69901645b2b1d2 \ - --hash=sha256:3755c9669307cad18e7e009860fdea98118978d2300451bd8530a53048e741e7 \ - --hash=sha256:3d4e0ce5a40a998cf608c8ba60ecfe18fdf364a9aa193ae4ac3faeecd0e86757 \ - --hash=sha256:40edffb4ec3689373825d367c4457727047a6e554f03245265ecc8cc03215f22 \ - --hash=sha256:43c121e135ae44d1559b430db2b2dfad7421cbbe40e1deba506c7dc62b439719 \ - --hash=sha256:4e032feb3bfb4e2749b140a2302a6baa8ead1b9781ff5cf7094e4402b5e9372e \ - --hash=sha256:5192857589f223e5a98ff0e31f6e551b19040e647d17bfe10116c8a2ce3b8696 \ - --hash=sha256:57b3b0e73a518fa286959b40c3eddd02703504ca186e8b7b2945954519bd8b2c \ - --hash=sha256:5e925a70fe99fe5794f7beca0ea034c75f068afcc356d79047e73f99cdcca34c \ - --hash=sha256:62bbe463c9f0f2ff24e31bd25f8dd8b4bae78900e315915a3195a0ef1471a855 \ - --hash=sha256:638ccc1b86f7540170a169cb900799b9296a1381e47879ce60b0de9d3db73d33 \ - --hash=sha256:725801c7086d7e4cd160e42bb2f54e0aeb976b9568df3cc6f843b15d29b79fb1 \ - --hash=sha256:77eb4e9fe61486bd1198cc7236ebb0f70e66234e63c0348f40bc2553ed16a88b \ - --hash=sha256:7915a2e63acdc05264a206e1bddfd8e1fb8a29e406c18d72d30f8c124e021374 \ - --hash=sha256:794e6aa648e8df47d8f908dc8c3b42347d04ec58438f1dcd4e445f09b4f6b0ce \ - --hash=sha256:8226ba097eed660ef14d36c6a69b85038552bb8b6d17b44a5aa6f9abf48b8e08 \ - --hash=sha256:87e33b7afcfb3585121b5f007d2c52b8c534104d18f556e840d35193ca2a9141 \ - --hash=sha256:8bb1789c94322a13336a2b6c58d9c14d68f8628b6e24205a799c69f5bf8516ce \ - --hash=sha256:8c0855a350886f713b9e458e2a10d208009dcaa849f574e39cd6067db1fe1279 \ - --hash=sha256:909bb3222b53235498d2c5817a0596d82b0aaea490ba93fdf1b060e2938a543c \ - --hash=sha256:97bbd623f7ded558fd4f7cb5a4f600c4d4de65c5dd364c83a5b14b2a10a2d3b5 \ - --hash=sha256:98c6240f563178fc5877bd50e6ff274463e53e1472128f4110742450739659fa \ - --hash=sha256:9f355384e5543ab77a755a7085225ecc19f32b76032e851cbd8145715d79dec8 \ - --hash=sha256:a524cd530900bd24511fcb7f2ed144da4ea37711c4b094475d0bceca7a93a170 \ - --hash=sha256:a5acd7efd3b1fe9b4eb0bcaaa1507eed68a0ad0678b654c3f7b464df9ba9dca5 \ - --hash=sha256:a9351055f52660b58f3d4890ea66188b5134399f82b11aa0c55bd4b99eff5390 \ - --hash=sha256:aa948712c8e5fa40ec250870bda14bc7578e1bb832a8912d9d2a0f720518edbe \ - --hash=sha256:aaaa4f7f2057d795952e4eacf3f342be8b5b156992f6ac85023c8b98794ebd47 \ - --hash=sha256:b4108e5d9d0f651b7eea749116181fe6c315b145661a80ec31f05ec2dbe21af7 \ - --hash=sha256:b76ea9d55cd08fcdbda25d28e0f76679536710acb7fbd5b1f70cb4ac49317265 \ - --hash=sha256:b8b025b6af43ee0ad4a70307025d77bcab5adde7c4597786010d802c203e9fc5 \ - --hash=sha256:b93cee313cae4e113fbb3a0ce1ea5633db6f63cfde2b2dc1d817429026b2a50b \ - --hash=sha256:c197e2ef75a442528072b29e9755da299110e8610e8bcbb59a6b4cf55384f005 \ - --hash=sha256:c36f5d5e97944cbda2d4096b4ae262e6e68506246b61582acf1b8591607f3ccc \ - --hash=sha256:c4fe218c5a35e1d87a5a26544237f1fa41dfd9cbd3c856b0810a30061f8b0aaf \ - --hash=sha256:c6ff087cb1f563f47b504b4e29e684129fc5ae4863faf3ebca08a327764ee6cb \ - --hash=sha256:cd78145b7f7784661c524624f3526c9c6f891b30a4b54cb93a40806d0d0d61e9 \ - --hash=sha256:db217c2e52931719f9937bd12082cd4d7b495b35803d5760686975c285924bf8 \ - --hash=sha256:dbdb99986548a7e87f8343805ef315fd4eb50ffaabf4fb1206e42f2542bb805d \ - --hash=sha256:e4d053900a0d24b75d7521139a3872150301b3d6bde3bed5e12318fb25791e4d \ - --hash=sha256:e7746ba3e6efc9e2b748eff59470a2b8684d5a9ec607c6580bcaa5be175820bc \ - --hash=sha256:f345de40ef2e65f63645d53d251824e6070e07804827c5b00ec2e44555f9f901 \ - --hash=sha256:f750a091fff3a3991731abc1f818bdc64874bb3528162732cb4d45f2e07821a6 \ - --hash=sha256:f85570a016d794c29b1e76cf22f67af4486ddbe779e0f30674f138fa4e1769ec \ - --hash=sha256:fbbe81314a9d92156abce8b62c09364eb8bafc0ca2a19919a45ec64b5c6cb664 \ - --hash=sha256:ff83d889e3ebf6341c8c7864ad8031591ad5ca61599072fc511644d1eb962d2b +grpcio==1.81.1 \ + --hash=sha256:0490c30c261eded63f3f354979f9dc4502a9fb944cccb60cd9dc85f5a7349854 \ + --hash=sha256:0a37165cc80b1a368384b383e63a4c38116a10467ae44c904d2d7468c4470ec2 \ + --hash=sha256:12b7524c88d4026d3dcb7b0ebe16b6714f3b4af402ddd0f0639ab064a00c87c3 \ + --hash=sha256:15641444eca4a29358107b3dceb74c1c6305c55c822fd199b458aaea4068a7fb \ + --hash=sha256:1b22c80559854b789a01fd89e8929b3798a156c0829b5282a8939f33ad4115ad \ + --hash=sha256:1e123f9b37edb8375fd74130d1f69c944bbf0a7b06761ae7211154b8759e94d2 \ + --hash=sha256:24c8e57504c8f45b237e40b99262d181071e5099a07053695b75d97bb53053a0 \ + --hash=sha256:2c2e2ae6867c2966b8daccc836d54a13218e0007e9a490aeb81dd05be64d22d7 \ + --hash=sha256:30e825f6848d9f18bba350ed6c75c1b02a0b5184474a31db9a32b1fa66fd8c79 \ + --hash=sha256:3768a5ff1b2125e6f552e561b6b2dca0e64982d8949689b4df145cf8b98d7821 \ + --hash=sha256:3ad74f8bb1a18963914c5452d289422830b39459e8776ebbcd207be1fbfb1d94 \ + --hash=sha256:410482da976329fe5f4067270401b12cf2bd552ff8020f054ecfaddb5475f9d6 \ + --hash=sha256:428bec0161b48d8cf583c068591bc0016d0d9cfff52462b72b3884861ea768c5 \ + --hash=sha256:506f48f2f9c29b143fca3dad7b0d518c188b6c9648c75a2ae6e2d9f2c13a060b \ + --hash=sha256:58ad1131c300d3c9b933802b3cc4dc69d380822935ba50b28703156ea826fbf7 \ + --hash=sha256:592b5fee597faa91cce2dd294dd7d9a1c83d76c4dbf877e33ec1adb866b2fbed \ + --hash=sha256:61233fe8951e5c85dff81c2458b6528624760166946b5b47ea150a589168411f \ + --hash=sha256:62481553b1793a27e9b9c3cf9e5bd483ef045ca72462592074b46d42b0c4d9b9 \ + --hash=sha256:6282caffb41ec326d4cb67ca9cf53b739d1b2f975a2acb498c7418e9f7d9a416 \ + --hash=sha256:69ef28e54fc85397f91b8c19592b8ef3d81952080366914823bd8572a2958120 \ + --hash=sha256:6f9a0c9c1cc15c112d1c053064fd032b64917062292c3d70aea280e02ae10b77 \ + --hash=sha256:6fa10a767143a5e82e8eaab53918af0cd8909a57a27f8cb2288b80a613ac671b \ + --hash=sha256:766bc7c9a9c340342f4c864ccbda8e78111e4751f13b895812b9c148fb79e9d0 \ + --hash=sha256:78e29211f26da2fdd0e9c6d2b79f489476140cf7029b6a64808ade7ca4156a42 \ + --hash=sha256:819edbdcb42ab8598b494bcf0222684bbb7a3c772bd1b1f0be7e029a6063c28e \ + --hash=sha256:85b10a45b8993d195c4f3ff57025b8d1e11834909ee475c403bfa60cb4caefaf \ + --hash=sha256:88268ca418cacea64cecb0d1d600d3c6b3a8038fcba02e1e205178c5b1f47661 \ + --hash=sha256:8b39472beafc0bdcafc4c8c73ad082ebfdb449d566897a61e7acb4fa88089115 \ + --hash=sha256:8ea1936c26b99999b27479853039a7f34713f56c49375ad52b38535ec93a796c \ + --hash=sha256:98a07f9bf591e3a8919797bee1c53f026ba4acd587e5a4404c8e57c9ec36b2a5 \ + --hash=sha256:a185a04039df6cae8648bc8ab6d6fde7bf94f7188ecf7828e76ac52eef1e41d6 \ + --hash=sha256:a35009284d0d3d5c2c9601c164a911b8b4331608d98a9a66d47d97bb2f522b70 \ + --hash=sha256:a3acb384427816dd5d470f47e62137b87f74da694faa8a50147012cf40df276a \ + --hash=sha256:aa2ba7d2ad6df4d80127cea65e5b8d5e2c3adbf153ff4804452836328aca7c54 \ + --hash=sha256:b10e1ff4756ed27d5a29d7fc79cfce7ef1ff56ad20025b89bac7cf79e09abbbe \ + --hash=sha256:b137f4bf3ada9dc44d411478decc6ff09a79ed30b306cd2abaa98408c3588137 \ + --hash=sha256:b259a04a737cb3496be0901328eb8b7552ed8df4865d8c8f1cf1bffcfc0776a3 \ + --hash=sha256:b427c19380991a4eaab2f6144b64b99b412043314c6bf4ab544f97bb31ee4190 \ + --hash=sha256:bb693b1e3d9a2f3fd228e2110daf4b5aeedb36761ca1e4282f74725f6d89f611 \ + --hash=sha256:c261d74b1a945cf895a9d6eccd1685a8e837531beaab782da4d630a8d12deffb \ + --hash=sha256:c5bf2dc311127d91230cc79b92188c082634a06cf66c5234db49a43b910183b0 \ + --hash=sha256:ca1cc11d82677b9662082e5478b7528e2b7db7beaa6bdff42bd62789d81be399 \ + --hash=sha256:d4b2dddfc219f54f956ccd53cf76a1d338ffe68fc7f2849ec9c7feb9927ff692 \ + --hash=sha256:d71d30f2d92f67d944631c523713934fee37292469e182ebcd2c1dd8a64ce53f \ + --hash=sha256:d865db4a6318e1c1bea83292e0ed231090538fc4ca45425b0f0480eb338bbc6e \ + --hash=sha256:e2aa72e3ce1770317ef534f63d397b55e130725f5149bd36077c3b539019db27 \ + --hash=sha256:e3657301562ac3cb8018d30d0d3ebfa39932239f7b5703422057ef14b69949f5 \ + --hash=sha256:e64dd101d380a115cc5a0c7856788adb535f1a4e21fc543775602f8be95180ae \ + --hash=sha256:e8ca6a1fcdb2943c9cbc1804a1baf3acb6071d72a471591678ded84218006e14 \ + --hash=sha256:edb59506291b647a30884b1d51a599d605f40b20af4a7dc3d33786a47a31de60 \ + --hash=sha256:f9a0ebbe45c29b5e5866593c12b78bd9035f0f0f0d4bc8361680cd580d99db49 # via # -c src/backend/requirements.txt # -r src/backend/requirements.in @@ -961,9 +961,9 @@ gunicorn==26.0.0 \ # via # -c src/backend/requirements.txt # -r src/backend/requirements.in -icalendar==7.1.2 \ - --hash=sha256:01c76243c76c549f58bb51510a8f0a4edb7c539726adda1356dfd0dc04fb7a53 \ - --hash=sha256:ebc43ebeb357be98984b573d975118008dee3410d8df28b054ef2943cf3e367e +icalendar==7.1.3 \ + --hash=sha256:690f30aa50a76cbf854db5ad52654705db9c5cd0e1b152222f5d4b7854b60667 \ + --hash=sha256:eb03a0e215f30db689a72c49f18f998aabf17522eb0858a293c393510850727c # via # -c src/backend/requirements.txt # django-ical @@ -2094,15 +2094,15 @@ rpds-py==2026.5.1 \ # -c src/backend/requirements.txt # jsonschema # referencing -s3transfer==0.18.0 \ - --hash=sha256:239c13b09e65ad0346e1be7348b8a202dcad44ac7ea7c6eb858fc881dce739b6 \ - --hash=sha256:3760b8b7ec1315da54048b2d626276732bee4300d054d492d4e1d43e20d4ecbd +s3transfer==0.19.0 \ + --hash=sha256:777cc2415536f1debadb5c2ef7779275d0fc0fe0e042411cdd6caebeb2685262 \ + --hash=sha256:ce436931687addc4c1712d52d40b32f53e88315723f107ffa20ba82b05a0f685 # via # -c src/backend/requirements.txt # boto3 -sentry-sdk==2.61.1 \ - --hash=sha256:9c6adccb3feefa9ba032c8d295ca477575c2f11896046a2b0ad686c47c4af555 \ - --hash=sha256:fa36eaf4b8ad708f718500d4bdcc1532637526a22beb874d88cbc0a46458b5ae +sentry-sdk==2.63.0 \ + --hash=sha256:2a1502bf864769275dbc8c2c9fc7a0f7f5e18358180b615d262d13a31ffba216 \ + --hash=sha256:3a9b5ddd403f79eb73bd670f75f04485819db53d28f76ced7bc09041cb0dfd6a # via # -c src/backend/requirements.txt # -r src/backend/requirements.in @@ -2131,9 +2131,9 @@ sqlparse==0.5.5 \ # -c src/backend/requirements.txt # django # django-sql-utils -structlog==25.5.0 \ - --hash=sha256:098522a3bebed9153d4570c6d0288abf80a031dfdb2048d59a49e9dc2190fc98 \ - --hash=sha256:a8453e9b9e636ec59bd9e79bbd4a72f025981b3ba0f5837aebf48f02f37a7f9f +structlog==26.1.0 \ + --hash=sha256:e081a26d6c373e6d201eca24eede26d8ffab07f88f477822e679183428d3d91e \ + --hash=sha256:f63a716cbd1b1291cf7661de7794b455acfa4c43c5bcf1630e6ad5ddc1adb3b7 # via # -c src/backend/requirements.txt # django-structlog @@ -2157,9 +2157,9 @@ tinyhtml5==2.1.0 \ # via # -c src/backend/requirements.txt # weasyprint -tqdm==4.68.1 \ - --hash=sha256:fc163d96b287bd031e1aa24421ce4411b25559bd0a1be4fe649bdaa4d2c02bf5 \ - --hash=sha256:fea4a90e4023f764914569f7802a297277c5ab1a66be5144143e142e1a4031d8 +tqdm==4.68.3 \ + --hash=sha256:00dfa48452b6b6cfae3dd9885636c23d3422d1ec97c66d96818cbd5e0821d482 \ + --hash=sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03 # via # -c src/backend/requirements.txt # -r src/backend/requirements.in @@ -2201,9 +2201,9 @@ urllib3==2.7.0 \ # dulwich # requests # sentry-sdk -wcwidth==0.7.0 \ - --hash=sha256:5d69154c429a82910e241c738cd0e2976fac8a2dd47a1a805f4afed1c0f136f2 \ - --hash=sha256:90e3a7ea092341c44b99562e75d09e4d5160fe7a3974c6fb842a101a95e7eed0 +wcwidth==0.8.1 \ + --hash=sha256:f453740b1e4a4f3291faa37944c555d71056c4da08d59809b307ef4feba695c8 \ + --hash=sha256:faf5b4a5366a72dc49cad48cdf21f52bdf63bdda995178e483ba247ff79089b9 # via # -c src/backend/requirements.txt # blessed @@ -2229,88 +2229,97 @@ whitenoise==6.12.0 \ # via # -c src/backend/requirements.txt # -r src/backend/requirements.in -wrapt==1.17.3 \ - --hash=sha256:02b551d101f31694fc785e58e0720ef7d9a10c4e62c1c9358ce6f63f23e30a56 \ - --hash=sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828 \ - --hash=sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f \ - --hash=sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396 \ - --hash=sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77 \ - --hash=sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d \ - --hash=sha256:0f5f51a6466667a5a356e6381d362d259125b57f059103dd9fdc8c0cf1d14139 \ - --hash=sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7 \ - --hash=sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb \ - --hash=sha256:1f23fa283f51c890eda8e34e4937079114c74b4c81d2b2f1f1d94948f5cc3d7f \ - --hash=sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f \ - --hash=sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067 \ - --hash=sha256:24c2ed34dc222ed754247a2702b1e1e89fdbaa4016f324b4b8f1a802d4ffe87f \ - --hash=sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7 \ - --hash=sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b \ - --hash=sha256:30ce38e66630599e1193798285706903110d4f057aab3168a34b7fdc85569afc \ - --hash=sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05 \ - --hash=sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd \ - --hash=sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7 \ - --hash=sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9 \ - --hash=sha256:3e62d15d3cfa26e3d0788094de7b64efa75f3a53875cdbccdf78547aed547a81 \ - --hash=sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977 \ - --hash=sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa \ - --hash=sha256:46acc57b331e0b3bcb3e1ca3b421d65637915cfcd65eb783cb2f78a511193f9b \ - --hash=sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe \ - --hash=sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58 \ - --hash=sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8 \ - --hash=sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77 \ - --hash=sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85 \ - --hash=sha256:55cbbc356c2842f39bcc553cf695932e8b30e30e797f961860afb308e6b1bb7c \ - --hash=sha256:59923aa12d0157f6b82d686c3fd8e1166fa8cdfb3e17b42ce3b6147ff81528df \ - --hash=sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454 \ - --hash=sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a \ - --hash=sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e \ - --hash=sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c \ - --hash=sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6 \ - --hash=sha256:656873859b3b50eeebe6db8b1455e99d90c26ab058db8e427046dbc35c3140a5 \ - --hash=sha256:65d1d00fbfb3ea5f20add88bbc0f815150dbbde3b026e6c24759466c8b5a9ef9 \ - --hash=sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd \ - --hash=sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277 \ - --hash=sha256:70d86fa5197b8947a2fa70260b48e400bf2ccacdcab97bb7de47e3d1e6312225 \ - --hash=sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22 \ - --hash=sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116 \ - --hash=sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16 \ - --hash=sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc \ - --hash=sha256:758895b01d546812d1f42204bd443b8c433c44d090248bf22689df673ccafe00 \ - --hash=sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2 \ - --hash=sha256:7e18f01b0c3e4a07fe6dfdb00e29049ba17eadbc5e7609a2a3a4af83ab7d710a \ - --hash=sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804 \ - --hash=sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04 \ - --hash=sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1 \ - --hash=sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba \ - --hash=sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390 \ - --hash=sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0 \ - --hash=sha256:a7c06742645f914f26c7f1fa47b8bc4c91d222f76ee20116c43d5ef0912bba2d \ - --hash=sha256:a9a2203361a6e6404f80b99234fe7fb37d1fc73487b5a78dc1aa5b97201e0f22 \ - --hash=sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0 \ - --hash=sha256:ad85e269fe54d506b240d2d7b9f5f2057c2aa9a2ea5b32c66f8902f768117ed2 \ - --hash=sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18 \ - --hash=sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6 \ - --hash=sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311 \ - --hash=sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89 \ - --hash=sha256:caea3e9c79d5f0d2c6d9ab96111601797ea5da8e6d0723f77eabb0d4068d2b2f \ - --hash=sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39 \ - --hash=sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4 \ - --hash=sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5 \ - --hash=sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa \ - --hash=sha256:df7d30371a2accfe4013e90445f6388c570f103d61019b6b7c57e0265250072a \ - --hash=sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050 \ - --hash=sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6 \ - --hash=sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235 \ - --hash=sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056 \ - --hash=sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2 \ - --hash=sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418 \ - --hash=sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c \ - --hash=sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a \ - --hash=sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6 \ - --hash=sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0 \ - --hash=sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775 \ - --hash=sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10 \ - --hash=sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c +wrapt==2.2.1 \ + --hash=sha256:036dfb40128819a751c6f451c6b9c10172c49e4c401aebcdb8ecf2aec1683598 \ + --hash=sha256:03df9ebed4c73ab93fa8c07e3d41d818dfca1852b15731a3de59457b27814624 \ + --hash=sha256:05d5cb74d1b232ec8cfa130a8f900708699ff2491d97b8f85a4cdc5996294b85 \ + --hash=sha256:07be671fa8875971222b0ba9059ed8b4dc738631122feba17c93aa36b4213e9a \ + --hash=sha256:09ac16c081bebfd15d8e4dfa5bdc805990bbd52249ecff22530da7a129d6120b \ + --hash=sha256:0d9ff006f420b2ec8296aa56ade43ea7da3e997e85769f0aafc5e0661aacb710 \ + --hash=sha256:0f68f478004475d97906686e702ddbddeaf717c0b68ad2794384308f2dc713ae \ + --hash=sha256:17de18fc12cea55b8a9587314cb830573e37fb33b247a7515696350863714188 \ + --hash=sha256:1ae574d65c9fa8e86f64f6a7c2668f9fcd507b183e0e577619f504b883cb0a6c \ + --hash=sha256:1c9934ea5d92957e3cd0adbc0845539dccfd62710ebe16195a8c66c53954db36 \ + --hash=sha256:1d676ee388bc42a04d56dd7deb5605244dac2e35cc2fadbb43c9fa25bbd93508 \ + --hash=sha256:1ffa9cfd4bdb581539951b14ae661ff20ed0c3599b3e911a131ee0ec5ac11337 \ + --hash=sha256:2076d2335085eb09b9547e7688656fa8f5cf0183eab589d33499cd353489d797 \ + --hash=sha256:211f595f8e7faae5c5930fcc64708f2ba36849e0ba0fd653a843de9fa8d7db77 \ + --hash=sha256:24c52546acf2ab82412f2ab6fc5948a7fe958d3b4f070202e8dcdd865489eaf9 \ + --hash=sha256:2d83966dc7f4f45e8b97b5933685ac2e6e67fc0e19246ea314bceb9a8970c956 \ + --hash=sha256:2de9e20769fe9c1f6dcdc893c6a89287c5ccf8537c90b5de78aed8017697aad5 \ + --hash=sha256:2e08688ab16525897da6589d56d0aebaf417bbe91c2d8e3b96203b1efa596e85 \ + --hash=sha256:2f8c90c8afde51969487be4e1343ae049b268854877d415c2510baf833775052 \ + --hash=sha256:368eac1e20fd0bb03dd3cc42bf9887154c3861b60989389ccb5fac032617d215 \ + --hash=sha256:3aafea2975caef8ca49400640dde02cc7426e798f24870ed01f490bc3cffd32f \ + --hash=sha256:3e2f02472a1cbbf3884b365714a810b5947134a95ad6952b554cb8cce9d492b0 \ + --hash=sha256:3ffad790d9d11d8ecf9f17c4bb671a5b4089e4d8b575c46c5129597f41f836b0 \ + --hash=sha256:401229e9d63ca09f9b8891ecf83798d26c11bbb445d11ed9f1836b6d4585b38a \ + --hash=sha256:436addbc4bb4fc0a88c702577f51195d7d73683a7f3e0e5b253d8404d7847243 \ + --hash=sha256:44255c84bc57554fed822e83e70036b51afa9edb56fc7ca56c54410ece7898c9 \ + --hash=sha256:50972a1d974ea07725a7f6b1cec5f8759008afd030a0024843ebe7d52de47f2b \ + --hash=sha256:5590d63f5243251641cf543009b4c9314a79d0598fdb8a8e4cfc918494536c53 \ + --hash=sha256:585916e210db57b23543342c2f298e42331b617fd0c934caf5c64df44de8640e \ + --hash=sha256:5f1845c2a8cc1180ccccfa45785dd06f562730d19ef75be180334254012b6283 \ + --hash=sha256:5fa9bf3b9e66336589d03f42abce2da1055ad5c69b0c2b764852a8471c9b9114 \ + --hash=sha256:61a0013344674d2b648bc6e6fe9828dd4fc1d3b4eb7523809792f8cb952e2f16 \ + --hash=sha256:61acce4257a9883669703c525447c5b4c392edf0f987ae77ec32668440158f0e \ + --hash=sha256:628f5220c7a904d5fc78f7075c8d7871433eb6d035c94728a22fdf85f193d2a8 \ + --hash=sha256:64b7deeda4b70408e382328d8bbe52a256fe9bc63ae3db86d804608367e5422c \ + --hash=sha256:6744f504375775d7609c82c8d3d94af1c9a6f05586984536905908ba905277b9 \ + --hash=sha256:67a97e5b6c457f0cd3cfc19ebb2d84463e60c3ece754cc831e4281a3ca29bb18 \ + --hash=sha256:69f2e9244542cb34dd59c7f073445b9e54ad9f3fce8d93606c368a1b499fc413 \ + --hash=sha256:6ce32763ac31ce94fe9aada947e479b1975012bff166da409b4b9e4e376cf7e5 \ + --hash=sha256:6f56a647e4eaf5f0ca40330fb070f566bdf9f7b0db89a1af20d71c28dcd7a0ab \ + --hash=sha256:727ab4244622cd6ad2390f322642090c877d2e83a608d2653a7643ae5368d926 \ + --hash=sha256:74d6a0c31472fe5d814917266b9f46495d7c61ed890af08b468acea92fb89a8d \ + --hash=sha256:78b0aa6bfb7be8deed0ab23e7aa028cc5210c29bc2d32a04d52b50e517a7307e \ + --hash=sha256:7975bc88ab4b0f72ef2a2d5ae9d77d87efb5ef95e8f8046242fa9afdaaf2030b \ + --hash=sha256:7a4fdb9326aab4a5a477a1640e5ad786a8495901009d7e7b038371edd23a9d2b \ + --hash=sha256:844c858fc3bb7eacc0ba8efa904935d16aac6a4470948ad1e7e55c9f5a2a665f \ + --hash=sha256:87bacdaf225117a342a20d9c03438d701c02112f6e3f351ce9b7f32354f14797 \ + --hash=sha256:8a983a603a18c8708f024f7f6991b2e66159219abbf894634c5056243c55f3cd \ + --hash=sha256:8d1b4d0e0c2119587a31f5c029abd547e0c81d93b89d394566fe1588659eb579 \ + --hash=sha256:9011395be8db1827d106c6449b4bb6dd17e331ff6ec521f227e4588f1c78e46f \ + --hash=sha256:93fc2bf40cd7f4a0256010dce073d44eeb4a351b9bca94d0477ce2b6e62532b3 \ + --hash=sha256:95821352042722cd9f1108874579a47989d0a7e12a37d87d2fc4af20fd99ab8a \ + --hash=sha256:9907a4402ab6db12b7077a0ea5d7a4d028ecb22c8eee2b53527080d347cd1562 \ + --hash=sha256:9a04c28c10ba7fd12842b109d2edb0678872a2fe65277ca4ff06a0d61edee245 \ + --hash=sha256:9a5934eaea872e17936b5f45501eba5ab0bce9a74122e172b663d7c28c459c4a \ + --hash=sha256:9b984d1eb252145d6302c1dbd5e87fc6d404d45531447c84eadec04bf1fcb027 \ + --hash=sha256:9c210a6994b21aa9b29e81c8d11560e8fdab54c117e9cff37870d0a27bde1343 \ + --hash=sha256:9d8f204c8e3a8bf9ece17e0a83d137fd807440977f8a5e762d59306795011440 \ + --hash=sha256:a8f7176b83664af44567e9cc06e0d3827823fcc1a5e52307ebb8ac3aa95860b9 \ + --hash=sha256:a9dec1aca52dddde7df94818310fa2fe79739c8f385b2014c4cb1035f5508199 \ + --hash=sha256:ab5be648d5a0b86b7438864f8df3c705a65cef35a2fd3e5561e3e203167e0f27 \ + --hash=sha256:abd621552ede77c4c69be7fac44ba911225b0c812b6ba604e5964cf98085b474 \ + --hash=sha256:ac2745950b2bff80219c15ebf2fa9d8427eba7e249739f97e55c9d169e47e9e1 \ + --hash=sha256:aed9658797d0b45d6c49adcfc6b41f66e6f2d0c6de3ec79e16cf4b1855df240f \ + --hash=sha256:b6c0febfe38f22df2eb565c0ce8a092bb80411e56861ca382c443da83105423f \ + --hash=sha256:b9cf53ba90717db2e292401de290776c498d4bbfb0d4a559ca2895db8b9dcb5c \ + --hash=sha256:ba519b2d765df9871a25879e6f7fa78948ea59a2a31f9c1a257e34b651994afc \ + --hash=sha256:c318a64b53d97b841d7b5e637517e50a27be64bc695128422953d4b21710954e \ + --hash=sha256:c3723ff8eb8721f4daac98bc0256f15158e05316d5e52648ce9cebee434fbdd5 \ + --hash=sha256:c754dafdf5aaf0b401b644a90a30046929a0dd1a536e0ff0ec959a59155d9c7f \ + --hash=sha256:c803a3d331796255af51ba2c79ed0ac8275865b516c09e61f248d1e7aff31ce9 \ + --hash=sha256:c8cc5094b08abeae52da9c73c8a32003623be691a5193df2f4e3eac3d557c394 \ + --hash=sha256:cf3638274ab9d9b724c9baa0b4c04e132cd6faefb78b4dd3dd1a02a4bdaad41e \ + --hash=sha256:d047f6498c973874ba08ac3f97c69a2c4b2211c8de6f4c205f75cb1c9522596e \ + --hash=sha256:d2beb1c7cab10603aecdc42f8edd6ff013f9a32e4543474e38e6b77ce9975aeb \ + --hash=sha256:d7f513d3185e6fec82d0c3518f2e6365d8b4e49f5f45f29640d5162d56a23b54 \ + --hash=sha256:dd57607acc85678925940bd5df0385ff8332083a32fa8d7a43f8767f4997263c \ + --hash=sha256:e0cb7e4dd71f4c32e5e84843cd3c4cd65dda034314004bbe1d7f99af2426ab80 \ + --hash=sha256:e3677c7146ce694874941ba82b57092cc4875445aadf29d72807351023105143 \ + --hash=sha256:e395f7bc31851ef9b612050368cb446e9bc14cd7454b025018980349caf25ae5 \ + --hash=sha256:e422b2d647a65d6b080cad5accd09055d3809bdff00c76fba8dca00ca935572a \ + --hash=sha256:ed55af48b3eb28f43228ca2306788892bcb629eb2b5c4876e2a3659872c2f17a \ + --hash=sha256:ed928d0fda15fc0adc8d13305c8b3c0f2fba5b0669950c9e6d019d9162a3b3e8 \ + --hash=sha256:f4e1a92032a39cd5e3c647ca57dbf33b6a1938fd975623175793f9dbb63236de \ + --hash=sha256:f53ac9f3ef573326d009ed809beff4efcac6451931c2b8132586da4b9e53ff31 \ + --hash=sha256:f5b9daf6b629fce418e0cc3dd0436eac045188fa35deadb7a7f3941d5b8203f9 \ + --hash=sha256:f6518b94edb9150452e9aba08027d4cc293433753ec1fbefb4629a21cbc74181 \ + --hash=sha256:f70db64e8266d7c45d3b735f2e08eeb434b5e03da9a479ae42b2e2e486a21a00 \ + --hash=sha256:fafb4e739e43544d12cb4abd1605fd4683b6ca6a9ad682b7fd8f4d21973eafa8 \ + --hash=sha256:fd0135d34387f5fd087d9be368ea77ea89cf2451dc1cd1c622d35021bcb3ab50 # via # -c src/backend/requirements.txt # opentelemetry-instrumentation @@ -2388,19 +2397,20 @@ xmlsec==1.3.17 \ # -c src/backend/requirements.txt # -r src/backend/requirements.in # python3-saml -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 +zopfli==0.4.3 \ + --hash=sha256:0087c9a6f0c8a052be0f6d1a9bb71b6caffdd3e10201d6d6166e28d482cebe6d \ + --hash=sha256:47604eee5c6704bdf0e94d8391fe3b74ddb2abd84128fbcfdc3ee0fc265feaef \ + --hash=sha256:62248dbf8dbcbd588ee194b210e5be9fa80bce29641f55599d6d394bd2a9d8a3 \ + --hash=sha256:628c3e941752880b3491db8d44163d0aedb221944e22a17187ff7fc549b050f6 \ + --hash=sha256:769875152d0625c46707bcca57d4b2233fe653482067acd55fbf6ec525cb9bdc \ + --hash=sha256:7e9703ca6e7ef66c8d05e0826b6f558b680c9db8206f84f05a3ee93430a12e42 \ + --hash=sha256:7fa3c35193475290e3f007bbcdebdbae64ba2f012d75c632da0d727e1da50d5e \ + --hash=sha256:88f4fbe429aad72bc206275d81fab11a097e0f951a5848d1f51083c37ea73073 \ + --hash=sha256:921c2c9907f4364963848da5ad194b46d68865e07fdb975d04fd09bc42d47357 \ + --hash=sha256:d3a50f91a13cea9bafe025de8fd87a005eb26de02a4f0c193127ddbf23ac8ebe \ + --hash=sha256:d4f51dd1ab5312e837e2091284e0d9f1a138188f2e65812f9a5799dc02c45f94 \ + --hash=sha256:eb0c9c1d40a8cb1d58762d7e57290ccb753e0828c4d01be8acb59aae5d0ca206 \ + --hash=sha256:f2e0adcf7d36c6fd0dd36cc771ef7f0c5803a05666feafcd90d7170174a4148e # via # -c src/backend/requirements.txt # fonttools diff --git a/src/backend/requirements-dev-3.14.txt b/src/backend/requirements-dev-3.14.txt index 1bd491ec1f..f711289cc9 100644 --- a/src/backend/requirements-dev-3.14.txt +++ b/src/backend/requirements-dev-3.14.txt @@ -1,21 +1,25 @@ # This file was autogenerated by uv via the following command: -# uv pip compile src/backend/requirements-dev.in -o src/backend/requirements-dev-3.14.txt -c src/backend/requirements-3.14.txt --python-version=3.14 -c src/backend/requirements.txt +# uv pip compile src/backend/requirements-dev.in -o src/backend/requirements-dev-3.14.txt -c src/backend/requirements-3.14.txt --python-version=3.14 -c src/backend/requirements.txt -c src/backend/requirements-dev.txt asgiref==3.11.1 \ --hash=sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce \ --hash=sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133 # via # -c src/backend/requirements-3.14.txt + # -c src/backend/requirements-dev.txt # -c src/backend/requirements.txt # django build==1.5.0 \ --hash=sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f \ --hash=sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647 - # via pip-tools -certifi==2026.5.20 \ - --hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \ - --hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d + # via + # -c src/backend/requirements-dev.txt + # pip-tools +certifi==2026.6.17 \ + --hash=sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432 \ + --hash=sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db # via # -c src/backend/requirements-3.14.txt + # -c src/backend/requirements-dev.txt # -c src/backend/requirements.txt # requests cffi==2.0.0 \ @@ -105,6 +109,7 @@ cffi==2.0.0 \ --hash=sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf # via # -c src/backend/requirements-3.14.txt + # -c src/backend/requirements-dev.txt # -c src/backend/requirements.txt # cryptography charset-normalizer==3.4.7 \ @@ -239,13 +244,16 @@ charset-normalizer==3.4.7 \ --hash=sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464 # via # -c src/backend/requirements-3.14.txt + # -c src/backend/requirements-dev.txt # -c src/backend/requirements.txt # pdfminer-six # requests click==8.4.1 \ --hash=sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2 \ --hash=sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96 - # via pip-tools + # via + # -c src/backend/requirements-dev.txt + # pip-tools coverage[toml]==7.14.1 \ --hash=sha256:0177614a0370f227888b4e436a7c55686d6a9f90eb1ade2b624ba685a1686e86 \ --hash=sha256:01b7733daad0237daa01ef80fe2dfceffc911e6a17fa7b55d14aa8214eaaaecd \ @@ -353,7 +361,9 @@ coverage[toml]==7.14.1 \ --hash=sha256:fad54e871165f6ec2f536063ac74c3104508a12963e64072ba44bd822de52b0c \ --hash=sha256:fc459e5d73be2d6332fcfe8dbf3d8994671fe33c700f4565988ecfa511547253 \ --hash=sha256:fd86572566fb40189a8260446158235159bc7a82dfbc87a3b39cf4fb57fcec1c - # via -r src/backend/requirements-dev.in + # via + # -c src/backend/requirements-dev.txt + # -r src/backend/requirements-dev.in cryptography==48.0.1 \ --hash=sha256:08a597acce1ff37f347400087776599e2348a3a8bc53b44120e463cd274efe4a \ --hash=sha256:09f73a725d582cef64b91281a322cd798d14a33b2b6f2b7ad9531dc336d84c02 \ @@ -406,6 +416,7 @@ cryptography==48.0.1 \ --hash=sha256:fe0180af5bf9236518a087e35bf2d9a347d5f5f51e63c579d683ddff424e3d46 # via # -c src/backend/requirements-3.14.txt + # -c src/backend/requirements-dev.txt # -c src/backend/requirements.txt # pdfminer-six django==5.2.15 \ @@ -413,6 +424,7 @@ django==5.2.15 \ --hash=sha256:5154a9bf84ac01dde011e367f355c07dbb329532e06810dcf3ef2af269e236e7 # via # -c src/backend/requirements-3.14.txt + # -c src/backend/requirements-dev.txt # -c src/backend/requirements.txt # django-silk # django-slowtests @@ -420,62 +432,88 @@ django==5.2.15 \ # django-stubs-ext django-querycount==0.8.3 \ --hash=sha256:0782484e8a1bd29498fa0195a67106e47cdcc98fafe80cebb1991964077cb694 - # via -r src/backend/requirements-dev.in + # via + # -c src/backend/requirements-dev.txt + # -r src/backend/requirements-dev.in django-silk==5.5.0 \ --hash=sha256:41fcabe65d59d31ccdb69daeb3c3e6d30879ab2c00b0875b111fda0f69aec065 \ --hash=sha256:82b5a690d623935be916dd145e2f605a4ac9454d54e03df6a7ffcf38333c8444 - # via -r src/backend/requirements-dev.in + # via + # -c src/backend/requirements-dev.txt + # -r src/backend/requirements-dev.in django-slowtests==1.1.1 \ --hash=sha256:3c6936d420c9df444ac03625b41d97de043c662bbde61fbcd33e4cd407d0c247 - # via -r src/backend/requirements-dev.in + # via + # -c src/backend/requirements-dev.txt + # -r src/backend/requirements-dev.in django-stubs==6.0.5 \ --hash=sha256:7742b8e60afc68a8545158e2bdb103376d5a092f7902c17f370920a9c08eb957 \ --hash=sha256:9fb9eede9b2fc47b36c3dc4a93652eb959dff45466ec4a580e4a22782192d746 - # via -r src/backend/requirements-dev.in + # via + # -c src/backend/requirements-dev.txt + # -r src/backend/requirements-dev.in django-stubs-ext==6.0.5 \ --hash=sha256:1cc325e991303849bce70e19748981b225ef08b37256f263e113caf97cd3bcc0 \ --hash=sha256:a9932c8233d6dd4e34ae0b192026379c1a9be8f0b7c27aa1fa09ae215169773e - # via django-stubs + # via + # -c src/backend/requirements-dev.txt + # django-stubs django-test-migrations==1.5.0 \ --hash=sha256:1cbff04b1e82c5564a6f635284907b381cc11a2ff883adff46776d9126824f07 \ --hash=sha256:96a08f085fc8bfaa53d44618341d82a2d22fd194c821cd81b147b66f0bec0da8 - # via -r src/backend/requirements-dev.in + # via + # -c src/backend/requirements-dev.txt + # -r src/backend/requirements-dev.in django-types==0.24.0 \ --hash=sha256:af903de8b9ee963b7594459a7a20cb8eaaab176ae2b3244ecaa089e0c570b0d1 \ --hash=sha256:ddb478ca733e0dde5475118dd59ab340156980f9659fd92de2083326ae96100a - # via -r src/backend/requirements-dev.in + # via + # -c src/backend/requirements-dev.txt + # -r src/backend/requirements-dev.in gprof2dot==2025.4.14 \ --hash=sha256:0742e4c0b4409a5e8777e739388a11e1ed3750be86895655312ea7c20bd0090e \ --hash=sha256:35743e2d2ca027bf48fa7cba37021aaf4a27beeae1ae8e05a50b55f1f921a6ce - # via django-silk + # via + # -c src/backend/requirements-dev.txt + # django-silk idna==3.18 \ --hash=sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2 \ --hash=sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848 # via # -c src/backend/requirements-3.14.txt + # -c src/backend/requirements-dev.txt # -c src/backend/requirements.txt # requests iniconfig==2.3.0 \ --hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \ --hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 - # via pytest + # via + # -c src/backend/requirements-dev.txt + # pytest isort==8.0.1 \ --hash=sha256:171ac4ff559cdc060bcfff550bc8404a486fee0caab245679c2abe7cb253c78d \ --hash=sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75 - # via -r src/backend/requirements-dev.in + # via + # -c src/backend/requirements-dev.txt + # -r src/backend/requirements-dev.in markdown-it-py==4.2.0 \ --hash=sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49 \ --hash=sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a - # via rich + # via + # -c src/backend/requirements-dev.txt + # rich mdurl==0.1.2 \ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba - # via markdown-it-py + # via + # -c src/backend/requirements-dev.txt + # markdown-it-py packaging==26.2 \ --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \ --hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661 # via # -c src/backend/requirements-3.14.txt + # -c src/backend/requirements-dev.txt # -c src/backend/requirements.txt # build # pytest @@ -483,61 +521,75 @@ packaging==26.2 \ pdfminer-six==20260107 \ --hash=sha256:366585ba97e80dffa8f00cebe303d2f381884d8637af4ce422f1df3ef38111a9 \ --hash=sha256:96bfd431e3577a55a0efd25676968ca4ce8fd5b53f14565f85716ff363889602 - # via -r src/backend/requirements-dev.in + # via + # -c src/backend/requirements-dev.txt + # -r src/backend/requirements-dev.in pip==26.1.2 \ --hash=sha256:382ff9f685ee3bc25864f820aa50505825f10f5458ffff07e30a6d96e5715cab \ --hash=sha256:f49cd134c61cf2fd75e0ce2676db03e4054504a5a4986d00f8299ae632dc4605 - # via pip-tools + # via + # -c src/backend/requirements-dev.txt + # pip-tools pip-tools==7.5.3 \ --hash=sha256:3aac0c473240ae90db7213c033401f345b05197293ccbdd2704e52e7a783785e \ --hash=sha256:8fa364779ebc010cbfe17cb9de404457ac733e100840423f28f6955de7742d41 - # via -r src/backend/requirements-dev.in + # via + # -c src/backend/requirements-dev.txt + # -r src/backend/requirements-dev.in pluggy==1.6.0 \ --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 - # via pytest -prek==0.4.4 \ - --hash=sha256:0b04a0f36d07474f2a9fc5b1ba1197a1b326b2b211f39cd74cf0d4613545f7f4 \ - --hash=sha256:143154b329c05b2f9fa3230e604d02d9c4297dd43f96135a8ba166772e8ecd60 \ - --hash=sha256:23cfd96a25de1c93e3c43c746643b80489e3b2fa49ca9c0ffd6022e51535c900 \ - --hash=sha256:3efa28fb37b9ddbafb7759da8d497f0d36cf02a05816e15d6541f5669d5d2114 \ - --hash=sha256:3f3a25041733de987a47e5a7bace47182a6f0e2ae5f960cb54c1d4630afd2591 \ - --hash=sha256:4058638532c6dcbf0076d23b9264cbdd9f0f0e320762e237a6b9e4e4b854a766 \ - --hash=sha256:4ec5771153d158a0e4473933b7fd9b51e1b1f57f2df50aeb7560ea6812226dc5 \ - --hash=sha256:619bab14071670249777deea0cc0b29d904c4a514cf33b20e583900a544f0399 \ - --hash=sha256:9cebca8c15da4f1d6e3a25e6ae0611425c8596e926222050f2588c390e42df8a \ - --hash=sha256:a2b7c8710546a1e894afa7ab022030cd4e21f1ee7ffb301b4360773d22f1f00f \ - --hash=sha256:a427b792c4436f49732b1f6ebccf221fdcc6390c148474280da9c2c6eaabc9c4 \ - --hash=sha256:b998038fc92c990e03147eb5b95b0f2c394517f8857ab911aac8e092f1b9b3ab \ - --hash=sha256:bad3586fcea3e913f0edf2e8f132f97889e03976b7ee3d120fd294ad4e89a5eb \ - --hash=sha256:c38c5140ae2ea55ebb02e6ca590a416664ea1af287cdd21f54daeec53a81015a \ - --hash=sha256:c8742ac26363e74c855df6215a709d5db183204d00ac0f1a722b13aed4da3cd0 \ - --hash=sha256:e02bd4d5e05c500e4d9f70f024e30d13aa361dc490724b7f476d2e35542c239f \ - --hash=sha256:f032ccbe2d6edc345f81a6d772c18cc169d63c27b5a8292bfe416b352bdfee57 - # via -r src/backend/requirements-dev.in + # via + # -c src/backend/requirements-dev.txt + # pytest +prek==0.4.5 \ + --hash=sha256:14109d37b33e5529db41a3539d4f8f72d295f6eeddede3964994d898b8cec05c \ + --hash=sha256:1fd98b986767dafdb6b4305b563ee5a3a8f13bd3c78b98d708626815ea9f147f \ + --hash=sha256:2be7bcf839de19a0144ed5a5aadf73bc5899cf6823bb1c58cf1d45ae389c201a \ + --hash=sha256:40d262418105b2ede9836593a1927fc927cc8093c432e998640964102196996e \ + --hash=sha256:50697089a86a78d16f087c1912a2f3bc2bea82319a220fac52cc8e3ec9fc0426 \ + --hash=sha256:590427a42a3c1e5064487a0dc91167ae0c8a52168e77f574758ef9b138fcfd61 \ + --hash=sha256:646a86a1a082dbd99fed96314b1064f5644bb34c1f4037a63547a18e2160fb86 \ + --hash=sha256:66877ff21ae9d548f0f7e56fab8e65f1500a74a810e7749188c3f35a4a1b911b \ + --hash=sha256:7546989b2403c96137bd79d19ebfe21facb87266cefe819db2458c3b9b23f350 \ + --hash=sha256:86f76bd3d2ecf6fd9034d75c62ff4c786eb11d0dd0a1f79bbb4343b023e12769 \ + --hash=sha256:8b2ac9227504371d97338215b344184cb0b31ca94113515a3a90c509c6c5a707 \ + --hash=sha256:a586d14c3b852fdee1c3dcd0b9cb0915db9f9d054334b854fd9470bf68edf129 \ + --hash=sha256:a8ed0d28f3e7790e4402a9324c386509066df6e67cc587f7406f9a245b97b7e8 \ + --hash=sha256:aca9fa995536036a0171bcf7a4db96dc0a14f480054eda1d7d1c2e7739650993 \ + --hash=sha256:e491a1a4641d91d8b03dcce5588397e76d2a5b432c9b0a6c70475972b4512ab4 \ + --hash=sha256:f7517774c72b001573520dc7111156779fd3e5b4452c11f09ff53c71a067e835 \ + --hash=sha256:fccd11613ae92619d1ecda0ab3359ceebeb38898909ec84a8d383733d12158cc + # via + # -c src/backend/requirements-dev.txt + # -r src/backend/requirements-dev.in pycparser==3.0 \ --hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \ --hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992 # via # -c src/backend/requirements-3.14.txt + # -c src/backend/requirements-dev.txt # -c src/backend/requirements.txt # cffi pygments==2.20.0 \ --hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \ --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 # via + # -c src/backend/requirements-dev.txt # pytest # rich pyproject-hooks==1.2.0 \ --hash=sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8 \ --hash=sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 # via + # -c src/backend/requirements-dev.txt # build # pip-tools -pytest==9.0.3 \ - --hash=sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9 \ - --hash=sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c +pytest==9.1.1 \ + --hash=sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313 \ + --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c # via + # -c src/backend/requirements-dev.txt # pytest-codspeed # pytest-django pytest-codspeed==5.0.3 \ @@ -570,31 +622,41 @@ pytest-codspeed==5.0.3 \ --hash=sha256:f56d0339cd98d26f6e561987be25bdd2761a5d53d8f73493b1ebe02d0d451093 \ --hash=sha256:f852bee785a7a124cb1720b1915670c6742af87747dc4d838f3ffdbd365ce9d9 \ --hash=sha256:fe2ea83c924c2250675b75686c3ee456b8cf0208d83d552e182a195fdf467378 - # via -r src/backend/requirements-dev.in + # via + # -c src/backend/requirements-dev.txt + # -r src/backend/requirements-dev.in pytest-django==4.12.0 \ --hash=sha256:3ff300c49f8350ba2953b90297d23bf5f589db69545f56f1ec5f8cff5da83e85 \ --hash=sha256:df94ec819a83c8979c8f6de13d9cdfbe76e8c21d39473cfe2b40c9fc9be3c758 - # via -r src/backend/requirements-dev.in + # via + # -c src/backend/requirements-dev.txt + # -r src/backend/requirements-dev.in requests==2.34.2 \ --hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0 \ --hash=sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed # via # -c src/backend/requirements-3.14.txt + # -c src/backend/requirements-dev.txt # -c src/backend/requirements.txt # requests-mock requests-mock==1.12.1 \ --hash=sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563 \ --hash=sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401 - # via -r src/backend/requirements-dev.in + # via + # -c src/backend/requirements-dev.txt + # -r src/backend/requirements-dev.in rich==15.0.0 \ --hash=sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb \ --hash=sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36 - # via pytest-codspeed + # via + # -c src/backend/requirements-dev.txt + # pytest-codspeed setuptools==82.0.1 \ --hash=sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9 \ --hash=sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb # via # -c src/backend/requirements-3.14.txt + # -c src/backend/requirements-dev.txt # -c src/backend/requirements.txt # -r src/backend/requirements-dev.in # pip-tools @@ -603,42 +665,50 @@ sqlparse==0.5.5 \ --hash=sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e # via # -c src/backend/requirements-3.14.txt + # -c src/backend/requirements-dev.txt # -c src/backend/requirements.txt # django # django-silk -ty==0.0.1a21 \ - --hash=sha256:0efba2e52b58f536f4198ba5c4a36cac2ba67d83ec6f429ebc7704233bcda4c3 \ - --hash=sha256:1474d883129bb63da3b2380fc7ead824cd3baf6a9551e6aa476ffefc58057af3 \ - --hash=sha256:1f276ceab23a1410aec09508248c76ae0989c67fb7a0c287e0d4564994295531 \ - --hash=sha256:218d53e7919e885bd98e9196d9cb952d82178b299aa36da6f7f39333eb7400ed \ - --hash=sha256:21f708d02b6588323ffdbfdba38830dd0ecfd626db50aa6006b296b5470e52f9 \ - --hash=sha256:334d2a212ebf42a0e55d57561926af7679fe1e878175e11dcb81ad8df892844e \ - --hash=sha256:3c3bc66fcae41eff133cfe326dd65d82567a2fb5d4efe2128773b10ec2766819 \ - --hash=sha256:5dfc73299d441cc6454e36ed0a976877415024143dfca6592dc36f7701424383 \ - --hash=sha256:7505aeb8bf2a62f00f12cfa496f6c965074d75c8126268776565284c8a12d5dd \ - --hash=sha256:84243455f295ed850bd53f7089819321807d4e6ee3b1cbff6086137ae0259466 \ - --hash=sha256:87a200c21e02962e8a27374d9d152582331d57d709672431be58f4f898bf6cad \ - --hash=sha256:9463cac96b8f1bb5ba740fe1d42cd6bd152b43c5b159b2f07f8fd629bcdded34 \ - --hash=sha256:a8c769987d00fbc33054ff7e342633f475ea10dc43bc60fb9fb056159d48cb90 \ - --hash=sha256:ba13d03b9e095216ceb4e4d554a308517f28ab0a6e4dcd07cfe94563e4c2c489 \ - --hash=sha256:be8f457d7841b7ead2a3f6b65ba668abc172a1150a0f1f6c0958af3725dbb61a \ - --hash=sha256:cc0880ec344fbdf736b05d8d0da01f0caaaa02409bd9a24b68d18d0127a79b0e \ - --hash=sha256:e941e9a9d1e54b03eeaf9c3197c26a19cf76009fd5e41e16e5657c1c827bd6d3 \ - --hash=sha256:ecf41706b803827b0de8717f32a434dad1e67be9f4b8caf403e12013179ea06a - # via -r src/backend/requirements-dev.in +ty==0.0.51 \ + --hash=sha256:08adbe53fb8bc9e7f00e89bf1d3c875a02cda76d83f109d2e6ab1ff35a7bfa8c \ + --hash=sha256:25a5b31e6f23fd5dc63ad29087ded09932409e4154e2fe07bbaed015035990bb \ + --hash=sha256:2faed19a8f1505370de071c008df52a994fc03a204f3267c3a33a32ca26f854f \ + --hash=sha256:429a997394dac73870d71b87cc90efc54da3efaf319e72ca18aeef35a78aef90 \ + --hash=sha256:49a21237f6fd1de56beaff0a3e85fe022a09a3401e67e3abec41ce838a5d4d2e \ + --hash=sha256:608d417cd1eaf79bcbd713d9830d5e3db9d57ec225c3af3e4ac9a9ff66b45d70 \ + --hash=sha256:61b4b6a003c3ebe53a63a1125c9b6542aa01bc1b6c9a235d01ee328d000d61a9 \ + --hash=sha256:62ced5e380284f12b2dc4802a3e4ed3dac39913fc6719afde7978814a4c7f169 \ + --hash=sha256:62d94f06e8c317e89b6884f2bde443040e596b88c7c79bd944c84c105b06257a \ + --hash=sha256:79d1877e93460f936bc10ed1a31525702b7ce51075763ccba993be17f0b9e905 \ + --hash=sha256:947986bd82d324b3a5c58ce03f1dad160cdf36443d3e8f64b3484b861ba9bc64 \ + --hash=sha256:abd92913bc90d1705ef9391ff8c6822b61e2e827fa295eb30bf0dfabcf815645 \ + --hash=sha256:b90172d46365bb9d51a7011cbb5c60cc4f514f42c86635df6c092b717f85e1ac \ + --hash=sha256:bc7459348a253247bbfb2669a021e614281b86bbea24c36112b8a6e1a2499a16 \ + --hash=sha256:c1bd1355aee86af01e4e21b0bc16fc460fb05905761f0d8b8d70841de0feade8 \ + --hash=sha256:cc233a6235fb23e2a44b14731a10043e37ba2f30f2c361cf49ad3633c5b9da9c \ + --hash=sha256:dc5e93695ab5dcbf1eef663aee60ec23a413547cc9cb06adcb0d842e9166bd0f \ + --hash=sha256:f8f52952cff665bc52a36147e610c10f5699d30007d7a14ab7f345cff93476ff + # via + # -c src/backend/requirements-dev.txt + # -r src/backend/requirements-dev.in types-psycopg2==2.9.21.20260518 \ --hash=sha256:2fd728a4fa3860db0a4a9813e5f49c30ed329b3d2e60e73530d9db01b2b98420 \ --hash=sha256:8b1f80d90d6799a4fcdac12198b382a8feee9ed4340d5f69b56fc5ffa0644143 - # via django-types + # via + # -c src/backend/requirements-dev.txt + # django-types types-pyyaml==6.0.12.20260518 \ --hash=sha256:d2150f75a231c9fe9c7463bd29487d93e60bac90400287351384bc2284eba7cd \ --hash=sha256:d917f83fb38462550338c1297faedd860b3ec83912b96b1e3d73255f7473e466 - # via django-stubs + # via + # -c src/backend/requirements-dev.txt + # django-stubs typing-extensions==4.15.0 \ --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 # via # -c src/backend/requirements-3.14.txt + # -c src/backend/requirements-dev.txt # -c src/backend/requirements.txt # django-stubs # django-stubs-ext @@ -648,9 +718,12 @@ urllib3==2.7.0 \ --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897 # via # -c src/backend/requirements-3.14.txt + # -c src/backend/requirements-dev.txt # -c src/backend/requirements.txt # requests wheel==0.47.0 \ --hash=sha256:212281cab4dff978f6cedd499cd893e1f620791ca6ff7107cf270781e587eced \ --hash=sha256:cc72bd1009ba0cf63922e28f94d9d83b920aa2bb28f798a31d0691b02fa3c9b3 - # via pip-tools + # via + # -c src/backend/requirements-dev.txt + # pip-tools diff --git a/src/backend/requirements-dev.txt b/src/backend/requirements-dev.txt index 1f01208c0b..ddac7da830 100644 --- a/src/backend/requirements-dev.txt +++ b/src/backend/requirements-dev.txt @@ -10,9 +10,9 @@ build==1.5.0 \ --hash=sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f \ --hash=sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647 # via pip-tools -certifi==2026.5.20 \ - --hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \ - --hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d +certifi==2026.6.17 \ + --hash=sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432 \ + --hash=sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db # via # -c src/backend/requirements.txt # requests @@ -242,113 +242,113 @@ click==8.4.1 \ --hash=sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2 \ --hash=sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96 # via pip-tools -coverage[toml]==7.13.5 \ - --hash=sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256 \ - --hash=sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b \ - --hash=sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5 \ - --hash=sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d \ - --hash=sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a \ - --hash=sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969 \ - --hash=sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642 \ - --hash=sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87 \ - --hash=sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740 \ - --hash=sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215 \ - --hash=sha256:0cef0cdec915d11254a7f549c1170afecce708d30610c6abdded1f74e581666d \ - --hash=sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422 \ - --hash=sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8 \ - --hash=sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911 \ - --hash=sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b \ - --hash=sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587 \ - --hash=sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8 \ - --hash=sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606 \ - --hash=sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9 \ - --hash=sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf \ - --hash=sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633 \ - --hash=sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6 \ - --hash=sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43 \ - --hash=sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2 \ - --hash=sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61 \ - --hash=sha256:356e76b46783a98c2a2fe81ec79df4883a1e62895ea952968fb253c114e7f930 \ - --hash=sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc \ - --hash=sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247 \ - --hash=sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75 \ - --hash=sha256:3e1bb5f6c78feeb1be3475789b14a0f0a5b47d505bfc7267126ccbd50289999e \ - --hash=sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376 \ - --hash=sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01 \ - --hash=sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1 \ - --hash=sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3 \ - --hash=sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743 \ - --hash=sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9 \ - --hash=sha256:52f444e86475992506b32d4e5ca55c24fc88d73bcbda0e9745095b28ef4dc0cf \ - --hash=sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e \ - --hash=sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1 \ - --hash=sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd \ - --hash=sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b \ - --hash=sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab \ - --hash=sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d \ - --hash=sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a \ - --hash=sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0 \ - --hash=sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510 \ - --hash=sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f \ - --hash=sha256:7034b5c56a58ae5e85f23949d52c14aca2cfc6848a31764995b7de88f13a1ea0 \ - --hash=sha256:704de6328e3d612a8f6c07000a878ff38181ec3263d5a11da1db294fa6a9bdf8 \ - --hash=sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf \ - --hash=sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209 \ - --hash=sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9 \ - --hash=sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3 \ - --hash=sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3 \ - --hash=sha256:79060214983769c7ba3f0cee10b54c97609dca4d478fa1aa32b914480fd5738d \ - --hash=sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd \ - --hash=sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2 \ - --hash=sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882 \ - --hash=sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09 \ - --hash=sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea \ - --hash=sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c \ - --hash=sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562 \ - --hash=sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3 \ - --hash=sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806 \ - --hash=sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e \ - --hash=sha256:9b74db26dfea4f4e50d48a4602207cd1e78be33182bc9cbf22da94f332f99878 \ - --hash=sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e \ - --hash=sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9 \ - --hash=sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45 \ - --hash=sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29 \ - --hash=sha256:a1a6d79a14e1ec1832cabc833898636ad5f3754a678ef8bb4908515208bf84f4 \ - --hash=sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c \ - --hash=sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479 \ - --hash=sha256:ad146744ca4fd09b50c482650e3c1b1f4dfa1d4792e0a04a369c7f23336f0400 \ - --hash=sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c \ - --hash=sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a \ - --hash=sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf \ - --hash=sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686 \ - --hash=sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de \ - --hash=sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028 \ - --hash=sha256:c555b48be1853fe3997c11c4bd521cdd9a9612352de01fa4508f16ec341e6fe0 \ - --hash=sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179 \ - --hash=sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16 \ - --hash=sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85 \ - --hash=sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a \ - --hash=sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0 \ - --hash=sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810 \ - --hash=sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161 \ - --hash=sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607 \ - --hash=sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26 \ - --hash=sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819 \ - --hash=sha256:dc022073d063b25a402454e5712ef9e007113e3a676b96c5f29b2bda29352f40 \ - --hash=sha256:e0723d2c96324561b9aa76fb982406e11d93cdb388a7a7da2b16e04719cf7ca5 \ - --hash=sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15 \ - --hash=sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0 \ - --hash=sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90 \ - --hash=sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0 \ - --hash=sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6 \ - --hash=sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a \ - --hash=sha256:eb7fdf1ef130660e7415e0253a01a7d5a88c9c4d158bcf75cbbd922fd65a5b58 \ - --hash=sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b \ - --hash=sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17 \ - --hash=sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5 \ - --hash=sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664 \ - --hash=sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0 \ - --hash=sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f +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.1 \ --hash=sha256:08a597acce1ff37f347400087776599e2348a3a8bc53b44120e463cd274efe4a \ @@ -422,13 +422,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.3 \ - --hash=sha256:5fee22bcbbad59a78c727a820b6f4e68ff442ca76a922b7002e57c25dd7cb390 \ - --hash=sha256:ee895f403c373608eeb50822f0733f9d9ec5ab12731d4ab58956053bb95fdd9e +django-stubs==6.0.5 \ + --hash=sha256:7742b8e60afc68a8545158e2bdb103376d5a092f7902c17f370920a9c08eb957 \ + --hash=sha256:9fb9eede9b2fc47b36c3dc4a93652eb959dff45466ec4a580e4a22782192d746 # via -r src/backend/requirements-dev.in -django-stubs-ext==6.0.3 \ - --hash=sha256:3307d42132bc295d5744de6276bc5fdf6896efc70f891e21c0ae8bdf529d2762 \ - --hash=sha256:9e4105955419ae310d7da9cfd808e039d4dae3092c628f021057bb4f2c237f8f +django-stubs-ext==6.0.5 \ + --hash=sha256:1cc325e991303849bce70e19748981b225ef08b37256f263e113caf97cd3bcc0 \ + --hash=sha256:a9932c8233d6dd4e34ae0b192026379c1a9be8f0b7c27aa1fa09ae215169773e # via django-stubs django-test-migrations==1.5.0 \ --hash=sha256:1cbff04b1e82c5564a6f635284907b381cc11a2ff883adff46776d9126824f07 \ @@ -456,9 +456,9 @@ isort==8.0.1 \ --hash=sha256:171ac4ff559cdc060bcfff550bc8404a486fee0caab245679c2abe7cb253c78d \ --hash=sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75 # via -r src/backend/requirements-dev.in -markdown-it-py==4.0.0 \ - --hash=sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147 \ - --hash=sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3 +markdown-it-py==4.2.0 \ + --hash=sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49 \ + --hash=sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a # via rich mdurl==0.1.2 \ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ @@ -488,24 +488,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.5 \ + --hash=sha256:14109d37b33e5529db41a3539d4f8f72d295f6eeddede3964994d898b8cec05c \ + --hash=sha256:1fd98b986767dafdb6b4305b563ee5a3a8f13bd3c78b98d708626815ea9f147f \ + --hash=sha256:2be7bcf839de19a0144ed5a5aadf73bc5899cf6823bb1c58cf1d45ae389c201a \ + --hash=sha256:40d262418105b2ede9836593a1927fc927cc8093c432e998640964102196996e \ + --hash=sha256:50697089a86a78d16f087c1912a2f3bc2bea82319a220fac52cc8e3ec9fc0426 \ + --hash=sha256:590427a42a3c1e5064487a0dc91167ae0c8a52168e77f574758ef9b138fcfd61 \ + --hash=sha256:646a86a1a082dbd99fed96314b1064f5644bb34c1f4037a63547a18e2160fb86 \ + --hash=sha256:66877ff21ae9d548f0f7e56fab8e65f1500a74a810e7749188c3f35a4a1b911b \ + --hash=sha256:7546989b2403c96137bd79d19ebfe21facb87266cefe819db2458c3b9b23f350 \ + --hash=sha256:86f76bd3d2ecf6fd9034d75c62ff4c786eb11d0dd0a1f79bbb4343b023e12769 \ + --hash=sha256:8b2ac9227504371d97338215b344184cb0b31ca94113515a3a90c509c6c5a707 \ + --hash=sha256:a586d14c3b852fdee1c3dcd0b9cb0915db9f9d054334b854fd9470bf68edf129 \ + --hash=sha256:a8ed0d28f3e7790e4402a9324c386509066df6e67cc587f7406f9a245b97b7e8 \ + --hash=sha256:aca9fa995536036a0171bcf7a4db96dc0a14f480054eda1d7d1c2e7739650993 \ + --hash=sha256:e491a1a4641d91d8b03dcce5588397e76d2a5b432c9b0a6c70475972b4512ab4 \ + --hash=sha256:f7517774c72b001573520dc7111156779fd3e5b4452c11f09ff53c71a067e835 \ + --hash=sha256:fccd11613ae92619d1ecda0ab3359ceebeb38898909ec84a8d383733d12158cc # via -r src/backend/requirements-dev.in pycparser==3.0 \ --hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \ @@ -525,9 +525,9 @@ pyproject-hooks==1.2.0 \ # via # build # pip-tools -pytest==9.0.3 \ - --hash=sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9 \ - --hash=sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c +pytest==9.1.1 \ + --hash=sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313 \ + --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c # via # pytest-codspeed # pytest-django @@ -643,25 +643,25 @@ tomli==2.4.1 \ --hash=sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9 \ --hash=sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049 # via coverage -ty==0.0.1a21 \ - --hash=sha256:0efba2e52b58f536f4198ba5c4a36cac2ba67d83ec6f429ebc7704233bcda4c3 \ - --hash=sha256:1474d883129bb63da3b2380fc7ead824cd3baf6a9551e6aa476ffefc58057af3 \ - --hash=sha256:1f276ceab23a1410aec09508248c76ae0989c67fb7a0c287e0d4564994295531 \ - --hash=sha256:218d53e7919e885bd98e9196d9cb952d82178b299aa36da6f7f39333eb7400ed \ - --hash=sha256:21f708d02b6588323ffdbfdba38830dd0ecfd626db50aa6006b296b5470e52f9 \ - --hash=sha256:334d2a212ebf42a0e55d57561926af7679fe1e878175e11dcb81ad8df892844e \ - --hash=sha256:3c3bc66fcae41eff133cfe326dd65d82567a2fb5d4efe2128773b10ec2766819 \ - --hash=sha256:5dfc73299d441cc6454e36ed0a976877415024143dfca6592dc36f7701424383 \ - --hash=sha256:7505aeb8bf2a62f00f12cfa496f6c965074d75c8126268776565284c8a12d5dd \ - --hash=sha256:84243455f295ed850bd53f7089819321807d4e6ee3b1cbff6086137ae0259466 \ - --hash=sha256:87a200c21e02962e8a27374d9d152582331d57d709672431be58f4f898bf6cad \ - --hash=sha256:9463cac96b8f1bb5ba740fe1d42cd6bd152b43c5b159b2f07f8fd629bcdded34 \ - --hash=sha256:a8c769987d00fbc33054ff7e342633f475ea10dc43bc60fb9fb056159d48cb90 \ - --hash=sha256:ba13d03b9e095216ceb4e4d554a308517f28ab0a6e4dcd07cfe94563e4c2c489 \ - --hash=sha256:be8f457d7841b7ead2a3f6b65ba668abc172a1150a0f1f6c0958af3725dbb61a \ - --hash=sha256:cc0880ec344fbdf736b05d8d0da01f0caaaa02409bd9a24b68d18d0127a79b0e \ - --hash=sha256:e941e9a9d1e54b03eeaf9c3197c26a19cf76009fd5e41e16e5657c1c827bd6d3 \ - --hash=sha256:ecf41706b803827b0de8717f32a434dad1e67be9f4b8caf403e12013179ea06a +ty==0.0.51 \ + --hash=sha256:08adbe53fb8bc9e7f00e89bf1d3c875a02cda76d83f109d2e6ab1ff35a7bfa8c \ + --hash=sha256:25a5b31e6f23fd5dc63ad29087ded09932409e4154e2fe07bbaed015035990bb \ + --hash=sha256:2faed19a8f1505370de071c008df52a994fc03a204f3267c3a33a32ca26f854f \ + --hash=sha256:429a997394dac73870d71b87cc90efc54da3efaf319e72ca18aeef35a78aef90 \ + --hash=sha256:49a21237f6fd1de56beaff0a3e85fe022a09a3401e67e3abec41ce838a5d4d2e \ + --hash=sha256:608d417cd1eaf79bcbd713d9830d5e3db9d57ec225c3af3e4ac9a9ff66b45d70 \ + --hash=sha256:61b4b6a003c3ebe53a63a1125c9b6542aa01bc1b6c9a235d01ee328d000d61a9 \ + --hash=sha256:62ced5e380284f12b2dc4802a3e4ed3dac39913fc6719afde7978814a4c7f169 \ + --hash=sha256:62d94f06e8c317e89b6884f2bde443040e596b88c7c79bd944c84c105b06257a \ + --hash=sha256:79d1877e93460f936bc10ed1a31525702b7ce51075763ccba993be17f0b9e905 \ + --hash=sha256:947986bd82d324b3a5c58ce03f1dad160cdf36443d3e8f64b3484b861ba9bc64 \ + --hash=sha256:abd92913bc90d1705ef9391ff8c6822b61e2e827fa295eb30bf0dfabcf815645 \ + --hash=sha256:b90172d46365bb9d51a7011cbb5c60cc4f514f42c86635df6c092b717f85e1ac \ + --hash=sha256:bc7459348a253247bbfb2669a021e614281b86bbea24c36112b8a6e1a2499a16 \ + --hash=sha256:c1bd1355aee86af01e4e21b0bc16fc460fb05905761f0d8b8d70841de0feade8 \ + --hash=sha256:cc233a6235fb23e2a44b14731a10043e37ba2f30f2c361cf49ad3633c5b9da9c \ + --hash=sha256:dc5e93695ab5dcbf1eef663aee60ec23a413547cc9cb06adcb0d842e9166bd0f \ + --hash=sha256:f8f52952cff665bc52a36147e610c10f5699d30007d7a14ab7f345cff93476ff # via -r src/backend/requirements-dev.in types-psycopg2==2.9.21.20260518 \ --hash=sha256:2fd728a4fa3860db0a4a9813e5f49c30ed329b3d2e60e73530d9db01b2b98420 \ diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 8f55c3569f..08533040af 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -95,15 +95,15 @@ blessed==1.44.0 \ --hash=sha256:d3b5037f0143eb7c80b2000be5bcdf86161af68aed231b91deadbce4e7379785 \ --hash=sha256:e1d2ed93d3d90d0a1494a8b134d188c83fb89ae25590af638b921e8c1c8fa223 # via -r src/backend/requirements.in -boto3==1.43.23 \ - --hash=sha256:5d26498702ffd021dc0d57d0eefcc7101cd995ea0ed08c057c9b631efccbaa48 \ - --hash=sha256:8afc058924ef8a5c62467fe2e1e2e0304c22018587a044714da89f9c602ba856 +boto3==1.43.34 \ + --hash=sha256:42595057324606928c6e2432b3093978e4d722e0d432bce942f2a385702c0a43 \ + --hash=sha256:444207c6c883d4df3ea3b2c36df43ad492b86e0b889eebd2fc1d5ea8db0a8a1a # via # django-anymail # django-storages -botocore==1.43.23 \ - --hash=sha256:69ff3d951cb644d1d84db646663c7eb919dc9c0c47e5768e947c8a71121b3d77 \ - --hash=sha256:a6737c598750f330bfa8ef2be2d9fa84b5d2d643b6bbb0d22e129e03b7535df1 +botocore==1.43.34 \ + --hash=sha256:238a0269f33c5914b9343900b44767e783b3e8b6dcb6e065eac8b4495601c5df \ + --hash=sha256:ccc973cf30c6445b30afe5760f6dc949a80f1f862cb23d9c45747f2c814ece77 # via # boto3 # s3transfer @@ -209,9 +209,9 @@ brotli==1.2.0 \ --hash=sha256:fc1530af5c3c275b8524f2e24841cbe2599d74462455e9bae5109e9ff42e9361 \ --hash=sha256:ff09cd8c5eec3b9d02d2408db41be150d8891c5566addce57513bf546e3d6c6d # via fonttools -certifi==2026.5.20 \ - --hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \ - --hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d +certifi==2026.6.17 \ + --hash=sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432 \ + --hash=sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db # via # requests # sentry-sdk @@ -574,9 +574,9 @@ django-ipware==7.0.1 \ --hash=sha256:d9ec43d2bf7cdf216fed8d494a084deb5761a54860a53b2e74346a4f384cff47 \ --hash=sha256:db16bbee920f661ae7f678e4270460c85850f03c6761a4eaeb489bdc91f64709 # via django-structlog -django-js-asset==3.1.2 \ - --hash=sha256:1fc7584199ed1941ed7c8e7b87ca5524bb0f2ba941561d2a104e88ee9f07bedd \ - --hash=sha256:b5ffe376aebbd73b7af886d675ac9f43ca63b39540190fa8409c9f8e79145f68 +django-js-asset==4.0.1 \ + --hash=sha256:51ad1060115bf741bef88dc1287ca7f70e1d6ea7cf935f52a34f39eca9bd9db1 \ + --hash=sha256:967ac7d95ce094d9f65f358278bcd494201e2f9bbee936430150de83eac9f656 # via django-mptt django-mailbox==4.10.1 \ --hash=sha256:9060a4ddc81d16aa699e266649c12eaf4f29671b5266352e2fad3043a6832b52 @@ -589,9 +589,9 @@ 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 \ - --hash=sha256:a8d249bf3ce6ad7fb953530c920cc85ea7f1137c0fde747a74204ea24ec97ab1 +django-money==3.6.1 \ + --hash=sha256:32a8027a91dea969951a34a9cc4a4f101aaa6ec45e7f784f0b17ec7813818fb2 \ + --hash=sha256:9b25d992a457af932eef1e5fef0098dd3ff0c92f4255d2d6d452af772b2fdde5 # via -r src/backend/requirements.in django-mptt==0.18.0 \ --hash=sha256:bfa3f01627e3966a1df901aeca74570a3e933e66809ebf58d9df673e63627afb \ @@ -795,58 +795,58 @@ googleapis-common-protos==1.75.0 \ # via # opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-http -grpcio==1.81.0 \ - --hash=sha256:0fba53cb96004b2b7fb758b46b2288cb49d0b658316a4e73f3ef67230616ee65 \ - --hash=sha256:194eddfacc84d80f50512e9fd4ee851d5f2499f18f299c95aa8fb4748f0537e0 \ - --hash=sha256:19f201da7b4e5c0559198abe5a97157e726f3abe6e8f5e832d4a50740f6dcc22 \ - --hash=sha256:21ec30b9ea320c8207ea7cd05873ad64aa69fdd0e81b6758b3347983ba20b50a \ - --hash=sha256:275144b0115353339dbb8a6f28a9cf8997b5bf40e37f8f66ac0b0ea57e95b43f \ - --hash=sha256:300f3337b6425fd16ead9a4f9b2ac25801acb64aa5bc0b99eb69901645b2b1d2 \ - --hash=sha256:3755c9669307cad18e7e009860fdea98118978d2300451bd8530a53048e741e7 \ - --hash=sha256:3d4e0ce5a40a998cf608c8ba60ecfe18fdf364a9aa193ae4ac3faeecd0e86757 \ - --hash=sha256:40edffb4ec3689373825d367c4457727047a6e554f03245265ecc8cc03215f22 \ - --hash=sha256:43c121e135ae44d1559b430db2b2dfad7421cbbe40e1deba506c7dc62b439719 \ - --hash=sha256:4e032feb3bfb4e2749b140a2302a6baa8ead1b9781ff5cf7094e4402b5e9372e \ - --hash=sha256:5192857589f223e5a98ff0e31f6e551b19040e647d17bfe10116c8a2ce3b8696 \ - --hash=sha256:57b3b0e73a518fa286959b40c3eddd02703504ca186e8b7b2945954519bd8b2c \ - --hash=sha256:5e925a70fe99fe5794f7beca0ea034c75f068afcc356d79047e73f99cdcca34c \ - --hash=sha256:62bbe463c9f0f2ff24e31bd25f8dd8b4bae78900e315915a3195a0ef1471a855 \ - --hash=sha256:638ccc1b86f7540170a169cb900799b9296a1381e47879ce60b0de9d3db73d33 \ - --hash=sha256:725801c7086d7e4cd160e42bb2f54e0aeb976b9568df3cc6f843b15d29b79fb1 \ - --hash=sha256:77eb4e9fe61486bd1198cc7236ebb0f70e66234e63c0348f40bc2553ed16a88b \ - --hash=sha256:7915a2e63acdc05264a206e1bddfd8e1fb8a29e406c18d72d30f8c124e021374 \ - --hash=sha256:794e6aa648e8df47d8f908dc8c3b42347d04ec58438f1dcd4e445f09b4f6b0ce \ - --hash=sha256:8226ba097eed660ef14d36c6a69b85038552bb8b6d17b44a5aa6f9abf48b8e08 \ - --hash=sha256:87e33b7afcfb3585121b5f007d2c52b8c534104d18f556e840d35193ca2a9141 \ - --hash=sha256:8bb1789c94322a13336a2b6c58d9c14d68f8628b6e24205a799c69f5bf8516ce \ - --hash=sha256:8c0855a350886f713b9e458e2a10d208009dcaa849f574e39cd6067db1fe1279 \ - --hash=sha256:909bb3222b53235498d2c5817a0596d82b0aaea490ba93fdf1b060e2938a543c \ - --hash=sha256:97bbd623f7ded558fd4f7cb5a4f600c4d4de65c5dd364c83a5b14b2a10a2d3b5 \ - --hash=sha256:98c6240f563178fc5877bd50e6ff274463e53e1472128f4110742450739659fa \ - --hash=sha256:9f355384e5543ab77a755a7085225ecc19f32b76032e851cbd8145715d79dec8 \ - --hash=sha256:a524cd530900bd24511fcb7f2ed144da4ea37711c4b094475d0bceca7a93a170 \ - --hash=sha256:a5acd7efd3b1fe9b4eb0bcaaa1507eed68a0ad0678b654c3f7b464df9ba9dca5 \ - --hash=sha256:a9351055f52660b58f3d4890ea66188b5134399f82b11aa0c55bd4b99eff5390 \ - --hash=sha256:aa948712c8e5fa40ec250870bda14bc7578e1bb832a8912d9d2a0f720518edbe \ - --hash=sha256:aaaa4f7f2057d795952e4eacf3f342be8b5b156992f6ac85023c8b98794ebd47 \ - --hash=sha256:b4108e5d9d0f651b7eea749116181fe6c315b145661a80ec31f05ec2dbe21af7 \ - --hash=sha256:b76ea9d55cd08fcdbda25d28e0f76679536710acb7fbd5b1f70cb4ac49317265 \ - --hash=sha256:b8b025b6af43ee0ad4a70307025d77bcab5adde7c4597786010d802c203e9fc5 \ - --hash=sha256:b93cee313cae4e113fbb3a0ce1ea5633db6f63cfde2b2dc1d817429026b2a50b \ - --hash=sha256:c197e2ef75a442528072b29e9755da299110e8610e8bcbb59a6b4cf55384f005 \ - --hash=sha256:c36f5d5e97944cbda2d4096b4ae262e6e68506246b61582acf1b8591607f3ccc \ - --hash=sha256:c4fe218c5a35e1d87a5a26544237f1fa41dfd9cbd3c856b0810a30061f8b0aaf \ - --hash=sha256:c6ff087cb1f563f47b504b4e29e684129fc5ae4863faf3ebca08a327764ee6cb \ - --hash=sha256:cd78145b7f7784661c524624f3526c9c6f891b30a4b54cb93a40806d0d0d61e9 \ - --hash=sha256:db217c2e52931719f9937bd12082cd4d7b495b35803d5760686975c285924bf8 \ - --hash=sha256:dbdb99986548a7e87f8343805ef315fd4eb50ffaabf4fb1206e42f2542bb805d \ - --hash=sha256:e4d053900a0d24b75d7521139a3872150301b3d6bde3bed5e12318fb25791e4d \ - --hash=sha256:e7746ba3e6efc9e2b748eff59470a2b8684d5a9ec607c6580bcaa5be175820bc \ - --hash=sha256:f345de40ef2e65f63645d53d251824e6070e07804827c5b00ec2e44555f9f901 \ - --hash=sha256:f750a091fff3a3991731abc1f818bdc64874bb3528162732cb4d45f2e07821a6 \ - --hash=sha256:f85570a016d794c29b1e76cf22f67af4486ddbe779e0f30674f138fa4e1769ec \ - --hash=sha256:fbbe81314a9d92156abce8b62c09364eb8bafc0ca2a19919a45ec64b5c6cb664 \ - --hash=sha256:ff83d889e3ebf6341c8c7864ad8031591ad5ca61599072fc511644d1eb962d2b +grpcio==1.81.1 \ + --hash=sha256:0490c30c261eded63f3f354979f9dc4502a9fb944cccb60cd9dc85f5a7349854 \ + --hash=sha256:0a37165cc80b1a368384b383e63a4c38116a10467ae44c904d2d7468c4470ec2 \ + --hash=sha256:12b7524c88d4026d3dcb7b0ebe16b6714f3b4af402ddd0f0639ab064a00c87c3 \ + --hash=sha256:15641444eca4a29358107b3dceb74c1c6305c55c822fd199b458aaea4068a7fb \ + --hash=sha256:1b22c80559854b789a01fd89e8929b3798a156c0829b5282a8939f33ad4115ad \ + --hash=sha256:1e123f9b37edb8375fd74130d1f69c944bbf0a7b06761ae7211154b8759e94d2 \ + --hash=sha256:24c8e57504c8f45b237e40b99262d181071e5099a07053695b75d97bb53053a0 \ + --hash=sha256:2c2e2ae6867c2966b8daccc836d54a13218e0007e9a490aeb81dd05be64d22d7 \ + --hash=sha256:30e825f6848d9f18bba350ed6c75c1b02a0b5184474a31db9a32b1fa66fd8c79 \ + --hash=sha256:3768a5ff1b2125e6f552e561b6b2dca0e64982d8949689b4df145cf8b98d7821 \ + --hash=sha256:3ad74f8bb1a18963914c5452d289422830b39459e8776ebbcd207be1fbfb1d94 \ + --hash=sha256:410482da976329fe5f4067270401b12cf2bd552ff8020f054ecfaddb5475f9d6 \ + --hash=sha256:428bec0161b48d8cf583c068591bc0016d0d9cfff52462b72b3884861ea768c5 \ + --hash=sha256:506f48f2f9c29b143fca3dad7b0d518c188b6c9648c75a2ae6e2d9f2c13a060b \ + --hash=sha256:58ad1131c300d3c9b933802b3cc4dc69d380822935ba50b28703156ea826fbf7 \ + --hash=sha256:592b5fee597faa91cce2dd294dd7d9a1c83d76c4dbf877e33ec1adb866b2fbed \ + --hash=sha256:61233fe8951e5c85dff81c2458b6528624760166946b5b47ea150a589168411f \ + --hash=sha256:62481553b1793a27e9b9c3cf9e5bd483ef045ca72462592074b46d42b0c4d9b9 \ + --hash=sha256:6282caffb41ec326d4cb67ca9cf53b739d1b2f975a2acb498c7418e9f7d9a416 \ + --hash=sha256:69ef28e54fc85397f91b8c19592b8ef3d81952080366914823bd8572a2958120 \ + --hash=sha256:6f9a0c9c1cc15c112d1c053064fd032b64917062292c3d70aea280e02ae10b77 \ + --hash=sha256:6fa10a767143a5e82e8eaab53918af0cd8909a57a27f8cb2288b80a613ac671b \ + --hash=sha256:766bc7c9a9c340342f4c864ccbda8e78111e4751f13b895812b9c148fb79e9d0 \ + --hash=sha256:78e29211f26da2fdd0e9c6d2b79f489476140cf7029b6a64808ade7ca4156a42 \ + --hash=sha256:819edbdcb42ab8598b494bcf0222684bbb7a3c772bd1b1f0be7e029a6063c28e \ + --hash=sha256:85b10a45b8993d195c4f3ff57025b8d1e11834909ee475c403bfa60cb4caefaf \ + --hash=sha256:88268ca418cacea64cecb0d1d600d3c6b3a8038fcba02e1e205178c5b1f47661 \ + --hash=sha256:8b39472beafc0bdcafc4c8c73ad082ebfdb449d566897a61e7acb4fa88089115 \ + --hash=sha256:8ea1936c26b99999b27479853039a7f34713f56c49375ad52b38535ec93a796c \ + --hash=sha256:98a07f9bf591e3a8919797bee1c53f026ba4acd587e5a4404c8e57c9ec36b2a5 \ + --hash=sha256:a185a04039df6cae8648bc8ab6d6fde7bf94f7188ecf7828e76ac52eef1e41d6 \ + --hash=sha256:a35009284d0d3d5c2c9601c164a911b8b4331608d98a9a66d47d97bb2f522b70 \ + --hash=sha256:a3acb384427816dd5d470f47e62137b87f74da694faa8a50147012cf40df276a \ + --hash=sha256:aa2ba7d2ad6df4d80127cea65e5b8d5e2c3adbf153ff4804452836328aca7c54 \ + --hash=sha256:b10e1ff4756ed27d5a29d7fc79cfce7ef1ff56ad20025b89bac7cf79e09abbbe \ + --hash=sha256:b137f4bf3ada9dc44d411478decc6ff09a79ed30b306cd2abaa98408c3588137 \ + --hash=sha256:b259a04a737cb3496be0901328eb8b7552ed8df4865d8c8f1cf1bffcfc0776a3 \ + --hash=sha256:b427c19380991a4eaab2f6144b64b99b412043314c6bf4ab544f97bb31ee4190 \ + --hash=sha256:bb693b1e3d9a2f3fd228e2110daf4b5aeedb36761ca1e4282f74725f6d89f611 \ + --hash=sha256:c261d74b1a945cf895a9d6eccd1685a8e837531beaab782da4d630a8d12deffb \ + --hash=sha256:c5bf2dc311127d91230cc79b92188c082634a06cf66c5234db49a43b910183b0 \ + --hash=sha256:ca1cc11d82677b9662082e5478b7528e2b7db7beaa6bdff42bd62789d81be399 \ + --hash=sha256:d4b2dddfc219f54f956ccd53cf76a1d338ffe68fc7f2849ec9c7feb9927ff692 \ + --hash=sha256:d71d30f2d92f67d944631c523713934fee37292469e182ebcd2c1dd8a64ce53f \ + --hash=sha256:d865db4a6318e1c1bea83292e0ed231090538fc4ca45425b0f0480eb338bbc6e \ + --hash=sha256:e2aa72e3ce1770317ef534f63d397b55e130725f5149bd36077c3b539019db27 \ + --hash=sha256:e3657301562ac3cb8018d30d0d3ebfa39932239f7b5703422057ef14b69949f5 \ + --hash=sha256:e64dd101d380a115cc5a0c7856788adb535f1a4e21fc543775602f8be95180ae \ + --hash=sha256:e8ca6a1fcdb2943c9cbc1804a1baf3acb6071d72a471591678ded84218006e14 \ + --hash=sha256:edb59506291b647a30884b1d51a599d605f40b20af4a7dc3d33786a47a31de60 \ + --hash=sha256:f9a0ebbe45c29b5e5866593c12b78bd9035f0f0f0d4bc8361680cd580d99db49 # via # -r src/backend/requirements.in # opentelemetry-exporter-otlp-proto-grpc @@ -854,9 +854,9 @@ gunicorn==26.0.0 \ --hash=sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc \ --hash=sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf # via -r src/backend/requirements.in -icalendar==7.1.2 \ - --hash=sha256:01c76243c76c549f58bb51510a8f0a4edb7c539726adda1356dfd0dc04fb7a53 \ - --hash=sha256:ebc43ebeb357be98984b573d975118008dee3410d8df28b054ef2943cf3e367e +icalendar==7.1.3 \ + --hash=sha256:690f30aa50a76cbf854db5ad52654705db9c5cd0e1b152222f5d4b7854b60667 \ + --hash=sha256:eb03a0e215f30db689a72c49f18f998aabf17522eb0858a293c393510850727c # via django-ical idna==3.18 \ --hash=sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2 \ @@ -1874,13 +1874,13 @@ rpds-py==2026.5.1 \ # via # jsonschema # referencing -s3transfer==0.18.0 \ - --hash=sha256:239c13b09e65ad0346e1be7348b8a202dcad44ac7ea7c6eb858fc881dce739b6 \ - --hash=sha256:3760b8b7ec1315da54048b2d626276732bee4300d054d492d4e1d43e20d4ecbd +s3transfer==0.19.0 \ + --hash=sha256:777cc2415536f1debadb5c2ef7779275d0fc0fe0e042411cdd6caebeb2685262 \ + --hash=sha256:ce436931687addc4c1712d52d40b32f53e88315723f107ffa20ba82b05a0f685 # via boto3 -sentry-sdk==2.61.1 \ - --hash=sha256:9c6adccb3feefa9ba032c8d295ca477575c2f11896046a2b0ad686c47c4af555 \ - --hash=sha256:fa36eaf4b8ad708f718500d4bdcc1532637526a22beb874d88cbc0a46458b5ae +sentry-sdk==2.63.0 \ + --hash=sha256:2a1502bf864769275dbc8c2c9fc7a0f7f5e18358180b615d262d13a31ffba216 \ + --hash=sha256:3a9b5ddd403f79eb73bd670f75f04485819db53d28f76ced7bc09041cb0dfd6a # via # -r src/backend/requirements.in # django-q-sentry @@ -1901,9 +1901,9 @@ sqlparse==0.5.5 \ # via # django # django-sql-utils -structlog==25.5.0 \ - --hash=sha256:098522a3bebed9153d4570c6d0288abf80a031dfdb2048d59a49e9dc2190fc98 \ - --hash=sha256:a8453e9b9e636ec59bd9e79bbd4a72f025981b3ba0f5837aebf48f02f37a7f9f +structlog==26.1.0 \ + --hash=sha256:e081a26d6c373e6d201eca24eede26d8ffab07f88f477822e679183428d3d91e \ + --hash=sha256:f63a716cbd1b1291cf7661de7794b455acfa4c43c5bcf1630e6ad5ddc1adb3b7 # via django-structlog tablib[xls, xlsx, yaml]==3.9.0 \ --hash=sha256:1b6abd8edb0f35601e04c6161d79660fdcde4abb4a54f66cc9f9054bd55d5fe2 \ @@ -1920,9 +1920,9 @@ tinyhtml5==2.1.0 \ --hash=sha256:60a50ec3d938a37e491efa01af895853060943dcebb5627de5b10d188b338a67 \ --hash=sha256:6e11cfff38515834268daf89d5f85bbde0b6dd02e8d9e212d1385c2289b89f0a # via weasyprint -tqdm==4.68.1 \ - --hash=sha256:fc163d96b287bd031e1aa24421ce4411b25559bd0a1be4fe649bdaa4d2c02bf5 \ - --hash=sha256:fea4a90e4023f764914569f7802a297277c5ab1a66be5144143e142e1a4031d8 +tqdm==4.68.3 \ + --hash=sha256:00dfa48452b6b6cfae3dd9885636c23d3422d1ec97c66d96818cbd5e0821d482 \ + --hash=sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03 # via -r src/backend/requirements.in typing-extensions==4.15.0 \ --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ @@ -1960,9 +1960,9 @@ urllib3==2.7.0 \ # dulwich # requests # sentry-sdk -wcwidth==0.7.0 \ - --hash=sha256:5d69154c429a82910e241c738cd0e2976fac8a2dd47a1a805f4afed1c0f136f2 \ - --hash=sha256:90e3a7ea092341c44b99562e75d09e4d5160fe7a3974c6fb842a101a95e7eed0 +wcwidth==0.8.1 \ + --hash=sha256:f453740b1e4a4f3291faa37944c555d71056c4da08d59809b307ef4feba695c8 \ + --hash=sha256:faf5b4a5366a72dc49cad48cdf21f52bdf63bdda995178e483ba247ff79089b9 # via # blessed # prettytable @@ -1982,88 +1982,97 @@ whitenoise==6.12.0 \ --hash=sha256:f723ebb76a112e98816ff80fcea0a6c9b8ecde835f8ddda25df7a30a3c2db6ad \ --hash=sha256:fc5e8c572e33ebf24795b47b6a7da8da3c00cff2349f5b04c02f28d0cc5a3cc2 # via -r src/backend/requirements.in -wrapt==1.17.3 \ - --hash=sha256:02b551d101f31694fc785e58e0720ef7d9a10c4e62c1c9358ce6f63f23e30a56 \ - --hash=sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828 \ - --hash=sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f \ - --hash=sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396 \ - --hash=sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77 \ - --hash=sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d \ - --hash=sha256:0f5f51a6466667a5a356e6381d362d259125b57f059103dd9fdc8c0cf1d14139 \ - --hash=sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7 \ - --hash=sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb \ - --hash=sha256:1f23fa283f51c890eda8e34e4937079114c74b4c81d2b2f1f1d94948f5cc3d7f \ - --hash=sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f \ - --hash=sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067 \ - --hash=sha256:24c2ed34dc222ed754247a2702b1e1e89fdbaa4016f324b4b8f1a802d4ffe87f \ - --hash=sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7 \ - --hash=sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b \ - --hash=sha256:30ce38e66630599e1193798285706903110d4f057aab3168a34b7fdc85569afc \ - --hash=sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05 \ - --hash=sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd \ - --hash=sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7 \ - --hash=sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9 \ - --hash=sha256:3e62d15d3cfa26e3d0788094de7b64efa75f3a53875cdbccdf78547aed547a81 \ - --hash=sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977 \ - --hash=sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa \ - --hash=sha256:46acc57b331e0b3bcb3e1ca3b421d65637915cfcd65eb783cb2f78a511193f9b \ - --hash=sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe \ - --hash=sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58 \ - --hash=sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8 \ - --hash=sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77 \ - --hash=sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85 \ - --hash=sha256:55cbbc356c2842f39bcc553cf695932e8b30e30e797f961860afb308e6b1bb7c \ - --hash=sha256:59923aa12d0157f6b82d686c3fd8e1166fa8cdfb3e17b42ce3b6147ff81528df \ - --hash=sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454 \ - --hash=sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a \ - --hash=sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e \ - --hash=sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c \ - --hash=sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6 \ - --hash=sha256:656873859b3b50eeebe6db8b1455e99d90c26ab058db8e427046dbc35c3140a5 \ - --hash=sha256:65d1d00fbfb3ea5f20add88bbc0f815150dbbde3b026e6c24759466c8b5a9ef9 \ - --hash=sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd \ - --hash=sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277 \ - --hash=sha256:70d86fa5197b8947a2fa70260b48e400bf2ccacdcab97bb7de47e3d1e6312225 \ - --hash=sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22 \ - --hash=sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116 \ - --hash=sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16 \ - --hash=sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc \ - --hash=sha256:758895b01d546812d1f42204bd443b8c433c44d090248bf22689df673ccafe00 \ - --hash=sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2 \ - --hash=sha256:7e18f01b0c3e4a07fe6dfdb00e29049ba17eadbc5e7609a2a3a4af83ab7d710a \ - --hash=sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804 \ - --hash=sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04 \ - --hash=sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1 \ - --hash=sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba \ - --hash=sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390 \ - --hash=sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0 \ - --hash=sha256:a7c06742645f914f26c7f1fa47b8bc4c91d222f76ee20116c43d5ef0912bba2d \ - --hash=sha256:a9a2203361a6e6404f80b99234fe7fb37d1fc73487b5a78dc1aa5b97201e0f22 \ - --hash=sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0 \ - --hash=sha256:ad85e269fe54d506b240d2d7b9f5f2057c2aa9a2ea5b32c66f8902f768117ed2 \ - --hash=sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18 \ - --hash=sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6 \ - --hash=sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311 \ - --hash=sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89 \ - --hash=sha256:caea3e9c79d5f0d2c6d9ab96111601797ea5da8e6d0723f77eabb0d4068d2b2f \ - --hash=sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39 \ - --hash=sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4 \ - --hash=sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5 \ - --hash=sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa \ - --hash=sha256:df7d30371a2accfe4013e90445f6388c570f103d61019b6b7c57e0265250072a \ - --hash=sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050 \ - --hash=sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6 \ - --hash=sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235 \ - --hash=sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056 \ - --hash=sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2 \ - --hash=sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418 \ - --hash=sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c \ - --hash=sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a \ - --hash=sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6 \ - --hash=sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0 \ - --hash=sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775 \ - --hash=sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10 \ - --hash=sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c +wrapt==2.2.1 \ + --hash=sha256:036dfb40128819a751c6f451c6b9c10172c49e4c401aebcdb8ecf2aec1683598 \ + --hash=sha256:03df9ebed4c73ab93fa8c07e3d41d818dfca1852b15731a3de59457b27814624 \ + --hash=sha256:05d5cb74d1b232ec8cfa130a8f900708699ff2491d97b8f85a4cdc5996294b85 \ + --hash=sha256:07be671fa8875971222b0ba9059ed8b4dc738631122feba17c93aa36b4213e9a \ + --hash=sha256:09ac16c081bebfd15d8e4dfa5bdc805990bbd52249ecff22530da7a129d6120b \ + --hash=sha256:0d9ff006f420b2ec8296aa56ade43ea7da3e997e85769f0aafc5e0661aacb710 \ + --hash=sha256:0f68f478004475d97906686e702ddbddeaf717c0b68ad2794384308f2dc713ae \ + --hash=sha256:17de18fc12cea55b8a9587314cb830573e37fb33b247a7515696350863714188 \ + --hash=sha256:1ae574d65c9fa8e86f64f6a7c2668f9fcd507b183e0e577619f504b883cb0a6c \ + --hash=sha256:1c9934ea5d92957e3cd0adbc0845539dccfd62710ebe16195a8c66c53954db36 \ + --hash=sha256:1d676ee388bc42a04d56dd7deb5605244dac2e35cc2fadbb43c9fa25bbd93508 \ + --hash=sha256:1ffa9cfd4bdb581539951b14ae661ff20ed0c3599b3e911a131ee0ec5ac11337 \ + --hash=sha256:2076d2335085eb09b9547e7688656fa8f5cf0183eab589d33499cd353489d797 \ + --hash=sha256:211f595f8e7faae5c5930fcc64708f2ba36849e0ba0fd653a843de9fa8d7db77 \ + --hash=sha256:24c52546acf2ab82412f2ab6fc5948a7fe958d3b4f070202e8dcdd865489eaf9 \ + --hash=sha256:2d83966dc7f4f45e8b97b5933685ac2e6e67fc0e19246ea314bceb9a8970c956 \ + --hash=sha256:2de9e20769fe9c1f6dcdc893c6a89287c5ccf8537c90b5de78aed8017697aad5 \ + --hash=sha256:2e08688ab16525897da6589d56d0aebaf417bbe91c2d8e3b96203b1efa596e85 \ + --hash=sha256:2f8c90c8afde51969487be4e1343ae049b268854877d415c2510baf833775052 \ + --hash=sha256:368eac1e20fd0bb03dd3cc42bf9887154c3861b60989389ccb5fac032617d215 \ + --hash=sha256:3aafea2975caef8ca49400640dde02cc7426e798f24870ed01f490bc3cffd32f \ + --hash=sha256:3e2f02472a1cbbf3884b365714a810b5947134a95ad6952b554cb8cce9d492b0 \ + --hash=sha256:3ffad790d9d11d8ecf9f17c4bb671a5b4089e4d8b575c46c5129597f41f836b0 \ + --hash=sha256:401229e9d63ca09f9b8891ecf83798d26c11bbb445d11ed9f1836b6d4585b38a \ + --hash=sha256:436addbc4bb4fc0a88c702577f51195d7d73683a7f3e0e5b253d8404d7847243 \ + --hash=sha256:44255c84bc57554fed822e83e70036b51afa9edb56fc7ca56c54410ece7898c9 \ + --hash=sha256:50972a1d974ea07725a7f6b1cec5f8759008afd030a0024843ebe7d52de47f2b \ + --hash=sha256:5590d63f5243251641cf543009b4c9314a79d0598fdb8a8e4cfc918494536c53 \ + --hash=sha256:585916e210db57b23543342c2f298e42331b617fd0c934caf5c64df44de8640e \ + --hash=sha256:5f1845c2a8cc1180ccccfa45785dd06f562730d19ef75be180334254012b6283 \ + --hash=sha256:5fa9bf3b9e66336589d03f42abce2da1055ad5c69b0c2b764852a8471c9b9114 \ + --hash=sha256:61a0013344674d2b648bc6e6fe9828dd4fc1d3b4eb7523809792f8cb952e2f16 \ + --hash=sha256:61acce4257a9883669703c525447c5b4c392edf0f987ae77ec32668440158f0e \ + --hash=sha256:628f5220c7a904d5fc78f7075c8d7871433eb6d035c94728a22fdf85f193d2a8 \ + --hash=sha256:64b7deeda4b70408e382328d8bbe52a256fe9bc63ae3db86d804608367e5422c \ + --hash=sha256:6744f504375775d7609c82c8d3d94af1c9a6f05586984536905908ba905277b9 \ + --hash=sha256:67a97e5b6c457f0cd3cfc19ebb2d84463e60c3ece754cc831e4281a3ca29bb18 \ + --hash=sha256:69f2e9244542cb34dd59c7f073445b9e54ad9f3fce8d93606c368a1b499fc413 \ + --hash=sha256:6ce32763ac31ce94fe9aada947e479b1975012bff166da409b4b9e4e376cf7e5 \ + --hash=sha256:6f56a647e4eaf5f0ca40330fb070f566bdf9f7b0db89a1af20d71c28dcd7a0ab \ + --hash=sha256:727ab4244622cd6ad2390f322642090c877d2e83a608d2653a7643ae5368d926 \ + --hash=sha256:74d6a0c31472fe5d814917266b9f46495d7c61ed890af08b468acea92fb89a8d \ + --hash=sha256:78b0aa6bfb7be8deed0ab23e7aa028cc5210c29bc2d32a04d52b50e517a7307e \ + --hash=sha256:7975bc88ab4b0f72ef2a2d5ae9d77d87efb5ef95e8f8046242fa9afdaaf2030b \ + --hash=sha256:7a4fdb9326aab4a5a477a1640e5ad786a8495901009d7e7b038371edd23a9d2b \ + --hash=sha256:844c858fc3bb7eacc0ba8efa904935d16aac6a4470948ad1e7e55c9f5a2a665f \ + --hash=sha256:87bacdaf225117a342a20d9c03438d701c02112f6e3f351ce9b7f32354f14797 \ + --hash=sha256:8a983a603a18c8708f024f7f6991b2e66159219abbf894634c5056243c55f3cd \ + --hash=sha256:8d1b4d0e0c2119587a31f5c029abd547e0c81d93b89d394566fe1588659eb579 \ + --hash=sha256:9011395be8db1827d106c6449b4bb6dd17e331ff6ec521f227e4588f1c78e46f \ + --hash=sha256:93fc2bf40cd7f4a0256010dce073d44eeb4a351b9bca94d0477ce2b6e62532b3 \ + --hash=sha256:95821352042722cd9f1108874579a47989d0a7e12a37d87d2fc4af20fd99ab8a \ + --hash=sha256:9907a4402ab6db12b7077a0ea5d7a4d028ecb22c8eee2b53527080d347cd1562 \ + --hash=sha256:9a04c28c10ba7fd12842b109d2edb0678872a2fe65277ca4ff06a0d61edee245 \ + --hash=sha256:9a5934eaea872e17936b5f45501eba5ab0bce9a74122e172b663d7c28c459c4a \ + --hash=sha256:9b984d1eb252145d6302c1dbd5e87fc6d404d45531447c84eadec04bf1fcb027 \ + --hash=sha256:9c210a6994b21aa9b29e81c8d11560e8fdab54c117e9cff37870d0a27bde1343 \ + --hash=sha256:9d8f204c8e3a8bf9ece17e0a83d137fd807440977f8a5e762d59306795011440 \ + --hash=sha256:a8f7176b83664af44567e9cc06e0d3827823fcc1a5e52307ebb8ac3aa95860b9 \ + --hash=sha256:a9dec1aca52dddde7df94818310fa2fe79739c8f385b2014c4cb1035f5508199 \ + --hash=sha256:ab5be648d5a0b86b7438864f8df3c705a65cef35a2fd3e5561e3e203167e0f27 \ + --hash=sha256:abd621552ede77c4c69be7fac44ba911225b0c812b6ba604e5964cf98085b474 \ + --hash=sha256:ac2745950b2bff80219c15ebf2fa9d8427eba7e249739f97e55c9d169e47e9e1 \ + --hash=sha256:aed9658797d0b45d6c49adcfc6b41f66e6f2d0c6de3ec79e16cf4b1855df240f \ + --hash=sha256:b6c0febfe38f22df2eb565c0ce8a092bb80411e56861ca382c443da83105423f \ + --hash=sha256:b9cf53ba90717db2e292401de290776c498d4bbfb0d4a559ca2895db8b9dcb5c \ + --hash=sha256:ba519b2d765df9871a25879e6f7fa78948ea59a2a31f9c1a257e34b651994afc \ + --hash=sha256:c318a64b53d97b841d7b5e637517e50a27be64bc695128422953d4b21710954e \ + --hash=sha256:c3723ff8eb8721f4daac98bc0256f15158e05316d5e52648ce9cebee434fbdd5 \ + --hash=sha256:c754dafdf5aaf0b401b644a90a30046929a0dd1a536e0ff0ec959a59155d9c7f \ + --hash=sha256:c803a3d331796255af51ba2c79ed0ac8275865b516c09e61f248d1e7aff31ce9 \ + --hash=sha256:c8cc5094b08abeae52da9c73c8a32003623be691a5193df2f4e3eac3d557c394 \ + --hash=sha256:cf3638274ab9d9b724c9baa0b4c04e132cd6faefb78b4dd3dd1a02a4bdaad41e \ + --hash=sha256:d047f6498c973874ba08ac3f97c69a2c4b2211c8de6f4c205f75cb1c9522596e \ + --hash=sha256:d2beb1c7cab10603aecdc42f8edd6ff013f9a32e4543474e38e6b77ce9975aeb \ + --hash=sha256:d7f513d3185e6fec82d0c3518f2e6365d8b4e49f5f45f29640d5162d56a23b54 \ + --hash=sha256:dd57607acc85678925940bd5df0385ff8332083a32fa8d7a43f8767f4997263c \ + --hash=sha256:e0cb7e4dd71f4c32e5e84843cd3c4cd65dda034314004bbe1d7f99af2426ab80 \ + --hash=sha256:e3677c7146ce694874941ba82b57092cc4875445aadf29d72807351023105143 \ + --hash=sha256:e395f7bc31851ef9b612050368cb446e9bc14cd7454b025018980349caf25ae5 \ + --hash=sha256:e422b2d647a65d6b080cad5accd09055d3809bdff00c76fba8dca00ca935572a \ + --hash=sha256:ed55af48b3eb28f43228ca2306788892bcb629eb2b5c4876e2a3659872c2f17a \ + --hash=sha256:ed928d0fda15fc0adc8d13305c8b3c0f2fba5b0669950c9e6d019d9162a3b3e8 \ + --hash=sha256:f4e1a92032a39cd5e3c647ca57dbf33b6a1938fd975623175793f9dbb63236de \ + --hash=sha256:f53ac9f3ef573326d009ed809beff4efcac6451931c2b8132586da4b9e53ff31 \ + --hash=sha256:f5b9daf6b629fce418e0cc3dd0436eac045188fa35deadb7a7f3941d5b8203f9 \ + --hash=sha256:f6518b94edb9150452e9aba08027d4cc293433753ec1fbefb4629a21cbc74181 \ + --hash=sha256:f70db64e8266d7c45d3b735f2e08eeb434b5e03da9a479ae42b2e2e486a21a00 \ + --hash=sha256:fafb4e739e43544d12cb4abd1605fd4683b6ca6a9ad682b7fd8f4d21973eafa8 \ + --hash=sha256:fd0135d34387f5fd087d9be368ea77ea89cf2451dc1cd1c622d35021bcb3ab50 # via # opentelemetry-instrumentation # opentelemetry-instrumentation-dbapi @@ -2135,17 +2144,18 @@ xmlsec==1.3.17 \ # via # -r src/backend/requirements.in # python3-saml -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 +zopfli==0.4.3 \ + --hash=sha256:0087c9a6f0c8a052be0f6d1a9bb71b6caffdd3e10201d6d6166e28d482cebe6d \ + --hash=sha256:47604eee5c6704bdf0e94d8391fe3b74ddb2abd84128fbcfdc3ee0fc265feaef \ + --hash=sha256:62248dbf8dbcbd588ee194b210e5be9fa80bce29641f55599d6d394bd2a9d8a3 \ + --hash=sha256:628c3e941752880b3491db8d44163d0aedb221944e22a17187ff7fc549b050f6 \ + --hash=sha256:769875152d0625c46707bcca57d4b2233fe653482067acd55fbf6ec525cb9bdc \ + --hash=sha256:7e9703ca6e7ef66c8d05e0826b6f558b680c9db8206f84f05a3ee93430a12e42 \ + --hash=sha256:7fa3c35193475290e3f007bbcdebdbae64ba2f012d75c632da0d727e1da50d5e \ + --hash=sha256:88f4fbe429aad72bc206275d81fab11a097e0f951a5848d1f51083c37ea73073 \ + --hash=sha256:921c2c9907f4364963848da5ad194b46d68865e07fdb975d04fd09bc42d47357 \ + --hash=sha256:d3a50f91a13cea9bafe025de8fd87a005eb26de02a4f0c193127ddbf23ac8ebe \ + --hash=sha256:d4f51dd1ab5312e837e2091284e0d9f1a138188f2e65812f9a5799dc02c45f94 \ + --hash=sha256:eb0c9c1d40a8cb1d58762d7e57290ccb753e0828c4d01be8acb59aae5d0ca206 \ + --hash=sha256:f2e0adcf7d36c6fd0dd36cc771ef7f0c5803a05666feafcd90d7170174a4148e # via fonttools diff --git a/src/frontend/src/components/dashboard/widgets/NewsWidget.tsx b/src/frontend/src/components/dashboard/widgets/NewsWidget.tsx index 5dab794dce..687fc5afc2 100644 --- a/src/frontend/src/components/dashboard/widgets/NewsWidget.tsx +++ b/src/frontend/src/components/dashboard/widgets/NewsWidget.tsx @@ -130,9 +130,13 @@ export default function NewsWidget() { )) ) : ( - - {t`There are no unread news items`} - + + + + {t`There are no unread news items`} + + + )} diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index 6f541f049c..2c940df733 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -131,7 +131,9 @@ export async function doBasicLogin( default: notifications.show({ title: `${t`Login failed`} (${err.response.status})`, - message: t`Check your input and try again.`, + message: + err.response?.data?.detail ?? + t`Check your input and try again.`, id: 'auth-login-error', color: 'red' }); diff --git a/tasks.py b/tasks.py index 4f94ff57f6..b59a43d738 100644 --- a/tasks.py +++ b/tasks.py @@ -906,6 +906,23 @@ def backend_trans(c, verbose: bool = False): success('Backend translations compiled successfully') +@task(help={'verbose': 'Print verbose output'}) +@state_logger('backend_compilemessages') +def backend_compilemessages(c, verbose: bool = False): + """Compile backend Django translation files without loading InvenTree settings.""" + info('Compiling backend translations...') + + cmd = 'python3 -m django compilemessages' + + if verbose: + cmd += ' -v 1' + else: + cmd += ' -v 0' + + run(c, cmd, manage_py_dir()) + success('Backend translations compiled successfully') + + @task( help={ 'clean': 'Clean up old backup files', @@ -2490,6 +2507,7 @@ internal = Collection( clear_generated, export_settings_definitions, export_definitions, + backend_compilemessages, frontend_build, frontend_check, frontend_compile,