mirror of
https://github.com/FuzzyGrim/Yamtrack.git
synced 2026-06-28 14:55:58 +00:00
Add custom admin interfaces for Item, Media, Event, CustomList, CustomListItem, and User models
This commit is contained in:
@@ -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` |
|
||||
|
||||
@@ -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
58
src/app/admin.py
Normal 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)
|
||||
@@ -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"
|
||||
|
||||
@@ -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
32
src/events/admin.py
Normal 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
48
src/lists/admin.py
Normal 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
67
src/users/admin.py
Normal 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)
|
||||
Reference in New Issue
Block a user