schon/core/admin.py
Egor fureunoir Gorbunov c682137fc5 Features: 1) Replace I18NFieldsetMixin with TranslationFieldsetMixin across all applicable admin classes; 2) Add new inlines for managing related objects such as OrderProduct and CategoryChildren; 3) Introduce support for dynamic translation fields in admin fieldsets.
Fixes: 1) Correct `notifications` field handling in `OrderAdmin` and `OrderItemAdmin` save logic; 2) Fix admin `get_queryset` prefetching logic for various models.

Extra: Remove outdated labels and sections in fieldsets; clean up unused imports and commented code for readability.
2025-06-22 16:15:48 +03:00

441 lines
13 KiB
Python

# noinspection PyUnresolvedReferences
from constance.admin import Config
from constance.admin import ConstanceAdmin as BaseConstanceAdmin
from django.apps import apps
from django.contrib import admin
from django.contrib.admin import ModelAdmin, TabularInline
from django.contrib.gis.admin import GISModelAdmin
from django.db.models import Model
from django.utils.translation import gettext_lazy as _
from modeltranslation.translator import translator
from modeltranslation.utils import get_translation_fields
from mptt.admin import DraggableMPTTAdmin
from evibes.settings import CONSTANCE_CONFIG
from .forms import OrderForm, OrderProductForm, VendorForm
from .models import (
Address,
Attribute,
AttributeGroup,
AttributeValue,
Brand,
Category,
CategoryTag,
Feedback,
Order,
OrderProduct,
Product,
ProductImage,
ProductTag,
PromoCode,
Promotion,
Stock,
Vendor,
Wishlist,
)
class TranslationFieldsetMixin:
model: Model
def get_fieldsets(self, request, obj=None):
fieldsets = super().get_fieldsets(request, obj)
opts = translator.get_options_for_model(self.model)
translation_fields = []
for orig in opts.local_fields:
translation_fields += get_translation_fields(orig)
if translation_fields:
fieldsets = list(fieldsets)
fieldsets.append(
(
_("translations"),
{"fields": translation_fields},
)
)
return fieldsets
class BasicModelAdmin(ModelAdmin):
@admin.action(description=str(_("activate selected %(verbose_name_plural)s")))
def activate_selected(self, _request, queryset) -> str:
queryset.update(is_active=True)
return str(_("%(verbose_name_plural)s activated successfully!"))
@admin.action(description=str(_("deactivate selected %(verbose_name_plural)s")))
def deactivate_selected(self, _request, queryset) -> str:
queryset.update(is_active=False)
return str(_("%(verbose_name_plural)s deactivated successfully."))
def get_actions(self, request):
actions = super().get_actions(request)
actions["activate_selected"] = (
self.activate_selected,
"activate_selected",
str(_("activate selected %(verbose_name_plural)s")),
)
actions["deactivate_selected"] = (
self.deactivate_selected,
"deactivate_selected",
str(_("deactivate selected %(verbose_name_plural)s")),
)
return actions
class AttributeValueInline(TabularInline):
model = AttributeValue
extra = 0
is_navtab = True
verbose_name = _("attribute value")
verbose_name_plural = _("attribute values")
autocomplete_fields = ["attribute"]
icon = "fa-regular fa-circle-dot"
class ProductImageInline(TabularInline):
model = ProductImage
extra = 0
is_navtab = True
verbose_name = _("image")
verbose_name_plural = _("images")
icon = "fa-regular fa-images"
class StockInline(TabularInline):
model = Stock
extra = 0
is_navtab = True
verbose_name = _("stock")
verbose_name_plural = _("stocks")
icon = "fa-regular fa-boxes-stacked"
class OrderProductInline(TabularInline):
model = OrderProduct
extra = 0
readonly_fields = ("product", "quantity", "buy_price")
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):
return (
super()
.get_queryset(request)
.select_related("product")
.only("product__name")
)
class CategoryChildrenInline(TabularInline):
model = Category
fk_name = "parent"
extra = 0
fields = ("name", "description", "is_active", "image", "markup_percent")
icon = "fa-regular fa-circle-dot"
# Admin registrations
@admin.register(AttributeGroup)
class AttributeGroupAdmin(TranslationFieldsetMixin, BasicModelAdmin):
list_display = ("name", "modified")
search_fields = ("uuid", "name")
@admin.register(Attribute)
class AttributeAdmin(TranslationFieldsetMixin, BasicModelAdmin):
list_display = ("name", "group", "value_type", "modified")
list_filter = ("value_type", "group", "is_active")
search_fields = ("uuid", "name", "group__name")
autocomplete_fields = ["categories", "group"]
@admin.register(AttributeValue)
class AttributeValueAdmin(TranslationFieldsetMixin, BasicModelAdmin):
list_display = ("attribute", "value", "modified")
list_filter = ("attribute__group", "is_active")
search_fields = ("uuid", "value", "attribute__name")
autocomplete_fields = ["attribute"]
@admin.register(Category)
class CategoryAdmin(TranslationFieldsetMixin, DraggableMPTTAdmin, BasicModelAdmin):
mptt_indent_field = "name"
list_display = ("indented_title", "parent", "is_active", "modified")
list_filter = ("is_active", "level", "created", "modified")
list_display_links = ("indented_title",)
search_fields = ("uuid", "name")
inlines = [CategoryChildrenInline]
autocomplete_fields = ["parent", "tags"]
readonly_fields = ("uuid", "slug", "created", "modified")
def indented_title(self, instance):
return instance.name
indented_title.short_description = _("name")
indented_title.admin_order_field = "name"
@admin.register(Brand)
class BrandAdmin(TranslationFieldsetMixin, BasicModelAdmin):
list_display = ("name",)
list_filter = ("categories", "is_active")
search_fields = ("uuid", "name", "categories__name")
readonly_fields = ("uuid", "slug")
@admin.register(Product)
class ProductAdmin(TranslationFieldsetMixin, BasicModelAdmin):
list_display = (
"name",
"partnumber",
"is_active",
"category",
"brand",
"price",
"rating",
"modified",
)
list_filter = (
"is_active",
"is_digital",
("tags", admin.RelatedOnlyFieldListFilter),
("stocks__vendor", admin.RelatedOnlyFieldListFilter),
"created",
"modified",
)
search_fields = (
"name",
"partnumber",
"brand__name",
"category__name",
"uuid",
"slug",
)
readonly_fields = ("created", "modified", "uuid", "rating", "price", "slug")
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]
def price(self, obj):
return obj.price
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)
class ProductTagAdmin(TranslationFieldsetMixin, BasicModelAdmin):
list_display = ("name",)
search_fields = ("name",)
@admin.register(CategoryTag)
class CategoryTagAdmin(TranslationFieldsetMixin, BasicModelAdmin):
list_display = ("name",)
search_fields = ("name",)
@admin.register(Vendor)
class VendorAdmin(BasicModelAdmin):
list_display = ("name", "markup_percent", "modified")
list_filter = ("markup_percent", "is_active")
search_fields = ("name",)
form = VendorForm
@admin.register(Feedback)
class FeedbackAdmin(TranslationFieldsetMixin, BasicModelAdmin):
list_display = ("order_product", "rating", "comment", "modified")
list_filter = ("rating", "is_active")
search_fields = ("order_product__product__name", "comment")
@admin.register(Order)
class OrderAdmin(BasicModelAdmin):
list_display = (
"human_readable_id",
"user",
"is_business",
"status",
"total_price",
"buy_time",
"modified",
)
list_filter = ("status", "buy_time", "modified", "created")
search_fields = ("user__email", "status", "uuid", "human_readable_id")
readonly_fields = ("total_price", "total_quantity", "buy_time", "human_readable_id")
inlines = [OrderProductInline]
form = OrderForm
def is_business(self, obj):
return obj.is_business
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)
class OrderProductAdmin(BasicModelAdmin):
list_display = ("order", "product", "quantity", "buy_price", "status", "modified")
list_filter = ("status",)
search_fields = ("order__user__email", "product__name")
form = OrderProductForm
def get_queryset(self, request):
return super().get_queryset(request).prefetch_related("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)
class PromoCodeAdmin(BasicModelAdmin):
list_display = (
"code",
"discount_percent",
"discount_amount",
"start_time",
"end_time",
"used_on",
)
list_filter = ("discount_percent", "discount_amount", "start_time", "end_time")
search_fields = ("code",)
def get_queryset(self, request):
return super().get_queryset(request).prefetch_related("user")
@admin.register(Promotion)
class PromotionAdmin(TranslationFieldsetMixin, BasicModelAdmin):
list_display = ("name", "discount_percent", "modified")
search_fields = ("name",)
autocomplete_fields = ("products",)
def get_queryset(self, request):
return super().get_queryset(request).prefetch_related("products")
@admin.register(Stock)
class StockAdmin(BasicModelAdmin):
list_display = ("product", "vendor", "sku", "quantity", "price", "modified")
list_filter = ("vendor", "quantity")
search_fields = ("product__name", "vendor__name", "sku")
autocomplete_fields = ("product", "vendor")
@admin.register(Wishlist)
class WishlistAdmin(BasicModelAdmin):
list_display = ("user", "modified")
search_fields = ("user__email",)
@admin.register(ProductImage)
class ProductImageAdmin(TranslationFieldsetMixin, BasicModelAdmin):
list_display = ("alt", "product", "priority", "modified")
list_filter = ("priority",)
search_fields = ("alt", "product__name")
autocomplete_fields = ("product",)
@admin.register(Address)
class AddressAdmin(GISModelAdmin):
list_display = ("street", "city", "region", "country", "user")
list_filter = ("country", "region")
search_fields = (
"raw_data",
"street",
"city",
"postal_code",
"user__email",
"address_line",
)
gis_widget_kwargs = {
"attrs": {
"default_lon": 37.61556,
"default_lat": 55.75222,
"default_zoom": 6,
}
}
# Constance config
class ConstanceConfig:
class Meta:
app_label = "core"
object_name = "Config"
concrete_model = None
model_name = module_name = "config"
verbose_name_plural = _("config")
abstract = False
swapped = False
is_composite_pk = False
def get_change_permission(self):
return f"change_{self.model_name}"
@property
def app_config(self):
return apps.get_app_config(self.app_label)
@property
def label(self):
return f"{self.app_label}.{self.object_name}"
@property
def label_lower(self):
return f"{self.app_label}.{self.model_name}"
def get_ordered_objects(self):
return False
_meta = Meta()
admin.site.unregister([Config])
admin.site.register([ConstanceConfig], BaseConstanceAdmin)
admin.site.site_title = f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]}"
admin.site.site_header = "eVibes"
admin.site.index_title = f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]}"