47 Commits

Author SHA1 Message Date
1fdd316d0d feat(models.py): add users to gitea teams on purchase/refund 2026-05-05 12:46:07 +02:00
da5662ceee feat(base.py): add GITEA_ROOT_URL and configure logger 2026-05-05 12:45:35 +02:00
9acb4c8d7c feat(module_lesson_page.html): auto login to gitea when viewing repo 2026-05-05 12:45:08 +02:00
36c14ab939 feat(pages.py): add gitea login redirect url property for handling links to repo 2026-05-05 12:44:37 +02:00
a9d7aef6dd feat(signals.py): add gitea repo to team 2026-05-05 12:44:01 +02:00
fa942809d9 chore(signals.py): remove unused commented code 2026-05-05 12:43:28 +02:00
29f8475b88 feat(module_lesson_page.html): add link to Gitea code 2026-04-23 19:56:09 +02:00
1875c6fd97 feat(blog_page.html): add BlogPage template 2026-04-23 19:54:51 +02:00
92aa1fc024 feat(blog_index_page.html): add BlogIndexPage template 2026-04-23 19:54:20 +02:00
3551d2b654 feat(header.html): add blog link to header 2026-04-23 19:54:02 +02:00
4c21e324f6 feat(pages.py): add posts ctx variable to BlogIndexPage 2026-04-23 19:53:41 +02:00
80a80553fe chore(home/views.py): remove debug print 2026-04-23 19:13:30 +02:00
15e74660f5 feat(admin_chat.html): add back button to admin chat 2026-04-23 19:13:10 +02:00
2b34cadf24 refactor(admin_chat.html): style chat 2026-04-23 19:09:46 +02:00
6dd3355087 refactor(user_chat.html): style chat 2026-04-23 18:58:31 +02:00
bfdc953e0f feat(header.html): add chat link to header 2026-04-23 18:57:39 +02:00
27567caf78 fix(forms.py): fix error when gitea account already exists 2026-04-23 18:57:18 +02:00
97df6349ab feat(pages.py): delete event's future occurrences if unpublished 2026-04-23 18:17:24 +02:00
6d355b603d fix(pages.py): fix event's other occurrences not being deleted when changing event from recurrent to single
Closes #1
2026-04-23 18:10:05 +02:00
f9f812caf0 chore(.gitignore): ignore gitea data 2026-04-22 20:39:59 +02:00
299a600b09 fix(docker-compose.yml): run gitea on host network 2026-04-22 20:39:47 +02:00
0cb2794eff chore(gitea): add gitea config 2026-04-22 20:39:26 +02:00
3d51c6f043 build(uv.lock): update depedencies 2026-04-22 20:38:27 +02:00
2a15556513 refactor(base.py): use relative DB path 2026-04-22 20:38:14 +02:00
ffa0b03661 fix(signals.py): fix repo creation even if create_gitea_repo False 2026-04-22 20:37:42 +02:00
3ec8963030 chore(0023): add EventIndexPage 2026-04-22 20:36:39 +02:00
d70bf79107 feat(pages.py): add EventIndexPage 2026-04-22 20:36:22 +02:00
daf0b05bb8 feat: add gitea docker 2026-04-02 14:59:29 +02:00
35d6bb5f2e docs(README.md): add README 2026-04-02 10:40:09 +02:00
dd936473d8 feat(signals.py): create per-lesson repositories on ModuleLessonPage save 2026-03-30 10:58:01 +02:00
787440d56f chore(migrations/0022): create BlogIndexPage and BlogPage 2026-03-30 10:13:12 +02:00
37b0a6a95b feat(pages.py): add blog pages 2026-03-30 10:13:12 +02:00
345914a519 chore(migrations/0021): add create_gitea_repo and gitea_repo_url to ModuleLessonPage 2026-03-30 10:13:12 +02:00
7e24ded8ee fix(pages.py): generate event occurrences only if live 2026-03-30 10:13:12 +02:00
64edf6656e feat(pages.py): add per-lesson repo fields 2026-03-30 10:13:03 +02:00
a2ad8e7ac9 feat(module_lesson_page.html): add link to course library 2026-03-23 14:03:38 +01:00
a0b4697c61 feat(course_module_page.html): add link to course library 2026-03-23 14:03:29 +01:00
983384f62b feat(course_page.html): add link to course library in CoursePage 2026-03-23 14:03:10 +01:00
668ddccea5 feat(settings/base.py): add LOGGING config 2026-03-23 14:02:24 +01:00
6dd826c3bd feat(home/signals.py): create gitea team and repo for course on CoursePage save 2026-03-23 14:02:07 +01:00
e74c1fb28d chore(migrations/0020): add repository_url field to CoursePage 2026-03-23 13:46:24 +01:00
cb19bc6262 feat(models/pages.py): add repository_url field to CoursePage 2026-03-23 13:45:19 +01:00
a918ee73c4 fix(models/pages.py): ensure course has ID before creating group 2026-03-23 13:44:33 +01:00
5913e847bc refactor(forms.py): move gitea account creation login to separate function 2026-03-20 14:47:12 +01:00
18b21b0892 feat(forms.py): create gitea account on signup 2026-03-20 14:37:40 +01:00
efb3799e12 feat(forms.py): capitalize first and last name 2026-03-20 14:37:22 +01:00
306d39bd22 feat(oauth_validators.py): use user ID for gitea username 2026-03-20 14:36:27 +01:00
27 changed files with 2049 additions and 266 deletions

View File

@@ -1,7 +1,7 @@
# Django project
/media/
/static/
*.sqlite3
# *.sqlite3
# Python and others
__pycache__
@@ -37,3 +37,6 @@ wheels/
*.egg-info/
.installed.cfg
*.egg
.venv/
gitea/

3
.gitignore vendored
View File

@@ -55,3 +55,6 @@ docs/ref/modules/
# Media (managed by wagtail)
media/
# Gitea data
gitea/data/

View File

