Compare commits
21 Commits
feature/do
...
feature/vo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bca1bb3c7 | ||
|
|
1ce8eb15c0 | ||
|
|
4d2ffeea27 | ||
|
|
8860947d38 | ||
|
|
6df72c95cb | ||
| 2afada0bce | |||
|
|
a42a65b40f | ||
| 5609a735f4 | |||
| 6654779e67 | |||
| 7befde104d | |||
| 96819a7427 | |||
| a437af554b | |||
| 650fe0a87b | |||
|
|
ddf035c50f | ||
|
|
886baa163e | ||
|
|
1146506ca2 | ||
|
|
9610024739 | ||
|
|
c8755e4339 | ||
|
|
0bc1fe7413 | ||
|
|
8ce761c248 | ||
|
|
39a2021cc3 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -10,6 +10,8 @@ keys/
|
||||
.idea/
|
||||
|
||||
*.kate-swp
|
||||
|
||||
node_modules/
|
||||
package-lock.json
|
||||
package.json
|
||||
# Diagram cache directory
|
||||
media/diagram_cache/
|
||||
|
||||
@@ -1,3 +1,820 @@
|
||||
from django.test import TestCase
|
||||
from django.test import TestCase, TransactionTestCase
|
||||
from django.core.management import call_command
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import default_storage
|
||||
from unittest.mock import patch, Mock, MagicMock
|
||||
from io import StringIO
|
||||
import os
|
||||
import hashlib
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
# Create your tests here.
|
||||
from .models import AbschnittTyp, Textabschnitt
|
||||
from .utils import render_textabschnitte, md_table_to_html
|
||||
from diagramm_proxy.diagram_cache import (
|
||||
get_cached_diagram, compute_hash, get_cache_path, clear_cache
|
||||
)
|
||||
|
||||
|
||||
class AbschnittTypModelTest(TestCase):
|
||||
"""Test cases for AbschnittTyp model"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test data"""
|
||||
self.abschnitttyp = AbschnittTyp.objects.create(
|
||||
abschnitttyp="text"
|
||||
)
|
||||
|
||||
def test_abschnitttyp_creation(self):
|
||||
"""Test that AbschnittTyp is created correctly"""
|
||||
self.assertEqual(self.abschnitttyp.abschnitttyp, "text")
|
||||
|
||||
def test_abschnitttyp_str(self):
|
||||
"""Test string representation of AbschnittTyp"""
|
||||
self.assertEqual(str(self.abschnitttyp), "text")
|
||||
|
||||
def test_abschnitttyp_verbose_name_plural(self):
|
||||
"""Test verbose name plural"""
|
||||
self.assertEqual(
|
||||
AbschnittTyp._meta.verbose_name_plural,
|
||||
"Abschnitttypen"
|
||||
)
|
||||
|
||||
def test_abschnitttyp_primary_key(self):
|
||||
"""Test that abschnitttyp field is the primary key"""
|
||||
pk_field = AbschnittTyp._meta.pk
|
||||
self.assertEqual(pk_field.name, 'abschnitttyp')
|
||||
|
||||
def test_create_multiple_abschnitttypen(self):
|
||||
"""Test creating multiple AbschnittTyp objects"""
|
||||
types = ['liste ungeordnet', 'liste geordnet', 'tabelle', 'diagramm', 'code']
|
||||
for typ in types:
|
||||
AbschnittTyp.objects.create(abschnitttyp=typ)
|
||||
|
||||
self.assertEqual(AbschnittTyp.objects.count(), 6) # Including setUp type
|
||||
|
||||
|
||||
class TextabschnittModelTest(TestCase):
|
||||
"""Test cases for Textabschnitt abstract model using VorgabeLangtext"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test data"""
|
||||
from dokumente.models import Dokumententyp, Dokument, Vorgabe, VorgabeLangtext, Thema
|
||||
from datetime import date
|
||||
|
||||
self.typ_text = AbschnittTyp.objects.create(abschnitttyp="text")
|
||||
self.typ_code = AbschnittTyp.objects.create(abschnitttyp="code")
|
||||
|
||||
# Create required dokumente objects
|
||||
self.dokumententyp = Dokumententyp.objects.create(
|
||||
name="Test Type", verantwortliche_ve="TEST"
|
||||
)
|
||||
self.dokument = Dokument.objects.create(
|
||||
nummer="TEST-001",
|
||||
name="Test Doc",
|
||||
dokumententyp=self.dokumententyp,
|
||||
aktiv=True
|
||||
)
|
||||
self.thema = Thema.objects.create(name="Test Thema")
|
||||
self.vorgabe = Vorgabe.objects.create(
|
||||
dokument=self.dokument,
|
||||
nummer=1,
|
||||
order=1,
|
||||
thema=self.thema,
|
||||
titel="Test Vorgabe",
|
||||
gueltigkeit_von=date.today()
|
||||
)
|
||||
|
||||
def test_textabschnitt_creation(self):
|
||||
"""Test that Textabschnitt can be instantiated via concrete model"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
|
||||
abschnitt = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_text,
|
||||
inhalt="Test content",
|
||||
order=1
|
||||
)
|
||||
self.assertEqual(abschnitt.abschnitttyp, self.typ_text)
|
||||
self.assertEqual(abschnitt.inhalt, "Test content")
|
||||
self.assertEqual(abschnitt.order, 1)
|
||||
|
||||
def test_textabschnitt_default_order(self):
|
||||
"""Test that order defaults to 0"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
|
||||
abschnitt = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_text,
|
||||
inhalt="Test"
|
||||
)
|
||||
self.assertEqual(abschnitt.order, 0)
|
||||
|
||||
def test_textabschnitt_blank_fields(self):
|
||||
"""Test that abschnitttyp and inhalt can be blank/null"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
|
||||
abschnitt = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe
|
||||
)
|
||||
self.assertIsNone(abschnitt.abschnitttyp)
|
||||
self.assertIsNone(abschnitt.inhalt)
|
||||
|
||||
def test_textabschnitt_ordering(self):
|
||||
"""Test that Textabschnitte can be ordered"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
|
||||
abschnitt1 = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_text,
|
||||
inhalt="First",
|
||||
order=2
|
||||
)
|
||||
abschnitt2 = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_text,
|
||||
inhalt="Second",
|
||||
order=1
|
||||
)
|
||||
abschnitt3 = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_text,
|
||||
inhalt="Third",
|
||||
order=3
|
||||
)
|
||||
|
||||
ordered = VorgabeLangtext.objects.filter(abschnitt=self.vorgabe).order_by('order')
|
||||
self.assertEqual(list(ordered), [abschnitt2, abschnitt1, abschnitt3])
|
||||
|
||||
def test_textabschnitt_foreign_key_protection(self):
|
||||
"""Test that AbschnittTyp is protected from deletion"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
from django.db.models import ProtectedError
|
||||
|
||||
abschnitt = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_text,
|
||||
inhalt="Test"
|
||||
)
|
||||
|
||||
# Try to delete the AbschnittTyp
|
||||
with self.assertRaises(ProtectedError):
|
||||
self.typ_text.delete()
|
||||
|
||||
|
||||
class RenderTextabschnitteTest(TestCase):
|
||||
"""Test cases for render_textabschnitte function"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test data"""
|
||||
from dokumente.models import Dokumententyp, Dokument, Vorgabe, VorgabeLangtext, Thema
|
||||
from datetime import date
|
||||
|
||||
self.typ_text = AbschnittTyp.objects.create(abschnitttyp="text")
|
||||
self.typ_unordered = AbschnittTyp.objects.create(abschnitttyp="liste ungeordnet")
|
||||
self.typ_ordered = AbschnittTyp.objects.create(abschnitttyp="liste geordnet")
|
||||
self.typ_table = AbschnittTyp.objects.create(abschnitttyp="tabelle")
|
||||
self.typ_code = AbschnittTyp.objects.create(abschnitttyp="code")
|
||||
self.typ_diagram = AbschnittTyp.objects.create(abschnitttyp="diagramm")
|
||||
|
||||
# Create required dokumente objects
|
||||
self.dokumententyp = Dokumententyp.objects.create(
|
||||
name="Test Type", verantwortliche_ve="TEST"
|
||||
)
|
||||
self.dokument = Dokument.objects.create(
|
||||
nummer="TEST-001",
|
||||
name="Test Doc",
|
||||
dokumententyp=self.dokumententyp,
|
||||
aktiv=True
|
||||
)
|
||||
self.thema = Thema.objects.create(name="Test Thema")
|
||||
self.vorgabe = Vorgabe.objects.create(
|
||||
dokument=self.dokument,
|
||||
nummer=1,
|
||||
order=1,
|
||||
thema=self.thema,
|
||||
titel="Test Vorgabe",
|
||||
gueltigkeit_von=date.today()
|
||||
)
|
||||
|
||||
def test_render_empty_queryset(self):
|
||||
"""Test rendering an empty queryset"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
|
||||
result = render_textabschnitte(VorgabeLangtext.objects.none())
|
||||
self.assertEqual(result, [])
|
||||
|
||||
def test_render_text_markdown(self):
|
||||
"""Test rendering plain text with markdown"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
|
||||
abschnitt = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_text,
|
||||
inhalt="# Heading\n\nThis is **bold** text.",
|
||||
order=1
|
||||
)
|
||||
|
||||
result = render_textabschnitte(VorgabeLangtext.objects.filter(abschnitt=self.vorgabe))
|
||||
self.assertEqual(len(result), 1)
|
||||
typ, html = result[0]
|
||||
self.assertEqual(typ, "text")
|
||||
self.assertIn("<h1>Heading</h1>", html)
|
||||
self.assertIn("<strong>bold</strong>", html)
|
||||
|
||||
def test_render_text_with_footnotes(self):
|
||||
"""Test rendering text with footnotes"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
|
||||
abschnitt = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_text,
|
||||
inhalt="This is text[^1].\n\n[^1]: This is a footnote.",
|
||||
order=1
|
||||
)
|
||||
|
||||
result = render_textabschnitte(VorgabeLangtext.objects.filter(abschnitt=self.vorgabe))
|
||||
typ, html = result[0]
|
||||
self.assertIn("footnote", html.lower())
|
||||
|
||||
def test_render_unordered_list(self):
|
||||
"""Test rendering unordered list"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
|
||||
abschnitt = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_unordered,
|
||||
inhalt="Item 1\nItem 2\nItem 3",
|
||||
order=1
|
||||
)
|
||||
|
||||
result = render_textabschnitte(VorgabeLangtext.objects.filter(abschnitt=self.vorgabe))
|
||||
typ, html = result[0]
|
||||
self.assertEqual(typ, "liste ungeordnet")
|
||||
self.assertIn("<ul>", html)
|
||||
self.assertIn("<li>Item 1</li>", html)
|
||||
self.assertIn("<li>Item 2</li>", html)
|
||||
self.assertIn("<li>Item 3</li>", html)
|
||||
|
||||
def test_render_ordered_list(self):
|
||||
"""Test rendering ordered list"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
|
||||
abschnitt = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_ordered,
|
||||
inhalt="First item\nSecond item\nThird item",
|
||||
order=1
|
||||
)
|
||||
|
||||
result = render_textabschnitte(VorgabeLangtext.objects.filter(abschnitt=self.vorgabe))
|
||||
typ, html = result[0]
|
||||
self.assertEqual(typ, "liste geordnet")
|
||||
self.assertIn("<ol>", html)
|
||||
self.assertIn("<li>First item</li>", html)
|
||||
self.assertIn("<li>Second item</li>", html)
|
||||
self.assertIn("<li>Third item</li>", html)
|
||||
|
||||
def test_render_table(self):
|
||||
"""Test rendering table"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
|
||||
table_content = """| Header 1 | Header 2 |
|
||||
|----------|----------|
|
||||
| Cell 1 | Cell 2 |
|
||||
| Cell 3 | Cell 4 |"""
|
||||
|
||||
abschnitt = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_table,
|
||||
inhalt=table_content,
|
||||
order=1
|
||||
)
|
||||
|
||||
result = render_textabschnitte(VorgabeLangtext.objects.filter(abschnitt=self.vorgabe))
|
||||
typ, html = result[0]
|
||||
self.assertEqual(typ, "tabelle")
|
||||
self.assertIn('<table class="table table-bordered table-hover">', html)
|
||||
self.assertIn("<thead>", html)
|
||||
self.assertIn("<th>Header 1</th>", html)
|
||||
self.assertIn("<th>Header 2</th>", html)
|
||||
self.assertIn("<tbody>", html)
|
||||
self.assertIn("<td>Cell 1</td>", html)
|
||||
self.assertIn("<td>Cell 2</td>", html)
|
||||
|
||||
def test_render_code_block(self):
|
||||
"""Test rendering code block"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
|
||||
code_content = "def hello():\n print('Hello, World!')"
|
||||
|
||||
abschnitt = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_code,
|
||||
inhalt=code_content,
|
||||
order=1
|
||||
)
|
||||
|
||||
result = render_textabschnitte(VorgabeLangtext.objects.filter(abschnitt=self.vorgabe))
|
||||
typ, html = result[0]
|
||||
self.assertEqual(typ, "code")
|
||||
self.assertIn("<pre><code>", html)
|
||||
self.assertIn("</code></pre>", html)
|
||||
self.assertIn("hello", html)
|
||||
|
||||
@patch('abschnitte.utils.get_cached_diagram')
|
||||
def test_render_diagram_success(self, mock_get_cached):
|
||||
"""Test rendering diagram with successful caching"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
|
||||
mock_get_cached.return_value = "diagram_cache/plantuml/abc123.svg"
|
||||
|
||||
diagram_content = """plantuml
|
||||
@startuml
|
||||
Alice -> Bob: Hello
|
||||
@enduml"""
|
||||
|
||||
abschnitt = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_diagram,
|
||||
inhalt=diagram_content,
|
||||
order=1
|
||||
)
|
||||
|
||||
result = render_textabschnitte(VorgabeLangtext.objects.filter(abschnitt=self.vorgabe))
|
||||
typ, html = result[0]
|
||||
self.assertEqual(typ, "diagramm")
|
||||
self.assertIn('<img', html)
|
||||
self.assertIn('width="100%"', html)
|
||||
self.assertIn('diagram_cache/plantuml/abc123.svg', html)
|
||||
|
||||
# Verify get_cached_diagram was called correctly
|
||||
mock_get_cached.assert_called_once()
|
||||
args = mock_get_cached.call_args[0]
|
||||
self.assertEqual(args[0], "plantuml")
|
||||
self.assertIn("Alice -> Bob", args[1])
|
||||
|
||||
@patch('abschnitte.utils.get_cached_diagram')
|
||||
def test_render_diagram_with_options(self, mock_get_cached):
|
||||
"""Test rendering diagram with custom options"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
|
||||
mock_get_cached.return_value = "diagram_cache/mermaid/xyz789.svg"
|
||||
|
||||
diagram_content = """mermaid
|
||||
option: width="50%" height="300px"
|
||||
graph TD
|
||||
A-->B"""
|
||||
|
||||
abschnitt = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_diagram,
|
||||
inhalt=diagram_content,
|
||||
order=1
|
||||
)
|
||||
|
||||
result = render_textabschnitte(VorgabeLangtext.objects.filter(abschnitt=self.vorgabe))
|
||||
typ, html = result[0]
|
||||
self.assertIn('width="50%"', html)
|
||||
self.assertIn('height="300px"', html)
|
||||
|
||||
@patch('abschnitte.utils.get_cached_diagram')
|
||||
def test_render_diagram_error(self, mock_get_cached):
|
||||
"""Test rendering diagram when caching fails"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
|
||||
mock_get_cached.side_effect = Exception("Connection error")
|
||||
|
||||
diagram_content = """plantuml
|
||||
@startuml
|
||||
A -> B
|
||||
@enduml"""
|
||||
|
||||
abschnitt = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_diagram,
|
||||
inhalt=diagram_content,
|
||||
order=1
|
||||
)
|
||||
|
||||
result = render_textabschnitte(VorgabeLangtext.objects.filter(abschnitt=self.vorgabe))
|
||||
typ, html = result[0]
|
||||
self.assertIn("Error generating diagram", html)
|
||||
self.assertIn("Connection error", html)
|
||||
self.assertIn('class="text-danger"', html)
|
||||
|
||||
def test_render_multiple_abschnitte(self):
|
||||
"""Test rendering multiple Textabschnitte in order"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
|
||||
abschnitt1 = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_text,
|
||||
inhalt="First section",
|
||||
order=1
|
||||
)
|
||||
abschnitt2 = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_unordered,
|
||||
inhalt="Item 1\nItem 2",
|
||||
order=2
|
||||
)
|
||||
abschnitt3 = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_code,
|
||||
inhalt="print('hello')",
|
||||
order=3
|
||||
)
|
||||
|
||||
result = render_textabschnitte(
|
||||
VorgabeLangtext.objects.filter(abschnitt=self.vorgabe).order_by('order')
|
||||
)
|
||||
|
||||
self.assertEqual(len(result), 3)
|
||||
self.assertEqual(result[0][0], "text")
|
||||
self.assertEqual(result[1][0], "liste ungeordnet")
|
||||
self.assertEqual(result[2][0], "code")
|
||||
|
||||
def test_render_abschnitt_without_type(self):
|
||||
"""Test rendering Textabschnitt without AbschnittTyp"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
|
||||
abschnitt = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=None,
|
||||
inhalt="Content without type",
|
||||
order=1
|
||||
)
|
||||
|
||||
result = render_textabschnitte(VorgabeLangtext.objects.filter(abschnitt=self.vorgabe))
|
||||
typ, html = result[0]
|
||||
self.assertEqual(typ, '')
|
||||
self.assertIn("Content without type", html)
|
||||
|
||||
def test_render_abschnitt_with_empty_content(self):
|
||||
"""Test rendering Textabschnitt with empty content"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
|
||||
abschnitt = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_text,
|
||||
inhalt=None,
|
||||
order=1
|
||||
)
|
||||
|
||||
result = render_textabschnitte(VorgabeLangtext.objects.filter(abschnitt=self.vorgabe))
|
||||
self.assertEqual(len(result), 1)
|
||||
typ, html = result[0]
|
||||
self.assertEqual(typ, "text")
|
||||
|
||||
|
||||
class MdTableToHtmlTest(TestCase):
|
||||
"""Test cases for md_table_to_html function"""
|
||||
|
||||
def test_simple_table(self):
|
||||
"""Test converting a simple markdown table to HTML"""
|
||||
md = """| Name | Age |
|
||||
|------|-----|
|
||||
| John | 30 |
|
||||
| Jane | 25 |"""
|
||||
|
||||
html = md_table_to_html(md)
|
||||
|
||||
self.assertIn('<table class="table table-bordered table-hover">', html)
|
||||
self.assertIn("<thead>", html)
|
||||
self.assertIn("<th>Name</th>", html)
|
||||
self.assertIn("<th>Age</th>", html)
|
||||
self.assertIn("<tbody>", html)
|
||||
self.assertIn("<td>John</td>", html)
|
||||
self.assertIn("<td>30</td>", html)
|
||||
self.assertIn("<td>Jane</td>", html)
|
||||
self.assertIn("<td>25</td>", html)
|
||||
|
||||
def test_table_with_multiple_rows(self):
|
||||
"""Test table with multiple rows"""
|
||||
md = """| A | B | C |
|
||||
|---|---|---|
|
||||
| 1 | 2 | 3 |
|
||||
| 4 | 5 | 6 |
|
||||
| 7 | 8 | 9 |"""
|
||||
|
||||
html = md_table_to_html(md)
|
||||
|
||||
self.assertEqual(html.count("<tr>"), 4) # 1 header + 3 body rows
|
||||
self.assertEqual(html.count("<td>"), 9) # 3x3 cells
|
||||
self.assertEqual(html.count("<th>"), 3) # 3 headers
|
||||
|
||||
def test_table_with_spaces(self):
|
||||
"""Test table with extra spaces"""
|
||||
md = """ | Header 1 | Header 2 |
|
||||
| --------- | ---------- |
|
||||
| Value 1 | Value 2 | """
|
||||
|
||||
html = md_table_to_html(md)
|
||||
|
||||
self.assertIn("<th>Header 1</th>", html)
|
||||
self.assertIn("<th>Header 2</th>", html)
|
||||
self.assertIn("<td>Value 1</td>", html)
|
||||
self.assertIn("<td>Value 2</td>", html)
|
||||
|
||||
def test_table_with_empty_cells(self):
|
||||
"""Test table with empty cells"""
|
||||
md = """| Col1 | Col2 | Col3 |
|
||||
|------|------|------|
|
||||
| A | | C |
|
||||
| | B | |"""
|
||||
|
||||
html = md_table_to_html(md)
|
||||
|
||||
self.assertIn("<td>A</td>", html)
|
||||
self.assertIn("<td></td>", html)
|
||||
self.assertIn("<td>C</td>", html)
|
||||
self.assertIn("<td>B</td>", html)
|
||||
|
||||
def test_table_insufficient_lines(self):
|
||||
"""Test that ValueError is raised for insufficient lines"""
|
||||
md = """| Header |"""
|
||||
|
||||
with self.assertRaises(ValueError) as context:
|
||||
md_table_to_html(md)
|
||||
|
||||
self.assertIn("at least header + separator", str(context.exception))
|
||||
|
||||
def test_table_empty_string(self):
|
||||
"""Test that ValueError is raised for empty string"""
|
||||
with self.assertRaises(ValueError):
|
||||
md_table_to_html("")
|
||||
|
||||
def test_table_only_whitespace(self):
|
||||
"""Test that ValueError is raised for only whitespace"""
|
||||
with self.assertRaises(ValueError):
|
||||
md_table_to_html(" \n \n ")
|
||||
|
||||
|
||||
class DiagramCacheTest(TestCase):
|
||||
"""Test cases for diagram caching functionality"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test environment"""
|
||||
# Create a temporary directory for testing
|
||||
self.test_media_root = tempfile.mkdtemp()
|
||||
self.original_media_root = settings.MEDIA_ROOT
|
||||
settings.MEDIA_ROOT = self.test_media_root
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up test environment"""
|
||||
# Restore original settings
|
||||
settings.MEDIA_ROOT = self.original_media_root
|
||||
# Remove test directory
|
||||
if os.path.exists(self.test_media_root):
|
||||
shutil.rmtree(self.test_media_root)
|
||||
|
||||
def test_compute_hash(self):
|
||||
"""Test that compute_hash generates consistent SHA256 hashes"""
|
||||
content1 = "test content"
|
||||
content2 = "test content"
|
||||
content3 = "different content"
|
||||
|
||||
hash1 = compute_hash(content1)
|
||||
hash2 = compute_hash(content2)
|
||||
hash3 = compute_hash(content3)
|
||||
|
||||
# Same content should produce same hash
|
||||
self.assertEqual(hash1, hash2)
|
||||
# Different content should produce different hash
|
||||
self.assertNotEqual(hash1, hash3)
|
||||
# Hash should be 64 characters (SHA256 hex)
|
||||
self.assertEqual(len(hash1), 64)
|
||||
|
||||
def test_get_cache_path(self):
|
||||
"""Test that get_cache_path generates correct paths"""
|
||||
diagram_type = "plantuml"
|
||||
content_hash = "abc123"
|
||||
|
||||
path = get_cache_path(diagram_type, content_hash)
|
||||
|
||||
self.assertIn("diagram_cache", path)
|
||||
self.assertIn("plantuml", path)
|
||||
self.assertIn("abc123.svg", path)
|
||||
|
||||
@patch('diagramm_proxy.diagram_cache.requests.post')
|
||||
@patch('diagramm_proxy.diagram_cache.default_storage')
|
||||
def test_get_cached_diagram_miss(self, mock_storage, mock_post):
|
||||
"""Test diagram generation on cache miss"""
|
||||
# Setup mocks
|
||||
mock_storage.exists.return_value = False
|
||||
mock_storage.path.return_value = os.path.join(
|
||||
self.test_media_root, 'diagram_cache/plantuml/test.svg'
|
||||
)
|
||||
mock_response = Mock()
|
||||
mock_response.content = b'<svg>test</svg>'
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
diagram_content = "@startuml\nA -> B\n@enduml"
|
||||
|
||||
# Call function
|
||||
result = get_cached_diagram("plantuml", diagram_content)
|
||||
|
||||
# Verify POST request was made
|
||||
mock_post.assert_called_once()
|
||||
call_args = mock_post.call_args
|
||||
# Check URL in positional args (first argument)
|
||||
self.assertIn("plantuml/svg", call_args[0][0])
|
||||
|
||||
# Verify storage.save was called
|
||||
mock_storage.save.assert_called_once()
|
||||
|
||||
@patch('diagramm_proxy.diagram_cache.default_storage')
|
||||
def test_get_cached_diagram_hit(self, mock_storage):
|
||||
"""Test diagram retrieval on cache hit"""
|
||||
# Setup mock - diagram exists in cache
|
||||
mock_storage.exists.return_value = True
|
||||
|
||||
diagram_content = "@startuml\nA -> B\n@enduml"
|
||||
|
||||
# Call function
|
||||
result = get_cached_diagram("plantuml", diagram_content)
|
||||
|
||||
# Verify no save was attempted (cache hit)
|
||||
mock_storage.save.assert_not_called()
|
||||
|
||||
# Verify result contains expected path elements
|
||||
self.assertIn("diagram_cache", result)
|
||||
self.assertIn("plantuml", result)
|
||||
self.assertIn(".svg", result)
|
||||
|
||||
@patch('diagramm_proxy.diagram_cache.requests.post')
|
||||
@patch('diagramm_proxy.diagram_cache.default_storage')
|
||||
def test_get_cached_diagram_request_error(self, mock_storage, mock_post):
|
||||
"""Test that request errors are properly raised"""
|
||||
import requests
|
||||
|
||||
mock_storage.exists.return_value = False
|
||||
mock_storage.path.return_value = os.path.join(
|
||||
self.test_media_root, 'diagram_cache/plantuml/test.svg'
|
||||
)
|
||||
mock_post.side_effect = requests.RequestException("Connection error")
|
||||
|
||||
with self.assertRaises(requests.RequestException):
|
||||
get_cached_diagram("plantuml", "@startuml\nA -> B\n@enduml")
|
||||
|
||||
@patch('diagramm_proxy.diagram_cache.default_storage')
|
||||
def test_clear_cache_specific_type(self, mock_storage):
|
||||
"""Test clearing cache for specific diagram type"""
|
||||
# Create real test cache structure for this test
|
||||
cache_dir = os.path.join(self.test_media_root, 'diagram_cache', 'plantuml')
|
||||
os.makedirs(cache_dir, exist_ok=True)
|
||||
|
||||
# Create test files
|
||||
test_file1 = os.path.join(cache_dir, 'test1.svg')
|
||||
test_file2 = os.path.join(cache_dir, 'test2.svg')
|
||||
open(test_file1, 'w').close()
|
||||
open(test_file2, 'w').close()
|
||||
|
||||
# Mock storage methods
|
||||
mock_storage.exists.return_value = True
|
||||
mock_storage.path.return_value = cache_dir
|
||||
|
||||
# Clear cache
|
||||
clear_cache('plantuml')
|
||||
|
||||
# Verify files are deleted
|
||||
self.assertFalse(os.path.exists(test_file1))
|
||||
self.assertFalse(os.path.exists(test_file2))
|
||||
|
||||
@patch('diagramm_proxy.diagram_cache.default_storage')
|
||||
def test_clear_cache_all_types(self, mock_storage):
|
||||
"""Test clearing cache for all diagram types"""
|
||||
# Create real test cache structure with multiple types
|
||||
cache_root = os.path.join(self.test_media_root, 'diagram_cache')
|
||||
for diagram_type in ['plantuml', 'mermaid', 'graphviz']:
|
||||
cache_dir = os.path.join(cache_root, diagram_type)
|
||||
os.makedirs(cache_dir, exist_ok=True)
|
||||
test_file = os.path.join(cache_dir, 'test.svg')
|
||||
open(test_file, 'w').close()
|
||||
|
||||
# Mock storage methods
|
||||
mock_storage.exists.return_value = True
|
||||
mock_storage.path.return_value = cache_root
|
||||
|
||||
# Clear all cache
|
||||
clear_cache()
|
||||
|
||||
# Verify all files are deleted
|
||||
for diagram_type in ['plantuml', 'mermaid', 'graphviz']:
|
||||
test_file = os.path.join(cache_root, diagram_type, 'test.svg')
|
||||
self.assertFalse(os.path.exists(test_file))
|
||||
|
||||
|
||||
class ClearDiagramCacheCommandTest(TestCase):
|
||||
"""Test cases for clear_diagram_cache management command"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test environment"""
|
||||
self.test_media_root = tempfile.mkdtemp()
|
||||
self.original_media_root = settings.MEDIA_ROOT
|
||||
settings.MEDIA_ROOT = self.test_media_root
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up test environment"""
|
||||
settings.MEDIA_ROOT = self.original_media_root
|
||||
if os.path.exists(self.test_media_root):
|
||||
shutil.rmtree(self.test_media_root)
|
||||
|
||||
def test_command_without_type(self):
|
||||
"""Test running command without specifying type"""
|
||||
# Create test cache
|
||||
cache_dir = os.path.join(self.test_media_root, 'diagram_cache', 'plantuml')
|
||||
os.makedirs(cache_dir, exist_ok=True)
|
||||
test_file = os.path.join(cache_dir, 'test.svg')
|
||||
open(test_file, 'w').close()
|
||||
|
||||
# Run command
|
||||
out = StringIO()
|
||||
call_command('clear_diagram_cache', stdout=out)
|
||||
|
||||
# Check output
|
||||
self.assertIn('Clearing all diagram caches', out.getvalue())
|
||||
self.assertIn('Cache cleared successfully', out.getvalue())
|
||||
|
||||
def test_command_with_type(self):
|
||||
"""Test running command with specific diagram type"""
|
||||
# Create test cache
|
||||
cache_dir = os.path.join(self.test_media_root, 'diagram_cache', 'mermaid')
|
||||
os.makedirs(cache_dir, exist_ok=True)
|
||||
test_file = os.path.join(cache_dir, 'test.svg')
|
||||
open(test_file, 'w').close()
|
||||
|
||||
# Run command
|
||||
out = StringIO()
|
||||
call_command('clear_diagram_cache', type='mermaid', stdout=out)
|
||||
|
||||
# Check output
|
||||
self.assertIn('Clearing cache for mermaid', out.getvalue())
|
||||
self.assertIn('Cache cleared successfully', out.getvalue())
|
||||
|
||||
|
||||
class IntegrationTest(TestCase):
|
||||
"""Integration tests with actual dokumente models"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test data using dokumente models"""
|
||||
from dokumente.models import (
|
||||
Dokumententyp, Dokument, Vorgabe, VorgabeLangtext, Thema
|
||||
)
|
||||
from datetime import date
|
||||
|
||||
# Create required objects
|
||||
self.dokumententyp = Dokumententyp.objects.create(
|
||||
name="Test Policy",
|
||||
verantwortliche_ve="TEST"
|
||||
)
|
||||
|
||||
self.dokument = Dokument.objects.create(
|
||||
nummer="TEST-001",
|
||||
name="Test Document",
|
||||
dokumententyp=self.dokumententyp,
|
||||
aktiv=True
|
||||
)
|
||||
|
||||
self.thema = Thema.objects.create(name="Test Thema")
|
||||
self.vorgabe = Vorgabe.objects.create(
|
||||
dokument=self.dokument,
|
||||
nummer=1,
|
||||
order=1,
|
||||
thema=self.thema,
|
||||
titel="Test Vorgabe",
|
||||
gueltigkeit_von=date.today()
|
||||
)
|
||||
|
||||
# Create AbschnittTypen
|
||||
self.typ_text = AbschnittTyp.objects.create(abschnitttyp="text")
|
||||
|
||||
# Create VorgabeLangtext (which inherits from Textabschnitt)
|
||||
self.langtext = VorgabeLangtext.objects.create(
|
||||
abschnitt=self.vorgabe,
|
||||
abschnitttyp=self.typ_text,
|
||||
inhalt="# Test\n\nThis is a **test** vorgabe.",
|
||||
order=1
|
||||
)
|
||||
|
||||
def test_render_vorgabe_langtext(self):
|
||||
"""Test rendering VorgabeLangtext through render_textabschnitte"""
|
||||
from dokumente.models import VorgabeLangtext
|
||||
|
||||
result = render_textabschnitte(
|
||||
VorgabeLangtext.objects.filter(abschnitt=self.vorgabe).order_by('order')
|
||||
)
|
||||
|
||||
self.assertEqual(len(result), 1)
|
||||
typ, html = result[0]
|
||||
self.assertEqual(typ, "text")
|
||||
self.assertIn("<h1>Test</h1>", html)
|
||||
self.assertIn("<strong>test</strong>", html)
|
||||
self.assertIn("vorgabe", html)
|
||||
|
||||
def test_textabschnitt_inheritance(self):
|
||||
"""Test that VorgabeLangtext properly inherits Textabschnitt fields"""
|
||||
self.assertEqual(self.langtext.abschnitttyp, self.typ_text)
|
||||
self.assertIn("test", self.langtext.inhalt)
|
||||
self.assertEqual(self.langtext.order, 1)
|
||||
|
||||
@@ -25,7 +25,7 @@ spec:
|
||||
mountPath: /data
|
||||
containers:
|
||||
- name: web
|
||||
image: git.baumann.gr/adebaumann/vui:0.933
|
||||
image: git.baumann.gr/adebaumann/vui:0.938
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
|
||||
Binary file not shown.
BIN
data/db.sqlite3
BIN
data/db.sqlite3
Binary file not shown.
@@ -4,6 +4,7 @@ from nested_admin import NestedStackedInline, NestedModelAdmin, NestedTabularInl
|
||||
from django import forms
|
||||
from mptt.forms import TreeNodeMultipleChoiceField
|
||||
from mptt.admin import DraggableMPTTAdmin
|
||||
from adminsortable2.admin import SortableInlineAdminMixin, SortableAdminBase
|
||||
|
||||
# Register your models here.
|
||||
from .models import *
|
||||
@@ -36,7 +37,7 @@ class VorgabeKurztextInline(NestedTabularInline):
|
||||
classes = ['collapse']
|
||||
#inline=inhalt
|
||||
|
||||
class VorgabeLangtextInline(NestedStackedInline):
|
||||
class VorgabeLangtextInline(NestedTabularInline):
|
||||
model=VorgabeLangtext
|
||||
extra=0
|
||||
sortable_field_name = "order"
|
||||
@@ -61,15 +62,16 @@ class EinleitungInline(NestedTabularInline):
|
||||
classes = ['collapse']
|
||||
|
||||
class VorgabeForm(forms.ModelForm):
|
||||
# referenzen = TreeNodeMultipleChoiceField(queryset=Referenz.objects.all(), required=False)
|
||||
referenzen = TreeNodeMultipleChoiceField(queryset=Referenz.objects.all(), required=False)
|
||||
class Meta:
|
||||
model = Vorgabe
|
||||
fields = '__all__'
|
||||
|
||||
class VorgabeInline(NestedTabularInline): # or StackedInline for more vertical layout
|
||||
class VorgabeInline(SortableInlineAdminMixin, NestedTabularInline): # or StackedInline for more vertical layout
|
||||
model = Vorgabe
|
||||
form = VorgabeForm
|
||||
extra = 0
|
||||
sortable_field_name = "order" # Add this - make sure your Vorgabe model has an 'order' field
|
||||
#show_change_link = True
|
||||
inlines = [VorgabeKurztextInline,VorgabeLangtextInline,ChecklistenfragenInline]
|
||||
autocomplete_fields = ['stichworte','referenzen','relevanz']
|
||||
@@ -77,7 +79,7 @@ class VorgabeInline(NestedTabularInline): # or StackedInline for more vertical
|
||||
list_filter=['stichworte']
|
||||
#classes=["collapse"]
|
||||
|
||||
class StichworterklaerungInline(NestedStackedInline):
|
||||
class StichworterklaerungInline(NestedTabularInline):
|
||||
model=Stichworterklaerung
|
||||
extra=0
|
||||
sortable_field_name = "order"
|
||||
@@ -100,7 +102,7 @@ class PersonAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(Dokument)
|
||||
class DokumentAdmin(NestedModelAdmin):
|
||||
class DokumentAdmin(SortableAdminBase, NestedModelAdmin):
|
||||
actions_on_top=True
|
||||
inlines = [EinleitungInline,GeltungsbereichInline,VorgabeInline]
|
||||
#filter_horizontal=['autoren','pruefende']
|
||||
@@ -117,11 +119,39 @@ class DokumentAdmin(NestedModelAdmin):
|
||||
|
||||
#admin.site.register(Stichwort)
|
||||
|
||||
@admin.register(VorgabenTable)
|
||||
class VorgabenTableAdmin(admin.ModelAdmin):
|
||||
list_display = ['order', 'nummer', 'dokument', 'thema', 'titel', 'gueltigkeit_von', 'gueltigkeit_bis']
|
||||
list_display_links = ['dokument']
|
||||
list_editable = ['order', 'nummer', 'thema', 'titel', 'gueltigkeit_von', 'gueltigkeit_bis']
|
||||
list_filter = ['dokument', 'thema', 'gueltigkeit_von', 'gueltigkeit_bis']
|
||||
search_fields = ['nummer', 'titel', 'dokument__nummer', 'dokument__name']
|
||||
autocomplete_fields = ['dokument', 'thema', 'stichworte', 'referenzen', 'relevanz']
|
||||
ordering = ['order']
|
||||
list_per_page = 100
|
||||
|
||||
fieldsets = (
|
||||
('Grunddaten', {
|
||||
'fields': ('order', 'nummer', 'dokument', 'thema', 'titel')
|
||||
}),
|
||||
('Gültigkeit', {
|
||||
'fields': ('gueltigkeit_von', 'gueltigkeit_bis')
|
||||
}),
|
||||
('Verknüpfungen', {
|
||||
'fields': ('referenzen', 'stichworte', 'relevanz'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
|
||||
@admin.register(Thema)
|
||||
class ThemaAdmin(admin.ModelAdmin):
|
||||
search_fields = ['name']
|
||||
ordering = ['name']
|
||||
|
||||
admin.site.register(Checklistenfrage)
|
||||
admin.site.register(Dokumententyp)
|
||||
#admin.site.register(Person)
|
||||
admin.site.register(Thema)
|
||||
#admin.site.register(Referenz, DraggableM§PTTAdmin)
|
||||
admin.site.register(Vorgabe)
|
||||
|
||||
|
||||
#admin.site.register(Changelog)
|
||||
|
||||
19
dokumente/migrations/0008_dokument_aktiv.py
Normal file
19
dokumente/migrations/0008_dokument_aktiv.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.2.5 on 2025-10-27 19:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dokumente', '0007_alter_changelog_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='dokument',
|
||||
name='aktiv',
|
||||
field=models.BooleanField(blank=True, default=False),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.5 on 2025-10-28 14:51
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dokumente', '0008_dokument_aktiv'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='vorgabe',
|
||||
options={'ordering': ['order'], 'verbose_name_plural': 'Vorgaben'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vorgabe',
|
||||
name='order',
|
||||
field=models.IntegerField(default=0),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@@ -47,6 +47,7 @@ class Dokument(models.Model):
|
||||
gueltigkeit_bis = models.DateField(null=True, blank=True)
|
||||
signatur_cso = models.CharField(max_length=255, blank=True)
|
||||
anhaenge = models.TextField(blank=True)
|
||||
aktiv = models.BooleanField(blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.nummer} – {self.name}"
|
||||
@@ -56,6 +57,7 @@ class Dokument(models.Model):
|
||||
verbose_name="Dokument"
|
||||
|
||||
class Vorgabe(models.Model):
|
||||
order = models.IntegerField()
|
||||
nummer = models.IntegerField()
|
||||
dokument = models.ForeignKey(Dokument, on_delete=models.CASCADE, related_name='vorgaben')
|
||||
thema = models.ForeignKey(Thema, on_delete=models.PROTECT)
|
||||
@@ -86,7 +88,7 @@ class Vorgabe(models.Model):
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural="Vorgaben"
|
||||
|
||||
ordering = ['order']
|
||||
|
||||
class VorgabeLangtext(Textabschnitt):
|
||||
abschnitt=models.ForeignKey(Vorgabe,on_delete=models.CASCADE)
|
||||
@@ -123,6 +125,12 @@ class Checklistenfrage(models.Model):
|
||||
verbose_name_plural="Fragen für Checkliste"
|
||||
verbose_name="Frage für Checkliste"
|
||||
|
||||
class VorgabenTable(Vorgabe):
|
||||
class Meta:
|
||||
proxy = True
|
||||
verbose_name = "Vorgabe (Tabellenansicht)"
|
||||
verbose_name_plural = "Vorgaben (Tabellenansicht)"
|
||||
|
||||
class Changelog(models.Model):
|
||||
dokument = models.ForeignKey(Dokument, on_delete=models.CASCADE, related_name='changelog')
|
||||
autoren = models.ManyToManyField(Person)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<!-- Autoren, Prüfende etc. -->
|
||||
<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 }}</p>
|
||||
<p><strong>Gültigkeit:</strong> {{ standard.gueltigkeit_von }} bis {{ standard.gueltigkeit_bis|default_if_none:"auf weiteres" }}</p>
|
||||
|
||||
<!-- Start Einleitung -->
|
||||
{% if standard.einleitung_html %}
|
||||
|
||||
@@ -114,7 +114,8 @@ class DokumentModelTest(TestCase):
|
||||
name="Security Policy",
|
||||
gueltigkeit_von=date.today(),
|
||||
signatur_cso="CSO-123",
|
||||
anhaenge="Appendix A, B"
|
||||
anhaenge="Appendix A, B",
|
||||
aktiv=True
|
||||
)
|
||||
self.dokument.autoren.add(self.autor)
|
||||
self.dokument.pruefende.add(self.pruefer)
|
||||
@@ -124,6 +125,7 @@ class DokumentModelTest(TestCase):
|
||||
self.assertEqual(self.dokument.nummer, "DOC-001")
|
||||
self.assertEqual(self.dokument.name, "Security Policy")
|
||||
self.assertEqual(self.dokument.dokumententyp, self.dokumententyp)
|
||||
self.assertEqual(self.dokument.aktiv, True)
|
||||
|
||||
def test_dokument_str(self):
|
||||
"""Test string representation of Dokument"""
|
||||
@@ -139,7 +141,8 @@ class DokumentModelTest(TestCase):
|
||||
dokument = Dokument.objects.create(
|
||||
nummer="DOC-002",
|
||||
dokumententyp=self.dokumententyp,
|
||||
name="Test Document"
|
||||
name="Test Document",
|
||||
aktiv=True
|
||||
)
|
||||
self.assertIsNone(dokument.gueltigkeit_von)
|
||||
self.assertIsNone(dokument.gueltigkeit_bis)
|
||||
@@ -158,10 +161,12 @@ class VorgabeModelTest(TestCase):
|
||||
self.dokument = Dokument.objects.create(
|
||||
nummer="R01234",
|
||||
dokumententyp=self.dokumententyp,
|
||||
name="IT Standard"
|
||||
name="IT Standard",
|
||||
aktiv=True
|
||||
)
|
||||
self.thema = Thema.objects.create(name="Security")
|
||||
self.vorgabe = Vorgabe.objects.create(
|
||||
order=1,
|
||||
nummer=1,
|
||||
dokument=self.dokument,
|
||||
thema=self.thema,
|
||||
@@ -171,6 +176,7 @@ class VorgabeModelTest(TestCase):
|
||||
|
||||
def test_vorgabe_creation(self):
|
||||
"""Test that Vorgabe is created correctly"""
|
||||
self.assertEqual(self.vorgabe.order, 1)
|
||||
self.assertEqual(self.vorgabe.nummer, 1)
|
||||
self.assertEqual(self.vorgabe.dokument, self.dokument)
|
||||
self.assertEqual(self.vorgabe.thema, self.thema)
|
||||
@@ -193,6 +199,7 @@ class VorgabeModelTest(TestCase):
|
||||
def test_get_status_future(self):
|
||||
"""Test get_status returns 'future' for future vorgabe"""
|
||||
future_vorgabe = Vorgabe.objects.create(
|
||||
order=2,
|
||||
nummer=2,
|
||||
dokument=self.dokument,
|
||||
thema=self.thema,
|
||||
@@ -205,6 +212,7 @@ class VorgabeModelTest(TestCase):
|
||||
def test_get_status_expired(self):
|
||||
"""Test get_status returns 'expired' for expired vorgabe"""
|
||||
expired_vorgabe = Vorgabe.objects.create(
|
||||
order=3,
|
||||
nummer=3,
|
||||
dokument=self.dokument,
|
||||
thema=self.thema,
|
||||
@@ -218,6 +226,7 @@ class VorgabeModelTest(TestCase):
|
||||
def test_get_status_verbose(self):
|
||||
"""Test get_status with verbose=True"""
|
||||
future_vorgabe = Vorgabe.objects.create(
|
||||
order=4,
|
||||
nummer=4,
|
||||
dokument=self.dokument,
|
||||
thema=self.thema,
|
||||
@@ -231,6 +240,7 @@ class VorgabeModelTest(TestCase):
|
||||
def test_get_status_with_custom_check_date(self):
|
||||
"""Test get_status with custom check_date"""
|
||||
vorgabe = Vorgabe.objects.create(
|
||||
order=5,
|
||||
nummer=5,
|
||||
dokument=self.dokument,
|
||||
thema=self.thema,
|
||||
@@ -254,10 +264,12 @@ class VorgabeTextAbschnitteTest(TestCase):
|
||||
self.dokument = Dokument.objects.create(
|
||||
nummer="R01234",
|
||||
dokumententyp=self.dokumententyp,
|
||||
name="Test Standard"
|
||||
name="Test Standard",
|
||||
aktiv=True
|
||||
)
|
||||
self.thema = Thema.objects.create(name="Testing")
|
||||
self.vorgabe = Vorgabe.objects.create(
|
||||
order=1,
|
||||
nummer=1,
|
||||
dokument=self.dokument,
|
||||
thema=self.thema,
|
||||
@@ -302,7 +314,8 @@ class DokumentTextAbschnitteTest(TestCase):
|
||||
self.dokument = Dokument.objects.create(
|
||||
nummer="POL-001",
|
||||
dokumententyp=self.dokumententyp,
|
||||
name="Test Policy"
|
||||
name="Test Policy",
|
||||
aktiv=True
|
||||
)
|
||||
self.abschnitttyp = AbschnittTyp.objects.create(
|
||||
abschnitttyp="Paragraph"
|
||||
@@ -342,10 +355,12 @@ class ChecklistenfrageModelTest(TestCase):
|
||||
self.dokument = Dokument.objects.create(
|
||||
nummer="QA-001",
|
||||
dokumententyp=self.dokumententyp,
|
||||
name="QA Standard"
|
||||
name="QA Standard",
|
||||
aktiv=True
|
||||
)
|
||||
self.thema = Thema.objects.create(name="Quality")
|
||||
self.vorgabe = Vorgabe.objects.create(
|
||||
order=1,
|
||||
nummer=1,
|
||||
dokument=self.dokument,
|
||||
thema=self.thema,
|
||||
@@ -382,7 +397,8 @@ class ChangelogModelTest(TestCase):
|
||||
self.dokument = Dokument.objects.create(
|
||||
nummer="R01234",
|
||||
dokumententyp=self.dokumententyp,
|
||||
name="IT Standard"
|
||||
name="IT Standard",
|
||||
aktiv=True
|
||||
)
|
||||
self.autor = Person.objects.create(
|
||||
name="John Doe",
|
||||
@@ -419,10 +435,12 @@ class ViewsTestCase(TestCase):
|
||||
self.dokument = Dokument.objects.create(
|
||||
nummer="R01234",
|
||||
dokumententyp=self.dokumententyp,
|
||||
name="Test Standard"
|
||||
name="Test Standard",
|
||||
aktiv=True
|
||||
)
|
||||
self.thema = Thema.objects.create(name="Testing")
|
||||
self.vorgabe = Vorgabe.objects.create(
|
||||
order=1,
|
||||
nummer=1,
|
||||
dokument=self.dokument,
|
||||
thema=self.thema,
|
||||
|
||||
@@ -28,6 +28,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.931</div>
|
||||
<div>VorgabenUI v0.936</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,7 +6,7 @@ import datetime
|
||||
import pprint
|
||||
|
||||
def startseite(request):
|
||||
standards=list(Dokument.objects.all())
|
||||
standards=list(Dokument.objects.filter(aktiv=True))
|
||||
return render(request, 'startseite.html', {"dokumente":standards,})
|
||||
|
||||
def search(request):
|
||||
|
||||
@@ -13,4 +13,4 @@ class ReferenzerklaerungInline(NestedStackedInline):
|
||||
class ReferenzAdmin(NestedModelAdmin):
|
||||
inlines=[ReferenzerklaerungInline]
|
||||
list_display =['Path']
|
||||
search_fields=("referenz",)
|
||||
search_fields=("referenz","path")
|
||||
|
||||
@@ -6,6 +6,7 @@ charset-normalizer==3.4.3
|
||||
curtsies==0.4.3
|
||||
cwcwidth==0.1.10
|
||||
Django==5.2.5
|
||||
django-admin-sortable2==2.2.8
|
||||
django-js-asset==3.1.2
|
||||
django-mptt==0.17.0
|
||||
django-mptt-admin==2.8.0
|
||||
|
||||
@@ -1,14 +1,40 @@
|
||||
/* Style each Vorgabe inline block */
|
||||
.djn-dynamic-form-Standards-vorgabe {
|
||||
border: 2px solid #ccc;
|
||||
.djn-dynamic-form-Standards-vorgabe,
|
||||
.djn-dynamic-form-dokumente-vorgabe {
|
||||
border: 3px solid #2c5aa0;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 50px;
|
||||
background-color: #f9f9f9;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* Make Vorgabe title prominent */
|
||||
.djn-dynamic-form-Standards-vorgabe > h3,
|
||||
.djn-dynamic-form-dokumente-vorgabe > h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #2c5aa0;
|
||||
margin: -15px -15px 15px -15px;
|
||||
padding: 12px 15px;
|
||||
background: linear-gradient(to bottom, #e8f0f8, #d4e4f3);
|
||||
border-bottom: 2px solid #2c5aa0;
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
/* Make Vorgabe identifier in tabular view prominent */
|
||||
tbody.djn-dynamic-form-Standards-vorgabe td.original,
|
||||
tbody.djn-dynamic-form-dokumente-vorgabe td.original,
|
||||
tbody.djn-dynamic-form-Standards-vorgabe td.original p,
|
||||
tbody.djn-dynamic-form-dokumente-vorgabe td.original p {
|
||||
font-size: 16px !important;
|
||||
font-weight: 700 !important;
|
||||
color: #2c5aa0 !important;
|
||||
}
|
||||
|
||||
/* Optional: Slight padding for inner fieldsets (e.g., Langtext/Kurztext inlines) */
|
||||
.djn-dynamic-form-Standards-vorgabe .inline-related {
|
||||
.djn-dynamic-form-Standards-vorgabe .inline-related,
|
||||
.djn-dynamic-form-dokumente-vorgabe .inline-related {
|
||||
margin-top: 10px;
|
||||
padding-left: 10px;
|
||||
border-left: 2px dashed #ccc;
|
||||
|
||||
Reference in New Issue
Block a user