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
|
||||
media/diagram_cache/
|
||||
.env
|
||||
data/db.sqlite3
|
||||
data/
|
||||
dataremote/
|
||||
|
||||
@@ -8,7 +8,7 @@ COPY requirements.txt /app/
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
FROM python:3.15-rc-slim-trixie
|
||||
RUN useradd -m -r appuser && \
|
||||
RUN useradd -m -r -u 99 appuser && \
|
||||
mkdir /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!
|
||||
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!
|
||||
SECRET_KEY = os.environ.get('VORGABENUI_SECRET')
|
||||
if not SECRET_KEY:
|
||||
@@ -94,6 +97,7 @@ TEMPLATES = [
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'VorgabenUI.context_processors.version',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -138,7 +142,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||
|
||||
LANGUAGE_CODE = 'de-ch'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
TIME_ZONE = 'Europe/Zurich'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
|
||||
@@ -41,6 +41,12 @@ urlpatterns = [
|
||||
]
|
||||
|
||||
# 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:
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
||||
@@ -13,9 +13,12 @@ data:
|
||||
LANGUAGE_CODE: "de-ch"
|
||||
TIME_ZONE: "UTC"
|
||||
|
||||
# Static and Media Configuration
|
||||
# Static and Media Configuration
|
||||
STATIC_URL: "/static/"
|
||||
MEDIA_URL: "/media/"
|
||||
|
||||
# Application Version
|
||||
VERSION: "0.987"
|
||||
|
||||
# Database Configuration (for future use)
|
||||
# DATABASE_ENGINE: "django.db.backends.sqlite3"
|
||||
|
||||
@@ -14,19 +14,23 @@ spec:
|
||||
app: django
|
||||
spec:
|
||||
securityContext:
|
||||
fsGroup: 999
|
||||
fsGroup: 99
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
initContainers:
|
||||
- name: loader
|
||||
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:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
containers:
|
||||
- name: web
|
||||
image: git.baumann.gr/adebaumann/vui:0.980
|
||||
image: git.baumann.gr/adebaumann/vui:0.987
|
||||
imagePullPolicy: Always
|
||||
securityContext:
|
||||
runAsUser: 99
|
||||
env:
|
||||
# Secret configuration
|
||||
- name: VORGABENUI_SECRET
|
||||
@@ -50,6 +54,11 @@ spec:
|
||||
configMapKeyRef:
|
||||
name: django-config
|
||||
key: DJANGO_SETTINGS_MODULE
|
||||
- name: VERSION
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: django-config
|
||||
key: VERSION
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
volumeMounts:
|
||||
|
||||
@@ -4,6 +4,9 @@ metadata:
|
||||
name: django-data-pv
|
||||
namespace: vorgabenui
|
||||
spec:
|
||||
claimRef:
|
||||
name: django-data-pvc
|
||||
namespace: vorgabenui
|
||||
capacity:
|
||||
storage: 2Gi
|
||||
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)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
return str(self.name)
|
||||
|
||||
class Meta:
|
||||
verbose_name="Dokumententyp"
|
||||
@@ -28,6 +28,7 @@ class Person(models.Model):
|
||||
class Meta:
|
||||
verbose_name_plural="Personen"
|
||||
ordering = ['name']
|
||||
verbose_name="Person"
|
||||
|
||||
class Thema(models.Model):
|
||||
name = models.CharField(max_length=100, primary_key=True)
|
||||
@@ -37,7 +38,7 @@ class Thema(models.Model):
|
||||
return self.name
|
||||
class Meta:
|
||||
verbose_name_plural="Themen"
|
||||
|
||||
verbose_name="Thema"
|
||||
|
||||
class Dokument(models.Model):
|
||||
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)
|
||||
signatur_cso = models.CharField(max_length=255, blank=True)
|
||||
anhaenge = models.TextField(blank=True)
|
||||
aktiv = models.BooleanField(blank=True)
|
||||
aktiv = models.BooleanField(blank=True,default=False)
|
||||
|
||||
def __str__(self):
|
||||
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>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
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:
|
||||
verbose_name_plural="Referenzen"
|
||||
verbose_name="Referenz"
|
||||
|
||||
class Referenzerklaerung (Textabschnitt):
|
||||
erklaerung = models.ForeignKey(Referenz,on_delete=models.CASCADE)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from django.shortcuts import render
|
||||
from .models import Referenz
|
||||
from abschnitte.utils import render_textabschnitte
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
# Create your views here.
|
||||
def tree(request):
|
||||
@@ -9,7 +10,7 @@ def tree(request):
|
||||
|
||||
|
||||
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.children = list(referenz_item.get_descendants(include_self=True))
|
||||
for child in referenz_item.children:
|
||||
|
||||
@@ -1,37 +1,44 @@
|
||||
appdirs==1.4.4
|
||||
asgiref==3.8.1
|
||||
blessed==1.21.0
|
||||
certifi==2025.8.3
|
||||
charset-normalizer==3.4.3
|
||||
asgiref==3.11.0
|
||||
bleach==6.3.0
|
||||
blessed==1.27.0
|
||||
certifi==2026.1.4
|
||||
charset-normalizer==3.4.4
|
||||
coverage==7.13.1
|
||||
curtsies==0.4.3
|
||||
cwcwidth==0.1.10
|
||||
Django==5.2.9
|
||||
django-admin-sortable2==2.2.8
|
||||
cwcwidth==0.1.12
|
||||
Django==6.0.1
|
||||
django-admin-sortable2==2.3
|
||||
django-js-asset==3.1.2
|
||||
django-mptt==0.17.0
|
||||
django-mptt-admin==2.8.0
|
||||
django-nested-admin==4.1.1
|
||||
django-mptt==0.18.0
|
||||
django-mptt-admin==2.9.0
|
||||
django-nested-admin==4.1.6
|
||||
django-nested-inline==0.4.6
|
||||
django-revproxy==0.13.0
|
||||
greenlet==3.2.4
|
||||
greenlet==3.3.0
|
||||
gunicorn==23.0.0
|
||||
idna==3.10
|
||||
idna==3.11
|
||||
jedi==0.19.2
|
||||
Markdown==3.8.2
|
||||
jproperties==2.1.2
|
||||
Markdown==3.10
|
||||
packaging==25.0
|
||||
parsedatetime==2.6
|
||||
parso==0.8.4
|
||||
parso==0.8.5
|
||||
pep8==1.7.1
|
||||
prompt_toolkit==3.0.51
|
||||
prompt_toolkit==3.0.52
|
||||
pyfakefs==5.9.3
|
||||
Pygments==2.19.2
|
||||
pysonar==1.2.1.3951
|
||||
python-dateutil==2.9.0.post0
|
||||
python-monkey-business==1.1.0
|
||||
pyxdg==0.28
|
||||
PyYAML==6.0.3
|
||||
requests==2.32.5
|
||||
responses==0.25.8
|
||||
six==1.17.0
|
||||
sqlparse==0.5.3
|
||||
sqlparse==0.5.5
|
||||
tomli==2.2.1
|
||||
urllib3==2.6.3
|
||||
wcwidth==0.2.13
|
||||
bleach==6.1.0
|
||||
coverage==7.6.1
|
||||
whitenoise==6.8.2
|
||||
wcwidth==0.2.14
|
||||
webencodings==0.5.1
|
||||
whitenoise==6.11.0
|
||||
|
||||
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,9 +9,10 @@ class Rolle(models.Model):
|
||||
return self.name
|
||||
class Meta:
|
||||
verbose_name_plural="Rollen"
|
||||
verbose_name="Rolle"
|
||||
|
||||
class RollenBeschreibung(Textabschnitt):
|
||||
abschnitt=models.ForeignKey(Rolle,on_delete=models.CASCADE)
|
||||
class Meta:
|
||||
verbose_name_plural="Rollenbeschreibung"
|
||||
verbose_name="Rollenbeschreibungs-Abschnitt"
|
||||
verbose_name="Rollenbeschreibungs-Abschnitt"
|
||||
|
||||
@@ -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"
|
||||
SECRET_NAME="django-secret"
|
||||
SECRET_FILE="argocd/secret.yaml"
|
||||
SECRET_FILE="templates/secret.yaml"
|
||||
|
||||
# Check if secret file exists
|
||||
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:$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
|
||||
cp "$DB_SOURCE" "$DB_DEST"
|
||||
|
||||
echo "Full deployment prepared:"
|
||||
echo " Data loader: $LOADER_VERSION -> $NEW_LOADER_VERSION"
|
||||
echo " Main container: $MAIN_VERSION -> $NEW_MAIN_VERSION"
|
||||
echo " ConfigMap VERSION: $NEW_MAIN_VERSION"
|
||||
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)
|
||||
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 " 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:
|
||||
verbose_name_plural="Stichworte"
|
||||
verbose_name = "Stichwort"
|
||||
|
||||
class Stichworterklaerung (Textabschnitt):
|
||||
erklaerung = models.ForeignKey(Stichwort,on_delete=models.CASCADE)
|
||||
|
||||
Reference in New Issue
Block a user