From e0b2f183ce20eb1397218c3b0e00f076b8658b5a Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Mon, 13 Oct 2025 16:27:47 +0300 Subject: [PATCH] Features: 1) Extend fieldsets by adding `additional_fields` support in `FieldsetsMixin`; 2) Add `CRMForm` for `CustomerRelationshipManagementProviderAdmin`; 3) Improve `Order.__str__` to use `human_readable_id` for better debugging context; Fixes: 1) Replace misplaced imports in `core/admin.py` for proper organization; Extra: Adjust admin fieldsets for consistency, formatting, and search enhancements; Minor contributions to migration file cleanup. --- core/admin.py | 618 ++++++++++++++---- core/forms.py | 10 + .../0044_vendor_last_processing_response.py | 1 - core/models.py | 2 +- evibes/settings/constance.py | 4 +- 5 files changed, 506 insertions(+), 129 deletions(-) diff --git a/core/admin.py b/core/admin.py index 63710aa7..a3f28ade 100644 --- a/core/admin.py +++ b/core/admin.py @@ -13,7 +13,7 @@ from modeltranslation.translator import NotRegistered, translator from modeltranslation.utils import get_translation_fields from mptt.admin import DraggableMPTTAdmin -from core.forms import OrderForm, OrderProductForm, VendorForm +from core.forms import CRMForm, OrderForm, OrderProductForm, VendorForm from core.models import ( Address, Attribute, @@ -22,8 +22,10 @@ from core.models import ( Brand, Category, CategoryTag, + CustomerRelationshipManagementProvider, Feedback, Order, + OrderCrmLink, OrderProduct, Product, ProductImage, @@ -33,8 +35,6 @@ from core.models import ( Stock, Vendor, Wishlist, - CustomerRelationshipManagementProvider, - OrderCrmLink, ) from evibes.settings import CONSTANCE_CONFIG @@ -42,6 +42,7 @@ from evibes.settings import CONSTANCE_CONFIG class FieldsetsMixin: general_fields: list = [] relation_fields: list = [] + additional_fields: list = [] model: ClassVar[Type[Model]] def get_fieldsets(self, request, obj=None): @@ -67,6 +68,8 @@ class FieldsetsMixin: fieldsets.append((_("general"), {"fields": self.general_fields})) if self.relation_fields: fieldsets.append((_("relations"), {"fields": self.relation_fields})) + if self.additional_fields: + fieldsets.append((_("additional info"), {"fields": self.additional_fields})) opts = self.model._meta meta_fields = [] @@ -186,69 +189,187 @@ class CategoryChildrenInline(TabularInline): class AttributeGroupAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc] # noinspection PyClassVar model = AttributeGroup # type: ignore [misc] - list_display = ("name", "modified") - search_fields = ("uuid", "name") - readonly_fields = ("uuid", "modified", "created") - - general_fields = ["is_active", "name", "parent"] - relation_fields = [] + list_display = ( + "name", + "modified", + ) + search_fields = ( + "uuid", + "name", + ) + readonly_fields = ( + "uuid", + "modified", + "created", + ) + general_fields = [ + "is_active", + "name", + "parent", + ] @register(Attribute) class AttributeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc] # noinspection PyClassVar model = Attribute # type: ignore [misc] - list_display = ("name", "group", "value_type", "modified") - list_filter = ("value_type", "group", "is_active") - search_fields = ("uuid", "name", "group__name") - readonly_fields = ("uuid", "modified", "created") - autocomplete_fields = ["categories", "group"] - - general_fields = ["is_active", "name", "value_type", "is_filterable"] - relation_fields = ["group", "categories"] + list_display = ( + "name", + "group", + "value_type", + "modified", + ) + list_filter = ( + "value_type", + "group", + "is_active", + ) + search_fields = ( + "uuid", + "name", + "group__name", + ) + readonly_fields = ( + "uuid", + "modified", + "created", + ) + autocomplete_fields = [ + "categories", + "group", + ] + general_fields = [ + "is_active", + "name", + "value_type", + "is_filterable", + ] + relation_fields = [ + "group", + "categories", + ] @register(AttributeValue) class AttributeValueAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc] # noinspection PyClassVar model = AttributeValue # type: ignore [misc] - list_display = ("attribute", "value", "modified") - list_filter = ("attribute__group", "is_active") - search_fields = ("uuid", "value", "attribute__name") - readonly_fields = ("uuid", "modified", "created") - autocomplete_fields = ["attribute"] - - general_fields = ["is_active", "value"] - relation_fields = ["attribute", "product"] + list_display = ( + "attribute", + "value", + "modified", + ) + list_filter = ( + "attribute__group", + "is_active", + ) + search_fields = ( + "uuid", + "value", + "attribute__name", + ) + readonly_fields = ( + "uuid", + "modified", + "created", + ) + autocomplete_fields = [ + "attribute", + ] + general_fields = [ + "is_active", + "value", + ] + relation_fields = [ + "attribute", + "product", + ] @register(Category) class CategoryAdmin(FieldsetsMixin, ActivationActionsMixin, DraggableMPTTAdmin): # noinspection PyClassVar model = Category - list_display = ("indented_title", "parent", "is_active", "modified") + list_display = ( + "indented_title", + "parent", + "is_active", + "modified", + ) # noinspection PyUnresolvedReferences - list_filter = ("is_active", "level", "created", "modified") - search_fields = ("uuid", "name") - inlines = [CategoryChildrenInline] - autocomplete_fields = ["parent", "tags"] - readonly_fields = ("slug", "uuid", "modified", "created") + list_filter = ( + "is_active", + "level", + "created", + "modified", + ) + search_fields = ( + "uuid", + "name", + ) + inlines = [ + CategoryChildrenInline, + ] + autocomplete_fields = [ + "parent", + "tags", + ] + readonly_fields = ( + "slug", + "uuid", + "modified", + "created", + ) - general_fields = ["is_active", "name", "description", "image", "markup_percent", "priority"] - relation_fields = ["parent", "tags"] + general_fields = [ + "is_active", + "name", + "description", + "image", + "markup_percent", + "priority", + ] + relation_fields = [ + "parent", + "tags", + ] @register(Brand) class BrandAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc] # noinspection PyClassVar model = Brand # type: ignore [misc] - list_display = ("name",) - list_filter = ("categories", "is_active") - search_fields = ("uuid", "name", "categories__name") - readonly_fields = ("uuid", "slug", "modified", "created") - - general_fields = ["is_active", "name", "description", "priority"] - relation_fields = ["small_logo", "big_logo", "categories"] + list_display = ( + "name", + "priority", + "is_active", + ) + list_filter = ( + "categories", + "is_active", + ) + search_fields = ( + "uuid", + "name", + "categories__name", + ) + readonly_fields = ( + "uuid", + "slug", + "modified", + "created", + ) + general_fields = [ + "is_active", + "name", + "description", + "priority", + ] + relation_fields = [ + "small_logo", + "big_logo", + "categories", + ] @register(Product) @@ -284,12 +405,34 @@ class ProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: "slug", "sku", ) - readonly_fields = ("slug", "uuid", "modified", "created") - autocomplete_fields = ("category", "brand", "tags") - inlines = [AttributeValueInline, ProductImageInline, StockInline] + readonly_fields = ( + "slug", + "uuid", + "modified", + "created", + ) + autocomplete_fields = ( + "category", + "brand", + "tags", + ) + inlines = [ + AttributeValueInline, + ProductImageInline, + StockInline, + ] - general_fields = ["is_active", "name", "partnumber", "is_digital"] - relation_fields = ["category", "brand", "tags"] + general_fields = [ + "is_active", + "name", + "partnumber", + "is_digital", + ] + relation_fields = [ + "category", + "brand", + "tags", + ] @register(ProductTag) @@ -298,49 +441,113 @@ class ProductTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # ty model = ProductTag # type: ignore [misc] list_display = ("tag_name",) search_fields = ("tag_name",) - readonly_fields = ("uuid", "modified", "created") - - general_fields = ["is_active", "tag_name", "name"] - relation_fields = [] + readonly_fields = ( + "uuid", + "modified", + "created", + ) + general_fields = [ + "is_active", + "tag_name", + "name", + ] @register(CategoryTag) class CategoryTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc] # noinspection PyClassVar model = CategoryTag # type: ignore [misc] - list_display = ("tag_name",) - search_fields = ("tag_name",) - readonly_fields = ("uuid", "modified", "created") - - general_fields = ["is_active", "tag_name", "name"] - relation_fields = [] + list_display = ( + "name", + "tag_name", + "is_active", + ) + search_fields = ( + "name", + "tag_name", + "is_active", + ) + readonly_fields = ( + "uuid", + "modified", + "created", + ) + general_fields = [ + "is_active", + "tag_name", + "name", + ] @register(Vendor) class VendorAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc] # noinspection PyClassVar model = Vendor # type: ignore [misc] - list_display = ("name", "markup_percent", "modified") - list_filter = ("markup_percent", "is_active") - search_fields = ("name",) - readonly_fields = ("uuid", "modified", "created") + list_display = ( + "name", + "markup_percent", + "modified", + ) + list_filter = ( + "markup_percent", + "is_active", + ) + search_fields = ( + "name", + "uuid", + ) + readonly_fields = ( + "uuid", + "modified", + "created", + ) form = VendorForm - - general_fields = ["is_active", "name", "markup_percent", "authentication", "b2b_auth_token"] - relation_fields = ["users"] + general_fields = [ + "is_active", + "name", + "markup_percent", + "authentication", + ] + relation_fields = [ + "users", + ] + additional_fields = [ + "additional_fields", + "b2b_auth_token", + ] @register(Feedback) class FeedbackAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc] # noinspection PyClassVar model = Feedback # type: ignore [misc] - list_display = ("order_product", "rating", "comment", "modified") - list_filter = ("rating", "is_active") - search_fields = ("order_product__product__name", "comment") - readonly_fields = ("uuid", "modified", "created") - - general_fields = ["is_active", "rating", "comment"] - relation_fields = ["order_product"] + list_display = ( + "order_product", + "rating", + "comment", + "modified", + ) + list_filter = ( + "rating", + "is_active", + ) + search_fields = ( + "order_product__product__name", + "comment", + ) + readonly_fields = ( + "uuid", + "modified", + "created", + ) + general_fields = [ + "is_active", + "rating", + "comment", + ] + relation_fields = [ + "order_product", + ] @register(Order) @@ -355,8 +562,18 @@ class OrderAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: i "buy_time", "modified", ) - list_filter = ("status", "buy_time", "modified", "created") - search_fields = ("user__email", "status", "uuid", "human_readable_id") + list_filter = ( + "status", + "buy_time", + "modified", + "created", + ) + search_fields = ( + "user__email", + "status", + "uuid", + "human_readable_id", + ) readonly_fields = ( "total_price", "total_quantity", @@ -365,25 +582,61 @@ class OrderAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: i "modified", "created", ) - inlines = [OrderProductInline] + inlines = [ + OrderProductInline, + ] form = OrderForm - - general_fields = ["is_active", "user", "status", "notifications", "attributes", "buy_time"] - relation_fields = ["promo_code", "billing_address", "shipping_address"] + general_fields = [ + "is_active", + "user", + "status", + "notifications", + "attributes", + "buy_time", + ] + relation_fields = [ + "promo_code", + "billing_address", + "shipping_address", + ] @register(OrderProduct) class OrderProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc] # noinspection PyClassVar model = OrderProduct # type: ignore [misc] - list_display = ("order", "product", "quantity", "buy_price", "status", "modified") - list_filter = ("status",) - search_fields = ("order__user__email", "product__name") - readonly_fields = ("uuid", "modified", "created") + list_display = ( + "order", + "product", + "quantity", + "buy_price", + "status", + "modified", + ) + list_filter = ( + "status", + "modified", + ) + search_fields = ( + "order__user__email", + "product__name", + ) + readonly_fields = ( + "uuid", + "modified", + "created", + ) form = OrderProductForm - - general_fields = ["is_active", "quantity", "buy_price", "status"] - relation_fields = ["order", "product"] + general_fields = [ + "is_active", + "quantity", + "buy_price", + "status", + ] + relation_fields = [ + "order", + "product", + ] @register(PromoCode) @@ -398,11 +651,24 @@ class PromoCodeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # typ "end_time", "used_on", ) - list_filter = ("discount_percent", "discount_amount", "start_time", "end_time") - search_fields = ("code",) - readonly_fields = ("used_on", "uuid", "modified", "created") + list_filter = ( + "discount_percent", + "discount_amount", + "start_time", + "end_time", + ) + search_fields = ( + "code", + "uuid", + "user__email", + ) + readonly_fields = ( + "used_on", + "uuid", + "modified", + "created", + ) autocomplete_fields = ("user",) - general_fields = [ "is_active", "code", @@ -412,32 +678,68 @@ class PromoCodeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # typ "end_time", "used_on", ] - relation_fields = ["user"] + relation_fields = [ + "user", + ] @register(Promotion) class PromotionAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc] # noinspection PyClassVar model = Promotion # type: ignore [misc] - list_display = ("name", "discount_percent", "modified") + list_display = ( + "name", + "discount_percent", + "modified", + ) search_fields = ("name",) - readonly_fields = ("uuid", "modified", "created") + readonly_fields = ( + "uuid", + "modified", + "created", + ) autocomplete_fields = ("products",) - - general_fields = ["is_active", "name", "discount_percent", "description"] - relation_fields = ["products"] + general_fields = [ + "is_active", + "name", + "discount_percent", + "description", + ] + relation_fields = [ + "products", + ] @register(Stock) class StockAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc] # noinspection PyClassVar model = Stock # type: ignore [misc] - list_display = ("product", "vendor", "sku", "quantity", "price", "modified") - list_filter = ("vendor", "quantity") - search_fields = ("product__name", "vendor__name", "sku") - readonly_fields = ("uuid", "modified", "created") - autocomplete_fields = ("product", "vendor") - + list_display = ( + "product", + "vendor", + "sku", + "quantity", + "price", + "modified", + ) + list_filter = ( + "vendor", + "quantity", + ) + search_fields = ( + "product__name", + "vendor__name", + "sku", + ) + readonly_fields = ( + "uuid", + "modified", + "created", + ) + autocomplete_fields = ( + "product", + "vendor", + ) general_fields = [ "is_active", "sku", @@ -446,48 +748,100 @@ class StockAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: i "purchase_price", "digital_asset", ] - relation_fields = ["product", "vendor"] + relation_fields = [ + "product", + "vendor", + ] @register(Wishlist) class WishlistAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc] # noinspection PyClassVar model = Wishlist # type: ignore [misc] - list_display = ("user", "modified") - search_fields = ("user__email",) - readonly_fields = ("uuid", "modified", "created") - - general_fields = ["is_active", "user"] - relation_fields = ["products"] + list_display = ( + "user", + "modified", + ) + search_fields = ( + "user__email", + "uuid", + ) + readonly_fields = ( + "uuid", + "modified", + "created", + ) + general_fields = [ + "is_active", + "user", + ] + relation_fields = [ + "products", + ] @register(ProductImage) class ProductImageAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc] # noinspection PyClassVar model = ProductImage # type: ignore [misc] - list_display = ("alt", "product", "priority", "modified") - list_filter = ("priority",) - search_fields = ("alt", "product__name") - readonly_fields = ("uuid", "modified", "created") + list_display = ( + "alt", + "product", + "priority", + "modified", + ) + list_filter = ( + "priority", + "modified", + "created", + ) + search_fields = ( + "alt", + "product__name", + ) + readonly_fields = ( + "uuid", + "modified", + "created", + ) autocomplete_fields = ("product",) - general_fields = [ "is_active", "alt", "priority", "image", ] - relation_fields = ["product"] + relation_fields = [ + "product", + ] @register(Address) class AddressAdmin(FieldsetsMixin, GISModelAdmin): # noinspection PyClassVar model = Address # type: ignore [misc] - list_display = ("street", "city", "region", "country", "user") - list_filter = ("country", "region") - search_fields = ("street", "city", "postal_code", "user__email") - readonly_fields = ("uuid", "modified", "created") + list_display = ( + "street", + "city", + "region", + "country", + "user", + ) + list_filter = ( + "country", + "region", + ) + search_fields = ( + "street", + "city", + "postal_code", + "user__email", + ) + readonly_fields = ( + "uuid", + "modified", + "created", + ) gis_widget_kwargs = { "attrs": { "default_lon": 37.61556, @@ -495,7 +849,6 @@ class AddressAdmin(FieldsetsMixin, GISModelAdmin): "default_zoom": 6, } } - general_fields = [ "is_active", "address_line", @@ -517,10 +870,20 @@ class AddressAdmin(FieldsetsMixin, GISModelAdmin): class CustomerRelationshipManagementProviderAdmin(FieldsetsMixin, ModelAdmin): # noinspection PyClassVar model = CustomerRelationshipManagementProvider # type: ignore [misc] - list_display = ("name", "default") - search_fields = ("name",) - readonly_fields = ("uuid", "modified", "created") - + list_display = ( + "name", + "default", + ) + search_fields = ( + "name", + "uuid", + ) + readonly_fields = ( + "uuid", + "modified", + "created", + ) + form = CRMForm general_fields = [ "is_active", "name", @@ -530,22 +893,29 @@ class CustomerRelationshipManagementProviderAdmin(FieldsetsMixin, ModelAdmin): "attributes", "authentication", ] - relation_fields = [] @register(OrderCrmLink) class OrderCrmLinkAdmin(FieldsetsMixin, ModelAdmin): # noinspection PyClassVar model = OrderCrmLink # type: ignore [misc] - list_display = ("crm_lead_id",) - search_fields = ("crm_lead_id",) + list_display = ( + "crm_lead_id", + "order", + ) + search_fields = ( + "crm_lead_id", + "order__human_readable_id", + "order__uuid", + "order__user__uuid", + "order__user__email", + ) readonly_fields = ( "uuid", "modified", "created", "crm_lead_id", ) - general_fields = [ "is_active", "crm_lead_id", diff --git a/core/forms.py b/core/forms.py index 97f7b94f..fc7a2b18 100644 --- a/core/forms.py +++ b/core/forms.py @@ -22,6 +22,16 @@ class VendorForm(forms.ModelForm): } +class CRMForm(forms.ModelForm): + class Meta: + model = Product + fields = "__all__" + widgets = { + "authentication": JSONTableWidget(), + "attributes": JSONTableWidget(), + } + + class OrderProductForm(forms.ModelForm): class Meta: model = OrderProduct diff --git a/core/migrations/0044_vendor_last_processing_response.py b/core/migrations/0044_vendor_last_processing_response.py index 9c2c8ca1..0dd24216 100644 --- a/core/migrations/0044_vendor_last_processing_response.py +++ b/core/migrations/0044_vendor_last_processing_response.py @@ -5,7 +5,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("core", "0043_attribute_is_filterable_and_more"), ] diff --git a/core/models.py b/core/models.py index 85d2e3aa..53a4b5b5 100644 --- a/core/models.py +++ b/core/models.py @@ -1180,7 +1180,7 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): # type: ignore [mi verbose_name_plural = _("orders") def __str__(self) -> str: - return f"#{self.pk} for {self.user.email if self.user else 'unregistered user'}" + return f"#{self.human_readable_id} for {self.user.email if self.user else 'unregistered user'}" @property def is_business(self) -> bool: diff --git a/evibes/settings/constance.py b/evibes/settings/constance.py index e95af18b..a919c669 100644 --- a/evibes/settings/constance.py +++ b/evibes/settings/constance.py @@ -85,9 +85,7 @@ CONSTANCE_CONFIG_FIELDSETS = OrderedDict( "ADVERTSIMENT", "ANALYTICS", ), - gettext_noop("Debugging Options"): ( - "SAVE_VENDORS_RESPONSES", - ), + gettext_noop("Debugging Options"): ("SAVE_VENDORS_RESPONSES",), } )