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 from django.views.decorators.http import require_POST from django.views.decorators.csrf import csrf_exempt from django.utils.html import escape, mark_safe from django.utils.safestring import SafeString import json from .models import Dokument, Vorgabe, VorgabeKurztext, VorgabeLangtext, Checklistenfrage, VorgabeComment from abschnitte.utils import render_textabschnitte from datetime import date import parsedatetime calendar=parsedatetime.Calendar() def standard_list(request): dokumente = Dokument.objects.all() return render(request, 'standards/standard_list.html', {'dokumente': dokumente} ) def standard_detail(request, nummer,check_date=""): standard = get_object_or_404(Dokument, nummer=nummer) if check_date: check_date = calendar.parseDT(check_date)[0].date() standard.history = True else: check_date = date.today() standard.history = False standard.check_date=check_date vorgaben = list(standard.vorgaben.order_by("thema","nummer").select_related("thema","dokument")) # convert queryset to list so we can attach attributes standard.geltungsbereich_html = render_textabschnitte(standard.geltungsbereich_set.order_by("order").select_related("abschnitttyp")) standard.einleitung_html=render_textabschnitte(standard.einleitung_set.order_by("order")) for vorgabe in vorgaben: # Prepare Kurztext HTML vorgabe.kurztext_html = render_textabschnitte(vorgabe.vorgabekurztext_set.order_by("order").select_related("abschnitttyp","abschnitt")) vorgabe.langtext_html = render_textabschnitte(vorgabe.vorgabelangtext_set.order_by("order").select_related("abschnitttyp","abschnitt")) vorgabe.long_status=vorgabe.get_status(check_date,verbose=True) vorgabe.relevanzset=list(vorgabe.relevanz.all()) referenz_items = [] for r in vorgabe.referenzen.all(): referenz_items.append(r.Path()) vorgabe.referenzpfade = referenz_items # Add comment count if request.user.is_authenticated: if request.user.is_staff: vorgabe.comment_count = vorgabe.comments.count() else: vorgabe.comment_count = vorgabe.comments.filter(user=request.user).count() else: vorgabe.comment_count = 0 return render(request, 'standards/standard_detail.html', { 'standard': standard, 'vorgaben': vorgaben, }) def standard_checkliste(request, nummer): standard = get_object_or_404(Dokument, nummer=nummer) vorgaben = list(standard.vorgaben.all()) return render(request, 'standards/standard_checkliste.html', { 'standard': standard, 'vorgaben': vorgaben, }) def is_staff_user(user): return user.is_staff @login_required @user_passes_test(is_staff_user) def incomplete_vorgaben(request): """ Show table of all Vorgaben with completeness status: - References (✓ or ✗) - Stichworte (✓ or ✗) - Text (✓ or ✗) - Checklistenfragen (✓ or ✗) """ # Get all active Vorgaben all_vorgaben = Vorgabe.objects.all().select_related('dokument', 'thema').prefetch_related( 'referenzen', 'stichworte', 'checklistenfragen', 'vorgabekurztext_set', 'vorgabelangtext_set' ) # Build table data vorgaben_data = [] for vorgabe in all_vorgaben: has_references = vorgabe.referenzen.exists() has_stichworte = vorgabe.stichworte.exists() has_kurztext = vorgabe.vorgabekurztext_set.exists() has_langtext = vorgabe.vorgabelangtext_set.exists() 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 }) # Sort by document number and Vorgabe number vorgaben_data.sort(key=lambda x: (x['vorgabe'].dokument.nummer, x['vorgabe'].Vorgabennummer())) 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) @login_required def get_vorgabe_comments(request, vorgabe_id): """Get comments for a specific Vorgabe""" vorgabe = get_object_or_404(Vorgabe, id=vorgabe_id) if request.user.is_staff: # Staff can see all comments comments = vorgabe.comments.all().select_related('user').order_by('created_at') else: # Regular users can only see their own comments comments = vorgabe.comments.filter(user=request.user).select_related('user').order_by('created_at') comments_data = [] for comment in comments: # Escape HTML but preserve line breaks escaped_text = escape(comment.text).replace('\n', '
') comments_data.append({ 'id': comment.id, 'text': escaped_text, 'user': escape(comment.user.first_name+" "+comment.user.last_name), 'created_at': comment.created_at.strftime('%d.%m.%Y %H:%M'), 'updated_at': comment.updated_at.strftime('%d.%m.%Y %H:%M'), 'is_own': comment.user == request.user }) response = JsonResponse({'comments': comments_data}) response['Content-Security-Policy'] = "default-src 'self'" response['X-Content-Type-Options'] = 'nosniff' return response @require_POST @login_required def add_vorgabe_comment(request, vorgabe_id): """Add a new comment to a Vorgabe""" vorgabe = get_object_or_404(Vorgabe, id=vorgabe_id) try: data = json.loads(request.body) text = data.get('text', '').strip() # Validate input if not text: return JsonResponse({'error': 'Kommentar darf nicht leer sein'}, status=400) if len(text) > 2000: # Reasonable length limit return JsonResponse({'error': 'Kommentar ist zu lang (max 2000 Zeichen)'}, status=400) # Additional XSS prevention - check for dangerous patterns dangerous_patterns = ['') response = JsonResponse({ 'success': True, 'comment': { 'id': comment.id, 'text': escaped_text, 'user': escape(comment.user.username), 'created_at': comment.created_at.strftime('%d.%m.%Y %H:%M'), 'updated_at': comment.updated_at.strftime('%d.%m.%Y %H:%M'), 'is_own': True } }) response['Content-Security-Policy'] = "default-src 'self'" response['X-Content-Type-Options'] = 'nosniff' return response except json.JSONDecodeError: response = JsonResponse({'error': 'Ungültige Daten'}, status=400) response['Content-Security-Policy'] = "default-src 'self'" response['X-Content-Type-Options'] = 'nosniff' return response except Exception as e: response = JsonResponse({'error': 'Serverfehler'}, status=500) response['Content-Security-Policy'] = "default-src 'self'" response['X-Content-Type-Options'] = 'nosniff' return response @require_POST @login_required def delete_vorgabe_comment(request, comment_id): """Delete a comment (only own comments or staff can delete)""" comment = get_object_or_404(VorgabeComment, id=comment_id) # Check if user can delete this comment if comment.user != request.user and not request.user.is_staff: response = JsonResponse({'error': 'Keine Berechtigung zum Löschen dieses Kommentars'}, status=403) response['Content-Security-Policy'] = "default-src 'self'" response['X-Content-Type-Options'] = 'nosniff' return response try: comment.delete() response = JsonResponse({'success': True}) response['Content-Security-Policy'] = "default-src 'self'" response['X-Content-Type-Options'] = 'nosniff' return response except Exception as e: response = JsonResponse({'error': 'Serverfehler'}, status=500) response['Content-Security-Policy'] = "default-src 'self'" response['X-Content-Type-Options'] = 'nosniff' return response @login_required def user_comments(request): """ Display all comments made by the logged-in user, grouped by document. """ # Get all comments by the current user user_comments = VorgabeComment.objects.filter( user=request.user ).select_related('vorgabe', 'vorgabe__dokument').order_by( 'vorgabe__dokument__nummer', '-created_at' ) # Group comments by document comments_by_document = {} for comment in user_comments: dokument = comment.vorgabe.dokument if dokument not in comments_by_document: comments_by_document[dokument] = [] comments_by_document[dokument].append(comment) return render(request, 'standards/user_comments.html', { 'comments_by_document': comments_by_document, 'total_comments': user_comments.count(), }) @login_required @user_passes_test(is_staff_user) def all_comments(request): """ Display all comments from all users, grouped by document. Staff only. """ # Get all comments all_comments_qs = VorgabeComment.objects.select_related( 'vorgabe', 'vorgabe__dokument', 'user' ).order_by( 'vorgabe__dokument__nummer', '-created_at' ) # Group comments by document comments_by_document = {} for comment in all_comments_qs: dokument = comment.vorgabe.dokument if dokument not in comments_by_document: comments_by_document[dokument] = [] comments_by_document[dokument].append(comment) return render(request, 'standards/all_comments.html', { 'comments_by_document': comments_by_document, 'total_comments': all_comments_qs.count(), })