├── public └── .keep ├── assets ├── js │ └── app.js ├── .yarnrc ├── css │ └── app.css ├── tailwind.config.js ├── static │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── images │ │ └── django.png │ ├── mstile-150x150.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── robots.txt │ ├── browserconfig.xml │ ├── site.webmanifest │ ├── maintenance.html │ ├── 502.html │ └── safari-pinned-tab.svg ├── package.json ├── esbuild.config.mjs └── yarn.lock ├── src ├── __init__.py ├── up │ ├── __init__.py │ ├── apps.py │ ├── urls.py │ ├── views.py │ └── tests.py ├── pages │ ├── __init__.py │ ├── apps.py │ ├── urls.py │ ├── tests.py │ ├── views.py │ └── templates │ │ └── pages │ │ └── home.html ├── config │ ├── celery.py │ ├── __init__.py │ ├── asgi.py │ ├── wsgi.py │ ├── gunicorn.py │ ├── urls.py │ └── settings.py ├── manage.py └── templates │ └── layouts │ └── index.html ├── public_collected └── .keep ├── .hadolint.yaml ├── .github ├── FUNDING.yml ├── docs │ └── screenshot.jpg └── workflows │ └── ci.yml ├── .editorconfig ├── bin ├── docker-entrypoint-web ├── uv-install └── rename-project ├── .dockerignore ├── pyproject.toml ├── LICENSE ├── compose.yaml ├── Dockerfile ├── .gitignore ├── .env.example ├── run ├── CHANGELOG.md ├── README.md └── uv.lock /public/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/js/app.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/up/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public_collected/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/.yarnrc: -------------------------------------------------------------------------------- 1 | --modules-folder /node_modules 2 | -------------------------------------------------------------------------------- /assets/css/app.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss" source("/app"); 2 | -------------------------------------------------------------------------------- /.hadolint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | failure-threshold: "style" 3 | ignored: 4 | - "DL3008" 5 | -------------------------------------------------------------------------------- /assets/tailwind.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | github: "nickjj" 4 | custom: ["https://www.paypal.me/nickjanetakis"] 5 | -------------------------------------------------------------------------------- /src/up/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UpConfig(AppConfig): 5 | name = "up" 6 | -------------------------------------------------------------------------------- /.github/docs/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/HEAD/.github/docs/screenshot.jpg -------------------------------------------------------------------------------- /assets/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/HEAD/assets/static/favicon.ico -------------------------------------------------------------------------------- /src/pages/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PagesConfig(AppConfig): 5 | name = "pages" 6 | -------------------------------------------------------------------------------- /assets/static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/HEAD/assets/static/favicon-16x16.png -------------------------------------------------------------------------------- /assets/static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/HEAD/assets/static/favicon-32x32.png -------------------------------------------------------------------------------- /assets/static/images/django.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/HEAD/assets/static/images/django.png -------------------------------------------------------------------------------- /assets/static/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/HEAD/assets/static/mstile-150x150.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [[sh]] 2 | indent_style = space 3 | indent_size = 2 4 | 5 | [[bash]] 6 | indent_style = space 7 | indent_size = 2 8 | -------------------------------------------------------------------------------- /assets/static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/HEAD/assets/static/apple-touch-icon.png -------------------------------------------------------------------------------- /assets/static/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/HEAD/assets/static/android-chrome-192x192.png -------------------------------------------------------------------------------- /assets/static/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/HEAD/assets/static/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/pages/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from pages import views 4 | 5 | urlpatterns = [ 6 | path("", views.home, name="home"), 7 | ] 8 | -------------------------------------------------------------------------------- /src/up/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from up import views 4 | 5 | urlpatterns = [ 6 | path("", views.index, name="index"), 7 | path("databases", views.databases, name="databases"), 8 | ] 9 | -------------------------------------------------------------------------------- /assets/static/robots.txt: -------------------------------------------------------------------------------- 1 | # TODO: This will block all robots from crawling your site. You probably don't 2 | # want this set in production, but could be useful for a test / staging server. 3 | User-agent: * 4 | Disallow: / 5 | -------------------------------------------------------------------------------- /bin/docker-entrypoint-web: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # Always keep this here as it ensures your latest built assets make their way 6 | # into your volume persisted public directory. 7 | cp -r /public_collected /app 8 | 9 | exec "$@" 10 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | .ruff_cache 3 | .pytest_cache/ 4 | __pycache__/ 5 | assets/node_modules/ 6 | public/ 7 | public_collected/ 8 | 9 | .coverage 10 | .dockerignore 11 | .env* 12 | !.env.example 13 | celerybeat-schedule 14 | docker-compose.override.yml 15 | -------------------------------------------------------------------------------- /src/config/celery.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from celery import Celery 4 | 5 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 6 | 7 | app = Celery("hello") 8 | app.config_from_object("django.conf:settings", namespace="CELERY") 9 | app.autodiscover_tasks() 10 | -------------------------------------------------------------------------------- /assets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello", 3 | "private": true, 4 | "dependencies": { 5 | "esbuild": "0.27.1", 6 | "esbuild-copy-static-files": "0.1.0", 7 | "tailwindcss": "4.1.17", 8 | "@tailwindcss/cli": "4.1.17", 9 | "@tailwindcss/postcss": "4.1.17" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/pages/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | 4 | class ViewTests(TestCase): 5 | def test_home_page(self): 6 | """Home page should respond with a success 200.""" 7 | response = self.client.get("/", follow=True) 8 | self.assertEqual(response.status_code, 200) 9 | -------------------------------------------------------------------------------- /assets/static/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/config/__init__.py: -------------------------------------------------------------------------------- 1 | # This will make sure the app is always imported when Django starts so that 2 | # shared_task will use this app. This is taken from Celery's docs at: 3 | # https://docs.celeryproject.org/en/stable/django/first-steps-with-django.html 4 | from config.celery import app as celery_app 5 | 6 | __all__ = ("celery_app",) 7 | -------------------------------------------------------------------------------- /bin/uv-install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | 6 | # Ensure we always have a valid up to date lock file. 7 | if ! test -f uv.lock || ! uv lock --check 2>/dev/null; then 8 | uv lock 9 | fi 10 | 11 | # Otherwise, use the existing lock file exactly how it is defined. 12 | uv sync --frozen --no-install-project 13 | -------------------------------------------------------------------------------- /src/pages/views.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django import get_version 4 | from django.conf import settings 5 | from django.shortcuts import render 6 | 7 | 8 | def home(request): 9 | context = { 10 | "debug": settings.DEBUG, 11 | "django_ver": get_version(), 12 | "python_ver": os.environ["PYTHON_VERSION"], 13 | } 14 | 15 | return render(request, "pages/home.html", context) 16 | -------------------------------------------------------------------------------- /src/up/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import connection 3 | from django.http import HttpResponse 4 | from redis import Redis 5 | 6 | redis = Redis.from_url(settings.REDIS_URL) 7 | 8 | 9 | def index(request): 10 | return HttpResponse("") 11 | 12 | 13 | def databases(request): 14 | redis.ping() 15 | connection.ensure_connection() 16 | 17 | return HttpResponse("") 18 | -------------------------------------------------------------------------------- /src/config/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for hello project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/6.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /src/config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for hello project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/6.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /src/up/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | 4 | class ViewTests(TestCase): 5 | def test_up(self): 6 | """Up should respond with a success 200.""" 7 | response = self.client.get("/up/", follow=True) 8 | self.assertEqual(response.status_code, 200) 9 | 10 | def test_up_databases(self): 11 | """Up databases should respond with a success 200.""" 12 | response = self.client.get("/up/databases", follow=True) 13 | self.assertEqual(response.status_code, 200) 14 | -------------------------------------------------------------------------------- /assets/static/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "hello" 3 | version = "0.1.0" 4 | description = "An example Django app running in Docker." 5 | readme = "README.md" 6 | requires-python = ">=3.13" 7 | dependencies = [ 8 | "django==6.0", 9 | "celery==5.6.0", 10 | "django-debug-toolbar==6.1.0", 11 | "gunicorn==23.0.0", 12 | "psycopg==3.3.1", 13 | "redis==7.1.0", 14 | "ruff==0.14.8", 15 | "setuptools==80.9.0", 16 | "whitenoise==6.11.0", 17 | ] 18 | 19 | [tool.ruff] 20 | line-length = 79 21 | 22 | [tool.ruff.lint] 23 | extend-select = ["I", "SIM"] 24 | -------------------------------------------------------------------------------- /src/config/gunicorn.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import multiprocessing 4 | import os 5 | 6 | from distutils.util import strtobool 7 | 8 | bind = f"0.0.0.0:{os.getenv('PORT', '8000')}" 9 | accesslog = "-" 10 | access_log_format = ( 11 | "%(h)s %(l)s %(u)s %(t)s '%(r)s' %(s)s %(b)s '%(f)s' '%(a)s' in %(D)sµs" # noqa: E501 12 | ) 13 | 14 | workers = int(os.getenv("WEB_CONCURRENCY", multiprocessing.cpu_count() * 2)) 15 | threads = int(os.getenv("PYTHON_MAX_THREADS", 1)) 16 | 17 | reload = bool(strtobool(os.getenv("WEB_RELOAD", "false"))) 18 | 19 | timeout = int(os.getenv("WEB_TIMEOUT", 120)) 20 | -------------------------------------------------------------------------------- /src/pages/templates/pages/home.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/index.html" %} 2 | {% load static %} 3 | 4 | {% block title %}Docker + Django{% endblock %} 5 | 6 | {% block body %} 7 |
8 | Django logo 10 |
11 | 12 |

13 | 🐳 Learn the Docker fundamentals at: 14 | 15 | https://diveintodocker.com 16 | 17 |

18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /assets/esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import * as esbuild from 'esbuild' 2 | import copyStaticFiles from 'esbuild-copy-static-files' 3 | 4 | let minify = false 5 | let sourcemap = true 6 | let watch = true 7 | 8 | if (process.env.NODE_ENV === 'production') { 9 | minify = true 10 | sourcemap = false 11 | watch = false 12 | } 13 | 14 | const config = { 15 | entryPoints: ['./js/app.js'], 16 | outfile: '../public/js/app.js', 17 | bundle: true, 18 | minify: minify, 19 | sourcemap: sourcemap, 20 | plugins: [copyStaticFiles()], 21 | } 22 | 23 | if (watch) { 24 | let context = await esbuild.context({...config, logLevel: 'info'}) 25 | await context.watch() 26 | } else { 27 | esbuild.build(config) 28 | } 29 | -------------------------------------------------------------------------------- /src/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | 4 | import os 5 | import sys 6 | 7 | 8 | def main(): 9 | """Run administrative tasks.""" 10 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 11 | try: 12 | from django.core.management import execute_from_command_line 13 | except ImportError as exc: 14 | raise ImportError( 15 | "Couldn't import Django. Are you sure it's installed and " 16 | "available on your PYTHONPATH environment variable? Did you " 17 | "forget to activate a virtual environment?" 18 | ) from exc 19 | execute_from_command_line(sys.argv) 20 | 21 | 22 | if __name__ == "__main__": 23 | main() 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "*" 7 | push: 8 | branches: 9 | - "main" 10 | - "master" 11 | schedule: 12 | - cron: "30 12 * * *" 13 | 14 | jobs: 15 | test: 16 | runs-on: "ubuntu-22.04" 17 | 18 | steps: 19 | - uses: "actions/checkout@v4" 20 | 21 | - name: "Install CI dependencies" 22 | run: | 23 | ./run ci:install-deps 24 | 25 | - name: "Test" 26 | run: | 27 | # Remove volumes in CI to avoid permission errors due to UID / GID. 28 | sed -i "s|.:/app|/tmp:/tmp|g" .env* 29 | sed -i "s|.:/app|/tmp:/tmp|g" compose.yaml 30 | 31 | # Django requires static files to be collected in order to run its 32 | # test suite. That means we need to generate production assets from 33 | # esbuild. This line ensures NODE_ENV is set to production. 34 | sed -i "s|export NODE_ENV|#export NODE_ENV|g" .env* 35 | 36 | ./run ci:test 37 | -------------------------------------------------------------------------------- /assets/static/maintenance.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Down for Planned Maintenance 4 | 25 | 26 | 27 |

Down for Temporary Planned Maintenance

28 |

Reason: The server is being upgraded to make things faster for you.

29 |

If all goes well everything will be up and running by 2pm EST, please check back then.

30 |

P.S., don't worry, your data is safely backed up!

31 |
32 |

Status updates may be posted on Twitter at: @nickjanetakis

33 | 34 | 35 | -------------------------------------------------------------------------------- /src/config/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL configuration for hello project. 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/6.0/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | 18 | from django.conf import settings 19 | from django.contrib import admin 20 | from django.urls import include, path 21 | 22 | urlpatterns = [ 23 | path("up/", include("up.urls")), 24 | path("", include("pages.urls")), 25 | path("admin/", admin.site.urls), 26 | ] 27 | if not settings.TESTING: 28 | urlpatterns = [ 29 | *urlpatterns, 30 | path("__debug__/", include("debug_toolbar.urls")), 31 | ] 32 | -------------------------------------------------------------------------------- /assets/static/502.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Please Reload Your Browser 4 | 25 | 26 | 27 |

A New Version of This Site Was Just Released

28 |

It takes a few seconds to come back up on its own. Nice timing!

29 |

It should be fixed by the time you read this message. Try reloading your browser.

30 |

P.S., don't worry, your data is safely backed up!

31 |
32 |

Seeing this page for a while? Chances are automated tools have notified me of the issue and I'm working on it but if it doesn't self resolve soon check Twitter for updates at: @nickjanetakis

