181 lines
8.0 KiB
Python
181 lines
8.0 KiB
Python
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.')
|