Established workplans
This commit is contained in:
parent
026f7c0db1
commit
315143a6fc
16 changed files with 5492 additions and 0 deletions
405
workplans/WP-0001-projektgeruest.md
Normal file
405
workplans/WP-0001-projektgeruest.md
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
---
|
||||
id: WP-0001
|
||||
title: Projektgerüst — Django-Setup, Tailwind, Dev-Stack
|
||||
status: todo
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
Erstelle folgende Apps mit `uv run manage.py startapp <name>`:
|
||||
core, accounts, ausschreibungen, lose, aufgaben,
|
||||
dokumente, preise, partner, bibliothek, marktbegleiter,
|
||||
nachbetrachtung, feedback
|
||||
|
||||
Verschiebe jede App in ein eigenes Unterverzeichnis:
|
||||
`vergabe_teilnahme/apps/<app_name>/`
|
||||
|
||||
Passe in jeder App `apps.py` den `name` auf `vergabe_teilnahme.apps.<app_name>` 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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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 <app> # Einzelne App testen
|
||||
uv run pytest tests/<pfad>.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: todo
|
||||
|
||||
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.
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue