Add custom admin interfaces for Item, Media, Event, CustomList, CustomListItem, and User models

This commit is contained in:
FuzzyGrim
2025-04-22 22:15:43 +02:00
parent 5f4c93d5c0
commit 4298ccf8ea
8 changed files with 215 additions and 1 deletions

View File

@@ -104,6 +104,7 @@ Note that the setting must include the correct protocol (`https` or `http`), and
| CSRF | Comma separated list of trusted origins for `POST` requests when using reverse proxies, e.g. `https://yamtrack.mydomain.com` or `https://yamtrack.mydomain.com, https://yamtrack.mydomain2.com` |
| REGISTRATION | Default to `True`, set to `False` to disable user registration |
| DEBUG | Default to `False`, set to `True` for debugging |
| ADMIN_ENABLED | Default to `False`, set to `True` to enable the Django admin interface |
| PUID | User ID for the app, default to `1000` |
| PGID | Group ID for the app, default to `1000` |
| TZ | Timezone, like `Europe/Berlin`. Default to `UTC` |

View File

@@ -22,4 +22,4 @@ ignore = "H006,H021"
[tool.coverage.run]
concurrency = ["multiprocessing"]
omit = ["*/migrations/*", "*/tests/*", "*/__init__.py"]
omit = ["*/migrations/*", "*/tests/*", "*/__init__.py", "*/admin.py"]

58
src/app/admin.py Normal file
View File

@@ -0,0 +1,58 @@
import contextlib
from django.apps import apps
from django.contrib import admin
from django.contrib.admin.sites import AlreadyRegistered
from app.models import (
Episode,
Item,
)
# Custom ModelAdmin classes with search functionality
class ItemAdmin(admin.ModelAdmin):
"""Custom admin for Item model with search and filter options."""
search_fields = ["title", "media_id", "source"]
list_display = [
"title",
"media_id",
"season_number",
"episode_number",
"media_type",
"source",
]
list_filter = ["media_type", "source"]
class EpisodeAdmin(admin.ModelAdmin):
"""Custom admin for Episode model with search and filter options."""
search_fields = ["item__title", "related_season__item__title"]
list_display = ["__str__", "end_date", "repeats"]
class MediaAdmin(admin.ModelAdmin):
"""Custom admin for regular media model with search and filter options."""
search_fields = ["item__title", "user__username", "notes"]
list_display = ["__str__", "status", "score", "user"]
list_filter = ["status"]
# Register models with custom admin classes
admin.site.register(Item, ItemAdmin)
admin.site.register(Episode, EpisodeAdmin)
# Auto-register remaining models
app_models = apps.get_app_config("app").get_models()
SpecialModels = ["Item", "Episode", "BasicMedia"]
for model in app_models:
if (
not model.__name__.startswith("Historical")
and model.__name__ not in SpecialModels
):
with contextlib.suppress(AlreadyRegistered):
admin.site.register(model, MediaAdmin)

View File

@@ -238,6 +238,10 @@ AUTH_USER_MODEL = "users.User"
# Yamtrack settings
ADMIN_ENABLED = config("ADMIN_ENABLED", default=False, cast=bool)
if ADMIN_ENABLED:
INSTALLED_APPS += ["django.contrib.admin"]
TZ = zoneinfo.ZoneInfo(TIME_ZONE)
IMG_NONE = "https://www.themoviedb.org/assets/2/v4/glyphicons/basic/glyphicons-basic-38-picture-grey-c2ebdbb057f2a7614185931650f8cee23fa137b93812ccb132b9df511df1cfac.svg"

View File

@@ -10,6 +10,7 @@ from allauth.socialaccount import views as allauth_social_account_views
from allauth.urls import build_provider_urlpatterns
from decorator_include import decorator_include
from django.conf import settings
from django.contrib import admin
from django.contrib.auth.decorators import login_not_required
from django.urls import include, path
@@ -81,6 +82,9 @@ if not settings.SOCIALACCOUNT_ONLY:
# Add the accounts URLs to the main urlpatterns
urlpatterns.append(path("accounts/", include(account_patterns)))
if settings.ADMIN_ENABLED:
urlpatterns.append(path("admin/", admin.site.urls))
# Add debug toolbar if in DEBUG mode
if settings.DEBUG:
urlpatterns.append(path("__debug__/", include("debug_toolbar.urls")))

