You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
318 lines
10 KiB
318 lines
10 KiB
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('<span style="color: #0066cc; font-weight: bold;">📌 Pinned (Top)</span>')
|
|
else:
|
|
return format_html('<span style="color: #666;">📋 Regular (Middle)</span>')
|
|
|
|
@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('<a href="{}">{}</a>', 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('<a href="{}">{}</a>', 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 list(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]
|
|
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)
|