reservations: Make ReservationAdmin more useful for subclasses

This commit is contained in:
Adam Goldsmith 2024-08-07 12:27:32 -04:00
parent 927e2f4b90
commit d792efc084
4 changed files with 97 additions and 6 deletions

View File

@ -1,5 +1,6 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.humanize.templatetags.humanize import naturaltime from django.contrib.humanize.templatetags.humanize import naturaltime
from django.http import HttpRequest
from django.utils.html import format_html from django.utils.html import format_html
from django_object_actions import ( from django_object_actions import (
@ -223,3 +224,9 @@ class EventAdmin(DjangoObjectActions, admin.ModelAdmin):
def has_delete_permission(self, request, obj=None): def has_delete_permission(self, request, obj=None):
return False return False
@admin.register(EventMeetingTime)
class EventMeetingTimeAdmin(admin.ModelAdmin):
def has_module_permission(self, request: HttpRequest) -> bool:
return False

View File

@ -626,6 +626,9 @@ class EventMeetingTime(Reservation):
EventExt, on_delete=models.CASCADE, related_name="meeting_times" EventExt, on_delete=models.CASCADE, related_name="meeting_times"
) )
def get_title(self) -> str:
return self.event.unescaped_title
# TODO: should probably do some validation in python to enforce # TODO: should probably do some validation in python to enforce
# - uniqueness and non-overlapping (per event) # - uniqueness and non-overlapping (per event)
# - min/max start/end time == event start end # - min/max start/end time == event start end

View File

@ -1,6 +1,13 @@
from django.contrib import admin from typing import Any
from .models import Reservation, Resource, UserReservation from django.contrib import admin
from django.contrib.admin.views.main import ChangeList
from django.db import models
from django.db.models import QuerySet
from django.http import HttpRequest
from django.urls import reverse
from .models import ExternalReservation, Reservation, Resource, UserReservation
@admin.register(Resource) @admin.register(Resource)
@ -11,18 +18,83 @@ class ResourceAdmin(admin.ModelAdmin):
show_facets = admin.ShowFacets.ALWAYS show_facets = admin.ShowFacets.ALWAYS
class ReservationTypeFilter(admin.SimpleListFilter):
"""Filter by subclass"""
title = "Reservation Type"
parameter_name = "type"
# TODO: this could be automatic
_subtypes = {
"eventmeetingtime": "Event Meeting Time",
"userreservation": "User Reservation",
"externalreservation": "External Reservation",
}
def lookups(
self, request: HttpRequest, model_admin: admin.ModelAdmin
) -> list[tuple[str, str]]:
return [
("generic", "Generic Reservation"),
] + list(self._subtypes.items())
def queryset(self, request: HttpRequest, queryset: QuerySet) -> QuerySet:
if self.value() in self._subtypes:
return queryset.filter(**{f"{self.value()}__isnull": False})
elif self.value() == "generic":
return queryset.filter(
**{f"{subtype}__isnull": True for subtype in self._subtypes}
)
else:
return queryset
class SubclassChangeList(ChangeList):
def url_for_result(self, result: models.Model) -> str:
opts = type(result)._meta
return reverse(
f"admin:{opts.app_label}_{opts.model_name}_change",
args=(result.pk,),
current_app=self.model_admin.admin_site.name,
)
@admin.register(Reservation) @admin.register(Reservation)
class ReservationAdmin(admin.ModelAdmin): class ReservationAdmin(admin.ModelAdmin):
list_display = ["_resources", "start", "end"] list_display = ["_title", "_resources", "start", "end"]
readonly_fields = ["google_calendar_event_id"] readonly_fields = ["google_calendar_event_id"]
list_filter = ["resources"] list_filter = ["resources", ReservationTypeFilter]
show_facets = admin.ShowFacets.ALWAYS show_facets = admin.ShowFacets.ALWAYS
date_hierarchy = "start"
def get_queryset(self, request):
return (
super()
.get_queryset(request)
.select_related("eventmeetingtime__event")
.prefetch_related("resources")
.select_subclasses()
)
@admin.display()
def _title(self, obj: Reservation):
return obj.get_title()
@admin.display() @admin.display()
def _resources(self, obj: Reservation): def _resources(self, obj: Reservation):
return list(obj.resources.all()) or None return list(obj.resources.all()) or None
def get_changelist(self, request: HttpRequest, **kwargs: Any) -> type[ChangeList]:
return SubclassChangeList
@admin.register(UserReservation) @admin.register(UserReservation)
class UserReservationAdmin(ReservationAdmin): class UserReservationAdmin(admin.ModelAdmin):
list_display = ["_resources", "user", "start", "end"] def has_module_permission(self, request: HttpRequest) -> bool:
return False
@admin.register(ExternalReservation)
class ExternalReservationAdmin(admin.ModelAdmin):
def has_module_permission(self, request: HttpRequest) -> bool:
return False

View File

@ -98,6 +98,9 @@ class Reservation(models.Model):
resources = ", ".join(str(resource) for resource in self.resources.all()) resources = ", ".join(str(resource) for resource in self.resources.all())
return f"{resources}: {self.start} - {self.end}" return f"{resources}: {self.start} - {self.end}"
def get_title(self) -> str:
return "Unknown Reservation"
def make_google_calendar_event(self): def make_google_calendar_event(self):
event = { event = {
"summary": "CMSManage Reservation", "summary": "CMSManage Reservation",
@ -128,6 +131,9 @@ class UserReservation(Reservation):
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.user} | {super().__str__()}" return f"{self.user} | {super().__str__()}"
def get_title(self) -> str:
return str(self.user)
def make_google_calendar_event(self): def make_google_calendar_event(self):
return super().make_google_calendar_event() | { return super().make_google_calendar_event() | {
"summary": str(self.user), "summary": str(self.user),
@ -142,6 +148,9 @@ class ExternalReservation(Reservation):
def __str__(self) -> str: def __str__(self) -> str:
return f'External "{self.title}" | {super().__str__()}' return f'External "{self.title}" | {super().__str__()}'
def get_title(self) -> str:
return str(self.title)
def make_google_calendar_event(self): def make_google_calendar_event(self):
"""This should never be called, as these are reservations from Google Calendar and shouldn't be synced back""" """This should never be called, as these are reservations from Google Calendar and shouldn't be synced back"""
raise AttributeError( raise AttributeError(