--- id: WP-0001 title: Projektgerüst — Django-Setup, Tailwind, Dev-Stack status: done phase: 1-of-12 created: "2026-05-08" --- # WP-0001 — Projektgerüst Legt das vollständige Django-Projektgerüst an: uv, Projektstruktur, Settings, alle App-Hüllen, Tailwind CSS v4 via Vite, HTMX + Alpine.js, Docker Compose für PostgreSQL, pytest-django, Makefile. **Referenzdokumente:** `wiki/ArchitectureBlueprint.md` Abschnitte 2 und 3. **Arbeitsverzeichnis:** `/home/worsch/vergabe-teilnahme/` --- ```task id: WP-0001-T01 title: pyproject.toml und uv-Projektstruktur anlegen status: done Erstelle `pyproject.toml` mit uv als Package-Manager. Abhängigkeiten (production): django>=5.2, psycopg[binary]>=3.2, django-storages>=1.14, whitenoise>=6.7, python-decouple>=3.8 Abhängigkeiten (dev): pytest-django>=4.8, pytest-cov>=5.0, factory-boy>=3.3, ruff>=0.4, mypy>=1.10, django-stubs>=5.0 Python: >=3.12 Erstelle außerdem `.python-version` mit `3.12`. Führe `uv sync` aus und bestätige, dass die virtuelle Umgebung erstellt wird. ``` ```task id: WP-0001-T02 title: Django-Projekt initialisieren und Settings-Struktur anlegen status: done Führe `uv run django-admin startproject vergabe_teilnahme .` aus (Punkt am Ende — kein verschachteltes Projektverzeichnis). Erstelle `vergabe_teilnahme/settings/` mit: - `__init__.py` (leer) - `base.py` — gemeinsame Settings (INSTALLED_APPS, TEMPLATES, STATIC, MEDIA, AUTH_USER_MODEL = 'accounts.Mitarbeiter', DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField', LANGUAGE_CODE = 'de-de', TIME_ZONE = 'Europe/Berlin', USE_I18N = True, USE_TZ = True) - `dev.py` — importiert base, setzt DEBUG=True, ALLOWED_HOSTS=['*'], DATABASE aus python-decouple .env - `prod.py` — importiert base, DEBUG=False, ALLOWED_HOSTS aus Env, WhiteNoise-Middleware, SECURE_* Flags Passe `manage.py` und `vergabe_teilnahme/wsgi.py` auf `DJANGO_SETTINGS_MODULE = 'vergabe_teilnahme.settings.dev'` an. ``` ```task id: WP-0001-T03 title: .env.example und PostgreSQL-Konfiguration status: done Erstelle `.env.example`: ``` DATABASE_URL=postgres://vergabe:vergabe@localhost:5432/vergabe_db SECRET_KEY=change-me-in-production DEBUG=True ALLOWED_HOSTS=localhost,127.0.0.1 MEDIA_ROOT=media/ MAX_UPLOAD_SIZE=52428800 ``` In `settings/base.py` lese DATABASE_URL via `python-decouple` und parse mit `dj-database-url` (füge `dj-database-url>=2.1` zu pyproject.toml hinzu). Erstelle `.env` (nur lokal, in .gitignore) mit Entwicklungswerten. Prüfe dass `.env` und `*.sqlite3` in `.gitignore` enthalten sind. ``` ```task id: WP-0001-T04 title: Alle Django-Apps anlegen status: done Erstelle folgende Apps mit `uv run manage.py startapp `: core, accounts, ausschreibungen, lose, aufgaben, dokumente, preise, partner, bibliothek, marktbegleiter, nachbetrachtung, feedback Verschiebe jede App in ein eigenes Unterverzeichnis: `vergabe_teilnahme/apps//` Passe in jeder App `apps.py` den `name` auf `vergabe_teilnahme.apps.` an. Füge alle Apps zu `INSTALLED_APPS` in `settings/base.py` hinzu. Stelle sicher, dass `django.contrib.contenttypes` ebenfalls in INSTALLED_APPS ist (wird für GenericForeignKey im core-Modell benötigt). ``` ```task id: WP-0001-T05 title: Tailwind CSS v4 via Vite integrieren status: done Erstelle `package.json` im Projektwurzelverzeichnis: ```json { "scripts": { "dev": "vite build --watch", "build": "vite build" }, "devDependencies": { "vite": "^5.0", "@tailwindcss/vite": "^4.0", "tailwindcss": "^4.0" } } ``` Erstelle `vite.config.js`: ```js import { defineConfig } from 'vite' import tailwindcss from '@tailwindcss/vite' export default defineConfig({ plugins: [tailwindcss()], build: { outDir: 'static/dist', emptyOutDir: true, rollupOptions: { input: 'static/src/main.css' } } }) ``` Erstelle `static/src/main.css`: ```css @import "tailwindcss"; @layer base { /* German-app base resets */ } @layer components { .card { @apply bg-white rounded-xl border border-slate-200 shadow-sm p-6; } .btn-primary { @apply bg-brand-500 text-white px-4 py-2 rounded-lg hover:bg-brand-600 transition-colors; } .btn-secondary { @apply bg-white text-slate-700 border border-slate-300 px-4 py-2 rounded-lg hover:bg-slate-50; } .btn-danger { @apply bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700; } .btn-ghost { @apply text-slate-600 px-3 py-2 rounded-lg hover:bg-slate-100; } .field-row { @apply grid grid-cols-3 gap-4 py-3 border-b border-slate-100 last:border-0; } .field-label { @apply text-sm font-medium text-slate-500 col-span-1; } .field-value { @apply text-sm text-slate-900 col-span-2; } .phase-badge { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold; } .phase-todo { @apply phase-badge bg-slate-200 text-slate-500; } .phase-active { @apply phase-badge bg-brand-500 text-white; } .phase-done { @apply phase-badge bg-green-500 text-white; } .phase-warn { @apply phase-badge bg-amber-400 text-amber-900; } .section-title { @apply text-base font-semibold text-slate-900 mb-4; } .page-title { @apply text-2xl font-bold text-slate-900; } .form-input { @apply w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent; } .form-label { @apply block text-sm font-medium text-slate-700 mb-1; } .table-base { @apply w-full text-sm text-left; } .table-header { @apply bg-slate-50 text-slate-500 font-medium text-xs uppercase tracking-wide; } .table-row { @apply border-t border-slate-100 hover:bg-slate-50 transition-colors; } } ``` Füge CSS-Theme-Token für `brand` in `main.css` hinzu: ```css @theme { --color-brand-50: #f0f4ff; --color-brand-100: #dce7ff; --color-brand-500: #3b5bdb; --color-brand-600: #2f4ac7; --color-brand-700: #2541b2; --color-brand-900: #152d99; } ``` Konfiguriere Django STATICFILES_DIRS und STATIC_ROOT in settings/base.py. Füge `STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'` in settings/prod.py ein. Führe `npm install` aus. ``` ```task id: WP-0001-T06 title: HTMX und Alpine.js einbinden status: done Lade HTMX und Alpine.js als lokale Vendor-Dateien (keine CDN-Abhängigkeit): - `static/vendor/htmx/htmx.min.js` — HTMX 2.x (von unpkg herunterladen) - `static/vendor/alpinejs/alpine.min.js` — Alpine.js 3.x Füge in `settings/base.py` hinzu: ```python STATICFILES_DIRS = [BASE_DIR / 'static'] ``` Die Einbindung im base.html-Template erfolgt in WP-0003. Erstelle hier nur die Verzeichnisstruktur und die Dateien. Prüfe: `ls static/vendor/htmx/` und `ls static/vendor/alpinejs/` sollten die Dateien zeigen. ``` ```task id: WP-0001-T07 title: Docker Compose für Entwicklungs-PostgreSQL status: done Erstelle `docker-compose.dev.yml`: ```yaml services: db: image: postgres:16-alpine environment: POSTGRES_DB: vergabe_db POSTGRES_USER: vergabe POSTGRES_PASSWORD: vergabe ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data volumes: postgres_data: ``` Erstelle außerdem `docker-compose.test.yml` für CI (gleiche Konfiguration, anderer DB-Name: `vergabe_test`). Starte die DB: `docker compose -f docker-compose.dev.yml up -d` Prüfe Verbindung: `uv run manage.py check --database default` ``` ```task id: WP-0001-T08 title: pytest-django konfigurieren status: done Füge in `pyproject.toml` hinzu: ```toml [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "vergabe_teilnahme.settings.dev" python_files = ["test_*.py"] python_classes = ["Test*"] python_functions = ["test_*"] addopts = "--tb=short -q" [tool.ruff] line-length = 100 target-version = "py312" select = ["E", "F", "I", "N", "UP"] ``` Erstelle `conftest.py` im Projektwurzel: ```python import pytest @pytest.fixture def mitarbeiter(db): from vergabe_teilnahme.apps.accounts.models import Mitarbeiter return Mitarbeiter.objects.create_user( username='testuser', password='testpass', first_name='Test', last_name='User' ) ``` Prüfe: `uv run pytest --co -q` (keine Tests vorhanden, aber Konfiguration valide). ``` ```task id: WP-0001-T09 title: Makefile für häufige Dev-Commands status: done Erstelle `Makefile` im Projektwurzel: ```makefile .PHONY: dev db migrate shell test lint css db: docker compose -f docker-compose.dev.yml up -d dev: db uv run manage.py runserver 0.0.0.0:8000 css: npm run dev migrate: uv run manage.py makemigrations uv run manage.py migrate shell: uv run manage.py shell_plus 2>/dev/null || uv run manage.py shell test: uv run pytest lint: uv run ruff check . uv run mypy vergabe_teilnahme/ createsuperuser: uv run manage.py createsuperuser collectstatic: uv run manage.py collectstatic --noinput ``` Prüfe: `make db` startet PostgreSQL, `make migrate` läuft fehlerfrei durch (zu diesem Zeitpunkt noch ohne Fachmodelle — nur Django-Default-Migrationen). ``` ```task id: WP-0001-T10 title: Django URL-Grundkonfiguration und Health-Check status: done Editiere `vergabe_teilnahme/urls.py`: ```python from django.contrib import admin from django.urls import path, include from django.conf import settings from django.conf.urls.static import static from django.http import JsonResponse def health(request): return JsonResponse({'status': 'ok'}) urlpatterns = [ path('admin/', admin.site.urls), path('health/', health), # Module-URLs werden in späteren Workplans ergänzt ] if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ``` Füge in `settings/base.py` hinzu: ```python MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media' ``` Prüfe: `make dev` startet ohne Fehler, `curl http://localhost:8000/health/` gibt `{"status": "ok"}` zurück. ``` ```task id: WP-0001-T11 title: CLAUDE.md mit Build-Commands aktualisieren status: done Aktualisiere `/home/worsch/vergabe-teilnahme/CLAUDE.md` um einen Abschnitt "## Entwicklungs-Commands": ```markdown ## Entwicklungs-Commands ```bash make db # PostgreSQL via Docker starten make dev # Django-Dev-Server (Port 8000) make css # Tailwind CSS im Watch-Modus make migrate # Migrations generieren und ausführen make test # pytest ausführen make lint # ruff + mypy uv run manage.py test # Einzelne App testen uv run pytest tests/.py # Einzelne Testdatei ``` ## Projektstruktur ``` vergabe_teilnahme/ ├── apps/ # Alle Django-Apps │ ├── core/ # FlexibleModel, CustomAttribute, EntityFieldConfig, Freigabe │ ├── accounts/ # Mitarbeiter (AbstractUser) │ └── ... # je eine App pro Fachdomäne ├── settings/ # base.py, dev.py, prod.py └── urls.py static/ ├── src/main.css # Tailwind-Quelldatei ├── vendor/ # HTMX, Alpine.js └── dist/ # Build-Output (gitignored) workplans/ # Ralph-Loop-Workplans wiki/ # PRD, Blueprint, Use-Case-Katalog ``` ``` Füge außerdem `static/dist/` zu `.gitignore` hinzu. ``` ```task id: WP-0001-T12 title: Erstes `uv run manage.py migrate` und Smoke-Test status: done Führe die gesamte initiale Setup-Sequenz durch und verifiziere: 1. `make db` → PostgreSQL läuft 2. `uv run manage.py migrate` → alle Django-Default-Migrationen laufen sauber durch 3. `uv run manage.py check` → keine Fehler 4. `uv run pytest` → 0 Tests gesammelt, kein Fehler 5. `npm run build` → `static/dist/` enthält die kompilierte CSS-Datei 6. `make dev` → Server startet, `/health/` antwortet mit 200 Notiere etwaige Fehler und behebe sie. Erst wenn alle 6 Checks bestanden sind gilt dieser Task als erledigt. ```