feat: scope project access to Django Admin
This commit is contained in:
+143
-20
@@ -1,15 +1,103 @@
|
||||
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, SubProduct, SubProductPerson,
|
||||
MainProductPerson, ProductVideo, ProjectAccess, ProjectAccessPolicy,
|
||||
SubProduct, SubProductPerson,
|
||||
)
|
||||
|
||||
|
||||
class PersonInline(admin.StackedInline):
|
||||
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",)
|
||||
@@ -27,21 +115,21 @@ class ArticlePersonInline(PersonInline):
|
||||
model = ArticlePerson
|
||||
|
||||
|
||||
class ArticleCitationInline(admin.TabularInline):
|
||||
class ArticleCitationInline(ProjectInlineAccessMixin, admin.TabularInline):
|
||||
model = ArticleCitation
|
||||
extra = 1
|
||||
fields = ("text", "read_more_text", "url", "order")
|
||||
ordering = ("order",)
|
||||
|
||||
|
||||
class ArticleSectionInline(admin.TabularInline):
|
||||
class ArticleSectionInline(ProjectInlineAccessMixin, admin.TabularInline):
|
||||
model = ArticleSection
|
||||
extra = 1
|
||||
fields = ("title", "value_format", "value", "read_more_value", "order")
|
||||
ordering = ("order",)
|
||||
|
||||
|
||||
class SubProductArticleInline(admin.StackedInline):
|
||||
class SubProductArticleInline(ProjectInlineAccessMixin, admin.StackedInline):
|
||||
model = Article
|
||||
fk_name = "sub_product"
|
||||
extra = 0
|
||||
@@ -50,7 +138,7 @@ class SubProductArticleInline(admin.StackedInline):
|
||||
show_change_link = True
|
||||
|
||||
|
||||
class MainProductArticleInline(admin.StackedInline):
|
||||
class MainProductArticleInline(ProjectInlineAccessMixin, admin.StackedInline):
|
||||
model = Article
|
||||
fk_name = "main_product"
|
||||
extra = 0
|
||||
@@ -59,7 +147,7 @@ class MainProductArticleInline(admin.StackedInline):
|
||||
show_change_link = True
|
||||
|
||||
|
||||
class ProductVideoInline(admin.StackedInline):
|
||||
class ProductVideoInline(ProjectInlineAccessMixin, admin.StackedInline):
|
||||
model = ProductVideo
|
||||
fk_name = "main_product"
|
||||
extra = 0
|
||||
@@ -82,7 +170,7 @@ class ProductVideoInline(admin.StackedInline):
|
||||
ordering = ("order",)
|
||||
|
||||
|
||||
class SubProductInline(admin.StackedInline):
|
||||
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")
|
||||
@@ -90,14 +178,16 @@ class SubProductInline(admin.StackedInline):
|
||||
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(admin.ModelAdmin):
|
||||
list_display = ("name", "show_on_homepage", "homepage_order", "order", "is_active", "created_at")
|
||||
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",)}
|
||||
list_editable = ("order", "is_active", "show_on_homepage", "homepage_order")
|
||||
inlines = [MainProductPersonInline, MainProductArticleInline, ProductVideoInline, SubProductInline]
|
||||
fieldsets = (
|
||||
(
|
||||
@@ -117,8 +207,23 @@ class MainProductAdmin(admin.ModelAdmin):
|
||||
("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)
|
||||
|
||||
class SubProductVideoInline(admin.StackedInline):
|
||||
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
|
||||
@@ -142,10 +247,11 @@ class SubProductVideoInline(admin.StackedInline):
|
||||
|
||||
|
||||
@admin.register(SubProduct)
|
||||
class SubProductAdmin(admin.ModelAdmin):
|
||||
class SubProductAdmin(ProjectAdminAccessMixin, admin.ModelAdmin):
|
||||
list_display = (
|
||||
"name",
|
||||
"main_product",
|
||||
"created_by",
|
||||
"show_on_homepage",
|
||||
"homepage_order",
|
||||
"order",
|
||||
@@ -155,7 +261,6 @@ class SubProductAdmin(admin.ModelAdmin):
|
||||
list_filter = ("is_active", "main_product", "show_on_homepage")
|
||||
search_fields = ("name", "description", "main_product__name")
|
||||
prepopulated_fields = {"slug": ("name",)}
|
||||
list_editable = ("order", "is_active", "show_on_homepage", "homepage_order")
|
||||
raw_id_fields = ("main_product",)
|
||||
inlines = [SubProductPersonInline, SubProductArticleInline, SubProductVideoInline]
|
||||
fieldsets = (
|
||||
@@ -177,13 +282,21 @@ class SubProductAdmin(admin.ModelAdmin):
|
||||
("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(admin.ModelAdmin):
|
||||
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")
|
||||
list_editable = ("order",)
|
||||
raw_id_fields = ("main_product", "sub_product")
|
||||
inlines = [ArticlePersonInline, ArticleSectionInline, ArticleCitationInline]
|
||||
fieldsets = (
|
||||
@@ -193,6 +306,10 @@ class ArticleAdmin(admin.ModelAdmin):
|
||||
("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:
|
||||
@@ -203,10 +320,9 @@ class ArticleAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(ProductVideo)
|
||||
class ProductVideoAdmin(admin.ModelAdmin):
|
||||
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")
|
||||
list_editable = ("order", "is_active")
|
||||
search_fields = ("title", "description", "video_url", "main_product__name", "sub_product__name")
|
||||
raw_id_fields = ("main_product", "sub_product")
|
||||
readonly_fields = ("video_preview",)
|
||||
@@ -216,6 +332,10 @@ class ProductVideoAdmin(admin.ModelAdmin):
|
||||
("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:
|
||||
@@ -230,13 +350,16 @@ class ProductVideoAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(ArticleSection)
|
||||
class ArticleSectionAdmin(admin.ModelAdmin):
|
||||
class ArticleSectionAdmin(ProjectAdminAccessMixin, admin.ModelAdmin):
|
||||
list_display = ("title", "article", "value_format", "order")
|
||||
search_fields = ("title", "value", "article__title")
|
||||
list_editable = ("order",)
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user