Compare commits
8 Commits
feature/li
...
improvemen
| Author | SHA1 | Date | |
|---|---|---|---|
| 8520412867 | |||
| e94f61a697 | |||
| 0cd09d0878 | |||
| 994ba5d797 | |||
| af636fe6ea | |||
| 3ccb32e8e1 | |||
| af4e1c61aa | |||
| 8153aa56ce |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,10 +8,11 @@ include/
|
||||
keys/
|
||||
.venv/
|
||||
.idea/
|
||||
|
||||
*.kate-swp
|
||||
node_modules/
|
||||
package-lock.json
|
||||
package.json
|
||||
# Diagram cache directory
|
||||
media/diagram_cache/
|
||||
.env
|
||||
data/db.sqlite3
|
||||
|
||||
1599
R0066.json
Normal file
1599
R0066.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,7 @@ spec:
|
||||
mountPath: /data
|
||||
containers:
|
||||
- name: web
|
||||
image: git.baumann.gr/adebaumann/vui:0.942
|
||||
image: git.baumann.gr/adebaumann/vui:0.943
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
|
||||
BIN
data/db.sqlite3
BIN
data/db.sqlite3
Binary file not shown.
@@ -2,6 +2,7 @@ from django.contrib import admin
|
||||
#from nested_inline.admin import NestedStackedInline, NestedModelAdmin
|
||||
from nested_admin import NestedStackedInline, NestedModelAdmin, NestedTabularInline
|
||||
from django import forms
|
||||
from django.utils.html import format_html
|
||||
from mptt.forms import TreeNodeMultipleChoiceField
|
||||
from mptt.admin import DraggableMPTTAdmin
|
||||
from adminsortable2.admin import SortableInlineAdminMixin, SortableAdminBase
|
||||
@@ -132,9 +133,57 @@ class StichworterklaerungInline(NestedTabularInline):
|
||||
|
||||
@admin.register(Stichwort)
|
||||
class StichwortAdmin(NestedModelAdmin):
|
||||
list_display = ('stichwort', 'vorgaben_count')
|
||||
search_fields = ('stichwort',)
|
||||
ordering=('stichwort',)
|
||||
inlines=[StichworterklaerungInline]
|
||||
readonly_fields = ('vorgaben_list',)
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': ('stichwort', 'vorgaben_list')
|
||||
}),
|
||||
)
|
||||
|
||||
def vorgaben_count(self, obj):
|
||||
"""Count the number of Vorgaben that have this Stichwort"""
|
||||
count = obj.vorgabe_set.count()
|
||||
return f"{count} Vorgabe{'n' if count != 1 else ''}"
|
||||
vorgaben_count.short_description = "Anzahl Vorgaben"
|
||||
|
||||
def vorgaben_list(self, obj):
|
||||
"""Display list of Vorgaben that use this Stichwort"""
|
||||
vorgaben = obj.vorgabe_set.select_related('dokument', 'thema').order_by('dokument__nummer', 'nummer')
|
||||
vorgaben_list = list(vorgaben) # Evaluate queryset once
|
||||
count = len(vorgaben_list)
|
||||
|
||||
if count == 0:
|
||||
return format_html("<em>Keine Vorgaben gefunden</em><p><strong>Gesamt: 0 Vorgaben</strong></p>")
|
||||
|
||||
html = "<div style='max-height: 300px; overflow-y: auto;'>"
|
||||
html += "<table style='width: 100%; border-collapse: collapse;'>"
|
||||
html += "<thead><tr style='background-color: #f5f5f5;'>"
|
||||
html += "<th style='padding: 8px; border: 1px solid #ddd; text-align: left;'>Vorgabe</th>"
|
||||
html += "<th style='padding: 8px; border: 1px solid #ddd; text-align: left;'>Titel</th>"
|
||||
html += "<th style='padding: 8px; border: 1px solid #ddd; text-align: left;'>Dokument</th>"
|
||||
html += "</tr></thead>"
|
||||
html += "<tbody>"
|
||||
|
||||
for vorgabe in vorgaben_list:
|
||||
html += "<tr>"
|
||||
html += f"<td style='padding: 6px; border: 1px solid #ddd;'>{vorgabe.Vorgabennummer()}</td>"
|
||||
html += f"<td style='padding: 6px; border: 1px solid #ddd;'>{vorgabe.titel}</td>"
|
||||
html += f"<td style='padding: 6px; border: 1px solid #ddd;'>{vorgabe.dokument.nummer} – {vorgabe.dokument.name}</td>"
|
||||
html += "</tr>"
|
||||
|
||||
html += "</tbody></table>"
|
||||
html += f"</div><p><strong>Gesamt: {count} Vorgabe{'n' if count != 1 else ''}</strong></p>"
|
||||
|
||||
return format_html(html)
|
||||
vorgaben_list.short_description = "Zugeordnete Vorgaben"
|
||||
|
||||
def get_queryset(self, request):
|
||||
"""Optimize queryset with related data"""
|
||||
return super().get_queryset(request).prefetch_related('vorgabe_set')
|
||||
|
||||
@admin.register(Person)
|
||||
class PersonAdmin(admin.ModelAdmin):
|
||||
|
||||
174
dokumente/management/commands/export_json.py
Normal file
174
dokumente/management/commands/export_json.py
Normal 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)
|
||||
@@ -9,6 +9,7 @@
|
||||
<p><strong>Autoren:</strong> {{ standard.autoren.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><a href="{% url 'standard_json' standard.nummer %}" class="button" download="{{ standard.nummer }}.json">JSON herunterladen</a></p>
|
||||
|
||||
<!-- Start Einleitung -->
|
||||
{% if standard.einleitung_html %}
|
||||
|
||||
@@ -7,6 +7,7 @@ urlpatterns = [
|
||||
path('<str:nummer>/', views.standard_detail, name='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>/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')
|
||||
]
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
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 abschnitte.utils import render_textabschnitte
|
||||
|
||||
@@ -102,3 +105,135 @@ def incomplete_vorgaben(request):
|
||||
return render(request, 'standards/incomplete_vorgaben.html', {
|
||||
'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)
|
||||
|
||||
@@ -31,6 +31,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.942</div>
|
||||
<div>VorgabenUI v0.943</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user