Files
com2care_website/apps/products/admin.py
T
2026-10-29 04:39:49 +08:00

366 lines
13 KiB
Python

from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from django.db.models import Q
from apps.core.admin_video import video_admin_preview
from apps.core.video import VIDEO_ADMIN_FIELDSET
from .access import accessible_projects, can_access_project, can_create_project, project_for_related
from .models import (
Article, ArticleCitation, ArticlePerson, ArticleSection, MainProduct,
MainProductPerson, ProductVideo, ProjectAccess, ProjectAccessPolicy,
SubProduct, SubProductPerson,
)
class ProjectAccessPolicyInline(admin.StackedInline):
model = ProjectAccessPolicy
extra = 1
max_num = 1
fields = ("mode",)
class ProjectAccessInline(admin.TabularInline):
model = ProjectAccess
extra = 1
fields = ("main_product", "sub_product", "level")
verbose_name = "Project access override"
verbose_name_plural = "Project access overrides (choose one project per row)"
User = get_user_model()
admin.site.unregister(User)
@admin.register(User)
class ProjectUserAdmin(UserAdmin):
inlines = (ProjectAccessPolicyInline, ProjectAccessInline)
def get_inlines(self, request, obj=None):
return self.inlines if request.user.is_superuser else ()
class ProjectInlineAccessMixin:
def has_view_permission(self, request, obj=None):
return request.user.is_staff and (obj is None or can_access_project(request.user, project_for_related(obj)))
def has_change_permission(self, request, obj=None):
return request.user.is_staff and (obj is None or can_access_project(request.user, project_for_related(obj), edit=True))
def has_add_permission(self, request, obj=None):
return request.user.is_staff and (obj is None or can_access_project(request.user, project_for_related(obj), edit=True))
def has_delete_permission(self, request, obj=None):
return request.user.is_superuser
class ProjectAdminAccessMixin:
def has_module_permission(self, request):
return request.user.is_staff
def get_queryset(self, request):
queryset = super().get_queryset(request)
main = accessible_projects(request.user, MainProduct)
sub = accessible_projects(request.user, SubProduct)
if self.model is MainProduct:
return queryset.filter(pk__in=main)
if self.model is SubProduct:
return queryset.filter(pk__in=sub)
if self.model in (Article, ProductVideo):
return queryset.filter(Q(main_product__in=main) | Q(sub_product__in=sub))
if self.model is ArticleSection:
return queryset.filter(Q(article__main_product__in=main) | Q(article__sub_product__in=sub))
return queryset.none()
def has_view_permission(self, request, obj=None):
return request.user.is_staff and (obj is None or can_access_project(request.user, project_for_related(obj)))
def has_change_permission(self, request, obj=None):
return request.user.is_staff and (obj is None or can_access_project(request.user, project_for_related(obj), edit=True))
def has_add_permission(self, request):
return request.user.is_staff and can_create_project(request.user)
def has_delete_permission(self, request, obj=None):
return request.user.is_superuser
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "main_product":
kwargs["queryset"] = accessible_projects(request.user, MainProduct, edit=True)
elif db_field.name == "sub_product":
kwargs["queryset"] = accessible_projects(request.user, SubProduct, edit=True)
elif db_field.name == "article":
main = accessible_projects(request.user, MainProduct, edit=True)
sub = accessible_projects(request.user, SubProduct, edit=True)
kwargs["queryset"] = Article.objects.filter(Q(main_product__in=main) | Q(sub_product__in=sub))
return super().formfield_for_foreignkey(db_field, request, **kwargs)
class PersonInline(ProjectInlineAccessMixin, admin.StackedInline):
extra = 0
fields = ("role", "name", "description", "read_more_description", "image", "image_alt", "url", "order")
ordering = ("order",)
class MainProductPersonInline(PersonInline):
model = MainProductPerson
class SubProductPersonInline(PersonInline):
model = SubProductPerson
class ArticlePersonInline(PersonInline):
model = ArticlePerson
class ArticleCitationInline(ProjectInlineAccessMixin, admin.TabularInline):
model = ArticleCitation
extra = 1
fields = ("text", "read_more_text", "url", "order")
ordering = ("order",)
class ArticleSectionInline(ProjectInlineAccessMixin, admin.TabularInline):
model = ArticleSection
extra = 1
fields = ("title", "value_format", "value", "read_more_value", "order")
ordering = ("order",)
class SubProductArticleInline(ProjectInlineAccessMixin, admin.StackedInline):
model = Article
fk_name = "sub_product"
extra = 0
fields = ("badge", "title", "description", "read_more_description", "order")
ordering = ("order",)
show_change_link = True
class MainProductArticleInline(ProjectInlineAccessMixin, admin.StackedInline):
model = Article
fk_name = "main_product"
extra = 0
fields = ("badge", "title", "description", "read_more_description", "order")
ordering = ("order",)
show_change_link = True
class ProductVideoInline(ProjectInlineAccessMixin, admin.StackedInline):
model = ProductVideo
fk_name = "main_product"
extra = 0
fields = (
"badge",
"title",
"description_format",
"description",
"read_more_description",
"video_source",
"video_url",
"video_file",
"video_poster",
"video_size",
"video_aspect_ratio",
"video_styled_background",
"order",
"is_active",
)
ordering = ("order",)
class SubProductInline(ProjectInlineAccessMixin, admin.StackedInline):
model = SubProduct
extra = 0
fields = ("name", "slug", "short_description", "read_more_short_description", "image", "logo", "show_on_homepage", "homepage_order", "order", "is_active")
ordering = ("order",)
show_change_link = True
prepopulated_fields = {"slug": ("name",)}
def get_queryset(self, request):
return super().get_queryset(request).filter(pk__in=accessible_projects(request.user, SubProduct, edit=True))
@admin.register(MainProduct)
class MainProductAdmin(ProjectAdminAccessMixin, admin.ModelAdmin):
list_display = ("name", "created_by", "show_on_homepage", "homepage_order", "order", "is_active", "created_at")
list_filter = ("is_active", "show_on_homepage")
search_fields = ("name", "description")
prepopulated_fields = {"slug": ("name",)}
inlines = [MainProductPersonInline, MainProductArticleInline, ProductVideoInline, SubProductInline]
fieldsets = (
(
None,
{
"fields": (
"name",
"slug",
"short_description",
"read_more_short_description",
)
},
),
("Description", {"fields": ("description_format", "description", "read_more_description")}),
("Media", {"fields": ("image",)}),
("Homepage", {"fields": ("show_on_homepage", "homepage_order")}),
("Settings", {"fields": ("order", "is_active")}),
)
def save_model(self, request, obj, form, change):
if not change and not obj.created_by_id:
obj.created_by = request.user
super().save_model(request, obj, form, change)
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for deleted in formset.deleted_objects:
deleted.delete()
for instance in instances:
if isinstance(instance, SubProduct) and not instance.created_by_id:
instance.created_by = request.user
instance.save()
formset.save_m2m()
class SubProductVideoInline(ProjectInlineAccessMixin, admin.StackedInline):
model = ProductVideo
fk_name = "sub_product"
extra = 0
fields = (
"badge",
"title",
"description_format",
"description",
"read_more_description",
"video_source",
"video_url",
"video_file",
"video_poster",
"video_size",
"video_aspect_ratio",
"video_styled_background",
"order",
"is_active",
)
ordering = ("order",)
@admin.register(SubProduct)
class SubProductAdmin(ProjectAdminAccessMixin, admin.ModelAdmin):
list_display = (
"name",
"main_product",
"created_by",
"show_on_homepage",
"homepage_order",
"order",
"is_active",
"created_at",
)
list_filter = ("is_active", "main_product", "show_on_homepage")
search_fields = ("name", "description", "main_product__name")
prepopulated_fields = {"slug": ("name",)}
raw_id_fields = ("main_product",)
inlines = [SubProductPersonInline, SubProductArticleInline, SubProductVideoInline]
fieldsets = (
(
None,
{
"fields": (
"main_product",
"name",
"slug",
"short_description",
"read_more_short_description",
)
},
),
("Description", {"fields": ("description_format", "description", "read_more_description")}),
("Media", {"fields": ("image", "logo")}),
("Homepage", {"fields": ("show_on_homepage", "homepage_order")}),
("Settings", {"fields": ("order", "is_active")}),
)
def get_readonly_fields(self, request, obj=None):
fields = super().get_readonly_fields(request, obj)
return (*fields, "main_product") if obj and not request.user.is_superuser else fields
def save_model(self, request, obj, form, change):
if not change and not obj.created_by_id:
obj.created_by = request.user
super().save_model(request, obj, form, change)
@admin.register(Article)
class ArticleAdmin(ProjectAdminAccessMixin, admin.ModelAdmin):
list_display = ("title", "parent", "order", "created_at")
list_filter = ("main_product", "sub_product__main_product")
search_fields = ("title", "description", "main_product__name", "sub_product__name")
raw_id_fields = ("main_product", "sub_product")
inlines = [ArticlePersonInline, ArticleSectionInline, ArticleCitationInline]
fieldsets = (
(None, {"fields": ("main_product", "sub_product", "badge", "title")}),
("Description", {"fields": ("description_format", "description", "read_more_description")}),
("Citation Badge", {"fields": ("citation_count_display", "citation_count_label"), "description": "Number and optional label for the dashed citation circle badge. Both are optional — a label without a number shows an empty circle with the label; neither hides the badge entirely."}),
("Settings", {"fields": ("order",)}),
)
def get_readonly_fields(self, request, obj=None):
fields = super().get_readonly_fields(request, obj)
return (*fields, "main_product", "sub_product") if obj and not request.user.is_superuser else fields
@admin.display(description="Parent")
def parent(self, obj):
if obj.main_product_id:
return obj.main_product
if obj.sub_product_id:
return obj.sub_product
return "—"
@admin.register(ProductVideo)
class ProductVideoAdmin(ProjectAdminAccessMixin, admin.ModelAdmin):
list_display = ("title", "parent", "video_source", "video_size", "order", "is_active")
list_filter = ("video_source", "is_active", "main_product", "sub_product__main_product")
search_fields = ("title", "description", "video_url", "main_product__name", "sub_product__name")
raw_id_fields = ("main_product", "sub_product")
readonly_fields = ("video_preview",)
fieldsets = (
(None, {"fields": ("main_product", "sub_product", "badge", "title", "description_format", "description", "read_more_description")}),
VIDEO_ADMIN_FIELDSET,
("Settings", {"fields": ("order", "is_active")}),
)
def get_readonly_fields(self, request, obj=None):
fields = super().get_readonly_fields(request, obj)
return (*fields, "main_product", "sub_product") if obj and not request.user.is_superuser else fields
@admin.display(description="Parent")
def parent(self, obj):
if obj.main_product_id:
return obj.main_product
if obj.sub_product_id:
return obj.sub_product
return "—"
@admin.display(description="Preview")
def video_preview(self, obj):
return video_admin_preview(obj)
@admin.register(ArticleSection)
class ArticleSectionAdmin(ProjectAdminAccessMixin, admin.ModelAdmin):
list_display = ("title", "article", "value_format", "order")
search_fields = ("title", "value", "article__title")
raw_id_fields = ("article",)
fieldsets = (
(None, {"fields": ("article", "title")}),
("Content", {"fields": ("value_format", "value", "read_more_value")}),
("Settings", {"fields": ("order",)}),
)
def get_readonly_fields(self, request, obj=None):
fields = super().get_readonly_fields(request, obj)
return (*fields, "article") if obj and not request.user.is_superuser else fields