33 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Nick Janetakis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /assets/static/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/rename-project: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | APP_NAME="${1}" 6 | MODULE_NAME="${2}" 7 | 8 | FIND_APP_NAME="hello" 9 | FIND_MODULE_NAME="Hello" 10 | FIND_FRAMEWORK="django" 11 | 12 | if [ -z "${APP_NAME}" ] || [ -z "${MODULE_NAME}" ]; then 13 | echo "You must supply both an app and module name, example: ${0} myapp MyApp" 14 | exit 1 15 | fi 16 | 17 | if [ "${APP_NAME}" = "${FIND_APP_NAME}" ]; then 18 | echo "Your new app name must be different than the current app name" 19 | exit 1 20 | fi 21 | 22 | cat < 4 | 5 | 6 | {% block title %}{% endblock %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {# Generated with: https://realfavicongenerator.net/ #} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |
29 |
30 | 31 | 32 | 33 |

34 | Django {{ django_ver }} and Python {{ python_ver }} 35 |

36 |
37 |
38 |
39 | 40 | DEBUG = {{ debug }} 41 | 42 |
43 |
44 |
45 |
46 |
47 | 48 |
49 | {% block body %}{% endblock %} 50 |
51 | 52 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22.21.1-trixie-slim AS assets 2 | LABEL maintainer="Nick Janetakis " 3 | 4 | WORKDIR /app/assets 5 | 6 | ARG UID=1000 7 | ARG GID=1000 8 | 9 | RUN apt-get update \ 10 | && apt-get install -y --no-install-recommends build-essential \ 11 | && rm -rf /var/lib/apt/lists/* /usr/share/doc /usr/share/man \ 12 | && apt-get clean \ 13 | && groupmod -g "${GID}" node && usermod -u "${UID}" -g "${GID}" node \ 14 | && mkdir -p /node_modules && chown node:node -R /node_modules /app 15 | 16 | USER node 17 | 18 | COPY --chown=node:node assets/package.json assets/*yarn* ./ 19 | 20 | RUN yarn install && yarn cache clean 21 | 22 | ARG NODE_ENV="production" 23 | ENV NODE_ENV="${NODE_ENV}" \ 24 | PATH="${PATH}:/node_modules/.bin" \ 25 | USER="node" 26 | 27 | COPY --chown=node:node . .. 28 | 29 | RUN if [ "${NODE_ENV}" != "development" ]; then \ 30 | ../run yarn:build:js && ../run yarn:build:css; else mkdir -p /app/public; fi 31 | 32 | CMD ["bash"] 33 | 34 | ############################################################################### 35 | 36 | FROM python:3.14.1-slim-trixie AS app-build 37 | LABEL maintainer="Nick Janetakis " 38 | 39 | WORKDIR /app 40 | 41 | ARG UID=1000 42 | ARG GID=1000 43 | 44 | RUN apt-get update \ 45 | && apt-get install -y --no-install-recommends build-essential curl libpq-dev \ 46 | && rm -rf /var/lib/apt/lists/* /usr/share/doc /usr/share/man \ 47 | && apt-get clean \ 48 | && groupadd -g "${GID}" python \ 49 | && useradd --create-home --no-log-init -u "${UID}" -g "${GID}" python \ 50 | && chown python:python -R /app 51 | 52 | COPY --from=ghcr.io/astral-sh/uv:0.8.17 /uv /uvx /usr/local/bin/ 53 | 54 | USER python 55 | 56 | COPY --chown=python:python pyproject.toml uv.lock* ./ 57 | COPY --chown=python:python bin/ ./bin 58 | 59 | ENV PYTHONUNBUFFERED="true" \ 60 | PYTHONPATH="." \ 61 | UV_COMPILE_BYTECODE=1 \ 62 | UV_PROJECT_ENVIRONMENT="/home/python/.local" \ 63 | PATH="${PATH}:/home/python/.local/bin" \ 64 | USER="python" 65 | 66 | RUN chmod 0755 bin/* && bin/uv-install 67 | 68 | CMD ["bash"] 69 | 70 | ############################################################################### 71 | 72 | FROM python:3.14.1-slim-trixie AS app 73 | LABEL maintainer="Nick Janetakis " 74 | 75 | WORKDIR /app 76 | 77 | ARG UID=1000 78 | ARG GID=1000 79 | 80 | RUN apt-get update \ 81 | && apt-get install -y --no-install-recommends curl libpq-dev \ 82 | && rm -rf /var/lib/apt/lists/* /usr/share/doc /usr/share/man \ 83 | && apt-get clean \ 84 | && groupadd -g "${GID}" python \ 85 | && useradd --create-home --no-log-init -u "${UID}" -g "${GID}" python \ 86 | && mkdir -p /public_collected public \ 87 | && chown python:python -R /public_collected /app 88 | 89 | USER python 90 | 91 | ARG DEBUG="false" 92 | ENV DEBUG="${DEBUG}" \ 93 | PYTHONUNBUFFERED="true" \ 94 | PYTHONPATH="." \ 95 | UV_PROJECT_ENVIRONMENT="/home/python/.local" \ 96 | PATH="${PATH}:/home/python/.local/bin" \ 97 | USER="python" 98 | 99 | COPY --chown=python:python --from=assets /app/public /public 100 | COPY --chown=python:python --from=app-build /home/python/.local /home/python/.local 101 | COPY --from=app-build /usr/local/bin/uv /usr/local/bin/uvx /usr/local/bin/ 102 | COPY --chown=python:python . . 103 | 104 | WORKDIR /app/src 105 | 106 | RUN if [ "${DEBUG}" = "false" ]; then \ 107 | SECRET_KEY=dummyvalue python3 manage.py collectstatic --no-input; \ 108 | else mkdir -p /app/public_collected; fi 109 | 110 | ENTRYPOINT ["/app/bin/docker-entrypoint-web"] 111 | 112 | EXPOSE 8000 113 | 114 | CMD ["gunicorn", "-c", "python:config.gunicorn", "config.wsgi"] 115 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mostly created by https://www.gitignore.io 2 | 3 | 4 | ### App ####################################################################### 5 | 6 | public/* 7 | !public/.keep 8 | public_collected/* 9 | !public_collected/.keep 10 | 11 | .env* 12 | !.env.example 13 | docker-compose.override.yml 14 | 15 | 16 | ### Python #################################################################### 17 | 18 | # Byte-compiled / optimized / DLL files 19 | __pycache__/ 20 | *.py[cod] 21 | *$py.class 22 | 23 | # Distribution / packaging 24 | .Python 25 | build/ 26 | develop-eggs/ 27 | downloads/ 28 | eggs/ 29 | .eggs/ 30 | parts/ 31 | sdist/ 32 | var/ 33 | wheels/ 34 | pip-wheel-metadata/ 35 | share/python-wheels/ 36 | *.egg-info/ 37 | .installed.cfg 38 | *.egg 39 | MANIFEST 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .nox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *.cover 55 | *.py,cover 56 | .hypothesis/ 57 | .pytest_cache/ 58 | pytestdebug.log 59 | 60 | # Translations 61 | *.mo 62 | *.pot 63 | 64 | # Flask stuff 65 | instance/ 66 | .webassets-cache 67 | 68 | # Celery stuff 69 | celerybeat-schedule 70 | celerybeat.pid 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | doc/_build/ 75 | 76 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 77 | __pypackages__/ 78 | 79 | # mkdocs documentation 80 | /site 81 | 82 | # mypy 83 | .mypy_cache/ 84 | .dmypy.json 85 | dmypy.json 86 | 87 | # Pyre type checker 88 | .pyre/ 89 | 90 | # pytype static type analyzer 91 | .pytype/ 92 | 93 | # profiling data 94 | .prof 95 | 96 | 97 | ### Node ###################################################################### 98 | 99 | # Dependency directories 100 | assets/node_modules/ 101 | 102 | # Optional eslint cache 103 | .eslintcache 104 | 105 | 106 | ### OSX ####################################################################### 107 | 108 | # General 109 | .DS_Store 110 | .AppleDouble 111 | .LSOverride 112 | 113 | # Icon must end with two \r 114 | Icon 115 | 116 | 117 | # Thumbnails 118 | ._* 119 | 120 | # Files that might appear in the root of a volume 121 | .DocumentRevisions-V100 122 | .fseventsd 123 | .Spotlight-V100 124 | .TemporaryItems 125 | .Trashes 126 | .VolumeIcon.icns 127 | .com.apple.timemachine.donotpresent 128 | 129 | # Directories potentially created on remote AFP share 130 | .AppleDB 131 | .AppleDesktop 132 | Network Trash Folder 133 | Temporary Items 134 | .apdisk 135 | 136 | 137 | ### Vim ####################################################################### 138 | 139 | # Swap 140 | [._]*.s[a-v][a-z] 141 | !*.svg # comment out if you don't need vector files 142 | [._]*.sw[a-p] 143 | [._]s[a-rt-v][a-z] 144 | [._]ss[a-gi-z] 145 | [._]sw[a-p] 146 | 147 | # Session 148 | Session.vim 149 | Sessionx.vim 150 | 151 | # Temporary 152 | .netrwhist 153 | # Auto-generated tag files 154 | tags 155 | # Persistent undo 156 | [._]*.un~ 157 | 158 | 159 | ### VSCode #################################################################### 160 | 161 | .vscode/* 162 | !.vscode/settings.json 163 | !.vscode/tasks.json 164 | !.vscode/launch.json 165 | !.vscode/extensions.json 166 | *.code-workspace 167 | 168 | 169 | ### Emacs ##################################################################### 170 | 171 | # -*- mode: gitignore; -*- 172 | *~ 173 | \#*\# 174 | /.emacs.desktop 175 | /.emacs.desktop.lock 176 | *.elc 177 | auto-save-list 178 | tramp 179 | .\#* 180 | 181 | # Org-mode 182 | .org-id-locations 183 | *_archive 184 | 185 | # flymake-mode 186 | *_flymake.* 187 | 188 | # eshell files 189 | /eshell/history 190 | /eshell/lastdir 191 | 192 | # elpa packages 193 | /elpa/ 194 | 195 | # reftex files 196 | *.rel 197 | 198 | # AUCTeX auto folder 199 | /auto/ 200 | 201 | # cask packages 202 | .cask/ 203 | dist/ 204 | 205 | # Flycheck 206 | flycheck_*.el 207 | 208 | # server auth directory 209 | /server/ 210 | 211 | # projectiles files 212 | .projectile 213 | 214 | # directory configuration 215 | .dir-locals.el 216 | 217 | # network security 218 | /network-security.data 219 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Default values are optimized for production to avoid having to configure 2 | # much in production. 3 | # 4 | # However it should be easy to get going in development too. If you see an 5 | # uncommented option that means it's either mandatory to set or it's being 6 | # overwritten in development to make your life easier. 7 | 8 | # Enable BuildKit by default: 9 | # https://docs.docker.com/develop/develop-images/build_enhancements 10 | export DOCKER_BUILDKIT=1 11 | 12 | # Rather than use the directory name, let's control the name of the project. 13 | export COMPOSE_PROJECT_NAME=hellodjango 14 | 15 | # In development we want all services to start but in production you don't 16 | # need the asset watchers to run since assets get built into the image. 17 | # 18 | # You can even choose not to run postgres and redis in prod if you plan to use 19 | # managed cloud services. Everything "just works", even optional depends_on! 20 | #export COMPOSE_PROFILES=postgres,redis,web,worker 21 | export COMPOSE_PROFILES=postgres,redis,assets,web,worker 22 | 23 | # If you're running native Linux and your uid:gid isn't 1000:1000 you can set 24 | # these to match your values before you build your image. You can check what 25 | # your uid:gid is by running `id` from your terminal. 26 | #export UID=1000 27 | #export GID=1000 28 | 29 | # In development avoid writing out bytecode to __pycache__ directories. 30 | #PYTHONDONTWRITEBYTECODE= 31 | export PYTHONDONTWRITEBYTECODE=true 32 | 33 | # You should generate a random string of 50+ characters for this value in prod. 34 | # You can generate a secure secret by running: ./run secret 35 | export SECRET_KEY=insecure_key_for_dev 36 | 37 | # This should never be set to true in production but it should be enabled in dev. 38 | #export DEBUG=false 39 | export DEBUG=true 40 | 41 | # Which Node environment is running? This should be "development" or "production". 42 | #export NODE_ENV=production 43 | export NODE_ENV=development 44 | 45 | # A comma separated list of allowed hosts. In production this should be your 46 | # domain name, such as "example.com,www.example.com" or ".example.com" to 47 | # support both example.com and all sub-domains for your domain. 48 | # 49 | # This is being overwritten in development to support multiple Docker dev 50 | # environments where you might be connecting over a local network IP address 51 | # instead of localhost. You should not use "*" in production. 52 | #export ALLOWED_HOSTS=".localhost,127.0.0.1,[::1]" 53 | export ALLOWED_HOSTS="*" 54 | 55 | # The bind port for gunicorn. 56 | # 57 | # Be warned that if you change this value you'll need to change 8000 in both 58 | # your Dockerfile and in a few spots in compose.yaml due to the nature of 59 | # how this value can be set (Docker Compose doesn't support nested ENV vars). 60 | #export PORT=8000 61 | 62 | # How many workers and threads should your app use? WEB_CONCURRENCY defaults 63 | # to the server's CPU count * 2. That is a good starting point. 64 | #export WEB_CONCURRENCY= 65 | #export PYTHON_MAX_THREADS=1 66 | 67 | # Do you want code reloading to work with the gunicorn app server? 68 | #export WEB_RELOAD=false 69 | export WEB_RELOAD=true 70 | 71 | # Configure the timeout value in seconds for gunicorn. 72 | #export WEB_TIMEOUT=120 73 | 74 | # You'll always want to set POSTGRES_USER and POSTGRES_PASSWORD since the 75 | # postgres Docker image uses them for its default database user and password. 76 | export POSTGRES_USER=hello 77 | export POSTGRES_PASSWORD=password 78 | #export POSTGRES_DB=hello 79 | #export POSTGRES_HOST=postgres 80 | #export POSTGRES_PORT=5432 81 | 82 | # Connection string to Redis. This will be used for the cache back-end and for 83 | # Celery. You can always split up your Redis servers later if needed. 84 | #export REDIS_URL=redis://redis:6379/0 85 | 86 | # You can choose between DEBUG, INFO, WARNING, ERROR, CRITICAL or FATAL. 87 | # DEBUG tends to get noisy but it could be useful for troubleshooting. 88 | #export CELERY_LOG_LEVEL=info 89 | 90 | # Should Docker restart your containers if they go down in unexpected ways? 91 | #export DOCKER_RESTART_POLICY=unless-stopped 92 | export DOCKER_RESTART_POLICY=no 93 | 94 | # What health check test command do you want to run? In development, having it 95 | # curl your web server will result in a lot of log spam, so setting it to 96 | # /bin/true is an easy way to make the health check do basically nothing. 97 | #export DOCKER_WEB_HEALTHCHECK_TEST=curl localhost:8000/up 98 | export DOCKER_WEB_HEALTHCHECK_TEST=/bin/true 99 | 100 | # What ip:port should be published back to the Docker host for the app server? 101 | # 102 | # If you have a port conflict because something else is using 8000 then you 103 | # can either stop that process or change 8000 to be something else. 104 | # 105 | # Use the default in production to avoid having gunicorn directly accessible on 106 | # the internet since it'll very likely be behind nginx or a load balancer. 107 | # 108 | # This is being overwritten in dev to be compatible with more dev environments, 109 | # such as accessing your site on another local device (phone, tablet, etc.). 110 | export DOCKER_WEB_PORT_FORWARD=8000 111 | 112 | # What volume path should be used? In dev we want to volume mount everything 113 | # so that we can develop our code without rebuilding our Docker images. 114 | #export DOCKER_WEB_VOLUME=./public_collected:/app/public_collected 115 | export DOCKER_WEB_VOLUME=.:/app 116 | 117 | # What CPU and memory constraints will be added to your services? When left at 118 | # 0, they will happily use as much as needed. 119 | #export DOCKER_POSTGRES_CPUS=0 120 | #export DOCKER_POSTGRES_MEMORY=0 121 | #export DOCKER_REDIS_CPUS=0 122 | #export DOCKER_REDIS_MEMORY=0 123 | #export DOCKER_WEB_CPUS=0 124 | #export DOCKER_WEB_MEMORY=0 125 | #export DOCKER_WORKER_CPUS=0 126 | #export DOCKER_WORKER_MEMORY=0 127 | -------------------------------------------------------------------------------- /src/config/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for hello project. 3 | 4 | Generated by 'django-admin startproject' using Django 6.0. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/6.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/6.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | import socket 15 | import sys 16 | from pathlib import Path 17 | 18 | from distutils.util import strtobool 19 | 20 | # Build paths inside the project like this: BASE_DIR / "subdir". 21 | BASE_DIR = Path(__file__).resolve().parent.parent 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = os.environ["SECRET_KEY"] 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = bool(strtobool(os.getenv("DEBUG", "false"))) 28 | 29 | TESTING = "test" in sys.argv 30 | 31 | # https://docs.djangoproject.com/en/6.0/ref/settings/#std:setting-ALLOWED_HOSTS 32 | allowed_hosts = os.getenv("ALLOWED_HOSTS", ".localhost,127.0.0.1,[::1]") 33 | ALLOWED_HOSTS = list(map(str.strip, allowed_hosts.split(","))) 34 | 35 | # Application definitions 36 | INSTALLED_APPS = [ 37 | "pages.apps.PagesConfig", 38 | "django.contrib.admin", 39 | "django.contrib.auth", 40 | "django.contrib.contenttypes", 41 | "django.contrib.sessions", 42 | "django.contrib.messages", 43 | "django.contrib.staticfiles", 44 | ] 45 | 46 | MIDDLEWARE = [ 47 | "django.middleware.security.SecurityMiddleware", 48 | "whitenoise.middleware.WhiteNoiseMiddleware", 49 | "django.contrib.sessions.middleware.SessionMiddleware", 50 | "django.middleware.common.CommonMiddleware", 51 | "django.middleware.csrf.CsrfViewMiddleware", 52 | "django.contrib.auth.middleware.AuthenticationMiddleware", 53 | "django.contrib.messages.middleware.MessageMiddleware", 54 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 55 | ] 56 | 57 | if not TESTING: 58 | INSTALLED_APPS = [*INSTALLED_APPS, "debug_toolbar"] 59 | MIDDLEWARE = [ 60 | "debug_toolbar.middleware.DebugToolbarMiddleware", 61 | *MIDDLEWARE, 62 | ] 63 | 64 | ROOT_URLCONF = "config.urls" 65 | 66 | # Starting with Django 4.1+ we need to pick which template loaders to use 67 | # based on our environment since 4.1+ will cache templates by default. 68 | default_loaders = [ 69 | "django.template.loaders.filesystem.Loader", 70 | "django.template.loaders.app_directories.Loader", 71 | ] 72 | 73 | cached_loaders = [("django.template.loaders.cached.Loader", default_loaders)] 74 | 75 | TEMPLATES = [ 76 | { 77 | "BACKEND": "django.template.backends.django.DjangoTemplates", 78 | "DIRS": [os.path.join(BASE_DIR, "templates")], 79 | "OPTIONS": { 80 | "context_processors": [ 81 | "django.template.context_processors.debug", 82 | "django.template.context_processors.request", 83 | "django.contrib.auth.context_processors.auth", 84 | "django.contrib.messages.context_processors.messages", 85 | ], 86 | "loaders": default_loaders if DEBUG else cached_loaders, 87 | }, 88 | }, 89 | ] 90 | 91 | WSGI_APPLICATION = "config.wsgi.application" 92 | 93 | # Database 94 | # https://docs.djangoproject.com/en/6.0/ref/settings/#databases 95 | DATABASES = { 96 | "default": { 97 | "ENGINE": "django.db.backends.postgresql", 98 | "NAME": os.getenv("POSTGRES_DB", "hello"), 99 | "USER": os.getenv("POSTGRES_USER", "hello"), 100 | "PASSWORD": os.getenv("POSTGRES_PASSWORD", "password"), 101 | "HOST": os.getenv("POSTGRES_HOST", "postgres"), 102 | "PORT": os.getenv("POSTGRES_PORT", "5432"), 103 | } 104 | } 105 | 106 | # Password validation 107 | # https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators 108 | AUTH_PASSWORD_VALIDATORS = [ 109 | { 110 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", # noqa: E501 111 | }, 112 | { 113 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", # noqa: E501 114 | }, 115 | { 116 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", # noqa: E501 117 | }, 118 | { 119 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", # noqa: E501 120 | }, 121 | ] 122 | 123 | # Sessions 124 | # https://docs.djangoproject.com/en/6.0/ref/settings/#sessions 125 | SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies" 126 | 127 | # Redis 128 | REDIS_URL = os.getenv("REDIS_URL", "redis://redis:6379/0") 129 | 130 | # Caching 131 | # https://docs.djangoproject.com/en/6.0/topics/cache/ 132 | CACHES = { 133 | "default": { 134 | "BACKEND": "django.core.cache.backends.redis.RedisCache", 135 | "LOCATION": REDIS_URL, 136 | } 137 | } 138 | 139 | # Celery 140 | # https://docs.celeryproject.org/en/stable/userguide/configuration.html 141 | CELERY_BROKER_URL = REDIS_URL 142 | CELERY_RESULT_BACKEND = REDIS_URL 143 | 144 | # Internationalization 145 | # https://docs.djangoproject.com/en/6.0/topics/i18n/ 146 | LANGUAGE_CODE = "en-us" 147 | TIME_ZONE = "UTC" 148 | USE_I18N = True 149 | USE_L10N = True 150 | USE_TZ = True 151 | 152 | # Static files (CSS, JavaScript, Images) 153 | # https://docs.djangoproject.com/en/6.0/howto/static-files/ 154 | STATIC_URL = "/static/" 155 | STATICFILES_DIRS = ["/public", os.path.join(BASE_DIR, "..", "public")] 156 | STATIC_ROOT = "/public_collected" 157 | STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" 158 | 159 | # Django Debug Toolbar 160 | # https://django-debug-toolbar.readthedocs.io/ 161 | if DEBUG: 162 | # We need to configure an IP address to allow connections from, but in 163 | # Docker we can't use 127.0.0.1 since this runs in a container but we want 164 | # to access the toolbar from our browser outside of the container. 165 | hostname, _, ips = socket.gethostbyname_ex(socket.gethostname()) 166 | INTERNAL_IPS = [ip[: ip.rfind(".")] + ".1" for ip in ips] + [ 167 | "127.0.0.1", 168 | "10.0.2.2", 169 | ] 170 | -------------------------------------------------------------------------------- /run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | 6 | DC="${DC:-exec}" 7 | 8 | # Disable tty allocation for Docker Compose when none is available, such as 9 | # in CI or when you pipe something into the run script. 0 = stdin, 1 = stdout. 10 | TTY="${TTY:-}" 11 | if ! [ -t 0 ] || ! [ -t 1 ]; then 12 | TTY="-T" 13 | fi 14 | 15 | # ----------------------------------------------------------------------------- 16 | # Helper functions start with _ and aren't listed in this script's help menu. 17 | # ----------------------------------------------------------------------------- 18 | 19 | _dc() { 20 | # shellcheck disable=SC2086 21 | docker compose "${DC}" ${TTY} "${@}" 22 | } 23 | 24 | _dc_run() { 25 | DC="run" _dc --no-deps --rm "${@}" 26 | } 27 | 28 | # ----------------------------------------------------------------------------- 29 | 30 | cmd() { 31 | # Run any command you want in the web container 32 | _dc web "${@}" 33 | } 34 | 35 | manage() { 36 | # Run any manage.py commands 37 | 38 | # We need to collectstatic before we run our tests. 39 | if [ "${1-''}" == "test" ]; then 40 | cmd python3 manage.py collectstatic --no-input 41 | fi 42 | 43 | cmd python3 manage.py "${@}" 44 | } 45 | 46 | lint:dockerfile() { 47 | # Lint Dockerfile 48 | docker container run --rm -i \ 49 | -v "${PWD}/.hadolint.yaml:/.config/hadolint.yaml" \ 50 | hadolint/hadolint hadolint "${@}" - /dev/null 2>&1; then 58 | local cmd=(docker container run --rm -i -v "${PWD}:/mnt" koalaman/shellcheck:stable) 59 | fi 60 | 61 | find . -type f \ 62 | ! -path "./.git/*" \ 63 | ! -path "./.ruff_cache/*" \ 64 | ! -path "./.pytest_cache/*" \ 65 | ! -path "./assets/*" \ 66 | ! -path "./public/*" \ 67 | ! -path "./public_collected/*" \ 68 | -exec grep --quiet '^#!.*sh' {} \; -exec "${cmd[@]}" {} + 69 | } 70 | 71 | lint() { 72 | # Lint Python code 73 | cmd ruff check "${@}" 74 | } 75 | 76 | format:shell() { 77 | # Format shell scripts 78 | local cmd=(shfmt) 79 | 80 | if ! command -v shfmt >/dev/null 2>&1; then 81 | local cmd=(docker container run --rm -i -v "${PWD}:/mnt" -u "$(id -u):$(id -g)" -w /mnt mvdan/shfmt:v3) 82 | fi 83 | 84 | local maybe_write=("--write") 85 | 86 | for arg in "${@}"; do 87 | if [ "${arg}" == "-d" ] || [ "${arg}" == "--diff" ]; then 88 | unset "maybe_write[0]" 89 | fi 90 | done 91 | 92 | "${cmd[@]}" "${maybe_write[@]}" "${@}" . 93 | } 94 | 95 | format() { 96 | # Format Python code 97 | cmd ruff check --fix 98 | cmd ruff format "${@}" 99 | } 100 | 101 | quality() { 102 | # Perform all code quality commands together 103 | lint:dockerfile 104 | lint:shell 105 | lint 106 | 107 | format:shell 108 | format 109 | } 110 | 111 | secret() { 112 | # Generate a random secret that can be used for your SECRET_KEY and more 113 | cmd python3 -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())" 114 | } 115 | 116 | shell() { 117 | # Start a shell session in the web container 118 | cmd bash "${@}" 119 | } 120 | 121 | psql() { 122 | # Connect to PostgreSQL 123 | # shellcheck disable=SC1091 124 | . .env 125 | _dc postgres psql -U "${POSTGRES_USER}" "${@}" 126 | } 127 | 128 | redis-cli() { 129 | # Connect to Redis 130 | _dc redis redis-cli "${@}" 131 | } 132 | 133 | deps:install() { 134 | # Install back-end and / or front-end dependencies 135 | local no_build="${1:-}" 136 | 137 | [ -z "${no_build}" ] && docker compose down && docker compose build 138 | 139 | _dc_run js yarn install 140 | _dc_run web bash -c "cd .. && bin/uv-install" 141 | } 142 | 143 | uv() { 144 | # Run any uv commands 145 | cmd uv "${@}" 146 | } 147 | 148 | uv:outdated() { 149 | # List any installed packages that are outdated 150 | _dc_run web uv tree --outdated --depth 1 "${@}" 151 | } 152 | 153 | yarn() { 154 | # Run any yarn commands 155 | _dc js yarn "${@}" 156 | } 157 | 158 | yarn:outdated() { 159 | # List any installed packages that are outdated 160 | _dc_run js yarn outdated 161 | } 162 | 163 | yarn:build:js() { 164 | # Build JS assets, this is meant to be run from within the assets container 165 | mkdir -p ../public/js 166 | node esbuild.config.mjs 167 | } 168 | 169 | yarn:build:css() { 170 | # Build CSS assets, this is meant to be run from within the assets container 171 | local args=() 172 | 173 | if [ "${NODE_ENV:-}" == "production" ]; then 174 | args=(--minify) 175 | else 176 | args=(--watch) 177 | fi 178 | 179 | mkdir -p ../public/css 180 | DEBUG=0 tailwindcss -i css/app.css -o ../public/css/app.css "${args[@]}" 181 | } 182 | 183 | clean() { 184 | # Remove cache and other machine generates files 185 | rm -rf public/*.* public/admin public/js public/css public/images public/fonts \ 186 | public_collected/*.* public_collected/admin public_collected/js \ 187 | public_collected/css public_collected/images public_collected/fonts \ 188 | .ruff_cache/ .pytest_cache/ .coverage celerybeat-schedule 189 | 190 | touch public/.keep public_collected/.keep 191 | } 192 | 193 | ci:install-deps() { 194 | # Install Continuous Integration (CI) dependencies 195 | sudo apt-get install -y curl 196 | sudo curl \ 197 | -L https://raw.githubusercontent.com/nickjj/wait-until/v0.2.0/wait-until \ 198 | -o /usr/local/bin/wait-until && sudo chmod +x /usr/local/bin/wait-until 199 | } 200 | 201 | ci:test() { 202 | # Execute Continuous Integration (CI) pipeline 203 | lint:dockerfile "${@}" 204 | lint:shell 205 | format:shell --diff 206 | 207 | cp --no-clobber .env.example .env 208 | 209 | docker compose build 210 | docker compose up -d 211 | 212 | # shellcheck disable=SC1091 213 | . .env 214 | wait-until "docker compose exec -T \ 215 | -e PGPASSWORD=${POSTGRES_PASSWORD} postgres \ 216 | psql -U ${POSTGRES_USER} ${POSTGRES_USER} -c 'SELECT 1'" 217 | 218 | docker compose logs 219 | 220 | lint "${@}" 221 | format --check --diff 222 | manage migrate 223 | manage test 224 | } 225 | 226 | help() { 227 | printf "%s [args]\n\nTasks:\n" "${0}" 228 | 229 | compgen -A function | grep -v "^_" | cat -n 230 | 231 | printf "\nExtended help:\n Each task has comments for general usage\n" 232 | } 233 | 234 | # This idea is heavily inspired by: https://github.com/adriancooney/Taskfile 235 | TIMEFORMAT=$'\nTask completed in %3lR' 236 | time "${@:-help}" 237 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a 6 | Changelog](https://keepachangelog.com/en/1.0.0/). 7 | 8 | ## [Unreleased] 9 | 10 | ### Added 11 | 12 | - `setuptools` Python dependency since Docker recently removed this from the official Python image 13 | - `./run pip3 [...]` to run any Pip command 14 | - `./run yarn [...]` to run any Yarn command 15 | - `./run lint:shell` for linting shell scripts with ShellCheck 16 | - `./run format:shell` for formatting shell scripts with shfmt 17 | 18 | ### Changed 19 | 20 | - Replace `./run pip3:install` with `./run deps:install [--no-build]` to install any deps 21 | - Replace `./run yarn:install` with `./run deps:install [--no-build]` to install any deps 22 | - Allow overriding `$TTY` as an environment variable in the `run` script 23 | - Use `.hadolint.yaml` to configure Hadolint instead of inline flags 24 | - Replace Black, flake8 and isort with Ruff 25 | - Replace `pip3` with `uv` for Python package management (~10x speed boost!) 26 | - Refactor `Dockerfile` to use multi-stage app builds (~50% / 250MB image size reduction!) 27 | 28 | #### Languages and services 29 | 30 | - Update `Python` to `3.14.1` 31 | - Update `Node` to `22.21.1` 32 | - Update `Postgres` to `18.1` 33 | - Update `Redis` to `8.4.0` 34 | 35 | #### Back-end dependencies 36 | 37 | - Update `celery` to `5.6.0` 38 | - Update `django-debug-toolbar` to `6.1.0` 39 | - Update `django` to `6.0` 40 | - Update `gunicorn` to `23.0.0` 41 | - Update `psycopg` to `3.3.1` 42 | - Update `redis` to `7.1.0` 43 | - Update `ruff` to `0.14.8` 44 | - Update `setuptools` to `80.9.0` 45 | - Update `whitenoise` to `6.11.0` 46 | 47 | #### Front-end dependencies 48 | 49 | - Update `@tailwindcss/cli` to `4.1.17` 50 | - Update `@tailwindcss/postcss` to `4.1.17` 51 | - Update `esbuild` to `0.27.1` 52 | - Update `tailwindcss` to `4.1.17` 53 | 54 | ### Fixed 55 | 56 | - Allow piping output into Docker Compose commands through the `run` script without TTY issues 57 | 58 | ## [0.11.0] - 2024-08-09 59 | 60 | ### Added 61 | 62 | - `django-debug-toolbar` package 63 | - `WEB_TIMEOUT` environment variable to configure gunicorn's timeout value (defaults to 120s) 64 | 65 | ### Changed 66 | 67 | - Convert `SECRET_KEY` into a required env var 68 | - Add `required: false` to `depends_on` in `docker-compose.yml` (requires Docker Compose v2.20.2+) 69 | - Adjust `WEB_CONCURRENCY` to default to N number of CPU cores instead of 1 in development 70 | - Rename `docker-compose.yml` to `compose.yaml` to stick to the official Docker Compose spec 71 | 72 | #### Languages and services 73 | 74 | - Update `Python` to `3.12.5` 75 | - Update `Node` to `20.6.1` 76 | - Update `Postgres` to `16.3` 77 | - Update `Redis` to `7.2.5` 78 | 79 | #### Back-end dependencies 80 | 81 | - Update `Django` to `5.1` 82 | - Update `black` to `24.8.0` 83 | - Update `celery` to `5.4.0` 84 | - Update `django-debug-toolbar` to `4.4.6` 85 | - Update `flake8` to `7.1.1` 86 | - Update `gunicorn` to `22.0.0` 87 | - Update `isort` to `5.13.2` 88 | - Update `psycopg` to `3.2.1` 89 | - Update `redis` to `5.0.8` 90 | - Update `whitenoise` to `6.7.0` 91 | 92 | #### Front-end dependencies 93 | 94 | - Update `autoprefixer` to `10.4.20` 95 | - Update `esbuild` to `0.23.0` 96 | - Update `postcss-import` to `16.1.0` 97 | - Update `postcss` to `8.4.41` 98 | - Update `tailwindcss` to `3.4.8` 99 | 100 | ## [0.10.0] - 2023-05-13 101 | 102 | ### Added 103 | 104 | - Ability to customize `UID` and `GID` if you're not using `1000:1000` (check the `.env.example` file) 105 | - Output `docker compose logs` in CI for easier debugging 106 | - `isort` to auto-sort Python imports and a new `./run isort` command 107 | - `./run quality` to run `lint`, `isort` and `format` in 1 command 108 | 109 | ### Changed 110 | 111 | - Reference `PORT` variable in the `docker-compose.yml` web service instead of hard coding `8000` 112 | - Adjust Hadolint to exit > 0 if any style warnings are present 113 | - Rename `esbuild.config.js` to `esbuild.config.mjs` and refactor config for esbuild 0.17+ 114 | 115 | #### Languages and services 116 | 117 | - Update `Python` to `3.11.3` 118 | - Update `Node` to `18.15.0` 119 | - Update `Postgres` to `15.3` 120 | - Update `Redis` to `7.0.11` 121 | 122 | #### Back-end dependencies 123 | 124 | - Replace `psycopg2` with `psycopg` (3.1.9) 125 | - Update `Django` to `4.2.1` 126 | - Update `flake8` to `6.0.0` 127 | - Update `isort` to `5.12.1` 128 | - Update `redis` to `4.5.5` 129 | - Update `whitenoise` to `6.4.0` 130 | 131 | #### Front-end dependencies 132 | 133 | - Update `autoprefixer` to `10.4.14` 134 | - Update `esbuild` to `0.17.19` 135 | - Update `postcss-import` to `15.1.0` 136 | - Update `postcss` to `8.4.23` 137 | - Update `tailwindcss` to `3.3.2` 138 | 139 | ### Removed 140 | 141 | - `set -o nounset` from `run` script since it's incompatible with Bash 3.2 (default on macOS) 142 | 143 | ### Fixed 144 | 145 | - Ensure Flake8, Black and isort all use 79 as the max line length 146 | - HTML templates not reloading in development by using `loaders` in `src/config/setting.py` 147 | 148 | ## [0.9.0] - 2022-09-09 149 | 150 | ### Added 151 | 152 | - `set -o nounset` to `run` script to exit if there's any undefined variables 153 | - Adjust `x-assets` to use a `stop_grace_period` of `0` for faster CTRL+c times in dev 154 | 155 | ### Changed 156 | 157 | - Switch Docker Compose `env_file` to `environment` for `postgres` to avoid needless recreates on `.env` changes 158 | - Replace override file with Docker Compose profiles for running specific services 159 | - Update Github Actions to use Ubuntu 22.04 160 | - Enable BuildKit by default in the `.env.example` file 161 | 162 | #### Languages and services 163 | 164 | - Update `Python` to `3.10.5` 165 | - Update `Node` to `16.15.1` 166 | - Update `PostgreSQL` to `14.5` 167 | - Update `Redis` to `7.0.4` 168 | 169 | #### Back-end dependencies 170 | 171 | - Update `Django` to `4.1.0` 172 | - Update `black` to `22.6.0` 173 | - Update `flake8` to `5.0.4` 174 | - Update `celery` to `5.2.7` 175 | - Update `redis` to `4.3.4` 176 | - Update `whitenoise` to `6.2.0` 177 | 178 | #### Front-end dependencies 179 | 180 | - Update `autoprefixer` to `10.4.8` 181 | - Update `esbuild` to `0.15.2` 182 | - Update `postcss` to `8.4.16` 183 | - Update `tailwindcss` to `3.1.8` 184 | 185 | ### Removed 186 | 187 | - Docker Compose `env_file` property for `redis` to avoid needless recreates on `.env` changes 188 | - Drop support for Docker Compose v1 (mainly to use profiles in an optimal way, it's worth it!) 189 | 190 | ## [0.8.0] - 2022-05-15 191 | 192 | ### Added 193 | 194 | - `yarn cache clean` after `yarn install` in `Dockerfile` (Hadolint warning) 195 | - `--no-cache-dir` flag to `pip3 install` command in `bin/pip3-install` (Hadolint warning) 196 | - [esbuild-copy-static-files](https://github.com/nickjj/esbuild-copy-static-files) plugin to drastically improve how static files are copied (check `assets/esbuild.config.js`) 197 | 198 | ### Changed 199 | 200 | - Update Bash shebang to use `#!/usr/bin/env bash` in `pip3-install` and `docker-entrypoint-web` 201 | - Refactor `/up/` endpoint into its own app and add `/up/databases` as a second URL 202 | 203 | #### Languages and services 204 | 205 | - Update `Python` to `3.10.4` 206 | - Update `Node` to `16.14.2` 207 | - Update `PostgreSQL` to `14.2` 208 | - Update `Redis` to `7.0.0` 209 | 210 | #### Back-end dependencies 211 | 212 | - Update `Django` to `4.0.4` 213 | - Update `black` to `22.3.0` 214 | - Update `celery` to `5.2.6` 215 | - Update `psycopg2` to `2.9.3` 216 | - Update `redis` to `4.3.1` 217 | - Update `whitenoise` to `6.1.0` 218 | 219 | #### Front-end dependencies 220 | 221 | - Update `autoprefixer` to `10.4.7` 222 | - Update `esbuild` to `0.14.39` 223 | - Update `postcss-import` to `14.1.0` 224 | - Update `postcss` to `8.4.13` 225 | - Update `tailwindcss` to `3.0.24` 226 | 227 | ### Fixed 228 | 229 | - `COPY --chown=node:node ../ ../` has been fixed to be `COPY --chown=node:node . ..` 230 | 231 | ## [0.7.0] - 2021-12-25 232 | 233 | ### Added 234 | 235 | - `/node_modules/.bin` to `$PATH` to easier access Yarn installed binaries 236 | - `yarn:build:js` and `yarn:build:css` run script commands 237 | 238 | ### Changed 239 | 240 | - Update `assets/tailwind.config.js` based on the new TailwindCSS v3 defaults 241 | - Replace all traces of Webpack with esbuild 242 | - Move JS and CSS from `assets/app` to `assets/js` and `assets/css` 243 | - Rename `webpack` Docker build stage to `assets` 244 | - Copy all files into the `assets` build stage to simplify things 245 | - Replace `cp -a` with `cp -r` in Docker entrypoint to make it easier to delete older assets 246 | - Rename `run hadolint` to `run lint:dockerfile` 247 | - Rename `run flake8` to `run lint` 248 | - Rename `run black` to `run format` 249 | - Rename `run bash` to `run shell` 250 | 251 | #### Languages and services 252 | 253 | - Update `Node` to `16.13.1` 254 | 255 | #### Front-end packages 256 | 257 | - Update `postcss` to `8.4.5` 258 | - Update `tailwindcss` to `3.0.7` 259 | 260 | ### Removed 261 | 262 | - Deleting old assets in the Docker entrypoint (it's best to handle this out of band in a cron job, etc.) 263 | 264 | ## [0.6.0] - 2021-12-07 265 | 266 | ### Added 267 | 268 | - Lint Dockerfile with 269 | - `redis` package to fulfill Django 4.x's Redis cache back-end requirements 270 | 271 | ### Changed 272 | 273 | - Update `assets/tailwind.config.js` based on the new TailwindCSS v3 defaults 274 | 275 | #### Languages and services 276 | 277 | - Update `Node` to `14.18.1` 278 | - Update `PostgreSQL` to `14.1` and switch to Debian Bullseye Slim 279 | - Update `Redis` to switch to Debian Bullseye Slim 280 | 281 | #### Back-end packages 282 | 283 | - Update `Django` to `4.0` 284 | - Update `celery` to `5.2.1` 285 | - Update `flake8` to `4.0.1` 286 | - Update `psycopg2` to `2.9.2` 287 | - Update `redis` to `4.0.2` 288 | 289 | #### Front-end packages 290 | 291 | - Update `@babel/core` to `7.16.0` 292 | - Update `@babel/preset-env` to `7.16.4` 293 | - Update `@babel/register` to `7.16.0` 294 | - Update `autoprefixer` to `10.4.0` 295 | - Update `babel-loader` to `8.2.3` 296 | - Update `copy-webpack-plugin` to `10.0.0` 297 | - Update `css-loader` to `6.5.1` 298 | - Update `css-minimizer-webpack-plugin` to `3.2.0` 299 | - Update `mini-css-extract-plugin` to `2.4.5` 300 | - Update `postcss-loader` to `6.2.1` 301 | - Update `postcss` to `8.4.3` 302 | - Update `tailwindcss` to `2.2.19` 303 | - Update `webpack-cli` to `4.9.1` 304 | - Update `webpack` to `5.64.4` 305 | 306 | ### Removed 307 | 308 | - `django-redis` package since Django 4.x supports using Redis as a cache back-end now 309 | 310 | ## [0.5.0] - 2021-10-10 311 | 312 | ### Changed 313 | 314 | #### Languages and services 315 | 316 | - Update `Python` to `3.10.0` and switch to Debian Bullseye Slim 317 | - Update `PostgreSQL` to `14.0` 318 | - Update `Redis` to `6.2.6` 319 | 320 | #### Back-end packages 321 | 322 | - Update `Django` to `3.2.8` 323 | - Update `celery` to `5.1.2` 324 | - Update `psycopg2` to `2.9.1` 325 | - Update `whitenoise` to `5.3.0` 326 | 327 | #### Front-end packages 328 | 329 | - Update `@babel/core` to `7.15.8` 330 | - Update `@babel/preset-env` to `7.15.8` 331 | - Update `@babel/register` to `7.15.3` 332 | - Update `autoprefixer` to `10.3.7` 333 | - Update `copy-webpack-plugin` to `9.0.1` 334 | - Update `css-loader` to `6.4.0` 335 | - Update `css-minimizer-webpack-plugin` to `3.1.1` 336 | - Update `mini-css-extract-plugin` to `2.4.2` 337 | - Update `postcss-loader` to `6.1.1` 338 | - Update `postcss` to `8.3.9` 339 | - Update `tailwindcss` to `2.2.16` 340 | - Update `webpack-cli` to `4.9.0` 341 | - Update `webpack` to `5.58.1` 342 | 343 | ## [0.4.0] - 2021-06-11 344 | 345 | ### Added 346 | 347 | - `bin/rename-project` script to assist with renaming the project 348 | - Use Black to format Python code 349 | - Set `PYTHONPATH="."` in the Dockerfile 350 | 351 | ### Changed 352 | 353 | - Rename `src/hello/` directory to `src/config/` to be more portable 354 | - Replace `APP_NAME` in `run` script with `POSTGRES_USER` for connecting to psql 355 | - Avoid using multi-line imports with commas or parenthesis 356 | - Update Python from `3.9.2` to `3.9.5` 357 | - Update PostgreSQL from `13.2` to `13.3` 358 | - Update Redis from `6.0.10` to `6.2.4` 359 | - Update Tailwind from `2.1.0` to `2.1.2` 360 | - Update Django from `3.2` to `3.2.4` 361 | - Update django-redis from `4.12.1` to `5.0.0` 362 | - Update Celery from `5.0.5` to `5.1.0` 363 | - Update flake8 from `3.9.0` to `3.9.2` 364 | - Update all Webpack related dependencies to their latest versions 365 | - Use the Docker Compose spec in `docker-compose.yml` (removes `version:` property) 366 | 367 | ### Fixed 368 | 369 | - Set an empty ENTRYPOINT for the worker to avoid race conditions when copying static files 370 | - Fix `run` script error for unbound variable in older versions of Bash on macOS 371 | - Potential issue on Mac M1s by adding `depends_on` to Webpack service 372 | 373 | ## [0.3.1] - 2021-04-06 374 | 375 | ### Fixed 376 | 377 | - Use `DEFAULT_AUTO_FIELD` in `settings.py` instead of the lowercase variant 378 | 379 | ## [0.3.0] - 2021-04-06 380 | 381 | ### Changed 382 | 383 | - Update Django to `3.2` 384 | - Update TailwindCSS to `2.1.0` and enable the JIT compiler 385 | 386 | ### Removed 387 | 388 | - Remove Webpack's cache since the JIT compiler is pretty speedy as is 389 | 390 | ## [0.2.0] - 2021-03-17 391 | 392 | ### Changed 393 | 394 | - Replace `##` comments with `#` in the `run` script 395 | - Switch `OptimizeCSSAssetsPlugin` with `CssMinimizerPlugin` for Webpack 5 396 | - Replace deprecated Webpack 5 `file-loader` with `asset/resource` 397 | - Update flake8 from `3.8.4` to `3.9.0` 398 | 399 | ### Removed 400 | 401 | - Remove unnecessary `mkdir` for the pip cache dir and chown'ing a few directories 402 | - Unused `webpack` import in Webpack config 403 | 404 | ### Fixed 405 | 406 | - Make sure `public_collected/.keep` is never removed 407 | - Code styling issues in the Webpack config (single quotes, semi-colons, etc.) 408 | 409 | ## [0.1.0] - 2021-02-24 410 | 411 | ### Added 412 | 413 | - Everything! 414 | 415 | [Unreleased]: https://github.com/nickjj/docker-django-example/compare/0.11.0...HEAD 416 | [0.11.0]: https://github.com/nickjj/docker-django-example/compare/0.10.0...0.11.0 417 | [0.10.0]: https://github.com/nickjj/docker-django-example/compare/0.9.0...0.10.0 418 | [0.9.0]: https://github.com/nickjj/docker-django-example/compare/0.8.0...0.9.0 419 | [0.8.0]: https://github.com/nickjj/docker-django-example/compare/0.7.0...0.8.0 420 | [0.7.0]: https://github.com/nickjj/docker-django-example/compare/0.6.0...0.7.0 421 | [0.6.0]: https://github.com/nickjj/docker-django-example/compare/0.5.0...0.6.0 422 | [0.5.0]: https://github.com/nickjj/docker-django-example/compare/0.4.0...0.5.0 423 | [0.4.0]: https://github.com/nickjj/docker-django-example/compare/0.3.1...0.4.0 424 | [0.3.1]: https://github.com/nickjj/docker-django-example/compare/0.3.0...0.3.1 425 | [0.3.0]: https://github.com/nickjj/docker-django-example/compare/0.2.0...0.3.0 426 | [0.2.0]: https://github.com/nickjj/docker-django-example/compare/0.1.0...0.2.0 427 | [0.1.0]: https://github.com/nickjj/docker-django-example/releases/tag/0.1.0 428 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🐳 An example Django + Docker app 2 | 3 | You could use this example app as a base for your new project or as a guide to 4 | Dockerize your existing Django app. 5 | 6 | The example app is minimal but it wires up a number of things you might use in 7 | a real world Django app, but at the same time it's not loaded up with a million 8 | personal opinions. 9 | 10 | For the Docker bits, everything included is an accumulation of [Docker best 11 | practices](https://nickjanetakis.com/blog/best-practices-around-production-ready-web-apps-with-docker-compose) 12 | based on building and deploying dozens of assorted Dockerized web apps since 13 | late 2014. 14 | 15 | **This app is using Django 6.0 and Python 3.14.1**. The screenshot shows 16 | `X.X.X` since they get updated regularly: 17 | 18 | [![Screenshot](.github/docs/screenshot.jpg)](https://github.com/nickjj/docker-django-example/blob/main/.github/docs/screenshot.jpg?raw=true) 19 | 20 | ## 🧾 Table of contents 21 | 22 | - [Tech stack](#tech-stack) 23 | - [Notable opinions and extensions](#notable-opinions-and-extensions) 24 | - [Running this app](#running-this-app) 25 | - [Files of interest](#files-of-interest) 26 | - [`.env`](#env) 27 | - [`run`](#run) 28 | - [Running a script to automate renaming the project](#running-a-script-to-automate-renaming-the-project) 29 | - [Updating dependencies](#updating-dependencies) 30 | - [See a way to improve something?](#see-a-way-to-improve-something) 31 | - [Additional resources](#additional-resources) 32 | - [Learn more about Docker and Django](#learn-more-about-docker-and-django) 33 | - [Deploy to production](#deploy-to-production) 34 | - [About the author](#about-the-author) 35 | 36 | ## 🧬 Tech stack 37 | 38 | If you don't like some of these choices that's no problem, you can swap them 39 | out for something else on your own. 40 | 41 | ### Back-end 42 | 43 | - [PostgreSQL](https://www.postgresql.org/) 44 | - [Redis](https://redis.io/) 45 | - [Celery](https://github.com/celery/celery) 46 | 47 | ### Front-end 48 | 49 | - [esbuild](https://esbuild.github.io/) 50 | - [TailwindCSS](https://tailwindcss.com/) 51 | - [Heroicons](https://heroicons.com/) 52 | 53 | #### But what about JavaScript?! 54 | 55 | Picking a JS library is a very app specific decision because it depends on 56 | which library you like and it also depends on if your app is going to be 57 | mostly Django templates with sprinkles of JS or an API back-end. 58 | 59 | This isn't an exhaustive list but here's a few reasonable choices depending on 60 | how you're building your app: 61 | 62 | - 63 | - 64 | - 65 | - 66 | - 67 | - 68 | 69 | On the bright side with esbuild being set up you can use any (or none) of these 70 | solutions very easily. You could follow a specific library's installation 71 | guides to get up and running in no time. 72 | 73 | Personally I'm going to be using Hotwire Turbo + Stimulus in most newer 74 | projects. 75 | 76 | ## 🍣 Notable opinions and extensions 77 | 78 | Django is an opinionated framework and I've added a few extra opinions based on 79 | having Dockerized and deployed a number of Django projects. Here's a few (but 80 | not all) note worthy additions and changes. 81 | 82 | - **Packages and extensions**: 83 | - *[gunicorn](https://gunicorn.org/)* for an app server in both development and production 84 | - *[whitenoise](https://github.com/evansd/whitenoise)* for serving static files 85 | - *[django-debug-toolbar](https://github.com/jazzband/django-debug-toolbar)* for displaying info about a request 86 | - **Linting and formatting**: 87 | - *[ruff](https://github.com/astral-sh/ruff)* is used to lint and format the code base 88 | - **Django apps**: 89 | - Add `pages` app to render a home page 90 | - Add `up` app to provide a few health check pages 91 | - **Config**: 92 | - Log to STDOUT so that Docker can consume and deal with log output 93 | - Extract a bunch of configuration settings into environment variables 94 | - Rename project directory from its custom name to `config/` 95 | - `src/config/settings.py` and the `.env` file handles configuration in all environments 96 | - **Front-end assets**: 97 | - `assets/` contains all your CSS, JS, images, fonts, etc. and is managed by esbuild 98 | - Custom `502.html` and `maintenance.html` pages 99 | - Generate favicons using modern best practices 100 | - **Django defaults that are changed**: 101 | - Use Redis as the default Cache back-end 102 | - Use signed cookies as the session back-end 103 | - `public/` is the static directory where Django will serve static files from 104 | - `public_collected/` is where `collectstatic` will write its files to 105 | 106 | Besides the Django app itself: 107 | 108 | - [uv](https://github.com/astral-sh/uv) is used for package management instead of `pip3` (builds on my machine are ~10x faster!) 109 | - Docker support has been added which would be any files having `*docker*` in 110 | its name 111 | - GitHub Actions have been set up 112 | 113 | ## 🚀 Running this app 114 | 115 | You'll need to have [Docker installed](https://docs.docker.com/get-docker/). 116 | It's available on Windows, macOS and most distros of Linux. If you're new to 117 | Docker and want to learn it in detail check out the [additional resources 118 | links](#learn-more-about-docker-and-django) near the bottom of this README. 119 | 120 | You'll also need to enable Docker Compose v2 support if you're using Docker 121 | Desktop. On native Linux without Docker Desktop you can [install it as a plugin 122 | to Docker](https://docs.docker.com/compose/install/linux/). It's been generally 123 | available for a while now and is stable. This project uses specific [Docker 124 | Compose v2 125 | features](https://nickjanetakis.com/blog/optional-depends-on-with-docker-compose-v2-20-2) 126 | that only work with Docker Compose v2 2.20.2+. 127 | 128 | If you're using Windows, it will be expected that you're following along inside 129 | of [WSL or WSL 130 | 2](https://nickjanetakis.com/blog/a-linux-dev-environment-on-windows-with-wsl-2-docker-desktop-and-more). 131 | That's because we're going to be running shell commands. You can always modify 132 | these commands for PowerShell if you want. 133 | 134 | #### Clone this repo anywhere you want and move into the directory: 135 | 136 | ```sh 137 | git clone https://github.com/nickjj/docker-django-example hellodjango 138 | cd hellodjango 139 | 140 | # Optionally checkout a specific tag, such as: git checkout 0.11.0 141 | ``` 142 | 143 | #### Copy an example .env file because the real one is git ignored: 144 | 145 | ```sh 146 | cp .env.example .env 147 | ``` 148 | 149 | #### Build everything: 150 | 151 | *The first time you run this it's going to take 5-10 minutes depending on your 152 | internet connection speed and computer's hardware specs. That's because it's 153 | going to download a few Docker images and build the Python + Yarn dependencies.* 154 | 155 | ```sh 156 | docker compose up --build 157 | ``` 158 | 159 | Now that everything is built and running we can treat it like any other Django 160 | app. 161 | 162 | Did you receive a `depends_on` "Additional property required is not allowed" 163 | error? Please update to at least Docker Compose v2.20.2+ or Docker Desktop 164 | 4.22.0+. 165 | 166 | Did you receive an error about a port being in use? Chances are it's because 167 | something on your machine is already running on port 8000. Check out the docs 168 | in the `.env` file for the `DOCKER_WEB_PORT_FORWARD` variable to fix this. 169 | 170 | Did you receive a permission denied error? Chances are you're running native 171 | Linux and your `uid:gid` aren't `1000:1000` (you can verify this by running 172 | `id`). Check out the docs in the `.env` file to customize the `UID` and `GID` 173 | variables to fix this. 174 | 175 | #### Setup the initial database: 176 | 177 | ```sh 178 | # You can run this from a 2nd terminal. 179 | ./run manage migrate 180 | ``` 181 | 182 | *We'll go over that `./run` script in a bit!* 183 | 184 | #### Check it out in a browser: 185 | 186 | Visit in your favorite browser. 187 | 188 | #### Linting the code base: 189 | 190 | ```sh 191 | # You should get no output (that means everything is operational). 192 | ./run lint 193 | ``` 194 | 195 | #### Formatting the code base: 196 | 197 | ```sh 198 | # You should see that everything is unchanged (it's all already formatted). 199 | ./run format 200 | ``` 201 | 202 | *There's also a `./run quality` command to run the above commands together.* 203 | 204 | #### Running the test suite: 205 | 206 | ```sh 207 | # You should see all passing tests. Warnings are typically ok. 208 | ./run manage test 209 | ``` 210 | 211 | #### Stopping everything: 212 | 213 | ```sh 214 | # Stop the containers and remove a few Docker related resources associated to this project. 215 | docker compose down 216 | ``` 217 | 218 | You can start things up again with `docker compose up` and unlike the first 219 | time it should only take seconds. 220 | 221 | ## 🔍 Files of interest 222 | 223 | I recommend checking out most files and searching the code base for `TODO:`, 224 | but please review the `.env` and `run` files before diving into the rest of the 225 | code and customizing it. Also, you should hold off on changing anything until 226 | we cover how to customize this example app's name with an automated script 227 | (coming up next in the docs). 228 | 229 | ### `.env` 230 | 231 | This file is ignored from version control so it will never be commit. There's a 232 | number of environment variables defined here that control certain options and 233 | behavior of the application. Everything is documented there. 234 | 235 | Feel free to add new variables as needed. This is where you should put all of 236 | your secrets as well as configuration that might change depending on your 237 | environment (specific dev boxes, CI, production, etc.). 238 | 239 | ### `run` 240 | 241 | You can run `./run` to get a list of commands and each command has 242 | documentation in the `run` file itself. 243 | 244 | It's a shell script that has a number of functions defined to help you interact 245 | with this project. It's basically a `Makefile` except with [less 246 | limitations](https://nickjanetakis.com/blog/replacing-make-with-a-shell-script-for-running-your-projects-tasks). 247 | For example as a shell script it allows us to pass any arguments to another 248 | program. 249 | 250 | This comes in handy to run various Docker commands because sometimes these 251 | commands can be a bit long to type. Feel free to add as many convenience 252 | functions as you want. This file's purpose is to make your experience better! 253 | 254 | *If you get tired of typing `./run` you can always create a shell alias with 255 | `alias run=./run` in your `~/.bash_aliases` or equivalent file. Then you'll be 256 | able to run `run` instead of `./run`.* 257 | 258 | ## ✨ Running a script to automate renaming the project 259 | 260 | The app is named `hello` right now but chances are your app will be a different 261 | name. Since the app is already created we'll need to do a find / replace on a 262 | few variants of the string "hello" and update a few Docker related resources. 263 | 264 | And by we I mean I created a zero dependency shell script that does all of the 265 | heavy lifting for you. All you have to do is run the script below. 266 | 267 | #### Run the rename-project script included in this repo: 268 | 269 | ```sh 270 | # The script takes 2 arguments. 271 | # 272 | # The first one is the lower case version of your app's name, such as myapp or 273 | # my_app depending on your preference. 274 | # 275 | # The second one is used for your app's module name. For example if you used 276 | # myapp or my_app for the first argument you would want to use MyApp here. 277 | bin/rename-project myapp MyApp 278 | ``` 279 | 280 | The [bin/rename-project 281 | script](https://github.com/nickjj/docker-django-example/blob/main/bin/rename-project) 282 | is going to: 283 | 284 | - Remove any Docker resources for your current project 285 | - Perform a number of find / replace actions 286 | - Optionally initialize a new git repo for you 287 | 288 | *Afterwards you can delete this script because its only purpose is to assist in 289 | helping you change this project's name without depending on any complicated 290 | project generator tools or 3rd party dependencies.* 291 | 292 | If you're not comfy running the script or it doesn't work for whatever reasons 293 | you can [check it 294 | out](https://github.com/nickjj/docker-django-example/blob/main/bin/rename-project) 295 | and perform the actions manually. It's mostly running a find / replace across 296 | files and then renaming a few directories and files. 297 | 298 | #### Start and setup the project: 299 | 300 | This won't take as long as before because Docker can re-use most things. We'll 301 | also need to setup our database since a new one will be created for us by 302 | Docker. 303 | 304 | ```sh 305 | docker compose up --build 306 | 307 | # Then in a 2nd terminal once it's up and ready. 308 | ./run manage migrate 309 | ``` 310 | 311 | #### Sanity check to make sure the tests still pass: 312 | 313 | It's always a good idea to make sure things are in a working state before 314 | adding custom changes. 315 | 316 | ```sh 317 | # You can run this from the same terminal as before. 318 | ./run quality 319 | ./run manage test 320 | ``` 321 | 322 | If everything passes now you can optionally `git add -A && git commit -m 323 | "Initial commit"` and start customizing your app. Alternatively you can wait 324 | until you develop more of your app before committing anything. It's up to you! 325 | 326 | #### Tying up a few loose ends: 327 | 328 | You'll probably want to create a fresh `CHANGELOG.md` file for your project. I 329 | like following the style guide at but feel free 330 | to use whichever style you prefer. 331 | 332 | Since this project is MIT licensed you should keep my name and email address in 333 | the `LICENSE` file to adhere to that license's agreement, but you can also add 334 | your name and email on a new line. 335 | 336 | If you happen to base your app off this example app or write about any of the 337 | code in this project it would be rad if you could credit this repo by linking 338 | to it. If you want to reference me directly please link to my site at 339 | . You don't have to do this, but it would be very 340 | much appreciated! 341 | 342 | ## 🛠 Updating dependencies 343 | 344 | You can run `./run uv:outdated` or `./run yarn:outdated` to get a list of 345 | outdated dependencies based on what you currently have installed. Once you've 346 | figured out what you want to update, go make those updates in your 347 | `pyproject.toml` and / or `package.json` file. 348 | 349 | Or, let's say you've customized your app and it's time to add a new dependency, 350 | either for Python or Node. 351 | 352 | #### In development: 353 | 354 | ##### Option 1 355 | 356 | 1. Directly edit `pyproject.toml` or `assets/package.json` to add your package 357 | 2. `./run deps:install` or `./run deps:install --no-build` 358 | - The `--no-build` option will only write out a new lock file without re-building your image 359 | 360 | ##### Option 2 361 | 362 | 1. Run `./run uv add mypackage --no-sync` or `run yarn add mypackage --no-lockfile` which will update your `pyproject.toml` or `assets/package.json` with the latest version of that package but not install it 363 | 2. The same step as step 2 from option 1 364 | 365 | Either option is fine, it's up to you based on what's more convenient at the 366 | time. You can modify the above workflows for updating an existing package or 367 | removing one as well. 368 | 369 | You can also access `uv` and `yarn` in Docker with `./run uv` and `./run yarn` 370 | after you've upped the project. 371 | 372 | #### In CI: 373 | 374 | You'll want to run `docker compose build` since it will use any existing lock 375 | files if they exist. You can also check out the complete CI test pipeline in 376 | the [run](https://github.com/nickjj/docker-django-example/blob/main/run) file 377 | under the `ci:test` function. 378 | 379 | #### In production: 380 | 381 | This is usually a non-issue since you'll be pulling down pre-built images from 382 | a Docker registry but if you decide to build your Docker images directly on 383 | your server you could run `docker compose build` as part of your deploy 384 | pipeline which is similar to how it would work in CI. 385 | 386 | ## 🤝 See a way to improve something? 387 | 388 | If you see anything that could be improved please open an issue or start a PR. 389 | Any help is much appreciated! 390 | 391 | ## 🌎 Additional resources 392 | 393 | Now that you have your app ready to go, it's time to build something cool! If 394 | you want to learn more about Docker, Django and deploying a Django app here's a 395 | couple of free and paid resources. There's Google too! 396 | 397 | ### Learn more about Docker and Django 398 | 399 | #### Official documentation 400 | 401 | - 402 | - 403 | 404 | #### Courses / books 405 | 406 | - [https://diveintodocker.com](https://diveintodocker.com?ref=docker-django-example) 407 | is a course I created which goes over the Docker and Docker Compose 408 | fundamentals 409 | - William Vincent has a bunch of [beginner and advanced Django 410 | books](https://gumroad.com/a/139727987). He also co-hosts the [Django 411 | Chat](https://djangochat.com/) podcast 412 | 413 | ### Deploy to production 414 | 415 | I'm creating an in-depth course related to deploying Dockerized web apps. If 416 | you want to get notified when it launches with a discount and potentially get 417 | free videos while the course is being developed then [sign up here to get 418 | notified](https://nickjanetakis.com/courses/deploy-to-production). 419 | 420 | ## 👀 About the author 421 | 422 | - Nick Janetakis | | [@nickjanetakis](https://twitter.com/nickjanetakis) 423 | 424 | I'm a self taught developer and have been freelancing for the last ~20 years. 425 | You can read about everything I've learned along the way on my site at 426 | [https://nickjanetakis.com](https://nickjanetakis.com/). 427 | 428 | There's hundreds of [blog posts](https://nickjanetakis.com/) and a couple 429 | of [video courses](https://nickjanetakis.com/courses) on web development and 430 | deployment topics. I also have a [podcast](https://runninginproduction.com) 431 | where I talk with folks about running web apps in production. 432 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 3 3 | requires-python = ">=3.13" 4 | 5 | [[package]] 6 | name = "amqp" 7 | version = "5.3.1" 8 | source = { registry = "https://pypi.org/simple" } 9 | dependencies = [ 10 | { name = "vine" }, 11 | ] 12 | sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432", size = 129013, upload-time = "2024-11-12T19:55:44.051Z" } 13 | wheels = [ 14 | { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944, upload-time = "2024-11-12T19:55:41.782Z" }, 15 | ] 16 | 17 | [[package]] 18 | name = "asgiref" 19 | version = "3.11.0" 20 | source = { registry = "https://pypi.org/simple" } 21 | sdist = { url = "https://files.pythonhosted.org/packages/76/b9/4db2509eabd14b4a8c71d1b24c8d5734c52b8560a7b1e1a8b56c8d25568b/asgiref-3.11.0.tar.gz", hash = "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4", size = 37969, upload-time = "2025-11-19T15:32:20.106Z" } 22 | wheels = [ 23 | { url = "https://files.pythonhosted.org/packages/91/be/317c2c55b8bbec407257d45f5c8d1b6867abc76d12043f2d3d58c538a4ea/asgiref-3.11.0-py3-none-any.whl", hash = "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d", size = 24096, upload-time = "2025-11-19T15:32:19.004Z" }, 24 | ] 25 | 26 | [[package]] 27 | name = "billiard" 28 | version = "4.2.1" 29 | source = { registry = "https://pypi.org/simple" } 30 | sdist = { url = "https://files.pythonhosted.org/packages/7c/58/1546c970afcd2a2428b1bfafecf2371d8951cc34b46701bea73f4280989e/billiard-4.2.1.tar.gz", hash = "sha256:12b641b0c539073fc8d3f5b8b7be998956665c4233c7c1fcd66a7e677c4fb36f", size = 155031, upload-time = "2024-09-21T13:40:22.491Z" } 31 | wheels = [ 32 | { url = "https://files.pythonhosted.org/packages/30/da/43b15f28fe5f9e027b41c539abc5469052e9d48fd75f8ff094ba2a0ae767/billiard-4.2.1-py3-none-any.whl", hash = "sha256:40b59a4ac8806ba2c2369ea98d876bc6108b051c227baffd928c644d15d8f3cb", size = 86766, upload-time = "2024-09-21T13:40:20.188Z" }, 33 | ] 34 | 35 | [[package]] 36 | name = "celery" 37 | version = "5.6.0" 38 | source = { registry = "https://pypi.org/simple" } 39 | dependencies = [ 40 | { name = "billiard" }, 41 | { name = "click" }, 42 | { name = "click-didyoumean" }, 43 | { name = "click-plugins" }, 44 | { name = "click-repl" }, 45 | { name = "exceptiongroup" }, 46 | { name = "kombu" }, 47 | { name = "python-dateutil" }, 48 | { name = "tzlocal" }, 49 | { name = "vine" }, 50 | ] 51 | sdist = { url = "https://files.pythonhosted.org/packages/ad/5f/b681ae3c89290d2ea6562ea96b40f5af6f6fc5f7743e2cd1a19e47721548/celery-5.6.0.tar.gz", hash = "sha256:641405206042d52ae460e4e9751a2e31b06cf80ab836fcf92e0b9311d7ea8113", size = 1712522, upload-time = "2025-11-30T17:39:46.282Z" } 52 | wheels = [ 53 | { url = "https://files.pythonhosted.org/packages/01/4e/53a125038d6a814491a0ae3457435c13cf8821eb602292cf9db37ce35f62/celery-5.6.0-py3-none-any.whl", hash = "sha256:33cf01477b175017fc8f22c5ee8a65157591043ba8ca78a443fe703aa910f581", size = 444561, upload-time = "2025-11-30T17:39:44.314Z" }, 54 | ] 55 | 56 | [[package]] 57 | name = "click" 58 | version = "8.1.8" 59 | source = { registry = "https://pypi.org/simple" } 60 | dependencies = [ 61 | { name = "colorama", marker = "sys_platform == 'win32'" }, 62 | ] 63 | sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } 64 | wheels = [ 65 | { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, 66 | ] 67 | 68 | [[package]] 69 | name = "click-didyoumean" 70 | version = "0.3.1" 71 | source = { registry = "https://pypi.org/simple" } 72 | dependencies = [ 73 | { name = "click" }, 74 | ] 75 | sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463", size = 3089, upload-time = "2024-03-24T08:22:07.499Z" } 76 | wheels = [ 77 | { url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c", size = 3631, upload-time = "2024-03-24T08:22:06.356Z" }, 78 | ] 79 | 80 | [[package]] 81 | name = "click-plugins" 82 | version = "1.1.1" 83 | source = { registry = "https://pypi.org/simple" } 84 | dependencies = [ 85 | { name = "click" }, 86 | ] 87 | sdist = { url = "https://files.pythonhosted.org/packages/5f/1d/45434f64ed749540af821fd7e42b8e4d23ac04b1eda7c26613288d6cd8a8/click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b", size = 8164, upload-time = "2019-04-04T04:27:04.82Z" } 88 | wheels = [ 89 | { url = "https://files.pythonhosted.org/packages/e9/da/824b92d9942f4e472702488857914bdd50f73021efea15b4cad9aca8ecef/click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8", size = 7497, upload-time = "2019-04-04T04:27:03.36Z" }, 90 | ] 91 | 92 | [[package]] 93 | name = "click-repl" 94 | version = "0.3.0" 95 | source = { registry = "https://pypi.org/simple" } 96 | dependencies = [ 97 | { name = "click" }, 98 | { name = "prompt-toolkit" }, 99 | ] 100 | sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449, upload-time = "2023-06-15T12:43:51.141Z" } 101 | wheels = [ 102 | { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289, upload-time = "2023-06-15T12:43:48.626Z" }, 103 | ] 104 | 105 | [[package]] 106 | name = "colorama" 107 | version = "0.4.6" 108 | source = { registry = "https://pypi.org/simple" } 109 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } 110 | wheels = [ 111 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, 112 | ] 113 | 114 | [[package]] 115 | name = "django" 116 | version = "6.0" 117 | source = { registry = "https://pypi.org/simple" } 118 | dependencies = [ 119 | { name = "asgiref" }, 120 | { name = "sqlparse" }, 121 | { name = "tzdata", marker = "sys_platform == 'win32'" }, 122 | ] 123 | sdist = { url = "https://files.pythonhosted.org/packages/15/75/19762bfc4ea556c303d9af8e36f0cd910ab17dff6c8774644314427a2120/django-6.0.tar.gz", hash = "sha256:7b0c1f50c0759bbe6331c6a39c89ae022a84672674aeda908784617ef47d8e26", size = 10932418, upload-time = "2025-12-03T16:26:21.878Z" } 124 | wheels = [ 125 | { url = "https://files.pythonhosted.org/packages/d7/ae/f19e24789a5ad852670d6885f5480f5e5895576945fcc01817dfd9bc002a/django-6.0-py3-none-any.whl", hash = "sha256:1cc2c7344303bbfb7ba5070487c17f7fc0b7174bbb0a38cebf03c675f5f19b6d", size = 8339181, upload-time = "2025-12-03T16:26:16.231Z" }, 126 | ] 127 | 128 | [[package]] 129 | name = "django-debug-toolbar" 130 | version = "6.1.0" 131 | source = { registry = "https://pypi.org/simple" } 132 | dependencies = [ 133 | { name = "django" }, 134 | { name = "sqlparse" }, 135 | ] 136 | sdist = { url = "https://files.pythonhosted.org/packages/c0/50/acae2dd379164f6f4c6b6b36fd48a4d21b02095a03f4df7c30a8d1f1a62c/django_debug_toolbar-6.1.0.tar.gz", hash = "sha256:e962ec350c9be8bdba918138e975a9cdb193f60ec396af2bb71b769e8e165519", size = 309141, upload-time = "2025-10-30T19:50:39.458Z" } 137 | wheels = [ 138 | { url = "https://files.pythonhosted.org/packages/6d/72/685c978af45ad08257e2c69687a873eda6b6531c79b6e6091794c41c5ff6/django_debug_toolbar-6.1.0-py3-none-any.whl", hash = "sha256:e214dea4494087e7cebdcea84223819c5eb97f9de3110a3665ad673f0ba98413", size = 269069, upload-time = "2025-10-30T19:50:37.71Z" }, 139 | ] 140 | 141 | [[package]] 142 | name = "exceptiongroup" 143 | version = "1.3.1" 144 | source = { registry = "https://pypi.org/simple" } 145 | sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } 146 | wheels = [ 147 | { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, 148 | ] 149 | 150 | [[package]] 151 | name = "gunicorn" 152 | version = "23.0.0" 153 | source = { registry = "https://pypi.org/simple" } 154 | dependencies = [ 155 | { name = "packaging" }, 156 | ] 157 | sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" } 158 | wheels = [ 159 | { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" }, 160 | ] 161 | 162 | [[package]] 163 | name = "hello" 164 | version = "0.1.0" 165 | source = { virtual = "." } 166 | dependencies = [ 167 | { name = "celery" }, 168 | { name = "django" }, 169 | { name = "django-debug-toolbar" }, 170 | { name = "gunicorn" }, 171 | { name = "psycopg" }, 172 | { name = "redis" }, 173 | { name = "ruff" }, 174 | { name = "setuptools" }, 175 | { name = "whitenoise" }, 176 | ] 177 | 178 | [package.metadata] 179 | requires-dist = [ 180 | { name = "celery", specifier = "==5.6.0" }, 181 | { name = "django", specifier = "==6.0" }, 182 | { name = "django-debug-toolbar", specifier = "==6.1.0" }, 183 | { name = "gunicorn", specifier = "==23.0.0" }, 184 | { name = "psycopg", specifier = "==3.3.1" }, 185 | { name = "redis", specifier = "==7.1.0" }, 186 | { name = "ruff", specifier = "==0.14.8" }, 187 | { name = "setuptools", specifier = "==80.9.0" }, 188 | { name = "whitenoise", specifier = "==6.11.0" }, 189 | ] 190 | 191 | [[package]] 192 | name = "kombu" 193 | version = "5.6.1" 194 | source = { registry = "https://pypi.org/simple" } 195 | dependencies = [ 196 | { name = "amqp" }, 197 | { name = "packaging" }, 198 | { name = "tzdata" }, 199 | { name = "vine" }, 200 | ] 201 | sdist = { url = "https://files.pythonhosted.org/packages/ac/05/749ada8e51718445d915af13f1d18bc4333848e8faa0cb234028a3328ec8/kombu-5.6.1.tar.gz", hash = "sha256:90f1febb57ad4f53ca327a87598191b2520e0c793c75ea3b88d98e3b111282e4", size = 471548, upload-time = "2025-11-25T11:07:33.504Z" } 202 | wheels = [ 203 | { url = "https://files.pythonhosted.org/packages/14/d6/943cf84117cd9ddecf6e1707a3f712a49fc64abdb8ac31b19132871af1dd/kombu-5.6.1-py3-none-any.whl", hash = "sha256:b69e3f5527ec32fc5196028a36376501682973e9620d6175d1c3d4eaf7e95409", size = 214141, upload-time = "2025-11-25T11:07:31.54Z" }, 204 | ] 205 | 206 | [[package]] 207 | name = "packaging" 208 | version = "24.2" 209 | source = { registry = "https://pypi.org/simple" } 210 | sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } 211 | wheels = [ 212 | { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, 213 | ] 214 | 215 | [[package]] 216 | name = "prompt-toolkit" 217 | version = "3.0.50" 218 | source = { registry = "https://pypi.org/simple" } 219 | dependencies = [ 220 | { name = "wcwidth" }, 221 | ] 222 | sdist = { url = "https://files.pythonhosted.org/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087, upload-time = "2025-01-20T15:55:35.072Z" } 223 | wheels = [ 224 | { url = "https://files.pythonhosted.org/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816, upload-time = "2025-01-20T15:55:29.98Z" }, 225 | ] 226 | 227 | [[package]] 228 | name = "psycopg" 229 | version = "3.3.1" 230 | source = { registry = "https://pypi.org/simple" } 231 | dependencies = [ 232 | { name = "tzdata", marker = "sys_platform == 'win32'" }, 233 | ] 234 | sdist = { url = "https://files.pythonhosted.org/packages/18/ed/3a30e8ef82d4128c76aa9bd6b2a7fe6c16c283811e6655997f5047801b47/psycopg-3.3.1.tar.gz", hash = "sha256:ccfa30b75874eef809c0fbbb176554a2640cc1735a612accc2e2396a92442fc6", size = 165596, upload-time = "2025-12-02T21:09:55.545Z" } 235 | wheels = [ 236 | { url = "https://files.pythonhosted.org/packages/b6/f3/0b4a4c25a47c2d907afa97674287dab61bc9941c9ac3972a67100e33894d/psycopg-3.3.1-py3-none-any.whl", hash = "sha256:e44d8eae209752efe46318f36dd0fdf5863e928009338d736843bb1084f6435c", size = 212760, upload-time = "2025-12-02T21:02:36.029Z" }, 237 | ] 238 | 239 | [[package]] 240 | name = "python-dateutil" 241 | version = "2.9.0.post0" 242 | source = { registry = "https://pypi.org/simple" } 243 | dependencies = [ 244 | { name = "six" }, 245 | ] 246 | sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } 247 | wheels = [ 248 | { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, 249 | ] 250 | 251 | [[package]] 252 | name = "redis" 253 | version = "7.1.0" 254 | source = { registry = "https://pypi.org/simple" } 255 | sdist = { url = "https://files.pythonhosted.org/packages/43/c8/983d5c6579a411d8a99bc5823cc5712768859b5ce2c8afe1a65b37832c81/redis-7.1.0.tar.gz", hash = "sha256:b1cc3cfa5a2cb9c2ab3ba700864fb0ad75617b41f01352ce5779dabf6d5f9c3c", size = 4796669, upload-time = "2025-11-19T15:54:39.961Z" } 256 | wheels = [ 257 | { url = "https://files.pythonhosted.org/packages/89/f0/8956f8a86b20d7bb9d6ac0187cf4cd54d8065bc9a1a09eb8011d4d326596/redis-7.1.0-py3-none-any.whl", hash = "sha256:23c52b208f92b56103e17c5d06bdc1a6c2c0b3106583985a76a18f83b265de2b", size = 354159, upload-time = "2025-11-19T15:54:38.064Z" }, 258 | ] 259 | 260 | [[package]] 261 | name = "ruff" 262 | version = "0.14.8" 263 | source = { registry = "https://pypi.org/simple" } 264 | sdist = { url = "https://files.pythonhosted.org/packages/ed/d9/f7a0c4b3a2bf2556cd5d99b05372c29980249ef71e8e32669ba77428c82c/ruff-0.14.8.tar.gz", hash = "sha256:774ed0dd87d6ce925e3b8496feb3a00ac564bea52b9feb551ecd17e0a23d1eed", size = 5765385, upload-time = "2025-12-04T15:06:17.669Z" } 265 | wheels = [ 266 | { url = "https://files.pythonhosted.org/packages/48/b8/9537b52010134b1d2b72870cc3f92d5fb759394094741b09ceccae183fbe/ruff-0.14.8-py3-none-linux_armv6l.whl", hash = "sha256:ec071e9c82eca417f6111fd39f7043acb53cd3fde9b1f95bbed745962e345afb", size = 13441540, upload-time = "2025-12-04T15:06:14.896Z" }, 267 | { url = "https://files.pythonhosted.org/packages/24/00/99031684efb025829713682012b6dd37279b1f695ed1b01725f85fd94b38/ruff-0.14.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8cdb162a7159f4ca36ce980a18c43d8f036966e7f73f866ac8f493b75e0c27e9", size = 13669384, upload-time = "2025-12-04T15:06:51.809Z" }, 268 | { url = "https://files.pythonhosted.org/packages/72/64/3eb5949169fc19c50c04f28ece2c189d3b6edd57e5b533649dae6ca484fe/ruff-0.14.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e2fcbefe91f9fad0916850edf0854530c15bd1926b6b779de47e9ab619ea38f", size = 12806917, upload-time = "2025-12-04T15:06:08.925Z" }, 269 | { url = "https://files.pythonhosted.org/packages/c4/08/5250babb0b1b11910f470370ec0cbc67470231f7cdc033cee57d4976f941/ruff-0.14.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d70721066a296f45786ec31916dc287b44040f553da21564de0ab4d45a869b", size = 13256112, upload-time = "2025-12-04T15:06:23.498Z" }, 270 | { url = "https://files.pythonhosted.org/packages/78/4c/6c588e97a8e8c2d4b522c31a579e1df2b4d003eddfbe23d1f262b1a431ff/ruff-0.14.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c87e09b3cd9d126fc67a9ecd3b5b1d3ded2b9c7fce3f16e315346b9d05cfb52", size = 13227559, upload-time = "2025-12-04T15:06:33.432Z" }, 271 | { url = "https://files.pythonhosted.org/packages/23/ce/5f78cea13eda8eceac71b5f6fa6e9223df9b87bb2c1891c166d1f0dce9f1/ruff-0.14.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d62cb310c4fbcb9ee4ac023fe17f984ae1e12b8a4a02e3d21489f9a2a5f730c", size = 13896379, upload-time = "2025-12-04T15:06:02.687Z" }, 272 | { url = "https://files.pythonhosted.org/packages/cf/79/13de4517c4dadce9218a20035b21212a4c180e009507731f0d3b3f5df85a/ruff-0.14.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1af35c2d62633d4da0521178e8a2641c636d2a7153da0bac1b30cfd4ccd91344", size = 15372786, upload-time = "2025-12-04T15:06:29.828Z" }, 273 | { url = "https://files.pythonhosted.org/packages/00/06/33df72b3bb42be8a1c3815fd4fae83fa2945fc725a25d87ba3e42d1cc108/ruff-0.14.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25add4575ffecc53d60eed3f24b1e934493631b48ebbc6ebaf9d8517924aca4b", size = 14990029, upload-time = "2025-12-04T15:06:36.812Z" }, 274 | { url = "https://files.pythonhosted.org/packages/64/61/0f34927bd90925880394de0e081ce1afab66d7b3525336f5771dcf0cb46c/ruff-0.14.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c943d847b7f02f7db4201a0600ea7d244d8a404fbb639b439e987edcf2baf9a", size = 14407037, upload-time = "2025-12-04T15:06:39.979Z" }, 275 | { url = "https://files.pythonhosted.org/packages/96/bc/058fe0aefc0fbf0d19614cb6d1a3e2c048f7dc77ca64957f33b12cfdc5ef/ruff-0.14.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb6e8bf7b4f627548daa1b69283dac5a296bfe9ce856703b03130732e20ddfe2", size = 14102390, upload-time = "2025-12-04T15:06:46.372Z" }, 276 | { url = "https://files.pythonhosted.org/packages/af/a4/e4f77b02b804546f4c17e8b37a524c27012dd6ff05855d2243b49a7d3cb9/ruff-0.14.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7aaf2974f378e6b01d1e257c6948207aec6a9b5ba53fab23d0182efb887a0e4a", size = 14230793, upload-time = "2025-12-04T15:06:20.497Z" }, 277 | { url = "https://files.pythonhosted.org/packages/3f/52/bb8c02373f79552e8d087cedaffad76b8892033d2876c2498a2582f09dcf/ruff-0.14.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e5758ca513c43ad8a4ef13f0f081f80f08008f410790f3611a21a92421ab045b", size = 13160039, upload-time = "2025-12-04T15:06:49.06Z" }, 278 | { url = "https://files.pythonhosted.org/packages/1f/ad/b69d6962e477842e25c0b11622548df746290cc6d76f9e0f4ed7456c2c31/ruff-0.14.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f74f7ba163b6e85a8d81a590363bf71618847e5078d90827749bfda1d88c9cdf", size = 13205158, upload-time = "2025-12-04T15:06:54.574Z" }, 279 | { url = "https://files.pythonhosted.org/packages/06/63/54f23da1315c0b3dfc1bc03fbc34e10378918a20c0b0f086418734e57e74/ruff-0.14.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eed28f6fafcc9591994c42254f5a5c5ca40e69a30721d2ab18bb0bb3baac3ab6", size = 13469550, upload-time = "2025-12-04T15:05:59.209Z" }, 280 | { url = "https://files.pythonhosted.org/packages/70/7d/a4d7b1961e4903bc37fffb7ddcfaa7beb250f67d97cfd1ee1d5cddb1ec90/ruff-0.14.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:21d48fa744c9d1cb8d71eb0a740c4dd02751a5de9db9a730a8ef75ca34cf138e", size = 14211332, upload-time = "2025-12-04T15:06:06.027Z" }, 281 | { url = "https://files.pythonhosted.org/packages/5d/93/2a5063341fa17054e5c86582136e9895db773e3c2ffb770dde50a09f35f0/ruff-0.14.8-py3-none-win32.whl", hash = "sha256:15f04cb45c051159baebb0f0037f404f1dc2f15a927418f29730f411a79bc4e7", size = 13151890, upload-time = "2025-12-04T15:06:11.668Z" }, 282 | { url = "https://files.pythonhosted.org/packages/02/1c/65c61a0859c0add13a3e1cbb6024b42de587456a43006ca2d4fd3d1618fe/ruff-0.14.8-py3-none-win_amd64.whl", hash = "sha256:9eeb0b24242b5bbff3011409a739929f497f3fb5fe3b5698aba5e77e8c833097", size = 14537826, upload-time = "2025-12-04T15:06:26.409Z" }, 283 | { url = "https://files.pythonhosted.org/packages/6d/63/8b41cea3afd7f58eb64ac9251668ee0073789a3bc9ac6f816c8c6fef986d/ruff-0.14.8-py3-none-win_arm64.whl", hash = "sha256:965a582c93c63fe715fd3e3f8aa37c4b776777203d8e1d8aa3cc0c14424a4b99", size = 13634522, upload-time = "2025-12-04T15:06:43.212Z" }, 284 | ] 285 | 286 | [[package]] 287 | name = "setuptools" 288 | version = "80.9.0" 289 | source = { registry = "https://pypi.org/simple" } 290 | sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } 291 | wheels = [ 292 | { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, 293 | ] 294 | 295 | [[package]] 296 | name = "six" 297 | version = "1.17.0" 298 | source = { registry = "https://pypi.org/simple" } 299 | sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } 300 | wheels = [ 301 | { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, 302 | ] 303 | 304 | [[package]] 305 | name = "sqlparse" 306 | version = "0.5.3" 307 | source = { registry = "https://pypi.org/simple" } 308 | sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999, upload-time = "2024-12-10T12:05:30.728Z" } 309 | wheels = [ 310 | { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415, upload-time = "2024-12-10T12:05:27.824Z" }, 311 | ] 312 | 313 | [[package]] 314 | name = "tzdata" 315 | version = "2025.2" 316 | source = { registry = "https://pypi.org/simple" } 317 | sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } 318 | wheels = [ 319 | { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, 320 | ] 321 | 322 | [[package]] 323 | name = "tzlocal" 324 | version = "5.3.1" 325 | source = { registry = "https://pypi.org/simple" } 326 | dependencies = [ 327 | { name = "tzdata", marker = "sys_platform == 'win32'" }, 328 | ] 329 | sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } 330 | wheels = [ 331 | { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, 332 | ] 333 | 334 | [[package]] 335 | name = "vine" 336 | version = "5.1.0" 337 | source = { registry = "https://pypi.org/simple" } 338 | sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980, upload-time = "2023-11-05T08:46:53.857Z" } 339 | wheels = [ 340 | { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636, upload-time = "2023-11-05T08:46:51.205Z" }, 341 | ] 342 | 343 | [[package]] 344 | name = "wcwidth" 345 | version = "0.2.13" 346 | source = { registry = "https://pypi.org/simple" } 347 | sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } 348 | wheels = [ 349 | { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, 350 | ] 351 | 352 | [[package]] 353 | name = "whitenoise" 354 | version = "6.11.0" 355 | source = { registry = "https://pypi.org/simple" } 356 | sdist = { url = "https://files.pythonhosted.org/packages/15/95/8c81ec6b6ebcbf8aca2de7603070ccf37dbb873b03f20708e0f7c1664bc6/whitenoise-6.11.0.tar.gz", hash = "sha256:0f5bfce6061ae6611cd9396a8231e088722e4fc67bc13a111be74c738d99375f", size = 26432, upload-time = "2025-09-18T09:16:10.995Z" } 357 | wheels = [ 358 | { url = "https://files.pythonhosted.org/packages/6c/e9/4366332f9295fe0647d7d3251ce18f5615fbcb12d02c79a26f8dba9221b3/whitenoise-6.11.0-py3-none-any.whl", hash = "sha256:b2aeb45950597236f53b5342b3121c5de69c8da0109362aee506ce88e022d258", size = 20197, upload-time = "2025-09-18T09:16:09.754Z" }, 359 | ] 360 | -------------------------------------------------------------------------------- /assets/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@alloc/quick-lru@^5.2.0": 6 | version "5.2.0" 7 | resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" 8 | integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== 9 | 10 | "@emnapi/core@^1.5.0": 11 | version "1.5.0" 12 | resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.5.0.tgz#85cd84537ec989cebb2343606a1ee663ce4edaf0" 13 | integrity sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg== 14 | dependencies: 15 | "@emnapi/wasi-threads" "1.1.0" 16 | tslib "^2.4.0" 17 | 18 | "@emnapi/core@^1.6.0": 19 | version "1.7.1" 20 | resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.7.1.tgz#3a79a02dbc84f45884a1806ebb98e5746bdfaac4" 21 | integrity sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg== 22 | dependencies: 23 | "@emnapi/wasi-threads" "1.1.0" 24 | tslib "^2.4.0" 25 | 26 | "@emnapi/runtime@^1.5.0": 27 | version "1.5.0" 28 | resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.5.0.tgz#9aebfcb9b17195dce3ab53c86787a6b7d058db73" 29 | integrity sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ== 30 | dependencies: 31 | tslib "^2.4.0" 32 | 33 | "@emnapi/runtime@^1.6.0": 34 | version "1.7.1" 35 | resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.7.1.tgz#a73784e23f5d57287369c808197288b52276b791" 36 | integrity sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA== 37 | dependencies: 38 | tslib "^2.4.0" 39 | 40 | "@emnapi/wasi-threads@1.1.0", "@emnapi/wasi-threads@^1.1.0": 41 | version "1.1.0" 42 | resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz#60b2102fddc9ccb78607e4a3cf8403ea69be41bf" 43 | integrity sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ== 44 | dependencies: 45 | tslib "^2.4.0" 46 | 47 | "@esbuild/aix-ppc64@0.27.1": 48 | version "0.27.1" 49 | resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz#116edcd62c639ed8ab551e57b38251bb28384de4" 50 | integrity sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA== 51 | 52 | "@esbuild/android-arm64@0.27.1": 53 | version "0.27.1" 54 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz#31c00d864c80f6de1900a11de8a506dbfbb27349" 55 | integrity sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ== 56 | 57 | "@esbuild/android-arm@0.27.1": 58 | version "0.27.1" 59 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.1.tgz#d2b73ab0ba894923a1d1378fd4b15cc20985f436" 60 | integrity sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg== 61 | 62 | "@esbuild/android-x64@0.27.1": 63 | version "0.27.1" 64 | resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.1.tgz#d9f74d8278191317250cfe0c15a13f410540b122" 65 | integrity sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ== 66 | 67 | "@esbuild/darwin-arm64@0.27.1": 68 | version "0.27.1" 69 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz#baf6914b8c57ed9d41f9de54023aa3ff9b084680" 70 | integrity sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ== 71 | 72 | "@esbuild/darwin-x64@0.27.1": 73 | version "0.27.1" 74 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz#64e37400795f780a76c858a118ff19681a64b4e0" 75 | integrity sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ== 76 | 77 | "@esbuild/freebsd-arm64@0.27.1": 78 | version "0.27.1" 79 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz#6572f2f235933eee906e070dfaae54488ee60acd" 80 | integrity sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg== 81 | 82 | "@esbuild/freebsd-x64@0.27.1": 83 | version "0.27.1" 84 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz#83105dba9cf6ac4f44336799446d7f75c8c3a1e1" 85 | integrity sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ== 86 | 87 | "@esbuild/linux-arm64@0.27.1": 88 | version "0.27.1" 89 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz#035ff647d4498bdf16eb2d82801f73b366477dfa" 90 | integrity sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q== 91 | 92 | "@esbuild/linux-arm@0.27.1": 93 | version "0.27.1" 94 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz#3516c74d2afbe305582dbb546d60f7978a8ece7f" 95 | integrity sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA== 96 | 97 | "@esbuild/linux-ia32@0.27.1": 98 | version "0.27.1" 99 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz#788db5db8ecd3d75dd41c42de0fe8f1fd967a4a7" 100 | integrity sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw== 101 | 102 | "@esbuild/linux-loong64@0.27.1": 103 | version "0.27.1" 104 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz#8211f08b146916a6302ec2b8f87ec0cc4b62c49e" 105 | integrity sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg== 106 | 107 | "@esbuild/linux-mips64el@0.27.1": 108 | version "0.27.1" 109 | resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz#cc58586ea83b3f171e727a624e7883a1c3eb4c04" 110 | integrity sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA== 111 | 112 | "@esbuild/linux-ppc64@0.27.1": 113 | version "0.27.1" 114 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz#632477bbd98175cf8e53a7c9952d17fb2d6d4115" 115 | integrity sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ== 116 | 117 | "@esbuild/linux-riscv64@0.27.1": 118 | version "0.27.1" 119 | resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz#35435a82435a8a750edf433b83ac0d10239ac3fe" 120 | integrity sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ== 121 | 122 | "@esbuild/linux-s390x@0.27.1": 123 | version "0.27.1" 124 | resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz#172edd7086438edacd86c0e2ea25ac9dbb62aac5" 125 | integrity sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw== 126 | 127 | "@esbuild/linux-x64@0.27.1": 128 | version "0.27.1" 129 | resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz#09c771de9e2d8169d5969adf298ae21581f08c7f" 130 | integrity sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA== 131 | 132 | "@esbuild/netbsd-arm64@0.27.1": 133 | version "0.27.1" 134 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz#475ac0ce7edf109a358b1669f67759de4bcbb7c4" 135 | integrity sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ== 136 | 137 | "@esbuild/netbsd-x64@0.27.1": 138 | version "0.27.1" 139 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz#3c31603d592477dc43b63df1ae100000f7fb59d7" 140 | integrity sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg== 141 | 142 | "@esbuild/openbsd-arm64@0.27.1": 143 | version "0.27.1" 144 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz#482067c847665b10d66431e936d4bc5fa8025abf" 145 | integrity sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g== 146 | 147 | "@esbuild/openbsd-x64@0.27.1": 148 | version "0.27.1" 149 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz#687a188c2b184e5b671c5f74a6cd6247c0718c52" 150 | integrity sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg== 151 | 152 | "@esbuild/openharmony-arm64@0.27.1": 153 | version "0.27.1" 154 | resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz#9929ee7fa8c1db2f33ef4d86198018dac9c1744f" 155 | integrity sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg== 156 | 157 | "@esbuild/sunos-x64@0.27.1": 158 | version "0.27.1" 159 | resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz#94071a146f313e7394c6424af07b2b564f1f994d" 160 | integrity sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA== 161 | 162 | "@esbuild/win32-arm64@0.27.1": 163 | version "0.27.1" 164 | resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz#869fde72a3576fdf48824085d05493fceebe395d" 165 | integrity sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg== 166 | 167 | "@esbuild/win32-ia32@0.27.1": 168 | version "0.27.1" 169 | resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz#31d7585893ed7b54483d0b8d87a4bfeba0ecfff5" 170 | integrity sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ== 171 | 172 | "@esbuild/win32-x64@0.27.1": 173 | version "0.27.1" 174 | resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz#5efe5a112938b1180e98c76685ff9185cfa4f16e" 175 | integrity sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw== 176 | 177 | "@jridgewell/gen-mapping@^0.3.5": 178 | version "0.3.8" 179 | resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" 180 | integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== 181 | dependencies: 182 | "@jridgewell/set-array" "^1.2.1" 183 | "@jridgewell/sourcemap-codec" "^1.4.10" 184 | "@jridgewell/trace-mapping" "^0.3.24" 185 | 186 | "@jridgewell/remapping@^2.3.4": 187 | version "2.3.5" 188 | resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" 189 | integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== 190 | dependencies: 191 | "@jridgewell/gen-mapping" "^0.3.5" 192 | "@jridgewell/trace-mapping" "^0.3.24" 193 | 194 | "@jridgewell/resolve-uri@^3.1.0": 195 | version "3.1.2" 196 | resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" 197 | integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== 198 | 199 | "@jridgewell/set-array@^1.2.1": 200 | version "1.2.1" 201 | resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" 202 | integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== 203 | 204 | "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": 205 | version "1.5.0" 206 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" 207 | integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== 208 | 209 | "@jridgewell/sourcemap-codec@^1.5.5": 210 | version "1.5.5" 211 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" 212 | integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== 213 | 214 | "@jridgewell/trace-mapping@^0.3.24": 215 | version "0.3.25" 216 | resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" 217 | integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== 218 | dependencies: 219 | "@jridgewell/resolve-uri" "^3.1.0" 220 | "@jridgewell/sourcemap-codec" "^1.4.14" 221 | 222 | "@napi-rs/wasm-runtime@^1.0.7": 223 | version "1.0.7" 224 | resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz#dcfea99a75f06209a235f3d941e3460a51e9b14c" 225 | integrity sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw== 226 | dependencies: 227 | "@emnapi/core" "^1.5.0" 228 | "@emnapi/runtime" "^1.5.0" 229 | "@tybys/wasm-util" "^0.10.1" 230 | 231 | "@parcel/watcher-android-arm64@2.5.1": 232 | version "2.5.1" 233 | resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz#507f836d7e2042f798c7d07ad19c3546f9848ac1" 234 | integrity sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA== 235 | 236 | "@parcel/watcher-darwin-arm64@2.5.1": 237 | version "2.5.1" 238 | resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz#3d26dce38de6590ef79c47ec2c55793c06ad4f67" 239 | integrity sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw== 240 | 241 | "@parcel/watcher-darwin-x64@2.5.1": 242 | version "2.5.1" 243 | resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz#99f3af3869069ccf774e4ddfccf7e64fd2311ef8" 244 | integrity sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg== 245 | 246 | "@parcel/watcher-freebsd-x64@2.5.1": 247 | version "2.5.1" 248 | resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz#14d6857741a9f51dfe51d5b08b7c8afdbc73ad9b" 249 | integrity sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ== 250 | 251 | "@parcel/watcher-linux-arm-glibc@2.5.1": 252 | version "2.5.1" 253 | resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz#43c3246d6892381db473bb4f663229ad20b609a1" 254 | integrity sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA== 255 | 256 | "@parcel/watcher-linux-arm-musl@2.5.1": 257 | version "2.5.1" 258 | resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz#663750f7090bb6278d2210de643eb8a3f780d08e" 259 | integrity sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q== 260 | 261 | "@parcel/watcher-linux-arm64-glibc@2.5.1": 262 | version "2.5.1" 263 | resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz#ba60e1f56977f7e47cd7e31ad65d15fdcbd07e30" 264 | integrity sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w== 265 | 266 | "@parcel/watcher-linux-arm64-musl@2.5.1": 267 | version "2.5.1" 268 | resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz#f7fbcdff2f04c526f96eac01f97419a6a99855d2" 269 | integrity sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg== 270 | 271 | "@parcel/watcher-linux-x64-glibc@2.5.1": 272 | version "2.5.1" 273 | resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz#4d2ea0f633eb1917d83d483392ce6181b6a92e4e" 274 | integrity sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A== 275 | 276 | "@parcel/watcher-linux-x64-musl@2.5.1": 277 | version "2.5.1" 278 | resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz#277b346b05db54f55657301dd77bdf99d63606ee" 279 | integrity sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg== 280 | 281 | "@parcel/watcher-win32-arm64@2.5.1": 282 | version "2.5.1" 283 | resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz#7e9e02a26784d47503de1d10e8eab6cceb524243" 284 | integrity sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw== 285 | 286 | "@parcel/watcher-win32-ia32@2.5.1": 287 | version "2.5.1" 288 | resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz#2d0f94fa59a873cdc584bf7f6b1dc628ddf976e6" 289 | integrity sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ== 290 | 291 | "@parcel/watcher-win32-x64@2.5.1": 292 | version "2.5.1" 293 | resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz#ae52693259664ba6f2228fa61d7ee44b64ea0947" 294 | integrity sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA== 295 | 296 | "@parcel/watcher@^2.5.1": 297 | version "2.5.1" 298 | resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.5.1.tgz#342507a9cfaaf172479a882309def1e991fb1200" 299 | integrity sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg== 300 | dependencies: 301 | detect-libc "^1.0.3" 302 | is-glob "^4.0.3" 303 | micromatch "^4.0.5" 304 | node-addon-api "^7.0.0" 305 | optionalDependencies: 306 | "@parcel/watcher-android-arm64" "2.5.1" 307 | "@parcel/watcher-darwin-arm64" "2.5.1" 308 | "@parcel/watcher-darwin-x64" "2.5.1" 309 | "@parcel/watcher-freebsd-x64" "2.5.1" 310 | "@parcel/watcher-linux-arm-glibc" "2.5.1" 311 | "@parcel/watcher-linux-arm-musl" "2.5.1" 312 | "@parcel/watcher-linux-arm64-glibc" "2.5.1" 313 | "@parcel/watcher-linux-arm64-musl" "2.5.1" 314 | "@parcel/watcher-linux-x64-glibc" "2.5.1" 315 | "@parcel/watcher-linux-x64-musl" "2.5.1" 316 | "@parcel/watcher-win32-arm64" "2.5.1" 317 | "@parcel/watcher-win32-ia32" "2.5.1" 318 | "@parcel/watcher-win32-x64" "2.5.1" 319 | 320 | "@tailwindcss/cli@4.1.17": 321 | version "4.1.17" 322 | resolved "https://registry.yarnpkg.com/@tailwindcss/cli/-/cli-4.1.17.tgz#50667756225788ba0083a7875400c7b90e2be3cb" 323 | integrity sha512-jUIxcyUNlCC2aNPnyPEWU/L2/ik3pB4fF3auKGXr8AvN3T3OFESVctFKOBoPZQaZJIeUpPn1uCLp0MRxuek8gg== 324 | dependencies: 325 | "@parcel/watcher" "^2.5.1" 326 | "@tailwindcss/node" "4.1.17" 327 | "@tailwindcss/oxide" "4.1.17" 328 | enhanced-resolve "^5.18.3" 329 | mri "^1.2.0" 330 | picocolors "^1.1.1" 331 | tailwindcss "4.1.17" 332 | 333 | "@tailwindcss/node@4.1.17": 334 | version "4.1.17" 335 | resolved "https://registry.yarnpkg.com/@tailwindcss/node/-/node-4.1.17.tgz#ec40a37293246f4eeab2dac00e4425af9272f600" 336 | integrity sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg== 337 | dependencies: 338 | "@jridgewell/remapping" "^2.3.4" 339 | enhanced-resolve "^5.18.3" 340 | jiti "^2.6.1" 341 | lightningcss "1.30.2" 342 | magic-string "^0.30.21" 343 | source-map-js "^1.2.1" 344 | tailwindcss "4.1.17" 345 | 346 | "@tailwindcss/oxide-android-arm64@4.1.17": 347 | version "4.1.17" 348 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz#17f0dc901f88a979c5bff618181bce596dff596d" 349 | integrity sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ== 350 | 351 | "@tailwindcss/oxide-darwin-arm64@4.1.17": 352 | version "4.1.17" 353 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz#63e12e62b83f6949dbb10b5a7f6e441606835efc" 354 | integrity sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg== 355 | 356 | "@tailwindcss/oxide-darwin-x64@4.1.17": 357 | version "4.1.17" 358 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz#6dad270d2777508f55e2b73eca0eaef625bc45a7" 359 | integrity sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog== 360 | 361 | "@tailwindcss/oxide-freebsd-x64@4.1.17": 362 | version "4.1.17" 363 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz#e7628b4602ac7d73c11a9922ecb83c24337eff55" 364 | integrity sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g== 365 | 366 | "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17": 367 | version "4.1.17" 368 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz#4d96a6fe4c7ed20e7a013101ee46f46caca2233e" 369 | integrity sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ== 370 | 371 | "@tailwindcss/oxide-linux-arm64-gnu@4.1.17": 372 | version "4.1.17" 373 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz#adc3c01cd73610870bfc21db5713571e08fb2210" 374 | integrity sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ== 375 | 376 | "@tailwindcss/oxide-linux-arm64-musl@4.1.17": 377 | version "4.1.17" 378 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz#39ceda30407af56a1ee125b2c5ce856c6d29250f" 379 | integrity sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg== 380 | 381 | "@tailwindcss/oxide-linux-x64-gnu@4.1.17": 382 | version "4.1.17" 383 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz#a3d4bd876c04d09856af0c394f5095fbc8a2b14c" 384 | integrity sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ== 385 | 386 | "@tailwindcss/oxide-linux-x64-musl@4.1.17": 387 | version "4.1.17" 388 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz#bdc20aa4fb2d28cc928f2cfffff7a9cd03a51d5b" 389 | integrity sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ== 390 | 391 | "@tailwindcss/oxide-wasm32-wasi@4.1.17": 392 | version "4.1.17" 393 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz#7c0804748935928751838f86ff4f879c38f8a6d7" 394 | integrity sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg== 395 | dependencies: 396 | "@emnapi/core" "^1.6.0" 397 | "@emnapi/runtime" "^1.6.0" 398 | "@emnapi/wasi-threads" "^1.1.0" 399 | "@napi-rs/wasm-runtime" "^1.0.7" 400 | "@tybys/wasm-util" "^0.10.1" 401 | tslib "^2.4.0" 402 | 403 | "@tailwindcss/oxide-win32-arm64-msvc@4.1.17": 404 | version "4.1.17" 405 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz#7222fc2ceee9d45ebe5aebf38707ee9833a20475" 406 | integrity sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A== 407 | 408 | "@tailwindcss/oxide-win32-x64-msvc@4.1.17": 409 | version "4.1.17" 410 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz#ac79087f451dfcd5c3099589027a5732b045a3bf" 411 | integrity sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw== 412 | 413 | "@tailwindcss/oxide@4.1.17": 414 | version "4.1.17" 415 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide/-/oxide-4.1.17.tgz#6c063b40a022f4fbdac557c0586cfb9ae08a3dfe" 416 | integrity sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA== 417 | optionalDependencies: 418 | "@tailwindcss/oxide-android-arm64" "4.1.17" 419 | "@tailwindcss/oxide-darwin-arm64" "4.1.17" 420 | "@tailwindcss/oxide-darwin-x64" "4.1.17" 421 | "@tailwindcss/oxide-freebsd-x64" "4.1.17" 422 | "@tailwindcss/oxide-linux-arm-gnueabihf" "4.1.17" 423 | "@tailwindcss/oxide-linux-arm64-gnu" "4.1.17" 424 | "@tailwindcss/oxide-linux-arm64-musl" "4.1.17" 425 | "@tailwindcss/oxide-linux-x64-gnu" "4.1.17" 426 | "@tailwindcss/oxide-linux-x64-musl" "4.1.17" 427 | "@tailwindcss/oxide-wasm32-wasi" "4.1.17" 428 | "@tailwindcss/oxide-win32-arm64-msvc" "4.1.17" 429 | "@tailwindcss/oxide-win32-x64-msvc" "4.1.17" 430 | 431 | "@tailwindcss/postcss@4.1.17": 432 | version "4.1.17" 433 | resolved "https://registry.yarnpkg.com/@tailwindcss/postcss/-/postcss-4.1.17.tgz#983b4233920a9d7083cd0d488d5cdc254f304f6b" 434 | integrity sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw== 435 | dependencies: 436 | "@alloc/quick-lru" "^5.2.0" 437 | "@tailwindcss/node" "4.1.17" 438 | "@tailwindcss/oxide" "4.1.17" 439 | postcss "^8.4.41" 440 | tailwindcss "4.1.17" 441 | 442 | "@tybys/wasm-util@^0.10.1": 443 | version "0.10.1" 444 | resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414" 445 | integrity sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg== 446 | dependencies: 447 | tslib "^2.4.0" 448 | 449 | braces@^3.0.3: 450 | version "3.0.3" 451 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" 452 | integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== 453 | dependencies: 454 | fill-range "^7.1.1" 455 | 456 | detect-libc@^1.0.3: 457 | version "1.0.3" 458 | resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" 459 | integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== 460 | 461 | detect-libc@^2.0.3: 462 | version "2.0.3" 463 | resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" 464 | integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== 465 | 466 | enhanced-resolve@^5.18.3: 467 | version "5.18.3" 468 | resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz#9b5f4c5c076b8787c78fe540392ce76a88855b44" 469 | integrity sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww== 470 | dependencies: 471 | graceful-fs "^4.2.4" 472 | tapable "^2.2.0" 473 | 474 | esbuild-copy-static-files@0.1.0: 475 | version "0.1.0" 476 | resolved "https://registry.yarnpkg.com/esbuild-copy-static-files/-/esbuild-copy-static-files-0.1.0.tgz#4bb4987b5b554d2fc122a45f077d74663b4dbcf0" 477 | integrity sha512-KlpmYqANA1t2nZavEdItfcOjJC6wbHA21v35HJWN32DddGTWKNNGDKljUzbCPojmpD+wAw8/DXr5abJ4jFCE0w== 478 | 479 | esbuild@0.27.1: 480 | version "0.27.1" 481 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.1.tgz#56bf43e6a4b4d2004642ec7c091b78de02b0831a" 482 | integrity sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA== 483 | optionalDependencies: 484 | "@esbuild/aix-ppc64" "0.27.1" 485 | "@esbuild/android-arm" "0.27.1" 486 | "@esbuild/android-arm64" "0.27.1" 487 | "@esbuild/android-x64" "0.27.1" 488 | "@esbuild/darwin-arm64" "0.27.1" 489 | "@esbuild/darwin-x64" "0.27.1" 490 | "@esbuild/freebsd-arm64" "0.27.1" 491 | "@esbuild/freebsd-x64" "0.27.1" 492 | "@esbuild/linux-arm" "0.27.1" 493 | "@esbuild/linux-arm64" "0.27.1" 494 | "@esbuild/linux-ia32" "0.27.1" 495 | "@esbuild/linux-loong64" "0.27.1" 496 | "@esbuild/linux-mips64el" "0.27.1" 497 | "@esbuild/linux-ppc64" "0.27.1" 498 | "@esbuild/linux-riscv64" "0.27.1" 499 | "@esbuild/linux-s390x" "0.27.1" 500 | "@esbuild/linux-x64" "0.27.1" 501 | "@esbuild/netbsd-arm64" "0.27.1" 502 | "@esbuild/netbsd-x64" "0.27.1" 503 | "@esbuild/openbsd-arm64" "0.27.1" 504 | "@esbuild/openbsd-x64" "0.27.1" 505 | "@esbuild/openharmony-arm64" "0.27.1" 506 | "@esbuild/sunos-x64" "0.27.1" 507 | "@esbuild/win32-arm64" "0.27.1" 508 | "@esbuild/win32-ia32" "0.27.1" 509 | "@esbuild/win32-x64" "0.27.1" 510 | 511 | fill-range@^7.1.1: 512 | version "7.1.1" 513 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" 514 | integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== 515 | dependencies: 516 | to-regex-range "^5.0.1" 517 | 518 | graceful-fs@^4.2.4: 519 | version "4.2.11" 520 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" 521 | integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== 522 | 523 | is-extglob@^2.1.1: 524 | version "2.1.1" 525 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 526 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 527 | 528 | is-glob@^4.0.3: 529 | version "4.0.3" 530 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 531 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 532 | dependencies: 533 | is-extglob "^2.1.1" 534 | 535 | is-number@^7.0.0: 536 | version "7.0.0" 537 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 538 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 539 | 540 | jiti@^2.6.1: 541 | version "2.6.1" 542 | resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.6.1.tgz#178ef2fc9a1a594248c20627cd820187a4d78d92" 543 | integrity sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ== 544 | 545 | lightningcss-android-arm64@1.30.2: 546 | version "1.30.2" 547 | resolved "https://registry.yarnpkg.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz#6966b7024d39c94994008b548b71ab360eb3a307" 548 | integrity sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A== 549 | 550 | lightningcss-darwin-arm64@1.30.2: 551 | version "1.30.2" 552 | resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz#a5fa946d27c029e48c7ff929e6e724a7de46eb2c" 553 | integrity sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA== 554 | 555 | lightningcss-darwin-x64@1.30.2: 556 | version "1.30.2" 557 | resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz#5ce87e9cd7c4f2dcc1b713f5e8ee185c88d9b7cd" 558 | integrity sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ== 559 | 560 | lightningcss-freebsd-x64@1.30.2: 561 | version "1.30.2" 562 | resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz#6ae1d5e773c97961df5cff57b851807ef33692a5" 563 | integrity sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA== 564 | 565 | lightningcss-linux-arm-gnueabihf@1.30.2: 566 | version "1.30.2" 567 | resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz#62c489610c0424151a6121fa99d77731536cdaeb" 568 | integrity sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA== 569 | 570 | lightningcss-linux-arm64-gnu@1.30.2: 571 | version "1.30.2" 572 | resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz#2a3661b56fe95a0cafae90be026fe0590d089298" 573 | integrity sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A== 574 | 575 | lightningcss-linux-arm64-musl@1.30.2: 576 | version "1.30.2" 577 | resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz#d7ddd6b26959245e026bc1ad9eb6aa983aa90e6b" 578 | integrity sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA== 579 | 580 | lightningcss-linux-x64-gnu@1.30.2: 581 | version "1.30.2" 582 | resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz#5a89814c8e63213a5965c3d166dff83c36152b1a" 583 | integrity sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w== 584 | 585 | lightningcss-linux-x64-musl@1.30.2: 586 | version "1.30.2" 587 | resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz#808c2e91ce0bf5d0af0e867c6152e5378c049728" 588 | integrity sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA== 589 | 590 | lightningcss-win32-arm64-msvc@1.30.2: 591 | version "1.30.2" 592 | resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz#ab4a8a8a2e6a82a4531e8bbb6bf0ff161ee6625a" 593 | integrity sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ== 594 | 595 | lightningcss-win32-x64-msvc@1.30.2: 596 | version "1.30.2" 597 | resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz#f01f382c8e0a27e1c018b0bee316d210eac43b6e" 598 | integrity sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw== 599 | 600 | lightningcss@1.30.2: 601 | version "1.30.2" 602 | resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.30.2.tgz#4ade295f25d140f487d37256f4cd40dc607696d0" 603 | integrity sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ== 604 | dependencies: 605 | detect-libc "^2.0.3" 606 | optionalDependencies: 607 | lightningcss-android-arm64 "1.30.2" 608 | lightningcss-darwin-arm64 "1.30.2" 609 | lightningcss-darwin-x64 "1.30.2" 610 | lightningcss-freebsd-x64 "1.30.2" 611 | lightningcss-linux-arm-gnueabihf "1.30.2" 612 | lightningcss-linux-arm64-gnu "1.30.2" 613 | lightningcss-linux-arm64-musl "1.30.2" 614 | lightningcss-linux-x64-gnu "1.30.2" 615 | lightningcss-linux-x64-musl "1.30.2" 616 | lightningcss-win32-arm64-msvc "1.30.2" 617 | lightningcss-win32-x64-msvc "1.30.2" 618 | 619 | magic-string@^0.30.21: 620 | version "0.30.21" 621 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" 622 | integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== 623 | dependencies: 624 | "@jridgewell/sourcemap-codec" "^1.5.5" 625 | 626 | micromatch@^4.0.5: 627 | version "4.0.8" 628 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" 629 | integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== 630 | dependencies: 631 | braces "^3.0.3" 632 | picomatch "^2.3.1" 633 | 634 | mri@^1.2.0: 635 | version "1.2.0" 636 | resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" 637 | integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== 638 | 639 | nanoid@^3.3.8: 640 | version "3.3.8" 641 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" 642 | integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== 643 | 644 | node-addon-api@^7.0.0: 645 | version "7.1.1" 646 | resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" 647 | integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== 648 | 649 | picocolors@^1.1.1: 650 | version "1.1.1" 651 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" 652 | integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== 653 | 654 | picomatch@^2.3.1: 655 | version "2.3.1" 656 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 657 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 658 | 659 | postcss@^8.4.41: 660 | version "8.5.1" 661 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.1.tgz#e2272a1f8a807fafa413218245630b5db10a3214" 662 | integrity sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ== 663 | dependencies: 664 | nanoid "^3.3.8" 665 | picocolors "^1.1.1" 666 | source-map-js "^1.2.1" 667 | 668 | source-map-js@^1.2.1: 669 | version "1.2.1" 670 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" 671 | integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== 672 | 673 | tailwindcss@4.1.17: 674 | version "4.1.17" 675 | resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-4.1.17.tgz#e6dcb7a9c60cef7522169b5f207ffec2fd652286" 676 | integrity sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q== 677 | 678 | tapable@^2.2.0: 679 | version "2.2.1" 680 | resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" 681 | integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== 682 | 683 | to-regex-range@^5.0.1: 684 | version "5.0.1" 685 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 686 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 687 | dependencies: 688 | is-number "^7.0.0" 689 | 690 | tslib@^2.4.0: 691 | version "2.8.1" 692 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" 693 | integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== 694 | --------------------------------------------------------------------------------