2024-09-09 17:17:45 -04:00
|
|
|
from dataclasses import dataclass
|
|
|
|
|
2022-02-03 13:45:58 -05:00
|
|
|
from django.contrib import admin
|
2023-12-21 14:56:38 -05:00
|
|
|
from django.contrib.humanize.templatetags.humanize import naturaltime
|
2024-08-07 12:27:32 -04:00
|
|
|
from django.http import HttpRequest
|
2024-01-02 10:54:29 -05:00
|
|
|
from django.utils.html import format_html
|
2023-12-21 14:56:38 -05:00
|
|
|
|
2024-01-29 21:48:19 -05:00
|
|
|
from django_object_actions import (
|
|
|
|
DjangoObjectActions,
|
|
|
|
action,
|
|
|
|
takes_instance_or_queryset,
|
|
|
|
)
|
2023-12-21 14:56:38 -05:00
|
|
|
from django_q.models import Task
|
2024-01-17 21:17:24 -05:00
|
|
|
from django_q.tasks import async_task
|
2024-09-09 17:17:45 -04:00
|
|
|
from django_vite.templatetags.django_vite import vite_asset_url
|
2024-08-28 15:10:13 -04:00
|
|
|
from simple_history.admin import SimpleHistoryAdmin
|
2022-02-03 13:45:58 -05:00
|
|
|
|
2023-12-30 14:34:55 -05:00
|
|
|
from .models import (
|
|
|
|
Event,
|
|
|
|
EventExt,
|
|
|
|
EventInstructor,
|
2024-04-05 14:10:08 -04:00
|
|
|
EventInvoice,
|
2024-01-17 21:17:24 -05:00
|
|
|
EventMeetingTime,
|
|
|
|
Flag,
|
|
|
|
Member,
|
|
|
|
Transaction,
|
2023-12-30 14:34:55 -05:00
|
|
|
)
|
2024-08-28 17:07:27 -04:00
|
|
|
from .tasks.scrape import scrape_event_details, scrape_events, scrape_membershipworks
|
2024-02-08 15:45:02 -05:00
|
|
|
from .tasks.ucsAccounts import sync_accounts
|
2022-02-03 13:45:58 -05:00
|
|
|
|
|
|
|
|
2024-08-28 16:43:47 -04:00
|
|
|
class TaskLabel:
|
|
|
|
def __init__(self, label: str, task) -> None:
|
|
|
|
self.label = label
|
|
|
|
self.task = task
|
|
|
|
|
|
|
|
def __str__(self) -> str:
|
|
|
|
try:
|
|
|
|
last_run = naturaltime(
|
|
|
|
Task.objects.filter(group=self.task.q_task_group)
|
|
|
|
.values_list("started", flat=True)
|
|
|
|
.latest("started")
|
|
|
|
)
|
|
|
|
except Task.DoesNotExist:
|
|
|
|
last_run = "Never"
|
|
|
|
return f"{self.label} [Last Run {last_run}]"
|
|
|
|
|
|
|
|
|
|
|
|
def run_task_action(admin: admin.ModelAdmin, label: str, task):
|
|
|
|
@action(label=TaskLabel(label, task))
|
|
|
|
def action_func(request, obj):
|
|
|
|
async_task(task, group=task.q_task_group)
|
|
|
|
admin.message_user(
|
|
|
|
request,
|
|
|
|
"Queued task, please wait a few seconds/minutes then refresh the page",
|
|
|
|
)
|
|
|
|
|
|
|
|
return action_func
|
|
|
|
|
|
|
|
|
2024-08-28 15:10:13 -04:00
|
|
|
class ReadOnlyAdminMixin:
|
2022-02-03 13:45:58 -05:00
|
|
|
def has_add_permission(self, request, obj=None):
|
|
|
|
return False
|
|
|
|
|
2022-02-10 16:51:32 -05:00
|
|
|
def has_change_permission(self, request, obj=None):
|
|
|
|
return False
|
|
|
|
|
2022-02-03 13:45:58 -05:00
|
|
|
def has_delete_permission(self, request, obj=None):
|
|
|
|
return False
|
2022-02-10 16:51:32 -05:00
|
|
|
|
|
|
|
|
2024-08-28 15:10:13 -04:00
|
|
|
class BaseMembershipWorksAdmin(
|
|
|
|
DjangoObjectActions, ReadOnlyAdminMixin, SimpleHistoryAdmin
|
|
|
|
):
|
2024-02-08 15:45:02 -05:00
|
|
|
changelist_actions = ("refresh_membershipworks_data", "sync_ucs_accounts")
|
2023-12-21 14:56:38 -05:00
|
|
|
|
2024-08-28 16:43:47 -04:00
|
|
|
@property
|
|
|
|
def refresh_membershipworks_data(self):
|
|
|
|
return run_task_action(self, "Refresh Data", scrape_membershipworks)
|
2023-12-21 14:56:38 -05:00
|
|
|
|
2024-08-28 16:43:47 -04:00
|
|
|
@property
|
|
|
|
def sync_ucs_accounts(self):
|
|
|
|
return run_task_action(self, "Sync UCS Accounts", sync_accounts)
|
2024-02-08 15:45:02 -05:00
|
|
|
|
2023-12-21 14:56:38 -05:00
|
|
|
|
2022-02-10 16:51:32 -05:00
|
|
|
class MemberFlagInline(admin.TabularInline):
|
|
|
|
model = Member.flags.through
|
|
|
|
|
|
|
|
|
|
|
|
@admin.register(Member)
|
2023-12-21 14:56:38 -05:00
|
|
|
class MemberAdmin(BaseMembershipWorksAdmin):
|
2023-03-31 23:54:16 -04:00
|
|
|
search_fields = ["^first_name", "^last_name", "^account_name"]
|
2022-02-10 16:51:32 -05:00
|
|
|
inlines = [MemberFlagInline]
|
|
|
|
|
|
|
|
|
|
|
|
@admin.register(Flag)
|
2023-12-21 14:56:38 -05:00
|
|
|
class FlagAdmin(BaseMembershipWorksAdmin):
|
2022-02-10 16:51:32 -05:00
|
|
|
inlines = [MemberFlagInline]
|
2023-01-19 19:02:03 -05:00
|
|
|
list_display = ["name", "type"]
|
|
|
|
list_filter = ["type"]
|
2023-12-04 13:08:36 -05:00
|
|
|
show_facets = admin.ShowFacets.ALWAYS
|
2023-01-19 19:02:03 -05:00
|
|
|
search_fields = ["name"]
|
2023-12-19 23:42:46 -05:00
|
|
|
|
|
|
|
|
|
|
|
@admin.register(Transaction)
|
2023-12-21 14:56:38 -05:00
|
|
|
class TransactionAdmin(BaseMembershipWorksAdmin):
|
2023-12-19 23:42:46 -05:00
|
|
|
list_display = ["timestamp", "member", "name", "type", "sum", "note"]
|
2024-08-07 13:01:28 -04:00
|
|
|
list_select_related = ["member"]
|
2023-12-19 23:42:46 -05:00
|
|
|
list_filter = ["type"]
|
|
|
|
show_facets = admin.ShowFacets.ALWAYS
|
2023-12-29 18:19:58 -05:00
|
|
|
search_fields = ["member", "name", "type", "note"]
|
|
|
|
date_hierarchy = "timestamp"
|
2023-12-30 14:34:55 -05:00
|
|
|
|
|
|
|
|
|
|
|
class EventMeetingTimeInline(admin.TabularInline):
|
|
|
|
model = EventMeetingTime
|
2024-08-05 22:48:36 -04:00
|
|
|
fields = ["start", "end", "duration", "resources"]
|
|
|
|
readonly_fields = ["duration"]
|
|
|
|
autocomplete_fields = ["resources"]
|
2023-12-30 14:34:55 -05:00
|
|
|
extra = 0
|
|
|
|
min_num = 1
|
|
|
|
|
|
|
|
|
|
|
|
@admin.register(EventInstructor)
|
|
|
|
class EventInstructorAdmin(admin.ModelAdmin):
|
|
|
|
autocomplete_fields = ["member"]
|
2024-01-29 21:49:45 -05:00
|
|
|
search_fields = ["name", "member__account_name"]
|
2024-08-07 13:01:28 -04:00
|
|
|
list_select_related = ["member"]
|
2023-12-30 14:34:55 -05:00
|
|
|
|
|
|
|
|
2024-05-24 11:18:57 -04:00
|
|
|
@admin.register(EventInvoice)
|
|
|
|
class EventInvoiceAdmin(admin.ModelAdmin):
|
|
|
|
model = EventInvoice
|
|
|
|
list_display = [
|
|
|
|
"uuid",
|
|
|
|
"event",
|
2024-08-07 13:18:05 -04:00
|
|
|
"event__start",
|
|
|
|
"event__end",
|
|
|
|
"event__instructor",
|
2024-05-24 11:18:57 -04:00
|
|
|
"date_submitted",
|
|
|
|
"date_paid",
|
|
|
|
"amount",
|
|
|
|
]
|
2024-08-07 13:01:28 -04:00
|
|
|
list_select_related = ["event__instructor__member"]
|
2024-05-24 11:18:57 -04:00
|
|
|
list_filter = [
|
|
|
|
("date_paid", admin.EmptyFieldListFilter),
|
|
|
|
]
|
|
|
|
show_facets = admin.ShowFacets.ALWAYS
|
|
|
|
search_fields = [
|
|
|
|
"uuid",
|
|
|
|
"event__eid",
|
|
|
|
"event__title",
|
|
|
|
"event__url",
|
|
|
|
"event__instructor__name",
|
|
|
|
"event__instructor__member__account_name",
|
|
|
|
]
|
|
|
|
date_hierarchy = "date_submitted"
|
|
|
|
|
|
|
|
|
2024-04-05 14:10:08 -04:00
|
|
|
class EventInvoiceInline(admin.StackedInline):
|
|
|
|
model = EventInvoice
|
|
|
|
|
|
|
|
|
2023-12-30 14:34:55 -05:00
|
|
|
@admin.register(EventExt)
|
2024-01-29 21:48:19 -05:00
|
|
|
class EventAdmin(DjangoObjectActions, admin.ModelAdmin):
|
2024-04-05 14:10:08 -04:00
|
|
|
inlines = [EventInvoiceInline, EventMeetingTimeInline]
|
2023-12-30 14:34:55 -05:00
|
|
|
list_display = [
|
2024-04-04 00:23:06 -04:00
|
|
|
"unescaped_title",
|
2023-12-30 14:34:55 -05:00
|
|
|
"start",
|
|
|
|
"duration",
|
|
|
|
"count",
|
|
|
|
"cap",
|
|
|
|
"category",
|
2024-09-09 20:17:45 -04:00
|
|
|
"meeting_times_match_event",
|
2024-01-02 10:54:29 -05:00
|
|
|
]
|
|
|
|
list_filter = [
|
|
|
|
"category",
|
|
|
|
"calendar",
|
2023-12-30 14:34:55 -05:00
|
|
|
"venue",
|
2024-01-02 10:54:29 -05:00
|
|
|
("materials_fee", admin.EmptyFieldListFilter),
|
2023-12-30 14:34:55 -05:00
|
|
|
]
|
|
|
|
show_facets = admin.ShowFacets.ALWAYS
|
|
|
|
search_fields = ["eid", "title", "url"]
|
|
|
|
date_hierarchy = "start"
|
2024-01-02 19:26:06 -05:00
|
|
|
autocomplete_fields = ["instructor"]
|
2024-01-29 21:48:19 -05:00
|
|
|
change_actions = ["fetch_details"]
|
|
|
|
actions = ["fetch_details"]
|
2024-08-28 17:07:27 -04:00
|
|
|
changelist_actions = ["refresh_membershipworks_data"]
|
2023-12-30 14:34:55 -05:00
|
|
|
|
2024-08-28 16:48:50 -04:00
|
|
|
fieldsets = [
|
|
|
|
(
|
|
|
|
None,
|
|
|
|
{
|
|
|
|
"fields": [
|
|
|
|
"instructor",
|
|
|
|
"materials_fee",
|
|
|
|
"materials_fee_included_in_price",
|
|
|
|
"instructor_percentage",
|
|
|
|
"instructor_flat_rate",
|
|
|
|
("should_survey", "survey_email_sent"),
|
2024-09-09 19:45:52 -04:00
|
|
|
"links",
|
2024-09-09 20:17:45 -04:00
|
|
|
"meeting_times_match_event",
|
2024-08-28 16:48:50 -04:00
|
|
|
]
|
|
|
|
},
|
|
|
|
),
|
|
|
|
(
|
|
|
|
"Details",
|
|
|
|
{
|
|
|
|
"classes": ["collapse"],
|
|
|
|
"fields": [
|
|
|
|
"eid",
|
2024-09-09 19:45:52 -04:00
|
|
|
"url",
|
2024-08-28 16:48:50 -04:00
|
|
|
"start",
|
|
|
|
"end",
|
|
|
|
"duration",
|
|
|
|
"count",
|
|
|
|
"cap",
|
|
|
|
"category",
|
|
|
|
"calendar",
|
|
|
|
"venue",
|
|
|
|
"occurred",
|
|
|
|
],
|
|
|
|
},
|
|
|
|
),
|
|
|
|
(
|
|
|
|
"Advanced details",
|
|
|
|
{
|
|
|
|
"classes": ["collapse"],
|
|
|
|
"fields": ["details_timestamp", "details", "registrations"],
|
|
|
|
},
|
|
|
|
),
|
|
|
|
]
|
|
|
|
|
2024-09-09 17:17:45 -04:00
|
|
|
class Media:
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
class LazyViteAssetUrl(str):
|
|
|
|
asset: str
|
|
|
|
|
|
|
|
def __str__(self) -> str:
|
|
|
|
return vite_asset_url(self.asset)
|
|
|
|
|
|
|
|
js = [
|
|
|
|
LazyViteAssetUrl(
|
|
|
|
"membershipworks/js/event_meeting_time_admin_helper.entry.ts"
|
|
|
|
)
|
|
|
|
]
|
|
|
|
|
2024-09-09 20:17:45 -04:00
|
|
|
def get_queryset(self, request):
|
|
|
|
return EventExt.objects.with_meeting_times_match_event()
|
|
|
|
|
2024-08-28 17:07:27 -04:00
|
|
|
@property
|
|
|
|
def refresh_membershipworks_data(self):
|
|
|
|
return run_task_action(self, "Refresh Data", scrape_events)
|
|
|
|
|
2024-08-07 13:01:28 -04:00
|
|
|
def get_readonly_fields(self, request: HttpRequest, obj: EventExt) -> list[str]:
|
2024-01-02 10:54:29 -05:00
|
|
|
fields = []
|
|
|
|
for field in Event._meta.get_fields():
|
|
|
|
if field.auto_created or field.many_to_many or not field.concrete:
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
fields.append(field.name)
|
|
|
|
fields.insert(fields.index("end") + 1, "duration")
|
2024-09-09 19:45:52 -04:00
|
|
|
fields += [
|
|
|
|
"links",
|
|
|
|
"details_timestamp",
|
|
|
|
"details",
|
|
|
|
"registrations",
|
2024-09-09 20:17:45 -04:00
|
|
|
"meeting_times_match_event",
|
2024-09-09 19:45:52 -04:00
|
|
|
]
|
2024-01-02 10:54:29 -05:00
|
|
|
return fields
|
|
|
|
|
2024-04-04 00:23:06 -04:00
|
|
|
@admin.display(ordering="title")
|
|
|
|
def unescaped_title(self, obj):
|
|
|
|
return obj.unescaped_title
|
|
|
|
|
2024-01-02 10:54:29 -05:00
|
|
|
@admin.display(ordering="duration")
|
2023-12-30 14:34:55 -05:00
|
|
|
def duration(self, obj):
|
|
|
|
return obj.duration
|
|
|
|
|
2024-09-09 20:17:45 -04:00
|
|
|
@admin.display(
|
|
|
|
boolean=True,
|
|
|
|
description="Meeting times match event start/end",
|
|
|
|
ordering="meeting_times_match_event",
|
|
|
|
)
|
|
|
|
def meeting_times_match_event(self, obj) -> bool:
|
|
|
|
return obj.meeting_times_match_event
|
|
|
|
|
2024-09-09 19:45:52 -04:00
|
|
|
@admin.display(description="MembershipWorks links")
|
|
|
|
def links(self, obj):
|
2024-01-02 10:54:29 -05:00
|
|
|
return format_html(
|
2024-09-09 19:45:52 -04:00
|
|
|
'<a href="https://membershipworks.com/admin/#!event/admin/{0}">Admin</a> | '
|
|
|
|
'<a href="https://claremontmakerspace.org/events/#!event/{0}">Event List</a>',
|
2024-01-02 10:54:29 -05:00
|
|
|
obj.url,
|
|
|
|
)
|
|
|
|
|
2024-01-29 21:48:19 -05:00
|
|
|
@takes_instance_or_queryset
|
|
|
|
def fetch_details(self, request, queryset):
|
|
|
|
scrape_event_details(queryset)
|
|
|
|
|
2023-12-30 14:34:55 -05:00
|
|
|
def has_add_permission(self, request, obj=None):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def has_delete_permission(self, request, obj=None):
|
|
|
|
return False
|
2024-08-07 12:27:32 -04:00
|
|
|
|
|
|
|
|
|
|
|
@admin.register(EventMeetingTime)
|
|
|
|
class EventMeetingTimeAdmin(admin.ModelAdmin):
|
|
|
|
def has_module_permission(self, request: HttpRequest) -> bool:
|
|
|
|
return False
|