├── backend ├── __init__.py ├── migrations │ └── __init__.py ├── models.py ├── admin.py ├── apps.py ├── urls.py ├── templates │ └── index.html ├── views.py └── tests.py ├── djangovue ├── __init__.py ├── wsgi.py ├── urls.py └── settings.py ├── .babelrc ├── frontend ├── img │ └── logo.png └── js │ ├── main.js │ └── App.vue ├── .gitignore ├── package.json ├── docker-compose.yml ├── manage.py ├── .dockerignore ├── LICENSE ├── vite.config.js ├── pyproject.toml ├── Dockerfile ├── .github └── workflows │ ├── codeql-analysis.yml │ ├── dependencies.yml │ └── ci.yml ├── gemini.md ├── README.md ├── Makefile └── uv.lock /backend/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djangovue/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } -------------------------------------------------------------------------------- /backend/models.py: -------------------------------------------------------------------------------- 1 | # Create your models here. 2 | -------------------------------------------------------------------------------- /backend/admin.py: -------------------------------------------------------------------------------- 1 | # Register your models here. 2 | -------------------------------------------------------------------------------- /frontend/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikebz/djangovue/HEAD/frontend/img/logo.png -------------------------------------------------------------------------------- /backend/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BackendConfig(AppConfig): 5 | name = "backend" 6 | -------------------------------------------------------------------------------- /frontend/js/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | 4 | createApp(App).mount('#app') 5 | -------------------------------------------------------------------------------- /backend/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path("", views.index, name="index"), 7 | ] 8 | -------------------------------------------------------------------------------- /backend/templates/index.html: -------------------------------------------------------------------------------- 1 | {% load django_vite %} 2 | 3 | 4 | 5 | 6 | 7 | Django Vue.js App 8 | 9 | 10 |
11 | {% vite_asset 'frontend/js/main.js' %} 12 | 13 | -------------------------------------------------------------------------------- /backend/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | 4 | def index(request): 5 | """ 6 | serving up the main app page which loads the Vue.js from WebPack 7 | """ 8 | context = { 9 | "data": "value", 10 | } 11 | return render(request, "index.html", context) 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python virtual environments 2 | venv 3 | .venv 4 | 5 | # UV cache and environment files 6 | .python-version 7 | 8 | # VS Code 9 | .vscode 10 | 11 | # Django 12 | db.sqlite3 13 | __pycache__/ 14 | *.py[cod] 15 | *.log 16 | 17 | # Node.js 18 | node_modules/ 19 | 20 | # Build outputs 21 | frontend/bundles/* 22 | frontend/dist/ 23 | build/ 24 | 25 | # Vite 26 | .vite/ -------------------------------------------------------------------------------- /djangovue/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for djangovue 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/1.11/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", "djangovue.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "djangovue", 3 | "version": "0.1.0", 4 | "description": "Django + Vue.js application with modern tooling", 5 | "private": true, 6 | "type": "module", 7 | "author": "Mike Borozdin", 8 | "license": "MIT", 9 | "scripts": { 10 | "dev": "vite", 11 | "build": "vite build", 12 | "preview": "vite preview", 13 | "watch": "vite build --watch" 14 | }, 15 | "dependencies": { 16 | "vue": "^3.5.0" 17 | }, 18 | "devDependencies": { 19 | "@vitejs/plugin-vue": "^5.2.0", 20 | "vite": "^6.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | web: 5 | build: . 6 | ports: 7 | - "8000:8000" 8 | volumes: 9 | - .:/app 10 | - /app/node_modules 11 | - /app/.venv 12 | environment: 13 | - DEBUG=1 14 | - DJANGO_SETTINGS_MODULE=djangovue.settings 15 | command: > 16 | sh -c "uv run python manage.py migrate && 17 | uv run python manage.py runserver 0.0.0.0:8000" 18 | healthcheck: 19 | test: ["CMD", "curl", "-f", "http://localhost:8000/"] 20 | interval: 30s 21 | timeout: 10s 22 | retries: 3 23 | start_period: 40s 24 | 25 | frontend: 26 | image: node:22-alpine 27 | working_dir: /app 28 | volumes: 29 | - .:/app 30 | - /app/node_modules 31 | command: npm run dev 32 | ports: 33 | - "3000:3000" 34 | environment: 35 | - NODE_ENV=development 36 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangovue.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /djangovue/urls.py: -------------------------------------------------------------------------------- 1 | """djangovue URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/5.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import path, include 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | 17 | from django.contrib import admin 18 | from django.urls import include, path 19 | 20 | urlpatterns = [ 21 | path("admin/", admin.site.urls), 22 | path("", include("backend.urls")), 23 | ] 24 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.so 6 | .Python 7 | env/ 8 | venv/ 9 | .venv/ 10 | ENV/ 11 | env.bak/ 12 | venv.bak/ 13 | .pytest_cache/ 14 | 15 | # Django 16 | db.sqlite3 17 | db.sqlite3-journal 18 | media/ 19 | staticfiles/ 20 | 21 | # Node.js 22 | node_modules/ 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # Build outputs 28 | frontend/bundles/ 29 | frontend/dist/ 30 | build/ 31 | 32 | # Development 33 | .vscode/ 34 | .idea/ 35 | *.swp 36 | *.swo 37 | *~ 38 | 39 | # OS 40 | .DS_Store 41 | .DS_Store? 42 | ._* 43 | .Spotlight-V100 44 | .Trashes 45 | ehthumbs.db 46 | Thumbs.db 47 | 48 | # Git 49 | .git/ 50 | .gitignore 51 | 52 | # Docker 53 | Dockerfile 54 | .dockerignore 55 | 56 | # CI/CD 57 | .github/ 58 | 59 | # Logs 60 | *.log 61 | 62 | # Environment variables 63 | .env 64 | .env.local 65 | .env.*.local 66 | 67 | # Temporary files 68 | *.tmp 69 | *.temp 70 | 71 | # UV 72 | .uv/ 73 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mike Borozdin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import path from 'path' 4 | 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | 8 | // Entry point 9 | build: { 10 | outDir: 'frontend/dist', 11 | emptyOutDir: true, 12 | manifest: true, 13 | rollupOptions: { 14 | input: 'frontend/js/main.js', 15 | output: { 16 | // Keep similar naming convention for Django integration 17 | entryFileNames: '[name]-[hash].js', 18 | chunkFileNames: '[name]-[hash].js', 19 | assetFileNames: '[name]-[hash].[ext]' 20 | } 21 | } 22 | }, 23 | 24 | // Development server 25 | server: { 26 | host: '127.0.0.1', 27 | port: 3000, 28 | open: false, 29 | cors: true 30 | }, 31 | 32 | // Asset handling 33 | resolve: { 34 | alias: { 35 | '@': path.resolve(__dirname, 'frontend'), 36 | '~': path.resolve(__dirname, 'frontend') 37 | } 38 | }, 39 | 40 | // Public assets 41 | publicDir: false, // Don't copy public assets to dist 42 | 43 | // CSS handling 44 | css: { 45 | devSourcemap: true 46 | }, 47 | 48 | // Define environment variables 49 | define: { 50 | __VUE_OPTIONS_API__: true, 51 | __VUE_PROD_DEVTOOLS__: false 52 | } 53 | }) 54 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "djangovue" 3 | version = "0.1.0" 4 | description = "Django + Vue application" 5 | authors = [ 6 | {name = "Mike Borozdin", email = "mike@example.com"}, 7 | ] 8 | readme = "README.md" 9 | license = {text = "MIT"} 10 | requires-python = ">=3.11" 11 | dependencies = [ 12 | "django>=5.1,<5.2", 13 | "django-vite>=3.1.0", 14 | "pytz>=2023.3", 15 | ] 16 | 17 | [project.optional-dependencies] 18 | dev = [ 19 | "ruff>=0.1.0", 20 | "black>=23.0.0", 21 | ] 22 | 23 | [build-system] 24 | requires = ["hatchling"] 25 | build-backend = "hatchling.build" 26 | 27 | [tool.ruff] 28 | line-length = 88 29 | target-version = "py313" 30 | 31 | [tool.ruff.lint] 32 | select = [ 33 | "E", # pycodestyle errors 34 | "W", # pycodestyle warnings 35 | "F", # pyflakes 36 | "I", # isort 37 | "B", # flake8-bugbear 38 | "C4", # flake8-comprehensions 39 | "UP", # pyupgrade 40 | ] 41 | ignore = [ 42 | "E501", # line too long, handled by black 43 | "B008", # do not perform function calls in argument defaults 44 | ] 45 | 46 | [tool.ruff.lint.isort] 47 | known-first-party = ["djangovue", "backend"] 48 | 49 | [tool.black] 50 | line-length = 88 51 | target-version = ['py313'] 52 | include = '\.pyi?$' 53 | extend-exclude = ''' 54 | /( 55 | # directories 56 | \.eggs 57 | | \.git 58 | | \.hg 59 | | \.mypy_cache 60 | | \.tox 61 | | \.venv 62 | | build 63 | | dist 64 | )/ 65 | ''' 66 | -------------------------------------------------------------------------------- /frontend/js/App.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 32 | 33 | 61 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Multi-stage build for Django + Vue.js application 2 | FROM node:22-alpine AS frontend-builder 3 | 4 | # Set working directory 5 | WORKDIR /app 6 | 7 | # Copy package files 8 | COPY package*.json ./ 9 | 10 | # Install dependencies 11 | RUN npm ci --only=production 12 | 13 | # Copy frontend source 14 | COPY frontend/ ./frontend/ 15 | COPY vite.config.js ./ 16 | 17 | # Build frontend 18 | RUN npm run build 19 | 20 | # Python application stage 21 | FROM python:3.13-slim 22 | 23 | # Set environment variables 24 | ENV PYTHONDONTWRITEBYTECODE=1 \ 25 | PYTHONUNBUFFERED=1 \ 26 | UV_SYSTEM_PYTHON=1 27 | 28 | # Install system dependencies 29 | RUN apt-get update \ 30 | && apt-get install -y --no-install-recommends \ 31 | curl \ 32 | build-essential \ 33 | && rm -rf /var/lib/apt/lists/* 34 | 35 | # Install UV 36 | RUN pip install uv 37 | 38 | # Set work directory 39 | WORKDIR /app 40 | 41 | # Copy Python dependency files 42 | COPY pyproject.toml uv.lock ./ 43 | 44 | # Install Python dependencies 45 | RUN uv sync --frozen --no-dev 46 | 47 | # Copy project 48 | COPY . . 49 | 50 | # Copy built frontend from previous stage 51 | COPY --from=frontend-builder /app/frontend/dist/ ./frontend/dist/ 52 | 53 | # Collect static files 54 | RUN python manage.py collectstatic --noinput 55 | 56 | # Create non-root user 57 | RUN adduser --disabled-password --gecos '' appuser && \ 58 | chown -R appuser:appuser /app 59 | USER appuser 60 | 61 | # Expose port 62 | EXPOSE 8000 63 | 64 | # Health check 65 | HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \ 66 | CMD curl -f http://localhost:8000/ || exit 1 67 | 68 | # Run application 69 | CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] 70 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '29 6 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'javascript', 'python' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /gemini.md: -------------------------------------------------------------------------------- 1 | ## role and expertise 2 | You are a senior software engineer who follows Kent Beck's Test-Driven Development (TDD). Your purpose is to guide development following these methodologies precisely. You leave comments as to why some things are designed or implemented as such. If you need to link out to an extenal source you leave the URL in the comments. 3 | 4 | ## core development principles 5 | - Always follow the TDD cycle: Red → Green → Refactor 6 | - Write the simplest failing test first 7 | - Implement the minimum code needed to make tests pass 8 | - Refactor only after tests are passing 9 | - Follow Beck's "Tidy First" approach by separating structural changes from behavioral changes 10 | - Maintain high code quality throughout development 11 | 12 | ## tdd methodology guidance 13 | - Start by writing a failing test that defines a small increment of functionality 14 | - Use meaningful test names that describe behavior (e.g., "shouldSumTwoPositiveNumbers") 15 | - Make test failures clear and informative 16 | - Write just enough code to make the test pass - no more 17 | - Once tests pass, consider if refactoring is needed 18 | - Repeat the cycle for new functionality 19 | 20 | ## tidy first approach 21 | - Separate all changes into two distinct types: 22 | 1. STRUCTURAL CHANGES: Rearranging code without changing behavior (renaming, extracting methods, moving code) 23 | 2. BEHAVIORAL CHANGES: Adding or modifying actual functionality 24 | - Never mix structural and behavioral changes in the same commit 25 | - Always make structural changes first when both are needed 26 | - Validate structural changes do not alter behavior by running tests before and after 27 | ## commit discipline 28 | - Only commit when: 29 | 1. ALL tests are passing 30 | 2. ALL compiler/linter warnings have been resolved 31 | 3. The change represents a single logical unit of work 32 | 4. Commit messages clearly state whether the commit contains structural or behavioral changes 33 | - Use small, frequent commits rather than large, infrequent ones 34 | ## code quality standards 35 | - Eliminate duplication ruthlessly 36 | - Express intent clearly through naming and structure 37 | - Make dependencies explicit 38 | - Keep methods small and focused on a single responsibility 39 | - Minimize state and side effects 40 | - Use the simplest solution that could possibly work 41 | # refactoring guidelines 42 | - Refactor only when tests are passing (in the "Green" phase) 43 | - Use established refactoring patterns with their proper names 44 | - Make one refactoring change at a time 45 | - Run tests after each refactoring step 46 | - Prioritize refactorings that remove duplication or improve clarity 47 | # example workflow 48 | When approaching a new feature: 49 | 1. Write a simple failing test for a small part of the feature 50 | 2. Implement the bare minimum to make it pass 51 | 3. Run tests to confirm they pass (Green) 52 | 4. Make any necessary structural changes (Tidy First), running tests after each change 53 | 5. Commit structural changes separately 54 | 6. Add another test for the next small increment of functionality 55 | 7. Repeat until the feature is complete, committing behavioral changes separately from structural ones 56 | Follow this process precisely, always prioritizing clean, well-tested code over quick implementation. 57 | Always write one test at a time, make it run, then improve structure. Always run all the tests (except long-running tests) each time. 58 | -------------------------------------------------------------------------------- /djangovue/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for djangovue project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.11.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.11/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "(0&w0&rj&i7^jmo9#g*!#fua2q$j6umcnegqaw+gyzi-hal5!=" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | "django.contrib.admin", 35 | "django.contrib.auth", 36 | "django.contrib.contenttypes", 37 | "django.contrib.sessions", 38 | "django.contrib.messages", 39 | "django.contrib.staticfiles", 40 | "backend", 41 | "django_vite", 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | "django.middleware.security.SecurityMiddleware", 46 | "django.contrib.sessions.middleware.SessionMiddleware", 47 | "django.middleware.common.CommonMiddleware", 48 | "django.middleware.csrf.CsrfViewMiddleware", 49 | "django.contrib.auth.middleware.AuthenticationMiddleware", 50 | "django.contrib.messages.middleware.MessageMiddleware", 51 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 52 | ] 53 | 54 | ROOT_URLCONF = "djangovue.urls" 55 | 56 | TEMPLATES = [ 57 | { 58 | "BACKEND": "django.template.backends.django.DjangoTemplates", 59 | "DIRS": [], 60 | "APP_DIRS": True, 61 | "OPTIONS": { 62 | "context_processors": [ 63 | "django.template.context_processors.debug", 64 | "django.template.context_processors.request", 65 | "django.contrib.auth.context_processors.auth", 66 | "django.contrib.messages.context_processors.messages", 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = "djangovue.wsgi.application" 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases 77 | 78 | DATABASES = { 79 | "default": { 80 | "ENGINE": "django.db.backends.sqlite3", 81 | "NAME": os.path.join(BASE_DIR, "db.sqlite3"), 82 | } 83 | } 84 | 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 88 | 89 | AUTH_PASSWORD_VALIDATORS = [ 90 | { 91 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 92 | }, 93 | { 94 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 95 | }, 96 | { 97 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 98 | }, 99 | { 100 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 101 | }, 102 | ] 103 | 104 | 105 | # Internationalization 106 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 107 | 108 | LANGUAGE_CODE = "en-us" 109 | 110 | TIME_ZONE = "UTC" 111 | 112 | USE_I18N = True 113 | 114 | USE_L10N = True 115 | 116 | USE_TZ = True 117 | 118 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 121 | 122 | STATIC_URL = "/static/" 123 | 124 | STATICFILES_DIRS = [ 125 | os.path.join(BASE_DIR, "frontend"), 126 | os.path.join(BASE_DIR, "frontend", "dist"), 127 | ] 128 | 129 | # Vite configuration 130 | DJANGO_VITE = { 131 | "default": { 132 | "dev_mode": False, # Force production mode for now 133 | "dev_server_host": "127.0.0.1", 134 | "dev_server_port": 3000, 135 | "manifest_path": os.path.join( 136 | BASE_DIR, "frontend", "dist", ".vite", "manifest.json" 137 | ), 138 | "static_url_prefix": "", 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /backend/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import Client, TestCase 2 | 3 | 4 | class IndexViewTest(TestCase): 5 | """ 6 | Test the main index view that serves the Vue.js application. 7 | Following TDD principles with clear test naming and structure. 8 | """ 9 | 10 | def setUp(self): 11 | """Set up test client for each test method.""" 12 | self.client = Client() 13 | 14 | def test_index_view_returns_200_status_code(self): 15 | """ 16 | GIVEN: A request to the root URL 17 | WHEN: The index view is called 18 | THEN: It should return a 200 status code 19 | """ 20 | response = self.client.get("/") 21 | self.assertEqual(response.status_code, 200) 22 | 23 | def test_index_view_uses_correct_template(self): 24 | """ 25 | GIVEN: A request to the root URL 26 | WHEN: The index view is called 27 | THEN: It should use the index.html template 28 | """ 29 | response = self.client.get("/") 30 | self.assertTemplateUsed(response, "index.html") 31 | 32 | def test_index_view_contains_vue_app_div(self): 33 | """ 34 | GIVEN: A request to the root URL 35 | WHEN: The index view is called 36 | THEN: The response should contain the Vue.js app mount point 37 | """ 38 | response = self.client.get("/") 39 | self.assertContains(response, '
') 40 | 41 | def test_index_view_contains_vue_js_title(self): 42 | """ 43 | GIVEN: A request to the root URL 44 | WHEN: The index view is called 45 | THEN: The response should contain the Vue.js App title 46 | """ 47 | response = self.client.get("/") 48 | self.assertContains(response, "Vue.js App") 49 | 50 | def test_index_view_contains_javascript_bundle(self): 51 | """ 52 | GIVEN: A request to the root URL 53 | WHEN: The index view is called 54 | THEN: The response should include JavaScript bundle references 55 | """ 56 | response = self.client.get("/") 57 | self.assertContains(response, ".js") 58 | 59 | def test_index_view_contains_css_bundle(self): 60 | """ 61 | GIVEN: A request to the root URL 62 | WHEN: The index view is called 63 | THEN: The response should include CSS bundle references 64 | """ 65 | response = self.client.get("/") 66 | self.assertContains(response, ".css") 67 | 68 | 69 | class URLConfigTest(TestCase): 70 | """ 71 | Test URL configuration and routing. 72 | Ensures proper URL patterns are working. 73 | """ 74 | 75 | def test_root_url_resolves_to_index_view(self): 76 | """ 77 | GIVEN: The root URL pattern 78 | WHEN: A request is made to '/' 79 | THEN: It should resolve to the index view 80 | """ 81 | response = self.client.get("/") 82 | # Test that the response is successful 83 | self.assertEqual(response.status_code, 200) 84 | # Test that it contains expected content 85 | self.assertContains(response, "Vue.js App") 86 | 87 | 88 | class ViteIntegrationTest(TestCase): 89 | """ 90 | Test Django-Vite integration. 91 | Ensures build assets are properly integrated. 92 | """ 93 | 94 | def test_vite_assets_are_loaded(self): 95 | """ 96 | GIVEN: A built frontend with Vite 97 | WHEN: The index page is loaded 98 | THEN: Vite-generated assets should be referenced 99 | """ 100 | response = self.client.get("/") 101 | # Check that the response contains script tags 102 | self.assertContains(response, " # Run Django commands 84 | uv add # Add production dependency 85 | uv add --dev # Add development dependency 86 | uv sync # Install dependencies 87 | ``` 88 | 89 | ## � CI/CD & Deployment 90 | 91 | ### GitHub Actions Workflows 92 | 93 | This project includes comprehensive CI/CD pipelines: 94 | 95 | #### **Continuous Integration** (`ci.yml`) 96 | - **Python Linting**: Ruff linting and Black formatting 97 | - **Frontend Linting**: Build validation and asset checking 98 | - **Python Tests**: Django test suite 99 | - **Integration Tests**: Full application testing 100 | - **Security Scanning**: Dependency vulnerability checks 101 | 102 | #### **Dependency Management** (`dependencies.yml`) 103 | - **Weekly Updates**: Automated dependency updates 104 | - **Security Monitoring**: Vulnerability scanning and reporting 105 | - **Pull Requests**: Automated PRs for dependency updates 106 | 107 | ### Local Development with Docker 108 | 109 | ```bash 110 | # Build and run with Docker 111 | make docker-build 112 | make docker-run 113 | 114 | # Development environment with Docker Compose 115 | make docker-dev 116 | 117 | # Or manually: 118 | docker-compose up --build 119 | ``` 120 | 121 | ### Manual Testing 122 | 123 | ```bash 124 | # Run all quality checks 125 | make qa 126 | 127 | # Build production assets 128 | make prod-build 129 | 130 | # Test Docker build 131 | make docker-build 132 | ``` 133 | 134 | ## �📦 What's New 135 | 136 | - **Python 3.11+** required (upgraded from 3.10+, using Python 3.13.5) 137 | - **Django 5.1** (upgraded from 4.2) 138 | - **UV package manager** for faster dependency management 139 | - **Vite 6.x** build tool (replaced Webpack 3.x) - 50x faster dev server 140 | - **Vue 3.5** (upgraded from Vue 2.x, latest Vue with Composition API) 141 | - **Hot Module Replacement (HMR)** for instant development feedback 142 | - **Modern linting** with Ruff (replaces pylint) 143 | - **Code formatting** with Black 144 | - **Improved URL patterns** (modern Django path() syntax) 145 | - **Makefile** for development automation 146 | - **GitHub Actions CI/CD** with comprehensive testing 147 | - **Docker support** for containerized deployment 148 | - **Automated dependency updates** and security monitoring 149 | 150 | ## 📝 Migration Notes 151 | 152 | If you're upgrading from the old setup: 153 | - `requirements.txt` is now replaced by `pyproject.toml` 154 | - Use `uv sync` instead of `pip install -r requirements.txt` 155 | - Virtual environment is automatically managed by UV in `.venv/` 156 | - All dependencies are pinned in `uv.lock` for reproducible builds 157 | - **Python 3.11+** is now required (upgraded from 3.10+) 158 | - **GitHub Actions** automatically test all changes 159 | - **Docker support** available for containerized deployments 160 | - **Automated security scanning** monitors for vulnerabilities 161 | 162 | ## Sources 163 | This only worked because people before me created some wonderful examples. 164 | - https://github.com/djstein/vue-django-webpack 165 | - https://github.com/ezhome/django-webpack-loader 166 | -------------------------------------------------------------------------------- /.github/workflows/dependencies.yml: -------------------------------------------------------------------------------- 1 | name: Dependency Updates 2 | 3 | on: 4 | schedule: 5 | # Run weekly on Mondays at 9 AM UTC 6 | - cron: '0 9 * * 1' 7 | workflow_dispatch: # Allow manual triggering 8 | 9 | jobs: 10 | update-python-deps: 11 | name: Update Python Dependencies 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | with: 18 | token: ${{ secrets.GITHUB_TOKEN }} 19 | 20 | - name: Install UV 21 | uses: astral-sh/setup-uv@v4 22 | with: 23 | version: "latest" 24 | 25 | - name: Set up Python 26 | run: uv python install 3.13 27 | 28 | - name: Update UV lock file 29 | run: | 30 | uv sync --extra dev 31 | uv lock --upgrade 32 | 33 | - name: Setup Node.js for frontend build 34 | uses: actions/setup-node@v4 35 | with: 36 | node-version: '22' 37 | cache: 'npm' 38 | 39 | - name: Install frontend dependencies 40 | run: npm ci 41 | 42 | - name: Build frontend for Django integration 43 | run: npm run build 44 | 45 | - name: Run tests with updated dependencies 46 | run: | 47 | uv sync --extra dev 48 | uv run python manage.py check 49 | uv run python manage.py test 50 | uv run ruff check backend/ djangovue/ 51 | 52 | - name: Create Pull Request for Python updates 53 | uses: peter-evans/create-pull-request@v5 54 | with: 55 | token: ${{ secrets.GITHUB_TOKEN }} 56 | commit-message: 'chore: update Python dependencies' 57 | title: 'chore: Update Python dependencies' 58 | body: | 59 | ## Python Dependency Updates 60 | 61 | This PR updates Python dependencies to their latest compatible versions. 62 | 63 | ### Changes 64 | - Updated `uv.lock` with latest dependency versions 65 | - All tests passing with new dependencies 66 | - Linting checks passed 67 | 68 | ### Testing 69 | - [x] Django system checks pass 70 | - [x] All tests pass 71 | - [x] Linting passes 72 | 73 | Auto-generated by GitHub Actions. 74 | branch: chore/update-python-deps 75 | delete-branch: true 76 | 77 | update-frontend-deps: 78 | name: Update Frontend Dependencies 79 | runs-on: ubuntu-latest 80 | 81 | steps: 82 | - name: Checkout code 83 | uses: actions/checkout@v4 84 | with: 85 | token: ${{ secrets.GITHUB_TOKEN }} 86 | 87 | - name: Setup Node.js 88 | uses: actions/setup-node@v4 89 | with: 90 | node-version: '22' 91 | cache: 'npm' 92 | 93 | - name: Update npm dependencies 94 | run: | 95 | npm update 96 | npm audit fix --audit-level=moderate || true 97 | 98 | - name: Test frontend build 99 | run: | 100 | npm run build 101 | 102 | - name: Create Pull Request for frontend updates 103 | uses: peter-evans/create-pull-request@v5 104 | with: 105 | token: ${{ secrets.GITHUB_TOKEN }} 106 | commit-message: 'chore: update frontend dependencies' 107 | title: 'chore: Update frontend dependencies' 108 | body: | 109 | ## Frontend Dependency Updates 110 | 111 | This PR updates frontend dependencies to their latest compatible versions. 112 | 113 | ### Changes 114 | - Updated `package-lock.json` with latest dependency versions 115 | - Frontend build successful with new dependencies 116 | - Security vulnerabilities addressed where possible 117 | 118 | ### Testing 119 | - [x] Frontend build passes 120 | - [x] Vite configuration compatible 121 | - [x] Vue.js application builds successfully 122 | 123 | Auto-generated by GitHub Actions. 124 | branch: chore/update-frontend-deps 125 | delete-branch: true 126 | 127 | security-updates: 128 | name: Security Updates 129 | runs-on: ubuntu-latest 130 | 131 | steps: 132 | - name: Checkout code 133 | uses: actions/checkout@v4 134 | with: 135 | token: ${{ secrets.GITHUB_TOKEN }} 136 | 137 | - name: Install UV 138 | uses: astral-sh/setup-uv@v4 139 | with: 140 | version: "latest" 141 | 142 | - name: Set up Python 143 | run: uv python install 3.13 144 | 145 | - name: Setup Node.js 146 | uses: actions/setup-node@v4 147 | with: 148 | node-version: '22' 149 | cache: 'npm' 150 | 151 | - name: Install dependencies 152 | run: | 153 | uv sync --extra dev 154 | npm ci 155 | 156 | - name: Check for Python security issues 157 | run: | 158 | uv add --dev safety 159 | uv run safety check --json > security-report-python.json || true 160 | 161 | - name: Check for frontend security issues 162 | run: | 163 | npm audit --json > security-report-frontend.json || true 164 | 165 | - name: Upload security reports 166 | uses: actions/upload-artifact@v4 167 | with: 168 | name: security-reports 169 | path: | 170 | security-report-*.json 171 | retention-days: 30 172 | 173 | - name: Create security issue if vulnerabilities found 174 | run: | 175 | if [ -s security-report-python.json ] || [ -s security-report-frontend.json ]; then 176 | echo "Security vulnerabilities detected - manual review required" 177 | echo "Check the security-reports artifact for details" 178 | fi 179 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master, main ] 6 | pull_request: 7 | branches: [ master, main ] 8 | 9 | jobs: 10 | lint-python: 11 | name: Python Linting 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | 18 | - name: Install UV 19 | uses: astral-sh/setup-uv@v4 20 | with: 21 | version: "latest" 22 | 23 | - name: Set up Python 24 | run: uv python install 3.13 25 | 26 | - name: Install Python dependencies 27 | run: uv sync --extra dev 28 | 29 | - name: Run Ruff linter 30 | run: uv run ruff check backend/ djangovue/ 31 | 32 | - name: Run Ruff formatter check 33 | run: uv run ruff format --check backend/ djangovue/ 34 | 35 | - name: Run Black formatter check 36 | run: uv run black --check backend/ djangovue/ 37 | 38 | lint-frontend: 39 | name: Frontend Linting 40 | runs-on: ubuntu-latest 41 | 42 | steps: 43 | - name: Checkout code 44 | uses: actions/checkout@v4 45 | 46 | - name: Setup Node.js 47 | uses: actions/setup-node@v4 48 | with: 49 | node-version: '22' 50 | cache: 'npm' 51 | 52 | - name: Install frontend dependencies 53 | run: npm ci 54 | 55 | - name: Build frontend 56 | run: npm run build 57 | 58 | - name: Check for build artifacts 59 | run: | 60 | if [ ! -d "frontend/dist" ]; then 61 | echo "Frontend build failed - no dist directory found" 62 | exit 1 63 | fi 64 | if [ ! -f "frontend/dist/.vite/manifest.json" ]; then 65 | echo "Frontend build failed - no manifest.json found" 66 | exit 1 67 | fi 68 | echo "✅ Frontend build successful" 69 | 70 | test-python: 71 | name: Python Tests 72 | runs-on: ubuntu-latest 73 | 74 | steps: 75 | - name: Checkout code 76 | uses: actions/checkout@v4 77 | 78 | - name: Install UV 79 | uses: astral-sh/setup-uv@v4 80 | with: 81 | version: "latest" 82 | 83 | - name: Set up Python 84 | run: uv python install 3.13 85 | 86 | - name: Install Python dependencies 87 | run: uv sync --extra dev 88 | 89 | - name: Setup Node.js for frontend build 90 | uses: actions/setup-node@v4 91 | with: 92 | node-version: '22' 93 | cache: 'npm' 94 | 95 | - name: Install frontend dependencies 96 | run: npm ci 97 | 98 | - name: Build frontend for Django integration 99 | run: npm run build 100 | 101 | - name: Run Django system checks 102 | run: uv run python manage.py check 103 | 104 | - name: Run Django tests 105 | run: uv run python manage.py test 106 | 107 | integration-test: 108 | name: Integration Tests 109 | runs-on: ubuntu-latest 110 | needs: [lint-python, lint-frontend, test-python] 111 | 112 | steps: 113 | - name: Checkout code 114 | uses: actions/checkout@v4 115 | 116 | - name: Install UV 117 | uses: astral-sh/setup-uv@v4 118 | with: 119 | version: "latest" 120 | 121 | - name: Set up Python 122 | run: uv python install 3.13 123 | 124 | - name: Setup Node.js 125 | uses: actions/setup-node@v4 126 | with: 127 | node-version: '22' 128 | cache: 'npm' 129 | 130 | - name: Install all dependencies 131 | run: | 132 | uv sync --extra dev 133 | npm ci 134 | 135 | - name: Build frontend 136 | run: npm run build 137 | 138 | - name: Run Django migrations 139 | run: uv run python manage.py migrate 140 | 141 | - name: Test Django template rendering 142 | run: | 143 | uv run python -c " 144 | import os 145 | import django 146 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangovue.settings') 147 | django.setup() 148 | from django.template.loader import render_to_string 149 | result = render_to_string('index.html', {}) 150 | print('✅ Template rendered successfully') 151 | print('📄 Length:', len(result), 'characters') 152 | assert 'Vue.js App' in result, 'Template missing Vue.js App title' 153 | assert '
' in result, 'Template missing Vue.js mount point' 154 | assert '.js' in result, 'Template missing JavaScript bundle' 155 | print('✅ All template assertions passed') 156 | " 157 | 158 | - name: Test development server startup 159 | run: | 160 | # Start Django server in background 161 | uv run python manage.py runserver & 162 | SERVER_PID=$! 163 | 164 | # Wait for server to start 165 | sleep 5 166 | 167 | # Test server responds 168 | curl -f http://127.0.0.1:8000/ || (echo "❌ Server not responding"; exit 1) 169 | 170 | # Kill server 171 | kill $SERVER_PID 172 | 173 | echo "✅ Development server test passed" 174 | 175 | security-scan: 176 | name: Security Scanning 177 | runs-on: ubuntu-latest 178 | 179 | steps: 180 | - name: Checkout code 181 | uses: actions/checkout@v4 182 | 183 | - name: Install UV 184 | uses: astral-sh/setup-uv@v4 185 | with: 186 | version: "latest" 187 | 188 | - name: Set up Python 189 | run: uv python install 3.13 190 | 191 | - name: Install Python dependencies 192 | run: uv sync --extra dev 193 | 194 | - name: Run safety check for Python dependencies 195 | run: | 196 | uv add --dev safety 197 | uv run safety check --json || echo "⚠️ Security vulnerabilities found - review required" 198 | 199 | - name: Setup Node.js for security audit 200 | uses: actions/setup-node@v4 201 | with: 202 | node-version: '22' 203 | cache: 'npm' 204 | 205 | - name: Install frontend dependencies 206 | run: npm ci 207 | 208 | - name: Run npm security audit 209 | run: | 210 | npm audit --audit-level=moderate || echo "⚠️ Frontend security issues found - review required" 211 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for djangovue project 2 | # Uses UV for Python package management 3 | 4 | .PHONY: help install run migrate shell test lint format check clean setup frontend all 5 | 6 | # Default target 7 | .DEFAULT_GOAL := help 8 | 9 | # Colors for output 10 | BLUE := \033[34m 11 | GREEN := \033[32m 12 | YELLOW := \033[33m 13 | RED := \033[31m 14 | RESET := \033[0m 15 | 16 | help: ## Show this help message 17 | @echo "$(BLUE)Django + Vue Development Commands$(RESET)" 18 | @echo "" 19 | @echo "$(GREEN)Python/Django:$(RESET)" 20 | @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " $(YELLOW)%-12s$(RESET) %s\n", $$1, $$2}' $(MAKEFILE_LIST) | grep -E "(install|run|migrate|shell|test|lint|format|check|clean|setup|status)" 21 | @echo "" 22 | @echo "$(GREEN)Frontend:$(RESET)" 23 | @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " $(YELLOW)%-12s$(RESET) %s\n", $$1, $$2}' $(MAKEFILE_LIST) | grep -E "(frontend-|build|watch|preview)" 24 | @echo "" 25 | @echo "$(GREEN)Compound commands:$(RESET)" 26 | @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " $(YELLOW)%-12s$(RESET) %s\n", $$1, $$2}' $(MAKEFILE_LIST) | grep -E "(all|dev)" 27 | @echo "" 28 | @echo "$(BLUE)UV Commands:$(RESET)" 29 | @echo " uv run python manage.py # Run Django commands" 30 | @echo " uv add # Add production dependency" 31 | @echo " uv add --dev # Add development dependency" 32 | 33 | install: ## Install Python dependencies 34 | @echo "$(BLUE)Installing dependencies...$(RESET)" 35 | uv sync --extra dev 36 | 37 | run: ## Start Django development server 38 | @echo "$(BLUE)Starting Django development server...$(RESET)" 39 | uv run python manage.py runserver 40 | 41 | migrate: ## Run Django migrations 42 | @echo "$(BLUE)Running Django migrations...$(RESET)" 43 | uv run python manage.py migrate 44 | 45 | makemigrations: ## Create new Django migrations 46 | @echo "$(BLUE)Creating Django migrations...$(RESET)" 47 | uv run python manage.py makemigrations 48 | 49 | shell: ## Start Django shell 50 | @echo "$(BLUE)Starting Django shell...$(RESET)" 51 | uv run python manage.py shell 52 | 53 | test: ## Run Django tests 54 | @echo "$(BLUE)Running tests...$(RESET)" 55 | uv run python manage.py test 56 | 57 | lint: ## Run code linter (ruff) 58 | @echo "$(BLUE)Running linter...$(RESET)" 59 | uv run ruff check backend/ djangovue/ 60 | 61 | lint-fix: ## Run linter with auto-fix 62 | @echo "$(BLUE)Running linter with auto-fix...$(RESET)" 63 | uv run ruff check --fix backend/ djangovue/ 64 | 65 | format: ## Format code with black 66 | @echo "$(BLUE)Formatting code...$(RESET)" 67 | uv run black backend/ djangovue/ 68 | 69 | format-check: ## Check code formatting without making changes 70 | @echo "$(BLUE)Checking code formatting...$(RESET)" 71 | uv run black --check backend/ djangovue/ 72 | 73 | check: ## Run Django system checks 74 | @echo "$(BLUE)Running Django system checks...$(RESET)" 75 | uv run python manage.py check 76 | 77 | status: ## Show project status and environment info 78 | @echo "$(BLUE)Project Status:$(RESET)" 79 | @echo "Python version: $$(uv run python --version)" 80 | @echo "UV version: $$(uv --version)" 81 | @echo "Django version: $$(uv run python -c 'import django; print(django.get_version())')" 82 | @echo "Virtual environment: $$(if [ -d .venv ]; then echo "✓ Active (.venv)"; else echo "✗ Not found"; fi)" 83 | @echo "Dependencies: $$(if [ -f uv.lock ]; then echo "✓ Locked (uv.lock)"; else echo "✗ Not locked"; fi)" 84 | @echo "Node.js: $$(if command -v node >/dev/null 2>&1; then echo "✓ $$(node --version)"; else echo "✗ Not installed"; fi)" 85 | @echo "NPM packages: $$(if [ -d node_modules ]; then echo "✓ Installed"; else echo "✗ Not installed"; fi)" 86 | 87 | collectstatic: ## Collect static files 88 | @echo "$(BLUE)Collecting static files...$(RESET)" 89 | uv run python manage.py collectstatic --noinput 90 | 91 | superuser: ## Create Django superuser 92 | @echo "$(BLUE)Creating Django superuser...$(RESET)" 93 | uv run python manage.py createsuperuser 94 | 95 | # Frontend commands 96 | frontend-install: ## Install Node.js dependencies 97 | @echo "$(BLUE)Installing Node.js dependencies...$(RESET)" 98 | npm install 99 | 100 | frontend-dev: ## Start Vite development server 101 | @echo "$(BLUE)Starting Vite development server...$(RESET)" 102 | npm run dev 103 | 104 | frontend-build: ## Build frontend for production 105 | @echo "$(BLUE)Building frontend...$(RESET)" 106 | npm run build 107 | 108 | frontend-watch: ## Watch frontend files for changes 109 | @echo "$(BLUE)Watching frontend files...$(RESET)" 110 | npm run watch 111 | 112 | frontend-preview: ## Preview production build 113 | @echo "$(BLUE)Starting preview server...$(RESET)" 114 | npm run preview 115 | 116 | # Cleanup commands 117 | clean: ## Clean up generated files 118 | @echo "$(BLUE)Cleaning up...$(RESET)" 119 | find . -type f -name "*.pyc" -delete 120 | find . -type d -name "__pycache__" -delete 121 | find . -type d -name "*.egg-info" -exec rm -rf {} + 122 | rm -rf frontend/bundles/* 123 | 124 | clean-all: clean ## Clean everything including dependencies 125 | @echo "$(BLUE)Cleaning everything...$(RESET)" 126 | rm -rf .venv/ 127 | rm -rf node_modules/ 128 | rm -f uv.lock 129 | rm -f package-lock.json 130 | 131 | # Development setup 132 | setup: install frontend-install migrate ## Initial project setup 133 | @echo "$(GREEN)Project setup complete!$(RESET)" 134 | @echo "Run '$(YELLOW)make dev$(RESET)' to start development" 135 | 136 | dev: migrate frontend-build ## Prepare for development 137 | @echo "$(GREEN)Development environment ready!$(RESET)" 138 | @echo "Run '$(YELLOW)make run$(RESET)' to start the server" 139 | 140 | dev-full: frontend-build run ## Build frontend and start Django server 141 | @echo "$(GREEN)Starting full development environment...$(RESET)" 142 | 143 | all: setup ## Setup everything and start development server 144 | @echo "$(GREEN)Starting development server...$(RESET)" 145 | make run 146 | 147 | # Quality assurance 148 | qa: lint-fix format test check ## Run all quality checks and fixes 149 | @echo "$(GREEN)All quality checks passed!$(RESET)" 150 | 151 | # Production commands 152 | prod-build: install frontend-build collectstatic ## Build for production 153 | @echo "$(GREEN)Production build complete!$(RESET)" 154 | 155 | docker-build: ## Build Docker image 156 | @echo "$(BLUE)Building Docker image...$(RESET)" 157 | docker build -t djangovue:latest . 158 | 159 | docker-run: docker-build ## Build and run Docker container 160 | @echo "$(BLUE)Running Docker container...$(RESET)" 161 | docker run --rm -p 8000:8000 djangovue:latest 162 | 163 | docker-dev: ## Run development environment in Docker 164 | @echo "$(BLUE)Starting development environment in Docker...$(RESET)" 165 | docker-compose up --build 166 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 3 3 | requires-python = ">=3.11" 4 | 5 | [[package]] 6 | name = "asgiref" 7 | version = "3.11.0" 8 | source = { registry = "https://pypi.org/simple" } 9 | 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" } 10 | wheels = [ 11 | { 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" }, 12 | ] 13 | 14 | [[package]] 15 | name = "black" 16 | version = "25.12.0" 17 | source = { registry = "https://pypi.org/simple" } 18 | dependencies = [ 19 | { name = "click" }, 20 | { name = "mypy-extensions" }, 21 | { name = "packaging" }, 22 | { name = "pathspec" }, 23 | { name = "platformdirs" }, 24 | { name = "pytokens" }, 25 | ] 26 | sdist = { url = "https://files.pythonhosted.org/packages/c4/d9/07b458a3f1c525ac392b5edc6b191ff140b596f9d77092429417a54e249d/black-25.12.0.tar.gz", hash = "sha256:8d3dd9cea14bff7ddc0eb243c811cdb1a011ebb4800a5f0335a01a68654796a7", size = 659264, upload-time = "2025-12-08T01:40:52.501Z" } 27 | wheels = [ 28 | { url = "https://files.pythonhosted.org/packages/60/ad/7ac0d0e1e0612788dbc48e62aef8a8e8feffac7eb3d787db4e43b8462fa8/black-25.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0cfa263e85caea2cff57d8f917f9f51adae8e20b610e2b23de35b5b11ce691a", size = 1877003, upload-time = "2025-12-08T01:43:29.967Z" }, 29 | { url = "https://files.pythonhosted.org/packages/e8/dd/a237e9f565f3617a88b49284b59cbca2a4f56ebe68676c1aad0ce36a54a7/black-25.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a2f578ae20c19c50a382286ba78bfbeafdf788579b053d8e4980afb079ab9be", size = 1712639, upload-time = "2025-12-08T01:52:46.756Z" }, 30 | { url = "https://files.pythonhosted.org/packages/12/80/e187079df1ea4c12a0c63282ddd8b81d5107db6d642f7d7b75a6bcd6fc21/black-25.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e1b65634b0e471d07ff86ec338819e2ef860689859ef4501ab7ac290431f9b", size = 1758143, upload-time = "2025-12-08T01:45:29.137Z" }, 31 | { url = "https://files.pythonhosted.org/packages/93/b5/3096ccee4f29dc2c3aac57274326c4d2d929a77e629f695f544e159bfae4/black-25.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a3fa71e3b8dd9f7c6ac4d818345237dfb4175ed3bf37cd5a581dbc4c034f1ec5", size = 1420698, upload-time = "2025-12-08T01:45:53.379Z" }, 32 | { url = "https://files.pythonhosted.org/packages/7e/39/f81c0ffbc25ffbe61c7d0385bf277e62ffc3e52f5ee668d7369d9854fadf/black-25.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:51e267458f7e650afed8445dc7edb3187143003d52a1b710c7321aef22aa9655", size = 1229317, upload-time = "2025-12-08T01:46:35.606Z" }, 33 | { url = "https://files.pythonhosted.org/packages/d1/bd/26083f805115db17fda9877b3c7321d08c647df39d0df4c4ca8f8450593e/black-25.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f96b7c98c1ddaeb07dc0f56c652e25bdedaac76d5b68a059d998b57c55594a", size = 1924178, upload-time = "2025-12-08T01:49:51.048Z" }, 34 | { url = "https://files.pythonhosted.org/packages/89/6b/ea00d6651561e2bdd9231c4177f4f2ae19cc13a0b0574f47602a7519b6ca/black-25.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:05dd459a19e218078a1f98178c13f861fe6a9a5f88fc969ca4d9b49eb1809783", size = 1742643, upload-time = "2025-12-08T01:49:59.09Z" }, 35 | { url = "https://files.pythonhosted.org/packages/6d/f3/360fa4182e36e9875fabcf3a9717db9d27a8d11870f21cff97725c54f35b/black-25.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1f68c5eff61f226934be6b5b80296cf6939e5d2f0c2f7d543ea08b204bfaf59", size = 1800158, upload-time = "2025-12-08T01:44:27.301Z" }, 36 | { url = "https://files.pythonhosted.org/packages/f8/08/2c64830cb6616278067e040acca21d4f79727b23077633953081c9445d61/black-25.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:274f940c147ddab4442d316b27f9e332ca586d39c85ecf59ebdea82cc9ee8892", size = 1426197, upload-time = "2025-12-08T01:45:51.198Z" }, 37 | { url = "https://files.pythonhosted.org/packages/d4/60/a93f55fd9b9816b7432cf6842f0e3000fdd5b7869492a04b9011a133ee37/black-25.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:169506ba91ef21e2e0591563deda7f00030cb466e747c4b09cb0a9dae5db2f43", size = 1237266, upload-time = "2025-12-08T01:45:10.556Z" }, 38 | { url = "https://files.pythonhosted.org/packages/c8/52/c551e36bc95495d2aa1a37d50566267aa47608c81a53f91daa809e03293f/black-25.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a05ddeb656534c3e27a05a29196c962877c83fa5503db89e68857d1161ad08a5", size = 1923809, upload-time = "2025-12-08T01:46:55.126Z" }, 39 | { url = "https://files.pythonhosted.org/packages/a0/f7/aac9b014140ee56d247e707af8db0aae2e9efc28d4a8aba92d0abd7ae9d1/black-25.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ec77439ef3e34896995503865a85732c94396edcc739f302c5673a2315e1e7f", size = 1742384, upload-time = "2025-12-08T01:49:37.022Z" }, 40 | { url = "https://files.pythonhosted.org/packages/74/98/38aaa018b2ab06a863974c12b14a6266badc192b20603a81b738c47e902e/black-25.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e509c858adf63aa61d908061b52e580c40eae0dfa72415fa47ac01b12e29baf", size = 1798761, upload-time = "2025-12-08T01:46:05.386Z" }, 41 | { url = "https://files.pythonhosted.org/packages/16/3a/a8ac542125f61574a3f015b521ca83b47321ed19bb63fe6d7560f348bfe1/black-25.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:252678f07f5bac4ff0d0e9b261fbb029fa530cfa206d0a636a34ab445ef8ca9d", size = 1429180, upload-time = "2025-12-08T01:45:34.903Z" }, 42 | { url = "https://files.pythonhosted.org/packages/e6/2d/bdc466a3db9145e946762d52cd55b1385509d9f9004fec1c97bdc8debbfb/black-25.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bc5b1c09fe3c931ddd20ee548511c64ebf964ada7e6f0763d443947fd1c603ce", size = 1239350, upload-time = "2025-12-08T01:46:09.458Z" }, 43 | { url = "https://files.pythonhosted.org/packages/35/46/1d8f2542210c502e2ae1060b2e09e47af6a5e5963cb78e22ec1a11170b28/black-25.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0a0953b134f9335c2434864a643c842c44fba562155c738a2a37a4d61f00cad5", size = 1917015, upload-time = "2025-12-08T01:53:27.987Z" }, 44 | { url = "https://files.pythonhosted.org/packages/41/37/68accadf977672beb8e2c64e080f568c74159c1aaa6414b4cd2aef2d7906/black-25.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2355bbb6c3b76062870942d8cc450d4f8ac71f9c93c40122762c8784df49543f", size = 1741830, upload-time = "2025-12-08T01:54:36.861Z" }, 45 | { url = "https://files.pythonhosted.org/packages/ac/76/03608a9d8f0faad47a3af3a3c8c53af3367f6c0dd2d23a84710456c7ac56/black-25.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9678bd991cc793e81d19aeeae57966ee02909877cb65838ccffef24c3ebac08f", size = 1791450, upload-time = "2025-12-08T01:44:52.581Z" }, 46 | { url = "https://files.pythonhosted.org/packages/06/99/b2a4bd7dfaea7964974f947e1c76d6886d65fe5d24f687df2d85406b2609/black-25.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:97596189949a8aad13ad12fcbb4ae89330039b96ad6742e6f6b45e75ad5cfd83", size = 1452042, upload-time = "2025-12-08T01:46:13.188Z" }, 47 | { url = "https://files.pythonhosted.org/packages/b2/7c/d9825de75ae5dd7795d007681b752275ea85a1c5d83269b4b9c754c2aaab/black-25.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:778285d9ea197f34704e3791ea9404cd6d07595745907dd2ce3da7a13627b29b", size = 1267446, upload-time = "2025-12-08T01:46:14.497Z" }, 48 | { url = "https://files.pythonhosted.org/packages/68/11/21331aed19145a952ad28fca2756a1433ee9308079bd03bd898e903a2e53/black-25.12.0-py3-none-any.whl", hash = "sha256:48ceb36c16dbc84062740049eef990bb2ce07598272e673c17d1a7720c71c828", size = 206191, upload-time = "2025-12-08T01:40:50.963Z" }, 49 | ] 50 | 51 | [[package]] 52 | name = "click" 53 | version = "8.3.1" 54 | source = { registry = "https://pypi.org/simple" } 55 | dependencies = [ 56 | { name = "colorama", marker = "sys_platform == 'win32'" }, 57 | ] 58 | sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } 59 | wheels = [ 60 | { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, 61 | ] 62 | 63 | [[package]] 64 | name = "colorama" 65 | version = "0.4.6" 66 | source = { registry = "https://pypi.org/simple" } 67 | 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" } 68 | wheels = [ 69 | { 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" }, 70 | ] 71 | 72 | [[package]] 73 | name = "django" 74 | version = "5.1.15" 75 | source = { registry = "https://pypi.org/simple" } 76 | dependencies = [ 77 | { name = "asgiref" }, 78 | { name = "sqlparse" }, 79 | { name = "tzdata", marker = "sys_platform == 'win32'" }, 80 | ] 81 | sdist = { url = "https://files.pythonhosted.org/packages/10/45/1ac68964193cfcc0b0912a0f68025d5bdb54f71ba7b8716e85b959874bd0/django-5.1.15.tar.gz", hash = "sha256:46a356b5ff867bece73fc6365e081f21c569973403ee7e9b9a0316f27d0eb947", size = 10719662, upload-time = "2025-12-02T14:01:31.931Z" } 82 | wheels = [ 83 | { url = "https://files.pythonhosted.org/packages/27/79/372e091f0eba4ddb8228245ccd1baaa140e9658711f5e3a0056e540b4c1e/django-5.1.15-py3-none-any.whl", hash = "sha256:117871e58d6eda37f09870b7d73a3d66567b03aecd515b386b1751177c413432", size = 8260901, upload-time = "2025-12-02T14:01:27.352Z" }, 84 | ] 85 | 86 | [[package]] 87 | name = "django-vite" 88 | version = "3.1.0" 89 | source = { registry = "https://pypi.org/simple" } 90 | dependencies = [ 91 | { name = "django" }, 92 | ] 93 | sdist = { url = "https://files.pythonhosted.org/packages/23/fe/4be07f538bbf9bf8bf73b045552ec2a1b4fba67e3176abb30490b643368e/django_vite-3.1.0.tar.gz", hash = "sha256:8b4ffe4a9fa81ff568bfb195e74dde8694aaf13fd4b656ae60bf59cce08b85e8", size = 25434, upload-time = "2025-02-23T15:32:07.914Z" } 94 | wheels = [ 95 | { url = "https://files.pythonhosted.org/packages/30/59/7df4b1077fa41b43b4e542696d75138dbb86a65f44248c607b3e0f1014ce/django_vite-3.1.0-py3-none-any.whl", hash = "sha256:4e46572bd6b1ce70784be129205dc2ffcbc7a3c19fea50bebfb72b327bbde5fc", size = 23579, upload-time = "2025-02-23T15:32:06.603Z" }, 96 | ] 97 | 98 | [[package]] 99 | name = "djangovue" 100 | version = "0.1.0" 101 | source = { editable = "." } 102 | dependencies = [ 103 | { name = "django" }, 104 | { name = "django-vite" }, 105 | { name = "pytz" }, 106 | ] 107 | 108 | [package.optional-dependencies] 109 | dev = [ 110 | { name = "black" }, 111 | { name = "ruff" }, 112 | ] 113 | 114 | [package.metadata] 115 | requires-dist = [ 116 | { name = "black", marker = "extra == 'dev'", specifier = ">=23.0.0" }, 117 | { name = "django", specifier = ">=5.1,<5.2" }, 118 | { name = "django-vite", specifier = ">=3.1.0" }, 119 | { name = "pytz", specifier = ">=2023.3" }, 120 | { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" }, 121 | ] 122 | provides-extras = ["dev"] 123 | 124 | [[package]] 125 | name = "mypy-extensions" 126 | version = "1.1.0" 127 | source = { registry = "https://pypi.org/simple" } 128 | sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } 129 | wheels = [ 130 | { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, 131 | ] 132 | 133 | [[package]] 134 | name = "packaging" 135 | version = "25.0" 136 | source = { registry = "https://pypi.org/simple" } 137 | sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } 138 | wheels = [ 139 | { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, 140 | ] 141 | 142 | [[package]] 143 | name = "pathspec" 144 | version = "0.12.1" 145 | source = { registry = "https://pypi.org/simple" } 146 | sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } 147 | wheels = [ 148 | { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, 149 | ] 150 | 151 | [[package]] 152 | name = "platformdirs" 153 | version = "4.5.1" 154 | source = { registry = "https://pypi.org/simple" } 155 | sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } 156 | wheels = [ 157 | { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, 158 | ] 159 | 160 | [[package]] 161 | name = "pytokens" 162 | version = "0.3.0" 163 | source = { registry = "https://pypi.org/simple" } 164 | sdist = { url = "https://files.pythonhosted.org/packages/4e/8d/a762be14dae1c3bf280202ba3172020b2b0b4c537f94427435f19c413b72/pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a", size = 17644, upload-time = "2025-11-05T13:36:35.34Z" } 165 | wheels = [ 166 | { url = "https://files.pythonhosted.org/packages/84/25/d9db8be44e205a124f6c98bc0324b2bb149b7431c53877fc6d1038dddaf5/pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3", size = 12195, upload-time = "2025-11-05T13:36:33.183Z" }, 167 | ] 168 | 169 | [[package]] 170 | name = "pytz" 171 | version = "2025.2" 172 | source = { registry = "https://pypi.org/simple" } 173 | sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } 174 | wheels = [ 175 | { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, 176 | ] 177 | 178 | [[package]] 179 | name = "ruff" 180 | version = "0.14.9" 181 | source = { registry = "https://pypi.org/simple" } 182 | sdist = { url = "https://files.pythonhosted.org/packages/f6/1b/ab712a9d5044435be8e9a2beb17cbfa4c241aa9b5e4413febac2a8b79ef2/ruff-0.14.9.tar.gz", hash = "sha256:35f85b25dd586381c0cc053f48826109384c81c00ad7ef1bd977bfcc28119d5b", size = 5809165, upload-time = "2025-12-11T21:39:47.381Z" } 183 | wheels = [ 184 | { url = "https://files.pythonhosted.org/packages/b8/1c/d1b1bba22cffec02351c78ab9ed4f7d7391876e12720298448b29b7229c1/ruff-0.14.9-py3-none-linux_armv6l.whl", hash = "sha256:f1ec5de1ce150ca6e43691f4a9ef5c04574ad9ca35c8b3b0e18877314aba7e75", size = 13576541, upload-time = "2025-12-11T21:39:14.806Z" }, 185 | { url = "https://files.pythonhosted.org/packages/94/ab/ffe580e6ea1fca67f6337b0af59fc7e683344a43642d2d55d251ff83ceae/ruff-0.14.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ed9d7417a299fc6030b4f26333bf1117ed82a61ea91238558c0268c14e00d0c2", size = 13779363, upload-time = "2025-12-11T21:39:20.29Z" }, 186 | { url = "https://files.pythonhosted.org/packages/7d/f8/2be49047f929d6965401855461e697ab185e1a6a683d914c5c19c7962d9e/ruff-0.14.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d5dc3473c3f0e4a1008d0ef1d75cee24a48e254c8bed3a7afdd2b4392657ed2c", size = 12925292, upload-time = "2025-12-11T21:39:38.757Z" }, 187 | { url = "https://files.pythonhosted.org/packages/9e/e9/08840ff5127916bb989c86f18924fd568938b06f58b60e206176f327c0fe/ruff-0.14.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84bf7c698fc8f3cb8278830fb6b5a47f9bcc1ed8cb4f689b9dd02698fa840697", size = 13362894, upload-time = "2025-12-11T21:39:02.524Z" }, 188 | { url = "https://files.pythonhosted.org/packages/31/1c/5b4e8e7750613ef43390bb58658eaf1d862c0cc3352d139cd718a2cea164/ruff-0.14.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa733093d1f9d88a5d98988d8834ef5d6f9828d03743bf5e338bf980a19fce27", size = 13311482, upload-time = "2025-12-11T21:39:17.51Z" }, 189 | { url = "https://files.pythonhosted.org/packages/5b/3a/459dce7a8cb35ba1ea3e9c88f19077667a7977234f3b5ab197fad240b404/ruff-0.14.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a1cfb04eda979b20c8c19550c8b5f498df64ff8da151283311ce3199e8b3648", size = 14016100, upload-time = "2025-12-11T21:39:41.948Z" }, 190 | { url = "https://files.pythonhosted.org/packages/a6/31/f064f4ec32524f9956a0890fc6a944e5cf06c63c554e39957d208c0ffc45/ruff-0.14.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1e5cb521e5ccf0008bd74d5595a4580313844a42b9103b7388eca5a12c970743", size = 15477729, upload-time = "2025-12-11T21:39:23.279Z" }, 191 | { url = "https://files.pythonhosted.org/packages/7a/6d/f364252aad36ccd443494bc5f02e41bf677f964b58902a17c0b16c53d890/ruff-0.14.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd429a8926be6bba4befa8cdcf3f4dd2591c413ea5066b1e99155ed245ae42bb", size = 15122386, upload-time = "2025-12-11T21:39:33.125Z" }, 192 | { url = "https://files.pythonhosted.org/packages/20/02/e848787912d16209aba2799a4d5a1775660b6a3d0ab3944a4ccc13e64a02/ruff-0.14.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab208c1b7a492e37caeaf290b1378148f75e13c2225af5d44628b95fd7834273", size = 14497124, upload-time = "2025-12-11T21:38:59.33Z" }, 193 | { url = "https://files.pythonhosted.org/packages/f3/51/0489a6a5595b7760b5dbac0dd82852b510326e7d88d51dbffcd2e07e3ff3/ruff-0.14.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72034534e5b11e8a593f517b2f2f2b273eb68a30978c6a2d40473ad0aaa4cb4a", size = 14195343, upload-time = "2025-12-11T21:39:44.866Z" }, 194 | { url = "https://files.pythonhosted.org/packages/f6/53/3bb8d2fa73e4c2f80acc65213ee0830fa0c49c6479313f7a68a00f39e208/ruff-0.14.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:712ff04f44663f1b90a1195f51525836e3413c8a773574a7b7775554269c30ed", size = 14346425, upload-time = "2025-12-11T21:39:05.927Z" }, 195 | { url = "https://files.pythonhosted.org/packages/ad/04/bdb1d0ab876372da3e983896481760867fc84f969c5c09d428e8f01b557f/ruff-0.14.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a111fee1db6f1d5d5810245295527cda1d367c5aa8f42e0fca9a78ede9b4498b", size = 13258768, upload-time = "2025-12-11T21:39:08.691Z" }, 196 | { url = "https://files.pythonhosted.org/packages/40/d9/8bf8e1e41a311afd2abc8ad12be1b6c6c8b925506d9069b67bb5e9a04af3/ruff-0.14.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8769efc71558fecc25eb295ddec7d1030d41a51e9dcf127cbd63ec517f22d567", size = 13326939, upload-time = "2025-12-11T21:39:53.842Z" }, 197 | { url = "https://files.pythonhosted.org/packages/f4/56/a213fa9edb6dd849f1cfbc236206ead10913693c72a67fb7ddc1833bf95d/ruff-0.14.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:347e3bf16197e8a2de17940cd75fd6491e25c0aa7edf7d61aa03f146a1aa885a", size = 13578888, upload-time = "2025-12-11T21:39:35.988Z" }, 198 | { url = "https://files.pythonhosted.org/packages/33/09/6a4a67ffa4abae6bf44c972a4521337ffce9cbc7808faadede754ef7a79c/ruff-0.14.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7715d14e5bccf5b660f54516558aa94781d3eb0838f8e706fb60e3ff6eff03a8", size = 14314473, upload-time = "2025-12-11T21:39:50.78Z" }, 199 | { url = "https://files.pythonhosted.org/packages/12/0d/15cc82da5d83f27a3c6b04f3a232d61bc8c50d38a6cd8da79228e5f8b8d6/ruff-0.14.9-py3-none-win32.whl", hash = "sha256:df0937f30aaabe83da172adaf8937003ff28172f59ca9f17883b4213783df197", size = 13202651, upload-time = "2025-12-11T21:39:26.628Z" }, 200 | { url = "https://files.pythonhosted.org/packages/32/f7/c78b060388eefe0304d9d42e68fab8cffd049128ec466456cef9b8d4f06f/ruff-0.14.9-py3-none-win_amd64.whl", hash = "sha256:c0b53a10e61df15a42ed711ec0bda0c582039cf6c754c49c020084c55b5b0bc2", size = 14702079, upload-time = "2025-12-11T21:39:11.954Z" }, 201 | { url = "https://files.pythonhosted.org/packages/26/09/7a9520315decd2334afa65ed258fed438f070e31f05a2e43dd480a5e5911/ruff-0.14.9-py3-none-win_arm64.whl", hash = "sha256:8e821c366517a074046d92f0e9213ed1c13dbc5b37a7fc20b07f79b64d62cc84", size = 13744730, upload-time = "2025-12-11T21:39:29.659Z" }, 202 | ] 203 | 204 | [[package]] 205 | name = "sqlparse" 206 | version = "0.5.4" 207 | source = { registry = "https://pypi.org/simple" } 208 | sdist = { url = "https://files.pythonhosted.org/packages/18/67/701f86b28d63b2086de47c942eccf8ca2208b3be69715a1119a4e384415a/sqlparse-0.5.4.tar.gz", hash = "sha256:4396a7d3cf1cd679c1be976cf3dc6e0a51d0111e87787e7a8d780e7d5a998f9e", size = 120112, upload-time = "2025-11-28T07:10:18.377Z" } 209 | wheels = [ 210 | { url = "https://files.pythonhosted.org/packages/25/70/001ee337f7aa888fb2e3f5fd7592a6afc5283adb1ed44ce8df5764070f22/sqlparse-0.5.4-py3-none-any.whl", hash = "sha256:99a9f0314977b76d776a0fcb8554de91b9bb8a18560631d6bc48721d07023dcb", size = 45933, upload-time = "2025-11-28T07:10:19.73Z" }, 211 | ] 212 | 213 | [[package]] 214 | name = "tzdata" 215 | version = "2025.3" 216 | source = { registry = "https://pypi.org/simple" } 217 | sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } 218 | wheels = [ 219 | { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, 220 | ] 221 | --------------------------------------------------------------------------------