-
Notifications
You must be signed in to change notification settings - Fork 22
Flexible Grant categories #4420
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Generated by Django 5.1.4 on 2025-07-27 14:30 | ||
|
|
||
| from django.db import migrations | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ('conferences', '0054_conference_frontend_revalidate_secret_and_more'), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.RemoveField( | ||
| model_name='conference', | ||
| name='grants_default_accommodation_amount', | ||
| ), | ||
| migrations.RemoveField( | ||
| model_name='conference', | ||
| name='grants_default_ticket_amount', | ||
| ), | ||
| migrations.RemoveField( | ||
| model_name='conference', | ||
| name='grants_default_travel_from_europe_amount', | ||
| ), | ||
| migrations.RemoveField( | ||
| model_name='conference', | ||
| name='grants_default_travel_from_extra_eu_amount', | ||
| ), | ||
| migrations.RemoveField( | ||
| model_name='conference', | ||
| name='grants_default_travel_from_italy_amount', | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,45 +1,53 @@ | ||
| import logging | ||
| from django.db import transaction | ||
| from custom_admin.audit import ( | ||
| create_addition_admin_log_entry, | ||
| create_change_admin_log_entry, | ||
| ) | ||
| from conferences.models.conference_voucher import ConferenceVoucher | ||
| from pycon.constants import UTC | ||
| from custom_admin.admin import ( | ||
| confirm_pending_status, | ||
| reset_pending_status_back_to_status, | ||
| validate_single_conference_selection, | ||
| ) | ||
| from import_export.resources import ModelResource | ||
| from datetime import timedelta | ||
| from typing import Dict, List, Optional | ||
| from countries.filters import CountryFilter | ||
|
|
||
| from django.contrib import admin, messages | ||
| from django.contrib.admin import SimpleListFilter | ||
| from django.db import transaction | ||
| from django.db.models import Exists, F, IntegerField, OuterRef, Sum, Value | ||
| from django.db.models.functions import Coalesce | ||
| from django.db.models.query import QuerySet | ||
| from django.urls import reverse | ||
| from django.utils import timezone | ||
| from django.utils.safestring import mark_safe | ||
| from import_export.admin import ExportMixin | ||
| from import_export.fields import Field | ||
| from users.admin_mixins import ConferencePermissionMixin | ||
| from import_export.resources import ModelResource | ||
|
|
||
| from conferences.models.conference_voucher import ConferenceVoucher | ||
| from countries import countries | ||
| from countries.filters import CountryFilter | ||
| from custom_admin.admin import ( | ||
| confirm_pending_status, | ||
| reset_pending_status_back_to_status, | ||
| validate_single_conference_selection, | ||
| ) | ||
| from custom_admin.audit import ( | ||
| create_addition_admin_log_entry, | ||
| create_change_admin_log_entry, | ||
| ) | ||
| from grants.tasks import ( | ||
| send_grant_reply_approved_email, | ||
| send_grant_reply_rejected_email, | ||
| send_grant_reply_waiting_list_email, | ||
| send_grant_reply_waiting_list_update_email, | ||
| send_grant_reply_rejected_email, | ||
| ) | ||
| from participants.models import Participant | ||
| from pretix import user_has_admission_ticket | ||
| from pycon.constants import UTC | ||
| from schedule.models import ScheduleItem | ||
| from submissions.models import Submission | ||
| from .models import Grant, GrantConfirmPendingStatusProxy | ||
| from django.db.models import Exists, OuterRef | ||
| from pretix import user_has_admission_ticket | ||
|
|
||
| from django.contrib.admin import SimpleListFilter | ||
| from participants.models import Participant | ||
| from django.urls import reverse | ||
| from django.utils.safestring import mark_safe | ||
| from users.admin_mixins import ConferencePermissionMixin | ||
| from visa.models import InvitationLetterRequest | ||
|
|
||
| from .models import ( | ||
| Grant, | ||
| GrantConfirmPendingStatusProxy, | ||
| GrantReimbursement, | ||
| GrantReimbursementCategory, | ||
| ) | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| EXPORT_GRANTS_FIELDS = ( | ||
|
|
@@ -394,6 +402,32 @@ def queryset(self, request, queryset): | |
| return queryset | ||
|
|
||
|
|
||
| @admin.register(GrantReimbursementCategory) | ||
| class GrantReimbursementCategoryAdmin(ConferencePermissionMixin, admin.ModelAdmin): | ||
| list_display = ("__str__", "max_amount", "category", "included_by_default") | ||
| list_filter = ("conference", "category", "included_by_default") | ||
| search_fields = ("category", "name") | ||
|
|
||
|
|
||
| @admin.register(GrantReimbursement) | ||
| class GrantReimbursementAdmin(ConferencePermissionMixin, admin.ModelAdmin): | ||
| list_display = ( | ||
| "grant", | ||
| "category", | ||
| "granted_amount", | ||
| ) | ||
| list_filter = ("grant__conference", "category") | ||
| search_fields = ("grant__full_name", "grant__email") | ||
| autocomplete_fields = ("grant",) | ||
|
|
||
|
|
||
| class GrantReimbursementInline(admin.TabularInline): | ||
| model = GrantReimbursement | ||
| extra = 0 | ||
| autocomplete_fields = ["category"] | ||
| fields = ["category", "granted_amount"] | ||
|
|
||
|
|
||
| @admin.register(Grant) | ||
| class GrantAdmin(ExportMixin, ConferencePermissionMixin, admin.ModelAdmin): | ||
| change_list_template = "admin/grants/grant/change_list.html" | ||
|
|
@@ -406,12 +440,8 @@ class GrantAdmin(ExportMixin, ConferencePermissionMixin, admin.ModelAdmin): | |
| "has_sent_invitation_letter_request", | ||
| "emoji_gender", | ||
| "conference", | ||
| "status", | ||
| "approved_type", | ||
| "ticket_amount", | ||
| "travel_amount", | ||
| "accommodation_amount", | ||
| "total_amount", | ||
| "current_or_pending_status", | ||
| "total_amount_display", | ||
| "country_type", | ||
| "user_has_ticket", | ||
| "has_voucher", | ||
|
|
@@ -425,7 +455,6 @@ class GrantAdmin(ExportMixin, ConferencePermissionMixin, admin.ModelAdmin): | |
| "pending_status", | ||
| "country_type", | ||
| "occupation", | ||
| "approved_type", | ||
| "needs_funds_for_travel", | ||
| "need_visa", | ||
| "need_accommodation", | ||
|
|
@@ -451,6 +480,7 @@ class GrantAdmin(ExportMixin, ConferencePermissionMixin, admin.ModelAdmin): | |
| "delete_selected", | ||
| ] | ||
| autocomplete_fields = ("user",) | ||
| inlines = [GrantReimbursementInline] | ||
|
|
||
| fieldsets = ( | ||
| ( | ||
|
|
@@ -459,12 +489,7 @@ class GrantAdmin(ExportMixin, ConferencePermissionMixin, admin.ModelAdmin): | |
| "fields": ( | ||
| "status", | ||
| "pending_status", | ||
| "approved_type", | ||
| "country_type", | ||
| "ticket_amount", | ||
| "travel_amount", | ||
| "accommodation_amount", | ||
| "total_amount", | ||
| "applicant_reply_sent_at", | ||
| "applicant_reply_deadline", | ||
| "internal_notes", | ||
|
|
@@ -528,6 +553,11 @@ def user_display_name(self, obj): | |
| return obj.user.display_name | ||
| return obj.email | ||
|
|
||
| @admin.display(description="Status") | ||
| def current_or_pending_status(self, obj): | ||
| return obj.current_or_pending_status | ||
|
|
||
|
|
||
| @admin.display( | ||
| description="C", | ||
| ) | ||
|
|
@@ -591,11 +621,22 @@ def has_sent_invitation_letter_request(self, obj: Grant) -> bool: | |
| if obj.has_invitation_letter_request: | ||
| return "📧" | ||
| return "" | ||
| @admin.display(description="Total") | ||
| def total_amount_display(self, obj): | ||
| return f"{obj.total_allocated:.2f}" | ||
|
||
|
|
||
| @admin.display(description="Approved Reimbursements") | ||
| def approved_amounts_display(self, obj): | ||
| return ", ".join( | ||
| f"{r.category.name}: {r.granted_amount}" for r in obj.reimbursements.all() | ||
| ) | ||
|
|
||
| def get_queryset(self, request): | ||
| qs = ( | ||
| super() | ||
| .get_queryset(request) | ||
| .select_related("user") | ||
| .prefetch_related("reimbursements__category") | ||
| .annotate( | ||
| is_proposed_speaker=Exists( | ||
| Submission.objects.non_cancelled().filter( | ||
|
|
@@ -622,6 +663,11 @@ def get_queryset(self, request): | |
| requester_id=OuterRef("user_id"), | ||
| ) | ||
| ), | ||
| total_allocated=Coalesce( | ||
| Sum("reimbursements__granted_amount"), | ||
| Value(0), | ||
| output_field=IntegerField(), | ||
| ), | ||
| ) | ||
| ) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Missing blank line after the method definition. There should be a blank line before the next method definition for consistency with Python style guidelines.