from django.contrib import admin from django.utils.translation import gettext_lazy as _ from django.urls import reverse from django.utils.html import format_html from django.db import models from ajaxdatatable.admin import AjaxDatatable from unfold.admin import ModelAdmin, StackedInline, TabularInline from django.contrib.admin import SimpleListFilter from unfold.widgets import UnfoldAdminSelectWidget from django.shortcuts import get_object_or_404, redirect, render from unfold.decorators import display, action from django import forms from django.urls import path, reverse_lazy from utils.admin import project_admin_site from unfold.sections import TableSection from apps.article.models import ( ArticleCategory, ArticleCollection, PinnedArticleCollection, MiddleArticleCollection, Article, ArticleInCollection, ArticleContent, ContentPart ) class ArticleInCollectionInline(TabularInline): model = ArticleInCollection extra = 1 autocomplete_fields = ('article',) fields = ('article', 'order') ordering = ('order',) verbose_name = _('Article') verbose_name_plural = _('Articles') tab = True class ContentPartInline(StackedInline): model = ContentPart extra = 1 fields = ('arabic_text', 'translation', 'order') ordering = ('order',) verbose_name = _('Content Part') verbose_name_plural = _('Content Parts') class ArticleContentInline(StackedInline): model = ArticleContent extra = 1 fields = ('title', 'content', 'priority', 'status') ordering = ('priority',) verbose_name = _('Article Content') verbose_name_plural = _('Article Contents') tab = True class ArticleCollectionAdminBase(ModelAdmin): list_display = ('get_title', 'get_display_position', 'status', 'order', 'count_articles') list_filter = ('status', 'order') search_fields = ('title',) ordering = ('order',) list_filter_submit = True warn_unsaved_form = True change_form_show_cancel_button = True inlines = [ArticleInCollectionInline] fieldsets = ( (None, { 'fields': ('title', 'summary', 'thumbnail', 'status', 'pin_top', 'order') }), ) exclude = ('display_position',) @display(description=_('Title')) def get_title(self, obj): return str(obj.title) @display(description=_('Display Position')) def get_display_position(self, obj): if obj.display_position == ArticleCollection.DisplayPosition.PINNED: return format_html('📌 Pinned (Top)') else: return format_html('📋 Regular (Middle)') @display(description=_('Number of Articles')) def count_articles(self, obj): count = obj.related_articles.count() if count > 0: url = reverse('admin:article_article_changelist') + f'?collections__id__exact={obj.id}' return format_html('{}', url, count) return count class PinnedArticleCollectionForm(forms.ModelForm): class Meta: model = PinnedArticleCollection exclude = ('slug',) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['thumbnail'].required = True class PinnedArticleCollectionAdmin(ArticleCollectionAdminBase): form = PinnedArticleCollectionForm def get_queryset(self, request): return super().get_queryset(request).filter(display_position=ArticleCollection.DisplayPosition.PINNED) def save_model(self, request, obj, form, change): obj.display_position = ArticleCollection.DisplayPosition.PINNED super().save_model(request, obj, form, change) @display(description=_('Title')) def get_title(self, obj): from django.templatetags.static import static thumbnail_path = obj.thumbnail.url if obj.thumbnail else None return obj.title # Uncomment below to show thumbnail in the title column # return [ # obj.title, # None, # None, # { # "path": thumbnail_path, # "height": 30, # "width": 50, # "borderless": True, # # "squared": True, # }, # ] class MiddleArticleCollectionAdmin(ArticleCollectionAdminBase): fieldsets = ( (None, { 'fields': ('title', 'status', 'pin_top', 'order') }), ) def get_queryset(self, request): return super().get_queryset(request).filter(display_position=ArticleCollection.DisplayPosition.MIDDLE) def save_model(self, request, obj, form, change): obj.display_position = ArticleCollection.DisplayPosition.MIDDLE super().save_model(request, obj, form, change) class ArticleCategoryAdmin(ModelAdmin): list_display = ('display_header', 'slug', 'status', 'order', 'count_articles', 'created_at') list_filter = ('status', 'created_at', 'updated_at') search_fields = ('title', 'slug') @admin.display(description=_('Number of Articles')) def count_articles(self, obj): count = obj.articles.count() if count > 0: url = reverse('admin:article_article_changelist') + f'?categories__id__exact={obj.id}' return format_html('{}', url, count) return count def get_form(self, request, obj=None, change=False, **kwargs): form = super().get_form(request, obj, change, **kwargs) if form.base_fields.get('slug'): form.base_fields['slug'].required = False return form @display(description=_("Category"), header=True) def display_header(self, obj): return obj.title class ArticleAdmin(ModelAdmin): # change_form_before_template = 'article/change_form_before_template.html' list_display = ('display_header', 'slug', 'status', 'view_count', 'created_at') list_filter = ('status', 'created_at', 'updated_at') search_fields = ('title', 'slug', 'description', 'content') autocomplete_fields = ('categories',) save_as = True search_help_text = _("Search by title, slug, description or content") search_fields_placeholder = _("Search articles") # inlines = [ArticleContentInline] actions_row = [ "action_contents", ] fieldsets = ( (None, { 'fields': ('title', 'slug', 'description', 'thumbnail', 'categories') }), (_('File'), { 'fields': ('article_file',) }), (_('Status'), { 'fields': ('status',) }), (_('Statistics'), { 'fields': ('view_count',) }), ) def get_form(self, request, obj=None, change=False, **kwargs): form = super().get_form(request, obj, change, **kwargs) if form.base_fields.get('slug'): form.base_fields['slug'].required = False if form.base_fields.get('thumbnail'): form.base_fields['thumbnail'].required = True return form @action( description=_("Contents"), url_path="actions-row-custom-url", ) def action_contents(self, request, object_id): article = get_object_or_404(Article, pk=object_id) url = reverse('admin:article_articlecontent_changelist') + f'?article__id__exact={article.id}' return redirect(url) @display(description=_("Article"), header=True) def display_header(self, obj): from django.templatetags.static import static # Get thumbnail image path - use article's thumbnail if available, otherwise use default thumbnail_path = obj.thumbnail.url if obj.thumbnail else None return [ obj.title, None, None, { "path": thumbnail_path, "height": 30, "width": 50, "borderless": True, # "squared": True, }, ] class ArticleContentAdmin(ModelAdmin): list_display = ('title', 'article', 'priority', 'status', 'created_at') list_filter = ('status', 'priority', 'created_at') search_fields = ('title', 'content') autocomplete_fields = ('article',) inlines = [ContentPartInline] fieldsets = ( (None, { 'fields': ('article', 'title', 'content', 'priority', 'status') }), ) def get_changeform_initial_data(self, request): initial = super().get_changeform_initial_data(request) if 'article__id__exact' in request.GET: initial['article'] = request.GET.get('article__id__exact') return initial # @display(description=_("Content Title"), header=True) # def display_header(self, obj): # return str(obj.title) # Register models with admin site project_admin_site.register(ArticleCategory, ArticleCategoryAdmin) project_admin_site.register(PinnedArticleCollection, PinnedArticleCollectionAdmin) project_admin_site.register(MiddleArticleCollection, MiddleArticleCollectionAdmin) project_admin_site.register(Article, ArticleAdmin) project_admin_site.register(ArticleContent, ArticleContentAdmin)