Compare commits

...

9 Commits

Author SHA1 Message Date
67a967da67 ArgoCD-Documentation updated 2026-02-20 13:52:41 +01:00
6af0b02442 Documentation for data import updated 2026-02-20 10:03:52 +01:00
f7a20648b2 Changes in referenz entry field
All checks were successful
Build containers when image tags change / build-if-image-changed (., web, containers, main container, git.baumann.gr/adebaumann/vui) (push) Successful in 31s
Build containers when image tags change / build-if-image-changed (data-loader, loader, initContainers, init-container, git.baumann.gr/adebaumann/vui-data-loader) (push) Successful in 8s
2026-02-12 15:40:21 +01:00
bae8c71028 Deploy last commit
All checks were successful
Build containers when image tags change / build-if-image-changed (., web, containers, main container, git.baumann.gr/adebaumann/vui) (push) Successful in 36s
Build containers when image tags change / build-if-image-changed (data-loader, loader, initContainers, init-container, git.baumann.gr/adebaumann/vui-data-loader) (push) Successful in 8s
2026-02-12 15:09:00 +01:00
a0495fdea0 Einleitung and Geltungsbereich now draggable 2026-02-12 15:05:52 +01:00
c125238f12 NFS adapted to use share "Kubernetesdata" 2026-02-10 16:04:53 +01:00
f5c0d9beac Delete .gitea/workflows/check_code_in_sonarqube.yaml 2026-02-10 07:37:38 +00:00
70752a2482 Deployment
Some checks failed
Build containers when image tags change / build-if-image-changed (., web, containers, main container, git.baumann.gr/adebaumann/vui) (push) Successful in 2m30s
Build containers when image tags change / build-if-image-changed (data-loader, loader, initContainers, init-container, git.baumann.gr/adebaumann/vui-data-loader) (push) Successful in 17s
SonarQube Scan / SonarQube Trigger (push) Failing after 15s
2026-02-09 08:39:03 +01:00
633ecabdb9 Admin interface for Referenzen fixed
Some checks failed
SonarQube Scan / SonarQube Trigger (push) Has been cancelled
2026-02-09 08:37:47 +01:00
14 changed files with 259 additions and 141 deletions

View File

@@ -1,67 +0,0 @@
on:
push:
# branches:
# - main
# - development
pull_request:
types: [opened, synchronize, reopened]
name: SonarQube Scan
jobs:
sonarqube:
name: SonarQube Trigger
runs-on: ubuntu-latest
steps:
- name: Checking out
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install -r requirements.txt
- name: Run tests with coverage
run: |
coverage run --source='.' manage.py test
coverage xml
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Cache SonarQube packages
uses: actions/cache@v3
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Download and setup SonarScanner
run: |
mkdir -p $HOME/.sonar
wget -q https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux.zip
unzip -q sonar-scanner-cli-5.0.1.3006-linux.zip -d $HOME/.sonar/
echo "$HOME/.sonar/sonar-scanner-5.0.1.3006-linux/bin" >> $GITHUB_PATH
- name: Verify Java version
run: java -version
- name: SonarQube Scan
env:
SONAR_HOST_URL: ${{ secrets.SONARQUBE_HOST }}
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
run: |
sonar-scanner \
-Dsonar.projectKey=${{ github.event.repository.name }} \
-Dsonar.sources=. \
-Dsonar.host.url=${SONAR_HOST_URL} \
-Dsonar.token=${SONAR_TOKEN} \
-Dsonar.python.coverage.reportPaths=coverage.xml

View File

