Compare commits

...

30 Commits

Author SHA1 Message Date
762f13fa6a Deploy 929 2025-10-20 07:38:18 +02:00
8b6d1653f0 Empty line in manage.py removed 2025-10-20 07:31:39 +02:00
de0a475a57 Static collection in Docker build 2025-10-16 16:15:57 +02:00
2aaab3b3d4 Added comments to standard_detail.html 2025-10-14 10:26:34 +02:00
db06ae0630 kroki containers pulled to local repo - typo corrected 2025-10-06 16:29:56 +02:00
6afc9f8f4e kroki containers pulled to local repo 2025-10-06 16:28:57 +02:00
5e0616dc6c debugging 2025-10-06 16:10:12 +02:00
a55736f736 Removed docker.io for kroki images 2025-10-06 16:08:18 +02:00
d97a66690a Removed docker.io for kroki images 2025-10-06 15:57:18 +02:00
784fbea088 Back to don't clobber database when copying. 2025-10-06 15:49:00 +02:00
6e8a978ae5 clobber database when copying. 2025-10-06 15:47:51 +02:00
2065d69a80 turns out busybox copy has different command line options. 2025-10-06 15:01:29 +02:00
dbd75f9e30 turns out busybox copy has different command line options. 2025-10-06 14:56:12 +02:00
077b376953 sleep added to init container; debugging... 2025-10-06 14:54:42 +02:00
7c1b89a13b turns out busybox copy has different command line options. 2025-10-06 14:51:30 +02:00
b0bfb4a38a sleep added to init container; debugging... 2025-10-06 14:48:54 +02:00
244e9e155f Data-Loader added as initcontainer 2025-10-06 14:34:54 +02:00
bba32d08e3 v026 2025-10-06 14:13:32 +02:00
4b257bae44 Added version number and trigger deployment 2025-10-06 13:41:51 +02:00
89f427462d Added Metas for all models 2025-10-06 13:31:15 +02:00
94f381c02f renamed and adjusted import script 2025-10-03 15:25:49 +02:00
a24c1059c8 minor changes for deployment 2025-10-03 10:25:04 +02:00
d7ddb0a88c Ingress changed to be publicly accessible 2025-10-03 08:10:43 +02:00
dd75bd20c4 New preload data 2025-10-03 00:40:37 +02:00
6c1b4938cf Merge branch 'development' 2025-10-02 14:26:57 +02:00
23f6c9bb31 Changed class name "Standards" to "Dokumente" 2025-10-02 14:18:11 +02:00
53c828c77f Deploy 0.923 2025-10-01 22:44:24 +02:00
412a5f3824 Merge pull request 'development' (#1) from development into main
Reviewed-on: #1
2025-10-01 20:36:58 +00:00
931131b8e6 Removed django debug toolbar 2025-10-01 22:34:49 +02:00
506b40db6c Removed debug toolbar 2025-10-01 22:33:53 +02:00
26 changed files with 136 additions and 262 deletions

2
.gitignore vendored
View File

@@ -7,5 +7,7 @@ bin/
pyvenv.cfg pyvenv.cfg
include/ include/
keys/ keys/
.venv/
.idea/
*.kate-swp *.kate-swp

View File

@@ -29,5 +29,6 @@ RUN rm -rf /app/Dockerfile* \
/app/data-loader \ /app/data-loader \
/app/keys \ /app/keys \
/app/requirements.txt /app/requirements.txt
RUN python3 manage.py collectstatic
CMD ["gunicorn","--bind","0.0.0.0:8000","--workers","3","VorgabenUI.wsgi:application"] CMD ["gunicorn","--bind","0.0.0.0:8000","--workers","3","VorgabenUI.wsgi:application"]

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)

12
argocd/001_pvc.yaml Normal file
View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: django-data-pvc
namespace: vorgabenui
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi

View File

@@ -16,9 +16,16 @@ spec:
securityContext: securityContext:
fsGroup: 999 fsGroup: 999
fsGroupChangePolicy: "OnRootMismatch" fsGroupChangePolicy: "OnRootMismatch"
initContainers:
- name: loader
image: git.baumann.gr/adebaumann/vgui-data-loader:0.5
command: [ "sh","-c","cp -n preload/preload.sqlite3 /data/db.sqlite3; chown -R 999:999 /data; ls -la /data; sleep 10; exit 0" ]
volumeMounts:
- name: data
mountPath: /data
containers: containers:
- name: web - name: web
image: git.baumann.gr/adebaumann/vui:0.922 image: git.baumann.gr/adebaumann/vui:0.929
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:
- containerPort: 8000 - containerPort: 8000

View File

