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