32
src/events/admin.py Normal file
View File

@@ -0,0 +1,32 @@
from django.contrib import admin
from django.utils import timezone
from events.models import Event
class EventAdmin(admin.ModelAdmin):
"""Admin configuration for the Event model."""
search_fields = ["item__title", "item__media_id"]
list_display = [
"__str__",
"formatted_datetime",
"episode_number",
"notification_sent",
]
list_filter = [
"notification_sent",
"item__media_type",
"item__source",
("datetime", admin.DateFieldListFilter),
]
def formatted_datetime(self, obj):
"""Display datetime in a safe format, handling extreme values."""
try:
return timezone.localtime(obj.datetime).strftime("%Y-%m-%d %H:%M")
except (OverflowError, ValueError):
return "Invalid date"
admin.site.register(Event, EventAdmin)

48
src/lists/admin.py Normal file
View File

@@ -0,0 +1,48 @@
from django.contrib import admin
from lists.models import CustomList, CustomListItem
class CustomListAdmin(admin.ModelAdmin):
"""Admin configuration for CustomList model."""
search_fields = ["name", "description", "owner__username"]
list_display = ["name", "owner", "item_count", "get_last_update"]
list_filter = ["owner"]
raw_id_fields = ["owner"]
autocomplete_fields = ["collaborators"]
filter_horizontal = ["collaborators"]
def item_count(self, obj):
"""Return the number of items in the list."""
return obj.items.count()
item_count.short_description = "Number of items"
def get_last_update(self, obj):
"""Return the date of the last item added."""
last_update = CustomListItem.objects.get_last_added_date(obj)
return last_update if last_update else "-"
get_last_update.short_description = "Last updated"
class CustomListItemAdmin(admin.ModelAdmin):
"""Admin configuration for CustomListItem model."""
search_fields = ["item__title", "custom_list__name", "item__media_id"]
list_display = ["item", "custom_list", "date_added", "get_media_type"]
list_filter = ["custom_list", "item__media_type", "custom_list__owner"]
raw_id_fields = ["item", "custom_list"]
autocomplete_fields = ["item", "custom_list"]
readonly_fields = ["date_added"]
def get_media_type(self, obj):
"""Return the media type of the item."""
return obj.item.get_media_type_display()
get_media_type.short_description = "Media Type"
admin.site.register(CustomList, CustomListAdmin)
admin.site.register(CustomListItem, CustomListItemAdmin)

67
src/users/admin.py Normal file
View File

@@ -0,0 +1,67 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import Group
from django.db.models import Field
from users.models import User
class CustomUserCreationForm(UserCreationForm):
"""A custom user creation form that only includes the username field."""
class Meta:
"""Meta class for the custom user creation form."""
model = User
fields = ("username",)
class CustomUserAdmin(UserAdmin):
"""Custom admin interface for the User model."""
add_form = CustomUserCreationForm
add_fieldsets = (
(
None,
{
"classes": ("wide",),
"fields": ("username", "password1", "password2"),
},
),
)
list_display = ("username", "is_staff", "is_active", "is_demo", "last_login")
list_filter = ("is_staff", "is_active", "is_demo")
def get_fieldsets(self, _, __=None):
"""Customize the fieldsets for the User model in the admin interface."""
fieldsets = [
(None, {"fields": ("username", "password")}),
("Permissions", {"fields": ("is_staff", "is_active")}),
]
field_groups = {}
for field in User._meta.get_fields(): # noqa: SLF001
if not isinstance(field, Field):
continue
# Skip fields already included
if field.name in {"username", "password", "is_staff", "is_active", "id"}:
continue
# Group fields by prefix (everything before first underscore)
prefix = field.name.split("_")[0]
field_groups.setdefault(prefix, []).append(field.name)
# Add grouped fields to fieldsets
for prefix, fields in field_groups.items():
fieldsets.append((prefix.title(), {"fields": tuple(fields)}))
return fieldsets
search_fields = ("username",)
ordering = ("username",)
admin.site.register(User, CustomUserAdmin)
admin.site.unregister(Group)