Compare commits
11 Commits
feature/im
...
ad9d55e986
| Author | SHA1 | Date | |
|---|---|---|---|
|
ad9d55e986
|
|||
|
996ea628c7
|
|||
|
523b991493
|
|||
|
310c4fdd0b
|
|||
| 353a8a5697 | |||
| f1d3c88a45 | |||
| 996584ef68 | |||
| 18fac6e8b9 | |||
| 492f8b4e90 | |||
| e86e3c19b5 | |||
| 938424a02e |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,4 +16,5 @@ AGENT*.md
|
|||||||
# Diagram cache directory
|
# Diagram cache directory
|
||||||
media/diagram_cache/
|
media/diagram_cache/
|
||||||
.env
|
.env
|
||||||
data/db.sqlite3
|
data/
|
||||||
|
dataremote/
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ COPY requirements.txt /app/
|
|||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
FROM python:3.15-rc-slim-trixie
|
FROM python:3.15-rc-slim-trixie
|
||||||
RUN useradd -m -r appuser && \
|
RUN useradd -m -r -u 99 appuser && \
|
||||||
mkdir /app && \
|
mkdir /app && \
|
||||||
chown -R appuser /app
|
chown -R appuser /app
|
||||||
|
|
||||||
|
|||||||
5
VorgabenUI/context_processors.py
Normal file
5
VorgabenUI/context_processors.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
def version(request):
|
||||||
|
return {'version': settings.VERSION}
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
"""
|
|
||||||
Django settings for VorgabenUI project.
|
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 5.2.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/5.2/topics/settings/
|
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
|
||||||
https://docs.djangoproject.com/en/5.2/ref/settings/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
||||||
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
|
||||||
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
|
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
|
||||||
SECRET_KEY = os.environ.get("SECRET_KEY")
|
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
|
||||||
DEBUG = bool(os.environ.get("DEBUG", default=0))
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS","127.0.0.1").split(",")
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
'dokumente',
|
|
||||||
'abschnitte',
|
|
||||||
'stichworte',
|
|
||||||
'mptt',
|
|
||||||
'nested_admin',
|
|
||||||
]
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
|
||||||
'django.middleware.security.SecurityMiddleware',
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
||||||
'django.middleware.common.CommonMiddleware',
|
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
||||||
]
|
|
||||||
|
|
||||||
ROOT_URLCONF = 'VorgabenUI.urls'
|
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
||||||
'DIRS': [],
|
|
||||||
'APP_DIRS': True,
|
|
||||||
'OPTIONS': {
|
|
||||||
'context_processors': [
|
|
||||||
'django.template.context_processors.request',
|
|
||||||
'django.contrib.auth.context_processors.auth',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
WSGI_APPLICATION = 'VorgabenUI.wsgi.application'
|
|
||||||
|
|
||||||
|
|
||||||
# Database
|
|
||||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
|
||||||
'NAME': BASE_DIR / 'data/db.sqlite3',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
|
||||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
|
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
|
||||||
# https://docs.djangoproject.com/en/5.2/topics/i18n/
|
|
||||||
|
|
||||||
LANGUAGE_CODE = 'de-ch'
|
|
||||||
|
|
||||||
TIME_ZONE = 'UTC'
|
|
||||||
|
|
||||||
USE_I18N = True
|
|
||||||
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/5.2/howto/static-files/
|
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
|
||||||
STATIC_ROOT="/home/adebaumann/VorgabenUI/staticfiles/"
|
|
||||||
STATICFILES_DIRS= (
|
|
||||||
os.path.join(BASE_DIR,"static"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Media files (User-uploaded content)
|
|
||||||
MEDIA_URL = '/media/'
|
|
||||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
|
||||||
|
|
||||||
# Diagram cache settings
|
|
||||||
DIAGRAM_CACHE_DIR = 'diagram_cache' # relative to MEDIA_ROOT
|
|
||||||
|
|
||||||
# Default primary key field type
|
|
||||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
|
||||||
DATA_UPLOAD_MAX_NUMBER_FIELDS=10250
|
|
||||||
NESTED_ADMIN_LAZY_INLINES = True
|
|
||||||
@@ -23,6 +23,9 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
|||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = os.environ.get('DEBUG', 'True').lower() in ('true', '1', 'yes', 'on')
|
DEBUG = os.environ.get('DEBUG', 'True').lower() in ('true', '1', 'yes', 'on')
|
||||||
|
|
||||||
|
# Application version (from ConfigMap)
|
||||||
|
VERSION = os.environ.get('VERSION', '0.0.0')
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = os.environ.get('VORGABENUI_SECRET')
|
SECRET_KEY = os.environ.get('VORGABENUI_SECRET')
|
||||||
if not SECRET_KEY:
|
if not SECRET_KEY:
|
||||||
@@ -94,6 +97,7 @@ TEMPLATES = [
|
|||||||
'django.template.context_processors.request',
|
'django.template.context_processors.request',
|
||||||
'django.contrib.auth.context_processors.auth',
|
'django.contrib.auth.context_processors.auth',
|
||||||
'django.contrib.messages.context_processors.messages',
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
'VorgabenUI.context_processors.version',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -138,7 +142,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||||||
|
|
||||||
LANGUAGE_CODE = 'de-ch'
|
LANGUAGE_CODE = 'de-ch'
|
||||||
|
|
||||||
TIME_ZONE = 'UTC'
|
TIME_ZONE = 'Europe/Zurich'
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ urlpatterns = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Serve media files (including cached diagrams)
|
# Serve media files (including cached diagrams)
|
||||||
|
# django.conf.urls.static.static() is a no-op when DEBUG=False,
|
||||||
|
# so we wire up the serve view directly for media files.
|
||||||
|
from django.views.static import serve
|
||||||
|
urlpatterns += [
|
||||||
|
re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
|
||||||
|
]
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
|
||||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ data:
|
|||||||
STATIC_URL: "/static/"
|
STATIC_URL: "/static/"
|
||||||
MEDIA_URL: "/media/"
|
MEDIA_URL: "/media/"
|
||||||
|
|
||||||
|
# Application Version
|
||||||
|
VERSION: "0.987"
|
||||||
|
|
||||||
# Database Configuration (for future use)
|
# Database Configuration (for future use)
|
||||||
# DATABASE_ENGINE: "django.db.backends.sqlite3"
|
# DATABASE_ENGINE: "django.db.backends.sqlite3"
|
||||||
# DATABASE_NAME: "/app/data/db.sqlite3"
|
# DATABASE_NAME: "/app/data/db.sqlite3"
|
||||||
|
|||||||
@@ -14,19 +14,23 @@ spec:
|
|||||||
app: django
|
app: django
|
||||||
spec:
|
spec:
|
||||||
securityContext:
|
securityContext:
|
||||||
fsGroup: 999
|
fsGroup: 99
|
||||||
fsGroupChangePolicy: "OnRootMismatch"
|
fsGroupChangePolicy: "OnRootMismatch"
|
||||||
initContainers:
|
initContainers:
|
||||||
- name: loader
|
- name: loader
|
||||||
image: git.baumann.gr/adebaumann/vui-data-loader:0.11
|
image: git.baumann.gr/adebaumann/vui-data-loader:0.11
|
||||||
command: [ "sh","-c","cp -n preload/preload.sqlite3 /data/db.sqlite3; chown -R 999:999 /data; ls -la /data; sleep 10; exit 0" ]
|
securityContext:
|
||||||
|
runAsUser: 99
|
||||||
|
command: [ "sh","-c","if [ ! -f /data/db.sqlite3 ] || [ ! -s /data/db.sqlite3 ]; then cp preload/preload.sqlite3 /data/db.sqlite3 && echo 'Database copied from preload'; else echo 'Existing database preserved'; fi" ]
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: data
|
- name: data
|
||||||
mountPath: /data
|
mountPath: /data
|
||||||
containers:
|
containers:
|
||||||
- name: web
|
- name: web
|
||||||
image: git.baumann.gr/adebaumann/vui:0.980
|
image: git.baumann.gr/adebaumann/vui:0.987
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 99
|
||||||
env:
|
env:
|
||||||
# Secret configuration
|
# Secret configuration
|
||||||
- name: VORGABENUI_SECRET
|
- name: VORGABENUI_SECRET
|
||||||
@@ -50,6 +54,11 @@ spec:
|
|||||||
configMapKeyRef:
|
configMapKeyRef:
|
||||||
name: django-config
|
name: django-config
|
||||||
key: DJANGO_SETTINGS_MODULE
|
key: DJANGO_SETTINGS_MODULE
|
||||||
|
- name: VERSION
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: django-config
|
||||||
|
key: VERSION
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8000
|
- containerPort: 8000
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ metadata:
|
|||||||
name: django-data-pv
|
name: django-data-pv
|
||||||
namespace: vorgabenui
|
namespace: vorgabenui
|
||||||
spec:
|
spec:
|
||||||
|
claimRef:
|
||||||
|
name: django-data-pvc
|
||||||
|
namespace: vorgabenui
|
||||||
capacity:
|
capacity:
|
||||||
storage: 2Gi
|
storage: 2Gi
|
||||||
accessModes:
|
accessModes:
|
||||||
|
|||||||
512
code-review.md
Normal file
512
code-review.md
Normal file
@@ -0,0 +1,512 @@
|
|||||||
|
# Code Review: vgui-cicd Django Project
|
||||||
|
|
||||||
|
**Date:** January 20, 2026
|
||||||
|
**Reviewer:** AI Code Review
|
||||||
|
**Project Path:** /home/adebaumann/development/vgui-cicd
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. PROJECT STRUCTURE
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
The project follows Django conventions with a clear app structure:
|
||||||
|
- **VorgabenUI** - Main project settings, URLs, WSGI/ASGI
|
||||||
|
- **dokumente** - Core document and Vorgabe models (315 lines)
|
||||||
|
- **abschnitte** - Text section models and utilities
|
||||||
|
- **stichworte** - Keyword/stichwort models
|
||||||
|
- **referenzen** - Reference models (MPTT-based)
|
||||||
|
- **rollen** - Role models
|
||||||
|
- **pages** - General pages and views
|
||||||
|
- **diagramm_proxy** - Diagram caching functionality
|
||||||
|
|
||||||
|
### Issues Found
|
||||||
|
|
||||||
|
**Minor - settings-docker.py**: The `settings-docker.py` file has duplicate `AUTH_PASSWORD_VALIDATORS` definitions (lines 92-105 and 183-199), which is redundant.
|
||||||
|
- **File**: `/home/adebaumann/development/vgui-cicd/VorgabenUI/settings-docker.py` (lines 92-105 and 183-199)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. SETTINGS REVIEW
|
||||||
|
|
||||||
|
### Critical Issues
|
||||||
|
|
||||||
|
**1. Fallback SECRET_KEY in production** (`/home/adebaumann/development/vgui-cicd/VorgabenUI/settings.py`, lines 27-47)
|
||||||
|
```python
|
||||||
|
SECRET_KEY = os.environ.get('VORGABENUI_SECRET')
|
||||||
|
if not SECRET_KEY:
|
||||||
|
is_build_env = any([...])
|
||||||
|
debug_mode = ...
|
||||||
|
if debug_mode or is_build_env:
|
||||||
|
SECRET_KEY = 'dev-fallback-key-for-local-debugging-only-not-for-production-use-12345'
|
||||||
|
```
|
||||||
|
- **Issue**: Even though there's a check for build environments, the hardcoded fallback key creates a significant security risk if the environment variable is not properly set in production
|
||||||
|
- **Recommendation**: The fallback should NEVER be enabled, even in development - require the environment variable to be set
|
||||||
|
|
||||||
|
**2. DEBUG mode default** (`/home/adebaumann/development/vgui-cicd/VorgabenUI/settings.py`, line 24)
|
||||||
|
```python
|
||||||
|
DEBUG = os.environ.get('DEBUG', 'True').lower() in ('true', '1', 'yes', 'on')
|
||||||
|
```
|
||||||
|
- **Issue**: DEBUG defaults to True, which could expose sensitive information if environment variables are misconfigured
|
||||||
|
- **Recommendation**: Require explicit setting of DEBUG to False in production
|
||||||
|
|
||||||
|
### Major Issues
|
||||||
|
|
||||||
|
**3. ALLOWED_HOSTS with wildcard** (`/home/adebaumann/development/vgui-cicd/VorgabenUI/settings.py`, line 50)
|
||||||
|
```python
|
||||||
|
ALLOWED_HOSTS = os.environ.get('DJANGO_ALLOWED_HOSTS', "10.128.128.144,localhost,127.0.0.1,*").split(",")
|
||||||
|
```
|
||||||
|
- **Issue**: Default includes `*` which allows any host - dangerous in production
|
||||||
|
- **Recommendation**: Default should not include wildcard; require explicit configuration
|
||||||
|
|
||||||
|
**4. No rate limiting on authentication** (`/home/adebaumann/development/vgui-cicd/VorgabenUI/settings.py`)
|
||||||
|
- **Issue**: No login throttling or rate limiting configured for authentication endpoints
|
||||||
|
- **Recommendation**: Add `django-axes` or similar for brute-force protection
|
||||||
|
|
||||||
|
**5. SQLite in production** (`/home/adebaumann/development/vgui-cicd/VorgabenUI/settings.py`, lines 109-114)
|
||||||
|
```python
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'data/db.sqlite3',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Issue**: SQLite is used as default database - not suitable for production with concurrent access
|
||||||
|
- **Recommendation**: Configure PostgreSQL or other production-ready database
|
||||||
|
|
||||||
|
### Minor Issues
|
||||||
|
|
||||||
|
**6. TIME_ZONE mismatch** (`/home/adebaumann/development/vgui-cicd/VorgabenUI/settings.py`, lines 139-145)
|
||||||
|
- **Issue**: `LANGUAGE_CODE = 'de-ch'` but `TIME_ZONE = 'UTC'` - timezone should probably be 'Europe/Zurich' for Swiss deployment
|
||||||
|
- **File**: `/home/adebaumann/development/vgui-cicd/VorgabenUI/settings.py` (lines 141)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. MODELS REVIEW
|
||||||
|
|
||||||
|
### Major Issues
|
||||||
|
|
||||||
|
**1. Missing `verbose_name` on models** - Several models are missing proper `verbose_name` in their Meta class:
|
||||||
|
- `Stichwort` (`/home/adebaumann/development/vgui-cicd/stichworte/models.py`, lines 4-11) - missing verbose_name
|
||||||
|
- `Referenz` (`/home/adebaumann/development/vgui-cicd/referenzen/models.py`, lines 6-27) - missing verbose_name
|
||||||
|
- `Rolle` (`/home/adebaumann/development/vgui-cicd/rollen/models.py`, lines 5-11) - missing verbose_name
|
||||||
|
- `Person` (`/home/adebaumann/development/vgui-cicd/dokumente/models.py`, lines 22-30) - missing verbose_name
|
||||||
|
- `Thema` (`/home/adebaumann/development/vgui-cicd/dokumente/models.py`, lines 32-39) - missing verbose_name
|
||||||
|
|
||||||
|
**2. BooleanField with `blank=True` but no default** (`/home/adebaumann/development/vgui-cicd/dokumente/models.py`, line 52)
|
||||||
|
```python
|
||||||
|
aktiv = models.BooleanField(blank=True)
|
||||||
|
```
|
||||||
|
- **Issue**: BooleanField should have explicit `default=False` for clarity
|
||||||
|
- **Recommendation**: Add `default=False`
|
||||||
|
|
||||||
|
**3. No database constraints for date validation** (`/home/adebaumann/development/vgui-cicd/dokumente/models.py`, lines 96-97)
|
||||||
|
```python
|
||||||
|
gueltigkeit_von = models.DateField()
|
||||||
|
gueltigkeit_bis = models.DateField(blank=True,null=True)
|
||||||
|
```
|
||||||
|
- **Issue**: No database-level constraint ensuring `gueltigkeit_bis >= gueltigkeit_von`
|
||||||
|
- **Recommendation**: Add constraint validation or override `save()` method
|
||||||
|
|
||||||
|
### Minor Issues
|
||||||
|
|
||||||
|
**4. Inconsistent naming in foreign key fields** - Some models use plural related names inconsistently:
|
||||||
|
- `autoren` and `pruefende` on `Dokument` use plural (correct)
|
||||||
|
- Could consider singular `related_name` for consistency where applicable
|
||||||
|
|
||||||
|
**5. Missing `related_name` on some ManyToMany fields** (`/home/adebaumann/development/vgui-cicd/dokumente/models.py`, line 289)
|
||||||
|
```python
|
||||||
|
autoren = models.ManyToManyField(Person) # Missing related_name
|
||||||
|
```
|
||||||
|
- **Recommendation**: Add `related_name='changelog_entries'` for clarity
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. VIEWS REVIEW
|
||||||
|
|
||||||
|
### Critical Issues
|
||||||
|
|
||||||
|
**1. No CSRF protection on comment endpoints** (`/home/adebaumann/development/vgui-cicd/dokumente/views.py`, lines 321-377)
|
||||||
|
```python
|
||||||
|
@require_POST
|
||||||
|
@login_required
|
||||||
|
def add_vorgabe_comment(request, vorgabe_id):
|
||||||
|
# No @csrf_exempt but also no CSRF token verification in the view
|
||||||
|
```
|
||||||
|
- **Issue**: The view uses `@require_POST` but doesn't verify CSRF tokens for the JSON endpoint
|
||||||
|
- **Recommendation**: Add `@csrf_exempt` ONLY if intentionally bypassing, or ensure CSRF is handled via the X-CSRFToken header (which is done in the template)
|
||||||
|
|
||||||
|
**Note**: Looking at line 368, the template sends `'X-CSRFToken': getCookie('csrftoken')`, so this is actually properly handled. **Not a bug.**
|
||||||
|
|
||||||
|
**2. XSS in comment display** (`/home/adebaumann/development/vgui-cicd/dokumente/views.py`, lines 305-306)
|
||||||
|
```python
|
||||||
|
escaped_text = escape(comment.text).replace('\n', '<br>')
|
||||||
|
```
|
||||||
|
- **Issue**: Using `escape()` is good, but line breaks are converted to `<br>` which could still be exploited
|
||||||
|
- **Note**: The `dangerous_patterns` check at lines 339-343 provides some protection
|
||||||
|
- **Recommendation**: Consider using a more robust HTML sanitization library
|
||||||
|
|
||||||
|
**3. No input validation on comment length** (`/home/adebaumann/development/vgui-cicd/dokumente/views.py`, line 335)
|
||||||
|
```python
|
||||||
|
if len(text) > 2000: # Reasonable length limit
|
||||||
|
```
|
||||||
|
- **Issue**: This is actually properly implemented with a 2000 character limit
|
||||||
|
- **Status**: OK
|
||||||
|
|
||||||
|
### Major Issues
|
||||||
|
|
||||||
|
**4. Referenz view lacks error handling** (`/home/adebaumann/development/vgui-cicd/referenzen/views.py`, lines 11-19)
|
||||||
|
```python
|
||||||
|
def detail(request, refid):
|
||||||
|
referenz_item = Referenz.objects.get(id=refid)
|
||||||
|
```
|
||||||
|
- **Issue**: `DoesNotExist` exception not caught - will return 500 error instead of 404
|
||||||
|
- **Recommendation**: Use `get_object_or_404` for consistency
|
||||||
|
|
||||||
|
### Minor Issues
|
||||||
|
|
||||||
|
**5. Search view allows complex regex patterns** (`/home/adebaumann/development/vgui-cicd/pages/views.py`, lines 36-70)
|
||||||
|
- **Issue**: The validation is good but the `groupby` usage at line 54 could fail if data is not properly sorted
|
||||||
|
- **Recommendation**: Add explicit ordering before groupby
|
||||||
|
|
||||||
|
**6. No rate limiting on search** (`/home/adebaumann/development/vgui-cicd/pages/views.py`)
|
||||||
|
- **Issue**: Search endpoint could be abused for DoS
|
||||||
|
- **Recommendation**: Add rate limiting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. URL CONFIGURATION
|
||||||
|
|
||||||
|
### Minor Issues
|
||||||
|
|
||||||
|
**1. No URL namespace for include** (`/home/adebaumann/development/vgui-cicd/VorgabenUI/urls.py`, lines 31-35)
|
||||||
|
```python
|
||||||
|
path('dokumente/', include("dokumente.urls")),
|
||||||
|
path('stichworte/', include("stichworte.urls")),
|
||||||
|
```
|
||||||
|
- **Issue**: No `app_name` namespace defined in included apps
|
||||||
|
- **Recommendation**: Add `app_name = 'dokumente'` to `dokumente/urls.py` for cleaner reversals
|
||||||
|
|
||||||
|
**2. Inconsistent trailing slashes** - Most URLs have trailing slashes but not all - ensure consistency
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. TEMPLATES REVIEW
|
||||||
|
|
||||||
|
### Critical Issues
|
||||||
|
|
||||||
|
**1. XSS vulnerability in `standard_detail.html`** (`/home/adebaumann/development/vgui-cicd/dokumente/templates/standards/standard_detail.html`, lines 163-164)
|
||||||
|
```html
|
||||||
|
{% for ref in vorgabe.referenzpfade %}
|
||||||
|
{{ ref|safe }}{% if not forloop.last %}, {% endif %}
|
||||||
|
```
|
||||||
|
- **Issue**: Using `|safe` on reference paths could allow XSS if malicious content is stored
|
||||||
|
- **Recommendation**: Use `escape` filter and handle line breaks separately
|
||||||
|
|
||||||
|
**2. JavaScript in template without CSP** (`/home/adebaumann/development/vgui-cicd/dokumente/templates/standards/standard_detail.html`, lines 249-424)
|
||||||
|
- **Issue**: Inline JavaScript in template
|
||||||
|
- **Note**: CSP headers are set in the view (line 316-317), but inline scripts violate strict CSP
|
||||||
|
- **Recommendation**: Move JavaScript to external file
|
||||||
|
|
||||||
|
### Major Issues
|
||||||
|
|
||||||
|
**3. Missing ARIA labels and roles** - Several accessibility issues:
|
||||||
|
- Base template (`/home/adebaumann/development/vgui-cicd/pages/templates/base.html`) has navigation but missing `aria-label` on some elements
|
||||||
|
- The mobile navigation could use better ARIA attributes
|
||||||
|
|
||||||
|
**4. Missing alt attributes on images** (`/home/adebaumann/development/vgui-cicd/pages/templates/base.html`, lines 39-41)
|
||||||
|
```html
|
||||||
|
<img src="{% static 'swiss/img/logo-CH.svg' %}"
|
||||||
|
onerror="this.onerror=null; this.src='{% static 'swiss/img/logo-CH.png' %}'"
|
||||||
|
alt="Zur Startseite" />
|
||||||
|
```
|
||||||
|
- **Issue**: alt is present but could be more descriptive
|
||||||
|
- **Status**: Acceptable
|
||||||
|
|
||||||
|
### Minor Issues
|
||||||
|
|
||||||
|
**5. Hardcoded URLs in templates** (`/home/adebaumann/development/vgui-cicd/dokumente/templates/standards/incomplete_vorgaben.html`, line 21)
|
||||||
|
```html
|
||||||
|
<a href="/autorenumgebung/dokumente/vorgabe/{{ item.vorgabe.id }}/change/"
|
||||||
|
```
|
||||||
|
- **Issue**: Hardcoded admin URL instead of using URL reversal
|
||||||
|
- **Recommendation**: Use `{% url 'admin:dokumente_vorgabe_change' item.vorgabe.id %}`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. FORMS REVIEW
|
||||||
|
|
||||||
|
### Minor Issues
|
||||||
|
|
||||||
|
**1. No dedicated form classes** - Most form handling is done via Django admin forms or directly in views
|
||||||
|
- **Recommendation**: Consider creating explicit `ModelForm` classes for views that accept user input (e.g., comment form)
|
||||||
|
|
||||||
|
**2. VorgabeForm in admin could have more validation** (`/home/adebaumann/development/vgui-cicd/dokumente/admin.py`, lines 95-107)
|
||||||
|
- **Issue**: The form only validates Thema is required
|
||||||
|
- **Recommendation**: Add validation for date ranges and conflicts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. MANAGEMENT COMMANDS
|
||||||
|
|
||||||
|
### Major Issues
|
||||||
|
|
||||||
|
**1. import-document command lacks error recovery** (`/home/adebaumann/development/vgui-cicd/dokumente/management/commands/import-document.py`, lines 288-349)
|
||||||
|
```python
|
||||||
|
for v in vorgaben_data:
|
||||||
|
try:
|
||||||
|
thema = Thema.objects.get(name=v["thema"])
|
||||||
|
except Thema.DoesNotExist:
|
||||||
|
self.stdout.write(self.style.WARNING(...))
|
||||||
|
continue # Silently skips vorgabe
|
||||||
|
```
|
||||||
|
- **Issue**: If one Vorgabe fails, the entire command may leave partial data
|
||||||
|
- **Recommendation**: Use `transaction.atomic()` to ensure atomicity
|
||||||
|
|
||||||
|
**2. No progress indicator for large imports** (`/home/adebaumann/development/vgui-cicd/dokumente/management/commands/import-document.py`)
|
||||||
|
- **Issue**: For large files, no progress shown
|
||||||
|
- **Recommendation**: Add progress output
|
||||||
|
|
||||||
|
### Minor Issues
|
||||||
|
|
||||||
|
**3. export_json command hardcodes "Standard IT-Sicherheit"** (`/home/adebaumann/development/vgui-cicd/dokumente/management/commands/export_json.py`, line 30)
|
||||||
|
```python
|
||||||
|
result = {
|
||||||
|
"Vorgabendokument": {
|
||||||
|
"Typ": "Standard IT-Sicherheit",
|
||||||
|
```
|
||||||
|
- **Issue**: Typ is hardcoded instead of using `dokument.dokumententyp.name`
|
||||||
|
- **Recommendation**: Use actual dokumententyp
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. TESTS REVIEW
|
||||||
|
|
||||||
|
### Major Issues
|
||||||
|
|
||||||
|
**1. No tests for diagram caching** - The `diagramm_proxy` module has no test coverage
|
||||||
|
- **Recommendation**: Add tests for `diagram_cache.py`
|
||||||
|
|
||||||
|
**2. No tests for referenzen views** - The tree view and detail view have no test coverage
|
||||||
|
- **Recommendation**: Add tests for `referenzen/views.py`
|
||||||
|
|
||||||
|
**3. No tests for authentication security** - Missing tests for:
|
||||||
|
- Brute-force protection
|
||||||
|
- Session management
|
||||||
|
- Password policy enforcement
|
||||||
|
|
||||||
|
### Minor Issues
|
||||||
|
|
||||||
|
**4. Test file organization** - `test_json.py` should be part of `tests.py` or in a proper test package structure
|
||||||
|
- **Status**: Acceptable but could be improved
|
||||||
|
|
||||||
|
**5. Tests rely on hardcoded paths** (`/home/adebaumann/development/vgui-cicd/dokumente/tests.py`, lines 1165-1168)
|
||||||
|
```python
|
||||||
|
self.assertContains(response, 'href="/autorenumgebung/dokumente/vorgabe/2/change/"')
|
||||||
|
```
|
||||||
|
- **Issue**: Uses hardcoded URL paths instead of URL reversal
|
||||||
|
- **Recommendation**: Use `reverse('admin:dokumente_vorgabe_change', args=[vorgabe.pk])`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. SECURITY REVIEW
|
||||||
|
|
||||||
|
### Critical Issues
|
||||||
|
|
||||||
|
**1. No rate limiting on any endpoint** - All views lack rate limiting
|
||||||
|
- **Recommendation**: Add `django-ratelimit` or similar
|
||||||
|
|
||||||
|
**2. Diagram cache potentially vulnerable to DoS** (`/home/adebaumann/development/vgui-cicd/diagramm_proxy/diagram_cache.py`, lines 24-67)
|
||||||
|
```python
|
||||||
|
def get_cached_diagram(diagram_type, diagram_content):
|
||||||
|
content_hash = compute_hash(diagram_content)
|
||||||
|
cache_path = get_cache_path(diagram_type, content_hash)
|
||||||
|
|
||||||
|
if default_storage.exists(cache_path):
|
||||||
|
return cache_path
|
||||||
|
|
||||||
|
# Generate diagram via POST request
|
||||||
|
url = f"{KROKI_UPSTREAM}/{diagram_type}/svg"
|
||||||
|
```
|
||||||
|
- **Issue**: No validation on diagram size or content - could lead to DoS via large diagrams
|
||||||
|
- **Recommendation**: Add size limits and timeout
|
||||||
|
|
||||||
|
### Major Issues
|
||||||
|
|
||||||
|
**3. CSRF trusted origins only for HTTPS** (`/home/adebaumann/development/vgui-cicd/VorgabenUI/settings.py`, line 104)
|
||||||
|
```python
|
||||||
|
CSRF_TRUSTED_ORIGINS=["https://vorgabenportal.knowyoursecurity.com"]
|
||||||
|
```
|
||||||
|
- **Issue**: Only one origin configured - ensure this covers all deployment URLs
|
||||||
|
- **Recommendation**: Make configurable via environment variable
|
||||||
|
|
||||||
|
**4. No session expiry configuration** (`/home/adebaumann/development/vgui-cicd/VorgabenUI/settings.py`)
|
||||||
|
- **Issue**: Sessions don't expire
|
||||||
|
- **Recommendation**: Set `SESSION_COOKIE_AGE` and `SESSION_SAVE_EVERY_REQUEST`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. CODE STYLE COMPLIANCE (AGENTS.md)
|
||||||
|
|
||||||
|
### Violations Found
|
||||||
|
|
||||||
|
**1. Import order inconsistent** (`/home/adebaumann/development/vgui-cicd/dokumente/views.py`, lines 1-16)
|
||||||
|
- Imports are not strictly ordered (stdlib, Django, local apps)
|
||||||
|
- Example: `import parsedatetime` is placed after Django imports
|
||||||
|
|
||||||
|
**2. Missing German `verbose_name` on models** (as noted in Section 3)
|
||||||
|
|
||||||
|
**3. Function naming** (`/home/adebaumann/development/vgui-cicd/dokumente/models.py`, line 101)
|
||||||
|
```python
|
||||||
|
def Vorgabennummer(self): # Should be vorgabennummer (snake_case)
|
||||||
|
```
|
||||||
|
- **Issue**: Method name uses PascalCase instead of snake_case per AGENTS.md guidelines
|
||||||
|
|
||||||
|
**4. Typo in error message** (`/home/adebaumann/development/vgui-cicd/dokumente/models.py`, line 213)
|
||||||
|
```python
|
||||||
|
"Geltungsdauer übeschneidet sich" # Should be "überschneidet sich"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. PERFORMANCE ISSUES
|
||||||
|
|
||||||
|
### Major Issues
|
||||||
|
|
||||||
|
**1. N+1 query potential in search** (`/home/adebaumann/development/vgui-cicd/pages/views.py`, lines 53-68)
|
||||||
|
```python
|
||||||
|
qs = VorgabeKurztext.objects.filter(inhalt__icontains=suchbegriff)...
|
||||||
|
```
|
||||||
|
- **Issue**: Uses `icontains` which cannot use database indexes effectively
|
||||||
|
- **Recommendation**: Consider PostgreSQL full-text search for better performance
|
||||||
|
|
||||||
|
**2. No select_related in referenzen views** (`/home/adebaumann/development/vgui-cicd/referenzen/views.py`, lines 7-8)
|
||||||
|
```python
|
||||||
|
def tree(request):
|
||||||
|
referenz_items = Referenz.objects.all()
|
||||||
|
```
|
||||||
|
- **Issue**: No prefetch_related for related data
|
||||||
|
- **Recommendation**: Add prefetch_related for `referenzerklaerung_set` and `unterreferenzen`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SUMMARY
|
||||||
|
|
||||||
|
### Critical Issues (Must Fix)
|
||||||
|
| # | Issue | Location | Recommendation |
|
||||||
|
|---|-------|----------|----------------|
|
||||||
|
| 1 | SECRET_KEY fallback | `VorgabenUI/settings.py:27-47` | Never enable fallback, require env var |
|
||||||
|
| 2 | DEBUG defaults to True | `VorgabenUI/settings.py:24` | Require explicit False for production |
|
||||||
|
| 3 | No rate limiting | All views | Add django-ratelimit |
|
||||||
|
| 4 | Session never expires | `VorgabenUI/settings.py` | Set SESSION_COOKIE_AGE |
|
||||||
|
| 5 | XSS via `\|safe` filter | `standard_detail.html:163-164` | Use `escape` filter |
|
||||||
|
|
||||||
|
### Major Issues (Should Fix)
|
||||||
|
| # | Issue | Location | Recommendation |
|
||||||
|
|---|-------|----------|----------------|
|
||||||
|
| 1 | SQLite database | `VorgabenUI/settings.py:109-114` | Use PostgreSQL for production |
|
||||||
|
| 2 | ALLOWED_HOSTS wildcard | `VorgabenUI/settings.py:50` | Remove `*` from default |
|
||||||
|
| 3 | Missing date constraint | `dokumente/models.py:96-97` | Add validation for date ranges |
|
||||||
|
| 4 | Import not atomic | `import-document.py:288-349` | Wrap in transaction.atomic() |
|
||||||
|
| 5 | Missing test coverage | Multiple modules | Add tests for untested code |
|
||||||
|
|
||||||
|
### Minor Issues (Nice to Fix)
|
||||||
|
| # | Issue | Location |
|
||||||
|
|---|-------|----------|
|
||||||
|
| 1 | Code style violations | `dokumente/views.py:1-16` |
|
||||||
|
| 2 | Typo: "übeschneidet" | `dokumente/models.py:213` |
|
||||||
|
| 3 | Missing ARIA labels | `base.html` |
|
||||||
|
| 4 | Hardcoded URLs in templates | `incomplete_vorgaben.html:21` |
|
||||||
|
| 5 | Duplicate AUTH_PASSWORD_VALIDATORS | `settings-docker.py:92-105, 183-199` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## RECOMMENDED ACTIONS
|
||||||
|
|
||||||
|
### Immediate (Before Production)
|
||||||
|
1. Set up proper SECRET_KEY via environment variable
|
||||||
|
2. Configure DEBUG=False explicitly
|
||||||
|
3. Remove wildcard from ALLOWED_HOSTS default
|
||||||
|
4. Add rate limiting to all endpoints
|
||||||
|
5. Configure session expiry
|
||||||
|
6. Fix XSS vulnerability in template
|
||||||
|
7. Switch to PostgreSQL database
|
||||||
|
|
||||||
|
### Short-term (1-2 Weeks)
|
||||||
|
1. Add database constraints for date validation
|
||||||
|
2. Wrap import command in transactions
|
||||||
|
3. Add missing verbose_name to models
|
||||||
|
4. Fix code style violations
|
||||||
|
5. Add test coverage for critical paths
|
||||||
|
6. Move inline JavaScript to external files
|
||||||
|
|
||||||
|
### Long-term (1 Month)
|
||||||
|
1. Implement PostgreSQL full-text search
|
||||||
|
2. Add comprehensive test suite
|
||||||
|
3. Set up CSP headers properly
|
||||||
|
4. Implement comprehensive authentication security
|
||||||
|
5. Add performance monitoring
|
||||||
|
6. Document all security configurations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## POSITIVE FINDINGS
|
||||||
|
|
||||||
|
1. **Good project organization** - Clear app structure following Django conventions
|
||||||
|
2. **Proper CSRF handling** - X-CSRFToken header properly implemented
|
||||||
|
3. **Input validation** - Comment length limits and dangerous pattern checks in place
|
||||||
|
4. **Good German localization** - German field names and verbose texts throughout
|
||||||
|
5. **Django admin integration** - Well-configured admin interface
|
||||||
|
6. **Management commands** - Useful import/export functionality
|
||||||
|
7. **Referenzen tree structure** - MPTT implementation for hierarchical data
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## APPENDIX: Additional Issues Detected by LSP
|
||||||
|
|
||||||
|
The following issues were detected by the Language Server Protocol (LSP) analyzer:
|
||||||
|
|
||||||
|
### dokumente/models.py
|
||||||
|
| Line | Issue |
|
||||||
|
|------|-------|
|
||||||
|
| 14, 26, 36, 274 | `__str__` method return type mismatch - returns `CharField` instead of `str` |
|
||||||
|
| 70 | Unknown attribute `vorgaben` on `Dokument` |
|
||||||
|
| 102 | Unknown attribute `nummer` on `ForeignKey` |
|
||||||
|
| 106, 114 | Unknown attribute `strftime` on `DateField` |
|
||||||
|
| 137, 144, 196 | Unknown attribute `objects` on `type[Vorgabe]` |
|
||||||
|
| 173 | Unknown attribute `thema_id` on `Vorgabe` |
|
||||||
|
| 248, 254, 260, 266 | `Meta` class overrides incompatible parent class |
|
||||||
|
| 282 | `VorgabenTable.Meta` incompatible with `Vorgabe.Meta` |
|
||||||
|
| 294 | Unknown attribute `nummer` on `ForeignKey` |
|
||||||
|
| 314 | Unknown attributes `username` and `Vorgabennummer` on `ForeignKey` |
|
||||||
|
|
||||||
|
### dokumente/views.py
|
||||||
|
| Line | Issue |
|
||||||
|
|------|-------|
|
||||||
|
| 22, 133, 265 | Unknown attribute `objects` on `type[Dokument]` |
|
||||||
|
| 94 | Unknown attribute `objects` on `type[Vorgabe]` |
|
||||||
|
| 285 | Argument type `str` cannot be assigned to parameter `content` of type `bytes` |
|
||||||
|
| 345, 412, 440 | Unknown attribute `objects` on `type[VorgabeComment]` |
|
||||||
|
|
||||||
|
### abschnitte/models.py
|
||||||
|
| Line | Issue |
|
||||||
|
|------|-------|
|
||||||
|
| 6 | `__str__` method return type mismatch |
|
||||||
|
| 18 | Argument type `Literal[0]` cannot be assigned to parameter `default` |
|
||||||
|
|
||||||
|
### referenzen/models.py
|
||||||
|
| Line | Issue |
|
||||||
|
|------|-------|
|
||||||
|
| 23 | `__str__` method return type mismatch |
|
||||||
|
| 26 | `Referenz.Meta` overrides incompatible parent `MPTTModel.Meta` |
|
||||||
|
| 32 | `Referenzerklaerung.Meta` overrides incompatible parent `Textabschnitt.Meta` |
|
||||||
|
|
||||||
|
### rollen/models.py
|
||||||
|
| Line | Issue |
|
||||||
|
|------|-------|
|
||||||
|
| 8 | `__str__` method return type mismatch |
|
||||||
|
| 15 | `RollenBeschreibung.Meta` overrides incompatible parent `Textabschnitt.Meta` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*End of Code Review*
|
||||||
@@ -12,7 +12,7 @@ class Dokumententyp(models.Model):
|
|||||||
verantwortliche_ve = models.CharField(max_length=255)
|
verantwortliche_ve = models.CharField(max_length=255)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return str(self.name)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name="Dokumententyp"
|
verbose_name="Dokumententyp"
|
||||||
@@ -28,6 +28,7 @@ class Person(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name_plural="Personen"
|
verbose_name_plural="Personen"
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
verbose_name="Person"
|
||||||
|
|
||||||
class Thema(models.Model):
|
class Thema(models.Model):
|
||||||
name = models.CharField(max_length=100, primary_key=True)
|
name = models.CharField(max_length=100, primary_key=True)
|
||||||
@@ -37,7 +38,7 @@ class Thema(models.Model):
|
|||||||
return self.name
|
return self.name
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name_plural="Themen"
|
verbose_name_plural="Themen"
|
||||||
|
verbose_name="Thema"
|
||||||
|
|
||||||
class Dokument(models.Model):
|
class Dokument(models.Model):
|
||||||
nummer = models.CharField(max_length=50, primary_key=True)
|
nummer = models.CharField(max_length=50, primary_key=True)
|
||||||
@@ -49,7 +50,7 @@ class Dokument(models.Model):
|
|||||||
gueltigkeit_bis = models.DateField(null=True, blank=True)
|
gueltigkeit_bis = models.DateField(null=True, blank=True)
|
||||||
signatur_cso = models.CharField(max_length=255, blank=True)
|
signatur_cso = models.CharField(max_length=255, blank=True)
|
||||||
anhaenge = models.TextField(blank=True)
|
anhaenge = models.TextField(blank=True)
|
||||||
aktiv = models.BooleanField(blank=True)
|
aktiv = models.BooleanField(blank=True,default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.nummer} – {self.name}"
|
return f"{self.nummer} – {self.name}"
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
name: data-loader
|
|
||||||
namespace: vorgabenui
|
|
||||||
spec:
|
|
||||||
restartPolicy: Never
|
|
||||||
containers:
|
|
||||||
- name: loader
|
|
||||||
image: adebaumann/vgui-preloader:0.5
|
|
||||||
command: ["sh","-c","cp -v --debug --update=none /preload/preload.sqlite3 /data/db.sqlite3; chown -R 999:999 /data; ls -la /data; exit 0"]
|
|
||||||
volumeMounts:
|
|
||||||
- name: data
|
|
||||||
mountPath: /data
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
- name: data
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: django-data-pvc
|
|
||||||
Binary file not shown.
@@ -1,68 +0,0 @@
|
|||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: django
|
|
||||||
namespace: vorgabenui
|
|
||||||
spec:
|
|
||||||
replicas: 10
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: django
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: django
|
|
||||||
spec:
|
|
||||||
securityContext:
|
|
||||||
fsGroup: 999
|
|
||||||
fsGroupChangePolicy: "OnRootMismatch"
|
|
||||||
initContainers:
|
|
||||||
- name: loader
|
|
||||||
image: adebaumann/vgui-preloader:0.5
|
|
||||||
command: [ "sh","-c","cp -v --debug --update=none /preload/preload.sqlite3 /data/db.sqlite3; chown -R 999:999 /data; ls -la /data; exit 0" ]
|
|
||||||
volumeMounts:
|
|
||||||
- name: data
|
|
||||||
mountPath: /data
|
|
||||||
containers:
|
|
||||||
- name: web
|
|
||||||
image: docker.io/adebaumann/vui:0.918
|
|
||||||
imagePullPolicy: Always
|
|
||||||
ports:
|
|
||||||
- containerPort: 8000
|
|
||||||
volumeMounts:
|
|
||||||
- name: data
|
|
||||||
mountPath: /app/data
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: 8000
|
|
||||||
initialDelaySeconds: 5
|
|
||||||
periodSeconds: 10
|
|
||||||
timeoutSeconds: 2
|
|
||||||
failureThreshold: 6
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: 8000
|
|
||||||
initialDelaySeconds: 20
|
|
||||||
periodSeconds: 20
|
|
||||||
timeoutSeconds: 2
|
|
||||||
failureThreshold: 3
|
|
||||||
volumes:
|
|
||||||
- name: data
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: django-data-pvc
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: django
|
|
||||||
namespace: vorgabenui
|
|
||||||
spec:
|
|
||||||
type: ClusterIP
|
|
||||||
selector:
|
|
||||||
app: django
|
|
||||||
ports:
|
|
||||||
- port: 8000
|
|
||||||
targetPort: 8000
|
|
||||||
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: kroki
|
|
||||||
namespace: vorgabenui
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: kroki
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: kroki
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: kroki
|
|
||||||
image: docker.io/yuzutech/kroki:latest
|
|
||||||
ports:
|
|
||||||
- containerPort: 8000
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: 8000
|
|
||||||
initialDelaySeconds: 5
|
|
||||||
periodSeconds: 10
|
|
||||||
timeoutSeconds: 2
|
|
||||||
failureThreshold: 6
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: 8000
|
|
||||||
initialDelaySeconds: 20
|
|
||||||
periodSeconds: 20
|
|
||||||
timeoutSeconds: 2
|
|
||||||
failureThreshold: 3
|
|
||||||
- name: mermaid
|
|
||||||
image: docker.io/yuzutech/kroki-mermaid:latest
|
|
||||||
ports:
|
|
||||||
- containerPort: 8002
|
|
||||||
- name: bpmn
|
|
||||||
image: docker.io/yuzutech/kroki-bpmn:latest
|
|
||||||
ports:
|
|
||||||
- containerPort: 8003
|
|
||||||
- name: excalidraw
|
|
||||||
image: docker.io/yuzutech/kroki-excalidraw:latest
|
|
||||||
ports:
|
|
||||||
- containerPort: 8004
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: svckroki
|
|
||||||
namespace: vorgabenui
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: kroki
|
|
||||||
ports:
|
|
||||||
- port: 8000
|
|
||||||
targetPort: 8000
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: vgui-cicd-django
|
|
||||||
namespace: vorgabenui
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: vgui-cicd-django
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: vgui-cicd-django
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: django
|
|
||||||
image: your-django-image:latest
|
|
||||||
ports:
|
|
||||||
- containerPort: 8000
|
|
||||||
env:
|
|
||||||
# Django SECRET_KEY from Kubernetes secret
|
|
||||||
- name: VORGABENUI_SECRET
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: vorgabenui-secrets
|
|
||||||
key: vorgabenui_secret
|
|
||||||
# Other environment variables can be added here
|
|
||||||
- name: DEBUG
|
|
||||||
value: "False"
|
|
||||||
# Add database configuration, etc.
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: "256Mi"
|
|
||||||
cpu: "250m"
|
|
||||||
limits:
|
|
||||||
memory: "512Mi"
|
|
||||||
cpu: "500m"
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: vgui-cicd-django-service
|
|
||||||
namespace: vorgabenui
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: vgui-cicd-django
|
|
||||||
ports:
|
|
||||||
- protocol: TCP
|
|
||||||
port: 80
|
|
||||||
targetPort: 8000
|
|
||||||
type: ClusterIP
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: vorgabenui-secrets
|
|
||||||
namespace: vorgabenui
|
|
||||||
type: Opaque
|
|
||||||
data:
|
|
||||||
# Base64 encoded SECRET_KEY - will be populated by deployment script
|
|
||||||
vorgabenui_secret: ""
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: django
|
|
||||||
namespace: vorgabenui
|
|
||||||
annotations:
|
|
||||||
traefik.ingress.kubernetes.io/router.middlewares: "vorgabenui-vorgabenui-rewrite@kubernetescrd"
|
|
||||||
spec:
|
|
||||||
rules:
|
|
||||||
- host: vorgabenui.adebaumann.com
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /
|
|
||||||
pathType: Prefix
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: django
|
|
||||||
port:
|
|
||||||
number: 8000
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: django
|
|
||||||
namespace: vorgabenui
|
|
||||||
spec:
|
|
||||||
ingressClassName: nginx
|
|
||||||
rules:
|
|
||||||
- host: vorgabenui.local
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /
|
|
||||||
pathType: Prefix
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: django
|
|
||||||
port:
|
|
||||||
number: 8000
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolume
|
|
||||||
metadata:
|
|
||||||
name: django-data-pv
|
|
||||||
namespace: vorgabenui
|
|
||||||
spec:
|
|
||||||
capacity:
|
|
||||||
storage: 2Gi
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteMany
|
|
||||||
persistentVolumeReclaimPolicy: Retain
|
|
||||||
storageClassName: nfs
|
|
||||||
nfs:
|
|
||||||
server: 192.168.17.199
|
|
||||||
path: /mnt/user/vorgabenui
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
apiVersion: storage.k8s.io/v1
|
|
||||||
kind: StorageClass
|
|
||||||
metadata:
|
|
||||||
name: nfs
|
|
||||||
provisioner: kubernetes.io/no-provisioner
|
|
||||||
allowVolumeExpansion: true
|
|
||||||
reclaimPolicy: Retain
|
|
||||||
volumeBindingMode: Immediate
|
|
||||||
13
k8s/pvc.yaml
13
k8s/pvc.yaml
@@ -1,13 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: django-data-pvc
|
|
||||||
namespace: vorgabenui
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteMany
|
|
||||||
storageClassName: nfs
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 2Gi
|
|
||||||
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
apiVersion: traefik.containo.us/v1alpha1
|
|
||||||
kind: Middleware
|
|
||||||
metadata:
|
|
||||||
name: vorgabenui-rewrite
|
|
||||||
namespace: vorgabenui
|
|
||||||
spec:
|
|
||||||
stripPrefix:
|
|
||||||
prefixes:
|
|
||||||
- "/"
|
|
||||||
@@ -219,7 +219,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 text-right">
|
<div class="col-sm-6 text-right">
|
||||||
<p class="text-muted">Version {{ version|default:"0.980" }}</p>
|
<p class="text-muted">Version {{ version|default:"0.986" }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
17
referenzen/migrations/0004_alter_referenz_options.py
Normal file
17
referenzen/migrations/0004_alter_referenz_options.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 6.0.1 on 2026-01-20 08:57
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('referenzen', '0003_alter_referenzerklaerung_options'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='referenz',
|
||||||
|
options={'verbose_name': 'Referenz', 'verbose_name_plural': 'Referenzen'},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -25,6 +25,7 @@ class Referenz(MPTTModel):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name_plural="Referenzen"
|
verbose_name_plural="Referenzen"
|
||||||
|
verbose_name="Referenz"
|
||||||
|
|
||||||
class Referenzerklaerung (Textabschnitt):
|
class Referenzerklaerung (Textabschnitt):
|
||||||
erklaerung = models.ForeignKey(Referenz,on_delete=models.CASCADE)
|
erklaerung = models.ForeignKey(Referenz,on_delete=models.CASCADE)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from .models import Referenz
|
from .models import Referenz
|
||||||
from abschnitte.utils import render_textabschnitte
|
from abschnitte.utils import render_textabschnitte
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
def tree(request):
|
def tree(request):
|
||||||
@@ -9,7 +10,7 @@ def tree(request):
|
|||||||
|
|
||||||
|
|
||||||
def detail(request, refid):
|
def detail(request, refid):
|
||||||
referenz_item = Referenz.objects.get(id=refid)
|
referenz_item = get_object_or_404(Referenz, id=refid)
|
||||||
referenz_item.erklaerung = render_textabschnitte(referenz_item.referenzerklaerung_set.order_by("order"))
|
referenz_item.erklaerung = render_textabschnitte(referenz_item.referenzerklaerung_set.order_by("order"))
|
||||||
referenz_item.children = list(referenz_item.get_descendants(include_self=True))
|
referenz_item.children = list(referenz_item.get_descendants(include_self=True))
|
||||||
for child in referenz_item.children:
|
for child in referenz_item.children:
|
||||||
|
|||||||
@@ -1,37 +1,44 @@
|
|||||||
appdirs==1.4.4
|
appdirs==1.4.4
|
||||||
asgiref==3.8.1
|
asgiref==3.11.0
|
||||||
blessed==1.21.0
|
bleach==6.3.0
|
||||||
certifi==2025.8.3
|
blessed==1.27.0
|
||||||
charset-normalizer==3.4.3
|
certifi==2026.1.4
|
||||||
|
charset-normalizer==3.4.4
|
||||||
|
coverage==7.13.1
|
||||||
curtsies==0.4.3
|
curtsies==0.4.3
|
||||||
cwcwidth==0.1.10
|
cwcwidth==0.1.12
|
||||||
Django==5.2.9
|
Django==6.0.1
|
||||||
django-admin-sortable2==2.2.8
|
django-admin-sortable2==2.3
|
||||||
django-js-asset==3.1.2
|
django-js-asset==3.1.2
|
||||||
django-mptt==0.17.0
|
django-mptt==0.18.0
|
||||||
django-mptt-admin==2.8.0
|
django-mptt-admin==2.9.0
|
||||||
django-nested-admin==4.1.1
|
django-nested-admin==4.1.6
|
||||||
django-nested-inline==0.4.6
|
django-nested-inline==0.4.6
|
||||||
django-revproxy==0.13.0
|
django-revproxy==0.13.0
|
||||||
greenlet==3.2.4
|
greenlet==3.3.0
|
||||||
gunicorn==23.0.0
|
gunicorn==23.0.0
|
||||||
idna==3.10
|
idna==3.11
|
||||||
jedi==0.19.2
|
jedi==0.19.2
|
||||||
Markdown==3.8.2
|
jproperties==2.1.2
|
||||||
|
Markdown==3.10
|
||||||
packaging==25.0
|
packaging==25.0
|
||||||
parsedatetime==2.6
|
parsedatetime==2.6
|
||||||
parso==0.8.4
|
parso==0.8.5
|
||||||
pep8==1.7.1
|
pep8==1.7.1
|
||||||
prompt_toolkit==3.0.51
|
prompt_toolkit==3.0.52
|
||||||
|
pyfakefs==5.9.3
|
||||||
Pygments==2.19.2
|
Pygments==2.19.2
|
||||||
|
pysonar==1.2.1.3951
|
||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
python-monkey-business==1.1.0
|
python-monkey-business==1.1.0
|
||||||
pyxdg==0.28
|
pyxdg==0.28
|
||||||
|
PyYAML==6.0.3
|
||||||
requests==2.32.5
|
requests==2.32.5
|
||||||
|
responses==0.25.8
|
||||||
six==1.17.0
|
six==1.17.0
|
||||||
sqlparse==0.5.3
|
sqlparse==0.5.5
|
||||||
|
tomli==2.2.1
|
||||||
urllib3==2.6.3
|
urllib3==2.6.3
|
||||||
wcwidth==0.2.13
|
wcwidth==0.2.14
|
||||||
bleach==6.1.0
|
webencodings==0.5.1
|
||||||
coverage==7.6.1
|
whitenoise==6.11.0
|
||||||
whitenoise==6.8.2
|
|
||||||
|
|||||||
17
rollen/migrations/0002_alter_rolle_options.py
Normal file
17
rollen/migrations/0002_alter_rolle_options.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 6.0.1 on 2026-01-20 08:57
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('rollen', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='rolle',
|
||||||
|
options={'verbose_name': 'Rolle (für Relevanz)', 'verbose_name_plural': 'Rolleni (für Relevanz)'},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -9,6 +9,7 @@ class Rolle(models.Model):
|
|||||||
return self.name
|
return self.name
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name_plural="Rollen"
|
verbose_name_plural="Rollen"
|
||||||
|
verbose_name="Rolle"
|
||||||
|
|
||||||
class RollenBeschreibung(Textabschnitt):
|
class RollenBeschreibung(Textabschnitt):
|
||||||
abschnitt=models.ForeignKey(Rolle,on_delete=models.CASCADE)
|
abschnitt=models.ForeignKey(Rolle,on_delete=models.CASCADE)
|
||||||
|
|||||||
@@ -1,201 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# deploy-django-secret.sh
|
|
||||||
# Script to generate a secure Django SECRET_KEY and deploy it to Kubernetes
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
NAMESPACE="${NAMESPACE:-vorgabenui}"
|
|
||||||
SECRET_NAME="vorgabenui-secrets"
|
|
||||||
SECRET_KEY_NAME="vorgabenui_secret"
|
|
||||||
K8S_DIR="$(dirname "$0")/../k8s"
|
|
||||||
SECRET_YAML="$K8S_DIR/django-secret.yaml"
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Logging functions
|
|
||||||
log_info() {
|
|
||||||
echo -e "${GREEN}[INFO]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_warn() {
|
|
||||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_error() {
|
|
||||||
echo -e "${RED}[ERROR]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to generate a secure Django SECRET_KEY
|
|
||||||
generate_secret_key() {
|
|
||||||
# Generate a 50-character secret key using Python (same as Django's default)
|
|
||||||
python3 -c "
|
|
||||||
import secrets
|
|
||||||
import string
|
|
||||||
|
|
||||||
# Django-style secret key generation
|
|
||||||
chars = string.ascii_letters + string.digits + '!@#$%^&*(-_=+)'
|
|
||||||
print(''.join(secrets.choice(chars) for _ in range(50)))
|
|
||||||
"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to check if kubectl is available
|
|
||||||
check_kubectl() {
|
|
||||||
if ! command -v kubectl &> /dev/null; then
|
|
||||||
log_error "kubectl is not installed or not in PATH"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to check if Python3 is available
|
|
||||||
check_python() {
|
|
||||||
if ! command -v python3 &> /dev/null; then
|
|
||||||
log_error "python3 is not installed or not in PATH"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to create the secret
|
|
||||||
create_secret() {
|
|
||||||
local secret_key="$1"
|
|
||||||
local encoded_key
|
|
||||||
|
|
||||||
# Base64 encode the secret key
|
|
||||||
encoded_key=$(echo -n "$secret_key" | base64 -w 0)
|
|
||||||
|
|
||||||
log_info "Creating Kubernetes secret '$SECRET_NAME' in namespace '$NAMESPACE'..."
|
|
||||||
|
|
||||||
# Create the secret directly with kubectl
|
|
||||||
kubectl create secret generic "$SECRET_NAME" \
|
|
||||||
--from-literal="$SECRET_KEY_NAME=$secret_key" \
|
|
||||||
--namespace="$NAMESPACE" \
|
|
||||||
--dry-run=client -o yaml | kubectl apply -f -
|
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
log_info "Successfully created/updated secret '$SECRET_NAME'"
|
|
||||||
else
|
|
||||||
log_error "Failed to create/update secret '$SECRET_NAME'"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to verify the secret
|
|
||||||
verify_secret() {
|
|
||||||
log_info "Verifying secret deployment..."
|
|
||||||
|
|
||||||
if kubectl get secret "$SECRET_NAME" --namespace="$NAMESPACE" &> /dev/null; then
|
|
||||||
log_info "Secret '$SECRET_NAME' exists in namespace '$NAMESPACE'"
|
|
||||||
|
|
||||||
# Show secret (without revealing the actual key)
|
|
||||||
kubectl describe secret "$SECRET_NAME" --namespace="$NAMESPACE"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
log_error "Secret '$SECRET_NAME' not found in namespace '$NAMESPACE'"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to show usage
|
|
||||||
show_usage() {
|
|
||||||
echo "Usage: $0 [OPTIONS]"
|
|
||||||
echo ""
|
|
||||||
echo "Options:"
|
|
||||||
echo " -n, --namespace NAMESPACE Kubernetes namespace (default: vorgabenui)"
|
|
||||||
echo " -s, --secret-name NAME Secret name (default: django-secrets)"
|
|
||||||
echo " -k, --key-name NAME Secret key name (default: django-secret-key)"
|
|
||||||
echo " -h, --help Show this help message"
|
|
||||||
echo ""
|
|
||||||
echo "Environment variables:"
|
|
||||||
echo " NAMESPACE Override default namespace"
|
|
||||||
echo ""
|
|
||||||
echo "Examples:"
|
|
||||||
echo " $0 # Deploy to vorgabenui namespace"
|
|
||||||
echo " $0 -n production # Deploy to production namespace"
|
|
||||||
echo " NAMESPACE=staging $0 # Deploy to staging namespace"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Parse command line arguments
|
|
||||||
while [[ $# -gt 0 ]]; do
|
|
||||||
case $1 in
|
|
||||||
-n|--namespace)
|
|
||||||
NAMESPACE="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
-s|--secret-name)
|
|
||||||
SECRET_NAME="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
-k|--key-name)
|
|
||||||
SECRET_KEY_NAME="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
-h|--help)
|
|
||||||
show_usage
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
log_error "Unknown option: $1"
|
|
||||||
show_usage
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# Main execution
|
|
||||||
main() {
|
|
||||||
log_info "Django SECRET_KEY Deployment Script"
|
|
||||||
log_info "==================================="
|
|
||||||
log_info "Namespace: $NAMESPACE"
|
|
||||||
log_info "Secret Name: $SECRET_NAME"
|
|
||||||
log_info "Secret Key Name: $SECRET_KEY_NAME"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Perform checks
|
|
||||||
check_kubectl
|
|
||||||
check_python
|
|
||||||
|
|
||||||
# Generate new secret key
|
|
||||||
log_info "Generating new Django SECRET_KEY..."
|
|
||||||
SECRET_KEY=$(generate_secret_key)
|
|
||||||
|
|
||||||
if [ -z "$SECRET_KEY" ]; then
|
|
||||||
log_error "Failed to generate secret key"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_info "Generated secret key (first 10 chars): ${SECRET_KEY:0:10}..."
|
|
||||||
|
|
||||||
# Create namespace if it doesn't exist
|
|
||||||
if ! kubectl get namespace "$NAMESPACE" &> /dev/null; then
|
|
||||||
log_warn "Namespace '$NAMESPACE' does not exist, creating..."
|
|
||||||
kubectl create namespace "$NAMESPACE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create the secret
|
|
||||||
create_secret "$SECRET_KEY"
|
|
||||||
|
|
||||||
# Verify deployment
|
|
||||||
verify_secret
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
log_info "Deployment completed successfully!"
|
|
||||||
log_info "To use this secret in your Django deployment, add the following to your pod spec:"
|
|
||||||
echo ""
|
|
||||||
echo " env:"
|
|
||||||
echo " - name: VORGABENUI_SECRET"
|
|
||||||
echo " valueFrom:"
|
|
||||||
echo " secretKeyRef:"
|
|
||||||
echo " name: $SECRET_NAME"
|
|
||||||
echo " key: $SECRET_KEY_NAME"
|
|
||||||
echo ""
|
|
||||||
log_warn "The old secret key in settings.py has been replaced with environment variable lookup."
|
|
||||||
log_warn "Make sure your Django deployment uses the environment variable before deploying."
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run main function
|
|
||||||
main
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
NAMESPACE="vorgabenui"
|
NAMESPACE="vorgabenui"
|
||||||
SECRET_NAME="django-secret"
|
SECRET_NAME="django-secret"
|
||||||
SECRET_FILE="argocd/secret.yaml"
|
SECRET_FILE="templates/secret.yaml"
|
||||||
|
|
||||||
# Check if secret file exists
|
# Check if secret file exists
|
||||||
if [ ! -f "$SECRET_FILE" ]; then
|
if [ ! -f "$SECRET_FILE" ]; then
|
||||||
|
|||||||
@@ -36,10 +36,15 @@ NEW_MAIN_VERSION=$(echo "$MAIN_VERSION + 0.001" | bc | sed 's/^\./0./')
|
|||||||
sed -i "s|image: git.baumann.gr/adebaumann/labhelper-data-loader:$LOADER_VERSION|image: git.baumann.gr/adebaumann/labhelper-data-loader:$NEW_LOADER_VERSION|" "$DEPLOYMENT_FILE"
|
sed -i "s|image: git.baumann.gr/adebaumann/labhelper-data-loader:$LOADER_VERSION|image: git.baumann.gr/adebaumann/labhelper-data-loader:$NEW_LOADER_VERSION|" "$DEPLOYMENT_FILE"
|
||||||
sed -i "s|image: git.baumann.gr/adebaumann/labhelper:$MAIN_VERSION|image: git.baumann.gr/adebaumann/labhelper:$NEW_MAIN_VERSION|" "$DEPLOYMENT_FILE"
|
sed -i "s|image: git.baumann.gr/adebaumann/labhelper:$MAIN_VERSION|image: git.baumann.gr/adebaumann/labhelper:$NEW_MAIN_VERSION|" "$DEPLOYMENT_FILE"
|
||||||
|
|
||||||
|
# Update the configmap version to match the main container
|
||||||
|
CONFIGMAP_FILE="argocd/configmap.yaml"
|
||||||
|
sed -i "s|VERSION: \"$MAIN_VERSION\"|VERSION: \"$NEW_MAIN_VERSION\"|" "$CONFIGMAP_FILE"
|
||||||
|
|
||||||
# Copy database
|
# Copy database
|
||||||
cp "$DB_SOURCE" "$DB_DEST"
|
cp "$DB_SOURCE" "$DB_DEST"
|
||||||
|
|
||||||
echo "Full deployment prepared:"
|
echo "Full deployment prepared:"
|
||||||
echo " Data loader: $LOADER_VERSION -> $NEW_LOADER_VERSION"
|
echo " Data loader: $LOADER_VERSION -> $NEW_LOADER_VERSION"
|
||||||
echo " Main container: $MAIN_VERSION -> $NEW_MAIN_VERSION"
|
echo " Main container: $MAIN_VERSION -> $NEW_MAIN_VERSION"
|
||||||
|
echo " ConfigMap VERSION: $NEW_MAIN_VERSION"
|
||||||
echo " Database copied to $DB_DEST"
|
echo " Database copied to $DB_DEST"
|
||||||
|
|||||||
@@ -23,5 +23,10 @@ NEW_VERSION=$(echo "$CURRENT_VERSION + 0.001" | bc | sed 's/^\./0./')
|
|||||||
# Update the deployment file (only the main container, not the data-loader)
|
# Update the deployment file (only the main container, not the data-loader)
|
||||||
sed -i "s|image: git.baumann.gr/adebaumann/vui:$CURRENT_VERSION|image: git.baumann.gr/adebaumann/vui:$NEW_VERSION|" "$DEPLOYMENT_FILE"
|
sed -i "s|image: git.baumann.gr/adebaumann/vui:$CURRENT_VERSION|image: git.baumann.gr/adebaumann/vui:$NEW_VERSION|" "$DEPLOYMENT_FILE"
|
||||||
|
|
||||||
|
# Update the configmap version to match the main container
|
||||||
|
CONFIGMAP_FILE="argocd/configmap.yaml"
|
||||||
|
sed -i "s|VERSION: \"$CURRENT_VERSION\"|VERSION: \"$NEW_VERSION\"|" "$CONFIGMAP_FILE"
|
||||||
|
|
||||||
echo "Partial deployment prepared:"
|
echo "Partial deployment prepared:"
|
||||||
echo " Main container: $CURRENT_VERSION -> $NEW_VERSION"
|
echo " Main container: $CURRENT_VERSION -> $NEW_VERSION"
|
||||||
|
echo " ConfigMap VERSION: $NEW_VERSION"
|
||||||
|
|||||||
17
stichworte/migrations/0004_alter_stichwort_options.py
Normal file
17
stichworte/migrations/0004_alter_stichwort_options.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 6.0.1 on 2026-01-20 08:57
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('stichworte', '0003_alter_stichworterklaerung_options'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='stichwort',
|
||||||
|
options={'verbose_name': 'Stichwort', 'verbose_name_plural': 'Stichworte'},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -9,6 +9,7 @@ class Stichwort(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name_plural="Stichworte"
|
verbose_name_plural="Stichworte"
|
||||||
|
verbose_name = "Stichwort"
|
||||||
|
|
||||||
class Stichworterklaerung (Textabschnitt):
|
class Stichworterklaerung (Textabschnitt):
|
||||||
erklaerung = models.ForeignKey(Stichwort,on_delete=models.CASCADE)
|
erklaerung = models.ForeignKey(Stichwort,on_delete=models.CASCADE)
|
||||||
|
|||||||
Reference in New Issue
Block a user