Merge branch 'feature/add-request-url-to-services' of gitlab.cosmocode.de:wikimedia/foerderbarometer into cosmocode

This commit is contained in:
Oliver Zander 2025-10-16 16:31:35 +02:00
commit 6a36b293f4
3 changed files with 106 additions and 26 deletions

View File

@ -1,10 +1,8 @@
import csv
from django.contrib import admin
from django.http import HttpResponse
from django.db import models
from django import forms
from django.contrib.admin.helpers import ActionForm
from django.http import HttpResponse
from .forms import BaseProjectForm
from .models import (
@ -27,8 +25,25 @@ from .models import (
)
def export_as_csv(self, request, queryset):
class RequestURLBeforeInternNotesMixin:
"""
Ensures that 'request_url' appears directly before 'intern_notes'.
Works whether 'fields' is explicitly defined or derived from the Model/Form.
"""
def get_fields(self, request, obj=None):
fields = [*super().get_fields(request, obj)]
fields.remove('request_url')
index = fields.index('intern_notes')
fields.insert(index, 'request_url')
return fields
def export_as_csv(self, request, queryset):
meta = self.model._meta
field_names = [field.name for field in meta.fields]
@ -42,6 +57,7 @@ def export_as_csv(self, request, queryset):
return response
export_as_csv.short_description = "Ausgewähltes zu CSV exportieren"
admin.site.add_action(export_as_csv)
@ -73,6 +89,7 @@ class ProjectAdminForm(BaseProjectForm):
@admin.register(Project)
class ProjectAdmin(admin.ModelAdmin):
save_as = True
form = ProjectAdminForm
search_fields = ('name', 'pid','finance_id', 'realname', 'start', 'end', 'participants_estimated', 'participants_real', 'cost', 'status', 'end_quartal')
list_display = ('name', 'pid','finance_id', 'realname', 'start', 'end', 'participants_estimated', 'participants_real', 'cost', 'status', 'end_quartal')
@ -145,7 +162,7 @@ class ProjectDeclinedAdmin(ProjectAdmin):
@admin.register(BusinessCard)
class BusinessCardAdmin(admin.ModelAdmin):
class BusinessCardAdmin(RequestURLBeforeInternNotesMixin, admin.ModelAdmin):
save_as = True
search_fields = ('realname', 'service_id', 'granted', 'granted_date', 'project')
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'project', 'terms_accepted')
@ -153,11 +170,13 @@ class BusinessCardAdmin(admin.ModelAdmin):
# action = ['export_as_csv']
date_hierarchy = 'granted_date'
readonly_fields = ['service_id']
class Media:
js = ('dropdown/js/base.js',)
js = ('dropdown/js/base.js', 'dropdown/js/otrs_link.js')
@admin.register(Literature)
class LiteratureAdmin(admin.ModelAdmin):
class LiteratureAdmin(RequestURLBeforeInternNotesMixin, admin.ModelAdmin):
save_as = True
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'terms_accepted')
@ -165,6 +184,9 @@ class LiteratureAdmin(admin.ModelAdmin):
date_hierarchy = 'granted_date'
readonly_fields = ['service_id']
class Media:
js = ('dropdown/js/otrs_link.js',)
@admin.register(Account)
class AccountAdmin(admin.ModelAdmin):
@ -175,15 +197,16 @@ class AccountAdmin(admin.ModelAdmin):
class HonoraryCertificateAdmin(admin.ModelAdmin):
save_as = True
search_fields = ['realname', 'granted', 'project__name', 'project__pid']
list_display = ('realname', 'granted','project')
list_display = ('realname', 'granted', 'project')
date_hierarchy = 'granted_date'
autocomplete_fields = ['project']
class Media:
js = ('dropdown/js/otrs_link.js',)
@admin.register(Library, ELiterature, Software)
class LibraryAdmin(admin.ModelAdmin):
class LibraryAdmin(RequestURLBeforeInternNotesMixin, admin.ModelAdmin):
save_as = True
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
list_display = ('realname', 'service_id', 'granted', 'granted_date')
@ -192,6 +215,9 @@ class LibraryAdmin(admin.ModelAdmin):
readonly_fields = ['service_id']
exclude = ['type']
class Media:
js = ('dropdown/js/otrs_link.js',)
def get_queryset(self, request):
return super().get_queryset(request).filter(type=self.model.TYPE)
@ -207,7 +233,7 @@ class LibraryAdmin(admin.ModelAdmin):
@admin.register(IFG)
class IFGAdmin(admin.ModelAdmin):
class IFGAdmin(RequestURLBeforeInternNotesMixin, admin.ModelAdmin):
save_as = True
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
list_display = ('realname', 'service_id', 'granted', 'granted_date')
@ -215,11 +241,16 @@ class IFGAdmin(admin.ModelAdmin):
date_hierarchy = 'granted_date'
readonly_fields = ['service_id']
class Media:
js = ('dropdown/js/otrs_link.js',)
@admin.register(Travel)
class TravelAdmin(admin.ModelAdmin):
save_as = True
search_fields = ['realname', 'service_id', 'granted_date', 'project__name', 'project__pid']
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'project_end', 'project', 'project_end_quartal')
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'project_end', 'project',
'project_end_quartal')
list_display_links = ('realname', 'project')
date_hierarchy = 'project_end'
autocomplete_fields = ['project']
@ -230,7 +261,7 @@ class TravelAdmin(admin.ModelAdmin):
@admin.register(Email)
class EmailAdmin(admin.ModelAdmin):
class EmailAdmin(RequestURLBeforeInternNotesMixin, admin.ModelAdmin):
save_as = True
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'terms_accepted')
@ -238,24 +269,16 @@ class EmailAdmin(admin.ModelAdmin):
date_hierarchy = 'granted_date'
radio_fields = {'adult': admin.VERTICAL}
readonly_fields = ['service_id']
class Media:
js = ('dropdown/js/base.js',)
js = ('dropdown/js/base.js', 'dropdown/js/otrs_link.js')
@admin.register(List)
class ListAdmin(admin.ModelAdmin):
class ListAdmin(RequestURLBeforeInternNotesMixin, admin.ModelAdmin):
save_as = True
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'terms_accepted')
list_display_links = ('realname', 'service_id')
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)')