@@ -13,21 +13,29 @@ This directory contains the ArgoCD application manifests for deploying the Vorga
- **Storage Class**: Uses NFS storage class for shared storage across multiple pods
- **Namespace**: vorgabenui
#### `configmap.yaml`
- **Purpose**: Django application configuration
- **Contains**: Environment variables, application settings, version information
- **Namespace**: vorgabenui
- **Version**: 0.990
#### `deployment.yaml`
- **Purpose**: Main application deployment configuration
- **Contains**: Django application container, environment variables, resource limits
- **Replicas**: Configurable replica count for high availability
- **Version**: 0.990
#### `ingress.yaml`
- **Purpose**: External access configuration
- **Host**: Configurable hostname for the application
- **TLS**: SSL/TLS termination configuration
- **Backend**: Routes traffic to the Django application service
- **Ingress Class**: traefik
#### `nfs-pv.yaml`
- **Purpose**: PersistentVolume definition for NFS storage
- **Server**: 192.168.17.199
- **Path**: /mnt/user/vorgabenui
- **Path**: /mnt/user/kubernetesdata/vorgabenui
- **Access**: ReadWriteMany for multi-pod access
- **Reclaim Policy**: Retain (data preserved after PVC deletion)
@@ -40,14 +48,21 @@ This directory contains the ArgoCD application manifests for deploying the Vorga
#### `diagrammer.yaml`
- **Purpose**: Deployment configuration for the diagram generation service
- **Function**: Handles diagram creation and caching for the application
- **Version**: 0.026
## NFS Storage Configuration
#### `secret.yaml` (Template)
- **Purpose**: Template for Django SECRET_KEY secret
- **Contains**: Secret key configuration for cryptographic operations
- **Namespace**: vorgabenui
- **Generated by**: `deploy-argocd-secret.sh` script
- **Version**: 0.026
### Prerequisites
1. NFS server must be running at 192.168.17.199
2. The directory `/mnt/user/vorgabenui` must exist and be exported
3. Kubernetes nodes must have NFS client utilities installed
4. For MicroK8s: `microk8s enable nfs`
#### `secret.yaml` (Template)
- **Purpose**: Template for Django SECRET_KEY secret
- **Contains**: Secret key configuration for cryptographic operations
- **Namespace**: vorgabenui
- **Generated by**: `deploy-argocd-secret.sh` script
- **Version**: 0.026
## MicroK8s Addons Required
@@ -136,7 +151,7 @@ microk8s kubectl get pods -n ingress
microk8s kubectl get svc -n ingress
# Test ingress connectivity
curl -k https://your-domain.com
curl -k https://vorgabenportal.knowyoursecurity.com
```
#### Storage Issues
@@ -159,24 +174,143 @@ On the NFS server (192.168.17.199), ensure the following:
```bash
# Create the shared directory
sudo mkdir -p /mnt/user/vorgabenui
sudo chmod 755 /mnt/user/vorgabenui
sudo mkdir -p /mnt/user/kubernetesdata/vorgabenui
sudo chmod 755 /mnt/user/kubernetesdata/vorgabenui
# Add to /etc/exports
echo "/mnt/user/vorgabenui *(rw,sync,no_subtree_check,no_root_squash)" | sudo tee -a /etc/exports
echo "/mnt/user/kubernetesdata/vorgabenui *(rw,sync,no_subtree_check,no_root_squash)" | sudo tee -a /etc/exports
# Export the directory
sudo exportfs -a
sudo systemctl restart nfs-kernel-server
```
## Configuration Management
### ConfigMap Deployment
The Django application uses a ConfigMap for application configuration. The ConfigMap contains environment variables and settings for the Django application.
#### ConfigMap File
- **File**: `configmap.yaml`
- **Name**: `django-config`
- **Namespace**: `vorgabenui`
- **Version**: 0.990
#### Configuration Contents
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: django-config
namespace: vorgabenui
data:
# Django Configuration
DEBUG: "false"
DJANGO_ALLOWED_HOSTS: "vorgabenportal.knowyoursecurity.com,localhost,127.0.0.1,*"
DJANGO_SETTINGS_MODULE: "VorgabenUI.settings"
# Application Configuration
LANGUAGE_CODE: "de-ch"
TIME_ZONE: "UTC"
# Static and Media Configuration
STATIC_URL: "/static/"
MEDIA_URL: "/media/"
# Application Version
VERSION: "0.990"
# Database Configuration (for future use)
# DATABASE_ENGINE: "django.db.backends.sqlite3"
# DATABASE_NAME: "/app/data/db.sqlite3"
# Security Configuration
# CSRF_TRUSTED_ORIGINS: "https://vorgabenportal.knowyoursecurity.com"
```
#### Deployment Script
The ConfigMap is deployed using the `deploy-argocd-configmap.sh` script located in the `scripts/` directory.
**Script Usage**:
```bash
# Deploy ConfigMap
./scripts/deploy-argocd-configmap.sh
# Verify ConfigMap only (no deployment)
./scripts/deploy-argocd-configmap.sh --verify-only
# Dry-run (show what would be deployed)
./scripts/deploy-argocd-configmap.sh --dry-run
```
**Script Features**:
- Validates kubectl availability
- Checks if ConfigMap file exists
- Creates namespace if it doesn't exist
- Applies ConfigMap configuration
- Verifies successful deployment
- Provides detailed logging and error handling
### Secret Deployment
The Django application requires a secure SECRET_KEY for cryptographic signing. This is managed through a Kubernetes Secret.
#### Secret Configuration
- **Secret Name**: `vorgabenui-secrets`
- **Secret Key**: `vorgabenui_secret`
- **Namespace**: `vorgabenui`
#### Secret Generation
The secret is automatically generated using the `deploy-argocd-secret.sh` script, which creates a secure Django-style SECRET_KEY.
**Script Usage**:
```bash
# Generate and deploy new secret
./scripts/deploy-argocd-secret.sh
# Verify existing secret only (no new generation)
./scripts/deploy-argocd-secret.sh --verify-only
# Dry-run (show what would be done)
./scripts/deploy-argocd-secret.sh --dry-run
```
**Secret Generation Features**:
- Generates secure 50-character SECRET_KEY using Python
- Uses Django-style character set (letters, digits, special characters)
- Creates or updates the secret in the vorgabenui namespace
- Verifies secret deployment and accessibility
- Tests secret accessibility in Django pods
#### Environment Variable Reference
The deployment.yaml references the secret through environment variables:
```yaml
env:
# Secret configuration
- name: VORGABENUI_SECRET
valueFrom:
secretKeyRef:
name: vorgabenui-secrets
key: vorgabenui_secret
```
#### Security Notes
- The SECRET_KEY is never logged or displayed in full
- Only the first 10 characters are shown during generation for verification
- The secret is stored securely in Kubernetes and only accessible to authorized pods
- Regular secret rotation is recommended for production environments
## Deployment Order
1. **StorageClass** (`nfs-storageclass.yaml`) - Defines NFS storage class
2. **PersistentVolume** (`nfs-pv.yaml`) - Creates the NFS volume
3. **PersistentVolumeClaim** (`001_pvc.yaml`) - Claims storage for application
4. **Application Deployments** (`deployment.yaml`, `diagrammer.yaml`) - Deploy application services
5. **Ingress** (`ingress.yaml`) - Configure external access
4. **ConfigMap** (`configmap.yaml`) - Deploy Django configuration
5. **Secret** (`secret.yaml`) - Generate and deploy Django SECRET_KEY
6. **Application Deployments** (`deployment.yaml`, `diagrammer.yaml`) - Deploy application services
7. **Ingress** (`ingress.yaml`) - Configure external access
## Configuration Notes
@@ -227,7 +361,7 @@ kubectl describe pod <pod-name> -n vorgabenui
## Maintenance
### Backup Strategy
- The NFS server should have regular backups of `/mnt/user/vorgabenui`
- The NFS server should have regular backups of `/mnt/user/kubernetesdata/vorgabenui`
- Consider snapshot capabilities if using enterprise NFS solutions
### Monitoring

