schon/engine/core/admin.py

1135 lines
26 KiB
Python

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