feat: Introduce lists and reviews apps, refactor user list functionality from accounts, and add user profile fields.

This commit is contained in:
pacnpal
2025-12-26 09:27:44 -05:00
parent ed04b30469
commit cd8868a591
37 changed files with 5900 additions and 281 deletions

View File

@@ -31,8 +31,6 @@ from apps.core.admin import (
from .models import (
EmailVerification,
PasswordReset,
TopList,
TopListItem,
User,
UserProfile,
)
@@ -81,18 +79,6 @@ class UserProfileInline(admin.StackedInline):
)
class TopListItemInline(admin.TabularInline):
"""
Inline admin for TopListItem within TopList admin.
Shows list items ordered by rank with content linking.
"""
model = TopListItem
extra = 1
fields = ("content_type", "object_id", "rank", "notes")
ordering = ("rank",)
show_change_link = True
@admin.register(User)
@@ -683,181 +669,4 @@ class PasswordResetAdmin(ReadOnlyAdminMixin, BaseModelAdmin):
return actions
@admin.register(TopList)
class TopListAdmin(
QueryOptimizationMixin, ExportActionMixin, TimestampFieldsMixin, BaseModelAdmin
):
"""
Admin interface for user top lists.
Manages user-created top lists with inline item editing
and category filtering.
"""
list_display = (
"title",
"user_link",
"category",
"item_count",
"created_at",
"updated_at",
)
list_filter = ("category", "created_at", "updated_at")
list_select_related = ["user"]
list_prefetch_related = ["items"]
search_fields = ("title", "user__username", "description")
autocomplete_fields = ["user"]
inlines = [TopListItemInline]
export_fields = ["id", "title", "user", "category", "created_at", "updated_at"]
export_filename_prefix = "top_lists"
fieldsets = (
(
"Basic Information",
{
"fields": ("user", "title", "category", "description"),
"description": "List identification and categorization.",
},
),
(
"Timestamps",
{
"fields": ("created_at", "updated_at"),
"classes": ("collapse",),
},
),
)
readonly_fields = ("created_at", "updated_at")
@admin.display(description="User")
def user_link(self, obj):
"""Display user as clickable link."""
if obj.user:
from django.urls import reverse
url = reverse("admin:accounts_customuser_change", args=[obj.user.pk])
return format_html('<a href="{}">{}</a>', url, obj.user.username)
return "-"
@admin.display(description="Items")
def item_count(self, obj):
"""Display count of items in the list."""
if hasattr(obj, "_item_count"):
return obj._item_count
return obj.items.count()
def get_queryset(self, request):
"""Optimize queryset with item count annotation."""
qs = super().get_queryset(request)
qs = qs.annotate(_item_count=Count("items", distinct=True))
return qs
@admin.action(description="Publish selected lists")
def publish_lists(self, request, queryset):
"""Mark selected lists as published."""
# Assuming there's a published field
self.message_user(request, f"Published {queryset.count()} lists.")
@admin.action(description="Unpublish selected lists")
def unpublish_lists(self, request, queryset):
"""Mark selected lists as unpublished."""
self.message_user(request, f"Unpublished {queryset.count()} lists.")
def get_actions(self, request):
"""Add custom actions."""
actions = super().get_actions(request)
actions["publish_lists"] = (
self.publish_lists,
"publish_lists",
"Publish selected lists",
)
actions["unpublish_lists"] = (
self.unpublish_lists,
"unpublish_lists",
"Unpublish selected lists",
)
return actions
@admin.register(TopListItem)
class TopListItemAdmin(QueryOptimizationMixin, BaseModelAdmin):
"""
Admin interface for top list items.
Manages individual items within top lists with
content type linking and reordering.
"""
list_display = (
"top_list_link",
"content_type",
"object_id",
"rank",
"content_preview",
)
list_filter = ("top_list__category", "content_type", "rank")
list_select_related = ["top_list", "top_list__user", "content_type"]
search_fields = ("top_list__title", "notes", "top_list__user__username")
autocomplete_fields = ["top_list"]
ordering = ("top_list", "rank")
fieldsets = (
(
"List Information",
{
"fields": ("top_list", "rank"),
"description": "The list this item belongs to and its position.",
},
),
(
"Item Details",
{
"fields": ("content_type", "object_id", "notes"),
"description": "The content this item references.",
},
),
)
@admin.display(description="Top List")
def top_list_link(self, obj):
"""Display top list as clickable link."""
if obj.top_list:
from django.urls import reverse
url = reverse("admin:accounts_toplist_change", args=[obj.top_list.pk])
return format_html('<a href="{}">{}</a>', url, obj.top_list.title)
return "-"
@admin.display(description="Content")
def content_preview(self, obj):
"""Display preview of linked content."""
try:
content_obj = obj.content_type.get_object_for_this_type(pk=obj.object_id)
return str(content_obj)[:50]
except Exception:
return format_html('<span style="color: red;">Not found</span>')
@admin.action(description="Move up in list")
def move_up(self, request, queryset):
"""Move selected items up in their lists."""
for item in queryset:
if item.rank > 1:
item.rank -= 1
item.save(update_fields=["rank"])
self.message_user(request, "Items moved up.")
@admin.action(description="Move down in list")
def move_down(self, request, queryset):
"""Move selected items down in their lists."""
for item in queryset:
item.rank += 1
item.save(update_fields=["rank"])
self.message_user(request, "Items moved down.")
def get_actions(self, request):
"""Add reordering actions."""
actions = super().get_actions(request)
actions["move_up"] = (self.move_up, "move_up", "Move up in list")
actions["move_down"] = (self.move_down, "move_down", "Move down in list")
return actions