View File

@@ -49,7 +49,7 @@ python manage.py import-document Documentation/import\ formats/r009.txt \
### Dry-Run Modus
Der Dry-Run Modus ist besonders nützlich zum Testen:
Der Dry-Run Modus ist zum Testen gedacht:
```bash
python manage.py import-document r009.txt \
@@ -73,7 +73,7 @@ python manage.py import-document r009.txt \
--purge
```
Nutzen Sie `--dry-run --purge` zuerst, um zu sehen, was gelöscht würde.
Mit `--dry-run --purge` kann zuerst geprüft werden, was gelöscht würde.
## Dateiformat
@@ -257,7 +257,7 @@ Abschliessender Text nach der Liste.
>>>Vorgabe [Thema]
```
Das Thema muss in der Datenbank bereits als `Thema`-Objekt existieren. Übliche Themen:
Das Thema muss in der Datenbank bereits als `Thema`-Objekt existieren. Die bestehenden Themen sind - wie in den bestehenden Standards - aus dem IT-Grundschutz übernommen:
- Organisation
- Technik
- Informationen
@@ -275,11 +275,7 @@ Das Thema muss in der Datenbank bereits als `Thema`-Objekt existieren. Übliche
oder inline:
```
>>>Nummer: 1
```
Die Nummer wird als Integer gespeichert. Sie ist eindeutig innerhalb eines Dokuments und Themas.
Die Nummer wird als Integer gespeichert. Sie ist nicht eindeutig innerhalb eines Dokuments und Themas. Wenn mehrere Vorgaben im selben Thema mit der selben Nummer vorkommen, darf sich der Geltungszeitraum der Vorgaben nicht überschneiden (wird beim Import geprüft).
#### Titel (Pflicht)
@@ -322,14 +318,6 @@ Komma-getrennte Liste:
Firewall, Netzwerk, Sicherheit
```
oder als Block:
```
>>>Stichworte
>>>Text
Firewall, Netzwerk, Sicherheit
```
**Hinweis:** Stichworte werden automatisch in der Datenbank angelegt, falls sie noch nicht existieren.
#### Checkliste (Optional)
@@ -347,7 +335,7 @@ Jede Zeile wird als separate Checklistenfrage gespeichert.
### 1. Dry-Run vor Import
Führen Sie immer zuerst einen Dry-Run durch:
Immer zuerst einen Dry-Run durchführen:
```bash
python manage.py import-document datei.txt \
@@ -359,7 +347,7 @@ python manage.py import-document datei.txt \
### 2. Themen vorab erstellen
Stellen Sie sicher, dass alle verwendeten Themen in der Datenbank existieren:
Sicherstellen, dass alle verwendeten Themen in der Datenbank existieren:
```python
python manage.py shell
@@ -391,11 +379,11 @@ Folgende Abschnitttypen müssen in der Datenbank existieren:
- `code`
- `diagramm`
Prüfen Sie diese in der Autorenumgebung unter "Abschnitttypen".
Prüfen in der Autorenumgebung unter "Abschnitttypen".
### 5. UTF-8 Kodierung
Stellen Sie sicher, dass Ihre Importdatei UTF-8 kodiert ist, besonders bei Umlauten (ä, ö, ü) und Sonderzeichen.
Sicherstellen, dass die Importdatei UTF-8 kodiert ist, besonders bei Umlauten (ä, ö, ü) und Sonderzeichen.
### 6. Versionierung mit Purge
@@ -436,37 +424,37 @@ Leerzeilen innerhalb eines Abschnitts werden beibehalten. Eine Leerzeile nach ei
Der angegebene Dokumententyp existiert nicht in der Datenbank.
**Lösung:** Erstellen Sie den Dokumententyp in der Autorenumgebung oder per Shell.
**Lösung:** Dokumententyp aus dem IT-Grundschutz verwenden, nötigenfalls hinzufügen in der Autorenumgebung oder per Shell.
### "Thema not found, skipping Vorgabe"
Das in der Vorgabe verwendete Thema existiert nicht.
**Lösung:** Erstellen Sie das Thema in der Autorenumgebung oder passen Sie die Importdatei an.
**Lösung:** Thema in der Autorenumgebung erstellen oder Importdatei anpassen.
### "AbschnittTyp not found"
Ein verwendeter Abschnitttyp existiert nicht.
**Lösung:**
- Prüfen Sie die Schreibweise (Gross-/Kleinschreibung wird normalisiert)
- Erstellen Sie den Abschnitttyp in der Autorenumgebung
- Standardtypen: `text`, `liste geordnet`, `liste ungeordnet`
- Schreibweise prüfen (Gross-/Kleinschreibung und "-"/" " wird normalisiert)
- Wenn nötig Abschnitttyp in der Autorenumgebung erstellen (Achtung! Ausgabeformat muss im Code definiert werden)
- Standardtypen: `text`, `liste geordnet`, `liste ungeordnet`, `tabelle`, `code`
### Vorgabe wird nicht importiert
Prüfen Sie:
Prüfen:
- Ist `>>>Nummer` gesetzt?
- Ist `>>>Titel` gesetzt?
- Existiert das Thema?
Verwenden Sie `--dry-run --verbose` für detaillierte Informationen.
`--dry-run --verbose` für detaillierte Informationen.
## Weitere Informationen
### Beispieldateien
Beispieldateien finden Sie in:
Beispieldateien:
- `Documentation/import formats/r009.txt`
- `Documentation/import formats/r0126.txt`
@@ -485,7 +473,3 @@ Oder über die Web-Oberfläche: `/dokumente/R0066/?format=json`
- `export_json` - Exportiert Dokumente als JSON
- `sanity_check_vorgaben` - Prüft Vorgaben auf Konflikte
- `clear_diagram_cache` - Löscht Diagramm-Cache
## Kontakt
Bei Fragen oder Problemen wenden Sie sich an das Information Security Management BIT.