@@ -15,7 +15,7 @@ spec:
spec: spec:
containers: containers:
- name: kroki - name: kroki
image: docker.io/yuzutech/kroki:latest image: git.baumann.gr/adebaumann/kroki:0.026
ports: ports:
- containerPort: 8000 - containerPort: 8000
readinessProbe: readinessProbe:
@@ -35,15 +35,15 @@ spec:
timeoutSeconds: 2 timeoutSeconds: 2
failureThreshold: 3 failureThreshold: 3
- name: mermaid - name: mermaid
image: docker.io/yuzutech/kroki-mermaid:latest image: git.baumann.gr/adebaumann/kroki-mermaid:0.026
ports: ports:
- containerPort: 8002 - containerPort: 8002
- name: bpmn - name: bpmn
image: docker.io/yuzutech/kroki-bpmn:latest image: git.baumann.gr/adebaumann/kroki-bpmn:0.026
ports: ports:
- containerPort: 8003 - containerPort: 8003
- name: excalidraw - name: excalidraw
image: docker.io/yuzutech/kroki-excalidraw:latest image: git.baumann.gr/adebaumann/kroki-excalidraw:0.026
ports: ports:
- containerPort: 8004 - containerPort: 8004
--- ---

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: /

View File

@@ -8,5 +8,4 @@ RUN chown appuser:appuser /preload/preload.sqlite3
RUN mkdir /data RUN mkdir /data
RUN chown appuser:appuser /data RUN chown appuser:appuser /data
USER root USER root
CMD ["sh"]

Binary file not shown.

Binary file not shown.

View File

@@ -7,8 +7,8 @@ spec:
restartPolicy: Never restartPolicy: Never
containers: containers:
- name: loader - name: loader
image: adebaumann/vgui-preloader:0.4 image: adebaumann/vgui-preloader:0.5
command: ["sh","-c","cp -v /preload/preload.sqlite3 /data/db.sqlite3; chown -R 999:999 /data; ls -la /data"] 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: volumeMounts:
- name: data - name: data
mountPath: /data mountPath: /data

View File

@@ -16,6 +16,13 @@ spec:
securityContext: securityContext:
fsGroup: 999 fsGroup: 999
fsGroupChangePolicy: "OnRootMismatch" 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: containers:
- name: web - name: web
image: docker.io/adebaumann/vui:0.917 image: docker.io/adebaumann/vui:0.917

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.927</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

@@ -0,0 +1,29 @@
# Generated by Django 5.2.5 on 2025-10-06 11:29
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('standards', '0006_rename_standard_dokument_alter_dokument_options'),
]
operations = [
migrations.AlterModelOptions(
name='changelog',
options={'verbose_name': 'Changelog-Eintrag', 'verbose_name_plural': 'Changelog'},
),
migrations.AlterModelOptions(
name='checklistenfrage',
options={'verbose_name': 'Frage für Checkliste', 'verbose_name_plural': 'Fragen für Checkliste'},
),
migrations.AlterModelOptions(
name='dokumententyp',
options={'verbose_name': 'Dokumententyp', 'verbose_name_plural': 'Dokumententypen'},
),
migrations.AlterModelOptions(
name='vorgabelangtext',
options={'verbose_name': 'Langtext-Abschnitt', 'verbose_name_plural': 'Langtext'},
),
]

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)
@@ -77,17 +81,17 @@ class Vorgabe(models.Model):
return "expired" if not verbose else "Ist seit dem "+self.gueltigkeit_bis.strftime('%d.%m.%Y')+" nicht mehr in Kraft." return "expired" if not verbose else "Ist seit dem "+self.gueltigkeit_bis.strftime('%d.%m.%Y')+" nicht mehr in Kraft."
def __str__(self):
return f"{self.Vorgabennummer()}: {self.titel}"
class Meta: class Meta:
verbose_name_plural="Vorgaben" verbose_name_plural="Vorgaben"
def __str__(self):
return f"{self.Vorgabennummer()}: {self.titel}"
class VorgabeLangtext(Textabschnitt): class VorgabeLangtext(Textabschnitt):
abschnitt=models.ForeignKey(Vorgabe,on_delete=models.CASCADE) abschnitt=models.ForeignKey(Vorgabe,on_delete=models.CASCADE)
class Meta: class Meta:
verbose_name_plural="Langtext-Abschnitte" verbose_name_plural="Langtext"
verbose_name="Langtext-Abschnitt" verbose_name="Langtext-Abschnitt"
class VorgabeKurztext(Textabschnitt): class VorgabeKurztext(Textabschnitt):
@@ -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"
@@ -117,12 +121,17 @@ class Checklistenfrage(models.Model):
class Meta: class Meta:
verbose_name_plural="Fragen für Checkliste" verbose_name_plural="Fragen für Checkliste"
verbose_name="Frage 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()
def __str__(self): def __str__(self):
return f"{self.datum} {self.dokument.nummer}" return f"{self.datum} {self.dokument.nummer}"
class Meta:
verbose_name_plural="Changelog"
verbose_name="Changelog-Eintrag"

