Enhance Destinataer functionality: inline editing and improved list view

- Add inline edit mode to destinataer detail view with AJAX save/cancel
- Fix form validation by aligning select choices with model definitions
- Update Destinataer model to make familienzweig and berufsgruppe optional
- Fix StiftungsKonto integration in forms and views
- Redesign destinataer list view with new column layout:
  * Vorname, Nachname, E-Mail, Vierteljährlicher Betrag
  * Letzter Studiennachweis, Unterstützung bestätigt, Aktionen
- Improve form styling and user experience
- Add proper field validation and error handling
- Enhance UI with better badges, icons, and formatting
This commit is contained in:
Stiftung Development
2025-09-19 23:52:26 +02:00
parent 584e2b8554
commit 69128196ef
6 changed files with 91 additions and 43 deletions

View File

@@ -369,14 +369,13 @@ class DestinataerForm(forms.ModelForm):
"haushaltsgroesse": forms.NumberInput( "haushaltsgroesse": forms.NumberInput(
attrs={"class": "form-control", "min": 1} attrs={"class": "form-control", "min": 1}
), ),
# renamed in UI: use vierteljaehrlicher_betrag field
"vermoegen": forms.NumberInput( "vermoegen": forms.NumberInput(
attrs={"class": "form-control", "step": "0.01"} attrs={"class": "form-control", "step": "0.01"}
), ),
"unterstuetzung_bestaetigt": forms.CheckboxInput( "unterstuetzung_bestaetigt": forms.CheckboxInput(
attrs={"class": "form-check-input"} attrs={"class": "form-check-input"}
), ),
"standard_konto": forms.Select(attrs={"class": "form-select"}), "standard_konto": forms.Select(attrs={"class": "form-select"}, choices=[(None, "---")] + [(c.pk, str(c)) for c in getattr(Destinataer, 'konten_queryset', lambda: [])()]),
"vierteljaehrlicher_betrag": forms.NumberInput( "vierteljaehrlicher_betrag": forms.NumberInput(
attrs={"class": "form-control", "step": "0.01"} attrs={"class": "form-control", "step": "0.01"}
), ),
@@ -386,8 +385,27 @@ class DestinataerForm(forms.ModelForm):
"letzter_studiennachweis": forms.DateInput( "letzter_studiennachweis": forms.DateInput(
attrs={"class": "form-control", "type": "date"} attrs={"class": "form-control", "type": "date"}
), ),
"familienzweig": forms.Select(attrs={"class": "form-select"}),
"berufsgruppe": forms.Select(attrs={"class": "form-select"}),
} }
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name, field in self.fields.items():
if field_name not in ["vorname", "nachname"]:
field.required = False
# Set choices for familienzweig and berufsgruppe to match model
self.fields["familienzweig"].choices = [("", "Bitte wählen...")] + list(Destinataer.FAMILIENZWIG_CHOICES)
self.fields["berufsgruppe"].choices = [("", "Bitte wählen...")] + list(Destinataer.BERUFSGRUPPE_CHOICES)
# Set choices for standard_konto to allow blank
self.fields["standard_konto"].empty_label = "---"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name, field in self.fields.items():
if field_name not in ["vorname", "nachname"]:
field.required = False
class LandForm(forms.ModelForm): class LandForm(forms.ModelForm):
"""Form für das Erstellen und Bearbeiten von Ländern""" """Form für das Erstellen und Bearbeiten von Ländern"""

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.0.6 on 2025-09-19 21:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('stiftung', '0028_alter_helpbox_page_key'),
]
operations = [
migrations.AlterField(
model_name='destinataer',
name='berufsgruppe',
field=models.CharField(blank=True, choices=[('student', 'Student/Studentin'), ('wissenschaftler', 'Wissenschaftler/in'), ('künstler', 'Künstler/in'), ('sozialarbeiter', 'Sozialarbeiter/in'), ('umweltschützer', 'Umweltschützer/in'), ('andere', 'Andere')], max_length=20, null=True, verbose_name='Berufsgruppe'),
),
migrations.AlterField(
model_name='destinataer',
name='familienzweig',
field=models.CharField(blank=True, choices=[('hauptzweig', 'Hauptzweig'), ('nebenzweig', 'Nebenzweig'), ('verwandt', 'Verwandt'), ('anderer', 'Anderer')], max_length=100, null=True),
),
]

