44 create_addition_admin_log_entry ,
55 create_change_admin_log_entry ,
66)
7+ from django .db .models import Value , IntegerField
8+
9+ from django .db .models import Sum
10+ from django .db .models .functions import Coalesce
711from conferences .models .conference_voucher import ConferenceVoucher
812from pycon .constants import UTC
913from custom_admin .admin import (
3034)
3135from schedule .models import ScheduleItem
3236from submissions .models import Submission
33- from .models import Grant , GrantConfirmPendingStatusProxy
37+ from .models import (
38+ Grant ,
39+ GrantConfirmPendingStatusProxy ,
40+ GrantReimbursementCategory ,
41+ GrantReimbursement ,
42+ )
3443from django .db .models import Exists , OuterRef , F
3544from pretix import user_has_admission_ticket
3645
@@ -393,6 +402,32 @@ def queryset(self, request, queryset):
393402 return queryset
394403
395404
405+ @admin .register (GrantReimbursementCategory )
406+ class GrantReimbursementCategoryAdmin (ConferencePermissionMixin , admin .ModelAdmin ):
407+ list_display = ("__str__" , "max_amount" , "category" , "included_by_default" )
408+ list_filter = ("conference" , "category" , "included_by_default" )
409+ search_fields = ("category" , "name" )
410+
411+
412+ @admin .register (GrantReimbursement )
413+ class GrantReimbursementAdmin (ConferencePermissionMixin , admin .ModelAdmin ):
414+ list_display = (
415+ "grant" ,
416+ "category" ,
417+ "granted_amount" ,
418+ )
419+ list_filter = ("grant__conference" , "category" )
420+ search_fields = ("grant__full_name" , "grant__email" )
421+ autocomplete_fields = ("grant" ,)
422+
423+
424+ class GrantReimbursementInline (admin .TabularInline ):
425+ model = GrantReimbursement
426+ extra = 0
427+ autocomplete_fields = ["category" ]
428+ fields = ["category" , "granted_amount" ]
429+
430+
396431@admin .register (Grant )
397432class GrantAdmin (ExportMixin , ConferencePermissionMixin , admin .ModelAdmin ):
398433 change_list_template = "admin/grants/grant/change_list.html"
@@ -405,11 +440,7 @@ class GrantAdmin(ExportMixin, ConferencePermissionMixin, admin.ModelAdmin):
405440 "emoji_gender" ,
406441 "conference" ,
407442 "status" ,
408- "approved_type" ,
409- "ticket_amount" ,
410- "travel_amount" ,
411- "accommodation_amount" ,
412- "total_amount" ,
443+ "total_amount_display" ,
413444 "country_type" ,
414445 "user_has_ticket" ,
415446 "has_voucher" ,
@@ -423,7 +454,6 @@ class GrantAdmin(ExportMixin, ConferencePermissionMixin, admin.ModelAdmin):
423454 "pending_status" ,
424455 "country_type" ,
425456 "occupation" ,
426- "approved_type" ,
427457 "needs_funds_for_travel" ,
428458 "need_visa" ,
429459 "need_accommodation" ,
@@ -449,6 +479,7 @@ class GrantAdmin(ExportMixin, ConferencePermissionMixin, admin.ModelAdmin):
449479 "delete_selected" ,
450480 ]
451481 autocomplete_fields = ("user" ,)
482+ inlines = [GrantReimbursementInline ]
452483
453484 fieldsets = (
454485 (
@@ -457,12 +488,7 @@ class GrantAdmin(ExportMixin, ConferencePermissionMixin, admin.ModelAdmin):
457488 "fields" : (
458489 "status" ,
459490 "pending_status" ,
460- "approved_type" ,
461491 "country_type" ,
462- "ticket_amount" ,
463- "travel_amount" ,
464- "accommodation_amount" ,
465- "total_amount" ,
466492 "applicant_reply_sent_at" ,
467493 "applicant_reply_deadline" ,
468494 "internal_notes" ,
@@ -584,10 +610,22 @@ def user_has_ticket(self, obj: Grant) -> bool:
584610 def has_voucher (self , obj : Grant ) -> bool :
585611 return obj .has_voucher
586612
613+ @admin .display (description = "Total" )
614+ def total_amount_display (self , obj ):
615+ return f"{ obj .total_allocated :.2f} "
616+
617+ @admin .display (description = "Approved Reimbursements" )
618+ def approved_amounts_display (self , obj ):
619+ return ", " .join (
620+ f"{ r .category .name } : { r .granted_amount } " for r in obj .reimbursements .all ()
621+ )
622+
587623 def get_queryset (self , request ):
588624 qs = (
589625 super ()
590626 .get_queryset (request )
627+ .select_related ("user" )
628+ .prefetch_related ("reimbursements__category" )
591629 .annotate (
592630 is_proposed_speaker = Exists (
593631 Submission .objects .non_cancelled ().filter (
@@ -608,6 +646,11 @@ def get_queryset(self, request):
608646 user_id = OuterRef ("user_id" ),
609647 )
610648 ),
649+ total_allocated = Coalesce (
650+ Sum ("reimbursements__granted_amount" ),
651+ Value (0 ),
652+ output_field = IntegerField (),
653+ ),
611654 )
612655 )
613656
0 commit comments