diff --git a/apps/article/admin.py b/apps/article/admin.py
index f25c0d1..73d5924 100755
--- a/apps/article/admin.py
+++ b/apps/article/admin.py
@@ -59,7 +59,7 @@ class ArticleContentInline(StackedInline):
class ArticleCollectionAdminBase(ModelAdmin):
- list_display = ('get_title', 'status', 'order', 'count_articles')
+ list_display = ('get_title', 'get_display_position', 'status', 'order', 'count_articles')
list_filter = ('status', 'order')
search_fields = ('title',)
ordering = ('order',)
@@ -81,6 +81,13 @@ class ArticleCollectionAdminBase(ModelAdmin):
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()
diff --git a/apps/article/models.py b/apps/article/models.py
index 91cf186..f9043b7 100755
--- a/apps/article/models.py
+++ b/apps/article/models.py
@@ -71,15 +71,15 @@ class ArticleCollection(models.Model):
class PinnedArticleCollection(ArticleCollection):
class Meta:
proxy = True
- verbose_name = _('Pinned Article Collection')
- verbose_name_plural = _('Pinned Article Collections')
+ verbose_name = _('Pinned Collection (Top Section)')
+ verbose_name_plural = _('Pinned Collections (Top Section)')
class MiddleArticleCollection(ArticleCollection):
class Meta:
proxy = True
- verbose_name = _('Middle Section Article Collection')
- verbose_name_plural = _('Middle Section Article Collections')
+ verbose_name = _('Regular Collection (Middle Section)')
+ verbose_name_plural = _('Regular Collections (Middle Section)')
class Article(models.Model):
diff --git a/apps/podcast/README_COLLECTIONS.md b/apps/podcast/README_COLLECTIONS.md
new file mode 100644
index 0000000..5117ef2
--- /dev/null
+++ b/apps/podcast/README_COLLECTIONS.md
@@ -0,0 +1,120 @@
+# مستندات مدیریت کالکشنهای پادکست
+
+## نحوه مدیریت کالکشنها در پنل ادمین
+
+### دو نوع کالکشن داریم:
+
+#### 1️⃣ **Pinned Collections (کالکشنهای پینشده - بخش بالا)**
+- **مسیر در پنل ادمین:** `Pinned Collections (Top Section)`
+- **API Endpoint:** `/api/podcast/pinned-collections/`
+- **کاربرد:** نمایش در بالای صفحه (carousel/featured section)
+- **ویژگیها:**
+ - نیاز به تصویر thumbnail دارد
+ - میتواند summary داشته باشد
+ - ترتیب نمایش با فیلد `order` مشخص میشود
+
+#### 2️⃣ **Regular Collections (کالکشنهای معمولی - بخش میانی)**
+- **مسیر در پنل ادمین:** `Regular Collections (Middle Section)`
+- **API Endpoint:** `/api/podcast/collections/`
+- **کاربرد:** نمایش در بخشهای میانی صفحه
+- **ویژگیها:**
+ - تصویر thumbnail اختیاری است
+ - ترتیب نمایش با فیلد `order` مشخص میشود
+
+---
+
+## راهنمای استفاده
+
+### ایجاد کالکشن جدید
+
+**برای کالکشن پینشده (بالای صفحه):**
+1. به بخش `Pinned Collections (Top Section)` بروید
+2. روی "Add" کلیک کنید
+3. فیلدهای زیر را پر کنید:
+ - Title (عنوان)
+ - Summary (خلاصه - اختیاری)
+ - Thumbnail (تصویر - **الزامی**)
+ - Order (ترتیب نمایش)
+ - Status (فعال/غیرفعال)
+4. پادکستهای مورد نظر را اضافه کنید
+
+**برای کالکشن معمولی (بخش میانی):**
+1. به بخش `Regular Collections (Middle Section)` بروید
+2. روی "Add" کلیک کنید
+3. فیلدهای زیر را پر کنید:
+ - Title (عنوان)
+ - Order (ترتیب نمایش)
+ - Status (فعال/غیرفعال)
+4. پادکستهای مورد نظر را اضافه کنید
+
+---
+
+## نکات مهم
+
+### تشخیص نوع کالکشن
+در لیست کالکشنها، ستون **Display Position** نوع هر کالکشن را نشان میدهد:
+- 📌 **Pinned (Top)** → کالکشن پینشده
+- 📋 **Regular (Middle)** → کالکشن معمولی
+
+### تفاوتهای کلیدی
+
+| ویژگی | Pinned | Regular |
+|-------|--------|---------|
+| تصویر thumbnail | ✅ الزامی | ⚪ اختیاری |
+| فیلد summary | ✅ دارد | ❌ ندارد |
+| محل نمایش | بالای صفحه | بخش میانی |
+| API Endpoint | `/pinned-collections/` | `/collections/` |
+
+---
+
+## ساختار فنی
+
+### مدلها
+```python
+# مدل پایه
+PodcastCollection
+├── display_position: 'pinned' یا 'middle'
+├── title
+├── slug
+├── summary (nullable)
+├── thumbnail (nullable)
+├── order
+└── status
+
+# مدلهای Proxy
+PinnedPodcastCollection (display_position='pinned')
+MiddlePodcastCollection (display_position='middle')
+```
+
+### فیلد display_position
+این فیلد به صورت خودکار توسط Django Admin تنظیم میشود:
+- در `Pinned Collections` → `display_position='pinned'`
+- در `Regular Collections` → `display_position='middle'`
+
+---
+
+## سوالات متداول
+
+**Q: چرا دو بخش جدا داریم؟**
+A: برای جلوگیری از اشتباه و مدیریت بهتر. هر کدام کاربرد و ویژگیهای متفاوتی دارند.
+
+**Q: میتوانم یک کالکشن را از Pinned به Regular تبدیل کنم؟**
+A: خیر، باید کالکشن جدیدی در بخش مورد نظر ایجاد کنید و پادکستها را کپی کنید.
+
+**Q: چرا در API دو endpoint جدا داریم؟**
+A: چون frontend نیاز دارد که کالکشنهای بالا و میانی را جداگانه دریافت کند.
+
+---
+
+## تغییرات اخیر
+
+### نسخه جدید (بهبود UX)
+- ✅ نامهای واضحتر برای مدلها
+- ✅ نمایش `Display Position` در لیست
+- ✅ آیکونهای بصری برای تشخیص سریعتر
+- ✅ مستندات کامل
+
+### نسخه قبلی
+- نامهای مبهم (`Middle Section` به جای `Regular`)
+- عدم نمایش نوع کالکشن در لیست
+- سردرگمی در یافتن بخش مناسب
\ No newline at end of file
diff --git a/apps/podcast/admin.py b/apps/podcast/admin.py
index 88e41c5..413c21b 100755
--- a/apps/podcast/admin.py
+++ b/apps/podcast/admin.py
@@ -27,7 +27,7 @@ class PodcastInCollectionInline(TabularInline):
class PodcastCollectionAdminBase(ModelAdmin):
- list_display = ('get_title', 'status', 'order', 'count_podcasts')
+ list_display = ('get_title', 'get_display_position', 'status', 'order', 'count_podcasts')
list_filter = ('status', 'order')
search_fields = ('title',)
ordering = ('order',)
@@ -49,6 +49,13 @@ class PodcastCollectionAdminBase(ModelAdmin):
def get_title(self, obj):
return str(obj.title)
+ @display(description=_('Display Position'))
+ def get_display_position(self, obj):
+ if obj.display_position == PodcastCollection.DisplayPosition.PINNED:
+ return format_html('📌 Pinned (Top)')
+ else:
+ return format_html('📋 Regular (Middle)')
+
@display(description=_('Number of Podcasts'))
def count_podcasts(self, obj):
count = obj.related_podcasts.count()
@@ -70,6 +77,12 @@ class PinnedPodcastCollectionForm(forms.ModelForm):
class PinnedPodcastCollectionAdmin(PodcastCollectionAdminBase):
form = PinnedPodcastCollectionForm
+ # Add help text to clarify this is for top section
+ class Media:
+ css = {
+ 'all': ()
+ }
+
def get_queryset(self, request):
return super().get_queryset(request).filter(display_position=PodcastCollection.DisplayPosition.PINNED)
@@ -77,7 +90,6 @@ class PinnedPodcastCollectionAdmin(PodcastCollectionAdminBase):
obj.display_position = PodcastCollection.DisplayPosition.PINNED
super().save_model(request, obj, form, change)
-
@display(description=_('Title'))
def get_title(self, obj):
from django.templatetags.static import static
@@ -91,6 +103,12 @@ class MiddlePodcastCollectionAdmin(PodcastCollectionAdminBase):
'fields': ('title', 'status', 'pin_top', 'order')
}),
)
+
+ # Add help text to clarify this is for middle section
+ class Media:
+ css = {
+ 'all': ()
+ }
def get_queryset(self, request):
return super().get_queryset(request).filter(display_position=PodcastCollection.DisplayPosition.MIDDLE)
diff --git a/apps/podcast/models.py b/apps/podcast/models.py
index be606b0..a505e23 100755
--- a/apps/podcast/models.py
+++ b/apps/podcast/models.py
@@ -71,15 +71,15 @@ class PodcastCollection(models.Model):
class PinnedPodcastCollection(PodcastCollection):
class Meta:
proxy = True
- verbose_name = _('Pinned Podcast Collection')
- verbose_name_plural = _('Pinned Podcast Collections')
+ verbose_name = _('Pinned Collection (Top Section)')
+ verbose_name_plural = _('Pinned Collections (Top Section)')
class MiddlePodcastCollection(PodcastCollection):
class Meta:
proxy = True
- verbose_name = _('Middle Section Podcast Collection')
- verbose_name_plural = _('Middle Section Podcast Collections')
+ verbose_name = _('Regular Collection (Middle Section)')
+ verbose_name_plural = _('Regular Collections (Middle Section)')
diff --git a/config/settings/base.py b/config/settings/base.py
index b6b70be..955ccf4 100755
--- a/config/settings/base.py
+++ b/config/settings/base.py
@@ -431,6 +431,24 @@ UNFOLD = {
},
],
},
+ {
+ "page": "article",
+ "models": ["article.articlecollection", "article.pinnedarticlecollection", "article.middlearticlecollection"],
+ "items": [
+ {
+ "title": _("Pinned Collections"),
+ "icon": "collections_bookmark",
+ "link": reverse_lazy("admin:article_pinnedarticlecollection_changelist"),
+ "active": lambda request: "article/pinnedarticlecollection" in request.path and "article/middlearticlecollection" not in request.path,
+ },
+ {
+ "title": _("Regular Collections"),
+ "icon": "view_module",
+ "link": reverse_lazy("admin:article_middlearticlecollection_changelist"),
+ "active": lambda request: "article/middlearticlecollection" in request.path,
+ },
+ ],
+ },
{
"page": "accounts",
"models": ["account.user", 'auth.group'],
@@ -526,6 +544,24 @@ UNFOLD = {
},
],
},
+ {
+ "page": "podcast",
+ "models": ["podcast.podcastcollection", "podcast.pinnedpodcastcollection", "podcast.middlepodcastcollection"],
+ "items": [
+ {
+ "title": _("Pinned Collections"),
+ "icon": "collections_bookmark",
+ "link": reverse_lazy("admin:podcast_pinnedpodcastcollection_changelist"),
+ "active": lambda request: "podcast/pinnedpodcastcollection" in request.path and "podcast/middlepodcastcollection" not in request.path,
+ },
+ {
+ "title": _("Regular Collections"),
+ "icon": "view_module",
+ "link": reverse_lazy("admin:podcast_middlepodcastcollection_changelist"),
+ "active": lambda request: "podcast/middlepodcastcollection" in request.path,
+ },
+ ],
+ },
],
"SIDEBAR": {
"show_search": True,
@@ -775,10 +811,15 @@ UNFOLD = {
"link": reverse_lazy("admin:article_articlecategory_changelist"),
},
{
- "title": _("Collections"),
- "icon": "view_module",
+ "title": _("Pinned Collections"),
+ "icon": "collections_bookmark",
"link": reverse_lazy("admin:article_pinnedarticlecollection_changelist"),
},
+ {
+ "title": _("Regular Collections"),
+ "icon": "view_module",
+ "link": reverse_lazy("admin:article_middlearticlecollection_changelist"),
+ },
{
"title": _("Article Contents"),
"icon": "text_snippet",
@@ -802,10 +843,15 @@ UNFOLD = {
"link": reverse_lazy("admin:podcast_podcastcategory_changelist"),
},
{
- "title": _("Collections"),
- "icon": "view_module",
+ "title": _("Pinned Collections"),
+ "icon": "collections_bookmark",
"link": reverse_lazy("admin:podcast_pinnedpodcastcollection_changelist"),
},
+ {
+ "title": _("Regular Collections"),
+ "icon": "view_module",
+ "link": reverse_lazy("admin:podcast_middlepodcastcollection_changelist"),
+ },
{
"title": _("Playlists"),
"icon": "playlist_play",