Features: 1) Add is_digital field to ProductAdmin for filtering and display; 2) Extend admin search fields to include slug for products; 3) Introduce slug field in readonly and form layout sections for products.

Fixes: 1) Add `# type: ignore` comments for ForeignKey fields to resolve type-checking warnings in models.

Extra: 1) Refactor admin field formatting for better readability; 2) Add `# noinspection PyProtectedMember` annotation in `rebuild_slugs` management command; 3) Clean up spacing and alignment in various files.
This commit is contained in:
Egor Pavlovich Gorbunov 2025-06-18 23:41:28 +03:00
parent 30144c5b6a
commit b15df3b72a
4 changed files with 44 additions and 13 deletions

View file

@ -169,10 +169,20 @@ class StockInline(TabularInline):
@admin.register(Product) @admin.register(Product)
class ProductAdmin(BasicModelAdmin, TabbedTranslationAdmin): class ProductAdmin(BasicModelAdmin, TabbedTranslationAdmin):
list_display = ("name", "partnumber", "is_active", "category", "brand", "price", "rating", "modified") list_display = (
"name",
"partnumber",
"is_active",
"category",
"brand",
"price",
"rating",
"modified",
)
list_filter = ( list_filter = (
"is_active", "is_active",
"is_digital",
("tags", admin.RelatedOnlyFieldListFilter), ("tags", admin.RelatedOnlyFieldListFilter),
("stocks__vendor", admin.RelatedOnlyFieldListFilter), ("stocks__vendor", admin.RelatedOnlyFieldListFilter),
"created", "created",
@ -185,9 +195,10 @@ class ProductAdmin(BasicModelAdmin, TabbedTranslationAdmin):
"brand__name", "brand__name",
"category__name", "category__name",
"uuid", "uuid",
"slug",
) )
readonly_fields = ("created", "modified", "uuid", "rating", "price") readonly_fields = ("created", "modified", "uuid", "rating", "price", "slug")
autocomplete_fields = ("category", "brand", "tags") autocomplete_fields = ("category", "brand", "tags")
def price(self, obj): def price(self, obj):
@ -206,8 +217,10 @@ class ProductAdmin(BasicModelAdmin, TabbedTranslationAdmin):
{ {
"fields": ( "fields": (
"uuid", "uuid",
"slug",
"partnumber", "partnumber",
"is_active", "is_active",
"is_digital",
"name", "name",
"category", "category",
"brand", "brand",
@ -270,7 +283,15 @@ class OrderProductInline(admin.TabularInline):
@admin.register(Order) @admin.register(Order)
class OrderAdmin(BasicModelAdmin): class OrderAdmin(BasicModelAdmin):
list_display = ("human_readable_id", "user", "is_business", "status", "total_price", "buy_time", "modified") list_display = (
"human_readable_id",
"user",
"is_business",
"status",
"total_price",
"buy_time",
"modified",
)
list_filter = ("status", "buy_time", "modified", "created") list_filter = ("status", "buy_time", "modified", "created")
search_fields = ("user__email", "status", "uuid", "human_readable_id") search_fields = ("user__email", "status", "uuid", "human_readable_id")
inlines = [OrderProductInline] inlines = [OrderProductInline]
@ -374,7 +395,14 @@ class ProductImageAdmin(BasicModelAdmin):
class AddressAdmin(GISModelAdmin): class AddressAdmin(GISModelAdmin):
list_display = ("street", "city", "region", "country", "user") list_display = ("street", "city", "region", "country", "user")
list_filter = ("country", "region") list_filter = ("country", "region")
search_fields = ("raw_data", "street", "city", "postal_code", "user__email", "address_line") search_fields = (
"raw_data",
"street",
"city",
"postal_code",
"user__email",
"address_line",
)
gis_widget_kwargs = { gis_widget_kwargs = {
"attrs": { "attrs": {
@ -394,7 +422,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"
),
] ]

View file

@ -78,7 +78,7 @@ def process_query(query: str = "", request: Request | None = None):
obj_name = ( obj_name = (
getattr(hit, "name", None) or getattr(hit, "title", None) or "N/A" getattr(hit, "name", None) or getattr(hit, "title", None) or "N/A"
) )
obj_slug = ""
raw_slug = getattr(hit, "slug", None) raw_slug = getattr(hit, "slug", None)
if raw_slug: if raw_slug:
obj_slug = raw_slug obj_slug = raw_slug

View file

@ -5,6 +5,7 @@ from django.utils.crypto import get_random_string
from core.models import Brand, Category, Product from core.models import Brand, Category, Product
# noinspection PyProtectedMember
class Command(BaseCommand): class Command(BaseCommand):
help = "Rebuild slug field for all slugified instances" help = "Rebuild slug field for all slugified instances"

View file

@ -300,14 +300,14 @@ class Brand(ExportModelOperationsMixin("brand"), NiceModel):
class Product(ExportModelOperationsMixin("product"), NiceModel): class Product(ExportModelOperationsMixin("product"), NiceModel):
is_publicly_visible = True is_publicly_visible = True
category: Category = ForeignKey( category: Category = ForeignKey( # type: ignore
"core.Category", "core.Category",
on_delete=CASCADE, on_delete=CASCADE,
help_text=_("category this product belongs to"), help_text=_("category this product belongs to"),
verbose_name=_("category"), verbose_name=_("category"),
related_name="products", related_name="products",
) )
brand: Brand = ForeignKey( brand: Brand = ForeignKey( # type: ignore
"core.Brand", "core.Brand",
on_delete=CASCADE, on_delete=CASCADE,
blank=True, blank=True,
@ -454,14 +454,14 @@ class Attribute(ExportModelOperationsMixin("attribute"), NiceModel):
class AttributeValue(ExportModelOperationsMixin("attribute_value"), NiceModel): class AttributeValue(ExportModelOperationsMixin("attribute_value"), NiceModel):
is_publicly_visible = True is_publicly_visible = True
attribute: Attribute = ForeignKey( attribute: Attribute = ForeignKey( # type: ignore
"core.Attribute", "core.Attribute",
on_delete=CASCADE, on_delete=CASCADE,
related_name="values", related_name="values",
help_text=_("attribute of this value"), help_text=_("attribute of this value"),
verbose_name=_("attribute"), verbose_name=_("attribute"),
) )
product: Product = ForeignKey( product: Product = ForeignKey( # type: ignore
"core.Product", "core.Product",
on_delete=CASCADE, on_delete=CASCADE,
blank=False, blank=False,
@ -852,7 +852,7 @@ class PromoCode(ExportModelOperationsMixin("promocode"), NiceModel):
class Order(ExportModelOperationsMixin("order"), NiceModel): class Order(ExportModelOperationsMixin("order"), NiceModel):
is_publicly_visible = False is_publicly_visible = False
billing_address: Address = ForeignKey( billing_address: Address = ForeignKey( # type: ignore
"core.Address", "core.Address",
on_delete=CASCADE, on_delete=CASCADE,
blank=True, blank=True,
@ -861,7 +861,7 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
help_text=_("the billing address used for this order"), help_text=_("the billing address used for this order"),
verbose_name=_("billing address"), verbose_name=_("billing address"),
) )
promo_code: PromoCode = ForeignKey( promo_code: PromoCode = ForeignKey( # type: ignore
"core.PromoCode", "core.PromoCode",
on_delete=PROTECT, on_delete=PROTECT,
blank=True, blank=True,
@ -869,7 +869,7 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
help_text=_("optional promo code applied to this order"), help_text=_("optional promo code applied to this order"),
verbose_name=_("applied promo code"), verbose_name=_("applied promo code"),
) )
shipping_address: Address = ForeignKey( shipping_address: Address = ForeignKey( # type: ignore
"core.Address", "core.Address",
on_delete=CASCADE, on_delete=CASCADE,
blank=True, blank=True,