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 dovoodi_admin_site from unfold.sections import TableSection from apps.article.models import ( ArticleCategory, ArticleCollection, PinnedArticleCollection, MiddleArticleCollection, Article, ArticleInCollection, ArticleContent, ContentPart, TextSection ) 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 TextSectionInline(TabularInline): model = TextSection extra = 1 fields = ('arabic_text', 'translation', 'order') ordering = ('order',) verbose_name = _('Text Section') verbose_name_plural = _('Text Sections') class ContentPartInline(StackedInline): model = ContentPart extra = 1 fields = ('order',) ordering = ('order',) verbose_name = _('Content Part') verbose_name_plural = _('Content Parts') tab = True 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 = ('title', '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 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] actions_row = [ "action_parts", ] 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 @action( description=_("Parts"), url_path="actions-row-parts-url", ) def action_parts(self, request, object_id): content = get_object_or_404(ArticleContent, pk=object_id) url = reverse('admin:article_contentpart_changelist') + f'?article_content__id__exact={content.id}' return redirect(url) class ContentPartAdmin(ModelAdmin): list_display = ('article_content', 'order', 'created_at') list_filter = ('created_at',) autocomplete_fields = ('article_content',) inlines = [TextSectionInline] fieldsets = ( (None, { 'fields': ('article_content', 'order') }), ) def get_changeform_initial_data(self, request): initial = super().get_changeform_initial_data(request) if 'article_content__id__exact' in request.GET: initial['article_content'] = request.GET.get('article_content__id__exact') return initial # Register models with admin site dovoodi_admin_site.register(ArticleCategory, ArticleCategoryAdmin) dovoodi_admin_site.register(PinnedArticleCollection, PinnedArticleCollectionAdmin) dovoodi_admin_site.register(MiddleArticleCollection, MiddleArticleCollectionAdmin) dovoodi_admin_site.register(Article, ArticleAdmin) dovoodi_admin_site.register(ArticleContent, ArticleContentAdmin) dovoodi_admin_site.register(ContentPart, ContentPartAdmin)