forked from beba/foerderbarometer
Add admin integration for ProjectRequest with approve/decline actions and custom ApproveActionForm
This commit is contained in:
parent
4e6906e318
commit
14717c8318
147
input/admin.py
147
input/admin.py
|
|
@ -2,6 +2,14 @@ import csv
|
|||
|
||||
from django.contrib import admin
|
||||
from django.http import HttpResponse
|
||||
from .models import ProjectRequest, ProjectsDeclined
|
||||
from .forms import ProjectRequestAdminForm
|
||||
from django.db import models
|
||||
from django import forms
|
||||
from django.contrib import messages
|
||||
from .services import approve_project_request, decline_project_request
|
||||
from django.contrib.admin.helpers import ActionForm
|
||||
|
||||
|
||||
from .models import (
|
||||
Account,
|
||||
|
|
@ -160,6 +168,145 @@ class ListAdmin(admin.ModelAdmin):
|
|||
date_hierarchy = 'granted_date'
|
||||
readonly_fields = ['service_id']
|
||||
|
||||
|
||||
class ApproveActionForm(ActionForm):
|
||||
"""
|
||||
Extra control rendered next to the bulk actions dropdown.
|
||||
Admin must choose an Account (Kostenstelle) when approving requests.
|
||||
"""
|
||||
account_code = forms.ModelChoiceField(queryset=Account.objects.all(), required=True,
|
||||
label='Kostenstelle (Account)')
|
||||
|
||||
|
||||
@admin.register(ProjectRequest)
|
||||
class ProjectRequestAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Admin for incoming project requests (< 1000 EUR).
|
||||
|
||||
- Uses a custom ModelForm to render JSON-backed fields (checkbox multiselects).
|
||||
- Provides two bulk actions: approve (moves to Projects) and decline (moves to Projects_declined).
|
||||
- Enforces that an Account must be selected for approval.
|
||||
"""
|
||||
form = ProjectRequestAdminForm
|
||||
save_as = True
|
||||
list_display = ('name', 'realname', 'email', 'start', 'end', 'cost', 'decision')
|
||||
list_filter = ('decision', 'insurance',)
|
||||
search_fields = ('name', 'realname', 'email')
|
||||
readonly_fields = ('decision', 'decision_date', 'decided_by')
|
||||
|
||||
# Make text areas more comfortable to edit in admin
|
||||
formfield_overrides = {
|
||||
# Increase rows for TextField edit widgets
|
||||
models.TextField: {'widget': forms.Textarea(attrs={'rows': 5, 'cols': 80})},
|
||||
}
|
||||
|
||||
# Show actions at the top (common UX in Django admin)
|
||||
actions = ['approve_selected', 'decline_selected']
|
||||
actions_on_top = True
|
||||
actions_on_bottom = False
|
||||
action_form = ApproveActionForm
|
||||
|
||||
@admin.action(description='Bewilligen → nach „Projects“')
|
||||
def approve_selected(self, request, queryset):
|
||||
"""
|
||||
Bulk-approve selected requests:
|
||||
- Requires an Account (Kostenstelle) chosen via ApproveActionForm.
|
||||
- Delegates the creation/move logic to the service layer.
|
||||
- Counts successes and reports via Django messages.
|
||||
"""
|
||||
account_pk = request.POST.get('account_code')
|
||||
if not account_pk:
|
||||
self.message_user(
|
||||
request,
|
||||
'Bitte eine Kostenstelle auswählen.',
|
||||
level=messages.ERROR
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
account = Account.objects.get(pk=account_pk)
|
||||
except Account.DoesNotExist:
|
||||
self.message_user(
|
||||
request,
|
||||
f'Unbekannte Kostenstelle (Account pk={account_pk}).',
|
||||
level=messages.ERROR
|
||||
)
|
||||
return
|
||||
|
||||
decided_by = request.user.get_username()
|
||||
ok, failed = 0, 0
|
||||
|
||||
for req in queryset:
|
||||
try:
|
||||
# Service call is atomic and locks the row (select_for_update)
|
||||
approve_project_request(req.id, decided_by, account.code)
|
||||
ok += 1
|
||||
except Exception as exc:
|
||||
failed += 1
|
||||
# Show a concise per-object error; keep details in server logs if needed
|
||||
self.message_user(
|
||||
request,
|
||||
f'Fehler beim Bewilligen von „{req}“: {exc}',
|
||||
level=messages.ERROR
|
||||
)
|
||||
|
||||
if ok:
|
||||
self.message_user(
|
||||
request,
|
||||
f'{ok} Antrag/Anträge bewilligt und als Project angelegt.',
|
||||
level=messages.SUCCESS
|
||||
)
|
||||
if failed:
|
||||
self.message_user(
|
||||
request,
|
||||
f'{failed} Antrag/Anträge konnten nicht bewilligt werden.',
|
||||
level=messages.WARNING
|
||||
)
|
||||
|
||||
@admin.action(description='Ablehnen → nach „Projects_declined“')
|
||||
def decline_selected(self, request, queryset):
|
||||
"""
|
||||
Bulk-decline selected requests:
|
||||
- Archives a minimal snapshot to Projects_declined (per ticket).
|
||||
- Delegates the move logic to the service layer.
|
||||
"""
|
||||
ok, failed = 0, 0
|
||||
|
||||
for req in queryset:
|
||||
try:
|
||||
decline_project_request(req.id, reason='')
|
||||
ok += 1
|
||||
except Exception as exc:
|
||||
failed += 1
|
||||
self.message_user(
|
||||
request,
|
||||
f'Fehler beim Ablehnen von „{req}“: {exc}',
|
||||
level=messages.ERROR
|
||||
)
|
||||
|
||||
if ok:
|
||||
self.message_user(
|
||||
request,
|
||||
f'{ok} Antrag/Anträge abgelehnt → „Projects_declined“',
|
||||
level=messages.WARNING
|
||||
)
|
||||
if failed:
|
||||
self.message_user(
|
||||
request,
|
||||
f'{failed} Antrag/Anträge konnten nicht abgelehnt werden.',
|
||||
level=messages.ERROR
|
||||
)
|
||||
|
||||
|
||||
@admin.register(ProjectsDeclined)
|
||||
class ProjectsDeclinedAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Read-only-ish list of declined requests for auditing.
|
||||
"""
|
||||
list_display = ('name', 'realname', 'email', 'decision_date')
|
||||
search_fields = ('name', 'realname', 'email')
|
||||
date_hierarchy = 'decision_date'
|
||||
|
||||
# commented out because of the individual registering to control displays in admin panel
|
||||
|
||||
#admin.site.register([
|
||||
|
|
|
|||
Loading…
Reference in New Issue