forked from beba/foerderbarometer
Merge branch 'feature/add-request-url-to-services' of gitlab.cosmocode.de:wikimedia/foerderbarometer into cosmocode
This commit is contained in:
commit
6a36b293f4
|
|
@ -1,10 +1,8 @@
|
||||||
import csv
|
import csv
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django import forms
|
from django.http import HttpResponse
|
||||||
from django.contrib.admin.helpers import ActionForm
|
|
||||||
|
|
||||||
from .forms import BaseProjectForm
|
from .forms import BaseProjectForm
|
||||||
from .models import (
|
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
|
meta = self.model._meta
|
||||||
field_names = [field.name for field in meta.fields]
|
field_names = [field.name for field in meta.fields]
|
||||||
|
|
||||||
|
|
@ -42,6 +57,7 @@ def export_as_csv(self, request, queryset):
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
export_as_csv.short_description = "Ausgewähltes zu CSV exportieren"
|
export_as_csv.short_description = "Ausgewähltes zu CSV exportieren"
|
||||||
|
|
||||||
admin.site.add_action(export_as_csv)
|
admin.site.add_action(export_as_csv)
|
||||||
|
|
@ -73,6 +89,7 @@ class ProjectAdminForm(BaseProjectForm):
|
||||||
|
|
||||||
@admin.register(Project)
|
@admin.register(Project)
|
||||||
class ProjectAdmin(admin.ModelAdmin):
|
class ProjectAdmin(admin.ModelAdmin):
|
||||||
|
save_as = True
|
||||||
form = ProjectAdminForm
|
form = ProjectAdminForm
|
||||||
search_fields = ('name', 'pid','finance_id', 'realname', 'start', 'end', 'participants_estimated', 'participants_real', 'cost', 'status', 'end_quartal')
|
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')
|
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)
|
@admin.register(BusinessCard)
|
||||||
class BusinessCardAdmin(admin.ModelAdmin):
|
class BusinessCardAdmin(RequestURLBeforeInternNotesMixin, admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ('realname', 'service_id', 'granted', 'granted_date', 'project')
|
search_fields = ('realname', 'service_id', 'granted', 'granted_date', 'project')
|
||||||
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'project', 'terms_accepted')
|
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'project', 'terms_accepted')
|
||||||
|
|
@ -153,11 +170,13 @@ class BusinessCardAdmin(admin.ModelAdmin):
|
||||||
# action = ['export_as_csv']
|
# action = ['export_as_csv']
|
||||||
date_hierarchy = 'granted_date'
|
date_hierarchy = 'granted_date'
|
||||||
readonly_fields = ['service_id']
|
readonly_fields = ['service_id']
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = ('dropdown/js/base.js',)
|
js = ('dropdown/js/base.js', 'dropdown/js/otrs_link.js')
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Literature)
|
@admin.register(Literature)
|
||||||
class LiteratureAdmin(admin.ModelAdmin):
|
class LiteratureAdmin(RequestURLBeforeInternNotesMixin, admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
||||||
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'terms_accepted')
|
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'terms_accepted')
|
||||||
|
|
@ -165,6 +184,9 @@ class LiteratureAdmin(admin.ModelAdmin):
|
||||||
date_hierarchy = 'granted_date'
|
date_hierarchy = 'granted_date'
|
||||||
readonly_fields = ['service_id']
|
readonly_fields = ['service_id']
|
||||||
|
|
||||||
|
class Media:
|
||||||
|
js = ('dropdown/js/otrs_link.js',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Account)
|
@admin.register(Account)
|
||||||
class AccountAdmin(admin.ModelAdmin):
|
class AccountAdmin(admin.ModelAdmin):
|
||||||
|
|
@ -175,15 +197,16 @@ class AccountAdmin(admin.ModelAdmin):
|
||||||
class HonoraryCertificateAdmin(admin.ModelAdmin):
|
class HonoraryCertificateAdmin(admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ['realname', 'granted', 'project__name', 'project__pid']
|
search_fields = ['realname', 'granted', 'project__name', 'project__pid']
|
||||||
list_display = ('realname', 'granted','project')
|
list_display = ('realname', 'granted', 'project')
|
||||||
date_hierarchy = 'granted_date'
|
date_hierarchy = 'granted_date'
|
||||||
autocomplete_fields = ['project']
|
autocomplete_fields = ['project']
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = ('dropdown/js/otrs_link.js',)
|
js = ('dropdown/js/otrs_link.js',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Library, ELiterature, Software)
|
@admin.register(Library, ELiterature, Software)
|
||||||
class LibraryAdmin(admin.ModelAdmin):
|
class LibraryAdmin(RequestURLBeforeInternNotesMixin, admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
||||||
list_display = ('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']
|
readonly_fields = ['service_id']
|
||||||
exclude = ['type']
|
exclude = ['type']
|
||||||
|
|
||||||
|
class Media:
|
||||||
|
js = ('dropdown/js/otrs_link.js',)
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
return super().get_queryset(request).filter(type=self.model.TYPE)
|
return super().get_queryset(request).filter(type=self.model.TYPE)
|
||||||
|
|
||||||
|
|
@ -207,7 +233,7 @@ class LibraryAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
@admin.register(IFG)
|
@admin.register(IFG)
|
||||||
class IFGAdmin(admin.ModelAdmin):
|
class IFGAdmin(RequestURLBeforeInternNotesMixin, admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
||||||
list_display = ('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'
|
date_hierarchy = 'granted_date'
|
||||||
readonly_fields = ['service_id']
|
readonly_fields = ['service_id']
|
||||||
|
|
||||||
|
class Media:
|
||||||
|
js = ('dropdown/js/otrs_link.js',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Travel)
|
@admin.register(Travel)
|
||||||
class TravelAdmin(admin.ModelAdmin):
|
class TravelAdmin(admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ['realname', 'service_id', 'granted_date', 'project__name', 'project__pid']
|
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')
|
list_display_links = ('realname', 'project')
|
||||||
date_hierarchy = 'project_end'
|
date_hierarchy = 'project_end'
|
||||||
autocomplete_fields = ['project']
|
autocomplete_fields = ['project']
|
||||||
|
|
@ -230,7 +261,7 @@ class TravelAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Email)
|
@admin.register(Email)
|
||||||
class EmailAdmin(admin.ModelAdmin):
|
class EmailAdmin(RequestURLBeforeInternNotesMixin, admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
||||||
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'terms_accepted')
|
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'terms_accepted')
|
||||||
|
|
@ -238,24 +269,16 @@ class EmailAdmin(admin.ModelAdmin):
|
||||||
date_hierarchy = 'granted_date'
|
date_hierarchy = 'granted_date'
|
||||||
radio_fields = {'adult': admin.VERTICAL}
|
radio_fields = {'adult': admin.VERTICAL}
|
||||||
readonly_fields = ['service_id']
|
readonly_fields = ['service_id']
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = ('dropdown/js/base.js',)
|
js = ('dropdown/js/base.js', 'dropdown/js/otrs_link.js')
|
||||||
|
|
||||||
|
|
||||||
@admin.register(List)
|
@admin.register(List)
|
||||||
class ListAdmin(admin.ModelAdmin):
|
class ListAdmin(RequestURLBeforeInternNotesMixin, admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
||||||
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'terms_accepted')
|
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'terms_accepted')
|
||||||
list_display_links = ('realname', 'service_id')
|
list_display_links = ('realname', 'service_id')
|
||||||
date_hierarchy = 'granted_date'
|
date_hierarchy = 'granted_date'
|
||||||
readonly_fields = ['service_id']
|
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)')
|
|
||||||
|
|
|
||||||
|
|
@ -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)'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -28,6 +28,20 @@ class TermsConsentMixin(models.Model):
|
||||||
abstract = True
|
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):
|
class Volunteer(models.Model):
|
||||||
realname = models.CharField(max_length=200, null=True, verbose_name='Realname',
|
realname = models.CharField(max_length=200, null=True, verbose_name='Realname',
|
||||||
help_text='Bitte gib deinen Vornamen und deinen Nachnamen ein.', default='')
|
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
|
# abstract base class for Library and IFG
|
||||||
class Grant(Extern):
|
class Grant(RequestUrlMixin, Extern):
|
||||||
cost = models.CharField(max_length=10, verbose_name='Kosten',
|
cost = models.CharField(max_length=10, verbose_name='Kosten',
|
||||||
help_text='Bitte gib die ungefähr zu erwartenden Kosten in Euro an.')
|
help_text='Bitte gib die ungefähr zu erwartenden Kosten in Euro an.')
|
||||||
notes = models.TextField(max_length=1000, blank=True, verbose_name='Anmerkungen',
|
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,
|
domain = models.CharField(max_length=10,
|
||||||
choices=DOMAIN_CHOICES.items(),
|
choices=DOMAIN_CHOICES.items(),
|
||||||
default='PEDIA')
|
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(),
|
project = models.CharField(max_length=20, choices=PROJECT_CHOICE.items(),
|
||||||
default='PEDIA', verbose_name='Wikimedia-Projekt',
|
default='PEDIA', verbose_name='Wikimedia-Projekt',
|
||||||
help_text='Für welches Wikimedia-Projekt möchtest Du Visitenkarten?')
|
help_text='Für welches Wikimedia-Projekt möchtest Du Visitenkarten?')
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue