from django.contrib import admin from import_export.admin import ImportExportModelAdmin from django.utils import timezone from core.admin import BaseModelAdmin, SoftDeleteListFilter from core.templatetags.jalali import jdate from apps.events.models import Event, Registration from apps.events.resources import EventResource, RegistrationResource from apps.events.services import verify_registration_attendance @admin.register(Event) class EventAdmin(BaseModelAdmin, ImportExportModelAdmin): resource_class = EventResource list_display = ( 'title', 'event_type', 'start_time_display', 'end_time_display', 'status', 'price_display', 'capacity_display', 'attendees_display', 'is_registration_open_display', ) list_filter = ( 'event_type', 'status', 'is_deleted', 'start_time', 'end_time', 'registration_start_date', 'registration_end_date', SoftDeleteListFilter, ) search_fields = ('title', 'description', 'address') prepopulated_fields = {'slug': ('title',)} date_hierarchy = 'start_time' filter_horizontal = ('gallery_images',) fieldsets = ( ('Event Details', {'fields': ('title', 'slug', 'description', 'featured_image')}), ('Timing & Type', {'fields': ('start_time', 'end_time', 'event_type', 'status')}), ('Location & Online', { 'fields': ('address', 'location', 'online_link'), 'description': 'For On-Site or Hybrid events, provide address and select on map. For Online events, provide a link.', }), ('Registration & Pricing', { 'fields': ('capacity', 'price', 'registration_start_date', 'registration_end_date', 'registration_success_markdown'), 'description': 'Leave capacity blank for unlimited. Leave price blank for free events.', }), ('Gallery', {'fields': ('gallery_images',), 'description': 'Add images related to this event from the Gallery app.'}), ('Soft Delete', {'fields': ('is_deleted', 'deleted_at'), 'classes': ('collapse',)}), ) readonly_fields = ('deleted_at',) actions = BaseModelAdmin.actions + [ 'make_published', 'make_draft', 'make_cancelled', 'make_completed', 'restore_events', ] @admin.display(description='Price') def price_display(self, obj): return obj.price if obj.price is not None else 'رایگان' @admin.display(description='Start') def start_time_display(self, obj): return jdate(obj.start_time) @admin.display(description='End') def end_time_display(self, obj): return jdate(obj.end_time) @admin.display(description='Capacity') def capacity_display(self, obj): return obj.capacity if obj.capacity is not None else 'نامحدود' @admin.display(description='Attendees') def attendees_display(self, obj): return obj.current_attendees_count @admin.display(description='Open', boolean=True) def is_registration_open_display(self, obj): return obj.is_registration_open @admin.action(description='Mark selected events as published') def make_published(self, request, queryset): queryset.update(status=Event.StatusChoices.PUBLISHED) self.message_user(request, f'Published {queryset.count()} events.') @admin.action(description='Mark selected events as draft') def make_draft(self, request, queryset): queryset.update(status=Event.StatusChoices.DRAFT) self.message_user(request, f'Marked {queryset.count()} events as draft.') @admin.action(description='Mark selected events as cancelled') def make_cancelled(self, request, queryset): queryset.update(status=Event.StatusChoices.CANCELLED) self.message_user(request, f'Cancelled {queryset.count()} events.') @admin.action(description='Mark selected events as completed') def make_completed(self, request, queryset): queryset.update(status=Event.StatusChoices.COMPLETED) self.message_user(request, f'Completed {queryset.count()} events.') @admin.action(description='Restore selected events') def restore_events(self, request, queryset): for event in queryset: event.restore() self.message_user(request, f'Restored {queryset.count()} events.') @admin.register(Registration) class RegistrationAdmin(BaseModelAdmin, ImportExportModelAdmin): resource_class = RegistrationResource list_display = ( 'user', 'event', 'status', 'registered_at', 'ticket_id', 'discount_code', 'discount_amount', 'final_price', ) list_filter = ('status', 'event', 'is_deleted', 'registered_at', SoftDeleteListFilter) search_fields = ( 'user__username', 'user__email', 'user__first_name', 'user__last_name', 'event__title', 'ticket_id', ) readonly_fields = ( 'ticket_id', 'registered_at', 'confirmation_notification_sent_at', 'cancellation_notification_sent_at', 'discount_code', 'discount_amount', 'final_price', 'deleted_at', 'attendance_verified_at', 'attendance_verified_by', 'attendance_verification_source', ) fieldsets = ( ('Registration Details', {'fields': ( 'user', 'event', 'status', 'registered_at', 'ticket_id', 'confirmation_notification_sent_at', 'cancellation_notification_sent_at', )}), ('Attendance verification', {'fields': ( 'attendance_verified_at', 'attendance_verified_by', 'attendance_verification_source', )}), ('Pricing & Discount', { 'fields': ('discount_code', 'discount_amount', 'final_price'), 'classes': ('collapse',), }), ('Soft Delete', {'fields': ('is_deleted', 'deleted_at'), 'classes': ('collapse',)}), ) actions = BaseModelAdmin.actions + [ 'confirm_registrations', 'cancel_registrations', 'mark_attended', 'restore_registrations', ] @admin.action(description='Confirm selected registrations') def confirm_registrations(self, request, queryset): count = 0 for registration in queryset: registration.status = Registration.StatusChoices.CONFIRMED registration.save(update_fields=['status']) count += 1 self.message_user(request, f'Confirmed {count} registrations.') @admin.action(description='Cancel selected registrations') def cancel_registrations(self, request, queryset): count = 0 for registration in queryset: registration.status = Registration.StatusChoices.CANCELLED registration.save(update_fields=['status']) count += 1 self.message_user(request, f'Cancelled {count} registrations.') @admin.action(description='Mark selected registrations as attended') def mark_attended(self, request, queryset): count = 0 for registration in queryset: verify_registration_attendance( registration, verifier=request.user, source=Registration.AttendanceVerificationSource.STAFF_ADMIN, ) count += 1 self.message_user(request, f'Marked {count} registrations as attended.') def save_model(self, request, obj, form, change): previous_status = None if change and obj.pk: previous_status = Registration.all_objects.filter(pk=obj.pk).values_list("status", flat=True).first() if ( obj.status == Registration.StatusChoices.ATTENDED and (previous_status != Registration.StatusChoices.ATTENDED or not obj.attendance_verified_at) ): obj.attendance_verified_by = request.user obj.attendance_verified_at = timezone.now() obj.attendance_verification_source = Registration.AttendanceVerificationSource.STAFF_ADMIN super().save_model(request, obj, form, change) @admin.action(description='Restore selected registrations') def restore_registrations(self, request, queryset): for registration in queryset: registration.restore() self.message_user(request, f'Restored {queryset.count()} registrations.')