from contextlib import suppress from typing import Any, ClassVar, Type from constance.admin import Config from constance.admin import ConstanceAdmin as BaseConstanceAdmin from django.apps import AppConfig, apps from django.conf import settings from django.contrib.admin import register, site from django.contrib.gis.admin import GISModelAdmin from django.contrib.messages import constants as messages from django.db.models import Model from django.db.models.query import QuerySet from django.http import HttpRequest from django.utils.translation import gettext_lazy as _ from django_celery_beat.admin import ClockedScheduleAdmin as BaseClockedScheduleAdmin from django_celery_beat.admin import CrontabScheduleAdmin as BaseCrontabScheduleAdmin from django_celery_beat.admin import PeriodicTaskAdmin as BasePeriodicTaskAdmin from django_celery_beat.admin import PeriodicTaskForm, TaskSelectWidget from django_celery_beat.models import ( ClockedSchedule, CrontabSchedule, IntervalSchedule, PeriodicTask, SolarSchedule, ) from djangoql.admin import DjangoQLSearchMixin from import_export.admin import ImportExportModelAdmin from modeltranslation.translator import NotRegistered, translator from modeltranslation.utils import get_translation_fields from mptt.admin import DraggableMPTTAdmin from unfold.admin import ModelAdmin, TabularInline from unfold.contrib.import_export.forms import ExportForm, ImportForm from unfold.decorators import action from unfold.widgets import UnfoldAdminSelectWidget, UnfoldAdminTextInputWidget from engine.core.forms import CRMForm, OrderForm, OrderProductForm, StockForm, VendorForm from engine.core.models import ( Address, Attribute, AttributeGroup, AttributeValue, Brand, Category, CategoryTag, CustomerRelationshipManagementProvider, Feedback, Order, OrderCrmLink, OrderProduct, Product, ProductImage, ProductTag, PromoCode, Promotion, Stock, Vendor, Wishlist, ) class FieldsetsMixin: general_fields: list[str] | None = [] relation_fields: list[str] | None = [] additional_fields: list[str] | None = [] model: ClassVar[Type[Model]] def get_fieldsets(self, request: HttpRequest, obj: Any = None) -> list[tuple[str, dict[str, list[str]]]]: if request: pass if obj: pass fieldsets = [] def add_translations_fieldset( fss: list[tuple[str, dict[str, list[str]]]], ) -> list[tuple[str, dict[str, list[str]]]]: with suppress(NotRegistered): transoptions = translator.get_options_for_model(self.model) translation_fields = [] for orig in transoptions.local_fields: translation_fields += get_translation_fields(orig) if translation_fields: fss = list(fss) + [(_("translations"), {"classes": ["tab"], "fields": translation_fields})] # type: ignore [list-item] return fss if self.general_fields: fieldsets.append((_("general"), {"classes": ["tab"], "fields": self.general_fields})) if self.relation_fields: fieldsets.append((_("relations"), {"classes": ["tab"], "fields": self.relation_fields})) if self.additional_fields: fieldsets.append((_("additional info"), {"classes": ["tab"], "fields": self.additional_fields})) opts = self.model._meta meta_fields = [] if any(f.name == "uuid" for f in opts.fields): meta_fields.append("uuid") if any(f.name == "slug" for f in opts.fields): meta_fields.append("slug") if any(f.name == "sku" for f in opts.fields): meta_fields.append("sku") if any(f.name == "human_readable_id" for f in opts.fields): meta_fields.append("human_readable_id") if meta_fields: fieldsets.append((_("metadata"), {"classes": ["tab"], "fields": meta_fields})) ts = [] for name in ("created", "modified"): if any(f.name == name for f in opts.fields): ts.append(name) if ts: fieldsets.append((_("timestamps"), {"classes": ["tab"], "fields": ts})) fieldsets = add_translations_fieldset(fieldsets) # type: ignore [arg-type, assignment] return fieldsets # type: ignore [return-value] # noinspection PyUnresolvedReferences class ActivationActionsMixin: actions_on_top = True actions_on_bottom = True actions = [ "delete_selected", "activate_selected", "deactivate_selected", ] @action(description=_("activate selected %(verbose_name_plural)s").lower(), permissions=["change"]) def activate_selected(self, request: HttpRequest, queryset: QuerySet[Any]) -> None: try: queryset.update(is_active=True) self.message_user( # type: ignore [attr-defined] request=request, message=_("selected items have been activated.").lower(), level=messages.SUCCESS ) except Exception as e: self.message_user(request=request, message=str(e), level=messages.ERROR) # type: ignore [attr-defined] @action(description=_("deactivate selected %(verbose_name_plural)s").lower(), permissions=["change"]) def deactivate_selected(self, request: HttpRequest, queryset: QuerySet[Any]) -> None: try: queryset.update(is_active=False) self.message_user( # type: ignore [attr-defined] request=request, message=_("selected items have been deactivated.").lower(), level=messages.SUCCESS ) except Exception as e: self.message_user(request=request, message=str(e), level=messages.ERROR) # type: ignore [attr-defined] class AttributeValueInline(TabularInline): # type: ignore [type-arg] model = AttributeValue extra = 0 autocomplete_fields = ["attribute"] verbose_name = _("attribute value") verbose_name_plural = _("attribute values") tab = True def get_queryset(self, request): return super().get_queryset(request).select_related("attribute", "product") class ProductImageInline(TabularInline): # type: ignore [type-arg] model = ProductImage extra = 0 tab = True verbose_name = _("image") verbose_name_plural = _("images") def get_queryset(self, request): return super().get_queryset(request).select_related("product") class StockInline(TabularInline): # type: ignore [type-arg] model = Stock extra = 0 form = StockForm tab = True verbose_name = _("stock") verbose_name_plural = _("stocks") def get_queryset(self, request): return super().get_queryset(request).select_related("vendor", "product") class OrderProductInline(TabularInline): # type: ignore [type-arg] model = OrderProduct extra = 0 readonly_fields = ("product", "quantity", "buy_price") form = OrderProductForm verbose_name = _("order product") verbose_name_plural = _("order products") tab = True def get_queryset(self, request): return super().get_queryset(request).select_related("product").only("product__name") class CategoryChildrenInline(TabularInline): # type: ignore [type-arg] model = Category fk_name = "parent" extra = 0 fields = ("name", "description", "is_active", "image", "markup_percent") tab = True verbose_name = _("children") verbose_name_plural = _("children") @register(AttributeGroup) class AttributeGroupAdmin(DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg] # 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", ] @register(Attribute) class AttributeAdmin(DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg] # 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 = [ "group", ] general_fields = [ "is_active", "name", "value_type", "is_filterable", ] relation_fields = [ "group", ] @register(AttributeValue) class AttributeValueAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg] # 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", ] @register(Category) class CategoryAdmin(DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, DraggableMPTTAdmin, ModelAdmin): # noinspection PyClassVar model = Category 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", ) general_fields = [ "is_active", "name", "description", "image", "markup_percent", "priority", ] relation_fields = [ "parent", "tags", ] @register(Brand) class BrandAdmin(DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg] # noinspection PyClassVar model = Brand # type: ignore [misc] list_display = ( "name", "priority", "is_active", ) list_filter = ( "categories", "is_active", ) search_fields = ( "uuid", "name", ) readonly_fields = ( "uuid", "slug", "modified", "created", ) general_fields = [ "is_active", "name", "description", "priority", ] additional_fields = ["small_logo", "big_logo"] @register(Product) class ProductAdmin(DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin, ImportExportModelAdmin): # type: ignore [misc, type-arg] # noinspection PyClassVar model = Product # type: ignore [misc] list_display = ( "sku", "name", "is_active", "category", "brand", "price", "rating", "modified", ) list_filter = ( "is_active", "is_digital", "stocks__vendor", "tags__name", "created", "modified", ) search_fields = ( "name", "partnumber", "brand__name", "brand__slug", "category__name", "category__slug", "uuid", "slug", "sku", ) readonly_fields = ( "sku", "slug", "uuid", "modified", "created", ) autocomplete_fields = ( "category", "brand", "tags", ) inlines = [ AttributeValueInline, ProductImageInline, StockInline, ] import_form_class = ImportForm export_form_class = ExportForm general_fields = [ "is_active", "name", "partnumber", "is_digital", ] relation_fields = [ "category", "brand", "tags", ] def get_queryset(self, request): return ( super() .get_queryset(request) .select_related("category", "brand") .prefetch_related( "tags", "images", "stocks__vendor", "attributes__attribute", ) ) @register(ProductTag) class ProductTagAdmin(DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg] # noinspection PyClassVar 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", ] @register(CategoryTag) class CategoryTagAdmin(DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg] # noinspection PyClassVar model = CategoryTag # type: ignore [misc] 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(DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg] # noinspection PyClassVar model = Vendor # type: ignore [misc] list_display = ( "name", "markup_percent", "modified", ) list_filter = ( "markup_percent", "is_active", ) search_fields = ( "name", "uuid", ) readonly_fields = ( "uuid", "modified", "created", "last_processing_response", ) form = VendorForm general_fields = [ "is_active", "name", "markup_percent", "authentication", ] relation_fields = [ "users", ] additional_fields = [ "integration_path", "last_processing_response", "b2b_auth_token", ] @register(Feedback) class FeedbackAdmin(DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg] # 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", ] @register(Order) class OrderAdmin(DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg] # noinspection PyClassVar model = Order # type: ignore [misc] list_display = ( "human_readable_id", "user", "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", "human_readable_id", "uuid", "modified", "created", ) inlines = [ OrderProductInline, ] form = OrderForm general_fields = [ "is_active", "user", "status", "notifications", "attributes", "buy_time", ] relation_fields = [ "promo_code", "billing_address", "shipping_address", ] @register(OrderProduct) class OrderProductAdmin(DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg] # noinspection PyClassVar model = OrderProduct # type: ignore [misc] 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", ] @register(PromoCode) class PromoCodeAdmin(DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg] # noinspection PyClassVar model = PromoCode # type: ignore [misc] 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", "uuid", "user__email", ) readonly_fields = ( "used_on", "uuid", "modified", "created", ) autocomplete_fields = ("user",) general_fields = [ "is_active", "code", "discount_amount", "discount_percent", "start_time", "end_time", "used_on", ] relation_fields = [ "user", ] @register(Promotion) class PromotionAdmin(DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg] # noinspection PyClassVar model = Promotion # type: ignore [misc] list_display = ( "name", "discount_percent", "modified", ) search_fields = ("name",) readonly_fields = ( "uuid", "modified", "created", ) autocomplete_fields = ("products",) general_fields = [ "is_active", "name", "discount_percent", "description", ] relation_fields = [ "products", ] @register(Stock) class StockAdmin(DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg] # noinspection PyClassVar model = Stock # type: ignore [misc] form = StockForm 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", "quantity", "price", "purchase_price", "digital_asset", ] additional_fields = [ "system_attributes", ] relation_fields = [ "product", "vendor", ] @register(Wishlist) class WishlistAdmin(DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg] # noinspection PyClassVar model = Wishlist # type: ignore [misc] 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(DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg] # noinspection PyClassVar model = ProductImage # type: ignore [misc] 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", ] @register(Address) class AddressAdmin(DjangoQLSearchMixin, FieldsetsMixin, GISModelAdmin): # type: ignore [misc] # 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", ) gis_widget_kwargs = { "attrs": { "default_lon": 37.61556, "default_lat": 55.75222, "default_zoom": 6, } } general_fields = [ "is_active", "address_line", "street", "district", "city", "region", "postal_code", "country", "raw_data", ] relation_fields = [ "user", "api_response", ] @register(CustomerRelationshipManagementProvider) class CustomerRelationshipManagementProviderAdmin(DjangoQLSearchMixin, FieldsetsMixin, ModelAdmin): # type: ignore [misc, type-arg] # noinspection PyClassVar model = CustomerRelationshipManagementProvider # type: ignore [misc] list_display = ( "name", "default", ) search_fields = ( "name", "uuid", ) readonly_fields = ( "uuid", "modified", "created", ) form = CRMForm general_fields = [ "is_active", "name", "default", "integration_url", "integration_location", "attributes", "authentication", ] @register(OrderCrmLink) class OrderCrmLinkAdmin(DjangoQLSearchMixin, FieldsetsMixin, ModelAdmin): # type: ignore [misc, type-arg] # noinspection PyClassVar model = OrderCrmLink # type: ignore [misc] 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", ] relation_fields = [ "order", "crm", ] # Constance configuration 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) -> str: return f"change_{self.model_name}" @property def app_config(self) -> AppConfig: return apps.get_app_config(self.app_label) @property def label(self) -> str: return f"{self.app_label}.{self.object_name}" @property def label_lower(self) -> str: return f"{self.app_label}.{self.model_name}" def get_ordered_objects(self) -> bool: return False _meta = Meta() # noinspection PyTypeChecker site.unregister([Config]) # noinspection PyTypeChecker site.register([ConstanceConfig], BaseConstanceAdmin) # type: ignore [list-item] site.site_title = settings.PROJECT_NAME site.site_header = "eVibes" site.index_title = settings.PROJECT_NAME site.unregister(PeriodicTask) site.unregister(IntervalSchedule) site.unregister(CrontabSchedule) site.unregister(SolarSchedule) site.unregister(ClockedSchedule) class UnfoldTaskSelectWidget(UnfoldAdminSelectWidget, TaskSelectWidget): pass class UnfoldPeriodicTaskForm(PeriodicTaskForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["task"].widget = UnfoldAdminTextInputWidget() self.fields["regtask"].widget = UnfoldTaskSelectWidget() @register(PeriodicTask) class PeriodicTaskAdmin(BasePeriodicTaskAdmin, ModelAdmin): form = UnfoldPeriodicTaskForm @register(IntervalSchedule) class IntervalScheduleAdmin(ModelAdmin): pass @register(CrontabSchedule) class CrontabScheduleAdmin(BaseCrontabScheduleAdmin, ModelAdmin): pass @register(SolarSchedule) class SolarScheduleAdmin(ModelAdmin): pass @register(ClockedSchedule) class ClockedScheduleAdmin(BaseClockedScheduleAdmin, ModelAdmin): pass