View File

@@ -199,7 +199,7 @@ class Destinataer(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
familienzweig = models.CharField( familienzweig = models.CharField(
max_length=100, choices=FAMILIENZWIG_CHOICES, default="hauptzweig" max_length=100, choices=FAMILIENZWIG_CHOICES, blank=True, null=True
) )
vorname = models.CharField(max_length=100, verbose_name="Vorname") vorname = models.CharField(max_length=100, verbose_name="Vorname")
nachname = models.CharField(max_length=100, verbose_name="Nachname") nachname = models.CharField(max_length=100, verbose_name="Nachname")
@@ -221,7 +221,8 @@ class Destinataer(models.Model):
berufsgruppe = models.CharField( berufsgruppe = models.CharField(
max_length=20, max_length=20,
choices=BERUFSGRUPPE_CHOICES, choices=BERUFSGRUPPE_CHOICES,
default="andere", blank=True,
null=True,
verbose_name="Berufsgruppe", verbose_name="Berufsgruppe",
) )
ausbildungsstand = models.CharField( ausbildungsstand = models.CharField(

View File

@@ -24,7 +24,7 @@ from rest_framework.response import Response
from .models import (AppConfiguration, CSVImport, Destinataer, from .models import (AppConfiguration, CSVImport, Destinataer,
DestinataerUnterstuetzung, DokumentLink, Foerderung, Land, DestinataerUnterstuetzung, DokumentLink, Foerderung, Land,
LandAbrechnung, LandVerpachtung, Paechter, Person, LandAbrechnung, LandVerpachtung, Paechter, Person,
UnterstuetzungWiederkehrend) StiftungsKonto, UnterstuetzungWiederkehrend)
def get_pdf_generator(): def get_pdf_generator():
@@ -1167,12 +1167,16 @@ def destinataer_detail(request, pk):
destinataer=destinataer destinataer=destinataer
).order_by("-erstellt_am") ).order_by("-erstellt_am")
# Alle verfügbaren StiftungsKonten für das Select-Feld laden
stiftungskonten = StiftungsKonto.objects.all().order_by("kontoname")
context = { context = {
"destinataer": destinataer, "destinataer": destinataer,
"verknuepfte_dokumente": verknuepfte_dokumente, "verknuepfte_dokumente": verknuepfte_dokumente,
"foerderungen": foerderungen, "foerderungen": foerderungen,
"unterstuetzungen": unterstuetzungen, "unterstuetzungen": unterstuetzungen,
"notizen_eintraege": notizen_eintraege, "notizen_eintraege": notizen_eintraege,
"stiftungskonten": stiftungskonten,
} }
return render(request, "stiftung/destinataer_detail.html", context) return render(request, "stiftung/destinataer_detail.html", context)

View File

@@ -87,9 +87,10 @@
</span> </span>
<select name="familienzweig" class="form-select edit-mode" style="display: none;"> <select name="familienzweig" class="form-select edit-mode" style="display: none;">
<option value="">Bitte wählen...</option> <option value="">Bitte wählen...</option>
<option value="hagemann" {% if destinataer.familienzweig == 'hagemann' %}selected{% endif %}>Hagemann</option> <option value="hauptzweig" {% if destinataer.familienzweig == 'hauptzweig' %}selected{% endif %}>Hauptzweig</option>
<option value="bechstein" {% if destinataer.familienzweig == 'bechstein' %}selected{% endif %}>Bechstein</option> <option value="nebenzweig" {% if destinataer.familienzweig == 'nebenzweig' %}selected{% endif %}>Nebenzweig</option>
<option value="other" {% if destinataer.familienzweig == 'other' %}selected{% endif %}>Sonstige</option> <option value="verwandt" {% if destinataer.familienzweig == 'verwandt' %}selected{% endif %}>Verwandt</option>
<option value="anderer" {% if destinataer.familienzweig == 'anderer' %}selected{% endif %}>Anderer</option>
</select> </select>
</p> </p>
</div> </div>
@@ -120,10 +121,12 @@
</span> </span>
<select name="berufsgruppe" class="form-select edit-mode" style="display: none;"> <select name="berufsgruppe" class="form-select edit-mode" style="display: none;">
<option value="">Bitte wählen...</option> <option value="">Bitte wählen...</option>
<option value="student_studentin" {% if destinataer.berufsgruppe == 'student_studentin' %}selected{% endif %}>Student/Studentin</option> <option value="student" {% if destinataer.berufsgruppe == 'student' %}selected{% endif %}>Student/Studentin</option>
<option value="auszubildende" {% if destinataer.berufsgruppe == 'auszubildende' %}selected{% endif %}>Auszubildende/r</option> <option value="wissenschaftler" {% if destinataer.berufsgruppe == 'wissenschaftler' %}selected{% endif %}>Wissenschaftler/in</option>
<option value="berufsanfaenger" {% if destinataer.berufsgruppe == 'berufsanfaenger' %}selected{% endif %}>Berufsanfänger/in</option> <option value="künstler" {% if destinataer.berufsgruppe == 'künstler' %}selected{% endif %}>Künstler/in</option>
<option value="sonstige" {% if destinataer.berufsgruppe == 'sonstige' %}selected{% endif %}>Sonstige</option> <option value="sozialarbeiter" {% if destinataer.berufsgruppe == 'sozialarbeiter' %}selected{% endif %}>Sozialarbeiter/in</option>
<option value="umweltschützer" {% if destinataer.berufsgruppe == 'umweltschützer' %}selected{% endif %}>Umweltschützer/in</option>
<option value="andere" {% if destinataer.berufsgruppe == 'andere' %}selected{% endif %}>Andere</option>
</select> </select>
</p> </p>
</div> </div>
@@ -369,7 +372,12 @@
<em class="text-muted">Nicht angegeben</em> <em class="text-muted">Nicht angegeben</em>
{% endif %} {% endif %}
</span> </span>
<input type="text" name="standard_konto" value="{{ destinataer.standard_konto }}" class="form-control edit-mode" style="display: none;" placeholder="Kontobezeichnung"> <select name="standard_konto" class="form-select edit-mode" style="display: none;">
<option value="">---</option>
{% for konto in stiftungskonten %}
<option value="{{ konto.pk }}" {% if destinataer.standard_konto_id == konto.pk %}selected{% endif %}>{{ konto.kontoname }}</option>
{% endfor %}
</select>
</p> </p>
</div> </div>
</div> </div>

View File

@@ -88,25 +88,22 @@
<thead class="table-light"> <thead class="table-light">
<tr> <tr>
<th> <th>
<a href="?sort=name&dir={% if sort == 'name' and dir != 'desc' %}desc{% else %}asc{% endif %}{% if search_query %}&search={{ search_query }}{% endif %}{% if familienzweig_filter %}&familienzweig={{ familienzweig_filter }}{% endif %}{% if berufsgruppe_filter %}&berufsgruppe={{ berufsgruppe_filter }}{% endif %}{% if aktiv_filter %}&aktiv={{ aktiv_filter }}{% endif %}">Name</a> <a href="?sort=vorname&dir={% if sort == 'vorname' and dir != 'desc' %}desc{% else %}asc{% endif %}{% if search_query %}&search={{ search_query }}{% endif %}{% if familienzweig_filter %}&familienzweig={{ familienzweig_filter }}{% endif %}{% if berufsgruppe_filter %}&berufsgruppe={{ berufsgruppe_filter }}{% endif %}{% if aktiv_filter %}&aktiv={{ aktiv_filter }}{% endif %}">Vorname</a>
</th> </th>
<th> <th>
<a href="?sort=familienzweig&dir={% if sort == 'familienzweig' and dir != 'desc' %}desc{% else %}asc{% endif %}{% if search_query %}&search={{ search_query }}{% endif %}{% if familienzweig_filter %}&familienzweig={{ familienzweig_filter }}{% endif %}{% if berufsgruppe_filter %}&berufsgruppe={{ berufsgruppe_filter }}{% endif %}{% if aktiv_filter %}&aktiv={{ aktiv_filter }}{% endif %}">Familienzweig</a> <a href="?sort=nachname&dir={% if sort == 'nachname' and dir != 'desc' %}desc{% else %}asc{% endif %}{% if search_query %}&search={{ search_query }}{% endif %}{% if familienzweig_filter %}&familienzweig={{ familienzweig_filter }}{% endif %}{% if berufsgruppe_filter %}&berufsgruppe={{ berufsgruppe_filter }}{% endif %}{% if aktiv_filter %}&aktiv={{ aktiv_filter }}{% endif %}">Nachname</a>
</th>
<th>
<a href="?sort=berufsgruppe&dir={% if sort == 'berufsgruppe' and dir != 'desc' %}desc{% else %}asc{% endif %}{% if search_query %}&search={{ search_query }}{% endif %}{% if familienzweig_filter %}&familienzweig={{ familienzweig_filter }}{% endif %}{% if berufsgruppe_filter %}&berufsgruppe={{ berufsgruppe_filter }}{% endif %}{% if aktiv_filter %}&aktiv={{ aktiv_filter }}{% endif %}">Berufsgruppe</a>
</th>
<th>
<a href="?sort=institution&dir={% if sort == 'institution' and dir != 'desc' %}desc{% else %}asc{% endif %}{% if search_query %}&search={{ search_query }}{% endif %}{% if familienzweig_filter %}&familienzweig={{ familienzweig_filter }}{% endif %}{% if berufsgruppe_filter %}&berufsgruppe={{ berufsgruppe_filter }}{% endif %}{% if aktiv_filter %}&aktiv={{ aktiv_filter }}{% endif %}">Institution</a>
</th> </th>
<th> <th>
<a href="?sort=email&dir={% if sort == 'email' and dir != 'desc' %}desc{% else %}asc{% endif %}{% if search_query %}&search={{ search_query }}{% endif %}{% if familienzweig_filter %}&familienzweig={{ familienzweig_filter }}{% endif %}{% if berufsgruppe_filter %}&berufsgruppe={{ berufsgruppe_filter }}{% endif %}{% if aktiv_filter %}&aktiv={{ aktiv_filter }}{% endif %}">E-Mail</a> <a href="?sort=email&dir={% if sort == 'email' and dir != 'desc' %}desc{% else %}asc{% endif %}{% if search_query %}&search={{ search_query }}{% endif %}{% if familienzweig_filter %}&familienzweig={{ familienzweig_filter }}{% endif %}{% if berufsgruppe_filter %}&berufsgruppe={{ berufsgruppe_filter }}{% endif %}{% if aktiv_filter %}&aktiv={{ aktiv_filter }}{% endif %}">E-Mail</a>
</th> </th>
<th> <th>
<a href="?sort=foerderungen&dir={% if sort == 'foerderungen' and dir != 'desc' %}desc{% else %}asc{% endif %}{% if search_query %}&search={{ search_query }}{% endif %}{% if familienzweig_filter %}&familienzweig={{ familienzweig_filter }}{% endif %}{% if berufsgruppe_filter %}&berufsgruppe={{ berufsgruppe_filter }}{% endif %}{% if aktiv_filter %}&aktiv={{ aktiv_filter }}{% endif %}">Förderungen</a> <a href="?sort=vierteljaehrlicher_betrag&dir={% if sort == 'vierteljaehrlicher_betrag' and dir != 'desc' %}desc{% else %}asc{% endif %}{% if search_query %}&search={{ search_query }}{% endif %}{% if familienzweig_filter %}&familienzweig={{ familienzweig_filter }}{% endif %}{% if berufsgruppe_filter %}&berufsgruppe={{ berufsgruppe_filter }}{% endif %}{% if aktiv_filter %}&aktiv={{ aktiv_filter }}{% endif %}">Vierteljährlicher Betrag</a>
</th> </th>
<th> <th>
<a href="?sort=status&dir={% if sort == 'status' and dir != 'desc' %}desc{% else %}asc{% endif %}{% if search_query %}&search={{ search_query }}{% endif %}{% if familienzweig_filter %}&familienzweig={{ familienzweig_filter }}{% endif %}{% if berufsgruppe_filter %}&berufsgruppe={{ berufsgruppe_filter }}{% endif %}{% if aktiv_filter %}&aktiv={{ aktiv_filter }}{% endif %}">Status</a> <a href="?sort=letzter_studiennachweis&dir={% if sort == 'letzter_studiennachweis' and dir != 'desc' %}desc{% else %}asc{% endif %}{% if search_query %}&search={{ search_query }}{% endif %}{% if familienzweig_filter %}&familienzweig={{ familienzweig_filter }}{% endif %}{% if berufsgruppe_filter %}&berufsgruppe={{ berufsgruppe_filter }}{% endif %}{% if aktiv_filter %}&aktiv={{ aktiv_filter }}{% endif %}">Letzter Studiennachweis</a>
</th>
<th>
<a href="?sort=unterstuetzung_bestaetigt&dir={% if sort == 'unterstuetzung_bestaetigt' and dir != 'desc' %}desc{% else %}asc{% endif %}{% if search_query %}&search={{ search_query }}{% endif %}{% if familienzweig_filter %}&familienzweig={{ familienzweig_filter }}{% endif %}{% if berufsgruppe_filter %}&berufsgruppe={{ berufsgruppe_filter }}{% endif %}{% if aktiv_filter %}&aktiv={{ aktiv_filter }}{% endif %}">Unterstützung bestätigt</a>
</th> </th>
<th>Aktionen</th> <th>Aktionen</th>
</tr> </tr>
@@ -115,24 +112,14 @@
{% for destinataer in page_obj %} {% for destinataer in page_obj %}
<tr> <tr>
<td> <td>
<strong>{{ destinataer.nachname }}, {{ destinataer.vorname }}</strong> <strong>{{ destinataer.vorname|default:"-" }}</strong>
</td>
<td>
<strong>{{ destinataer.nachname }}</strong>
{% if destinataer.geburtsdatum %} {% if destinataer.geburtsdatum %}
<br><small class="text-muted">{{ destinataer.geburtsdatum|date:"d.m.Y" }}</small> <br><small class="text-muted">{{ destinataer.geburtsdatum|date:"d.m.Y" }}</small>
{% endif %} {% endif %}
</td> </td>
<td>
<span class="badge bg-info">{{ destinataer.get_familienzweig_display }}</span>
</td>
<td>
<span class="badge bg-secondary">{{ destinataer.get_berufsgruppe_display }}</span>
</td>
<td>
{% if destinataer.institution %}
{{ destinataer.institution }}
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td> <td>
{% if destinataer.email %} {% if destinataer.email %}
<a href="mailto:{{ destinataer.email }}">{{ destinataer.email }}</a> <a href="mailto:{{ destinataer.email }}">{{ destinataer.email }}</a>
@@ -141,17 +128,24 @@
{% endif %} {% endif %}
</td> </td>
<td> <td>
{% if destinataer.total_foerderungen %} {% if destinataer.vierteljaehrlicher_betrag %}
<span class="text-success fw-bold">€{{ destinataer.total_foerderungen|floatformat:2 }}</span> <span class="text-success fw-bold">€{{ destinataer.vierteljaehrlicher_betrag|floatformat:2 }}</span>
{% else %} {% else %}
<span class="text-muted">€0.00</span> <span class="text-muted">-</span>
{% endif %} {% endif %}
</td> </td>
<td> <td>
{% if destinataer.aktiv %} {% if destinataer.letzter_studiennachweis %}
<span class="badge bg-success">Aktiv</span> <span class="badge bg-info">{{ destinataer.letzter_studiennachweis|date:"d.m.Y" }}</span>
{% else %} {% else %}
<span class="badge bg-secondary">Inaktiv</span> <span class="text-muted">-</span>
{% endif %}
</td>
<td>
{% if destinataer.unterstuetzung_bestaetigt %}
<span class="badge bg-success"><i class="fas fa-check me-1"></i>Bestätigt</span>
{% else %}
<span class="badge bg-warning"><i class="fas fa-clock me-1"></i>Ausstehend</span>
{% endif %} {% endif %}
</td> </td>
<td> <td>