Features: 1) Add I18NFieldsetMixin to organize translation fields into a dedicated fieldset; 2) Implement I18NTabTranslationAdmin for enhanced multi-language admin support; 3) Apply new translation admin to AttributeGroupAdmin, AttributeAdmin, AttributeValueAdmin, CategoryAdmin, BrandAdmin, ProductAdmin, ProductTagAdmin, CategoryTagAdmin, and PromotionAdmin.

Fixes: 1) Replace `TranslationGenericTabularInline` with `I18NTabTranslationAdmin` to avoid potential translation field duplication.

Extra: 1) Update fieldsets in various model admins for improved structure and translation field grouping; 2) Add constants for fieldset section names to enhance readability and consistency.
This commit is contained in:
Egor Pavlovich Gorbunov 2025-06-22 01:31:33 +03:00
parent 02102fa470
commit 48ef110ac4

View file

@ -5,9 +5,14 @@ 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.urls import path
from django.utils.translation import gettext_lazy as _
from modeltranslation.admin import TranslationGenericTabularInline
from modeltranslation.admin import (
TabbedExternalJqueryTranslationAdmin,
)
from modeltranslation.translator import translator
from modeltranslation.utils import get_translation_fields
from mptt.admin import DraggableMPTTAdmin
from evibes.settings import CONSTANCE_CONFIG
@ -34,6 +39,51 @@ from .models import (
Wishlist,
)
SECTION_GENERAL = _("general")
SECTION_I18N = _("I18N")
SECTION_META = _("metadata")
SECTION_DATES = _("timestamps")
SECTION_RELATIONS = _("relations")
class I18NFieldsetMixin:
model: type[Model]
"""
Pulls all <field>_<lang> translation columns out of the regular
fieldsets and tucks them into one extra fieldset/tab called "I18N".
"""
def get_fieldsets(self, request, obj=None):
base_fieldsets = super().get_fieldsets(request, obj)
trans_opts = translator.get_options_for_model(self.model)
translation_fields = []
for orig in trans_opts.fields.keys(): # noqa: SIM118
translation_fields.extend(get_translation_fields(orig))
cleaned = []
for title, opts in base_fieldsets:
original = list(opts.get("fields", []))
filtered = [f for f in original if f not in translation_fields]
opts = opts.copy()
opts["fields"] = filtered
cleaned.append((title, opts))
cleaned.append(
(
_("I18N"),
{
"classes": ("suit-tab-i18n",),
"fields": tuple(translation_fields),
},
)
)
return cleaned
class I18NTabTranslationAdmin(I18NFieldsetMixin, TabbedExternalJqueryTranslationAdmin):
pass
class BasicModelAdmin(ModelAdmin):
@admin.action(description=str(_("activate selected %(verbose_name_plural)s")))
@ -71,16 +121,41 @@ class AttributeValueInline(TabularInline):
@admin.register(AttributeGroup)
class AttributeGroupAdmin(BasicModelAdmin, TranslationGenericTabularInline):
class AttributeGroupAdmin(BasicModelAdmin, I18NTabTranslationAdmin):
list_display = ("name", "modified")
search_fields = (
"uuid",
"name",
)
fieldsets = (
(
SECTION_GENERAL,
{
"fields": ("name", "parent", "is_active"),
},
),
(
SECTION_META,
{
"fields": ("uuid",),
},
),
(
SECTION_I18N,
{
"classes": ("suit-tab-i18n",),
"fields": tuple( # pulled in by I18NFieldsetMixin
translator.get_options_for_model(
AttributeGroup
).get_all_translation_fields()
),
},
),
)
@admin.register(Attribute)
class AttributeAdmin(BasicModelAdmin, TranslationGenericTabularInline):
class AttributeAdmin(BasicModelAdmin, I18NTabTranslationAdmin):
list_display = ("name", "group", "value_type", "modified")
list_filter = ("value_type", "group", "is_active")
search_fields = ("uuid", "name", "group__name")
@ -88,7 +163,7 @@ class AttributeAdmin(BasicModelAdmin, TranslationGenericTabularInline):
@admin.register(AttributeValue)
class AttributeValueAdmin(BasicModelAdmin, TranslationGenericTabularInline):
class AttributeValueAdmin(BasicModelAdmin, I18NTabTranslationAdmin):
list_display = ("attribute", "value", "modified")
list_filter = ("attribute__group", "is_active")
search_fields = ("uuid", "value", "attribute__name")
@ -104,7 +179,7 @@ class CategoryChildrenInline(admin.TabularInline):
@admin.register(Category)
class CategoryAdmin(DraggableMPTTAdmin, BasicModelAdmin, TranslationGenericTabularInline):
class CategoryAdmin(DraggableMPTTAdmin, BasicModelAdmin, I18NTabTranslationAdmin):
mptt_indent_field = "name"
list_display = ("indented_title", "parent", "is_active", "modified")
# noinspection PyUnresolvedReferences
@ -115,25 +190,48 @@ class CategoryAdmin(DraggableMPTTAdmin, BasicModelAdmin, TranslationGenericTabul
"name",
)
inlines = [CategoryChildrenInline]
fieldsets = [
fieldsets = (
(
None,
SECTION_GENERAL,
{
"fields": (
"uuid",
"slug",
"name",
"description",
"slug",
"parent",
"is_active",
"priority",
"image",
"markup_percent",
"tags",
)
),
},
)
]
),
(
SECTION_RELATIONS,
{
"fields": ("tags",),
},
),
(
SECTION_META,
{
"fields": ("uuid",),
},
),
(
SECTION_DATES,
{
"fields": ("created", "modified"),
},
),
(
SECTION_I18N,
{
"classes": ("suit-tab-i18n",),
"fields": tuple(
translator.get_options_for_model(
Category
).get_all_translation_fields()
),
},
),
)
autocomplete_fields = ["parent", "tags"]
readonly_fields = (
"uuid",
@ -148,7 +246,7 @@ class CategoryAdmin(DraggableMPTTAdmin, BasicModelAdmin, TranslationGenericTabul
@admin.register(Brand)
class BrandAdmin(BasicModelAdmin, TranslationGenericTabularInline):
class BrandAdmin(BasicModelAdmin, I18NTabTranslationAdmin):
list_display = ("name",)
list_filter = ("categories", "is_active")
search_fields = (
@ -179,7 +277,7 @@ class StockInline(TabularInline):
@admin.register(Product)
class ProductAdmin(BasicModelAdmin, TranslationGenericTabularInline):
class ProductAdmin(BasicModelAdmin, I18NTabTranslationAdmin):
list_display = (
"name",
"partnumber",
@ -224,23 +322,46 @@ class ProductAdmin(BasicModelAdmin, TranslationGenericTabularInline):
fieldsets = (
(
_("basic info"),
SECTION_GENERAL,
{
"fields": (
"uuid",
"name",
"slug",
"partnumber",
"is_active",
"is_digital",
"name",
"category",
"brand",
"description",
"tags",
)
),
},
),
(
SECTION_RELATIONS,
{
"fields": ("category", "brand", "tags"),
},
),
(
SECTION_META,
{
"fields": ("uuid",),
},
),
(
SECTION_DATES,
{
"fields": ("created", "modified"),
},
),
(
SECTION_I18N,
{
"classes": ("suit-tab-i18n",),
"fields": tuple(
translator.get_options_for_model(
Product
).get_all_translation_fields()
),
},
),
(_("important dates"), {"fields": ("created", "modified")}),
)
inlines = [AttributeValueInline, ProductImageInline, StockInline]
@ -252,13 +373,13 @@ class ProductAdmin(BasicModelAdmin, TranslationGenericTabularInline):
@admin.register(ProductTag)
class ProductTagAdmin(BasicModelAdmin, TranslationGenericTabularInline):
class ProductTagAdmin(BasicModelAdmin, I18NTabTranslationAdmin):
list_display = ("name",)
search_fields = ("name",)
@admin.register(CategoryTag)
class CategoryTagAdmin(BasicModelAdmin, TranslationGenericTabularInline):
class CategoryTagAdmin(BasicModelAdmin, I18NTabTranslationAdmin):
list_display = ("name",)
search_fields = ("name",)
@ -370,7 +491,7 @@ class PromoCodeAdmin(BasicModelAdmin):
@admin.register(Promotion)
class PromotionAdmin(BasicModelAdmin, TranslationGenericTabularInline):
class PromotionAdmin(BasicModelAdmin, I18NTabTranslationAdmin):
list_display = ("name", "discount_percent", "modified")
search_fields = ("name",)
autocomplete_fields = ("products",)
@ -433,7 +554,9 @@ class ConstanceAdmin(BaseConstanceAdmin):
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"),
path(
"", self.admin_site.admin_view(self.changelist_view), name=f"{info}_add"
),
]