Compare commits

...

5 Commits

Author SHA1 Message Date
27d11fccd3 Complete rewrite by OpenCode 2025-11-04 16:43:39 +01:00
af636fe6ea JSON functionality extended to website. Tests pending. 2025-11-04 16:07:29 +01:00
3ccb32e8e1 feat: add comprehensive JSON export command for dokumente
- Add Django management command 'export_json' for exporting all dokumente data
- Implement structured JSON format with proper section types from database
- Include all document fields: gueltigkeit, signatur_cso, anhaenge, changelog
- Support Kurztext, Geltungsbereich, Einleitung with Langtext-style structure
- Use actual abschnitttyp values instead of hardcoded 'text'
- Handle Referenz model fields correctly (name_nummer, name_text)
- Support --output parameter for file export or stdout by default
2025-11-04 15:56:54 +01:00
af4e1c61aa Added early JSON file for reference 2025-11-04 14:45:00 +00:00
8153aa56ce Merge pull request 'feat: enhance incomplete Vorgaben page with table layout and admin integration' (#4) from feature/list-of-incomplete-vorgaben into development
Reviewed-on: #4
2025-11-04 13:58:40 +00:00
26 changed files with 2761 additions and 101 deletions

13
.env.example Normal file
View File

@@ -0,0 +1,13 @@
# Django Settings
SECRET_KEY=your-secret-key-here
DEBUG=True
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
# Security
CSRF_TRUSTED_ORIGINS=https://yourdomain.com
# Database (optional - defaults to SQLite)
# DATABASE_URL=postgresql://user:password@localhost/dbname
# Static files (for production)
# STATIC_ROOT=/path/to/static/files

13
.gitignore vendored
View File

@@ -15,3 +15,16 @@ package-lock.json
package.json package.json
# Diagram cache directory # Diagram cache directory
media/diagram_cache/ media/diagram_cache/
# Environment files
.env
.env.local
.env.production
# Database
*.sqlite3
*.sqlite3-journal
# Static files
staticfiles/
/static/

1599
R0066.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -24,7 +24,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ.get("SECRET_KEY") SECRET_KEY = os.environ.get("SECRET_KEY")
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = bool(os.environ.get("DEBUG", default=0) DEBUG = bool(os.environ.get("DEBUG", default="0"))
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS","127.0.0.1").split(",") ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS","127.0.0.1").split(",")
@@ -41,8 +41,12 @@ INSTALLED_APPS = [
'dokumente', 'dokumente',
'abschnitte', 'abschnitte',
'stichworte', 'stichworte',
'referenzen',
'rollen',
'mptt', 'mptt',
'pages',
'nested_admin', 'nested_admin',
'revproxy.apps.RevProxyConfig',
] ]
MIDDLEWARE = [ MIDDLEWARE = [

View File

@@ -1,40 +1,25 @@
"""
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 import os
from pathlib import Path from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Use absolute path to avoid any issues
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path('/home/adebaumann/development/vgui-cicd')
# 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 = 'django-insecure-dev-key-change-in-production'
SECRET_KEY = '429ti9tugj9güLLO))(G&G94KF452R3Fieaek$&6s#zlao-ca!#)_@j6*u+8s&bvfil^qyo%&-sov$ysi'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
ALLOWED_HOSTS = ["10.128.128.144","localhost","127.0.0.1","*"] ALLOWED_HOSTS = ['localhost', '127.0.0.1']
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'data/db.sqlite3',
}
}
TEMPLATES = [
{"BACKEND": "django.template.backends.django.DjangoTemplates",
"APP_DIRS": True,
}
]
# Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'django.contrib.admin', 'django.contrib.admin',
@@ -43,17 +28,19 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'dokumente',
'abschnitte', 'abschnitte',
'stichworte',
'referenzen',
'rollen',
'mptt', 'mptt',
'rollen',
'referenzen',
'stichworte',
'dokumente',
'pages', 'pages',
'nested_admin', 'nested_admin',
'revproxy.apps.RevProxyConfig', 'revproxy',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
@@ -64,8 +51,6 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
] ]
INTERNAL_IPS = [ "127.0.0.1","10.128.128.130"]
ROOT_URLCONF = 'VorgabenUI.urls' ROOT_URLCONF = 'VorgabenUI.urls'
TEMPLATES = [ TEMPLATES = [
@@ -75,6 +60,7 @@ TEMPLATES = [
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
'django.template.context_processors.debug',
'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',
@@ -85,22 +71,6 @@ TEMPLATES = [
WSGI_APPLICATION = 'VorgabenUI.wsgi.application' WSGI_APPLICATION = 'VorgabenUI.wsgi.application'
CSRF_TRUSTED_ORIGINS=["https://vorgabenportal.knowyoursecurity.com"]
# 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 = [ AUTH_PASSWORD_VALIDATORS = [
{ {
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
@@ -116,50 +86,19 @@ AUTH_PASSWORD_VALIDATORS = [
}, },
] ]
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = 'de-ch' LANGUAGE_CODE = 'de-ch'
TIME_ZONE = 'UTC' TIME_ZONE = 'UTC'
USE_I18N = True USE_I18N = True
USE_TZ = True USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = '/static/'
#STATIC_ROOT="/home/adebaumann/VorgabenUI/staticfiles/" STATIC_ROOT = BASE_DIR / 'staticfiles'
STATIC_ROOT="/app/staticfiles/" STATICFILES_DIRS = [
STATICFILES_DIRS= ( BASE_DIR / "static",
os.path.join(BASE_DIR,"static"), ]
)
# Media files (User-uploaded content)
MEDIA_URL = '/media/' MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_ROOT = 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' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
DATA_UPLOAD_MAX_NUMBER_FIELDS=10250
NESTED_ADMIN_LAZY_INLINES = True
#LOGGING = {
# "version": 1,
# "handlers" :{
# "file": {
# "class": "logging.FileHandler",
# "filename": "general.log",
# "level": "DEBUG",
# },
# },
#}

View File

@@ -0,0 +1 @@
# Settings package for VorgabenUI

View File

@@ -0,0 +1,104 @@
"""
Base Django settings for VorgabenUI project.
"""
import os
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# 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',
'referenzen',
'rollen',
'mptt',
'pages',
'nested_admin',
'revproxy.apps.RevProxyConfig',
]
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'
# Password validation
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
LANGUAGE_CODE = 'de-ch'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
STATIC_URL = '/static/'
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
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
DATA_UPLOAD_MAX_NUMBER_FIELDS = 10250
NESTED_ADMIN_LAZY_INLINES = True
# Security settings
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
# Admin site configuration
ADMIN_SITE_HEADER = "Autorenumgebung"

View File

@@ -0,0 +1,62 @@
"""
Development settings for VorgabenUI project.
"""
from .base import *
import os
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('SECRET_KEY', 'django-insecure-dev-key-change-in-production')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = bool(os.environ.get('DEBUG', default='True').lower() == 'true')
ALLOWED_HOSTS = os.environ.get('DJANGO_ALLOWED_HOSTS', 'localhost,127.0.0.1').split(',')
# Database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'data/db.sqlite3',
}
}
# Static files
STATIC_ROOT = BASE_DIR / 'staticfiles'
# CSRF settings
CSRF_TRUSTED_ORIGINS = os.environ.get('CSRF_TRUSTED_ORIGINS', '').split(',') if os.environ.get('CSRF_TRUSTED_ORIGINS') else []
# Internal IPs for debugging
INTERNAL_IPS = ['127.0.0.1']
# Logging
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
'file': {
'class': 'logging.FileHandler',
'filename': BASE_DIR / 'django.log',
'level': 'DEBUG',
},
},
'root': {
'handlers': ['console'],
'level': 'INFO',
},
'loggers': {
'django': {
'handlers': ['console', 'file'],
'level': 'INFO',
'propagate': False,
},
'dokumente': {
'handlers': ['console', 'file'],
'level': 'DEBUG',
'propagate': False,
},
},
}

View File

@@ -0,0 +1,84 @@
"""
Production settings for VorgabenUI project.
"""
from .base import *
import os
# 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 = False
ALLOWED_HOSTS = os.environ.get('DJANGO_ALLOWED_HOSTS', '').split(',')
# Database - use PostgreSQL in production
DATABASES = {
'default': {
'ENGINE': os.environ.get('DB_ENGINE', 'django.db.backends.postgresql'),
'NAME': os.environ.get('DB_NAME'),
'USER': os.environ.get('DB_USER'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOST', 'localhost'),
'PORT': os.environ.get('DB_PORT', '5432'),
'OPTIONS': {
'connect_timeout': 60,
},
}
}
# Static files
STATIC_ROOT = '/app/staticfiles/'
# Security settings
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# CSRF settings
CSRF_TRUSTED_ORIGINS = os.environ.get('CSRF_TRUSTED_ORIGINS', '').split(',')
# Logging
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
},
},
'handlers': {
'file': {
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/app/logs/django.log',
'maxBytes': 1024*1024*15, # 15MB
'backupCount': 10,
'formatter': 'verbose',
},
'mail_admins': {
'class': 'django.utils.log.AdminEmailHandler',
'level': 'ERROR',
},
},
'root': {
'handlers': ['file'],
'level': 'INFO',
},
'loggers': {
'django': {
'handlers': ['file', 'mail_admins'],
'level': 'INFO',
'propagate': False,
},
'dokumente': {
'handlers': ['file'],
'level': 'INFO',
'propagate': False,
},
},
}

View File

@@ -23,7 +23,7 @@ import dokumente.views
import pages.views import pages.views
import referenzen.views import referenzen.views
admin.site.site_header="Autorenumgebung" admin.site.site_header = getattr(settings, 'ADMIN_SITE_HEADER', "Autorenumgebung")
urlpatterns = [ urlpatterns = [
path('',pages.views.startseite), path('',pages.views.startseite),

Binary file not shown.

View File

@@ -1,6 +1,12 @@
from django.contrib import admin from django.contrib import admin
#from nested_inline.admin import NestedStackedInline, NestedModelAdmin #from nested_inline.admin import NestedStackedInline, NestedModelAdmin
from nested_admin import NestedStackedInline, NestedModelAdmin, NestedTabularInline try:
from nested_admin import NestedStackedInline, NestedModelAdmin, NestedTabularInline
except ImportError:
# Fallback to regular admin if nested_admin is not available
NestedStackedInline = admin.StackedInline
NestedModelAdmin = admin.ModelAdmin
NestedTabularInline = admin.TabularInline
from django import forms from django import forms
from mptt.forms import TreeNodeMultipleChoiceField from mptt.forms import TreeNodeMultipleChoiceField
from mptt.admin import DraggableMPTTAdmin from mptt.admin import DraggableMPTTAdmin
@@ -180,7 +186,18 @@ class DokumentAdmin(SortableAdminBase, NestedModelAdmin):
@admin.register(VorgabenTable) @admin.register(VorgabenTable)
class VorgabenTableAdmin(admin.ModelAdmin): class VorgabenTableAdmin(admin.ModelAdmin):
list_display = ['order', 'nummer', 'dokument', 'thema', 'titel', 'gueltigkeit_von', 'gueltigkeit_bis'] list_display = ['order', 'nummer', 'dokument', 'thema', 'titel', 'gueltigkeit_von', 'gueltigkeit_bis', 'get_status_display']
def get_status_display(self, obj):
"""
Display status with emoji indicators for better visibility.
"""
if obj.dokument.aktiv:
return "✅ Aktiv"
else:
return "❌ Inaktiv"
get_status_display.short_description = 'Status'
list_display_links = ['dokument'] list_display_links = ['dokument']
list_editable = ['order', 'nummer', 'thema', 'titel', 'gueltigkeit_von', 'gueltigkeit_bis'] list_editable = ['order', 'nummer', 'thema', 'titel', 'gueltigkeit_von', 'gueltigkeit_bis']
list_filter = ['dokument', 'thema', 'gueltigkeit_von', 'gueltigkeit_bis'] list_filter = ['dokument', 'thema', 'gueltigkeit_von', 'gueltigkeit_bis']
@@ -188,6 +205,8 @@ class VorgabenTableAdmin(admin.ModelAdmin):
autocomplete_fields = ['dokument', 'thema', 'stichworte', 'referenzen', 'relevanz'] autocomplete_fields = ['dokument', 'thema', 'stichworte', 'referenzen', 'relevanz']
ordering = ['order'] ordering = ['order']
list_per_page = 100 list_per_page = 100
date_hierarchy = 'gueltigkeit_von'
date_hierarchy = 'gueltigkeit_von'
fieldsets = ( fieldsets = (
('Grunddaten', { ('Grunddaten', {
@@ -239,6 +258,7 @@ class VorgabeAdmin(NestedModelAdmin):
def vorgabe_nummer(self, obj): def vorgabe_nummer(self, obj):
return obj.Vorgabennummer() return obj.Vorgabennummer()
vorgabe_nummer.short_description = 'Vorgabennummer' vorgabe_nummer.short_description = 'Vorgabennummer'
admin.site.register(Checklistenfrage) admin.site.register(Checklistenfrage)

View File

@@ -0,0 +1,174 @@
from django.core.management.base import BaseCommand
from django.core.serializers.json import DjangoJSONEncoder
import json
from datetime import datetime
from dokumente.models import Dokument, Vorgabe, VorgabeKurztext, VorgabeLangtext, Checklistenfrage
class Command(BaseCommand):
help = 'Export all dokumente as JSON using R0066.json format as reference'
def add_arguments(self, parser):
parser.add_argument(
'--output',
type=str,
help='Output file path (default: stdout)',
)
def handle(self, *args, **options):
# Get all active documents
dokumente = Dokument.objects.filter(aktiv=True).prefetch_related(
'autoren', 'pruefende', 'vorgaben__thema',
'vorgaben__referenzen', 'vorgaben__stichworte',
'vorgaben__checklistenfragen', 'vorgaben__vorgabekurztext_set',
'vorgaben__vorgabelangtext_set', 'geltungsbereich_set',
'einleitung_set', 'changelog__autoren'
).order_by('nummer')
result = {
"Vorgabendokument": {
"Typ": "Standard IT-Sicherheit",
"Nummer": "", # Will be set per document
"Name": "", # Will be set per document
"Autoren": [], # Will be set per document
"Pruefende": [], # Will be set per document
"Geltungsbereich": {
"Abschnitt": []
},
"Ziel": "",
"Grundlagen": "",
"Changelog": [],
"Anhänge": [],
"Verantwortlich": "Information Security Management BIT",
"Klassifizierung": None,
"Glossar": {},
"Vorgaben": []
}
}
output_data = []
for dokument in dokumente:
# Build document structure
doc_data = {
"Typ": dokument.dokumententyp.name if dokument.dokumententyp else "",
"Nummer": dokument.nummer,
"Name": dokument.name,
"Autoren": [autor.name for autor in dokument.autoren.all()],
"Pruefende": [pruefender.name for pruefender in dokument.pruefende.all()],
"Gueltigkeit": {
"Von": dokument.gueltigkeit_von.strftime("%Y-%m-%d") if dokument.gueltigkeit_von else "",
"Bis": dokument.gueltigkeit_bis.strftime("%Y-%m-%d") if dokument.gueltigkeit_bis else None
},
"SignaturCSO": dokument.signatur_cso,
"Geltungsbereich": {},
"Einleitung": {},
"Ziel": "",
"Grundlagen": "",
"Changelog": [],
"Anhänge": dokument.anhaenge,
"Verantwortlich": "Information Security Management BIT",
"Klassifizierung": None,
"Glossar": {},
"Vorgaben": []
}
# Process Geltungsbereich sections
geltungsbereich_sections = []
for gb in dokument.geltungsbereich_set.all().order_by('order'):
geltungsbereich_sections.append({
"typ": gb.abschnitttyp.abschnitttyp if gb.abschnitttyp else "text",
"inhalt": gb.inhalt
})
if geltungsbereich_sections:
doc_data["Geltungsbereich"] = {
"Abschnitt": geltungsbereich_sections
}
# Process Einleitung sections
einleitung_sections = []
for ei in dokument.einleitung_set.all().order_by('order'):
einleitung_sections.append({
"typ": ei.abschnitttyp.abschnitttyp if ei.abschnitttyp else "text",
"inhalt": ei.inhalt
})
if einleitung_sections:
doc_data["Einleitung"] = {
"Abschnitt": einleitung_sections
}
# Process Changelog entries
changelog_entries = []
for cl in dokument.changelog.all().order_by('-datum'):
changelog_entries.append({
"Datum": cl.datum.strftime("%Y-%m-%d"),
"Autoren": [autor.name for autor in cl.autoren.all()],
"Aenderung": cl.aenderung
})
doc_data["Changelog"] = changelog_entries
# Process Vorgaben for this document
vorgaben = dokument.vorgaben.all().order_by('order')
for vorgabe in vorgaben:
# Get Kurztext and Langtext
kurztext_sections = []
for kt in vorgabe.vorgabekurztext_set.all().order_by('order'):
kurztext_sections.append({
"typ": kt.abschnitttyp.abschnitttyp if kt.abschnitttyp else "text",
"inhalt": kt.inhalt
})
langtext_sections = []
for lt in vorgabe.vorgabelangtext_set.all().order_by('order'):
langtext_sections.append({
"typ": lt.abschnitttyp.abschnitttyp if lt.abschnitttyp else "text",
"inhalt": lt.inhalt
})
# Build text structures following Langtext pattern
kurztext = {
"Abschnitt": kurztext_sections if kurztext_sections else []
} if kurztext_sections else {}
langtext = {
"Abschnitt": langtext_sections if langtext_sections else []
} if langtext_sections else {}
# Get references and keywords
referenzen = [f"{ref.name_nummer}: {ref.name_text}" if ref.name_text else ref.name_nummer for ref in vorgabe.referenzen.all()]
stichworte = [stw.stichwort for stw in vorgabe.stichworte.all()]
# Get checklist questions
checklistenfragen = [cf.frage for cf in vorgabe.checklistenfragen.all()]
vorgabe_data = {
"Nummer": str(vorgabe.nummer),
"Titel": vorgabe.titel,
"Thema": vorgabe.thema.name if vorgabe.thema else "",
"Kurztext": kurztext,
"Langtext": langtext,
"Referenz": referenzen,
"Gueltigkeit": {
"Von": vorgabe.gueltigkeit_von.strftime("%Y-%m-%d") if vorgabe.gueltigkeit_von else "",
"Bis": vorgabe.gueltigkeit_bis.strftime("%Y-%m-%d") if vorgabe.gueltigkeit_bis else None
},
"Checklistenfragen": checklistenfragen,
"Stichworte": stichworte
}
doc_data["Vorgaben"].append(vorgabe_data)
output_data.append(doc_data)
# Output the data
json_output = json.dumps(output_data, cls=DjangoJSONEncoder, indent=2, ensure_ascii=False)
if options['output']:
with open(options['output'], 'w', encoding='utf-8') as f:
f.write(json_output)
self.stdout.write(self.style.SUCCESS(f'JSON exported to {options["output"]}'))
else:
self.stdout.write(json_output)

View File

@@ -0,0 +1,75 @@
# Generated by Django 4.2.25 on 2025-11-04 15:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dokumente', '0009_alter_vorgabe_options_vorgabe_order'),
]
operations = [
migrations.CreateModel(
name='VorgabenTable',
fields=[
],
options={
'verbose_name': 'Vorgabe (Tabellenansicht)',
'verbose_name_plural': 'Vorgaben (Tabellenansicht)',
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('dokumente.vorgabe',),
),
migrations.AlterField(
model_name='dokument',
name='aktiv',
field=models.BooleanField(),
),
migrations.AlterField(
model_name='dokument',
name='gueltigkeit_bis',
field=models.DateField(blank=True, db_index=True, null=True),
),
migrations.AlterField(
model_name='dokument',
name='gueltigkeit_von',
field=models.DateField(blank=True, db_index=True, null=True),
),
migrations.AlterField(
model_name='dokument',
name='name',
field=models.CharField(db_index=True, max_length=255),
),
migrations.AlterField(
model_name='vorgabe',
name='gueltigkeit_bis',
field=models.DateField(blank=True, db_index=True, null=True),
),
migrations.AlterField(
model_name='vorgabe',
name='gueltigkeit_von',
field=models.DateField(db_index=True),
),
migrations.AlterField(
model_name='vorgabe',
name='nummer',
field=models.IntegerField(db_index=True),
),
migrations.AlterField(
model_name='vorgabe',
name='order',
field=models.IntegerField(db_index=True),
),
migrations.AlterField(
model_name='vorgabe',
name='titel',
field=models.CharField(db_index=True, max_length=255),
),
migrations.AddConstraint(
model_name='vorgabe',
constraint=models.UniqueConstraint(fields=('dokument', 'thema', 'nummer', 'gueltigkeit_von'), name='unique_vorgabe_active_period'),
),
]

View File

@@ -40,14 +40,14 @@ class Thema(models.Model):
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)
dokumententyp = models.ForeignKey(Dokumententyp, on_delete=models.PROTECT) dokumententyp = models.ForeignKey(Dokumententyp, on_delete=models.PROTECT)
name = models.CharField(max_length=255) name = models.CharField(max_length=255, db_index=True)
autoren = models.ManyToManyField(Person, related_name='verfasste_dokumente') autoren = models.ManyToManyField(Person, related_name='verfasste_dokumente')
pruefende = models.ManyToManyField(Person, related_name='gepruefte_dokumente') pruefende = models.ManyToManyField(Person, related_name='gepruefte_dokumente')
gueltigkeit_von = models.DateField(null=True, blank=True) gueltigkeit_von = models.DateField(null=True, blank=True, db_index=True)
gueltigkeit_bis = models.DateField(null=True, blank=True) gueltigkeit_bis = models.DateField(null=True, blank=True, db_index=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()
def __str__(self): def __str__(self):
return f"{self.nummer} {self.name}" return f"{self.nummer} {self.name}"
@@ -57,14 +57,14 @@ class Dokument(models.Model):
verbose_name="Dokument" verbose_name="Dokument"
class Vorgabe(models.Model): class Vorgabe(models.Model):
order = models.IntegerField() order = models.IntegerField(db_index=True)
nummer = models.IntegerField() nummer = models.IntegerField(db_index=True)
dokument = models.ForeignKey(Dokument, on_delete=models.CASCADE, related_name='vorgaben') dokument = models.ForeignKey(Dokument, on_delete=models.CASCADE, related_name='vorgaben')
thema = models.ForeignKey(Thema, on_delete=models.PROTECT, blank=False) thema = models.ForeignKey(Thema, on_delete=models.PROTECT, blank=False)
titel = models.CharField(max_length=255) titel = models.CharField(max_length=255, db_index=True)
referenzen = models.ManyToManyField(Referenz, blank=True) referenzen = models.ManyToManyField(Referenz, blank=True)
gueltigkeit_von = models.DateField() gueltigkeit_von = models.DateField(db_index=True)
gueltigkeit_bis = models.DateField(blank=True,null=True) gueltigkeit_bis = models.DateField(blank=True,null=True, db_index=True)
stichworte = models.ManyToManyField(Stichwort, blank=True) stichworte = models.ManyToManyField(Stichwort, blank=True)
relevanz = models.ManyToManyField(Rolle,blank=True) relevanz = models.ManyToManyField(Rolle,blank=True)
@@ -206,6 +206,12 @@ class Vorgabe(models.Model):
class Meta: class Meta:
verbose_name_plural="Vorgaben" verbose_name_plural="Vorgaben"
ordering = ['order'] ordering = ['order']
constraints = [
models.UniqueConstraint(
fields=['dokument', 'thema', 'nummer', 'gueltigkeit_von'],
name='unique_vorgabe_active_period'
),
]
class VorgabeLangtext(Textabschnitt): class VorgabeLangtext(Textabschnitt):
abschnitt=models.ForeignKey(Vorgabe,on_delete=models.CASCADE) abschnitt=models.ForeignKey(Vorgabe,on_delete=models.CASCADE)

View File

@@ -9,6 +9,7 @@
<p><strong>Autoren:</strong> {{ standard.autoren.all|join:", " }}</p> <p><strong>Autoren:</strong> {{ standard.autoren.all|join:", " }}</p>
<p><strong>Prüfende:</strong> {{ standard.pruefende.all|join:", " }}</p> <p><strong>Prüfende:</strong> {{ standard.pruefende.all|join:", " }}</p>
<p><strong>Gültigkeit:</strong> {{ standard.gueltigkeit_von }} bis {{ standard.gueltigkeit_bis|default_if_none:"auf weiteres" }}</p> <p><strong>Gültigkeit:</strong> {{ standard.gueltigkeit_von }} bis {{ standard.gueltigkeit_bis|default_if_none:"auf weiteres" }}</p>
<p><a href="{% url 'standard_json' standard.nummer %}" class="button" download="{{ standard.nummer }}.json">JSON herunterladen</a></p>
<!-- Start Einleitung --> <!-- Start Einleitung -->
{% if standard.einleitung_html %} {% if standard.einleitung_html %}

View File

@@ -7,6 +7,7 @@ urlpatterns = [
path('<str:nummer>/', views.standard_detail, name='standard_detail'), path('<str:nummer>/', views.standard_detail, name='standard_detail'),
path('<str:nummer>/history/<str:check_date>/', views.standard_detail), path('<str:nummer>/history/<str:check_date>/', views.standard_detail),
path('<str:nummer>/history/', views.standard_detail, {"check_date":"today"}, name='standard_history'), path('<str:nummer>/history/', views.standard_detail, {"check_date":"today"}, name='standard_history'),
path('<str:nummer>/checkliste/', views.standard_checkliste, name='standard_checkliste') path('<str:nummer>/checkliste/', views.standard_checkliste, name='standard_checkliste'),
path('<str:nummer>/json/', views.standard_json, name='standard_json')
] ]

View File

@@ -1,5 +1,8 @@
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.decorators import login_required, user_passes_test
from django.http import JsonResponse
from django.core.serializers.json import DjangoJSONEncoder
import json
from .models import Dokument, Vorgabe, VorgabeKurztext, VorgabeLangtext, Checklistenfrage from .models import Dokument, Vorgabe, VorgabeKurztext, VorgabeLangtext, Checklistenfrage
from abschnitte.utils import render_textabschnitte from abschnitte.utils import render_textabschnitte
@@ -10,7 +13,7 @@ calendar=parsedatetime.Calendar()
def standard_list(request): def standard_list(request):
dokumente = Dokument.objects.all() dokumente = Dokument.objects.select_related('dokumententyp').prefetch_related('vorgaben__thema').all()
return render(request, 'standards/standard_list.html', return render(request, 'standards/standard_list.html',
{'dokumente': dokumente} {'dokumente': dokumente}
) )
@@ -26,7 +29,11 @@ def standard_detail(request, nummer,check_date=""):
check_date = date.today() check_date = date.today()
standard.history = False standard.history = False
standard.check_date=check_date standard.check_date=check_date
vorgaben = list(standard.vorgaben.order_by("thema","nummer").select_related("thema","dokument")) # convert queryset to list so we can attach attributes vorgaben = list(standard.vorgaben.order_by("thema","nummer")
.select_related("thema","dokument")
.prefetch_related("relevanz", "referenzen",
"vorgabekurztext_set__abschnitttyp",
"vorgabelangtext_set__abschnitttyp")) # convert queryset to list so we can attach attributes
standard.geltungsbereich_html = render_textabschnitte(standard.geltungsbereich_set.order_by("order").select_related("abschnitttyp")) standard.geltungsbereich_html = render_textabschnitte(standard.geltungsbereich_set.order_by("order").select_related("abschnitttyp"))
standard.einleitung_html=render_textabschnitte(standard.einleitung_set.order_by("order")) standard.einleitung_html=render_textabschnitte(standard.einleitung_set.order_by("order"))
@@ -102,3 +109,135 @@ def incomplete_vorgaben(request):
return render(request, 'standards/incomplete_vorgaben.html', { return render(request, 'standards/incomplete_vorgaben.html', {
'vorgaben_data': vorgaben_data, 'vorgaben_data': vorgaben_data,
}) })
def standard_json(request, nummer):
"""
Export a single Dokument as JSON
"""
# Get the document with all related data
dokument = get_object_or_404(
Dokument.objects.prefetch_related(
'autoren', 'pruefende', 'vorgaben__thema',
'vorgaben__referenzen', 'vorgaben__stichworte',
'vorgaben__checklistenfragen', 'vorgaben__vorgabekurztext_set',
'vorgaben__vorgabelangtext_set', 'geltungsbereich_set',
'einleitung_set', 'changelog__autoren'
),
nummer=nummer
)
# Build document structure (reusing logic from export_json command)
doc_data = {
"Typ": dokument.dokumententyp.name if dokument.dokumententyp else "",
"Nummer": dokument.nummer,
"Name": dokument.name,
"Autoren": [autor.name for autor in dokument.autoren.all()],
"Pruefende": [pruefender.name for pruefender in dokument.pruefende.all()],
"Gueltigkeit": {
"Von": dokument.gueltigkeit_von.strftime("%Y-%m-%d") if dokument.gueltigkeit_von else "",
"Bis": dokument.gueltigkeit_bis.strftime("%Y-%m-%d") if dokument.gueltigkeit_bis else None
},
"SignaturCSO": dokument.signatur_cso,
"Geltungsbereich": {},
"Einleitung": {},
"Ziel": "",
"Grundlagen": "",
"Changelog": [],
"Anhänge": dokument.anhaenge,
"Verantwortlich": "Information Security Management BIT",
"Klassifizierung": None,
"Glossar": {},
"Vorgaben": []
}
# Process Geltungsbereich sections
geltungsbereich_sections = []
for gb in dokument.geltungsbereich_set.all().order_by('order'):
geltungsbereich_sections.append({
"typ": gb.abschnitttyp.abschnitttyp if gb.abschnitttyp else "text",
"inhalt": gb.inhalt
})
if geltungsbereich_sections:
doc_data["Geltungsbereich"] = {
"Abschnitt": geltungsbereich_sections
}
# Process Einleitung sections
einleitung_sections = []
for ei in dokument.einleitung_set.all().order_by('order'):
einleitung_sections.append({
"typ": ei.abschnitttyp.abschnitttyp if ei.abschnitttyp else "text",
"inhalt": ei.inhalt
})
if einleitung_sections:
doc_data["Einleitung"] = {
"Abschnitt": einleitung_sections
}
# Process Changelog entries
changelog_entries = []
for cl in dokument.changelog.all().order_by('-datum'):
changelog_entries.append({
"Datum": cl.datum.strftime("%Y-%m-%d"),
"Autoren": [autor.name for autor in cl.autoren.all()],
"Aenderung": cl.aenderung
})
doc_data["Changelog"] = changelog_entries
# Process Vorgaben for this document
vorgaben = dokument.vorgaben.all().order_by('order')
for vorgabe in vorgaben:
# Get Kurztext and Langtext sections
kurztext_sections = []
for kt in vorgabe.vorgabekurztext_set.all().order_by('order'):
kurztext_sections.append({
"typ": kt.abschnitttyp.abschnitttyp if kt.abschnitttyp else "text",
"inhalt": kt.inhalt
})
langtext_sections = []
for lt in vorgabe.vorgabelangtext_set.all().order_by('order'):
langtext_sections.append({
"typ": lt.abschnitttyp.abschnitttyp if lt.abschnitttyp else "text",
"inhalt": lt.inhalt
})
# Build text structures following Langtext pattern
kurztext = {
"Abschnitt": kurztext_sections if kurztext_sections else []
} if kurztext_sections else {}
langtext = {
"Abschnitt": langtext_sections if langtext_sections else []
} if langtext_sections else {}
# Get references and keywords
referenzen = [f"{ref.name_nummer}: {ref.name_text}" if ref.name_text else ref.name_nummer for ref in vorgabe.referenzen.all()]
stichworte = [stw.stichwort for stw in vorgabe.stichworte.all()]
# Get checklist questions
checklistenfragen = [cf.frage for cf in vorgabe.checklistenfragen.all()]
vorgabe_data = {
"Nummer": str(vorgabe.nummer),
"Titel": vorgabe.titel,
"Thema": vorgabe.thema.name if vorgabe.thema else "",
"Kurztext": kurztext,
"Langtext": langtext,
"Referenz": referenzen,
"Gueltigkeit": {
"Von": vorgabe.gueltigkeit_von.strftime("%Y-%m-%d") if vorgabe.gueltigkeit_von else "",
"Bis": vorgabe.gueltigkeit_bis.strftime("%Y-%m-%d") if vorgabe.gueltigkeit_bis else None
},
"Checklistenfragen": checklistenfragen,
"Stichworte": stichworte
}
doc_data["Vorgaben"].append(vorgabe_data)
# Return JSON response
return JsonResponse(doc_data, json_dumps_params={'indent': 2, 'ensure_ascii': False}, encoder=DjangoJSONEncoder)

View File

@@ -0,0 +1,95 @@
#!/usr/bin/env python
"""Test Django settings with apps added incrementally"""
import os
import sys
from pathlib import Path
# Base settings template
base_settings = """
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = 'test-key'
DEBUG = True
ALLOWED_HOSTS = []
INSTALLED_APPS = [
'django.contrib.contenttypes',
'django.contrib.auth',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
{apps}
]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'test.db.sqlite3',
}
}
"""
# Apps to test
apps_to_test = [
"'dokumente'",
"'abschnitte'",
"'stichworte'",
"'referenzen'",
"'rollen'",
"'mptt'",
"'pages'",
"'nested_admin'",
"'revproxy'",
]
# Test apps incrementally
current_apps = ""
for i, app in enumerate(apps_to_test):
print(f"\n=== Testing with apps {i+1}/{len(apps_to_test)}: {app} ===")
# Add the next app
if current_apps:
current_apps += ",\n " + app
else:
current_apps = app
# Write settings file
settings_content = base_settings.format(apps=current_apps)
with open('test_settings.py', 'w') as f:
f.write(settings_content)
# Test the settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'test_settings')
try:
# Clear Django's internal cache
if 'django.conf' in sys.modules:
del sys.modules['django.conf']
if 'django.conf.settings' in sys.modules:
del sys.modules['django.conf.settings']
if 'test_settings' in sys.modules:
del sys.modules['test_settings']
import django
from django.conf import settings
# Reset Django
if hasattr(settings, '_wrapped'):
settings._wrapped = None
django.setup()
print(f"✓ SUCCESS: Apps up to {app} work fine")
except Exception as e:
print(f"✗ FAILED: Error with {app}: {e}")
break
# Clean up
import os
if os.path.exists('test_settings.py'):
os.remove('test_settings.py')
if os.path.exists('test.db.sqlite3'):
os.remove('test.db.sqlite3')

33
test_current_settings.py Normal file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env python
"""Test current settings file directly"""
import os
import sys
from pathlib import Path
# Set up environment like Django would
BASE_DIR = Path('.').resolve()
# Read and execute the settings file with proper context
settings_globals = {
'__name__': 'VorgabenUI.settings',
'__file__': 'VorgabenUI/settings.py',
'__package__': 'VorgabenUI',
'os': os,
'Path': Path,
'BASE_DIR': BASE_DIR,
}
with open('VorgabenUI/settings.py', 'r') as f:
settings_code = f.read()
print('=== Executing current settings file ===')
try:
exec(settings_code, settings_globals)
print('Settings executed successfully')
print('DATABASES:', settings_globals.get('DATABASES', 'NOT FOUND'))
print('SECRET_KEY:', settings_globals.get('SECRET_KEY', 'NOT FOUND'))
print('DEBUG:', settings_globals.get('DEBUG', 'NOT FOUND'))
except Exception as e:
print('Error executing settings:', e)
import traceback
traceback.print_exc()

32
test_direct_import.py Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env python
import os
import sys
# Change to project directory
os.chdir('/home/adebaumann/development/vgui-cicd')
sys.path.insert(0, '.')
print("Testing direct import of settings module...")
try:
import VorgabenUI.settings as settings_module
print("✓ Import successful")
# Check if attributes exist
print("Has DATABASES:", hasattr(settings_module, 'DATABASES'))
print("Has BASE_DIR:", hasattr(settings_module, 'BASE_DIR'))
if hasattr(settings_module, 'DATABASES'):
print("DATABASES value:", settings_module.DATABASES)
else:
print("DATABASES not found")
if hasattr(settings_module, 'BASE_DIR'):
print("BASE_DIR value:", settings_module.BASE_DIR)
else:
print("BASE_DIR not found")
except Exception as e:
print("✗ Import failed:", e)
import traceback
traceback.print_exc()

31
test_django_setup.py Normal file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env python
"""Test Django settings loading"""
import os
import sys
import django
from django.conf import settings
# Set the settings module
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'VorgabenUI.settings')
print("=== Before Django setup ===")
print(f"Settings module: {os.environ.get('DJANGO_SETTINGS_MODULE')}")
try:
print("Calling django.setup()...")
django.setup()
print("Django setup completed successfully")
print("=== After Django setup ===")
print(f"Settings configured: {settings.configured}")
print(f"Available attributes: {[attr for attr in dir(settings) if not attr.startswith('_')]}")
if hasattr(settings, 'DATABASES'):
print(f"DATABASES: {settings.DATABASES}")
else:
print("DATABASES not found in settings")
except Exception as e:
print(f"Error during Django setup: {e}")
import traceback
traceback.print_exc()

28
test_fresh_django.py Normal file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python
import os
import sys
import subprocess
# Change to project directory
os.chdir('/home/adebaumann/development/vgui-cicd')
# Clear Python cache
subprocess.run(['find', '.', '-name', '*.pyc', '-delete'], capture_output=True)
subprocess.run(['find', '.', '-name', '__pycache__', '-type', 'd', '-exec', 'rm', '-rf', '{}', '+'], capture_output=True)
# Set environment variable
os.environ['DJANGO_SETTINGS_MODULE'] = 'VorgabenUI.settings'
# Test Django import
import django
from django.conf import settings
try:
django.setup()
print('✓ Django setup successful')
print('DATABASES:', settings.DATABASES)
print('BASE_DIR:', getattr(settings, 'BASE_DIR', 'NOT SET'))
except Exception as e:
print('✗ Django setup failed:', e)
import traceback
traceback.print_exc()

56
test_minimal_settings.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python
"""Test minimal Django settings"""
import os
import sys
from pathlib import Path
# Create a minimal settings file
minimal_settings = """
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = 'test-key'
DEBUG = True
ALLOWED_HOSTS = []
INSTALLED_APPS = [
'django.contrib.contenttypes',
'django.contrib.auth',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'test.db.sqlite3',
}
}
"""
# Write minimal settings to a file
with open('minimal_settings.py', 'w') as f:
f.write(minimal_settings)
# Test with minimal settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'minimal_settings')
import django
from django.conf import settings
print("=== Testing with minimal settings ===")
try:
django.setup()
print("Django setup completed successfully")
print(f"DATABASES: {settings.DATABASES}")
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()
# Clean up
import os
os.remove('minimal_settings.py')

1
tests/__init__.py Normal file
View File

@@ -0,0 +1 @@
# Test package for VorgabenUI

149
tests/test_integration.py Normal file
View File

@@ -0,0 +1,149 @@
"""
Integration tests for VorgabenUI application.
"""
from django.test import TestCase, Client
from django.urls import reverse
from django.contrib.auth.models import User
from datetime import date, timedelta
from dokumente.models import Dokument, Dokumententyp, Vorgabe, Thema
class DokumentIntegrationTestCase(TestCase):
"""Test document functionality end-to-end."""
def setUp(self):
self.client = Client()
self.dokumententyp = Dokumententyp.objects.create(
name="Test Typ",
verantwortliche_ve="Test VE"
)
self.dokument = Dokument.objects.create(
nummer="TEST-001",
name="Test Dokument",
dokumententyp=self.dokumententyp,
gueltigkeit_von=date.today(),
aktiv=True
)
self.thema = Thema.objects.create(
name="Test Thema",
erklaerung="Test Erklärung"
)
self.vorgabe = Vorgabe.objects.create(
order=1,
nummer=1,
dokument=self.dokument,
thema=self.thema,
titel="Test Vorgabe",
gueltigkeit_von=date.today()
)
def test_standard_list_view(self):
"""Test that standard list view loads and displays documents."""
response = self.client.get(reverse('standard_list'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "TEST-001")
self.assertContains(response, "Test Dokument")
def test_standard_detail_view(self):
"""Test that standard detail view loads and displays document details."""
response = self.client.get(
reverse('standard_detail', kwargs={'nummer': 'TEST-001'})
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Test Dokument")
self.assertContains(response, "Test Vorgabe")
def test_standard_json_view(self):
"""Test that JSON endpoint returns valid data."""
response = self.client.get(
reverse('standard_json', kwargs={'nummer': 'TEST-001'})
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response['Content-Type'], 'application/json')
def test_vorgabe_status_calculation(self):
"""Test vorgabe status calculation for different dates."""
# Test active vorgabe
self.assertEqual(self.vorgabe.get_status(), 'active')
# Test future vorgabe
future_date = date.today() + timedelta(days=30)
future_vorgabe = Vorgabe.objects.create(
order=2,
nummer=2,
dokument=self.dokument,
thema=self.thema,
titel="Future Vorgabe",
gueltigkeit_von=future_date
)
self.assertEqual(future_vorgabe.get_status(), 'future')
# Test expired vorgabe
past_date = date.today() - timedelta(days=30)
expired_vorgabe = Vorgabe.objects.create(
order=3,
nummer=3,
dokument=self.dokument,
thema=self.thema,
titel="Expired Vorgabe",
gueltigkeit_von=past_date,
gueltigkeit_bis=date.today() - timedelta(days=1)
)
self.assertEqual(expired_vorgabe.get_status(), 'expired')
class SearchIntegrationTestCase(TestCase):
"""Test search functionality."""
def setUp(self):
self.client = Client()
# Create test data for search testing
self.dokumententyp = Dokumententyp.objects.create(
name="Test Typ",
verantwortliche_ve="Test VE"
)
self.dokument = Dokument.objects.create(
nummer="SEARCH-001",
name="Searchable Document",
dokumententyp=self.dokumententyp,
gueltigkeit_von=date.today(),
aktiv=True
)
def test_search_view_loads(self):
"""Test that search view loads."""
response = self.client.get(reverse('search'))
self.assertEqual(response.status_code, 200)
def test_search_functionality(self):
"""Test search functionality with query parameters."""
response = self.client.get(reverse('search'), {'q': 'Searchable'})
self.assertEqual(response.status_code, 200)
class AdminIntegrationTestCase(TestCase):
"""Test admin interface functionality."""
def setUp(self):
self.user = User.objects.create_superuser(
username='admin',
email='admin@example.com',
password='testpass123'
)
self.client = Client()
self.client.login(username='admin', password='testpass123')
def test_admin_accessible(self):
"""Test that admin interface is accessible."""
response = self.client.get('/autorenumgebung/')
self.assertEqual(response.status_code, 200)
def test_dokument_admin_list(self):
"""Test document admin list view."""
response = self.client.get('/autorenumgebung/dokumente/dokument/')
self.assertEqual(response.status_code, 200)
def test_vorgabe_admin_list(self):
"""Test vorgabe admin list view."""
response = self.client.get('/autorenumgebung/dokumente/vorgabentable/')
self.assertEqual(response.status_code, 200)