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 import admin
from django.contrib.admin import ModelAdmin, TabularInline from django.contrib.admin import ModelAdmin, TabularInline
from django.contrib.gis.admin import GISModelAdmin from django.contrib.gis.admin import GISModelAdmin
from django.db.models import Model
from django.urls import path from django.urls import path
from django.utils.translation import gettext_lazy as _ 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 mptt.admin import DraggableMPTTAdmin
from evibes.settings import CONSTANCE_CONFIG from evibes.settings import CONSTANCE_CONFIG
@ -34,6 +39,51 @@ from .models import (
Wishlist, 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): class BasicModelAdmin(ModelAdmin):
@admin.action(description=str(_("activate selected %(verbose_name_plural)s"))) @admin.action(description=str(_("activate selected %(verbose_name_plural)s")))
@ -71,16 +121,41 @@ class AttributeValueInline(TabularInline):
@admin.register(AttributeGroup) @admin.register(AttributeGroup)
class AttributeGroupAdmin(BasicModelAdmin, TranslationGenericTabularInline): class AttributeGroupAdmin(BasicModelAdmin, I18NTabTranslationAdmin):
list_display = ("name", "modified") list_display = ("name", "modified")
search_fields = ( search_fields = (
"uuid", "uuid",
"name", "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) @admin.register(Attribute)
class AttributeAdmin(BasicModelAdmin, TranslationGenericTabularInline): class AttributeAdmin(BasicModelAdmin, I18NTabTranslationAdmin):
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")
@ -88,7 +163,7 @@ class AttributeAdmin(BasicModelAdmin, TranslationGenericTabularInline):
@admin.register(AttributeValue) @admin.register(AttributeValue)
class AttributeValueAdmin(BasicModelAdmin, TranslationGenericTabularInline): class AttributeValueAdmin(BasicModelAdmin, I18NTabTranslationAdmin):
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")
@ -104,7 +179,7 @@ class CategoryChildrenInline(admin.TabularInline):
@admin.register(Category) @admin.register(Category)
class CategoryAdmin(DraggableMPTTAdmin, BasicModelAdmin, TranslationGenericTabularInline): class CategoryAdmin(DraggableMPTTAdmin, BasicModelAdmin, I18NTabTranslationAdmin):
mptt_indent_field = "name" mptt_indent_field = "name"
list_display = ("indented_title", "parent", "is_active", "modified") list_display = ("indented_title", "parent", "is_active", "modified")
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
@ -115,25 +190,48 @@ class CategoryAdmin(DraggableMPTTAdmin, BasicModelAdmin, TranslationGenericTabul
"name", "name",
) )
inlines = [CategoryChildrenInline] inlines = [CategoryChildrenInline]
fieldsets = [ fieldsets = (
( (
None, SECTION_GENERAL,
{ {
"fields": ( "fields": (
"uuid",
"slug",
"name", "name",
"description", "slug",
"parent", "parent",
"is_active", "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"] autocomplete_fields = ["parent", "tags"]
readonly_fields = ( readonly_fields = (
"uuid", "uuid",
@ -148,7 +246,7 @@ class CategoryAdmin(DraggableMPTTAdmin, BasicModelAdmin, TranslationGenericTabul
@admin.register(Brand) @admin.register(Brand)
class BrandAdmin(BasicModelAdmin, TranslationGenericTabularInline): class BrandAdmin(BasicModelAdmin, I18NTabTranslationAdmin):
list_display = ("name",) list_display = ("name",)
list_filter = ("categories", "is_active") list_filter = ("categories", "is_active")
search_fields = ( search_fields = (
@ -179,7 +277,7 @@ class StockInline(TabularInline):
@admin.register(Product) @admin.register(Product)
class ProductAdmin(BasicModelAdmin, TranslationGenericTabularInline): class ProductAdmin(BasicModelAdmin, I18NTabTranslationAdmin):
list_display = ( list_display = (
"name", "name",
"partnumber", "partnumber",
@ -224,23 +322,46 @@ class ProductAdmin(BasicModelAdmin, TranslationGenericTabularInline):
fieldsets = ( fieldsets = (
( (
_("basic info"), SECTION_GENERAL,
{ {
"fields": ( "fields": (
"uuid", "name",
"slug", "slug",
"partnumber", "partnumber",
"is_active", "is_active",
"is_digital", "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] inlines = [AttributeValueInline, ProductImageInline, StockInline]
@ -252,13 +373,13 @@ class ProductAdmin(BasicModelAdmin, TranslationGenericTabularInline):
@admin.register(ProductTag) @admin.register(ProductTag)
class ProductTagAdmin(BasicModelAdmin, TranslationGenericTabularInline): class ProductTagAdmin(BasicModelAdmin, I18NTabTranslationAdmin):
list_display = ("name",) list_display = ("name",)
search_fields = ("name",) search_fields = ("name",)
@admin.register(CategoryTag) @admin.register(CategoryTag)
class CategoryTagAdmin(BasicModelAdmin, TranslationGenericTabularInline): class CategoryTagAdmin(BasicModelAdmin, I18NTabTranslationAdmin):
list_display = ("name",) list_display = ("name",)
search_fields = ("name",) search_fields = ("name",)
@ -370,7 +491,7 @@ class PromoCodeAdmin(BasicModelAdmin):
@admin.register(Promotion) @admin.register(Promotion)
class PromotionAdmin(BasicModelAdmin, TranslationGenericTabularInline): class PromotionAdmin(BasicModelAdmin, I18NTabTranslationAdmin):
list_display = ("name", "discount_percent", "modified") list_display = ("name", "discount_percent", "modified")
search_fields = ("name",) search_fields = ("name",)
autocomplete_fields = ("products",) autocomplete_fields = ("products",)
@ -433,7 +554,9 @@ class ConstanceAdmin(BaseConstanceAdmin):
self.admin_site.admin_view(self.changelist_view), self.admin_site.admin_view(self.changelist_view),
name=f"{info}_changelist", 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"
),
] ]