From cb374bfa7797b3fc06263747ebf5ee65ffc31d10 Mon Sep 17 00:00:00 2001 From: "Adrian A. Baumann" Date: Tue, 4 Nov 2025 14:52:41 +0100 Subject: [PATCH] feat: enhance incomplete Vorgaben page with table layout and admin integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Redesign incomplete Vorgaben page from card layout to unified table format - Add visual status indicators (✓/✗) for each completeness category - Link Vorgaben directly to admin edit pages (/autorenumgebung/ instead of /admin/) - Enhance Vorgabe admin with Kurztext and Langtext inlines for complete editing - Update all tests to work with new table structure and admin URLs - Add JavaScript for dynamic summary count updates - Maintain staff-only access control and responsive design All 112 tests passing successfully. --- dokumente/admin.py | 35 ++- .../standards/incomplete_vorgaben.html | 276 +++++++++--------- dokumente/tests.py | 46 +-- dokumente/views.py | 55 ++-- 4 files changed, 221 insertions(+), 191 deletions(-) diff --git a/dokumente/admin.py b/dokumente/admin.py index f2002ad..82331ae 100644 --- a/dokumente/admin.py +++ b/dokumente/admin.py @@ -207,10 +207,43 @@ class ThemaAdmin(admin.ModelAdmin): search_fields = ['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(Dokumententyp) #admin.site.register(Person) #admin.site.register(Referenz, DraggableM§PTTAdmin) -admin.site.register(Vorgabe) #admin.site.register(Changelog) diff --git a/dokumente/templates/standards/incomplete_vorgaben.html b/dokumente/templates/standards/incomplete_vorgaben.html index d0d8251..e6c9f15 100644 --- a/dokumente/templates/standards/incomplete_vorgaben.html +++ b/dokumente/templates/standards/incomplete_vorgaben.html @@ -2,166 +2,150 @@ {% block content %}

Unvollständige Vorgaben

-
- -
-
-
-
- - Vorgaben ohne Referenzen - {{ no_references|length }} -
-
-
- {% if no_references %} - - {% else %} -

Alle Vorgaben haben Referenzen.

- {% endif %} -
-
+{% if vorgaben_data %} +
+ + + + + + + + + + + + {% for item in vorgaben_data %} + + + + + + + + {% endfor %} + +
VorgabeReferenzenStichworteTextChecklistenfragen
+ + {{ item.vorgabe.Vorgabennummer }}
+ {{ item.vorgabe.titel }}
+ {{ item.vorgabe.dokument.nummer }} – {{ item.vorgabe.dokument.name }} +
+
+ {% if item.has_references %} + + {% else %} + + {% endif %} + + {% if item.has_stichworte %} + + {% else %} + + {% endif %} + + {% if item.has_text %} + + {% else %} + + {% endif %} + + {% if item.has_checklistenfragen %} + + {% else %} + + {% endif %} +
- -
-
-
-
- - Vorgaben ohne Stichworte - {{ no_stichworte|length }} -
-
-
- {% if no_stichworte %} - - {% else %} -

Alle Vorgaben haben Stichworte.

- {% endif %} -
-
-
- - -
-
-
-
- - Vorgaben ohne Kurz- oder Langtext - {{ no_text|length }} -
-
-
- {% if no_text %} - - {% else %} -

Alle Vorgaben haben Kurz- oder Langtext.

- {% endif %} -
-
-
- - -
-
-
-
- - Vorgaben ohne Checklistenfragen - {{ no_checklistenfragen|length }} -
-
-
- {% if no_checklistenfragen %} - - {% else %} -

Alle Vorgaben haben Checklistenfragen.

- {% endif %} -
-
-
-
- - -
-
-
-
-
Zusammenfassung
-
-
-
-
-
-

{{ no_references|length }}

-

Ohne Referenzen

+ +
+
+
+
+
Zusammenfassung
+
+
+
+
+
+

0

+

Ohne Referenzen

+
+
+
+
+

0

+

Ohne Stichworte

+
+
+
+
+

0

+

Ohne Text

+
+
+
+
+

0

+

Ohne Checklistenfragen

+
-
-
-

{{ no_stichworte|length }}

-

Ohne Stichworte

-
-
-
-
-

{{ no_text|length }}

-

Ohne Text

-
-
-
-
-

{{ no_checklistenfragen|length }}

-

Ohne Checklistenfragen

+
+
+

Gesamt: {{ vorgaben_data|length }} unvollständige Vorgaben

-
+{% else %} + +{% endif %} + + {% endblock %} \ No newline at end of file diff --git a/dokumente/tests.py b/dokumente/tests.py index 1e24337..a6d6f91 100644 --- a/dokumente/tests.py +++ b/dokumente/tests.py @@ -966,10 +966,13 @@ class IncompleteVorgabenTest(TestCase): """Test that the page contains expected content""" response = self.client.get(reverse('incomplete_vorgaben')) self.assertContains(response, 'Unvollständige Vorgaben') - self.assertContains(response, 'Vorgaben ohne Referenzen') - self.assertContains(response, 'Vorgaben ohne Stichworte') - self.assertContains(response, 'Vorgaben ohne Kurz- oder Langtext') - self.assertContains(response, 'Vorgaben ohne Checklistenfragen') + self.assertContains(response, 'Referenzen') + self.assertContains(response, 'Stichworte') + self.assertContains(response, 'Text') + self.assertContains(response, 'Checklistenfragen') + # Check for table structure + self.assertContains(response, '') + self.assertContains(response, '') def test_no_references_list(self): """Test that Vorgaben without references are listed""" @@ -996,27 +999,34 @@ class IncompleteVorgabenTest(TestCase): self.assertNotContains(response, 'Vollständige Vorgabe') # Should not appear 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')) - # Should contain links to Vorgabe detail pages - self.assertContains(response, f'href="/dokumente/{self.dokument.nummer}/#TEST-001.T.2"') - self.assertContains(response, f'href="/dokumente/{self.dokument.nummer}/#TEST-001.T.3"') - self.assertContains(response, f'href="/dokumente/{self.dokument.nummer}/#TEST-001.T.4"') - self.assertContains(response, f'href="/dokumente/{self.dokument.nummer}/#TEST-001.T.5"') + # Should contain links to Vorgabe admin pages + self.assertContains(response, 'href="/autorenumgebung/dokumente/vorgabe/2/change/"') + self.assertContains(response, 'href="/autorenumgebung/dokumente/vorgabe/3/change/"') + self.assertContains(response, 'href="/autorenumgebung/dokumente/vorgabe/4/change/"') + self.assertContains(response, 'href="/autorenumgebung/dokumente/vorgabe/5/change/"') def test_badge_counts(self): """Test that badge counts are correct""" response = self.client.get(reverse('incomplete_vorgaben')) - # Each category should have exactly 1 Vorgabe - self.assertContains(response, '1', count=4) + # Check that JavaScript updates the counts correctly + 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): """Test that summary section shows correct counts""" response = self.client.get(reverse('incomplete_vorgaben')) self.assertContains(response, 'Zusammenfassung') - self.assertContains(response, '

1

', count=2) # No refs, no stichworte - self.assertContains(response, '

1

') # No text - self.assertContains(response, '

1

') # No checklistenfragen + self.assertContains(response, 'Ohne Referenzen') + self.assertContains(response, 'Ohne Stichworte') + self.assertContains(response, 'Ohne Text') + self.assertContains(response, 'Ohne Checklistenfragen') + self.assertContains(response, 'Gesamt: 4 unvollständige Vorgaben') def test_empty_lists_message(self): """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() response = self.client.get(reverse('incomplete_vorgaben')) - self.assertContains(response, 'Alle Vorgaben haben Referenzen.') - self.assertContains(response, 'Alle Vorgaben haben Stichworte.') - self.assertContains(response, 'Alle Vorgaben haben Kurz- oder Langtext.') - self.assertContains(response, 'Alle Vorgaben haben Checklistenfragen.') + self.assertContains(response, 'Alle Vorgaben sind vollständig!') + self.assertContains(response, 'Alle Vorgaben haben Referenzen, Stichworte, Text und Checklistenfragen.') def test_back_link(self): """Test that back link to standard list exists""" diff --git a/dokumente/views.py b/dokumente/views.py index 273df9c..d0ac07d 100644 --- a/dokumente/views.py +++ b/dokumente/views.py @@ -64,36 +64,41 @@ def is_staff_user(user): @user_passes_test(is_staff_user) def incomplete_vorgaben(request): """ - Show lists of incomplete Vorgaben: - 1. Ones with no references - 2. Ones with no Stichworte - 3. Ones without Kurz- or Langtext - 4. Ones without Checklistenfragen + 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') + all_vorgaben = Vorgabe.objects.all().select_related('dokument', 'thema').prefetch_related( + 'referenzen', 'stichworte', 'checklistenfragen', 'vorgabekurztext_set', 'vorgabelangtext_set' + ) - # 1. Vorgaben with no references - no_references = [v for v in all_vorgaben if not v.referenzen.exists()] - - # 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 = [] + # Build table data + vorgaben_data = [] for vorgabe in all_vorgaben: - has_kurztext = VorgabeKurztext.objects.filter(abschnitt=vorgabe).exists() - has_langtext = VorgabeLangtext.objects.filter(abschnitt=vorgabe).exists() - - if not has_kurztext and not has_langtext: - no_text.append(vorgabe) + 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 + }) - # 4. Vorgaben without Checklistenfragen - no_checklistenfragen = [v for v in all_vorgaben if not v.checklistenfragen.exists()] + # 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', { - 'no_references': no_references, - 'no_stichworte': no_stichworte, - 'no_text': no_text, - 'no_checklistenfragen': no_checklistenfragen, + 'vorgaben_data': vorgaben_data, })
Referenzen