schon/core/admin.py
Egor fureunoir Gorbunov 76c3cd17e3 Features: 1) Replace I18NTabTranslationAdmin with I18NFieldsetMixin for admin classes; 2) Introduce _TranslationInline for managing translation fields dynamically; 3) Add created and modified fields to readonly_fields in Category admin class;
Fixes: 1) Resolve unnecessary imports from `modeltranslation.admin` and streamline admin imports;

Extra: 1) Refactor translation logic into `get_inline_instances` for improved modularity and clarity.
2025-06-22 01:55:46 +03:00

577 lines
16 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.admin.options import InlineModelAdmin
from django.contrib.gis.admin import GISModelAdmin
from django.db.models import Model
from django.forms import modelform_factory
from django.urls import path
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,
)
SECTION_GENERAL = _("general")
SECTION_I18N = _("I18N")
SECTION_META = _("metadata")
SECTION_DATES = _("timestamps")
SECTION_RELATIONS = _("relations")
class I18NFieldsetMixin:
model: type[Model]
def get_inline_instances(self, request, obj=None):
inlines = super().get_inline_instances(request, obj)
trans_opts = translator.get_options_for_model(self.model)
translation_fields = []
for orig in trans_opts.local_fields:
translation_fields += get_translation_fields(orig)
class _TranslationInline(InlineModelAdmin[self.model, self.model]):
model = self.model
form = modelform_factory(
self.model,
fields=translation_fields,
formfield_callback=lambda f, **kw: super(
I18NFieldsetMixin, self
).formfield_for_dbfield(f, request, **kw),
)
template = "admin/edit_inline/tabular.html"
is_navtab = True
verbose_name = _("translations")
verbose_name_plural = _("translations")
extra = 0
def get_queryset(self, request):
return (
self.model.objects.filter(pk=obj.pk)
if obj
else self.model.objects.none()
)
def get_formset(self, request, obj=None, **kw):
return super().get_formset(
request,
obj,
**{
**kw,
"form": self.form,
"fields": translation_fields,
"exclude": [],
},
)
inlines.append(_TranslationInline(self.model, self.admin_site))
return inlines
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 ""
@admin.action(description=str(_("deactivate selected %(verbose_name_plural)s")))
def deactivate_selected(self, _request, queryset) -> str:
queryset.update(is_active=False)
return ""
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"]
@admin.register(AttributeGroup)
class AttributeGroupAdmin(BasicModelAdmin, I18NFieldsetMixin):
list_display = ("name", "modified")
search_fields = (
"uuid",
"name",
)
fieldsets = (
(
SECTION_GENERAL,
{
"fields": ("name", "parent", "is_active"),
},
),
(
SECTION_META,
{
"fields": ("uuid",),
},
),
)
@admin.register(Attribute)
class AttributeAdmin(BasicModelAdmin, I18NFieldsetMixin):
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(BasicModelAdmin, I18NFieldsetMixin):
list_display = ("attribute", "value", "modified")
list_filter = ("attribute__group", "is_active")
search_fields = ("uuid", "value", "attribute__name")
autocomplete_fields = ["attribute"]
class CategoryChildrenInline(admin.TabularInline):
model = Category
fk_name = "parent"
extra = 0
fields = ("name", "description", "is_active", "image", "markup_percent")
@admin.register(Category)
class CategoryAdmin(DraggableMPTTAdmin, BasicModelAdmin, I18NFieldsetMixin):
mptt_indent_field = "name"
list_display = ("indented_title", "parent", "is_active", "modified")
# noinspection PyUnresolvedReferences
list_filter = ("is_active", "level", "created", "modified")
list_display_links = ("indented_title",)
search_fields = (
"uuid",
"name",
)
inlines = [CategoryChildrenInline]
fieldsets = (
(
SECTION_GENERAL,
{
"fields": (
"name",
"slug",
"parent",
"is_active",
),
},
),
(
SECTION_RELATIONS,
{
"fields": ("tags",),
},
),
(
SECTION_META,
{
"fields": ("uuid",),
},
),
(
SECTION_DATES,
{
"fields": ("created", "modified"),
},
),
)
autocomplete_fields = ["parent", "tags"]
readonly_fields = (
"uuid",
"slug",
"created",
"modified",
)
def indented_title(self, instance):
return instance.name
indented_title.short_description = _("name") # type: ignore
indented_title.admin_order_field = "name" # type: ignore
@admin.register(Brand)
class BrandAdmin(BasicModelAdmin, I18NFieldsetMixin):
list_display = ("name",)
list_filter = ("categories", "is_active")
search_fields = (
"uuid",
"name",
"categories__name",
)
readonly_fields = (
"uuid",
"slug",
)
class ProductImageInline(TabularInline):
model = ProductImage
extra = 0
is_navtab = True
verbose_name = _("image")
verbose_name_plural = _("images")
class StockInline(TabularInline):
model = Stock
extra = 0
is_navtab = True
verbose_name = _("stock")
verbose_name_plural = _("stocks")
@admin.register(Product)
class ProductAdmin(BasicModelAdmin, I18NFieldsetMixin):
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")
def price(self, obj):
return obj.price
price.short_description = _("price") # type: ignore
def rating(self, obj):
return obj.rating
rating.short_description = _("rating") # type: ignore
fieldsets = (
(
SECTION_GENERAL,
{
"fields": (
"name",
"slug",
"partnumber",
"is_active",
"is_digital",
),
},
),
(
SECTION_RELATIONS,
{
"fields": ("category", "brand", "tags"),
},
),
(
SECTION_META,
{
"fields": ("uuid",),
},
),
(
SECTION_DATES,
{
"fields": ("created", "modified"),
},
),
)
inlines = [AttributeValueInline, ProductImageInline, StockInline]
def get_changelist(self, request, **kwargs):
changelist = super().get_changelist(request, **kwargs)
changelist.filter_input_length = 64
return changelist
@admin.register(ProductTag)
class ProductTagAdmin(BasicModelAdmin, I18NFieldsetMixin):
list_display = ("name",)
search_fields = ("name",)
@admin.register(CategoryTag)
class CategoryTagAdmin(BasicModelAdmin, I18NFieldsetMixin):
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(BasicModelAdmin):
list_display = ("order_product", "rating", "comment", "modified")
list_filter = ("rating", "is_active")
search_fields = ("order_product__product__name", "comment")
class OrderProductInline(admin.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")
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.select_related("product").only("product__name")
@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")
inlines = [OrderProductInline]
form = OrderForm
readonly_fields = ("total_price", "total_quantity", "buy_time", "human_readable_id")
def is_business(self, obj):
return obj.is_business
is_business.short_description = _("is business") # type: ignore
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.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.attributes = 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):
qs = super().get_queryset(request)
return qs.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.attributes = 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):
qs = super().get_queryset(request)
return qs.prefetch_related("user")
@admin.register(Promotion)
class PromotionAdmin(BasicModelAdmin, I18NFieldsetMixin):
list_display = ("name", "discount_percent", "modified")
search_fields = ("name",)
autocomplete_fields = ("products",)
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.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(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,
}
}
class ConstanceAdmin(BaseConstanceAdmin):
def get_urls(self):
info = f"{self.model._meta.app_label}_{self.model._meta.model_name}"
return [
path(
"",
self.admin_site.admin_view(self.changelist_view),
name=f"{info}_changelist",
),
path(
"", self.admin_site.admin_view(self.changelist_view), name=f"{info}_add"
),
]
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_ordered_objects(self):
return 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}"
_meta = Meta()
admin.site.unregister([Config]) # type: ignore
admin.site.register([ConstanceConfig], ConstanceAdmin) # type: ignore
admin.site.site_title = f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]}" # type: ignore
admin.site.site_header = "eVibes"
admin.site.index_title = f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]}" # type: ignore