Compare commits
25 Commits
consolidat
...
0d0199ca62
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d0199ca62 | |||
| 5f58d660c0 | |||
| e84f25ca1d | |||
| dfb8eeef97 | |||
| 0225fb3396 | |||
| 7377ddaea3 | |||
| 67c393ecf1 | |||
| dbb3ecd5bf | |||
| 966cd46228 | |||
| 1ee9b3c46f | |||
| 8f57f5fc5b | |||
| cd7195b3aa | |||
| 020dff0871 | |||
| 1dbdbc7f3c | |||
| 4d1232b764 | |||
|
fe2e02934a
|
|||
|
add1a88ce4
|
|||
| 3c23918e1f | |||
| fa0a2a9df9 | |||
|
|
9feaf6686f | ||
| 7087be672a | |||
|
|
969141601d | ||
|
|
b391ab0ef6 | ||
| 4de2ad38c5 | |||
| d46d937e93 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -10,3 +10,6 @@ keys/
|
||||
.idea/
|
||||
|
||||
*.kate-swp
|
||||
|
||||
# Diagram cache directory
|
||||
media/diagram_cache/
|
||||
|
||||
105
Documentation/DIAGRAM_CACHING.md
Normal file
105
Documentation/DIAGRAM_CACHING.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Diagram POST Caching Implementation
|
||||
|
||||
This feature replaces the URL-encoded GET approach for diagram generation with POST requests and local filesystem caching.
|
||||
|
||||
## Changes Overview
|
||||
|
||||
### New Files
|
||||
- `diagramm_proxy/__init__.py` - Module initialization
|
||||
- `diagramm_proxy/diagram_cache.py` - Caching logic and POST request handling
|
||||
- `abschnitte/management/commands/clear_diagram_cache.py` - Management command for cache clearing
|
||||
|
||||
### Modified Files
|
||||
- `abschnitte/utils.py` - Updated `render_textabschnitte()` to use caching
|
||||
- `.gitignore` - Added cache directory exclusion
|
||||
|
||||
## Configuration Required
|
||||
|
||||
Add to your Django settings file (e.g., `VorgabenUI/settings.py`):
|
||||
|
||||
```python
|
||||
# Diagram cache settings
|
||||
DIAGRAM_CACHE_DIR = 'diagram_cache' # relative to MEDIA_ROOT
|
||||
|
||||
# Ensure MEDIA_ROOT and MEDIA_URL are configured
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
MEDIA_URL = '/media/'
|
||||
```
|
||||
|
||||
### URL Configuration
|
||||
|
||||
Ensure media files are served in development. In your main `urls.py`:
|
||||
|
||||
```python
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
|
||||
# ... existing urlpatterns ...
|
||||
|
||||
# Serve media files in development
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. When a diagram is rendered, the system computes a SHA256 hash of the diagram content
|
||||
2. It checks if a cached SVG exists for that hash
|
||||
3. If cached: serves the existing file
|
||||
4. If not cached: POSTs content to Kroki server, saves the response, and serves it
|
||||
5. Diagrams are served from `MEDIA_URL/diagram_cache/{type}/{hash}.svg`
|
||||
|
||||
## Benefits
|
||||
|
||||
- **No URL length limitations** - Content is POSTed instead of URL-encoded
|
||||
- **Improved performance** - Cached diagrams are served directly from filesystem
|
||||
- **Reduced server load** - Kroki server is only called once per unique diagram
|
||||
- **Persistent cache** - Survives application restarts
|
||||
- **Better error handling** - Graceful fallback on generation failures
|
||||
|
||||
## Usage
|
||||
|
||||
### Viewing Diagrams
|
||||
No changes required - diagrams will be automatically cached on first render.
|
||||
|
||||
### Clearing Cache
|
||||
|
||||
Clear all cached diagrams:
|
||||
```bash
|
||||
python manage.py clear_diagram_cache
|
||||
```
|
||||
|
||||
Clear diagrams of a specific type:
|
||||
```bash
|
||||
python manage.py clear_diagram_cache --type plantuml
|
||||
python manage.py clear_diagram_cache --type mermaid
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
1. Create or view a page with diagrams
|
||||
2. Verify diagrams render correctly
|
||||
3. Check that `media/diagram_cache/` directory is created with cached SVGs
|
||||
4. Refresh the page - second load should be faster (cache hit)
|
||||
5. Check logs for cache hit/miss messages
|
||||
6. Test cache clearing command
|
||||
|
||||
## Migration Notes
|
||||
|
||||
- Existing diagrams will be regenerated on first view after deployment
|
||||
- The old URL-based approach is completely replaced
|
||||
- No database migrations needed
|
||||
- Ensure `requests` library is installed (already in requirements.txt)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Diagrams not rendering
|
||||
- Check that MEDIA_ROOT and MEDIA_URL are configured correctly
|
||||
- Verify Kroki server is accessible at `http://svckroki:8000`
|
||||
- Check application logs for error messages
|
||||
- Ensure media directory is writable
|
||||
|
||||
### Cache not working
|
||||
- Verify Django storage configuration
|
||||
- Check file permissions on media/diagram_cache directory
|
||||
- Review logs for cache-related errors
|
||||
@@ -1 +1,6 @@
|
||||
# VDeployment2
|
||||
# vgui-cicd
|
||||
|
||||
There are examples for importing text in the "Documentation"-directory. Actual documentation follows.
|
||||
|
||||
Documentation on Confluence so far.
|
||||
This commit should be signed.
|
||||
|
||||
@@ -38,7 +38,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'standards',
|
||||
'dokumente',
|
||||
'abschnitte',
|
||||
'stichworte',
|
||||
'mptt',
|
||||
|
||||
@@ -43,7 +43,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'standards',
|
||||
'dokumente',
|
||||
'abschnitte',
|
||||
'stichworte',
|
||||
'referenzen',
|
||||
|
||||
@@ -19,7 +19,7 @@ from django.urls import include, path, re_path
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from diagramm_proxy.views import DiagrammProxyView
|
||||
import standards.views
|
||||
import dokumente.views
|
||||
import pages.views
|
||||
import referenzen.views
|
||||
|
||||
@@ -28,7 +28,7 @@ admin.site.site_header="Autorenumgebung"
|
||||
urlpatterns = [
|
||||
path('',pages.views.startseite),
|
||||
path('search/',pages.views.search),
|
||||
path('standards/', include("standards.urls")),
|
||||
path('dokumente/', include("dokumente.urls")),
|
||||
path('autorenumgebung/', admin.site.urls),
|
||||
path('stichworte/', include("stichworte.urls")),
|
||||
path('referenzen/', referenzen.views.tree, name="referenz_tree"),
|
||||
|
||||
1
abschnitte/management/__init__.py
Normal file
1
abschnitte/management/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Management commands
|
||||
1
abschnitte/management/commands/__init__.py
Normal file
1
abschnitte/management/commands/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Commands package
|
||||
23
abschnitte/management/commands/clear_diagram_cache.py
Normal file
23
abschnitte/management/commands/clear_diagram_cache.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from diagramm_proxy.diagram_cache import clear_cache
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Clear cached diagrams'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--type',
|
||||
type=str,
|
||||
help='Diagram type to clear (e.g., plantuml, mermaid)',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
diagram_type = options.get('type')
|
||||
if diagram_type:
|
||||
self.stdout.write(f'Clearing cache for {diagram_type}...')
|
||||
clear_cache(diagram_type)
|
||||
else:
|
||||
self.stdout.write('Clearing all diagram caches...')
|
||||
clear_cache()
|
||||
self.stdout.write(self.style.SUCCESS('Cache cleared successfully'))
|
||||
@@ -3,6 +3,10 @@ import base64
|
||||
import zlib
|
||||
import re
|
||||
from textwrap import dedent
|
||||
from django.conf import settings
|
||||
|
||||
# Import the caching function
|
||||
from diagramm_proxy.diagram_cache import get_cached_diagram
|
||||
|
||||
DIAGRAMMSERVER="/diagramm"
|
||||
|
||||
@@ -25,15 +29,23 @@ def render_textabschnitte(queryset):
|
||||
elif typ == "tabelle":
|
||||
html = md_table_to_html(inhalt)
|
||||
elif typ == "diagramm":
|
||||
temp=inhalt.splitlines()
|
||||
diagramtype=temp.pop(0)
|
||||
diagramoptions='width="100%"'
|
||||
if temp[0][0:6].lower() == "option":
|
||||
diagramoptions=temp.pop(0).split(":",1)[1]
|
||||
rest="\n".join(temp)
|
||||
html = '<p><img '+diagramoptions+' src="'+DIAGRAMMSERVER+"/"+diagramtype+"/svg/"
|
||||
html += base64.urlsafe_b64encode(zlib.compress(rest.encode("utf-8"),9)).decode()
|
||||
html += '"></p>'
|
||||
temp = inhalt.splitlines()
|
||||
diagramtype = temp.pop(0)
|
||||
diagramoptions = 'width="100%"'
|
||||
if temp and temp[0][0:6].lower() == "option":
|
||||
diagramoptions = temp.pop(0).split(":", 1)[1]
|
||||
rest = "\n".join(temp)
|
||||
|
||||
# Use caching instead of URL encoding
|
||||
try:
|
||||
cache_path = get_cached_diagram(diagramtype, rest)
|
||||
# Generate URL to serve from media/static
|
||||
diagram_url = settings.MEDIA_URL + cache_path
|
||||
html = f'<p><img {diagramoptions} src="{diagram_url}"></p>'
|
||||
except Exception as e:
|
||||
# Fallback to error message
|
||||
html = f'<p class="text-danger">Error generating diagram: {str(e)}</p>'
|
||||
|
||||
elif typ == "code":
|
||||
html = "<pre><code>"
|
||||
html += markdown(inhalt, extensions=['tables', 'attr_list'])
|
||||
|
||||
@@ -18,36 +18,20 @@ spec:
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
initContainers:
|
||||
- name: loader
|
||||
image: git.baumann.gr/adebaumann/vgui-data-loader:0.5
|
||||
command: [ "sh","-c","cp -n preload/preload.sqlite3 /data/db.sqlite3; chown -R 999:999 /data; ls -la /data; sleep 10; exit 0" ]
|
||||
image: git.baumann.gr/adebaumann/vui-data-loader:0.8
|
||||
command: [ "sh","-c","cp preload/preload.sqlite3 /data/db.sqlite3; chown -R 999:999 /data; ls -la /data; sleep 10; exit 0" ]
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
containers:
|
||||
- name: web
|
||||
image: git.baumann.gr/adebaumann/vui:0.929
|
||||
image: git.baumann.gr/adebaumann/vui:0.932
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /app/data
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 8000
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 2
|
||||
failureThreshold: 6
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 8000
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 20
|
||||
timeoutSeconds: 2
|
||||
failureThreshold: 3
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
|
||||
Binary file not shown.
BIN
data/db.sqlite3
BIN
data/db.sqlite3
Binary file not shown.
1
diagramm_proxy/__init__.py
Normal file
1
diagramm_proxy/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Diagram proxy module
|
||||
91
diagramm_proxy/diagram_cache.py
Normal file
91
diagramm_proxy/diagram_cache.py
Normal file
@@ -0,0 +1,91 @@
|
||||
import hashlib
|
||||
import os
|
||||
import requests
|
||||
from pathlib import Path
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import default_storage
|
||||
from django.core.files.base import ContentFile
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Configure cache directory
|
||||
CACHE_DIR = getattr(settings, 'DIAGRAM_CACHE_DIR', 'diagram_cache')
|
||||
KROKI_UPSTREAM = "http://svckroki:8000"
|
||||
|
||||
def get_cache_path(diagram_type, content_hash):
|
||||
"""Generate cache file path for a diagram."""
|
||||
return os.path.join(CACHE_DIR, diagram_type, f"{content_hash}.svg")
|
||||
|
||||
def compute_hash(content):
|
||||
"""Compute SHA256 hash of diagram content."""
|
||||
return hashlib.sha256(content.encode('utf-8')).hexdigest()
|
||||
|
||||
def get_cached_diagram(diagram_type, diagram_content):
|
||||
"""
|
||||
Retrieve diagram from cache or generate it via POST.
|
||||
|
||||
Args:
|
||||
diagram_type: Type of diagram (e.g., 'plantuml', 'mermaid')
|
||||
diagram_content: Raw diagram content
|
||||
|
||||
Returns:
|
||||
Path to cached diagram file (relative to MEDIA_ROOT)
|
||||
"""
|
||||
content_hash = compute_hash(diagram_content)
|
||||
cache_path = get_cache_path(diagram_type, content_hash)
|
||||
|
||||
# Check if diagram exists in cache
|
||||
if default_storage.exists(cache_path):
|
||||
logger.debug(f"Cache hit for {diagram_type} diagram: {content_hash[:8]}")
|
||||
return cache_path
|
||||
|
||||
# Generate diagram via POST request
|
||||
logger.info(f"Cache miss for {diagram_type} diagram: {content_hash[:8]}, generating...")
|
||||
try:
|
||||
url = f"{KROKI_UPSTREAM}/{diagram_type}/svg"
|
||||
response = requests.post(
|
||||
url,
|
||||
data=diagram_content.encode('utf-8'),
|
||||
headers={'Content-Type': 'text/plain'},
|
||||
timeout=30
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
# Ensure cache directory exists
|
||||
cache_dir = os.path.dirname(default_storage.path(cache_path))
|
||||
os.makedirs(cache_dir, exist_ok=True)
|
||||
|
||||
# Save to cache
|
||||
default_storage.save(cache_path, ContentFile(response.content))
|
||||
logger.info(f"Diagram cached successfully: {cache_path}")
|
||||
|
||||
return cache_path
|
||||
|
||||
except requests.RequestException as e:
|
||||
logger.error(f"Error generating diagram: {e}")
|
||||
raise
|
||||
|
||||
def clear_cache(diagram_type=None):
|
||||
"""
|
||||
Clear cached diagrams.
|
||||
|
||||
Args:
|
||||
diagram_type: If specified, only clear diagrams of this type
|
||||
"""
|
||||
if diagram_type:
|
||||
cache_path = os.path.join(CACHE_DIR, diagram_type)
|
||||
else:
|
||||
cache_path = CACHE_DIR
|
||||
|
||||
if default_storage.exists(cache_path):
|
||||
full_path = default_storage.path(cache_path)
|
||||
# Walk through and delete files
|
||||
for root, dirs, files in os.walk(full_path):
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
try:
|
||||
os.remove(file_path)
|
||||
logger.info(f"Deleted cached diagram: {file_path}")
|
||||
except OSError as e:
|
||||
logger.error(f"Error deleting {file_path}: {e}")
|
||||
@@ -3,4 +3,4 @@ from django.apps import AppConfig
|
||||
|
||||
class standardsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'standards'
|
||||
name = 'dokumente'
|
||||
@@ -4,7 +4,7 @@ from pathlib import Path
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.utils import timezone
|
||||
|
||||
from standards.models import (
|
||||
from dokumente.models import (
|
||||
Dokument,
|
||||
Dokumententyp,
|
||||
Thema,
|
||||
@@ -53,7 +53,7 @@ class Migration(migrations.Migration):
|
||||
('rght', models.PositiveIntegerField(editable=False)),
|
||||
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
|
||||
('level', models.PositiveIntegerField(editable=False)),
|
||||
('oberreferenz', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='unterreferenzen', to='standards.referenz')),
|
||||
('oberreferenz', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='unterreferenzen', to='dokumente.referenz')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Referenzen',
|
||||
@@ -65,7 +65,7 @@ class Migration(migrations.Migration):
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('inhalt', models.TextField(blank=True, null=True)),
|
||||
('abschnitttyp', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='abschnitte.abschnitttyp')),
|
||||
('erklaerung', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='standards.referenz')),
|
||||
('erklaerung', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dokumente.referenz')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Erklärung',
|
||||
@@ -80,9 +80,9 @@ class Migration(migrations.Migration):
|
||||
('gueltigkeit_bis', models.DateField(blank=True, null=True)),
|
||||
('signatur_cso', models.CharField(blank=True, max_length=255)),
|
||||
('anhaenge', models.TextField(blank=True)),
|
||||
('autoren', models.ManyToManyField(related_name='verfasste_dokumente', to='standards.person')),
|
||||
('dokumententyp', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='standards.dokumententyp')),
|
||||
('pruefende', models.ManyToManyField(related_name='gepruefte_dokumente', to='standards.person')),
|
||||
('autoren', models.ManyToManyField(related_name='verfasste_dokumente', to='dokumente.person')),
|
||||
('dokumententyp', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='dokumente.dokumententyp')),
|
||||
('pruefende', models.ManyToManyField(related_name='gepruefte_dokumente', to='dokumente.person')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Standard',
|
||||
@@ -95,7 +95,7 @@ class Migration(migrations.Migration):
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('inhalt', models.TextField(blank=True, null=True)),
|
||||
('abschnitttyp', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='abschnitte.abschnitttyp')),
|
||||
('geltungsbereich', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='standards.standard')),
|
||||
('geltungsbereich', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dokumente.standard')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Geltungsbereichs-Abschnitt',
|
||||
@@ -108,8 +108,8 @@ class Migration(migrations.Migration):
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('datum', models.DateField()),
|
||||
('aenderung', models.TextField()),
|
||||
('autoren', models.ManyToManyField(to='standards.person')),
|
||||
('dokument', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='changelog', to='standards.standard')),
|
||||
('autoren', models.ManyToManyField(to='dokumente.person')),
|
||||
('dokument', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='changelog', to='dokumente.standard')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
@@ -120,10 +120,10 @@ class Migration(migrations.Migration):
|
||||
('titel', models.CharField(max_length=255)),
|
||||
('gueltigkeit_von', models.DateField()),
|
||||
('gueltigkeit_bis', models.DateField(blank=True, null=True)),
|
||||
('dokument', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vorgaben', to='standards.standard')),
|
||||
('referenzen', models.ManyToManyField(blank=True, to='standards.referenz')),
|
||||
('dokument', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vorgaben', to='dokumente.standard')),
|
||||
('referenzen', models.ManyToManyField(blank=True, to='dokumente.referenz')),
|
||||
('stichworte', models.ManyToManyField(blank=True, to='stichworte.stichwort')),
|
||||
('thema', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='standards.thema')),
|
||||
('thema', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='dokumente.thema')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Vorgaben',
|
||||
@@ -134,7 +134,7 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('frage', models.CharField(max_length=255)),
|
||||
('vorgabe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='checklistenfragen', to='standards.vorgabe')),
|
||||
('vorgabe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='checklistenfragen', to='dokumente.vorgabe')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Fragen für Checkliste',
|
||||
@@ -145,7 +145,7 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('inhalt', models.TextField(blank=True, null=True)),
|
||||
('abschnitt', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='standards.vorgabe')),
|
||||
('abschnitt', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dokumente.vorgabe')),
|
||||
('abschnitttyp', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='abschnitte.abschnitttyp')),
|
||||
],
|
||||
options={
|
||||
@@ -158,7 +158,7 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('inhalt', models.TextField(blank=True, null=True)),
|
||||
('abschnitt', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='standards.vorgabe')),
|
||||
('abschnitt', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dokumente.vorgabe')),
|
||||
('abschnitttyp', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='abschnitte.abschnitttyp')),
|
||||
],
|
||||
options={
|
||||
@@ -8,7 +8,7 @@ class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('abschnitte', '0001_initial'),
|
||||
('standards', '0001_initial'),
|
||||
('dokumente', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -18,7 +18,7 @@ class Migration(migrations.Migration):
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('inhalt', models.TextField(blank=True, null=True)),
|
||||
('abschnitttyp', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='abschnitte.abschnitttyp')),
|
||||
('einleitung', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='standards.standard')),
|
||||
('einleitung', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dokumente.standard')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Einleitungs-Abschnitt',
|
||||
@@ -6,7 +6,7 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('standards', '0002_einleitung'),
|
||||
('dokumente', '0002_einleitung'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -7,7 +7,7 @@ class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('referenzen', '0001_initial'),
|
||||
('standards', '0003_einleitung_order_geltungsbereich_order_and_more'),
|
||||
('dokumente', '0003_einleitung_order_geltungsbereich_order_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -7,7 +7,7 @@ class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('rollen', '0001_initial'),
|
||||
('standards', '0004_remove_referenzerklaerung_erklaerung_and_more'),
|
||||
('dokumente', '0004_remove_referenzerklaerung_erklaerung_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -6,7 +6,7 @@ from django.db import migrations
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('standards', '0005_vorgabe_relevanz'),
|
||||
('dokumente', '0005_vorgabe_relevanz'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -6,7 +6,7 @@ from django.db import migrations
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('standards', '0006_rename_standard_dokument_alter_dokument_options'),
|
||||
('dokumente', '0006_rename_standard_dokument_alter_dokument_options'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -2,10 +2,10 @@
|
||||
{% block content %}
|
||||
<h1>Standards Informatiksicherheit</h1>
|
||||
<ul>
|
||||
{% for standard in standards %}
|
||||
{% for dokument in dokumente %}
|
||||
<li>
|
||||
<a href="{% url 'standard_detail' nummer=standard.nummer %}">
|
||||
{{ standard.nummer }} – {{ standard.name }}
|
||||
<a href="{% url 'standard_detail' nummer=dokument.nummer %}">
|
||||
{{ dokument.nummer }} – {{ dokument.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
8
dokumente/tests.py
Normal file
8
dokumente/tests.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.test import TestCase
|
||||
from myapp.models import Dokument
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
class DokumentTestCase (TestCase):
|
||||
def setUp(self):
|
||||
Document.objects.create(name)
|
||||
@@ -9,9 +9,9 @@ calendar=parsedatetime.Calendar()
|
||||
|
||||
|
||||
def standard_list(request):
|
||||
standards = Dokument.objects.all()
|
||||
dokumente = Dokument.objects.all()
|
||||
return render(request, 'standards/standard_list.html',
|
||||
{'standards': standards}
|
||||
{'dokumente': dokumente}
|
||||
)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
|
||||
<div class="navbar-nav">
|
||||
<a class="nav-item nav-link active" href="/standards">Standards</a>
|
||||
<a class="nav-item nav-link active" href="/dokumente">Standards</a>
|
||||
<a class="nav-item nav-link" href="/referenzen">Referenzen</a>
|
||||
<a class="nav-item nav-link" href="/stichworte">Stichworte</a>
|
||||
<a class="nav-item nav-link" href="/search">Suche</a>
|
||||
@@ -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.930</div>
|
||||
<div>VorgabenUI v0.931</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<h1>Vorgaben Informatiksicherheit BIT</h1>
|
||||
<h2>Aktuell erfasste Standards</h2>
|
||||
<ul>
|
||||
{% for standard in standards %}
|
||||
{% for standard in dokumente %}
|
||||
<li><a href="{% url 'standard_detail' nummer=standard.nummer %}">{{ standard }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
from django.shortcuts import render
|
||||
from abschnitte.utils import render_textabschnitte
|
||||
from standards.models import Dokument, VorgabeLangtext, VorgabeKurztext, Geltungsbereich
|
||||
from dokumente.models import Dokument, VorgabeLangtext, VorgabeKurztext, Geltungsbereich
|
||||
from itertools import groupby
|
||||
import datetime
|
||||
import pprint
|
||||
|
||||
def startseite(request):
|
||||
standards=list(Dokument.objects.all())
|
||||
return render(request, 'startseite.html', {"standards":standards,})
|
||||
return render(request, 'startseite.html', {"dokumente":standards,})
|
||||
|
||||
def search(request):
|
||||
if request.method == "GET":
|
||||
return render(request, 'search.html')
|
||||
elif request.method == "POST":
|
||||
suchbegriff=request.POST.get("q")
|
||||
areas=request.POST.getlist("suchbereich[]")
|
||||
result= {"all": {}}
|
||||
qs = VorgabeKurztext.objects.filter(inhalt__contains=suchbegriff).exclude(abschnitt__gueltigkeit_bis__lt=datetime.date.today())
|
||||
result["kurztext"] = {k: [o.abschnitt for o in g] for k, g in groupby(qs, key=lambda o: o.abschnitt.dokument)}
|
||||
qs = VorgabeLangtext.objects.filter(inhalt__contains=suchbegriff).exclude(abschnitt__gueltigkeit_bis__lt=datetime.date.today())
|
||||
result['langtext']= {k: [o.abschnitt for o in g] for k, g in groupby(qs, key=lambda o: o.abschnitt.dokument)}
|
||||
for r in result.keys():
|
||||
for s in result[r].keys():
|
||||
result["all"][s] = set(result[r][s])
|
||||
result["geltungsbereich"]={}
|
||||
geltungsbereich=set(list([x.geltungsbereich for x in Geltungsbereich.objects.filter(inhalt__contains=suchbegriff)]))
|
||||
for s in geltungsbereich:
|
||||
result["geltungsbereich"][s]=render_textabschnitte(s.geltungsbereich_set.order_by("order"))
|
||||
for r in result.keys():
|
||||
for s in result[r].keys():
|
||||
result["all"][s] = set(result[r][s])
|
||||
print (result)
|
||||
pprint.pp (result)
|
||||
return render(request,"results.html",{"suchbegriff":suchbegriff,"resultat":result})
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
Reference in New Issue
Block a user