View File

@@ -5,27 +5,32 @@
{% if standard.history == True %} {% if standard.history == True %}
<h2>Version vom {{ standard.check_date }}</h2> <h2>Version vom {{ standard.check_date }}</h2>
{% endif %} {% endif %}
<!-- Autoren, Prüfende etc. -->
<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 }}</p> <p><strong>Gültigkeit:</strong> {{ standard.gueltigkeit_von }} bis {{ standard.gueltigkeit_bis }}</p>
<!-- Start Einleitung -->
{% if standard.einleitung_html %} {% if standard.einleitung_html %}
<h2>Einleitung</h2> <h2>Einleitung</h2>
{% for typ, html in standard.einleitung_html %} {% for typ, html in standard.einleitung_html %}
<div>{{ html|safe }}</div> <div>{{ html|safe }}</div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<!-- End Einleitung -->
<!-- Start Geltungsbereich -->
{% if standard.geltungsbereich_html %} {% if standard.geltungsbereich_html %}
<h2>Geltungsbereich</h2> <h2>Geltungsbereich</h2>
{% for typ, html in standard.geltungsbereich_html %} {% for typ, html in standard.geltungsbereich_html %}
<div>{{ html|safe }}</div> <div>{{ html|safe }}</div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<!-- End Geltungsbereich -->
<h2>Vorgaben</h2> <h2>Vorgaben</h2>
{% for vorgabe in vorgaben %} {% for vorgabe in vorgaben %}
<!-- Start Vorgabe -->
{% if standard.history == True or vorgabe.long_status == "active" %} {% if standard.history == True or vorgabe.long_status == "active" %}
<a id="{{ vorgabe.Vorgabennummer }}"></a><div class="card mb-4"> <a id="{{ vorgabe.Vorgabennummer }}"></a><div class="card mb-4">
{% if vorgabe.long_status == "active"%} {% if vorgabe.long_status == "active"%}
@@ -46,7 +51,7 @@
</div> </div>
<div class="card-body p-0"> <div class="card-body p-0">
<!-- Start Kurztext -->
{% comment %} KURZTEXT BLOCK {% endcomment %} {% comment %} KURZTEXT BLOCK {% endcomment %}
{% if vorgabe.kurztext_html.0.1 %} {% if vorgabe.kurztext_html.0.1 %}
<div class="p-3 mb-3 bg-light border-3" style="width: 100%;"> <div class="p-3 mb-3 bg-light border-3" style="width: 100%;">
@@ -57,13 +62,14 @@
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
<!-- Langtext -->
<div class="p-3 mb-3"> <div class="p-3 mb-3">
{% comment %} LANGTEXT BLOCK {% endcomment %} {% comment %} LANGTEXT BLOCK {% endcomment %}
{# <h5>Langtext</h5> #} {# <h5>Langtext</h5> #}
{% for typ, html in vorgabe.langtext_html %} {% for typ, html in vorgabe.langtext_html %}
{% if html %}<div class="mb-3">{{ html|safe }}</div>{% endif %} {% if html %}<div class="mb-3">{{ html|safe }}</div>{% endif %}
{% endfor %} {% endfor %}
<!-- Checklistenfragen -->
{% comment %} CHECKLISTENFRAGEN BLOCK {% endcomment %} {% comment %} CHECKLISTENFRAGEN BLOCK {% endcomment %}
<h5>Checklistenfragen</h5> <h5>Checklistenfragen</h5>
{% if vorgabe.checklistenfragen.all %} {% if vorgabe.checklistenfragen.all %}

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,

View File

@@ -1,14 +0,0 @@
.nested-inline h3 {
background-color: #f3f3f3;
border-bottom: 1px solid #ccc;
padding: 0.4em;
margin: 0;
font-weight: bold;
cursor: pointer;
}
.nested-inline fieldset.module {
margin-bottom: 1em;
border: 1px solid #ccc;
padding: 0;
}

View File

@@ -1,24 +0,0 @@
document.addEventListener("DOMContentLoaded", function () {
const inlineSections = document.querySelectorAll(".nested-inline fieldset.module");
inlineSections.forEach(section => {
const header = section.querySelector("h2");
const content = Array.from(section.children).filter(child => !child.matches("h2"));
if (header && content.length > 0) {
header.style.cursor = "pointer";
header.style.userSelect = "none";
header.style.background = "#f3f3f3";
header.style.borderBottom = "1px solid #ccc";
header.style.padding = "4px";
// Collapse by default
content.forEach(el => el.style.display = "none");
header.addEventListener("click", () => {
const currentlyVisible = content[0].style.display !== "none";
content.forEach(el => el.style.display = currentlyVisible ? "none" : "block");
});
}
});
});