View File

@ -0,0 +1,43 @@
# Generated by Django 5.2.5 on 2025-10-08 10:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('input', '0102_project_request_declined'),
]
operations = [
migrations.AddField(
model_name='businesscard',
name='request_url',
field=models.URLField(max_length=2000, null=True, verbose_name='Antrag (URL)'),
),
migrations.AddField(
model_name='email',
name='request_url',
field=models.URLField(max_length=2000, null=True, verbose_name='Antrag (URL)'),
),
migrations.AddField(
model_name='ifg',
name='request_url',
field=models.URLField(max_length=2000, null=True, verbose_name='Antrag (URL)'),
),
migrations.AddField(
model_name='library',
name='request_url',
field=models.URLField(max_length=2000, null=True, verbose_name='Antrag (URL)'),
),
migrations.AddField(
model_name='list',
name='request_url',
field=models.URLField(max_length=2000, null=True, verbose_name='Antrag (URL)'),
),
migrations.AddField(
model_name='literature',
name='request_url',
field=models.URLField(max_length=2000, null=True, verbose_name='Antrag (URL)'),
),
]

View File

@ -28,6 +28,20 @@ class TermsConsentMixin(models.Model):
abstract = True
class RequestUrlMixin(models.Model):
"""
Abstract mixin for adding an OTRS request URL field to admin models.
This field stores a direct link to the related OTRS ticket.
Note: OTRS links may contain semicolons, which must not be URL-encoded.
"""
request_url = models.URLField(max_length=2000, null=True, verbose_name='Antrag (URL)')
class Meta:
abstract = True
class Volunteer(models.Model):
realname = models.CharField(max_length=200, null=True, verbose_name='Realname',
help_text='Bitte gib deinen Vornamen und deinen Nachnamen ein.', default='')
@ -370,7 +384,7 @@ def get_project_end(sender, instance, **kwargs):
# abstract base class for Library and IFG
class Grant(Extern):
class Grant(RequestUrlMixin, Extern):
cost = models.CharField(max_length=10, verbose_name='Kosten',
help_text='Bitte gib die ungefähr zu erwartenden Kosten in Euro an.')
notes = models.TextField(max_length=1000, blank=True, verbose_name='Anmerkungen',
@ -496,7 +510,7 @@ DOMAIN_CHOICES = {
}
class Domain(Extern):
class Domain(RequestUrlMixin, Extern):
domain = models.CharField(max_length=10,
choices=DOMAIN_CHOICES.items(),
default='PEDIA')
@ -553,7 +567,7 @@ BC_VARIANT = {
}
class BusinessCard(TermsConsentMixin, Extern):
class BusinessCard(RequestUrlMixin, TermsConsentMixin, Extern):
project = models.CharField(max_length=20, choices=PROJECT_CHOICE.items(),
default='PEDIA', verbose_name='Wikimedia-Projekt',
help_text='Für welches Wikimedia-Projekt möchtest Du Visitenkarten?')