Compare commits

...

10 Commits

15 changed files with 55 additions and 211 deletions

View File

@@ -52,11 +52,9 @@ INSTALLED_APPS = [
'pages', 'pages',
'nested_admin', 'nested_admin',
'revproxy.apps.RevProxyConfig', 'revproxy.apps.RevProxyConfig',
'debug_toolbar',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
'debug_toolbar.middleware.DebugToolbarMiddleware',
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', '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.urls import include, path, re_path
from django.conf import settings from django.conf import settings
from django.conf.urls.static import static from django.conf.urls.static import static
from debug_toolbar.toolbar import debug_toolbar_urls
from diagramm_proxy.views import DiagrammProxyView from diagramm_proxy.views import DiagrammProxyView
import standards.views import standards.views
import pages.views import pages.views
@@ -35,5 +34,5 @@ urlpatterns = [
path('referenzen/', referenzen.views.tree, name="referenz_tree"), path('referenzen/', referenzen.views.tree, name="referenz_tree"),
path('referenzen/<str:refid>/', referenzen.views.detail, name="referenz_detail"), path('referenzen/<str:refid>/', referenzen.views.detail, name="referenz_detail"),
re_path(r'^diagramm/(?P<path>.*)$', DiagrammProxyView.as_view()), 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" fsGroupChangePolicy: "OnRootMismatch"
containers: containers:
- name: web - name: web
image: git.baumann.gr/adebaumann/vui:0.922 image: git.baumann.gr/adebaumann/vui:0.924
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:
- containerPort: 8000 - containerPort: 8000

View File

@@ -7,7 +7,7 @@ metadata:
nginx.ingress.kubernetes.io/rewrite-target: / nginx.ingress.kubernetes.io/rewrite-target: /
spec: spec:
rules: rules:
- host: vorgabenui.adebaumann.com - host: vorgabenportal.knowyoursecurity.com
http: http:
paths: paths:
- path: / - 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="flex-fill">{% block content %}Main Content{% endblock %}</div>
<div class="col-md-2">{% block sidebar_right %}{% endblock %}</div> <div class="col-md-2">{% block sidebar_right %}{% endblock %}</div>
</div> </div>
<div>VorgabenUI v0.8</div> <div>VorgabenUI v0.81</div>
</body> </body>
</html> </html>

View File

@@ -1,11 +1,11 @@
from django.shortcuts import render from django.shortcuts import render
from abschnitte.utils import render_textabschnitte 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 from itertools import groupby
import datetime import datetime
def startseite(request): def startseite(request):
standards=list(Standard.objects.all()) standards=list(Dokument.objects.all())
return render(request, 'startseite.html', {"standards":standards,}) return render(request, 'startseite.html', {"standards":standards,})
def search(request): def search(request):

View File

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

View File

@@ -99,8 +99,8 @@ class PersonAdmin(admin.ModelAdmin):
@admin.register(Standard) @admin.register(Dokument)
class StandardAdmin(NestedModelAdmin): class DokumentAdmin(NestedModelAdmin):
actions_on_top=True actions_on_top=True
inlines = [EinleitungInline,GeltungsbereichInline,VorgabeInline] inlines = [EinleitungInline,GeltungsbereichInline,VorgabeInline]
#filter_horizontal=['autoren','pruefende'] #filter_horizontal=['autoren','pruefende']
@@ -118,7 +118,7 @@ class StandardAdmin(NestedModelAdmin):
#admin.site.register(Stichwort) #admin.site.register(Stichwort)
admin.site.register(Checklistenfrage) admin.site.register(Checklistenfrage)
#admin.site.register(Dokumententyp) admin.site.register(Dokumententyp)
#admin.site.register(Person) #admin.site.register(Person)
admin.site.register(Thema) admin.site.register(Thema)
#admin.site.register(Referenz, DraggableM§PTTAdmin) #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 import re
from pathlib import Path from pathlib import Path
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone from django.utils import timezone
from standards.models import ( from standards.models import (
Standard, Dokument,
Dokumententyp, Dokumententyp,
Thema, Thema,
Vorgabe, Vorgabe,
@@ -21,15 +21,15 @@ from stichworte.models import Stichwort
class Command(BaseCommand): class Command(BaseCommand):
help = ( 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), " "Supports Einleitung, Geltungsbereich, Vorgaben (Kurztext/Langtext with AbschnittTyp), "
"Stichworte (comma-separated), Checklistenfragen, dry-run, verbose, and purge." "Stichworte (comma-separated), Checklistenfragen, dry-run, verbose, and purge."
) )
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument("file_path", type=str, help="Path to the plaintext file") 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("--nummer", required=True, help="Document number (e.g., STD-001)")
parser.add_argument("--name", required=True, help='Standard name (e.g., "IT-Sicherheit Container")') 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("--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_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("--gueltigkeit_bis", default=None, help="End date (YYYY-MM-DD)")
@@ -63,8 +63,8 @@ class Command(BaseCommand):
if dry_run: if dry_run:
self.stdout.write(self.style.WARNING("Dry run: no database changes will be made.")) 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) # get or create Document (we want a real instance even in purge to count existing rows)
standard, created = Standard.objects.get_or_create( standard, created = Dokument.objects.get_or_create(
nummer=nummer, nummer=nummer,
defaults={ defaults={
"dokumententyp": dokumententyp, "dokumententyp": dokumententyp,
@@ -74,9 +74,9 @@ class Command(BaseCommand):
}, },
) )
if created: 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: 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) # purge (Einleitung + Geltungsbereich + Vorgaben cascade)
if purge: if purge:
@@ -347,6 +347,6 @@ class Command(BaseCommand):
) )
self.stdout.write(self.style.SUCCESS( 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): def __str__(self):
return self.name return self.name
class Meta:
verbose_name="Dokumententyp"
verbose_name_plural="Dokumententypen"
class Person(models.Model): class Person(models.Model):
name = models.CharField(max_length=100, primary_key=True) name = models.CharField(max_length=100, primary_key=True)
@@ -33,7 +37,7 @@ class Thema(models.Model):
verbose_name_plural="Themen" verbose_name_plural="Themen"
class Standard(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)
@@ -48,12 +52,12 @@ class Standard(models.Model):
return f"{self.nummer} {self.name}" return f"{self.nummer} {self.name}"
class Meta: class Meta:
verbose_name_plural="Standards" verbose_name_plural="Dokumente"
verbose_name="Standard" verbose_name="Dokument"
class Vorgabe(models.Model): class Vorgabe(models.Model):
nummer = models.IntegerField() 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) thema = models.ForeignKey(Thema, on_delete=models.PROTECT)
titel = models.CharField(max_length=255) titel = models.CharField(max_length=255)
referenzen = models.ManyToManyField(Referenz, blank=True) referenzen = models.ManyToManyField(Referenz, blank=True)
@@ -97,13 +101,13 @@ class VorgabeKurztext(Textabschnitt):
verbose_name="Kurztext-Abschnitt" verbose_name="Kurztext-Abschnitt"
class Geltungsbereich(Textabschnitt): class Geltungsbereich(Textabschnitt):
geltungsbereich=models.ForeignKey(Standard,on_delete=models.CASCADE) geltungsbereich=models.ForeignKey(Dokument,on_delete=models.CASCADE)
class Meta: class Meta:
verbose_name_plural="Geltungsbereich" verbose_name_plural="Geltungsbereich"
verbose_name="Geltungsbereichs-Abschnitt" verbose_name="Geltungsbereichs-Abschnitt"
class Einleitung(Textabschnitt): class Einleitung(Textabschnitt):
einleitung=models.ForeignKey(Standard,on_delete=models.CASCADE) einleitung=models.ForeignKey(Dokument,on_delete=models.CASCADE)
class Meta: class Meta:
verbose_name_plural="Einleitung" verbose_name_plural="Einleitung"
verbose_name="Einleitungs-Abschnitt" verbose_name="Einleitungs-Abschnitt"
@@ -119,7 +123,7 @@ class Checklistenfrage(models.Model):
verbose_name_plural="Fragen für Checkliste" verbose_name_plural="Fragen für Checkliste"
class Changelog(models.Model): 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) autoren = models.ManyToManyField(Person)
datum = models.DateField() datum = models.DateField()
aenderung = models.TextField() aenderung = models.TextField()

View File

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