Compare commits
6 Commits
2b41490806
...
feature/js
| Author | SHA1 | Date | |
|---|---|---|---|
| af636fe6ea | |||
| 3ccb32e8e1 | |||
| af4e1c61aa | |||
| 8153aa56ce | |||
| b82c6fea38 | |||
| cb374bfa77 |
1599
R0066.json
Normal file
1599
R0066.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -207,10 +207,43 @@ class ThemaAdmin(admin.ModelAdmin):
|
|||||||
search_fields = ['name']
|
search_fields = ['name']
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
|
||||||
|
@admin.register(Vorgabe)
|
||||||
|
class VorgabeAdmin(NestedModelAdmin):
|
||||||
|
form = VorgabeForm
|
||||||
|
list_display = ['vorgabe_nummer', 'titel', 'dokument', 'thema', 'gueltigkeit_von', 'gueltigkeit_bis']
|
||||||
|
list_filter = ['dokument', 'thema', 'gueltigkeit_von', 'gueltigkeit_bis']
|
||||||
|
search_fields = ['nummer', 'titel', 'dokument__nummer', 'dokument__name']
|
||||||
|
autocomplete_fields = ['stichworte', 'referenzen', 'relevanz']
|
||||||
|
ordering = ['dokument', 'order']
|
||||||
|
|
||||||
|
inlines = [
|
||||||
|
VorgabeKurztextInline,
|
||||||
|
VorgabeLangtextInline,
|
||||||
|
ChecklistenfragenInline
|
||||||
|
]
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
('Grunddaten', {
|
||||||
|
'fields': (('order', 'nummer'), ('dokument', 'thema'), 'titel'),
|
||||||
|
'classes': ('wide',),
|
||||||
|
}),
|
||||||
|
('Gültigkeit', {
|
||||||
|
'fields': (('gueltigkeit_von', 'gueltigkeit_bis'),),
|
||||||
|
'classes': ('wide',),
|
||||||
|
}),
|
||||||
|
('Verknüpfungen', {
|
||||||
|
'fields': (('referenzen', 'stichworte', 'relevanz'),),
|
||||||
|
'classes': ('wide',),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
def vorgabe_nummer(self, obj):
|
||||||
|
return obj.Vorgabennummer()
|
||||||
|
vorgabe_nummer.short_description = 'Vorgabennummer'
|
||||||
|
|
||||||
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(Referenz, DraggableM§PTTAdmin)
|
#admin.site.register(Referenz, DraggableM§PTTAdmin)
|
||||||
admin.site.register(Vorgabe)
|
|
||||||
|
|
||||||
#admin.site.register(Changelog)
|
#admin.site.register(Changelog)
|
||||||
|
|||||||
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)
|
||||||
@@ -2,166 +2,150 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="mb-4">Unvollständige Vorgaben</h1>
|
<h1 class="mb-4">Unvollständige Vorgaben</h1>
|
||||||
|
|
||||||
<div class="row">
|
{% if vorgaben_data %}
|
||||||
<!-- Vorgaben ohne Referenzen -->
|
<div class="table-responsive">
|
||||||
<div class="col-md-6 mb-4">
|
<table class="table table-striped table-hover">
|
||||||
<div class="card">
|
<thead class="table-dark">
|
||||||
<div class="card-header bg-warning text-dark">
|
<tr>
|
||||||
<h5 class="mb-0">
|
<th>Vorgabe</th>
|
||||||
<i class="fas fa-exclamation-triangle"></i>
|
<th class="text-center">Referenzen</th>
|
||||||
Vorgaben ohne Referenzen
|
<th class="text-center">Stichworte</th>
|
||||||
<span class="badge bg-secondary float-end">{{ no_references|length }}</span>
|
<th class="text-center">Text</th>
|
||||||
</h5>
|
<th class="text-center">Checklistenfragen</th>
|
||||||
</div>
|
</tr>
|
||||||
<div class="card-body">
|
</thead>
|
||||||
{% if no_references %}
|
<tbody>
|
||||||
<div class="list-group list-group-flush">
|
{% for item in vorgaben_data %}
|
||||||
{% for vorgabe in no_references %}
|
<tr>
|
||||||
<a href="{% url 'standard_detail' nummer=vorgabe.dokument.nummer %}#{{ vorgabe.Vorgabennummer }}"
|
<td>
|
||||||
class="list-group-item list-group-item-action">
|
<a href="/autorenumgebung/dokumente/vorgabe/{{ item.vorgabe.id }}/change/"
|
||||||
<strong>{{ vorgabe.Vorgabennummer }}</strong>: {{ vorgabe.titel }}
|
class="text-decoration-none" target="_blank">
|
||||||
<br>
|
<strong>{{ item.vorgabe.Vorgabennummer }}</strong><br>
|
||||||
<small class="text-muted">{{ vorgabe.dokument.nummer }} – {{ vorgabe.dokument.name }}</small>
|
<small class="text-muted">{{ item.vorgabe.titel }}</small><br>
|
||||||
</a>
|
<small class="text-muted">{{ item.vorgabe.dokument.nummer }} – {{ item.vorgabe.dokument.name }}</small>
|
||||||
{% endfor %}
|
</a>
|
||||||
</div>
|
</td>
|
||||||
{% else %}
|
<td class="text-center align-middle">
|
||||||
<p class="text-muted mb-0">Alle Vorgaben haben Referenzen.</p>
|
{% if item.has_references %}
|
||||||
{% endif %}
|
<span class="text-success fs-4">✓</span>
|
||||||
</div>
|
{% else %}
|
||||||
</div>
|
<span class="text-danger fs-4">✗</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="text-center align-middle">
|
||||||
|
{% if item.has_stichworte %}
|
||||||
|
<span class="text-success fs-4">✓</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-danger fs-4">✗</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="text-center align-middle">
|
||||||
|
{% if item.has_text %}
|
||||||
|
<span class="text-success fs-4">✓</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-danger fs-4">✗</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="text-center align-middle">
|
||||||
|
{% if item.has_checklistenfragen %}
|
||||||
|
<span class="text-success fs-4">✓</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-danger fs-4">✗</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Vorgaben ohne Stichworte -->
|
<!-- Summary -->
|
||||||
<div class="col-md-6 mb-4">
|
<div class="row mt-4">
|
||||||
<div class="card">
|
<div class="col-12">
|
||||||
<div class="card-header bg-warning text-dark">
|
<div class="card">
|
||||||
<h5 class="mb-0">
|
<div class="card-header">
|
||||||
<i class="fas fa-tags"></i>
|
<h5 class="mb-0">Zusammenfassung</h5>
|
||||||
Vorgaben ohne Stichworte
|
</div>
|
||||||
<span class="badge bg-secondary float-end">{{ no_stichworte|length }}</span>
|
<div class="card-body">
|
||||||
</h5>
|
<div class="row text-center">
|
||||||
</div>
|
<div class="col-md-3">
|
||||||
<div class="card-body">
|
<div class="p-3">
|
||||||
{% if no_stichworte %}
|
<h4 class="text-danger" id="no-references-count">0</h4>
|
||||||
<div class="list-group list-group-flush">
|
<p class="mb-0">Ohne Referenzen</p>
|
||||||
{% for vorgabe in no_stichworte %}
|
</div>
|
||||||
<a href="{% url 'standard_detail' nummer=vorgabe.dokument.nummer %}#{{ vorgabe.Vorgabennummer }}"
|
</div>
|
||||||
class="list-group-item list-group-item-action">
|
<div class="col-md-3">
|
||||||
<strong>{{ vorgabe.Vorgabennummer }}</strong>: {{ vorgabe.titel }}
|
<div class="p-3">
|
||||||
<br>
|
<h4 class="text-danger" id="no-stichworte-count">0</h4>
|
||||||
<small class="text-muted">{{ vorgabe.dokument.nummer }} – {{ vorgabe.dokument.name }}</small>
|
<p class="mb-0">Ohne Stichworte</p>
|
||||||
</a>
|
</div>
|
||||||
{% endfor %}
|
</div>
|
||||||
</div>
|
<div class="col-md-3">
|
||||||
{% else %}
|
<div class="p-3">
|
||||||
<p class="text-muted mb-0">Alle Vorgaben haben Stichworte.</p>
|
<h4 class="text-danger" id="no-text-count">0</h4>
|
||||||
{% endif %}
|
<p class="mb-0">Ohne Text</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="col-md-3">
|
||||||
|
<div class="p-3">
|
||||||
<!-- Vorgaben ohne Kurz- oder Langtext -->
|
<h4 class="text-danger" id="no-checklistenfragen-count">0</h4>
|
||||||
<div class="col-md-6 mb-4">
|
<p class="mb-0">Ohne Checklistenfragen</p>
|
||||||
<div class="card">
|
</div>
|
||||||
<div class="card-header bg-danger text-white">
|
|
||||||
<h5 class="mb-0">
|
|
||||||
<i class="fas fa-file-alt"></i>
|
|
||||||
Vorgaben ohne Kurz- oder Langtext
|
|
||||||
<span class="badge bg-secondary float-end">{{ no_text|length }}</span>
|
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
{% if no_text %}
|
|
||||||
<div class="list-group list-group-flush">
|
|
||||||
{% for vorgabe in no_text %}
|
|
||||||
<a href="{% url 'standard_detail' nummer=vorgabe.dokument.nummer %}#{{ vorgabe.Vorgabennummer }}"
|
|
||||||
class="list-group-item list-group-item-action">
|
|
||||||
<strong>{{ vorgabe.Vorgabennummer }}</strong>: {{ vorgabe.titel }}
|
|
||||||
<br>
|
|
||||||
<small class="text-muted">{{ vorgabe.dokument.nummer }} – {{ vorgabe.dokument.name }}</small>
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<p class="text-muted mb-0">Alle Vorgaben haben Kurz- oder Langtext.</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Vorgaben ohne Checklistenfragen -->
|
|
||||||
<div class="col-md-6 mb-4">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header bg-info text-white">
|
|
||||||
<h5 class="mb-0">
|
|
||||||
<i class="fas fa-question-circle"></i>
|
|
||||||
Vorgaben ohne Checklistenfragen
|
|
||||||
<span class="badge bg-secondary float-end">{{ no_checklistenfragen|length }}</span>
|
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
{% if no_checklistenfragen %}
|
|
||||||
<div class="list-group list-group-flush">
|
|
||||||
{% for vorgabe in no_checklistenfragen %}
|
|
||||||
<a href="{% url 'standard_detail' nummer=vorgabe.dokument.nummer %}#{{ vorgabe.Vorgabennummer }}"
|
|
||||||
class="list-group-item list-group-item-action">
|
|
||||||
<strong>{{ vorgabe.Vorgabennummer }}</strong>: {{ vorgabe.titel }}
|
|
||||||
<br>
|
|
||||||
<small class="text-muted">{{ vorgabe.dokument.nummer }} – {{ vorgabe.dokument.name }}</small>
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<p class="text-muted mb-0">Alle Vorgaben haben Checklistenfragen.</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Summary -->
|
|
||||||
<div class="row mt-4">
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h5 class="mb-0">Zusammenfassung</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="row text-center">
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="p-3">
|
|
||||||
<h4 class="text-warning">{{ no_references|length }}</h4>
|
|
||||||
<p class="mb-0">Ohne Referenzen</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="row mt-3">
|
||||||
<div class="p-3">
|
<div class="col-12 text-center">
|
||||||
<h4 class="text-warning">{{ no_stichworte|length }}</h4>
|
<h4 class="text-primary">Gesamt: {{ vorgaben_data|length }} unvollständige Vorgaben</h4>
|
||||||
<p class="mb-0">Ohne Stichworte</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="p-3">
|
|
||||||
<h4 class="text-danger">{{ no_text|length }}</h4>
|
|
||||||
<p class="mb-0">Ohne Text</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="p-3">
|
|
||||||
<h4 class="text-info">{{ no_checklistenfragen|length }}</h4>
|
|
||||||
<p class="mb-0">Ohne Checklistenfragen</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% else %}
|
||||||
|
<div class="alert alert-success" role="alert">
|
||||||
|
<h4 class="alert-heading">
|
||||||
|
<i class="fas fa-check-circle"></i> Alle Vorgaben sind vollständig!
|
||||||
|
</h4>
|
||||||
|
<p>Alle Vorgaben haben Referenzen, Stichworte, Text und Checklistenfragen.</p>
|
||||||
|
<hr>
|
||||||
|
<p class="mb-0">
|
||||||
|
<a href="{% url 'standard_list' %}" class="btn btn-primary">
|
||||||
|
<i class="fas fa-list"></i> Zurück zur Übersicht
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<a href="{% url 'standard_list' %}" class="btn btn-secondary">
|
<a href="{% url 'standard_list' %}" class="btn btn-secondary">
|
||||||
<i class="fas fa-arrow-left"></i> Zurück zur Übersicht
|
<i class="fas fa-arrow-left"></i> Zurück zur Übersicht
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Update summary counts
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
let noReferences = 0;
|
||||||
|
let noStichworte = 0;
|
||||||
|
let noText = 0;
|
||||||
|
let noChecklistenfragen = 0;
|
||||||
|
|
||||||
|
const rows = document.querySelectorAll('tbody tr');
|
||||||
|
rows.forEach(function(row) {
|
||||||
|
const cells = row.querySelectorAll('td');
|
||||||
|
if (cells.length >= 5) {
|
||||||
|
if (cells[1].textContent.trim() === '✗') noReferences++;
|
||||||
|
if (cells[2].textContent.trim() === '✗') noStichworte++;
|
||||||
|
if (cells[3].textContent.trim() === '✗') noText++;
|
||||||
|
if (cells[4].textContent.trim() === '✗') noChecklistenfragen++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('no-references-count').textContent = noReferences;
|
||||||
|
document.getElementById('no-stichworte-count').textContent = noStichworte;
|
||||||
|
document.getElementById('no-text-count').textContent = noText;
|
||||||
|
document.getElementById('no-checklistenfragen-count').textContent = noChecklistenfragen;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -966,10 +966,13 @@ class IncompleteVorgabenTest(TestCase):
|
|||||||
"""Test that the page contains expected content"""
|
"""Test that the page contains expected content"""
|
||||||
response = self.client.get(reverse('incomplete_vorgaben'))
|
response = self.client.get(reverse('incomplete_vorgaben'))
|
||||||
self.assertContains(response, 'Unvollständige Vorgaben')
|
self.assertContains(response, 'Unvollständige Vorgaben')
|
||||||
self.assertContains(response, 'Vorgaben ohne Referenzen')
|
self.assertContains(response, 'Referenzen')
|
||||||
self.assertContains(response, 'Vorgaben ohne Stichworte')
|
self.assertContains(response, 'Stichworte')
|
||||||
self.assertContains(response, 'Vorgaben ohne Kurz- oder Langtext')
|
self.assertContains(response, 'Text')
|
||||||
self.assertContains(response, 'Vorgaben ohne Checklistenfragen')
|
self.assertContains(response, 'Checklistenfragen')
|
||||||
|
# Check for table structure
|
||||||
|
self.assertContains(response, '<table class="table table-striped table-hover">')
|
||||||
|
self.assertContains(response, '<th class="text-center">Referenzen</th>')
|
||||||
|
|
||||||
def test_no_references_list(self):
|
def test_no_references_list(self):
|
||||||
"""Test that Vorgaben without references are listed"""
|
"""Test that Vorgaben without references are listed"""
|
||||||
@@ -996,27 +999,34 @@ class IncompleteVorgabenTest(TestCase):
|
|||||||
self.assertNotContains(response, 'Vollständige Vorgabe') # Should not appear
|
self.assertNotContains(response, 'Vollständige Vorgabe') # Should not appear
|
||||||
|
|
||||||
def test_vorgabe_links(self):
|
def test_vorgabe_links(self):
|
||||||
"""Test that Vorgaben link to their detail pages"""
|
"""Test that Vorgaben link to their admin pages"""
|
||||||
response = self.client.get(reverse('incomplete_vorgaben'))
|
response = self.client.get(reverse('incomplete_vorgaben'))
|
||||||
# Should contain links to Vorgabe detail pages
|
# Should contain links to Vorgabe admin pages
|
||||||
self.assertContains(response, f'href="/dokumente/{self.dokument.nummer}/#TEST-001.T.2"')
|
self.assertContains(response, 'href="/autorenumgebung/dokumente/vorgabe/2/change/"')
|
||||||
self.assertContains(response, f'href="/dokumente/{self.dokument.nummer}/#TEST-001.T.3"')
|
self.assertContains(response, 'href="/autorenumgebung/dokumente/vorgabe/3/change/"')
|
||||||
self.assertContains(response, f'href="/dokumente/{self.dokument.nummer}/#TEST-001.T.4"')
|
self.assertContains(response, 'href="/autorenumgebung/dokumente/vorgabe/4/change/"')
|
||||||
self.assertContains(response, f'href="/dokumente/{self.dokument.nummer}/#TEST-001.T.5"')
|
self.assertContains(response, 'href="/autorenumgebung/dokumente/vorgabe/5/change/"')
|
||||||
|
|
||||||
def test_badge_counts(self):
|
def test_badge_counts(self):
|
||||||
"""Test that badge counts are correct"""
|
"""Test that badge counts are correct"""
|
||||||
response = self.client.get(reverse('incomplete_vorgaben'))
|
response = self.client.get(reverse('incomplete_vorgaben'))
|
||||||
# Each category should have exactly 1 Vorgabe
|
# Check that JavaScript updates the counts correctly
|
||||||
self.assertContains(response, '<span class="badge bg-secondary float-end">1</span>', count=4)
|
self.assertContains(response, 'id="no-references-count"')
|
||||||
|
self.assertContains(response, 'id="no-stichworte-count"')
|
||||||
|
self.assertContains(response, 'id="no-text-count"')
|
||||||
|
self.assertContains(response, 'id="no-checklistenfragen-count"')
|
||||||
|
# Check total count
|
||||||
|
self.assertContains(response, 'Gesamt: 4 unvollständige Vorgaben')
|
||||||
|
|
||||||
def test_summary_section(self):
|
def test_summary_section(self):
|
||||||
"""Test that summary section shows correct counts"""
|
"""Test that summary section shows correct counts"""
|
||||||
response = self.client.get(reverse('incomplete_vorgaben'))
|
response = self.client.get(reverse('incomplete_vorgaben'))
|
||||||
self.assertContains(response, 'Zusammenfassung')
|
self.assertContains(response, 'Zusammenfassung')
|
||||||
self.assertContains(response, '<h4 class="text-warning">1</h4>', count=2) # No refs, no stichworte
|
self.assertContains(response, 'Ohne Referenzen')
|
||||||
self.assertContains(response, '<h4 class="text-danger">1</h4>') # No text
|
self.assertContains(response, 'Ohne Stichworte')
|
||||||
self.assertContains(response, '<h4 class="text-info">1</h4>') # No checklistenfragen
|
self.assertContains(response, 'Ohne Text')
|
||||||
|
self.assertContains(response, 'Ohne Checklistenfragen')
|
||||||
|
self.assertContains(response, 'Gesamt: 4 unvollständige Vorgaben')
|
||||||
|
|
||||||
def test_empty_lists_message(self):
|
def test_empty_lists_message(self):
|
||||||
"""Test that appropriate messages are shown when lists are empty"""
|
"""Test that appropriate messages are shown when lists are empty"""
|
||||||
@@ -1024,10 +1034,8 @@ class IncompleteVorgabenTest(TestCase):
|
|||||||
Vorgabe.objects.exclude(pk=self.complete_vorgabe.pk).delete()
|
Vorgabe.objects.exclude(pk=self.complete_vorgabe.pk).delete()
|
||||||
|
|
||||||
response = self.client.get(reverse('incomplete_vorgaben'))
|
response = self.client.get(reverse('incomplete_vorgaben'))
|
||||||
self.assertContains(response, 'Alle Vorgaben haben Referenzen.')
|
self.assertContains(response, 'Alle Vorgaben sind vollständig!')
|
||||||
self.assertContains(response, 'Alle Vorgaben haben Stichworte.')
|
self.assertContains(response, 'Alle Vorgaben haben Referenzen, Stichworte, Text und Checklistenfragen.')
|
||||||
self.assertContains(response, 'Alle Vorgaben haben Kurz- oder Langtext.')
|
|
||||||
self.assertContains(response, 'Alle Vorgaben haben Checklistenfragen.')
|
|
||||||
|
|
||||||
def test_back_link(self):
|
def test_back_link(self):
|
||||||
"""Test that back link to standard list exists"""
|
"""Test that back link to standard list exists"""
|
||||||
|
|||||||
@@ -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')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -64,36 +67,173 @@ def is_staff_user(user):
|
|||||||
@user_passes_test(is_staff_user)
|
@user_passes_test(is_staff_user)
|
||||||
def incomplete_vorgaben(request):
|
def incomplete_vorgaben(request):
|
||||||
"""
|
"""
|
||||||
Show lists of incomplete Vorgaben:
|
Show table of all Vorgaben with completeness status:
|
||||||
1. Ones with no references
|
- References (✓ or ✗)
|
||||||
2. Ones with no Stichworte
|
- Stichworte (✓ or ✗)
|
||||||
3. Ones without Kurz- or Langtext
|
- Text (✓ or ✗)
|
||||||
4. Ones without Checklistenfragen
|
- Checklistenfragen (✓ or ✗)
|
||||||
"""
|
"""
|
||||||
# Get all active Vorgaben
|
# Get all active Vorgaben
|
||||||
all_vorgaben = Vorgabe.objects.all().select_related('dokument', 'thema')
|
all_vorgaben = Vorgabe.objects.all().select_related('dokument', 'thema').prefetch_related(
|
||||||
|
'referenzen', 'stichworte', 'checklistenfragen', 'vorgabekurztext_set', 'vorgabelangtext_set'
|
||||||
|
)
|
||||||
|
|
||||||
# 1. Vorgaben with no references
|
# Build table data
|
||||||
no_references = [v for v in all_vorgaben if not v.referenzen.exists()]
|
vorgaben_data = []
|
||||||
|
|
||||||
# 2. Vorgaben with no Stichworte
|
|
||||||
no_stichworte = [v for v in all_vorgaben if not v.stichworte.exists()]
|
|
||||||
|
|
||||||
# 3. Vorgaben without Kurz- or Langtext
|
|
||||||
no_text = []
|
|
||||||
for vorgabe in all_vorgaben:
|
for vorgabe in all_vorgaben:
|
||||||
has_kurztext = VorgabeKurztext.objects.filter(abschnitt=vorgabe).exists()
|
has_references = vorgabe.referenzen.exists()
|
||||||
has_langtext = VorgabeLangtext.objects.filter(abschnitt=vorgabe).exists()
|
has_stichworte = vorgabe.stichworte.exists()
|
||||||
|
has_kurztext = vorgabe.vorgabekurztext_set.exists()
|
||||||
if not has_kurztext and not has_langtext:
|
has_langtext = vorgabe.vorgabelangtext_set.exists()
|
||||||
no_text.append(vorgabe)
|
has_text = has_kurztext or has_langtext
|
||||||
|
has_checklistenfragen = vorgabe.checklistenfragen.exists()
|
||||||
|
|
||||||
|
# Only include Vorgaben that are incomplete in at least one way
|
||||||
|
if not (has_references and has_stichworte and has_text and has_checklistenfragen):
|
||||||
|
vorgaben_data.append({
|
||||||
|
'vorgabe': vorgabe,
|
||||||
|
'has_references': has_references,
|
||||||
|
'has_stichworte': has_stichworte,
|
||||||
|
'has_text': has_text,
|
||||||
|
'has_checklistenfragen': has_checklistenfragen,
|
||||||
|
'is_complete': has_references and has_stichworte and has_text and has_checklistenfragen
|
||||||
|
})
|
||||||
|
|
||||||
# 4. Vorgaben without Checklistenfragen
|
# Sort by document number and Vorgabe number
|
||||||
no_checklistenfragen = [v for v in all_vorgaben if not v.checklistenfragen.exists()]
|
vorgaben_data.sort(key=lambda x: (x['vorgabe'].dokument.nummer, x['vorgabe'].Vorgabennummer()))
|
||||||
|
|
||||||
return render(request, 'standards/incomplete_vorgaben.html', {
|
return render(request, 'standards/incomplete_vorgaben.html', {
|
||||||
'no_references': no_references,
|
'vorgaben_data': vorgaben_data,
|
||||||
'no_stichworte': no_stichworte,
|
|
||||||
'no_text': no_text,
|
|
||||||
'no_checklistenfragen': no_checklistenfragen,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user