Compare commits

...

10 Commits

15 changed files with 55 additions and 211 deletions

View File

@@ -52,11 +52,9 @@ INSTALLED_APPS = [
'pages',
'nested_admin',
'revproxy.apps.RevProxyConfig',
'debug_toolbar',
]
MIDDLEWARE = [
'debug_toolbar.middleware.DebugToolbarMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',

View File

@@ -18,7 +18,6 @@ from django.contrib import admin
from django.urls import include, path, re_path
from django.conf import settings
from django.conf.urls.static import static
from debug_toolbar.toolbar import debug_toolbar_urls
from diagramm_proxy.views import DiagrammProxyView
import standards.views
import pages.views
@@ -35,5 +34,5 @@ urlpatterns = [
path('referenzen/', referenzen.views.tree, name="referenz_tree"),
path('referenzen/<str:refid>/', referenzen.views.detail, name="referenz_detail"),
re_path(r'^diagramm/(?P<path>.*)$', DiagrammProxyView.as_view()),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) +debug_toolbar_urls()
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

View File

@@ -18,7 +18,7 @@ spec:
fsGroupChangePolicy: "OnRootMismatch"
containers:
- name: web
image: git.baumann.gr/adebaumann/vui:0.922
image: git.baumann.gr/adebaumann/vui:0.924
imagePullPolicy: Always
ports:
- containerPort: 8000

View File

@@ -7,7 +7,7 @@ metadata:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: vorgabenui.adebaumann.com
- host: vorgabenportal.knowyoursecurity.com
http:
paths:
- path: /

Binary file not shown.

Binary file not shown.

View File

@@ -28,6 +28,6 @@
<div class="flex-fill">{% block content %}Main Content{% endblock %}</div>
<div class="col-md-2">{% block sidebar_right %}{% endblock %}</div>
</div>
<div>VorgabenUI v0.8</div>
<div>VorgabenUI v0.81</div>
</body>
</html>

View File

@@ -1,11 +1,11 @@
from django.shortcuts import render
from abschnitte.utils import render_textabschnitte
from standards.models import Standard, VorgabeLangtext, VorgabeKurztext, Geltungsbereich
from standards.models import Dokument, VorgabeLangtext, VorgabeKurztext, Geltungsbereich
from itertools import groupby
import datetime
def startseite(request):
standards=list(Standard.objects.all())
standards=list(Dokument.objects.all())
return render(request, 'startseite.html', {"standards":standards,})
def search(request):

View File

@@ -6,7 +6,6 @@ charset-normalizer==3.4.3
curtsies==0.4.3
cwcwidth==0.1.10
Django==5.2.5
django-debug-toolbar==6.0.0
django-js-asset==3.1.2
django-mptt==0.17.0
django-mptt-admin==2.8.0

View File

@@ -99,8 +99,8 @@ class PersonAdmin(admin.ModelAdmin):
@admin.register(Standard)
class StandardAdmin(NestedModelAdmin):
@admin.register(Dokument)
class DokumentAdmin(NestedModelAdmin):
actions_on_top=True
inlines = [EinleitungInline,GeltungsbereichInline,VorgabeInline]
#filter_horizontal=['autoren','pruefende']
@@ -118,7 +118,7 @@ class StandardAdmin(NestedModelAdmin):
#admin.site.register(Stichwort)
admin.site.register(Checklistenfrage)
#admin.site.register(Dokumententyp)
admin.site.register(Dokumententyp)
#admin.site.register(Person)
admin.site.register(Thema)
#admin.site.register(Referenz, DraggableM§PTTAdmin)

View File

@@ -1,11 +1,11 @@
# Standards/management/commands/import_standard.py
# Document/management/commands/import_standard.py
import re
from pathlib import Path
from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone
from standards.models import (
Standard,
Dokument,
Dokumententyp,
Thema,
Vorgabe,
@@ -21,15 +21,15 @@ from stichworte.models import Stichwort
class Command(BaseCommand):
help = (
"Import a security standard from a structured text file.\n"
"Import a policy document from a structured text file.\n"
"Supports Einleitung, Geltungsbereich, Vorgaben (Kurztext/Langtext with AbschnittTyp), "
"Stichworte (comma-separated), Checklistenfragen, dry-run, verbose, and purge."
)
def add_arguments(self, parser):
parser.add_argument("file_path", type=str, help="Path to the plaintext file")
parser.add_argument("--nummer", required=True, help="Standard number (e.g., STD-001)")
parser.add_argument("--name", required=True, help='Standard name (e.g., "IT-Sicherheit Container")')
parser.add_argument("--nummer", required=True, help="Document number (e.g., STD-001)")
parser.add_argument("--name", required=True, help='Document name (e.g., "IT-Sicherheit Container")')
parser.add_argument("--dokumententyp", required=True, help='Dokumententyp name (e.g., "IT-Sicherheit")')
parser.add_argument("--gueltigkeit_von", default=None, help="Start date (YYYY-MM-DD)")
parser.add_argument("--gueltigkeit_bis", default=None, help="End date (YYYY-MM-DD)")
@@ -63,8 +63,8 @@ class Command(BaseCommand):
if dry_run:
self.stdout.write(self.style.WARNING("Dry run: no database changes will be made."))
# get or create Standard (we want a real instance even in purge to count existing rows)
standard, created = Standard.objects.get_or_create(
# get or create Document (we want a real instance even in purge to count existing rows)
standard, created = Dokument.objects.get_or_create(
nummer=nummer,
defaults={
"dokumententyp": dokumententyp,
@@ -74,9 +74,9 @@ class Command(BaseCommand):
},
)
if created:
self.stdout.write(self.style.SUCCESS(f"Created Standard {nummer} {name}"))
self.stdout.write(self.style.SUCCESS(f"Created Document {nummer} {name}"))
else:
self.stdout.write(self.style.WARNING(f"Standard {nummer} already exists; content may be updated."))
self.stdout.write(self.style.WARNING(f"Document {nummer} already exists; content may be updated."))
# purge (Einleitung + Geltungsbereich + Vorgaben cascade)
if purge:
@@ -347,6 +347,6 @@ class Command(BaseCommand):
)
self.stdout.write(self.style.SUCCESS(
"Dry run complete" if dry_run else f"Imported standard {nummer} {name} with {len(vorgaben_data)} Vorgaben"
"Dry run complete" if dry_run else f"Imported document {nummer} {name} with {len(vorgaben_data)} Vorgaben"
))

View File

@@ -1,177 +0,0 @@
# Standards/management/commands/import_standard.py
import re
from pathlib import Path
from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone
from standards.models import (
Standard,
Vorgabe,
VorgabeKurztext,
VorgabeLangtext,
Geltungsbereich,
Dokumententyp,
Thema,
)
from abschnitte.models import AbschnittTyp
class Command(BaseCommand):
help = "Import a security standard from a structured text file"
def add_arguments(self, parser):
parser.add_argument("file_path", type=str, help="Path to the plaintext file")
parser.add_argument("--nummer", required=True, help="Standard number (e.g., STD-001)")
parser.add_argument("--name", required=True, help="Standard name (e.g., IT-Sicherheit Container)")
parser.add_argument("--dokumententyp", required=True, help="Dokumententyp name")
parser.add_argument("--gueltigkeit_von", default=None, help="Start date (YYYY-MM-DD)")
parser.add_argument("--gueltigkeit_bis", default=None, help="End date (YYYY-MM-DD)")
parser.add_argument("--dry-run", action="store_true", help="Perform a dry run without saving to the database")
parser.add_argument("--verbose", action="store_true", help="Verbose output for dry run")
def handle(self, *args, **options):
dry_run = options["dry_run"]
verbose = options["verbose"]
file_path = Path(options["file_path"])
if not file_path.exists():
raise CommandError(f"File {file_path} does not exist")
nummer = options["nummer"]
name = options["name"]
dokumententyp_name = options["dokumententyp"]
try:
dokumententyp = Dokumententyp.objects.get(name=dokumententyp_name)
except Dokumententyp.DoesNotExist:
raise CommandError(f"Dokumententyp '{dokumententyp_name}' does not exist")
if dry_run:
self.stdout.write(self.style.WARNING("Dry run: no database changes will be made"))
# Create or get the Standard
if dry_run:
standard = {"nummer": nummer, "name": name, "dokumententyp": dokumententyp}
else:
standard, created = Standard.objects.get_or_create(
nummer=nummer,
defaults={
"dokumententyp": dokumententyp,
"name": name,
"gueltigkeit_von": options["gueltigkeit_von"],
"gueltigkeit_bis": options["gueltigkeit_bis"],
},
)
if not created:
self.stdout.write(self.style.WARNING(f"Standard {nummer} already exists, updating content"))
# Read and parse the file
content = file_path.read_text(encoding="utf-8")
blocks = re.split(r"^>>>", content, flags=re.MULTILINE)
blocks = [b.strip() for b in blocks if b.strip()]
geltungsbereich_sections = []
current_vorgabe = None
vorgaben_data = []
current_context = "geltungsbereich"
abschnittstyp_headers = ["text", "liste geordnet", "liste ungeordnet"]
for block in blocks:
lines = block.splitlines()
header = lines[0].strip()
text = "\n".join(lines[1:]).strip()
header_lower = header.lower()
# Determine AbschnittTyp if applicable
abschnitt_typ = None
if header_lower in abschnittstyp_headers:
try:
abschnitt_typ = AbschnittTyp.objects.get(abschnitttyp=header_lower)
except AbschnittTyp.DoesNotExist:
self.stdout.write(self.style.WARNING(f"AbschnittTyp '{header_lower}' not found, defaulting to 'text'"))
abschnitt_typ = AbschnittTyp.objects.get(abschnitttyp="text")
if header_lower == "geltungsbereich":
current_context = "geltungsbereich"
elif header_lower.startswith("vorgabe"):
if current_vorgabe:
vorgaben_data.append(current_vorgabe)
thema_name = header.split(" ", 1)[1].strip()
current_vorgabe = {"thema": thema_name, "titel": "", "nummer": None, "kurztext": [], "langtext": []}
current_context = "vorgabe_none"
elif header_lower.startswith("titel") and current_vorgabe:
current_vorgabe["titel"] = text
elif header_lower.startswith("nummer") and current_vorgabe:
nummer_match = re.search(r"\d+", header)
if nummer_match:
current_vorgabe["nummer"] = int(nummer_match.group())
current_context = "vorgabe_none"
elif header_lower == "kurztext":
current_context = "vorgabe_kurztext"
elif header_lower == "langtext":
current_context = "vorgabe_langtext"
elif header_lower in abschnittstyp_headers:
abschnitt = {"inhalt": text, "typ": abschnitt_typ}
if current_context == "geltungsbereich":
geltungsbereich_sections.append(abschnitt)
if dry_run and verbose:
self.stdout.write(self.style.SUCCESS(f"[DRY RUN] Geltungsbereich Abschnitt (Abschnittstyp: {abschnitt_typ}): {text[:50]}..."))
elif current_context == "vorgabe_kurztext" and current_vorgabe:
current_vorgabe["kurztext"].append(abschnitt)
if dry_run and verbose:
self.stdout.write(self.style.SUCCESS(f"[DRY RUN] Vorgabe {current_vorgabe['nummer']} Kurztext Abschnitt (Abschnittstyp: {abschnitt_typ}): {text[:50]}..."))
elif current_context == "vorgabe_langtext" and current_vorgabe:
current_vorgabe["langtext"].append(abschnitt)
if dry_run and verbose:
self.stdout.write(self.style.SUCCESS(f"[DRY RUN] Vorgabe {current_vorgabe['nummer']} Langtext Abschnitt (Abschnittstyp: {abschnitt_typ}): {text[:50]}..."))
if current_vorgabe:
vorgaben_data.append(current_vorgabe)
# Save Geltungsbereich
for sektion in geltungsbereich_sections:
if dry_run:
self.stdout.write(self.style.SUCCESS(f"[DRY RUN] Would create Geltungsbereich Abschnitt (Abschnittstyp: {sektion['typ']}): {sektion['inhalt'][:50]}..."))
else:
Geltungsbereich.objects.create(
geltungsbereich=standard,
abschnitttyp=sektion["typ"],
inhalt=sektion["inhalt"],
)
# Save Vorgaben
for v in vorgaben_data:
try:
thema = Thema.objects.get(name=v["thema"])
except Thema.DoesNotExist:
self.stdout.write(self.style.WARNING(f"Thema '{v['thema']}' not found, skipping Vorgabe {v['nummer']}"))
continue
if dry_run:
self.stdout.write(self.style.SUCCESS(f"[DRY RUN] Would create Vorgabe {v['nummer']}: '{v['titel']}' (Thema: {v['thema']})"))
for sektion in v["kurztext"]:
self.stdout.write(self.style.SUCCESS(f"[DRY RUN] Kurztext Abschnitt (Abschnittstyp: {sektion['typ']}): {sektion['inhalt'][:50]}..."))
for sektion in v["langtext"]:
self.stdout.write(self.style.SUCCESS(f"[DRY RUN] Langtext Abschnitt (Abschnittstyp: {sektion['typ']}): {sektion['inhalt'][:50]}..."))
else:
vorgabe = Vorgabe.objects.create(
nummer=v["nummer"],
dokument=standard,
thema=thema,
titel=v["titel"],
gueltigkeit_von=timezone.now().date(),
)
for sektion in v["kurztext"]:
VorgabeKurztext.objects.create(abschnitt=vorgabe, abschnitttyp=sektion["typ"], inhalt=sektion["inhalt"])
for sektion in v["langtext"]:
VorgabeLangtext.objects.create(abschnitt=vorgabe, abschnitttyp=sektion["typ"], inhalt=sektion["inhalt"])
self.stdout.write(self.style.SUCCESS(
f"{'Dry run complete' if dry_run else f'Imported standard {standard} with {len(vorgaben_data)} Vorgaben'}"
))

View File

@@ -0,0 +1,21 @@
# Generated by Django 5.2.5 on 2025-10-02 12:13
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('standards', '0005_vorgabe_relevanz'),
]
operations = [
migrations.RenameModel(
old_name='Standard',
new_name='Dokument',
),
migrations.AlterModelOptions(
name='dokument',
options={'verbose_name': 'Dokument', 'verbose_name_plural': 'Dokumente'},
),
]

View File

@@ -13,6 +13,10 @@ class Dokumententyp(models.Model):
def __str__(self):
return self.name
class Meta:
verbose_name="Dokumententyp"
verbose_name_plural="Dokumententypen"
class Person(models.Model):
name = models.CharField(max_length=100, primary_key=True)
@@ -33,7 +37,7 @@ class Thema(models.Model):
verbose_name_plural="Themen"
class Standard(models.Model):
class Dokument(models.Model):
nummer = models.CharField(max_length=50, primary_key=True)
dokumententyp = models.ForeignKey(Dokumententyp, on_delete=models.PROTECT)
name = models.CharField(max_length=255)
@@ -48,12 +52,12 @@ class Standard(models.Model):
return f"{self.nummer} {self.name}"
class Meta:
verbose_name_plural="Standards"
verbose_name="Standard"
verbose_name_plural="Dokumente"
verbose_name="Dokument"
class Vorgabe(models.Model):
nummer = models.IntegerField()
dokument = models.ForeignKey(Standard, 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)
titel = models.CharField(max_length=255)
referenzen = models.ManyToManyField(Referenz, blank=True)
@@ -97,13 +101,13 @@ class VorgabeKurztext(Textabschnitt):
verbose_name="Kurztext-Abschnitt"
class Geltungsbereich(Textabschnitt):
geltungsbereich=models.ForeignKey(Standard,on_delete=models.CASCADE)
geltungsbereich=models.ForeignKey(Dokument,on_delete=models.CASCADE)
class Meta:
verbose_name_plural="Geltungsbereich"
verbose_name="Geltungsbereichs-Abschnitt"
class Einleitung(Textabschnitt):
einleitung=models.ForeignKey(Standard,on_delete=models.CASCADE)
einleitung=models.ForeignKey(Dokument,on_delete=models.CASCADE)
class Meta:
verbose_name_plural="Einleitung"
verbose_name="Einleitungs-Abschnitt"
@@ -119,7 +123,7 @@ class Checklistenfrage(models.Model):
verbose_name_plural="Fragen für Checkliste"
class Changelog(models.Model):
dokument = models.ForeignKey(Standard, on_delete=models.CASCADE, related_name='changelog')
dokument = models.ForeignKey(Dokument, on_delete=models.CASCADE, related_name='changelog')
autoren = models.ManyToManyField(Person)
datum = models.DateField()
aenderung = models.TextField()

View File

@@ -1,5 +1,5 @@
from django.shortcuts import render, get_object_or_404
from .models import Standard
from .models import Dokument
from abschnitte.utils import render_textabschnitte
from datetime import date
@@ -9,14 +9,14 @@ calendar=parsedatetime.Calendar()
def standard_list(request):
standards = Standard.objects.all()
standards = Dokument.objects.all()
return render(request, 'standards/standard_list.html',
{'standards': standards}
)
def standard_detail(request, nummer,check_date=""):
standard = get_object_or_404(Standard, nummer=nummer)
standard = get_object_or_404(Dokument, nummer=nummer)
if check_date:
check_date = calendar.parseDT(check_date)[0].date()
@@ -48,7 +48,7 @@ def standard_detail(request, nummer,check_date=""):
def standard_checkliste(request, nummer):
standard = get_object_or_404(Standard, nummer=nummer)
standard = get_object_or_404(Dokument, nummer=nummer)
vorgaben = list(standard.vorgaben.all())
return render(request, 'standards/standard_checkliste.html', {
'standard': standard,