View File

@@ -18,7 +18,7 @@ data:
MEDIA_URL: "/media/"
# Application Version
VERSION: "0.987"
VERSION: "0.990"
# Database Configuration (for future use)
# DATABASE_ENGINE: "django.db.backends.sqlite3"

View File

@@ -27,7 +27,7 @@ spec:
mountPath: /data
containers:
- name: web
image: git.baumann.gr/adebaumann/vui:0.987
image: git.baumann.gr/adebaumann/vui:0.990
imagePullPolicy: Always
securityContext:
runAsUser: 99

View File

@@ -15,4 +15,4 @@ spec:
storageClassName: nfs
nfs:
server: 192.168.17.199
path: /mnt/user/vorgabenui
path: /mnt/user/kubernetesdata/vorgabenui

View File

@@ -1,16 +1,10 @@
from django.contrib import admin
#from nested_inline.admin import NestedStackedInline, NestedModelAdmin
from nested_admin import NestedStackedInline, NestedModelAdmin, NestedTabularInline
from django import forms
from django.utils.html import format_html
from mptt.forms import TreeNodeMultipleChoiceField
from mptt.admin import DraggableMPTTAdmin
from adminsortable2.admin import SortableInlineAdminMixin, SortableAdminBase
from django.utils.safestring import mark_safe
# Register your models here.
from nested_admin import NestedStackedInline, NestedModelAdmin, NestedTabularInline
from .models import *
from stichworte.models import Stichwort, Stichworterklaerung
from referenzen.models import Referenz
@@ -69,7 +63,6 @@ class GeltungsbereichInline(NestedStackedInline):
extra=0
sortable_field_name = "order"
show_change_link=True
classes = ['collapse']
verbose_name_plural = "Geltungsbereich-Abschnitte"
fieldsets = (
(None, {
@@ -83,7 +76,6 @@ class EinleitungInline(NestedStackedInline):
extra = 0
sortable_field_name = "order"
show_change_link = True
classes = ['collapse']
verbose_name_plural = "Einleitungs-Abschnitte"
fieldsets = (
(None, {
@@ -93,12 +85,10 @@ class EinleitungInline(NestedStackedInline):
)
class VorgabeForm(forms.ModelForm):
referenzen = TreeNodeMultipleChoiceField(queryset=Referenz.objects.all(), required=False)
class Meta:
model = Vorgabe
fields = '__all__'
def clean_thema(self):
"""Validate that thema is provided."""
thema = self.cleaned_data.get('thema')
@@ -106,7 +96,7 @@ class VorgabeForm(forms.ModelForm):
raise forms.ValidationError('Thema ist ein Pflichtfeld. Bitte wählen Sie ein Thema aus.')
return thema
class VorgabeInline(SortableInlineAdminMixin, NestedStackedInline):
class VorgabeInline(NestedStackedInline):
model = Vorgabe
form = VorgabeForm
extra = 0
@@ -165,7 +155,7 @@ class StichwortAdmin(NestedModelAdmin):
count = len(vorgaben_list)
if count == 0:
return format_html("<em>Keine Vorgaben gefunden</em><p><strong>Gesamt: 0 Vorgaben</strong></p>")
return mark_safe("<em>Keine Vorgaben gefunden</em><p><strong>Gesamt: 0 Vorgaben</strong></p>")
html = "<div style='max-height: 300px; overflow-y: auto;'>"
html += "<table style='width: 100%; border-collapse: collapse;'>"
@@ -186,7 +176,7 @@ class StichwortAdmin(NestedModelAdmin):
html += "</tbody></table>"
html += f"</div><p><strong>Gesamt: {count} Vorgabe{'n' if count != 1 else ''}</strong></p>"
return format_html(html)
return mark_safe(html)
vorgaben_list.short_description = "Zugeordnete Vorgaben"
def get_queryset(self, request):
@@ -204,7 +194,7 @@ class PersonAdmin(admin.ModelAdmin):
@admin.register(Dokument)
class DokumentAdmin(SortableAdminBase, NestedModelAdmin):
class DokumentAdmin(NestedModelAdmin):
actions_on_top=True
inlines = [EinleitungInline, GeltungsbereichInline, VorgabeInline]
filter_horizontal=['autoren','pruefende']

View File

@@ -0,0 +1,34 @@
# Generated by Django 6.0.1 on 2026-02-12 13:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dokumente', '0010_vorgabentable_alter_person_options_vorgabecomment'),
]
operations = [
migrations.AlterModelOptions(
name='einleitung',
options={'ordering': ('order',), 'verbose_name': 'Einleitungs-Abschnitt', 'verbose_name_plural': 'Einleitung'},
),
migrations.AlterModelOptions(
name='geltungsbereich',
options={'ordering': ('order',), 'verbose_name': 'Geltungsbereichs-Abschnitt', 'verbose_name_plural': 'Geltungsbereich'},
),
migrations.AlterModelOptions(
name='person',
options={'ordering': ['name'], 'verbose_name': 'Person', 'verbose_name_plural': 'Personen'},
),
migrations.AlterModelOptions(
name='thema',
options={'verbose_name': 'Thema', 'verbose_name_plural': 'Themen'},
),
migrations.AlterField(
model_name='dokument',
name='aktiv',
field=models.BooleanField(blank=True, default=False),
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 6.0.1 on 2026-02-12 13:43
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('dokumente', '0011_alter_einleitung_options_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='einleitung',
options={'ordering': ['order'], 'verbose_name': 'Einleitungs-Abschnitt', 'verbose_name_plural': 'Einleitung'},
),
migrations.AlterModelOptions(
name='geltungsbereich',
options={'ordering': ['order'], 'verbose_name': 'Geltungsbereichs-Abschnitt', 'verbose_name_plural': 'Geltungsbereich'},
),
]

View File

@@ -261,12 +261,14 @@ class Geltungsbereich(Textabschnitt):
class Meta:
verbose_name_plural="Geltungsbereich"
verbose_name="Geltungsbereichs-Abschnitt"
ordering = ['order']
class Einleitung(Textabschnitt):
einleitung=models.ForeignKey(Dokument,on_delete=models.CASCADE)
class Meta:
verbose_name_plural="Einleitung"
verbose_name="Einleitungs-Abschnitt"
ordering = ['order']
class Checklistenfrage(models.Model):
vorgabe=models.ForeignKey(Vorgabe, on_delete=models.CASCADE, related_name="checklistenfragen")

View File

@@ -15,6 +15,7 @@
<h2>Checkliste</h2>
<p>Die Checkliste wird unter anderem für Audits oder Prüfungen benutzt. Wir empfehlen allen Verantwortlichen, sie als internes Qualitätssicherungs-Hilfsmittel zu verwenden.</p>
<ul class="list-group">
{% for vorgabe in vorgaben %}
{% if vorgabe.checklistenfragen.all %}
@@ -25,4 +26,4 @@
{% endfor %}
</ul>
</body>
</html>
</html>

View File

@@ -1,5 +1,5 @@
from django.contrib import admin
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from nested_admin import NestedStackedInline, NestedModelAdmin, NestedTabularInline
from .models import *
@@ -14,7 +14,7 @@ class ReferenzerklaerungInline(NestedStackedInline):
class ReferenzAdmin(NestedModelAdmin):
list_display = ('Path', 'vorgaben_count')
inlines=[ReferenzerklaerungInline]
search_fields=("name_nummer","Path")
search_fields=("name_nummer","name_text")
readonly_fields=("vorgaben_list",)
fieldsets = (
(None, {
@@ -34,7 +34,7 @@ class ReferenzAdmin(NestedModelAdmin):
vorgaben_list = list(vorgaben) # Evaluate queryset once
count = len(vorgaben_list)
if count == 0:
return format_html("<em>Keine Vorgaben gefunden</em><p><strong>Gesamt: 0 Vorgaben</strong></p>")
return mark_safe("<em>Keine Vorgaben gefunden</em><p><strong>Gesamt: 0 Vorgaben</strong></p>")
html = "<div style='max-height: 300px; overflow-y: auto;'>"
html += "<table style='width: 100%; border-collapse: collapse;'>"
@@ -55,7 +55,7 @@ class ReferenzAdmin(NestedModelAdmin):
html += "</tbody></table>"
html += f"</div><p><strong>Gesamt: {count} Vorgabe{'n' if count != 1 else ''}</strong></p>"
return format_html(html)
return mark_safe(html)
vorgaben_list.short_description = "Zugeordnete Vorgaben"

View File

@@ -13,15 +13,17 @@ class Referenz(MPTTModel):
url = models.URLField(blank=True)
def Path(self):
Temp = "".join([str(x) for x in self.get_ancestors(include_self=True)])+(" (%s)"%self.name_text if self.name_text else "")
return Temp
path = "".join([x.name_nummer for x in self.get_ancestors(include_self=True)])
if self.name_text:
path += " (%s)" % self.name_text
return path
class MPTTMeta:
parent_attr = 'oberreferenz' # optional, but safe
order_insertion_by = ['name_nummer']
def __str__(self):
return self.name_nummer
return self.Path()
class Meta:
verbose_name_plural="Referenzen"

View File

@@ -0,0 +1,17 @@
# Generated by Django 6.0.1 on 2026-02-12 13:27
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('rollen', '0002_alter_rolle_options'),
]
operations = [
migrations.AlterModelOptions(
name='rolle',
options={'verbose_name': 'Rolle', 'verbose_name_plural': 'Rollen'},
),
]