Features: 1) Implement FieldsetsMixin to dynamically generate and manage admin fieldsets; 2) Refactor admin class definitions to leverage FieldsetsMixin for better consistency and extensibility.

Fixes: 1) Correct usage of `CONSTANCE_CONFIG` when setting `site_title` and `index_title` in `admin`.

Extra: Updated imports, removed redundant inline configs, cleaned up unused code, and improved readability in `admin.py`.
This commit is contained in:
Egor Pavlovich Gorbunov 2025-06-22 16:34:07 +03:00
parent d9c4717a8d
commit 092c7e908c

View file

@ -1,4 +1,3 @@
# noinspection PyUnresolvedReferences
from constance.admin import Config from constance.admin import Config
from constance.admin import ConstanceAdmin as BaseConstanceAdmin from constance.admin import ConstanceAdmin as BaseConstanceAdmin
from django.apps import apps from django.apps import apps
@ -46,13 +45,32 @@ class TranslationFieldsetMixin:
for orig in opts.local_fields: for orig in opts.local_fields:
translation_fields += get_translation_fields(orig) translation_fields += get_translation_fields(orig)
if translation_fields: if translation_fields:
fieldsets = list(fieldsets) fieldsets = list(fieldsets) + [
fieldsets.append( (_("Translations"), {"fields": translation_fields})
( ]
_("translations"), return fieldsets
{"fields": translation_fields},
)
) class FieldsetsMixin:
general_fields = []
relation_fields = []
model: Model
def get_fieldsets(self, request, obj=None):
fieldsets = []
if self.general_fields:
fieldsets.append((_("General"), {"fields": self.general_fields}))
if self.relation_fields:
fieldsets.append((_("Relations"), {"fields": self.relation_fields}))
opts = self.model._meta
if any(f.name == "uuid" for f in opts.fields):
fieldsets.append((_("Metadata"), {"fields": ["uuid"]}))
ts = []
for name in ("created", "modified"):
if any(f.name == name for f in opts.fields):
ts.append(name)
if ts:
fieldsets.append((_("Timestamps"), {"fields": ts, "classes": ["collapse"]}))
return fieldsets return fieldsets
@ -85,29 +103,17 @@ class BasicModelAdmin(ModelAdmin):
class AttributeValueInline(TabularInline): class AttributeValueInline(TabularInline):
model = AttributeValue model = AttributeValue
extra = 0 extra = 0
is_navtab = True
verbose_name = _("attribute value")
verbose_name_plural = _("attribute values")
autocomplete_fields = ["attribute"] autocomplete_fields = ["attribute"]
icon = "fa-regular fa-circle-dot"
class ProductImageInline(TabularInline): class ProductImageInline(TabularInline):
model = ProductImage model = ProductImage
extra = 0 extra = 0
is_navtab = True
verbose_name = _("image")
verbose_name_plural = _("images")
icon = "fa-regular fa-images"
class StockInline(TabularInline): class StockInline(TabularInline):
model = Stock model = Stock
extra = 0 extra = 0
is_navtab = True
verbose_name = _("stock")
verbose_name_plural = _("stocks")
icon = "fa-regular fa-boxes-stacked"
class OrderProductInline(TabularInline): class OrderProductInline(TabularInline):
@ -115,10 +121,6 @@ class OrderProductInline(TabularInline):
extra = 0 extra = 0
readonly_fields = ("product", "quantity", "buy_price") readonly_fields = ("product", "quantity", "buy_price")
form = OrderProductForm form = OrderProductForm
is_navtab = True
verbose_name = _("order product")
verbose_name_plural = _("order products")
icon = "fa-regular fa-circle-dot"
def get_queryset(self, request): def get_queryset(self, request):
return ( return (
@ -134,60 +136,72 @@ class CategoryChildrenInline(TabularInline):
fk_name = "parent" fk_name = "parent"
extra = 0 extra = 0
fields = ("name", "description", "is_active", "image", "markup_percent") fields = ("name", "description", "is_active", "image", "markup_percent")
icon = "fa-regular fa-circle-dot"
# Admin registrations
@admin.register(AttributeGroup) @admin.register(AttributeGroup)
class AttributeGroupAdmin(TranslationFieldsetMixin, BasicModelAdmin): class AttributeGroupAdmin(FieldsetsMixin, TranslationFieldsetMixin, BasicModelAdmin):
model = AttributeGroup
list_display = ("name", "modified") list_display = ("name", "modified")
search_fields = ("uuid", "name") search_fields = ("uuid", "name")
general_fields = ["name", "parent"]
relation_fields = []
@admin.register(Attribute) @admin.register(Attribute)
class AttributeAdmin(TranslationFieldsetMixin, BasicModelAdmin): class AttributeAdmin(FieldsetsMixin, TranslationFieldsetMixin, BasicModelAdmin):
model = Attribute
list_display = ("name", "group", "value_type", "modified") list_display = ("name", "group", "value_type", "modified")
list_filter = ("value_type", "group", "is_active") list_filter = ("value_type", "group", "is_active")
search_fields = ("uuid", "name", "group__name") search_fields = ("uuid", "name", "group__name")
autocomplete_fields = ["categories", "group"] autocomplete_fields = ["categories", "group"]
general_fields = ["name", "value_type"]
relation_fields = ["group", "categories"]
@admin.register(AttributeValue) @admin.register(AttributeValue)
class AttributeValueAdmin(TranslationFieldsetMixin, BasicModelAdmin): class AttributeValueAdmin(FieldsetsMixin, TranslationFieldsetMixin, BasicModelAdmin):
model = AttributeValue
list_display = ("attribute", "value", "modified") list_display = ("attribute", "value", "modified")
list_filter = ("attribute__group", "is_active") list_filter = ("attribute__group", "is_active")
search_fields = ("uuid", "value", "attribute__name") search_fields = ("uuid", "value", "attribute__name")
autocomplete_fields = ["attribute"] autocomplete_fields = ["attribute"]
general_fields = ["value"]
relation_fields = ["attribute", "product"]
@admin.register(Category) @admin.register(Category)
class CategoryAdmin(TranslationFieldsetMixin, DraggableMPTTAdmin, BasicModelAdmin): class CategoryAdmin(
mptt_indent_field = "name" FieldsetsMixin, TranslationFieldsetMixin, DraggableMPTTAdmin, BasicModelAdmin
):
model = Category
list_display = ("indented_title", "parent", "is_active", "modified") list_display = ("indented_title", "parent", "is_active", "modified")
list_filter = ("is_active", "level", "created", "modified") list_filter = ("is_active", "level", "created", "modified")
list_display_links = ("indented_title",)
search_fields = ("uuid", "name") search_fields = ("uuid", "name")
inlines = [CategoryChildrenInline] inlines = [CategoryChildrenInline]
autocomplete_fields = ["parent", "tags"] autocomplete_fields = ["parent", "tags"]
readonly_fields = ("uuid", "slug", "created", "modified") readonly_fields = ("slug",)
def indented_title(self, instance): general_fields = ["name", "description", "image", "markup_percent"]
return instance.name relation_fields = ["parent", "tags"]
indented_title.short_description = _("name")
indented_title.admin_order_field = "name"
@admin.register(Brand) @admin.register(Brand)
class BrandAdmin(TranslationFieldsetMixin, BasicModelAdmin): class BrandAdmin(FieldsetsMixin, TranslationFieldsetMixin, BasicModelAdmin):
model = Brand
list_display = ("name",) list_display = ("name",)
list_filter = ("categories", "is_active") list_filter = ("categories", "is_active")
search_fields = ("uuid", "name", "categories__name") search_fields = ("uuid", "name", "categories__name")
readonly_fields = ("uuid", "slug")
general_fields = ["name", "description"]
relation_fields = ["small_logo", "big_logo", "categories"]
@admin.register(Product) @admin.register(Product)
class ProductAdmin(TranslationFieldsetMixin, BasicModelAdmin): class ProductAdmin(FieldsetsMixin, TranslationFieldsetMixin, BasicModelAdmin):
model = Product
list_display = ( list_display = (
"name", "name",
"partnumber", "partnumber",
@ -214,68 +228,63 @@ class ProductAdmin(TranslationFieldsetMixin, BasicModelAdmin):
"uuid", "uuid",
"slug", "slug",
) )
readonly_fields = ("created", "modified", "uuid", "rating", "price", "slug") readonly_fields = ("slug",)
autocomplete_fields = ("category", "brand", "tags") autocomplete_fields = ("category", "brand", "tags")
fieldsets = (
(
"general",
{"fields": ("name", "slug", "partnumber", "is_active", "is_digital")},
),
("relations", {"fields": ("category", "brand", "tags")}),
("metadata", {"fields": ("uuid",)}),
("timestamps", {"fields": ("created", "modified")}),
)
inlines = [AttributeValueInline, ProductImageInline, StockInline] inlines = [AttributeValueInline, ProductImageInline, StockInline]
def price(self, obj): general_fields = ["name", "partnumber", "is_active", "is_digital"]
return obj.price relation_fields = ["category", "brand", "tags"]
price.short_description = _("price")
def rating(self, obj):
return obj.rating
rating.short_description = _("rating")
def get_changelist(self, request, **kwargs):
cl = super().get_changelist(request, **kwargs)
cl.filter_input_length = 64
return cl
@admin.register(ProductTag) @admin.register(ProductTag)
class ProductTagAdmin(TranslationFieldsetMixin, BasicModelAdmin): class ProductTagAdmin(FieldsetsMixin, TranslationFieldsetMixin, BasicModelAdmin):
list_display = ("name",) model = ProductTag
search_fields = ("name",) list_display = ("tag_name",)
search_fields = ("tag_name",)
general_fields = ["tag_name", "name"]
relation_fields = []
@admin.register(CategoryTag) @admin.register(CategoryTag)
class CategoryTagAdmin(TranslationFieldsetMixin, BasicModelAdmin): class CategoryTagAdmin(FieldsetsMixin, TranslationFieldsetMixin, BasicModelAdmin):
list_display = ("name",) model = CategoryTag
search_fields = ("name",) list_display = ("tag_name",)
search_fields = ("tag_name",)
general_fields = ["tag_name", "name"]
relation_fields = []
@admin.register(Vendor) @admin.register(Vendor)
class VendorAdmin(BasicModelAdmin): class VendorAdmin(FieldsetsMixin, BasicModelAdmin):
model = Vendor
list_display = ("name", "markup_percent", "modified") list_display = ("name", "markup_percent", "modified")
list_filter = ("markup_percent", "is_active") list_filter = ("markup_percent", "is_active")
search_fields = ("name",) search_fields = ("name",)
form = VendorForm form = VendorForm
general_fields = ["name", "markup_percent", "authentication"]
relation_fields = []
@admin.register(Feedback) @admin.register(Feedback)
class FeedbackAdmin(TranslationFieldsetMixin, BasicModelAdmin): class FeedbackAdmin(FieldsetsMixin, TranslationFieldsetMixin, BasicModelAdmin):
model = Feedback
list_display = ("order_product", "rating", "comment", "modified") list_display = ("order_product", "rating", "comment", "modified")
list_filter = ("rating", "is_active") list_filter = ("rating", "is_active")
search_fields = ("order_product__product__name", "comment") search_fields = ("order_product__product__name", "comment")
general_fields = ["rating", "comment"]
relation_fields = ["order_product"]
@admin.register(Order) @admin.register(Order)
class OrderAdmin(BasicModelAdmin): class OrderAdmin(FieldsetsMixin, BasicModelAdmin):
model = Order
list_display = ( list_display = (
"human_readable_id", "human_readable_id",
"user", "user",
"is_business",
"status", "status",
"total_price", "total_price",
"buy_time", "buy_time",
@ -283,56 +292,29 @@ class OrderAdmin(BasicModelAdmin):
) )
list_filter = ("status", "buy_time", "modified", "created") list_filter = ("status", "buy_time", "modified", "created")
search_fields = ("user__email", "status", "uuid", "human_readable_id") search_fields = ("user__email", "status", "uuid", "human_readable_id")
readonly_fields = ("total_price", "total_quantity", "buy_time", "human_readable_id") readonly_fields = ("total_price", "total_quantity", "human_readable_id")
inlines = [OrderProductInline] inlines = [OrderProductInline]
form = OrderForm form = OrderForm
def is_business(self, obj): general_fields = ["user", "status"]
return obj.is_business relation_fields = ["promo_code", "billing_address", "shipping_address"]
is_business.short_description = _("is business")
def get_queryset(self, request):
return (
super()
.get_queryset(request)
.prefetch_related(
"user",
"shipping_address",
"billing_address",
"order_products",
"promo_code",
)
)
def save_model(self, request, obj, form, change):
if form.cleaned_data.get("attributes") is None:
obj.attributes = None
if form.cleaned_data.get("notifications") is None:
obj.notifications = None
super().save_model(request, obj, form, change)
@admin.register(OrderProduct) @admin.register(OrderProduct)
class OrderProductAdmin(BasicModelAdmin): class OrderProductAdmin(FieldsetsMixin, BasicModelAdmin):
model = OrderProduct
list_display = ("order", "product", "quantity", "buy_price", "status", "modified") list_display = ("order", "product", "quantity", "buy_price", "status", "modified")
list_filter = ("status",) list_filter = ("status",)
search_fields = ("order__user__email", "product__name") search_fields = ("order__user__email", "product__name")
form = OrderProductForm form = OrderProductForm
def get_queryset(self, request): general_fields = ["quantity", "buy_price", "status"]
return super().get_queryset(request).prefetch_related("order", "product") relation_fields = ["order", "product"]
def save_model(self, request, obj, form, change):
if form.cleaned_data.get("attributes") is None:
obj.attributes = None
if form.cleaned_data.get("notifications") is None:
obj.notifications = None
super().save_model(request, obj, form, change)
@admin.register(PromoCode) @admin.register(PromoCode)
class PromoCodeAdmin(BasicModelAdmin): class PromoCodeAdmin(FieldsetsMixin, BasicModelAdmin):
model = PromoCode
list_display = ( list_display = (
"code", "code",
"discount_percent", "discount_percent",
@ -344,54 +326,61 @@ class PromoCodeAdmin(BasicModelAdmin):
list_filter = ("discount_percent", "discount_amount", "start_time", "end_time") list_filter = ("discount_percent", "discount_amount", "start_time", "end_time")
search_fields = ("code",) search_fields = ("code",)
def get_queryset(self, request): general_fields = ["code", "discount_amount", "discount_percent"]
return super().get_queryset(request).prefetch_related("user") relation_fields = ["user"]
@admin.register(Promotion) @admin.register(Promotion)
class PromotionAdmin(TranslationFieldsetMixin, BasicModelAdmin): class PromotionAdmin(FieldsetsMixin, TranslationFieldsetMixin, BasicModelAdmin):
model = Promotion
list_display = ("name", "discount_percent", "modified") list_display = ("name", "discount_percent", "modified")
search_fields = ("name",) search_fields = ("name",)
autocomplete_fields = ("products",) autocomplete_fields = ("products",)
def get_queryset(self, request): general_fields = ["name", "discount_percent", "description"]
return super().get_queryset(request).prefetch_related("products") relation_fields = ["products"]
@admin.register(Stock) @admin.register(Stock)
class StockAdmin(BasicModelAdmin): class StockAdmin(FieldsetsMixin, BasicModelAdmin):
model = Stock
list_display = ("product", "vendor", "sku", "quantity", "price", "modified") list_display = ("product", "vendor", "sku", "quantity", "price", "modified")
list_filter = ("vendor", "quantity") list_filter = ("vendor", "quantity")
search_fields = ("product__name", "vendor__name", "sku") search_fields = ("product__name", "vendor__name", "sku")
autocomplete_fields = ("product", "vendor") autocomplete_fields = ("product", "vendor")
general_fields = ["sku", "quantity", "price", "purchase_price", "digital_asset"]
relation_fields = ["product", "vendor"]
@admin.register(Wishlist) @admin.register(Wishlist)
class WishlistAdmin(BasicModelAdmin): class WishlistAdmin(FieldsetsMixin, BasicModelAdmin):
model = Wishlist
list_display = ("user", "modified") list_display = ("user", "modified")
search_fields = ("user__email",) search_fields = ("user__email",)
general_fields = ["user"]
relation_fields = ["products"]
@admin.register(ProductImage) @admin.register(ProductImage)
class ProductImageAdmin(TranslationFieldsetMixin, BasicModelAdmin): class ProductImageAdmin(FieldsetsMixin, TranslationFieldsetMixin, BasicModelAdmin):
model = ProductImage
list_display = ("alt", "product", "priority", "modified") list_display = ("alt", "product", "priority", "modified")
list_filter = ("priority",) list_filter = ("priority",)
search_fields = ("alt", "product__name") search_fields = ("alt", "product__name")
autocomplete_fields = ("product",) autocomplete_fields = ("product",)
general_fields = ["alt", "priority", "image"]
relation_fields = ["product"]
@admin.register(Address) @admin.register(Address)
class AddressAdmin(GISModelAdmin): class AddressAdmin(FieldsetsMixin, GISModelAdmin):
model = Address
list_display = ("street", "city", "region", "country", "user") list_display = ("street", "city", "region", "country", "user")
list_filter = ("country", "region") list_filter = ("country", "region")
search_fields = ( search_fields = ("street", "city", "postal_code", "user__email")
"raw_data",
"street",
"city",
"postal_code",
"user__email",
"address_line",
)
gis_widget_kwargs = { gis_widget_kwargs = {
"attrs": { "attrs": {
"default_lon": 37.61556, "default_lon": 37.61556,
@ -400,15 +389,27 @@ class AddressAdmin(GISModelAdmin):
} }
} }
general_fields = [
"address_line",
"street",
"district",
"city",
"region",
"postal_code",
"country",
"raw_data",
]
relation_fields = ["user", "api_response"]
# Constance config
# Constance configuration
class ConstanceConfig: class ConstanceConfig:
class Meta: class Meta:
app_label = "core" app_label = "core"
object_name = "Config" object_name = "Config"
concrete_model = None concrete_model = None
model_name = module_name = "config" model_name = module_name = "config"
verbose_name_plural = _("config") verbose_name_plural = _("Config")
abstract = False abstract = False
swapped = False swapped = False
is_composite_pk = False is_composite_pk = False
@ -436,6 +437,6 @@ class ConstanceConfig:
admin.site.unregister([Config]) admin.site.unregister([Config])
admin.site.register([ConstanceConfig], BaseConstanceAdmin) admin.site.register([ConstanceConfig], BaseConstanceAdmin)
admin.site.site_title = f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]}" admin.site.site_title = CONSTANCE_CONFIG["PROJECT_NAME"][0]
admin.site.site_header = "eVibes" admin.site.site_header = "eVibes"
admin.site.index_title = f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]}" admin.site.index_title = CONSTANCE_CONFIG["PROJECT_NAME"][0]