@@ -1,60 +1,57 @@
# Use an official Python runtime based on Debian 12 "bookworm" as a parent image.
FROM python:3.12-slim-bookworm
FROM python:3.14-slim-bookworm AS builder
# Add user that will be used in the container.
RUN useradd wagtail
RUN useradd -m kursy
# Port used by this container to serve HTTP.
EXPOSE 8000
ENV NODE_VERSION=24.14.1
# Set environment variables.
# 1. Force Python stdout and stderr streams to be unbuffered.
# 2. Set PORT variable that is used by Gunicorn. This should match "EXPOSE"
# command.
ENV PYTHONUNBUFFERED=1 \
PORT=8000
# install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
# Install system packages required by Wagtail and Django.
RUN apt-get update --yes --quiet && apt-get install --yes --quiet --no-install-recommends \
build-essential \
libpq-dev \
libmariadb-dev \
libjpeg62-turbo-dev \
zlib1g-dev \
libwebp-dev \
&& rm -rf /var/lib/apt/lists/*
# install nodejs
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get install -y nodejs
# Install the application server.
RUN pip install "gunicorn==20.0.4"
# copy project files
WORKDIR /app
COPY pyproject.toml uv.lock ./
# Install the project requirements.
COPY requirements.txt /
RUN pip install -r /requirements.txt
RUN chown kursy:kursy /app -R
# Use /app folder as a directory where the source code is stored.
USER kursy
# install project dependencies
RUN uv sync
COPY --chown=kursy:kursy . .
# install tailwind
WORKDIR /app/theme/static_src
RUN npm install
# collect static files
WORKDIR /app
RUN uv run python manage.py collectstatic --noinput --clear
# --- RUNTIME IMAGE ---
FROM python:3.14-slim-bookworm
RUN useradd -m kursy
WORKDIR /app
# Set this directory to be owned by the "wagtail" user. This Wagtail project
# uses SQLite, the folder needs to be owned by the user that
# will be writing to the database file.
RUN chown wagtail:wagtail /app
RUN mkdir -p /app/data && chown kursy:kursy /app/data
# Copy the source code of the project into the container.
COPY --chown=wagtail:wagtail . .
USER kursy
# Use user "wagtail" to run the build commands below and the server itself.
USER wagtail
COPY --from=builder --chown=kursy:kursy /app/.venv /app/.venv
COPY --from=builder --chown=kursy:kursy /app /app
# Collect static files.
RUN python manage.py collectstatic --noinput --clear
ENV PATH="/app/.venv/bin:$PATH" \
PYTHONUNBUFFERED=1 \
PORT=8000
EXPOSE 8000
CMD set -xe; python manage.py migrate --noinput; python manage.py runserver 0.0.0.0:8000
# Runtime command that executes when "docker run" is called, it does the
# following:
# 1. Migrate the database.
# 2. Start the application server.
# WARNING:
# Migrating database at the same time as starting the server IS NOT THE BEST
# PRACTICE. The database should be migrated manually or using the release
# phase facilities of your hosting platform. This is used only so the
# Wagtail instance can be started with a simple "docker run" command.
CMD set -xe; python manage.py migrate --noinput; gunicorn kursy.wsgi:application

89
README.md Normal file
View File

@@ -0,0 +1,89 @@
# Studio77 (kursy)
## Instalacja
### Wymagania
- `uv` >= 0.11.0
- `python` >= 3.14 (instalowany automatycznie przez `uv`)
- `node` >= 24.14.1 (LTS)
- `npm` >= 9.2.0 (LTS)
### Instalacja wymaganych narzędzi
1. `uv` - można zainstalować za pomocą skryptu instalacyjnego dostępnego na [stronie projektu](https://docs.astral.sh/uv/getting-started/installation):
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
2. `node` i `npm` - można zainstalować za pomocą [Node Version Manager (nvm)](https://nodejs.org/en/download):
```bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash
\. "$HOME/.nvm/nvm.sh"
nvm install 24
```
### Instalacja projektu
1. Sklonuj repozytorium:
```bash
git clone http://192.168.190:3000/StudioCodeLab/kursy.git
cd kursy
```
2. Zainstaluj zależności:
```bash
uv sync
```
3. Zainstaluj zależności tailwind:
```bash
cd theme/static_src
npm install
cd ../..
```
## Pierwsze uruchomienie
1. Wykonaj migracje bazy danych:
```bash
uv run python manage.py migrate
```
2. Utwórz superużytkownika (admina):
```bash
uv run python manage.py createsuperuser
```
## Użycie
### Uruchom serwery
- Django:
```bash
uv run python manage.py runserver
```
- Tailwind:
```bash
cd theme/static_src
npm run dev
```
> [!IMPORTANT]
> Oba serwery muszą być uruchomione równolegle, aby aplikacja działała poprawnie.
## Autorzy
- [Artur Borecki](https://github.com/pufereq)

38
docker-compose.yml Normal file
View File

@@ -0,0 +1,38 @@
networks:
gitea:
external: false
services:
gitea:
image: docker.gitea.com/gitea:latest
container_name: gitea
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=sqlite3
- GITEA__server__ROOT_URL=http://localhost:3000/
restart: always
network_mode: host
# networks:
# - gitea
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- ./gitea/data:/data
# - ./gitea/config:/data/gitea/conf
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "3022:3022"
# kursy:
# build: .
# container_name: kursy
# restart: unless-stopped
# networks:
# - gitea
# volumes:
# - ./db:/app/db
# ports:
# - "8000:8000"

820
gitea/config/app.ini Normal file
View File

@@ -0,0 +1,820 @@
; ; Copy required sections to your own app.ini (default is custom/conf/app.ini)
; ; and modify as needed.
; ; Do not copy the whole file as-is, as it contains some invalid sections for illustrative purposes.
; ; If you don't know what a setting is you should not set it.
; ;
; ; see https://docs.gitea.com/administration/config-cheat-sheet for additional documentation.
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ; Default Configuration (non-`app.ini` configuration)
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; ; These values are environment-dependent but form the basis of a lot of values. They will be
; ; reported as part of the default configuration when running `gitea help` or on start-up. The order they are emitted there is slightly different but we will list them here in the order they are set-up.
; ;
; ; - _`AppPath`_: This is the absolute path of the running gitea binary.
; ; - _`AppWorkPath`_: This refers to "working path" of the `gitea` binary. It is determined by using the first set thing in the following hierarchy:
; ; - The "WORK_PATH" option in "app.ini" file
; ; - The `--work-path` flag passed to the binary
; ; - The environment variable `$GITEA_WORK_DIR`
; ; - A built-in value set at build time (see building from source)
; ; - Otherwise it defaults to the directory of the _`AppPath`_
; ; - If any of the above are relative paths then they are made absolute against the directory of the _`AppPath`_
; ; - _`CustomPath`_: This is the base directory for custom templates and other options. It is determined by using the first set thing in the following hierarchy:
; ; - The `--custom-path` flag passed to the binary
; ; - The environment variable `$GITEA_CUSTOM`
; ; - A built-in value set at build time (see building from source)
; ; - Otherwise it defaults to _`AppWorkPath`_`/custom`
; ; - If any of the above are relative paths then they are made absolute against the directory of the _`AppWorkPath`_
; ; - _`CustomConf`_: This is the path to the `app.ini` file.
; ; - The `--config` flag passed to the binary
; ; - A built-in value set at build time (see building from source)
; ; - Otherwise it defaults to _`CustomPath`_`/conf/app.ini`
; ; - If any of the above are relative paths then they are made absolute against the directory of the _`CustomPath`_
; ;
; ; In addition there is _`StaticRootPath`_ which can be set as a built-in at build time, but will otherwise default to _`AppWorkPath`_
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ; General Settings
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; ; App name that shows in every page title
; Gitea: Git with a cup of tea
APP_NAME = Studio77 Gitea (test instance)
; ;
; ; RUN_USER will automatically detect the current user - but you can set it here change it if you run locally
; git
RUN_USER = gitea
WORK_PATH = /var/lib/gitea
RUN_MODE = prod
; ;
; ; Application run mode, affects performance and debugging: "dev" or "prod", default is "prod"
; ; Mode "dev" makes Gitea easier to develop and debug, values other than "dev" are treated as "prod" which is for production use.
; RUN_MODE = prod
; ;
; ; The working directory, see the comment of AppWorkPath above
; WORK_PATH =
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[server]
SSH_DOMAIN = 127.0.0.1
DOMAIN = 127.0.0.1
HTTP_PORT = 3000
ROOT_URL = http://localhost:3000/
APP_DATA_PATH = /var/lib/gitea/data
DISABLE_SSH = false
SSH_PORT = 22
LFS_START_SERVER = true
LFS_JWT_SECRET = ZlMvLXiVZtuaVymLGuO4Qg5dFHCQ45fIzui99Qw9ecM
OFFLINE_MODE = true
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; ; The protocol the server listens on. One of "http", "https", "http+unix", "fcgi" or "fcgi+unix".
; PROTOCOL = http
; ;
; ; Set the domain for the server.
; DOMAIN = localhost
; ;
; ; The AppURL is used to generate public URL links, defaults to "{PROTOCOL}://{DOMAIN}:{HTTP_PORT}/".
; ; Most users should set it to the real website URL of their Gitea instance when there is a reverse proxy.
; ROOT_URL =
; ;
; ; Controls how to detect the public URL.
; ; Although it defaults to "legacy" (to avoid breaking existing users), most instances should use the "auto" behavior,
; ; especially when the Gitea instance needs to be accessed in a container network.
; ; * legacy: detect the public URL from "Host" header if "X-Forwarded-Proto" header exists, otherwise use "ROOT_URL".
; ; * auto: always use "Host" header, and also use "X-Forwarded-Proto" header if it exists. If no "Host" header, use "ROOT_URL".
; PUBLIC_URL_DETECTION = legacy
; ;
; ; For development purpose only. It makes Gitea handle sub-path ("/sub-path/owner/repo/...") directly when debugging without a reverse proxy.
; ; DO NOT USE IT IN PRODUCTION!!!
; USE_SUB_URL_PATH = false
; ;
; ; when STATIC_URL_PREFIX is empty it will follow ROOT_URL
; STATIC_URL_PREFIX =
; ;
; ; The address to listen on. Either a IPv4/IPv6 address or the path to a unix socket.
; ; If PROTOCOL is set to "http+unix" or "fcgi+unix", this should be the name of the Unix socket file to use.
; ; Relative paths will be made absolute against the _`AppWorkPath`_.
; HTTP_ADDR = 0.0.0.0
; ;
; ; The port to listen on for "http" or "https" protocol. Leave empty when using a unix socket.
; HTTP_PORT = 3000
; ;
; ; Expect PROXY protocol headers on connections
; USE_PROXY_PROTOCOL = false
; ;
; ; Use PROXY protocol in TLS Bridging mode
; PROXY_PROTOCOL_TLS_BRIDGING = false
; ;
; ; Timeout to wait for PROXY protocol header (set to 0 to have no timeout)
; PROXY_PROTOCOL_HEADER_TIMEOUT = 5s
; ;
; ; Accept PROXY protocol headers with UNKNOWN type
; PROXY_PROTOCOL_ACCEPT_UNKNOWN = false
; ;
; ; If REDIRECT_OTHER_PORT is true, and PROTOCOL is set to https an http server
; ; will be started on PORT_TO_REDIRECT and it will redirect plain, non-secure http requests to the main
; ; ROOT_URL. Defaults are false for REDIRECT_OTHER_PORT and 80 for
; ; PORT_TO_REDIRECT.
; REDIRECT_OTHER_PORT = false
; PORT_TO_REDIRECT = 80
; ;
; ; expect PROXY protocol header on connections to https redirector, defaults to USE_PROXY_PROTOCOL
; REDIRECTOR_USE_PROXY_PROTOCOL =
; ; Minimum and maximum supported TLS versions
; SSL_MIN_VERSION=TLSv1.2
; SSL_MAX_VERSION=
; ;
; ; SSL Curve Preferences
; SSL_CURVE_PREFERENCES=X25519,P256
; ;
; ; SSL Cipher Suites
; SSL_CIPHER_SUITES=; Will default to "ecdhe_ecdsa_with_aes_256_gcm_sha384,ecdhe_rsa_with_aes_256_gcm_sha384,ecdhe_ecdsa_with_aes_128_gcm_sha256,ecdhe_rsa_with_aes_128_gcm_sha256,ecdhe_ecdsa_with_chacha20_poly1305,ecdhe_rsa_with_chacha20_poly1305" if aes is supported by hardware, otherwise chacha will be first.
; ;
; ; Timeout for any write to the connection. (Set to -1 to disable all timeouts.)
; PER_WRITE_TIMEOUT = 30s
; ;
; ; Timeout per Kb written to connections.
; PER_WRITE_PER_KB_TIMEOUT = 30s
; ;
; ; Permission for unix socket
; UNIX_SOCKET_PERMISSION = 666
; ;
; ; Local (DMZ) URL for Gitea workers (such as SSH update) accessing web service. In
; ; most cases you do not need to change the default value. Alter it only if
; ; your SSH server node is not the same as HTTP node. For different protocol, the default
; ; values are different. If `PROTOCOL` is `http+unix`, the default value is `http://unix/`.
; ; If `PROTOCOL` is `fcgi` or `fcgi+unix`, the default value is `{PROTOCOL}://{HTTP_ADDR}:{HTTP_PORT}/`.
; ; If listen on `0.0.0.0`, the default value is `{PROTOCOL}://localhost:{HTTP_PORT}/`.
; ; Otherwise the default value is `{PROTOCOL}://{HTTP_ADDR}:{HTTP_PORT}/`.
; ; Most users don't need (and shouldn't) set this value.
; LOCAL_ROOT_URL =
; ;
; ; When making local connections pass the PROXY protocol header, defaults to USE_PROXY_PROTOCOL
; LOCAL_USE_PROXY_PROTOCOL =
; ;
; ; Disable SSH feature when not available
; DISABLE_SSH = false
; ;
; ; Whether to use the builtin SSH server or not.
; START_SSH_SERVER = false
; ;
; ; Expect PROXY protocol header on connections to the built-in SSH server
; SSH_SERVER_USE_PROXY_PROTOCOL = false
; ;
; ; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER.
; BUILTIN_SSH_SERVER_USER =
; ;
; ; Domain name to be exposed in clone URL, defaults to DOMAIN or the domain part of ROOT_URL
; SSH_DOMAIN =
; ;
; ; SSH username displayed in clone URLs. It defaults to BUILTIN_SSH_SERVER_USER or RUN_USER.
; ; If it is set to "(DOER_USERNAME)", it will use current signed-in user's username.
; ; This option is only for some advanced users who have configured their SSH reverse-proxy
; ; and need to use different usernames for git SSH clone.
; ; Most users should just leave it blank.
; SSH_USER =
; ;
; ; The network interface the builtin SSH server should listen on
; SSH_LISTEN_HOST =
; ;
; ; Port number to be exposed in clone URL
; SSH_PORT = 22
; ;
; ; The port number the builtin SSH server should listen on, defaults to SSH_PORT
; SSH_LISTEN_PORT =
; ;
; ; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'.
; SSH_ROOT_PATH =
; ;
; ; Gitea will create a authorized_keys file by default when it is not using the internal ssh server
; ; If you intend to use the AuthorizedKeysCommand functionality then you should turn this off.
; SSH_CREATE_AUTHORIZED_KEYS_FILE = true
; ;
; ; Gitea will create a authorized_principals file by default when it is not using the internal ssh server
; ; If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off.
; SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true
; ;
; ; For the builtin SSH server, choose the supported ciphers/key-exchange-algorithms/MACs for SSH connections.
; ; The supported names are listed in https://github.com/golang/crypto/blob/master/ssh/common.go.
; ; Leave them empty to use the Golang crypto's recommended default values.
; ; For system SSH (non-builtin SSH server), this setting has no effect.
; SSH_SERVER_CIPHERS =
; SSH_SERVER_KEY_EXCHANGES =
; SSH_SERVER_MACS =
; ;
; ; For the built-in SSH server, choose the keypair to offer as the host key
; ; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub
; ; relative paths are made absolute relative to the APP_DATA_PATH
; SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gogs.rsa
; ;
; ; Enable SSH Authorized Key Backup when rewriting all keys, default is false
; SSH_AUTHORIZED_KEYS_BACKUP = false
; ;
; ; Determines which principals to allow
; ; - empty: if SSH_TRUSTED_USER_CA_KEYS is empty this will default to off, otherwise will default to email, username.
; ; - off: Do not allow authorized principals
; ; - email: the principal must match the user's email
; ; - username: the principal must match the user's username
; ; - anything: there will be no checking on the content of the principal
; SSH_AUTHORIZED_PRINCIPALS_ALLOW = email, username
; ;
; ; Enable SSH Authorized Principals Backup when rewriting all keys, default is true
; SSH_AUTHORIZED_PRINCIPALS_BACKUP = true
; ;
; ; Specifies the public keys of certificate authorities that are trusted to sign user certificates for authentication.
; ; Multiple keys should be comma separated.
; ; E.g."ssh-<algorithm> <key>". or "ssh-<algorithm> <key1>, ssh-<algorithm> <key2>".
; ; For more information see "TrustedUserCAKeys" in the sshd config manpages.
; SSH_TRUSTED_USER_CA_KEYS =
; ; Absolute path of the `TrustedUserCaKeys` file gitea will manage.
; ; Default this `RUN_USER`/.ssh/gitea-trusted-user-ca-keys.pem
; ; If you're running your own ssh server and you want to use the gitea managed file you'll also need to modify your
; ; sshd_config to point to this file. The official docker image will automatically work without further configuration.
; SSH_TRUSTED_USER_CA_KEYS_FILENAME =
; ;
; ; Enable exposure of SSH clone URL to anonymous visitors, default is false
; SSH_EXPOSE_ANONYMOUS = false
; ;
; ; Timeout for any write to ssh connections. (Set to -1 to disable all timeouts.)
; ; Will default to the PER_WRITE_TIMEOUT.
; SSH_PER_WRITE_TIMEOUT = 30s
; ;
; ; Timeout per Kb written to ssh connections.
; ; Will default to the PER_WRITE_PER_KB_TIMEOUT.
; SSH_PER_WRITE_PER_KB_TIMEOUT = 30s
; ;
; ; Indicate whether to check minimum key size with corresponding type
; MINIMUM_KEY_SIZE_CHECK = false
; ;
; ; Disable CDN even in "prod" mode
; OFFLINE_MODE = true
; ;
; ; TLS Settings: Either ACME or manual
; ; (Other common TLS configuration are found before)
; ENABLE_ACME = false
; ;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; ; ACME automatic TLS settings
; ;
; ; ACME directory URL (e.g. LetsEncrypt's staging/testing URL: https://acme-staging-v02.api.letsencrypt.org/directory)
; ; Leave empty to default to LetsEncrypt's (production) URL
; ACME_URL =
; ;
; ; Explicitly accept the ACME's TOS. The specific TOS cannot be retrieved at the moment.
; ACME_ACCEPTTOS = false
; ;
; ; If the ACME CA is not in your system's CA trust chain, it can be manually added here
; ACME_CA_ROOT =
; ;
; ; Email used for the ACME registration service
; ; Can be left blank to initialize at first run and use the cached value
; ACME_EMAIL =
; ;
; ; ACME live directory (not to be confused with ACME directory URL: ACME_URL)
; ; (Refer to caddy's ACME manager https://github.com/caddyserver/certmagic)
; ACME_DIRECTORY = https
; ;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; ; Manual TLS settings: (Only applicable if ENABLE_ACME=false)
; ;
; ; Generate steps:
; ; $ ./gitea cert -ca=true -duration=8760h0m0s -host=myhost.example.com
; ;
; ; Or from a .pfx file exported from the Windows certificate store (do
; ; not forget to export the private key):
; ; $ openssl pkcs12 -in cert.pfx -out cert.pem -nokeys
; ; $ openssl pkcs12 -in cert.pfx -out key.pem -nocerts -nodes
; ; Paths are relative to CUSTOM_PATH
; CERT_FILE = https/cert.pem
; KEY_FILE = https/key.pem
; ;
; ; Root directory containing templates and static files.
; ; default is the path where Gitea is executed
; STATIC_ROOT_PATH = ; Will default to the built-in value _`StaticRootPath`_
; ;
; ; Default path for App data
; APP_DATA_PATH = data ; relative paths will be made absolute with _`AppWorkPath`_
; ;
; ; Base path for App's temp files, leave empty to use the managed tmp directory in APP_DATA_PATH
; APP_TEMP_PATH =
; ;
; ; Enable gzip compression for runtime-generated content, static resources excluded
; ENABLE_GZIP = false
; ;
; ; Application profiling (memory and cpu)
; ; For "web" command it listens on localhost:6060
; ; For "serve" command it dumps to disk at PPROF_DATA_PATH as (cpuprofile|memprofile)_<username>_<temporary id>
; ENABLE_PPROF = false
; ;
; ; PPROF_DATA_PATH, use an absolute path when you start gitea as service
; PPROF_DATA_PATH = data/tmp/pprof ; Path is relative to _`AppWorkPath`_
; ;
; ; Landing page, can be "home", "explore", "organizations", "login", or any URL such as "/org/repo" or even "https://anotherwebsite.com"
; ; The "login" choice is not a security measure but just a UI flow change, use REQUIRE_SIGNIN_VIEW to force users to log in.
; LANDING_PAGE = home
; ;
; ; Enables git-lfs support. true or false, default is false.
; LFS_START_SERVER = false
; ;
; ; Enables git-lfs SSH protocol support. true or false, default is false.
; LFS_ALLOW_PURE_SSH = false
; ;
; ; LFS authentication secret, change this yourself
; LFS_JWT_SECRET =
; ;
; ; Alternative location to specify LFS authentication secret. You cannot specify both this and LFS_JWT_SECRET, and must pick one
; LFS_JWT_SECRET_URI = file:/etc/gitea/lfs_jwt_secret
; ;
; ; LFS authentication validity period (in time.Duration), pushes taking longer than this may fail.
; LFS_HTTP_AUTH_EXPIRY = 24h
; ;
; ; Maximum allowed LFS file size in bytes (Set to 0 for no limit).
; LFS_MAX_FILE_SIZE = 0
; ;
; ; Maximum number of locks returned per page
; LFS_LOCKS_PAGING_NUM = 50
; ;
; ; When clients make lfs batch requests, reject them if there are more pointers than this number
; ; zero means 'unlimited'
; LFS_MAX_BATCH_SIZE = 0
; ;
; ; Allow graceful restarts using SIGHUP to fork
; ALLOW_GRACEFUL_RESTARTS = true
; ;
; ; After a restart the parent will finish ongoing requests before
; ; shutting down. Force shutdown if this process takes longer than this delay.
; ; set to a negative value to disable
; GRACEFUL_HAMMER_TIME = 60s
; ;
; ; Allows the setting of a startup timeout and waithint for Windows as SVC service
; ; 0 disables this.
; STARTUP_TIMEOUT = 0
; ;
; ; Static resources, includes resources on custom/, public/ and all uploaded avatars web browser cache time. Note that this cache is disabled when RUN_MODE is "dev". Default is 6h
; STATIC_CACHE_TIME = 6h
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[database]
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; ; Database to use. Either "mysql", "postgres", "mssql" or "sqlite3".
; ;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; ; MySQL Configuration
; ;
; DB_TYPE = mysql
; HOST = 127.0.0.1:3306 ; can use socket e.g. /var/run/mysqld/mysqld.sock
; NAME = gitea
; USER = root
; PASSWD = ;Use PASSWD = `your password` for quoting if you use special characters in the password.
; SSL_MODE = false ; either "false" (default), "true", or "skip-verify"
; CHARSET_COLLATION = ; Empty as default, Gitea will try to find a case-sensitive collation. Don't change it unless you clearly know what you need.
; ;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; ; Postgres Configuration
; ;
; DB_TYPE = postgres
; HOST = 127.0.0.1:5432 ; can use socket e.g. /var/run/postgresql/
; NAME = gitea
; USER = root
; PASSWD =
; SCHEMA =
; SSL_MODE=disable ;either "disable" (default), "require", or "verify-full"
; ;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; ; SQLite Configuration
; ;
DB_TYPE = sqlite3
; defaults to data/gitea.db
PATH = /var/lib/gitea/data/gitea.db
; Query timeout defaults to: 500
SQLITE_TIMEOUT =
; defaults to sqlite database default (often DELETE), can be used to enable WAL mode. https://www.sqlite.org/pragma.html#pragma_journal_mode
SQLITE_JOURNAL_MODE =
HOST = 127.0.0.1:3306
NAME = gitea
USER = gitea
PASSWD =
SCHEMA =
SSL_MODE = disable
LOG_SQL = false
; ;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; ; MSSQL Configuration
; ;
; DB_TYPE = mssql
; HOST = 172.17.0.2:1433
; NAME = gitea
; USER = SA
; PASSWD = MwantsaSecurePassword1
; CHARSET_COLLATION = ; Empty as default, Gitea will try to find a case-sensitive collation. Don't change it unless you clearly know what you need.
; ;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; ; Other settings
; ;
; ; For iterate buffer, default is 50
; ITERATE_BUFFER_SIZE = 50
; ;
; ; Show the database generated SQL
; LOG_SQL = false
; ;
; ; Maximum number of DB Connect retries
; DB_RETRIES = 10
; ;
; ; Backoff time per DB retry (time.Duration)
; DB_RETRY_BACKOFF = 3s
; ;
; ; Max idle database connections on connection pool, default is 2
; MAX_IDLE_CONNS = 2
; ;
; ; Database connection max life time, default is 0 or 3s mysql (See #6804 & #7071 for reasoning)
; CONN_MAX_LIFETIME = 3s
; ;
; ; Database maximum number of open connections, default is 0 meaning no maximum
; MAX_OPEN_CONNS = 0
; ;
; ; Whether execute database models migrations automatically
; AUTO_MIGRATION = true
; ;
; ; Threshold value (in seconds) beyond which query execution time is logged as a warning in the xorm logger
; ;
; SLOW_QUERY_THRESHOLD = 5s
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[security]
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; ; Whether the installer is disabled (set to true to disable the installer)
INSTALL_LOCK = true
; ;
; ; Global secret key that will be used
; ; This key is VERY IMPORTANT. If you lose it, the data encrypted by it (like 2FA secret) can't be decrypted anymore.
SECRET_KEY =
; ;
; ; Alternative location to specify secret key, instead of this file; you cannot specify both this and SECRET_KEY, and must pick one
; ; This key is VERY IMPORTANT. If you lose it, the data encrypted by it (like 2FA secret) can't be decrypted anymore.
; SECRET_KEY_URI = file:/etc/gitea/secret_key
; ;
; ; Secret used to validate communication within Gitea binary.
INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE3NzM4MjYzMzJ9.XxUurNQ5drHXpepExUTNEtYS4yLpTdac_fGOeL0eVco
PASSWORD_HASH_ALGO = pbkdf2
; ;
; ; Alternative location to specify internal token, instead of this file; you cannot specify both this and INTERNAL_TOKEN, and must pick one
; INTERNAL_TOKEN_URI = file:/etc/gitea/internal_token
; ;
; ; How long to remember that a user is logged in before requiring relogin (in days)
; LOGIN_REMEMBER_DAYS = 31
; ;
; ; Name of the cookie used to store the current username.
; COOKIE_USERNAME = gitea_awesome
; ;
; ; Name of cookie used to store authentication information.
; COOKIE_REMEMBER_NAME = gitea_incredible
; ;
; ; Reverse proxy authentication header name of user name, email, and full name
; REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER
; REVERSE_PROXY_AUTHENTICATION_EMAIL = X-WEBAUTH-EMAIL
; REVERSE_PROXY_AUTHENTICATION_FULL_NAME = X-WEBAUTH-FULLNAME
; ;
; ; Interpret X-Forwarded-For header or the X-Real-IP header and set this as the remote IP for the request
; REVERSE_PROXY_LIMIT = 1
; ;
; ; List of IP addresses and networks separated by comma of trusted proxy servers. Use `*` to trust all.
; REVERSE_PROXY_TRUSTED_PROXIES = 127.0.0.0/8,::1/128
; ;
; ; The minimum password length for new Users
; MIN_PASSWORD_LENGTH = 8
; ;
; ; Set to true to allow users to import local server paths
; IMPORT_LOCAL_PATHS = false
; ;
; ; Set to false to allow users with git hook privileges to create custom git hooks.
; ; Custom git hooks can be used to perform arbitrary code execution on the host operating system.
; ; This enables the users to access and modify this config file and the Gitea database and interrupt the Gitea service.
; ; By modifying the Gitea database, users can gain Gitea administrator privileges.
; ; It also enables them to access other resources available to the user on the operating system that is running the Gitea instance and perform arbitrary actions in the name of the Gitea OS user.
; ; WARNING: This maybe harmful to you website or your operating system.
; ; WARNING: Setting this to true does not change existing hooks in git repos; adjust it before if necessary.
; DISABLE_GIT_HOOKS = true
; ;
; ; Set to true to disable webhooks feature.
; DISABLE_WEBHOOKS = false
; ;
; ; Set to false to allow pushes to gitea repositories despite having an incomplete environment - NOT RECOMMENDED
; ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET = true
; ;
; ;Comma separated list of character classes required to pass minimum complexity.
; ;If left empty or no valid values are specified, the default is off (no checking)
; ;Classes include "lower,upper,digit,spec"
; PASSWORD_COMPLEXITY = off
; ;
; ; Password Hash algorithm, either "argon2", "pbkdf2", "scrypt" or "bcrypt"
; PASSWORD_HASH_ALGO = pbkdf2
; ;
; ; Set false to allow JavaScript to read CSRF cookie
; CSRF_COOKIE_HTTP_ONLY = true
; ;
; ; Validate against https://haveibeenpwned.com/Passwords to see if a password has been exposed
; PASSWORD_CHECK_PWN = false
; ;
; ; Cache successful token hashes. API tokens are stored in the DB as pbkdf2 hashes however, this means that there is a potentially significant hashing load when there are multiple API operations.
; ; This cache will store the successfully hashed tokens in a LRU cache as a balance between performance and security.
; SUCCESSFUL_TOKENS_CACHE_SIZE = 20
; ;
; ; Reject API tokens sent in URL query string (Accept Header-based API tokens only). This avoids security vulnerabilities
; ; stemming from cached/logged plain-text API tokens.
; ; In future releases, this will become the default behavior
; DISABLE_QUERY_AUTH_TOKEN = false
; ;
; ; On user registration, record the IP address and user agent of the user to help identify potential abuse.
; ; RECORD_USER_SIGNUP_METADATA = false
; ;
; ; Set the two-factor auth behavior.
; ; Set to "enforced", to force users to enroll into Two-Factor Authentication, users without 2FA have no access to repositories via API or web.
; TWO_FACTOR_AUTH =
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[camo]
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; ; At the moment we only support images
; ;
; ; if the camo is enabled
; ENABLED = false
; ; url to a camo image proxy, it **is required** if camo is enabled.
; SERVER_URL =
; ; HMAC to encode urls with, it **is required** if camo is enabled.
; HMAC_KEY =
; ; Set to true to use camo for https too lese only non https urls are proxyed
; ; ALLWAYS is deprecated and will be removed in the future
; ALWAYS = false
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[oauth2]
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; ; Enables OAuth2 provider
ENABLED = true
JWT_SECRET = tU0mvpYYFatn8ZJ45ZnuA4RLy6kogrerIblDbihu8oU
; ;
; ; Algorithm used to sign OAuth2 tokens. Valid values: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, EdDSA
; JWT_SIGNING_ALGORITHM = RS256
; ;
; ; Private key file path used to sign OAuth2 tokens. The path is relative to APP_DATA_PATH.
; ; This setting is only needed if JWT_SIGNING_ALGORITHM is set to RS256, RS384, RS512, ES256, ES384 or ES512.
; ; The file must contain a RSA or ECDSA private key in the PKCS8 format. If no key exists a 4096 bit key will be created for you.
; JWT_SIGNING_PRIVATE_KEY_FILE = jwt/private.pem
; ;
; ; OAuth2 authentication secret for access and refresh tokens, change this yourself to a unique string. CLI generate option is helpful in this case. https://docs.gitea.io/en-us/command-line/#generate
; ; This setting is only needed if JWT_SIGNING_ALGORITHM is set to HS256, HS384 or HS512.
; JWT_SECRET =
; ;
; ; Alternative location to specify OAuth2 authentication secret. You cannot specify both this and JWT_SECRET, and must pick one
; JWT_SECRET_URI = file:/etc/gitea/oauth2_jwt_secret
; ;
; ; The "issuer" claim identifies the principal that issued the JWT.
; ; Gitea 1.25 makes it default to "ROOT_URL without the last slash" to follow the standard.
; ; If you have old logins from before 1.25, you may want to set it to the old (non-standard) value "ROOT_URL with the last slash".
; JWT_CLAIM_ISSUER =
; ;
; ; Lifetime of an OAuth2 access token in seconds
; ACCESS_TOKEN_EXPIRATION_TIME = 3600
; ;
; ; Lifetime of an OAuth2 refresh token in hours
; REFRESH_TOKEN_EXPIRATION_TIME = 730
; ;
; ; Check if refresh token got already used
; INVALIDATE_REFRESH_TOKENS = false
; ;
; ; Maximum length of oauth2 token/cookie stored on server
; MAX_TOKEN_LENGTH = 32767
; ;
; ; Pre-register OAuth2 applications for some universally useful services
; ; * https://github.com/hickford/git-credential-oauth
; ; * https://github.com/git-ecosystem/git-credential-manager
; ; * https://gitea.com/gitea/tea
; DEFAULT_APPLICATIONS = git-credential-oauth, git-credential-manager, tea
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[log]
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ; Root path for the log files - defaults to "{AppWorkPath}/log"
; ROOT_PATH =
; ;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ; Main Logger
; ;
; ; Either "console", "file" or "conn", default is "console"
; ; Use comma to separate multiple modes, e.g. "console, file"
MODE = console
; ;
; ; Either "Trace", "Debug", "Info", "Warn", "Error" or "None", default is "Info"
LEVEL = info
ROOT_PATH = /var/lib/gitea/log
; ;
; ; Print Stacktrace with logs (rarely helpful, do not set) Either "Trace", "Debug", "Info", "Warn", "Error", default is "None"
; STACKTRACE_LEVEL = None
; ;
; ; Buffer length of the channel, keep it as it is if you don't know what it is.
; BUFFER_LEN = 10000
; ;
; ; Sub logger modes, a single comma means use default MODE above, empty means disable it
; logger.access.MODE=
; logger.router.MODE=,
; logger.xorm.MODE=,
; ;
; ; Collect SSH logs (Creates log from ssh git request)
; ;
; ENABLE_SSH_LOG = false
; ;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; ; Access Logger (Creates log in NCSA common log format)
; ;
; ; Print request id which parsed from request headers in access log, when access log is enabled.
; ; * E.g:
; ; * In request Header: X-Request-ID: test-id-123
; ; * Configuration in app.ini: REQUEST_ID_HEADERS = X-Request-ID
; ; * Print in log: 127.0.0.1:58384 - - [14/Feb/2023:16:33:51 +0800] "test-id-123"
; ;
; ; If you configure more than one in the .ini file, it will match in the order of configuration,
; ; and the first match will be finally printed in the log.
; ; * E.g:
; ; * In request Header: X-Trace-ID: trace-id-1q2w3e4r
; ; * Configuration in app.ini: REQUEST_ID_HEADERS = X-Request-ID, X-Trace-ID, X-Req-ID
; ; * Print in log: 127.0.0.1:58384 - - [14/Feb/2023:16:33:51 +0800] "trace-id-1q2w3e4r"
; ;
; REQUEST_ID_HEADERS =
; ;
; ; Sets the template used to create the access log.
; ACCESS_LOG_TEMPLATE = {{.Ctx.RemoteHost}} - {{.Identity}} {{.Start.Format "[02/Jan/2006:15:04:05 -0700]" }} "{{.Ctx.Req.Method}} {{.Ctx.Req.URL.RequestURI}} {{.Ctx.Req.Proto}}" {{.ResponseWriter.Status}} {{.ResponseWriter.Size}} "{{.Ctx.Req.Referer}}" "{{.Ctx.Req.UserAgent}}"
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; ; Log modes (aka log writers)
; ;
; [log.%(WriterMode)]
; MODE=console/file/conn/...
; LEVEL=
; FLAGS = stdflags
; EXPRESSION =
; PREFIX =
; COLORIZE = false
; ;
; [log.console]
; STDERR = false
; ;
; [log.file]
; ; Set the file_name for the logger. If this is a relative path this will be relative to ROOT_PATH
; FILE_NAME =
; ; This enables automated log rotate(switch of following options), default is true
; LOG_ROTATE = true
; ; Max size shift of a single file, default is 28 means 1 << 28, 256MB
; MAX_SIZE_SHIFT = 28
; ; Segment log daily, default is true
; DAILY_ROTATE = true
; ; delete the log file after n days, default is 7
; MAX_DAYS = 7
; ; compress logs with gzip
; COMPRESS = true
; ; compression level see godoc for compress/gzip
; COMPRESSION_LEVEL = -1
; ;
; [log.conn]
; ; Reconnect host for every single message, default is false
; RECONNECT_ON_MSG = false
; ; Try to reconnect when connection is lost, default is false
; RECONNECT = false
; ; Either "tcp", "unix" or "udp", default is "tcp"
; PROTOCOL = tcp
; ; Host address
; ADDR =
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[git]
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; ; The path of git executable. If empty, Gitea searches through the PATH environment.
; PATH =
; ;
; ; The HOME directory for Git, defaults to "{APP_DATA_PATH}/home"
; HOME_PATH =
; ;
; ; Disables highlight of added and removed changes
; DISABLE_DIFF_HIGHLIGHT = false
; ;
; ; Max number of lines allowed in a single file in diff view
; MAX_GIT_DIFF_LINES = 1000
; ;
; ; Max number of allowed characters in a line in diff view
; MAX_GIT_DIFF_LINE_CHARACTERS = 5000
; ;
; ; Max number of files shown in diff view
; MAX_GIT_DIFF_FILES = 100
; ;
; ; Set the default commits range size
; COMMITS_RANGE_SIZE = 50
; ;
; ; Set the default branches range size
; BRANCHES_RANGE_SIZE = 20
; ;
; ; Arguments for command 'git gc', e.g. "--aggressive --auto"
; ; see more on http://git-scm.com/docs/git-gc/
; GC_ARGS =
; ;
; ; If use git wire protocol version 2 when git version >= 2.18, default is true, set to false when you always want git wire protocol version 1
; ; To enable this for Git over SSH when using a OpenSSH server, add `AcceptEnv GIT_PROTOCOL` to your sshd_config file.
; ENABLE_AUTO_GIT_WIRE_PROTOCOL = true
; ;
; ; Respond to pushes to a non-default branch with a URL for creating a Pull Request (if the repository has them enabled)
; PULL_REQUEST_PUSH_MESSAGE = true
; ;
; ; (Go-Git only) Don't cache objects greater than this in memory. (Set to 0 to disable.)
; LARGE_OBJECT_THRESHOLD = 1048576
; ; Set to true to forcibly set core.protectNTFS=false
; DISABLE_CORE_PROTECT_NTFS=false
; ; Disable the usage of using partial clones for git.
; DISABLE_PARTIAL_CLONE = false
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ; Git Operation timeout in seconds
; [git.timeout]
; DEFAULT = 360
; MIGRATE = 600
; MIRROR = 300
; CLONE = 300
; PULL = 300
; GC = 60
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ; Git config options
; ; This section only does "set" config, a removed config key from this section won't be removed from git config automatically. The format is `some.configKey = value`.
; [git.config]
; diff.algorithm = histogram
; core.logAllRefUpdates = true
; gc.reflogExpire = 90
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[service]
REGISTER_EMAIL_CONFIRM = false
ENABLE_NOTIFY_MAIL = false
DISABLE_REGISTRATION = false
ALLOW_ONLY_EXTERNAL_REGISTRATION = true
ENABLE_CAPTCHA = false
REQUIRE_SIGNIN_VIEW = false
DEFAULT_KEEP_EMAIL_PRIVATE = false
DEFAULT_ALLOW_CREATE_ORGANIZATION = false
DEFAULT_ENABLE_TIMETRACKING = true
DEFAULT_USER_VISIBILITY = private
DEFAULT_USER_IS_RESTRICTED = true
NO_REPLY_ADDRESS = noreply.localhost
[repository]
ROOT = /var/lib/gitea/data/gitea-repositories
MAX_CREATION_LIMIT = 0
[lfs]
PATH = /var/lib/gitea/data/lfs
[mailer]
ENABLED = false
[openid]
ENABLE_OPENID_SIGNIN = true
ENABLE_OPENID_SIGNUP = true
[cron.update_checker]
ENABLED = false
[session]
PROVIDER = file
[repository.pull-request]
DEFAULT_MERGE_STYLE = merge
[repository.signing]
DEFAULT_TRUST_MODEL = committer
[oauth2_client]
ENABLE_AUTO_REGISTRATION = true
ACCOUNT_LINKING = auto
OPENID_CONNECT_SCOPES = profile email

View File

@@ -0,0 +1,25 @@
# Generated by Django 6.0.3 on 2026-03-23 11:59
import modelcluster.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('home', '0019_coursepage_description'),
]
operations = [
migrations.AddField(
model_name='coursepage',
name='repository_url',
field=models.URLField(blank=True, null=True),
),
migrations.AlterField(
model_name='coursepage',
name='allowed_groups',
field=modelcluster.fields.ParentalManyToManyField(help_text="Additional groups that should have access to this course, e.g. Editors. NOTE: Users who purchase the course will be automatically added to a dedicated access group for this course, so you don't need to add that group here.", related_name='course_pages', to='auth.group'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 6.0.3 on 2026-03-30 07:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0020_coursepage_repository_url_and_more'),
]
operations = [
migrations.AddField(
model_name='modulelessonpage',
name='create_gitea_repo',
field=models.BooleanField(default=False, help_text='If enabled, a Gitea repository will be automatically created for this module when the module is published.'),
),
migrations.AddField(
model_name='modulelessonpage',
name='gitea_repo_url',
field=models.URLField(blank=True, help_text="URL of the Gitea repository for this lesson (auto-generated if 'create_gitea_repo' is enabled)", null=True),
),
]

View File

@@ -0,0 +1,40 @@
# Generated by Django 6.0.3 on 2026-03-30 07:53
import django.db.models.deletion
import wagtail.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0021_modulelessonpage_create_gitea_repo_and_more'),
('wagtailcore', '0096_referenceindex_referenceindex_source_object_and_more'),
('wagtailimages', '0027_image_description'),
]
operations = [
migrations.CreateModel(
name='BlogIndexPage',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
],
options={
'abstract': False,
},
bases=('wagtailcore.page',),
),
migrations.CreateModel(
name='BlogPage',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
('author', models.CharField(max_length=255)),
('body', wagtail.fields.StreamField([('heading', 0), ('paragraph', 1), ('image', 2)], block_lookup={0: ('wagtail.blocks.CharBlock', (), {'form_classname': 'title'}), 1: ('wagtail.blocks.RichTextBlock', (), {}), 2: ('wagtail.images.blocks.ImageBlock', [], {})})),
('image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image')),
],
options={
'abstract': False,
},
bases=('wagtailcore.page',),
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 6.0.3 on 2026-04-22 17:52
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0022_blogindexpage_blogpage'),
('wagtailcore', '0096_referenceindex_referenceindex_source_object_and_more'),
]
operations = [
migrations.CreateModel(
name='EventIndexPage',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
],
options={
'abstract': False,
},
bases=('wagtailcore.page',),
),
]

View File

@@ -1,15 +1,17 @@
from datetime import datetime, timedelta
from django.contrib.auth.models import Group, User
from django.conf import settings
from django.contrib.auth.models import Group, User
from django.db import models
from django.forms import CheckboxSelectMultiple
from django.utils import timezone
from modelcluster.contrib.taggit import ClusterTaggableManager
from modelcluster.fields import ParentalKey
from taggit.models import TaggedItemBase
from wagtail import blocks
from wagtail.admin.panels import FieldPanel
from wagtail.fields import RichTextField
from wagtail.fields import RichTextField, StreamField
from wagtail.images.blocks import ImageBlock
from wagtail.models import Page
from wagtail.models.copying import ParentalManyToManyField
from wagtail_color_panel.edit_handlers import NativeColorPanel
@@ -28,6 +30,15 @@ class HomePage(Page):
content_panels = Page.content_panels + ["body"]
class BlogIndexPage(Page):
subpage_types = ["home.BlogPage"]
def get_context(self, request):
context = super().get_context(request)
context["posts"] = self.get_children().live().order_by("-first_published_at")
return context
class CourseIndexPage(Page):
subpage_types = ["home.CoursePage"]
@@ -50,6 +61,35 @@ class CourseIndexPage(Page):
return context
class EventIndexPage(Page):
subpage_types = ["home.EventPage"]
class BlogPage(Page):
author = models.CharField(max_length=255)
image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
body = StreamField(
[
("heading", blocks.CharBlock(classname="title")),
("paragraph", blocks.RichTextBlock()),
("image", ImageBlock()),
]
)
content_panels = Page.content_panels + [
FieldPanel("author"),
FieldPanel("image"),
FieldPanel("body"),
]
parent_page_types = ["home.BlogIndexPage"]
class CoursePage(Page):
course_image = models.ForeignKey(
"wagtailimages.Image",
@@ -66,6 +106,11 @@ class CoursePage(Page):
help_text="Additional groups that should have access to this course, e.g. Editors. NOTE: Users who purchase the course will be automatically added to a dedicated access group for this course, so you don't need to add that group here.",
)
repository_url = models.URLField(
null=True,
blank=True,
)
def _user_has_access(self, user):
if not user.is_authenticated:
return False
@@ -105,11 +150,11 @@ class CoursePage(Page):
return created
def save(self, *args, **kwargs):
group_name = f"course_{self.id}_access"
group, created = Group.objects.get_or_create(name=group_name)
if state := not self.allowed_groups.filter(id=group.id).exists():
print(state)
self.allowed_groups.add(group)
if self.id is not None:
group_name = f"course_{self.id}_access"
group, created = Group.objects.get_or_create(name=group_name)
if not self.allowed_groups.filter(id=group.id).exists():
self.allowed_groups.add(group)
super().save(*args, **kwargs)
@@ -124,6 +169,11 @@ class CoursePage(Page):
FieldPanel("description"),
FieldPanel("body"),
FieldPanel("allowed_groups", widget=CheckboxSelectMultiple),
FieldPanel(
"repository_url",
read_only=True,
heading="Repository URL (auto-generated)",
),
]
parent_page_types = ["home.CourseIndexPage"]
subpage_types = ["home.CourseModulePage"]
@@ -154,6 +204,23 @@ class CourseModulePage(Page):
class ModuleLessonPage(Page):
body = RichTextField(blank=True)
create_gitea_repo = models.BooleanField(
default=False,
help_text="If enabled, a Gitea repository will be automatically created for this module when the module is published.",
)
gitea_repo_url = models.URLField(
null=True,
blank=True,
help_text="URL of the Gitea repository for this lesson (auto-generated if 'create_gitea_repo' is enabled)",
)
@property
def gitea_login_redirect_url(self):
gitea_root_url = getattr(settings, "GITEA_ROOT_URL")
if self.gitea_repo_url and gitea_root_url:
uri = str(self.gitea_repo_url).replace(gitea_root_url, "")
return f"{gitea_root_url}/user/login?redirect_to={uri}"
return None
@property
def module(self):
@@ -170,7 +237,15 @@ class ModuleLessonPage(Page):
return f"{module.full_title} - {self.title}"
return self.title
content_panels = Page.content_panels + ["body"]
content_panels = Page.content_panels + [
FieldPanel("body"),
FieldPanel("create_gitea_repo"),
FieldPanel(
"gitea_repo_url",
read_only=True,
heading="Gitea Repository URL",
),
]
parent_page_types = ["home.CourseModulePage"]
@@ -251,6 +326,8 @@ class EventPage(Page):
help_text="Select users who will be listed as hosts of this event.",
)
parent_page_types = ["home.EventIndexPage"]
def get_context(self, request):
context = super().get_context(request)
# Occurrence-specific context should be handled in views/templates
@@ -261,15 +338,26 @@ class EventPage(Page):
Generate EventOccurrence objects for this event based on recurrence settings.
For endless recurrence, generate up to days_ahead into the future.
"""
from .event_occurrence import EventOccurrence
now = timezone.now()
if not self.live:
# if not live, no future occurrences should be generated
self.occurrences.filter(start__gt=now).delete()
return
if not self.recurrence_enabled:
# if recurrence is not enabled, ensure there's at least one occurrence for the specified start/end
# and delete any other occurrences that don't match the current start/end
if self.occurrences.exists():
occurrence = self.occurrences.first()
if occurrence.start != self.start or occurrence.end != self.end:
occurrence.start = self.start
occurrence.end = self.end
occurrence.save(update_fields=["start", "end"])
self.occurrences.exclude(id=occurrence.id).delete()
else:
EventOccurrence.objects.create(
event=self, start=self.start, end=self.end

View File

@@ -1,39 +1,214 @@
import logging as lg
import os
import requests
from django.conf import settings
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from home.models.pages import CoursePage, ModuleLessonPage
@receiver(post_save, sender=User)
def notify_external_service_on_signup(sender, instance, created, **kwargs):
pass
# if created and not instance.is_staff:
# payload = {
# "user_id": instance.id,
# "username": f"KURSY-{instance.id}",
# "email": instance.email,
# "full_name": f"{instance.first_name} {instance.last_name}".strip(),
# # "must_change_password": True,
# # "password": instance.password,
# "visibility": "private",
# }
# api_url = getattr(settings, "GITEA_URL", None)
# if api_url:
# url = f"{api_url}/admin/users"
# try:
# response = requests.post(
# url,
# json=payload,
# timeout=5,
# headers={"Authorization": f"token {os.getenv('GITEA_API_TOKEN')}"},
# )
# response.raise_for_status()
# print(f"Successfully created Gitea account for {instance.email}")
# except Exception as e:
# print(
# f"Failed to create Gitea account for user {instance.email}: {e}\n{response.text}"
# )
# raise e
GITEA_ORG_NAME = "Studio77"
logger = lg.getLogger(__name__)
@receiver(post_save, sender=CoursePage)
def create_gitea_team_on_course_creation(sender, instance, created, **kwargs):
if not instance.live:
logger.debug(
f"Course {instance.title} is not live, skipping Gitea team creation"
)
return
course = instance
team_name = f"course-{course.id}"
api_url = getattr(settings, "GITEA_URL", None)
if not api_url:
logger.debug("GITEA_URL is not set, skipping Gitea team creation")
return
# check if team already exists
try:
response = requests.get(
f"{api_url}/orgs/{GITEA_ORG_NAME}/teams",
timeout=5,
headers={"Authorization": f"token {os.getenv('GITEA_API_TOKEN')}"},
)
response.raise_for_status()
teams = response.json()
if any(team["name"] == team_name for team in teams):
logger.info(f"Gitea team {team_name} already exists, skipping creation")
return
except Exception as e:
logger.exception(
f"Failed to check existing Gitea teams: {e}\n{response.text}",
e,
)
return
url = f"{api_url}/orgs/{GITEA_ORG_NAME}/teams"
payload = {
"can_create_org_repo": False,
"description": f"Team for course {course.title}",
"includes_all_repositories": False,
"name": team_name,
"permission": "read",
"units": [
# "repo.actions",
"repo.code",
# "repo.issues",
# "repo.ext_issues",
# "repo.wiki",
# "repo.ext_wiki",
# "repo.pulls",
# "repo.releases",
# "repo.projects",
# "repo.ext_wiki",
],
}
try:
response = requests.post(
url,
json=payload,
timeout=5,
headers={"Authorization": f"token {os.getenv('GITEA_API_TOKEN')}"},
)
response.raise_for_status()
logger.info(
f"Successfully created Gitea team `{team_name}` for course {course.title}"
)
except Exception as e:
logger.exception(
f"Failed to create Gitea team for course {course.title}: {e}\n{response.text}",
e,
)
@receiver(post_save, sender=ModuleLessonPage)
def create_gitea_repo_on_lesson_creation(
sender, instance: ModuleLessonPage, created, **kwargs
):
if not instance.live:
logger.debug(
f"Lesson {instance.title} is not live, skipping Gitea repository creation"
)
return
course = instance.module.course
repo_name = f"course-{course.id}-lesson-{instance.id}"
if not instance.create_gitea_repo:
logger.debug(
f"Lesson {instance.title} is not marked for Gitea repository creation, skipping"
)
return
if not course.live:
logger.debug(
f"Course {course.title} is not live, skipping Gitea repository creation for lesson {instance.title}"
)
return
api_url = getattr(settings, "GITEA_URL", None)
if not api_url:
logger.debug("GITEA_URL is not set, skipping Gitea repository creation")
return
def create_repository() -> None:
# check if repository already exists
try:
response = requests.get(
f"{api_url}/orgs/{GITEA_ORG_NAME}/repos",
timeout=5,
headers={"Authorization": f"token {os.getenv('GITEA_API_TOKEN')}"},
)
response.raise_for_status()
repos = response.json()
if any(repo["name"] == repo_name for repo in repos):
logger.info(
f"Gitea repository {repo_name} already exists, skipping creation"
)
return
except Exception as e:
logger.exception(
f"Failed to check existing Gitea repositories: {e}\n{response.text}",
e,
)
return
# create lesson repository
url = f"{api_url}/orgs/{GITEA_ORG_NAME}/repos"
payload = {
"auto_init": True,
"default_branch": "main",
"description": f"{instance.module.course} {instance.module}: {instance.title}",
"name": repo_name,
"private": True,
}
try:
response = requests.post(
url,
json=payload,
timeout=5,
headers={"Authorization": f"token {os.getenv('GITEA_API_TOKEN')}"},
)
response.raise_for_status()
repo_url = response.json().get("html_url")
instance.gitea_repo_url = repo_url
instance.save(update_fields=["gitea_repo_url"])
logger.info(
f"Successfully created Gitea repository for lesson {instance.title}: {repo_url}"
)
except Exception as e:
logger.exception(
f"Failed to create Gitea repository for lesson {instance.title}: {e}\n{response.text}",
e,
)
def add_repository_to_team() -> None:
# add repository to course team
team_name = f"course-{course.id}"
try:
response = requests.get(
f"{api_url}/orgs/{GITEA_ORG_NAME}/teams",
timeout=5,
headers={"Authorization": f"token {os.getenv('GITEA_API_TOKEN')}"},
)
response.raise_for_status()
teams = response.json()
team = next((team for team in teams if team["name"] == team_name), None)
if not team:
logger.error(
f"Gitea team {team_name} not found when trying to add repository {repo_name}"
)
return
team_id = team["id"]
add_repo_url = (
f"{api_url}/teams/{team_id}/repos/{GITEA_ORG_NAME}/{repo_name}"
)
add_response = requests.put(
add_repo_url,
timeout=5,
headers={"Authorization": f"token {os.getenv('GITEA_API_TOKEN')}"},
)
add_response.raise_for_status()
logger.info(
f"Successfully added repository {repo_name} to Gitea team {team_name}"
)
except Exception as e:
logger.exception(
f"Failed to add repository {repo_name} to Gitea team {team_name}: {e}\n{response.text}",
e,
)
create_repository()
add_repository_to_team()

View File

@@ -5,25 +5,88 @@
{% trans "Chat with" %} {{ chat_user.email }}
{% endblock titletag %}
{% block extra_css %}
<style>
.admin-chat-messages {
list-style: none;
padding: 0;
margin: 1.5em 0;
max-height: 350px;
overflow-y: auto;
border-radius: 6px;
border: 1px solid #e2e2e2;
}
.admin-chat-messages li {
padding: 0.7em 1em;
border-bottom: 1px solid #ececec;
display: flex;
align-items: baseline;
font-size: 1.05em;
}
.admin-chat-messages li:last-child {
border-bottom: none;
}
.admin-chat-message-meta {
color: #888;
font-size: 0.92em;
margin-right: 0.5em;
white-space: nowrap;
}
.admin-chat-form {
display: flex;
gap: 0.5em;
margin-top: 1em;
}
.admin-chat-form input[type="text"] {
flex: 1;
padding: 0.5em;
border-radius: 4px;
border: 1px solid #ccc;
font-size: 1em;
}
.admin-chat-form button {
padding: 0.5em 1.2em;
border-radius: 4px;
border: none;
background: #0073e6;
color: #fff;
font-weight: bold;
cursor: pointer;
transition: background 0.2s;
}
.admin-chat-form button:hover {
background: #005bb5;
}
.admin-message-admin {
background: #334;
}
.admin-message-user {
}
</style>
{% endblock extra_css %}
{% block content %}
{% include "wagtailadmin/shared/header.html" with title="Chat" icon="mail" %}
<h1>{% trans "Admin Chat View" %}</h1>
<p>{% trans "This is the admin view of the chat. Here you can manage conversations and monitor user interactions." %}</p>
<div style="padding: 0 3em;">
<a href="{% url 'user_chat' %}" class="button button-secondary">&larr; {% trans "Back" %}</a>
<h1>{% trans "Chat with" %} <em>{{ chat_user.email }}</em></h1>
<ul>
{% for message in chat_messages %}
<li>
<strong>{{ message.sender.email }}:</strong> {{ message.content }}
</li>
{% empty %}
<li>{% trans "No messages found." %}</li>
{% endfor %}
</ul>
<form action="/chat/send/{{ chat_user.id }}/" method="post">
{% csrf_token %}
<input type="text" name="content" placeholder="{% trans "Type your message here..." %}" required>
<button type="submit">{% trans "Send" %}</button>
</form>
<ul class="admin-chat-messages">
{% for message in chat_messages %}
<li class="{% if message.sender.id == chat_user.id %}admin-message-user{% else %}admin-message-admin{% endif %}">
<span class="admin-chat-message-meta">{{ message.timestamp|date:"Y-m-d H:i" }}</span>
<strong>{{ message.sender.email }}:&nbsp;</strong> {{ message.content }}
</li>
{% empty %}
<li>{% trans "No messages found." %}</li>
{% endfor %}
</ul>
<form action="/chat/send/{{ chat_user.id }}/" method="post" class="admin-chat-form">
{% csrf_token %}
<input type="text" name="content" placeholder="{% trans "Type your message here..." %}" required>
<button type="submit">{% trans "Send" %}</button>
</form>
</div>
{% endblock content %}

View File

@@ -6,21 +6,36 @@
{% endblock title %}
{% block content %}
<h1>{% trans "Chat with Support" %}</h1>
<p>{% trans "This is the user chat interface. Here you can communicate with our support team for assistance." %}</p>
<h1 class="text-2xl font-bold mb-4 text-gray-800">{% trans "Chat with Support" %}</h1>
<p class="mb-6 text-gray-600">{% trans "Welcome! We're here to help. Type your message below to start a conversation with our support team." %}</p>
<ul>
{% for message in chat_messages %}
<li>
<strong>{{ message.sender.email }}:</strong> {{ message.content }}
<ul class="flex flex-col gap-2 mb-6">
{% regroup chat_messages by timestamp.date as dated_messages %}
{% for day in dated_messages %}
<li class="flex justify-center my-2">
<span class="px-4 py-1 bg-gray-100 text-gray-500 text-xs rounded-full shadow">{{ day.grouper|date:'l, d M Y' }}</span>
</li>
{% for message in day.list %}
<li class="flex flex-col items-start {% if message.sender.id == user.id %}items-end{% endif %}">
<div class="max-w-xs px-4 py-2 rounded-2xl shadow text-sm
{% if message.sender.id == user.id %}
bg-blue-600 text-white rounded-br-none self-end
{% else %}
bg-gray-200 text-gray-900 rounded-bl-none self-start
{% endif %}">
<span class="block font-semibold text-xs mb-1 {% if message.sender.id == user.id %}text-blue-100{% else %}text-blue-700{% endif %}">{{ message.sender.email }}</span>
<span>{{ message.content }}</span>
<span class="block text-xs {% if message.sender.id == user.id %}text-blue-100{% else %}text-blue-700{% endif %} mt-1 text-right">{{ message.timestamp|date:'H:i' }}</span>
</div>
</li>
{% endfor %}
{% empty %}
<li>{% trans "No messages found." %}</li>
<li class="text-gray-400">{% trans "No messages found." %}</li>
{% endfor %}
</ul>
<form action="/chat/send/{{ user.id }}/" method="post">
<form action="/chat/send/{{ user.id }}/" method="post" class="flex gap-4">
{% csrf_token %}
<textarea name="content" placeholder="{% trans "Type your message here..." %}"></textarea>
<button type="submit">{% trans "Send" %}</button>
<textarea rows=1 name="content" placeholder="{% trans "Type your message here..." %}" class="grow resize-y min-h-10 border border-gray-300 rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-blue-400 text-gray-800"></textarea>
<button type="submit" class="self-center px-5 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition">{% trans "Send" %}</button>
</form>
{% endblock content %}

View File

@@ -0,0 +1,36 @@
{% extends "base.html" %}
{% load static i18n wagtailcore_tags wagtailimages_tags %}
{% block title %}{% trans "Blog" %}{% endblock title %}
{% block body_class %}template-blogindex{% endblock body_class %}
{% block content %}
<h1 class="text-3xl font-bold mb-6 text-gray-800 text-center">
{% trans "Blog" %}
</h1>
<div class="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
{% for post in posts %}
<a href="{{ post.url }}" class="block bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300">
{% image post.specific.image original %}
<div class="p-4">
<div class="flex justify-between items-center mb-2 text-gray-500">
<div class="flex items-center gap-1">
<i class="fi fi-br-circle-user leading-0"></i>
<span class="text-sm">{{ post.specific.author }}</span>
</div>
<div class="flex items-center gap-1">
<i class="fi fi-br-calendar leading-0"></i>
<span class="text-sm">{{ post.first_published_at|date:"F j, Y | H:i" }}</span>
</div>
</div>
<h2 class="text-xl font-semibold mb-2">{{ post.specific.title }}</h2>
<p class="text-gray-600">{{ post.specific.description|truncatewords:20 }}</p>
</div>
</a>
{% empty %}
<p class="text-gray-600">{% trans "No blog posts available." %}</p>
{% endfor %}
</div>
{% endblock content %}

View File

@@ -0,0 +1,45 @@
{% extends "base.html" %}
{% load static i18n wagtailcore_tags wagtailimages_tags %}
{% block title %}{{ page.specific.title }} | {% trans "Blog" %}{% endblock title %}
{% block body_class %}template-blog{% endblock body_class %}
{% block content %}
<div class="max-w-3xl mx-auto py-12 px-4">
<a href="{{ page.get_parent.url }}" class="inline-flex items-center gap-1 text-gray-500 text-4xl mb-4 hover:text-gray-700 transition-colors duration-300">
<i class="fi fi-br-arrow-left leading-0"></i>
</a>
<h1 class="text-4xl font-bold mb-6 text-gray-800">
{{ page.specific.title }}
</h1>
<div class="flex items-center gap-4 mb-8 text-gray-500">
<div class="flex items-center gap-1">
<i class="fi fi-br-circle-user leading-0"></i>
<span class="text-sm">{{ page.specific.author }}</span>
</div>
<div class="flex items-center gap-1">
<i class="fi fi-br-calendar leading-0"></i>
<span class="text-sm">{{ page.first_published_at|date:"F j, Y | H:i" }}</span>
</div>
</div>
{% if page.specific.image %}
<div class="mb-8">
{% image page.specific.image original class="w-full max-h-100 object-contain rounded-lg" %}
</div>
{% endif %}
<article class="prose max-w-none">
{% for block in page.body %}
{% if block.block_type == 'heading' %}
<h2>
{{ block.value }}
</h2>
{% else %}
<section class="block-{{ block.block_type }}">
{% include_block block %}
</section>
{% endif %}
{% endfor %}
</article>
</div>
{% endblock content %}

View File

@@ -13,7 +13,9 @@
{% block content %}
<h2 class="not-prose text-xl mb-4 text-gray-700">
<a href="{{ page.course.url }}" class="font-bold">{{ page.course.title }}</a> &raquo; {{ page.title }}
<a href="{% slugurl 'courses' %}" class="font-bold hover:underline">{% trans "Courses" %}</a>
&raquo; <a href="{{ page.course.url }}" class="font-bold hover:underline">{{ page.course.title }}</a>
&raquo; {{ page.title }}
</h2>
{{ page.body|richtext }}

View File

@@ -12,8 +12,9 @@
{% block content_class %}prose{% endblock content_class %}
{% block content %}
<h1 class="not-prose text-3xl mb-4 text-gray-700 font-bold">
{{ page.title }}
<h1 class="not-prose text-3xl mb-4 text-gray-700">
<a href="{% slugurl 'courses' %}" class="font-bold hover:underline">{% trans "Courses" %}</a>
&raquo; {{ page.title }}
</h1>
{% if page.course_image %}

View File

@@ -13,13 +13,21 @@
{% block content %}
<h2 class="not-prose text-xl mb-4 text-gray-700">
<a href="{{ page.module.course.url }}" class="font-bold">{{ page.module.course.title }}</a>
&raquo;
<a href="{{ page.module.url }}" class="font-bold">{{ page.module.title }}</a>
&raquo;
<span class="text-gray-500">{{ page.title }}</span>
<a href="{% slugurl 'courses' %}" class="font-bold hover:underline">{% trans "Courses" %}</a>
&raquo; <a href="{{ page.module.course.url }}" class="font-bold hover:underline">{{ page.module.course.title }}</a>
&raquo; <a href="{{ page.module.url }}" class="font-bold hover:underline">{{ page.module.title }}</a>
&raquo; <span class="text-gray-500">{{ page.title }}</span>
</h2>
{% if page.create_gitea_repo and page.gitea_repo_url %}
<p class="not-prose mb-6 text-gray-600 text-lg flex items-center gap-1">
<i class="fi fi-br-code-window leading-0"></i>
<a href="{{ page.gitea_login_redirect_url }}" class="hover:underline" target="_blank" rel="noopener noreferrer">
{% trans "View code on Gitea" %}
</a>
</p>
{% endif %}
{{ page.body|richtext }}
{% endblock content %}

View File

@@ -14,7 +14,6 @@ def admin_chat_dashboard(request):
@login_required
def admin_chat(request, user_id):
chat_user = User.objects.filter(id=user_id, is_staff=False).first()
print(chat_user)
chat_messages = ChatMessage.get_support_chat(chat_user)
return render(
request,

View File

@@ -1,13 +1,63 @@
import logging as lg
import os
import requests
from django import forms
from django.conf import settings
from django.core.handlers.wsgi import WSGIRequest
logger = lg.getLogger(__name__)
def create_gitea_account(user, password):
payload = {
"user_id": user.id,
"username": f"studio77-{user.id}",
"email": user.email,
"full_name": f"{user.first_name} {user.last_name}".strip(),
"password": password,
"must_change_password": False,
"visibility": "private",
}
api_url = getattr(settings, "GITEA_URL", None)
if api_url:
url = f"{api_url}/admin/users"
try:
response = requests.post(
url,
json=payload,
timeout=5,
headers={"Authorization": f"token {os.getenv('GITEA_API_TOKEN')}"},
)
response.raise_for_status()
logger.info(f"Successfully created Gitea account for {user.email}")
except requests.exceptions.HTTPError as http_err:
status_code = http_err.response.status_code
if status_code == 422:
logger.warning(
f"Gitea account for user {user.email} already exists. Skipping creation."
)
else:
logger.error(
f"HTTP error occurred while creating Gitea account for user {user.email}: {http_err}\n{response.text}"
)
raise http_err
except Exception as e:
logger.error(
f"Failed to create Gitea account for user {user.email}: {e}\n{response.text}"
)
raise e
class SignUpForm(forms.Form):
first_name = forms.CharField(max_length=60, required=True, label="First Name")
last_name = forms.CharField(max_length=60, required=True, label="Last Name")
def signup(self, request, user):
user.first_name = self.cleaned_data["first_name"]
user.last_name = self.cleaned_data["last_name"]
def signup(self, request: WSGIRequest, user):
user.first_name = self.cleaned_data["first_name"].strip().title()
user.last_name = self.cleaned_data["last_name"].strip().title()
user.save()
return user
# gitea account creation
password = request.POST.get("password1")
create_gitea_account(user, password)

View File

@@ -6,6 +6,6 @@ class CustomOAuth2Validator(OAuth2Validator):
print("get_additional_claims", request.user)
return {
"name": " ".join([request.user.first_name, request.user.last_name]),
"preferred_username": f"studio77-{request.user.username}",
"preferred_username": f"studio77-{request.user.id}",
"email": request.user.email,
}

View File

@@ -16,6 +16,7 @@ import os
from pathlib import Path
import dotenv
import logging as lg
PROJECT_DIR = Path(__file__).resolve().parent.parent
BASE_DIR = PROJECT_DIR.parent
@@ -161,7 +162,7 @@ OAUTH2_PROVIDER = {
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
"NAME": BASE_DIR / "db" / "db.sqlite3",
}
}
@@ -241,6 +242,43 @@ STORAGES = {
DATA_UPLOAD_MAX_NUMBER_FIELDS = 10_000
# Logging
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"verbose": {
"format": "{asctime} : {levelname} : {filename}:{lineno} : {name} :: {message}",
"style": "{",
},
"simple": {"format": "{asctime} : {levelname} :: {message}", "style": "{"},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "verbose",
},
},
"loggers": {
"django": {
"handlers": ["console"],
"level": "INFO",
"propagate": True,
},
"django.request": {
"handlers": ["console"],
"level": "INFO",
"propagate": False,
},
"home": {
"handlers": ["console"],
"level": "DEBUG",
"propagate": True,
},
},
}
# Wagtail settings
WAGTAIL_SITE_NAME = "kursy"
@@ -277,4 +315,10 @@ WAGTAILDOCS_EXTENSIONS = [
TAILWIND_APP_NAME = "theme"
# Gitea API
GITEA_URL = "http://localhost:3000/api/v1"
GITEA_ROOT_URL = "http://localhost:3000"
GITEA_URL = f"{GITEA_ROOT_URL}/api/v1"
lg.basicConfig(
level=lg.DEBUG,
format="%(asctime)s : %(levelname)s : %(filename)s:%(lineno)d : %(name)s :: %(message)s",
)

View File

@@ -5,7 +5,9 @@
<nav class="flex items-center gap-4">
<a class="text-xl font-bold" href="/">{{ current_site.site_name }}</a>
<a href="{% slugurl 'courses' %}" class="hover:underline">{% trans "Courses" %}</a>
<a href="{% slugurl 'blog' %}" class="hover:underline">{% trans "Blog" %}</a>
<a href="{% url 'calendar' %}" class="hover:underline">{% trans "Calendar" %}</a>
<a href="{% url 'user_chat' %}" class="hover:underline">{% trans "Help" %}</a>
</nav>
<nav class="flex items-center gap-4">

View File

@@ -1,7 +1,14 @@
import logging as lg
import os
import requests
from django.conf import settings
from django.contrib.auth.models import Group
from django.db import models
GITEA_ORG_NAME = "Studio77"
logger = lg.getLogger(__name__)
class CoursePurchase(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
@@ -13,11 +20,115 @@ class CoursePurchase(models.Model):
self.refunded = True
self.save()
def _get_gitea_team_id(self, team_name):
api_url = getattr(settings, "GITEA_URL", None)
if not api_url:
logger.debug("GITEA_URL is not set, skipping Gitea team assignment")
return None
try:
response = requests.get(
f"{api_url}/orgs/{GITEA_ORG_NAME}/teams",
timeout=5,
headers={"Authorization": f"token {os.getenv('GITEA_API_TOKEN')}"},
)
response.raise_for_status()
teams = response.json()
team = next((team for team in teams if team["name"] == team_name), None)
return team["id"] if team else None
except Exception as e:
logger.exception(
f"Failed to check existing Gitea teams: {e}\n{getattr(response, 'text', '')}",
e,
)
return None
def add_to_gitea_team(self):
course = self.course
user = self.user
team_name = f"course-{course.id}"
api_url = getattr(settings, "GITEA_URL", None)
if not api_url:
logger.debug("GITEA_URL is not set, skipping Gitea team assignment")
return
team_id = self._get_gitea_team_id(team_name)
if not team_id:
logger.warning(
f"Gitea team {team_name} not found for course {course.title}"
)
return
url = f"{api_url}/teams/{team_id}/members/studio77-{user.id}"
try:
response = requests.put(
url,
timeout=5,
headers={"Authorization": f"token {os.getenv('GITEA_API_TOKEN')}"},
)
if response.status_code == 204:
logger.info(
f"Successfully added user {user.email} to Gitea team {team_name}"
)
else:
response.raise_for_status()
except Exception as e:
logger.error(
f"Failed to add user {user.email} to Gitea team {team_name}: {e}\n{getattr(response, 'text', '')}"
)
def remove_from_gitea_team(self):
course = self.course
user = self.user
team_name = f"course-{course.id}"
api_url = getattr(settings, "GITEA_URL", None)
if not api_url:
logger.debug("GITEA_URL is not set, skipping Gitea team removal")
return
team_id = self._get_gitea_team_id(team_name)
if not team_id:
logger.warning(
f"Gitea team {team_name} not found for course {course.title}"
)
return
url = f"{api_url}/teams/{team_id}/members/studio77-{user.id}"
try:
response = requests.delete(
url,
timeout=5,
headers={"Authorization": f"token {os.getenv('GITEA_API_TOKEN')}"},
)
if response.status_code == 204:
logger.info(
f"Successfully removed user {user.email} from Gitea team {team_name}"
)
else:
response.raise_for_status()
except Exception as e:
logger.error(
f"Failed to remove user {user.email} from Gitea team {team_name}: {e}\n{getattr(response, 'text', '')}"
)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
group_name = f"course_{self.course.id}_access"
group, _ = Group.objects.get_or_create(name=group_name)
print(
f"Saving CoursePurchase for user {self.user} and course {self.course.title}, refunded={self.refunded}"
)
if self.refunded:
print(f"Removing user {self.user} from group {group_name} due to refund")
self.remove_from_gitea_team()
self.user.groups.remove(group)
else:
logger.debug(
f"Adding user {self.user} to group {group_name} for course {self.course.title}"
)
self.add_to_gitea_team()

View File

@@ -11,8 +11,11 @@ dependencies = [
"django-oauth-toolkit>=3.2.0",
"django-tailwind>=4.4.2",
"django-widget-tweaks>=1.5.1",
"gunicorn>=25.3.0",
"python-dotenv>=1.2.2",
"slippers>=0.6.2",
"uvicorn>=0.42.0",
"uvicorn-worker>=0.4.0",
"wagtail==7.3rc1",
"wagtail-color-panel>=1.7.1",
"wagtailmedia>=0.17.2",

352
uv.lock generated
View File

@@ -57,11 +57,11 @@ wheels = [
[[package]]
name = "certifi"
version = "2026.2.25"
version = "2026.4.22"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" }
sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
{ url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" },
]
[[package]]
@@ -99,39 +99,55 @@ wheels = [
[[package]]
name = "charset-normalizer"
version = "3.4.5"
version = "3.4.7"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/43/be/0f0fd9bb4a7fa4fb5067fb7d9ac693d4e928d306f80a0d02bde43a7c4aee/charset_normalizer-3.4.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8197abe5ca1ffb7d91e78360f915eef5addff270f8a71c1fc5be24a56f3e4873", size = 280232, upload-time = "2026-03-06T06:02:01.508Z" },
{ url = "https://files.pythonhosted.org/packages/28/02/983b5445e4bef49cd8c9da73a8e029f0825f39b74a06d201bfaa2e55142a/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2aecdb364b8a1802afdc7f9327d55dad5366bc97d8502d0f5854e50712dbc5f", size = 189688, upload-time = "2026-03-06T06:02:02.857Z" },
{ url = "https://files.pythonhosted.org/packages/d0/88/152745c5166437687028027dc080e2daed6fe11cfa95a22f4602591c42db/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a66aa5022bf81ab4b1bebfb009db4fd68e0c6d4307a1ce5ef6a26e5878dfc9e4", size = 206833, upload-time = "2026-03-06T06:02:05.127Z" },
{ url = "https://files.pythonhosted.org/packages/cb/0f/ebc15c8b02af2f19be9678d6eed115feeeccc45ce1f4b098d986c13e8769/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d77f97e515688bd615c1d1f795d540f32542d514242067adcb8ef532504cb9ee", size = 202879, upload-time = "2026-03-06T06:02:06.446Z" },
{ url = "https://files.pythonhosted.org/packages/38/9c/71336bff6934418dc8d1e8a1644176ac9088068bc571da612767619c97b3/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01a1ed54b953303ca7e310fafe0fe347aab348bd81834a0bcd602eb538f89d66", size = 195764, upload-time = "2026-03-06T06:02:08.763Z" },
{ url = "https://files.pythonhosted.org/packages/b7/95/ce92fde4f98615661871bc282a856cf9b8a15f686ba0af012984660d480b/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:b2d37d78297b39a9eb9eb92c0f6df98c706467282055419df141389b23f93362", size = 183728, upload-time = "2026-03-06T06:02:10.137Z" },
{ url = "https://files.pythonhosted.org/packages/1c/e7/f5b4588d94e747ce45ae680f0f242bc2d98dbd4eccfab73e6160b6893893/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e71bbb595973622b817c042bd943c3f3667e9c9983ce3d205f973f486fec98a7", size = 192937, upload-time = "2026-03-06T06:02:11.663Z" },
{ url = "https://files.pythonhosted.org/packages/f9/29/9d94ed6b929bf9f48bf6ede6e7474576499f07c4c5e878fb186083622716/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cd966c2559f501c6fd69294d082c2934c8dd4719deb32c22961a5ac6db0df1d", size = 192040, upload-time = "2026-03-06T06:02:13.489Z" },
{ url = "https://files.pythonhosted.org/packages/15/d2/1a093a1cf827957f9445f2fe7298bcc16f8fc5e05c1ed2ad1af0b239035e/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d5e52d127045d6ae01a1e821acfad2f3a1866c54d0e837828538fabe8d9d1bd6", size = 184107, upload-time = "2026-03-06T06:02:14.83Z" },
{ url = "https://files.pythonhosted.org/packages/0f/7d/82068ce16bd36135df7b97f6333c5d808b94e01d4599a682e2337ed5fd14/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:30a2b1a48478c3428d047ed9690d57c23038dac838a87ad624c85c0a78ebeb39", size = 208310, upload-time = "2026-03-06T06:02:16.165Z" },
{ url = "https://files.pythonhosted.org/packages/84/4e/4dfb52307bb6af4a5c9e73e482d171b81d36f522b21ccd28a49656baa680/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d8ed79b8f6372ca4254955005830fd61c1ccdd8c0fac6603e2c145c61dd95db6", size = 192918, upload-time = "2026-03-06T06:02:18.144Z" },
{ url = "https://files.pythonhosted.org/packages/08/a4/159ff7da662cf7201502ca89980b8f06acf3e887b278956646a8aeb178ab/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c5af897b45fa606b12464ccbe0014bbf8c09191e0a66aab6aa9d5cf6e77e0c94", size = 204615, upload-time = "2026-03-06T06:02:19.821Z" },
{ url = "https://files.pythonhosted.org/packages/d6/62/0dd6172203cb6b429ffffc9935001fde42e5250d57f07b0c28c6046deb6b/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1088345bcc93c58d8d8f3d783eca4a6e7a7752bbff26c3eee7e73c597c191c2e", size = 197784, upload-time = "2026-03-06T06:02:21.86Z" },
{ url = "https://files.pythonhosted.org/packages/c7/5e/1aab5cb737039b9c59e63627dc8bbc0d02562a14f831cc450e5f91d84ce1/charset_normalizer-3.4.5-cp314-cp314-win32.whl", hash = "sha256:ee57b926940ba00bca7ba7041e665cc956e55ef482f851b9b65acb20d867e7a2", size = 133009, upload-time = "2026-03-06T06:02:23.289Z" },
{ url = "https://files.pythonhosted.org/packages/40/65/e7c6c77d7aaa4c0d7974f2e403e17f0ed2cb0fc135f77d686b916bf1eead/charset_normalizer-3.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4481e6da1830c8a1cc0b746b47f603b653dadb690bcd851d039ffaefe70533aa", size = 143511, upload-time = "2026-03-06T06:02:26.195Z" },
{ url = "https://files.pythonhosted.org/packages/ba/91/52b0841c71f152f563b8e072896c14e3d83b195c188b338d3cc2e582d1d4/charset_normalizer-3.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:97ab7787092eb9b50fb47fa04f24c75b768a606af1bcba1957f07f128a7219e4", size = 133775, upload-time = "2026-03-06T06:02:27.473Z" },
{ url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z" },
{ url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" },
{ url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" },
{ url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" },
{ url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" },
{ url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" },
{ url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" },
{ url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" },
{ url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" },
{ url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" },
{ url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" },
{ url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" },
{ url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" },
{ url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" },
{ url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" },
{ url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" },
{ url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" },
{ url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" },
{ url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" },
{ url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" },
{ url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" },
{ url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" },
{ url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" },
{ url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" },
{ url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" },
{ url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" },
{ url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" },
{ url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" },
{ url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" },
{ url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" },
{ url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" },
{ url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" },
{ url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" },
{ url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" },
]
[[package]]
name = "click"
version = "8.3.1"
version = "8.3.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
{ url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" },
]
[[package]]
@@ -164,55 +180,55 @@ wheels = [
[[package]]
name = "cryptography"
version = "46.0.5"
version = "46.0.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" }
sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" },
{ url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" },
{ url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" },
{ url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" },
{ url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" },
{ url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" },
{ url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" },
{ url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" },
{ url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" },
{ url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" },
{ url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" },
{ url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" },
{ url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" },
{ url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" },
{ url = "https://files.pythonhosted.org/packages/00/13/3d278bfa7a15a96b9dc22db5a12ad1e48a9eb3d40e1827ef66a5df75d0d0/cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2", size = 7119287, upload-time = "2026-02-10T19:17:33.801Z" },
{ url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" },
{ url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" },
{ url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" },
{ url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" },
{ url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" },
{ url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" },
{ url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" },
{ url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" },
{ url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" },
{ url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" },
{ url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" },
{ url = "https://files.pythonhosted.org/packages/86/ef/5d00ef966ddd71ac2e6951d278884a84a40ffbd88948ef0e294b214ae9e4/cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a", size = 3003637, upload-time = "2026-02-10T19:17:52.997Z" },
{ url = "https://files.pythonhosted.org/packages/b7/57/f3f4160123da6d098db78350fdfd9705057aad21de7388eacb2401dceab9/cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4", size = 3469487, upload-time = "2026-02-10T19:17:54.549Z" },
{ url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" },
{ url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" },
{ url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" },
{ url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" },
{ url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" },
{ url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" },
{ url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" },
{ url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" },
{ url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" },
{ url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" },
{ url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" },
{ url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" },
{ url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" },
{ url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" },
{ url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" },
{ url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" },
{ url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" },
{ url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" },
{ url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" },
{ url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" },
{ url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" },
{ url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" },
{ url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" },
{ url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" },
{ url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" },
{ url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" },
{ url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" },
{ url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" },
{ url = "https://files.pythonhosted.org/packages/7b/56/15619b210e689c5403bb0540e4cb7dbf11a6bf42e483b7644e471a2812b3/cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842", size = 7119671, upload-time = "2026-04-08T01:56:44Z" },
{ url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" },
{ url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" },
{ url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" },
{ url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" },
{ url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" },
{ url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" },
{ url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" },
{ url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" },
{ url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" },
{ url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" },
{ url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" },
{ url = "https://files.pythonhosted.org/packages/95/b6/3da51d48415bcb63b00dc17c2eff3a651b7c4fed484308d0f19b30e8cb2c/cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298", size = 3002227, upload-time = "2026-04-08T01:57:06.91Z" },
{ url = "https://files.pythonhosted.org/packages/32/a8/9f0e4ed57ec9cebe506e58db11ae472972ecb0c659e4d52bbaee80ca340a/cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb", size = 3475332, upload-time = "2026-04-08T01:57:08.807Z" },
{ url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" },
{ url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" },
{ url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" },
{ url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" },
{ url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" },
{ url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" },
{ url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" },
{ url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" },
{ url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" },
{ url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" },
{ url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" },
{ url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" },
{ url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" },
{ url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" },
]
[[package]]
@@ -226,29 +242,29 @@ wheels = [
[[package]]
name = "django"
version = "6.0.3"
version = "6.0.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "asgiref" },
{ name = "sqlparse" },
{ name = "tzdata", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/80/e1/894115c6bd70e2c8b66b0c40a3c367d83a5a48c034a4d904d31b62f7c53a/django-6.0.3.tar.gz", hash = "sha256:90be765ee756af8a6cbd6693e56452404b5ad15294f4d5e40c0a55a0f4870fe1", size = 10872701, upload-time = "2026-03-03T13:55:15.026Z" }
sdist = { url = "https://files.pythonhosted.org/packages/60/b9/4155091ad1788b38563bd77a7258c0834e8c12a7f56f6975deaf54f8b61d/django-6.0.4.tar.gz", hash = "sha256:8cfa2572b3f2768b2e84983cf3c4811877a01edb64e817986ec5d60751c113ac", size = 10907407, upload-time = "2026-04-07T13:55:44.961Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/72/b1/23f2556967c45e34d3d3cf032eb1bd3ef925ee458667fb99052a0b3ea3a6/django-6.0.3-py3-none-any.whl", hash = "sha256:2e5974441491ddb34c3f13d5e7a9f97b07ba03bf70234c0a9c68b79bbb235bc3", size = 8358527, upload-time = "2026-03-03T13:55:10.552Z" },
{ url = "https://files.pythonhosted.org/packages/e9/47/3d61d611609764aa71a37f7037b870e7bfb22937366974c4fd46cada7bab/django-6.0.4-py3-none-any.whl", hash = "sha256:14359c809fc16e8f81fd2b59d7d348e4d2d799da6840b10522b6edf7b8afc1da", size = 8368342, upload-time = "2026-04-07T13:55:37.999Z" },
]
[[package]]
name = "django-allauth"
version = "65.15.0"
version = "65.16.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "asgiref" },
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/84/c1/d3385f4c3169c1d6eea3c63aed0f36af51478c1d72e46db12bb1a08f8034/django_allauth-65.15.0.tar.gz", hash = "sha256:b404d48cf0c3ee14dacc834c541f30adedba2ff1c433980ecc494d6cb0b395a8", size = 2215709, upload-time = "2026-03-09T13:51:28.675Z" }
sdist = { url = "https://files.pythonhosted.org/packages/3d/df/357187dfff18c7783e4911827a6c69437e290d7259a32a99c23fcd85997f/django_allauth-65.16.1.tar.gz", hash = "sha256:4425ac3088541c4c54983e16e08f6e3eb9f438dc1b1009534fa51c8bb739ed31", size = 2232835, upload-time = "2026-04-17T18:53:59.475Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/75/b8/c8411339171bd8bc075c09ef190fb42195e9a2149e5c5026e094fe62fce0/django_allauth-65.15.0-py3-none-any.whl", hash = "sha256:ad9fc49c49a9368eaa5bb95456b76e2a4f377b3c6862ee8443507816578c098d", size = 2022994, upload-time = "2026-03-09T13:51:19.711Z" },
{ url = "https://files.pythonhosted.org/packages/ad/58/d95b6c3088d83697bfd93782ee57bc6a6462e41eb19121a947b8a015396a/django_allauth-65.16.1-py3-none-any.whl", hash = "sha256:e49df24056bf37c44e56aaad1e51f78994b7d175bc3476d65e8f8f58390a8ce8", size = 2051868, upload-time = "2026-04-17T18:54:12.032Z" },
]
[package.optional-dependencies]
@@ -337,15 +353,15 @@ wheels = [
[[package]]
name = "django-stubs-ext"
version = "5.2.9"
version = "6.0.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/55/03/9c2be939490d2282328db4611bc5956899f5ff7eabc3e88bd4b964a87373/django_stubs_ext-5.2.9.tar.gz", hash = "sha256:6db4054d1580657b979b7d391474719f1a978773e66c7070a5e246cd445a25a9", size = 6497, upload-time = "2026-01-20T23:58:59.462Z" }
sdist = { url = "https://files.pythonhosted.org/packages/fb/e6/5dcdaa785ec3eed5fc196c7e68fb7ad9d9fe6d5acccea4690e65f2546417/django_stubs_ext-6.0.3.tar.gz", hash = "sha256:3307d42132bc295d5744de6276bc5fdf6896efc70f891e21c0ae8bdf529d2762", size = 6663, upload-time = "2026-04-18T15:10:53.667Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9b/f7/0d5f7d7e76fe972d9f560f687fdc0cab4db9e1624fd90728ca29b4ed7a63/django_stubs_ext-5.2.9-py3-none-any.whl", hash = "sha256:230c51575551b0165be40177f0f6805f1e3ebf799b835c85f5d64c371ca6cf71", size = 9974, upload-time = "2026-01-20T23:58:58.438Z" },
{ url = "https://files.pythonhosted.org/packages/10/fa/0a3a05c29d6295dbd52fa3cb4047a95de11ba4f2696072d6f3f2c1e6f370/django_stubs_ext-6.0.3-py3-none-any.whl", hash = "sha256:9e4105955419ae310d7da9cfd808e039d4dae3092c628f021057bb4f2c237f8f", size = 10354, upload-time = "2026-04-18T15:10:52.395Z" },
]
[[package]]
@@ -413,14 +429,14 @@ wheels = [
[[package]]
name = "djangorestframework"
version = "3.16.1"
version = "3.17.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8a/95/5376fe618646fde6899b3cdc85fd959716bb67542e273a76a80d9f326f27/djangorestframework-3.16.1.tar.gz", hash = "sha256:166809528b1aced0a17dc66c24492af18049f2c9420dbd0be29422029cfc3ff7", size = 1089735, upload-time = "2025-08-06T17:50:53.251Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/d7/c016e69fac19ff8afdc89db9d31d9ae43ae031e4d1993b20aca179b8301a/djangorestframework-3.17.1.tar.gz", hash = "sha256:a6def5f447fe78ff853bff1d47a3c59bf38f5434b031780b351b0c73a62db1a5", size = 905742, upload-time = "2026-03-24T16:58:33.705Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b0/ce/bf8b9d3f415be4ac5588545b5fcdbbb841977db1c1d923f7568eeabe1689/djangorestframework-3.16.1-py3-none-any.whl", hash = "sha256:33a59f47fb9c85ede792cbf88bde71893bcda0667bc573f784649521f1102cec", size = 1080442, upload-time = "2025-08-06T17:50:50.667Z" },
{ url = "https://files.pythonhosted.org/packages/5a/e1/2c516bdc83652b1a60c6119366ac2c0607b479ed05cd6093f916ca8928f8/djangorestframework-3.17.1-py3-none-any.whl", hash = "sha256:c3c74dd3e83a5a3efc37b3c18d92bd6f86a6791c7b7d4dff62bb068500e76457", size = 898844, upload-time = "2026-03-24T16:58:31.845Z" },
]
[[package]]
@@ -451,12 +467,33 @@ wheels = [
]
[[package]]
name = "idna"
version = "3.11"
name = "gunicorn"
version = "25.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
dependencies = [
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c4/f4/e78fa054248fab913e2eab0332c6c2cb07421fca1ce56d8fe43b6aef57a4/gunicorn-25.3.0.tar.gz", hash = "sha256:f74e1b2f9f76f6cd1ca01198968bd2dd65830edc24b6e8e4d78de8320e2fe889", size = 634883, upload-time = "2026-03-27T00:00:26.092Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
{ url = "https://files.pythonhosted.org/packages/43/c8/8aaf447698c4d59aa853fd318eed300b5c9e44459f242ab8ead6c9c09792/gunicorn-25.3.0-py3-none-any.whl", hash = "sha256:cacea387dab08cd6776501621c295a904fe8e3b7aae9a1a3cbb26f4e7ed54660", size = 208403, upload-time = "2026-03-27T00:00:27.386Z" },
]
[[package]]
name = "h11"
version = "0.16.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
]
[[package]]
name = "idna"
version = "3.13"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" },
]
[[package]]
@@ -473,15 +510,15 @@ wheels = [
[[package]]
name = "jwcrypto"
version = "1.5.6"
version = "1.5.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cryptography" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e1/db/870e5d5fb311b0bcf049630b5ba3abca2d339fd5e13ba175b4c13b456d08/jwcrypto-1.5.6.tar.gz", hash = "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039", size = 87168, upload-time = "2024-03-06T19:58:31.831Z" }
sdist = { url = "https://files.pythonhosted.org/packages/8c/90/f065668004d22715c1940d6e88e4c3afc8ee16d5664e4478d2c8fd23a250/jwcrypto-1.5.7.tar.gz", hash = "sha256:70204d7cca406eda8c82352e3c41ba2d946610dafd19e54403f0a1f4f18633c6", size = 89535, upload-time = "2026-04-07T00:35:36.116Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cd/58/4a1880ea64032185e9ae9f63940c9327c6952d5584ea544a8f66972f2fda/jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789", size = 92520, upload-time = "2024-03-06T19:58:29.765Z" },
{ url = "https://files.pythonhosted.org/packages/72/24/fb7da4d6613de7001feaf540d4b5969c6b5a1c42839043b0196cb13aa057/jwcrypto-1.5.7-py3-none-any.whl", hash = "sha256:729463fefe28b6de5cf1ebfda3e94f1a1b41d2799148ef98a01cb9678ebe2bb0", size = 94799, upload-time = "2026-04-07T00:35:35.085Z" },
]
[[package]]
@@ -497,8 +534,11 @@ dependencies = [
{ name = "django-oauth-toolkit" },
{ name = "django-tailwind" },
{ name = "django-widget-tweaks" },
{ name = "gunicorn" },
{ name = "python-dotenv" },
{ name = "slippers" },
{ name = "uvicorn" },
{ name = "uvicorn-worker" },
{ name = "wagtail" },
{ name = "wagtail-color-panel" },
{ name = "wagtailmedia" },
@@ -514,8 +554,11 @@ requires-dist = [
{ name = "django-oauth-toolkit", specifier = ">=3.2.0" },
{ name = "django-tailwind", specifier = ">=4.4.2" },
{ name = "django-widget-tweaks", specifier = ">=1.5.1" },
{ name = "gunicorn", specifier = ">=25.3.0" },
{ name = "python-dotenv", specifier = ">=1.2.2" },
{ name = "slippers", specifier = ">=0.6.2" },
{ name = "uvicorn", specifier = ">=0.42.0" },
{ name = "uvicorn-worker", specifier = ">=0.4.0" },
{ name = "wagtail", specifier = "==7.3rc1" },
{ name = "wagtail-color-panel", specifier = ">=1.7.1" },
{ name = "wagtailmedia", specifier = ">=0.17.2" },
@@ -619,36 +662,45 @@ wheels = [
]
[[package]]
name = "pillow"
version = "12.1.1"
name = "packaging"
version = "26.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" }
sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" },
{ url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" },
{ url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" },
{ url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" },
{ url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" },
{ url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" },
{ url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" },
{ url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" },
{ url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" },
{ url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" },
{ url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" },
{ url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" },
{ url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" },
{ url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" },
{ url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" },
{ url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" },
{ url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" },
{ url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" },
{ url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" },
{ url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" },
{ url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" },
{ url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" },
{ url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" },
{ url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" },
{ url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" },
{ url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" },
]
[[package]]
name = "pillow"
version = "12.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" },
{ url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" },
{ url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" },
{ url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" },
{ url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" },
{ url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" },
{ url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" },
{ url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" },
{ url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" },
{ url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" },
{ url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" },
{ url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" },
{ url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" },
{ url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" },
{ url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" },
{ url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" },
{ url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" },
{ url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" },
{ url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" },
{ url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" },
{ url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" },
{ url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" },
{ url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" },
{ url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" },
{ url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" },
]
[[package]]
@@ -687,20 +739,20 @@ wheels = [
[[package]]
name = "pygments"
version = "2.19.2"
version = "2.20.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
]
[[package]]
name = "pyjwt"
version = "2.11.0"
version = "2.12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" },
{ url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" },
]
[package.optional-dependencies]
@@ -778,7 +830,7 @@ wheels = [
[[package]]
name = "requests"
version = "2.32.5"
version = "2.33.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
@@ -786,22 +838,22 @@ dependencies = [
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
{ url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" },
]
[[package]]
name = "rich"
version = "14.3.3"
version = "15.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" },
{ url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },
]
[[package]]
@@ -815,7 +867,7 @@ wheels = [
[[package]]
name = "slippers"
version = "0.6.2"
version = "0.6.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
@@ -823,9 +875,9 @@ dependencies = [
{ name = "typeguard" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a7/c6/6af4500f31e53e9f782d264cb715f1a8cc9ac4248fba9fe2fe172822ea79/slippers-0.6.2.tar.gz", hash = "sha256:4cb555b8822ba0d404e5405723f5d723994022c29046008ee917081031bc0cf1", size = 59499, upload-time = "2023-08-01T05:04:18.964Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/3c/979ab01ee6515fb3d6f10cfc10ef2e6219817828b972c54d49f6ddd9ddd2/slippers-0.6.3.tar.gz", hash = "sha256:8602f462da79b707d25ec050fb51b6cbe5fb225b3c7faf40d1f02876d970e9f0", size = 59793, upload-time = "2026-03-30T04:51:59.436Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d5/8c/1fe2803870861f9a7f0cf502e4d1361dbb3685997118f4f76fd83ec9272f/slippers-0.6.2-py3-none-any.whl", hash = "sha256:739e05f85354becbf0a65daab831eea62557d89e7512042209ab629af4378bca", size = 61359, upload-time = "2023-08-01T05:04:17.378Z" },
{ url = "https://files.pythonhosted.org/packages/9c/b9/76c4b0535d26089d0d01f5c9cb3529b121babd1abf605e6c7e12acedb09b/slippers-0.6.3-py3-none-any.whl", hash = "sha256:cddd92cb998fb48472f863a811566f5a187b392f65ae4b0df81008b56dff214c", size = 62528, upload-time = "2026-03-30T04:51:58.227Z" },
]
[[package]]
@@ -884,11 +936,11 @@ wheels = [
[[package]]
name = "tzdata"
version = "2025.3"
version = "2026.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" }
sdist = { url = "https://files.pythonhosted.org/packages/19/f5/cd531b2d15a671a40c0f66cf06bc3570a12cd56eef98960068ebbad1bf5a/tzdata-2026.1.tar.gz", hash = "sha256:67658a1903c75917309e753fdc349ac0efd8c27db7a0cb406a25be4840f87f98", size = 197639, upload-time = "2026-04-03T11:25:22.002Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" },
{ url = "https://files.pythonhosted.org/packages/b0/70/d460bd685a170790ec89317e9bd33047988e4bce507b831f5db771e142de/tzdata-2026.1-py2.py3-none-any.whl", hash = "sha256:4b1d2be7ac37ceafd7327b961aa3a54e467efbdb563a23655fbfe0d39cfc42a9", size = 348952, upload-time = "2026-04-03T11:25:20.313Z" },
]
[[package]]
@@ -900,6 +952,32 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
]
[[package]]
name = "uvicorn"
version = "0.45.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/62b0d9a2cfc8b4de6771322dae30f2db76c66dae9ec32e94e176a44ad563/uvicorn-0.45.0.tar.gz", hash = "sha256:3fe650df136c5bd2b9b06efc5980636344a2fbb840e9ddd86437d53144fa335d", size = 87818, upload-time = "2026-04-21T10:43:46.815Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c1/88/d0f7512465b166a4e931ccf7e77792be60fb88466a43964c7566cbaff752/uvicorn-0.45.0-py3-none-any.whl", hash = "sha256:2db26f588131aeec7439de00f2dd52d5f210710c1f01e407a52c90b880d1fd4f", size = 69838, upload-time = "2026-04-21T10:43:45.029Z" },
]
[[package]]
name = "uvicorn-worker"
version = "0.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "gunicorn" },
{ name = "uvicorn" },
]
sdist = { url = "https://files.pythonhosted.org/packages/80/59/9101b9c0680fd80e9d26c07deb822a5d18a324339fcf9cd017885ee808ad/uvicorn_worker-0.4.0.tar.gz", hash = "sha256:8ee5306070d8f38dce124adce488c3c0b50f20cf0c0222b12c66188da7214493", size = 9361, upload-time = "2025-09-20T10:47:01.218Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/90/25/09cd7a90c8bb7fb693be0d6704fccd5f9778d5513214b7a01cc4a94ff314/uvicorn_worker-0.4.0-py3-none-any.whl", hash = "sha256:e2ed952cef976f5e9e429d7269640bbcafbd36c80aa80f1003c8c77a6797abde", size = 5364, upload-time = "2025-09-20T10:46:59.776Z" },
]
[[package]]
name = "wagtail"
version = "7.3rc1"
@@ -931,14 +1009,14 @@ wheels = [
[[package]]
name = "wagtail-color-panel"
version = "1.7.1"
version = "1.8.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "wagtail" },
]
sdist = { url = "https://files.pythonhosted.org/packages/58/bd/043c8cfc6eae14303b555782add80148db0596441e8770723cf17e664d4d/wagtail_color_panel-1.7.1.tar.gz", hash = "sha256:efc42058a49c39bea6a61523b230c71cf31e5aeaa269281c53146eeec02fc509", size = 10315, upload-time = "2025-11-23T06:07:53.765Z" }
sdist = { url = "https://files.pythonhosted.org/packages/9e/44/bd3e5a9ee9bc9b72153e0a227aae5b7e181d2ce4e601ff9b01c1074fbbb5/wagtail_color_panel-1.8.1.tar.gz", hash = "sha256:aca26f8b7178ff596aa2eac8982167396bbbc222fadbae4b6b1f18291b33473a", size = 10776, upload-time = "2026-04-12T19:16:14.464Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/db/1d/d004a5b57c8b1db6edee953e3222436c050dff2667320ca345964551640a/wagtail_color_panel-1.7.1-py3-none-any.whl", hash = "sha256:77ee02a6dd08b21e8806bb3e8c7dc01ad679dbeef864ad915fec32f5e8bcd574", size = 9383, upload-time = "2025-11-23T06:07:52.733Z" },
{ url = "https://files.pythonhosted.org/packages/6d/bb/86ee676a4613c0dade91685fb3402766de4c20874ad6f18d91807d9813cc/wagtail_color_panel-1.8.1-py3-none-any.whl", hash = "sha256:6e8aaeceac3618a9b69370b5097fa4d3c2db19b31bff95b7726343660793e55e", size = 9256, upload-time = "2026-04-12T19:16:13.68Z" },
]
[[package]]