Features: 1) Add image field to search result types (SearchProductsResultsType, SearchCategoriesResultsType, SearchBrandsResultsType); 2) Enhance tags field for categories with DjangoFilterConnectionField; 3) Implement descriptive error handling in rebuild_slugs script.

Fixes: 1) Correct inconsistent function calls with improved attribute formatting; 2) Fix invalid key usage (`uuid` vs. `pk`) in slug rebuilding logic to prevent uniqueness errors; 3) Ensure correct `query` usage in `process_query` function calls.

Extra: Reformat code for readability by updating indentation, and breaking long function signatures and expressions into multiple lines.
This commit is contained in:
Egor Pavlovich Gorbunov 2025-06-18 23:03:50 +03:00
parent d4ff637169
commit 30144c5b6a
3 changed files with 153 additions and 42 deletions

View file

@ -35,7 +35,9 @@ class CacheOperator(BaseMutation):
description = _("cache I/O")
class Arguments:
key = String(required=True, description=_("key to look for in or set into the cache"))
key = String(
required=True, description=_("key to look for in or set into the cache")
)
data = GenericScalar(required=False, description=_("data to store in cache"))
timeout = Int(
required=False,
@ -65,7 +67,9 @@ class RequestCursedURL(BaseMutation):
try:
data = cache.get(url, None)
if not data:
response = requests.get(url, headers={"content-type": "application/json"})
response = requests.get(
url, headers={"content-type": "application/json"}
)
response.raise_for_status()
data = camelize(response.json())
cache.set(url, data, 86400)
@ -93,7 +97,9 @@ class AddOrderProduct(BaseMutation):
if not (user.has_perm("core.add_orderproduct") or user == order.user):
raise PermissionDenied(permission_denied_message)
order = order.add_product(product_uuid=product_uuid, attributes=format_attributes(attributes))
order = order.add_product(
product_uuid=product_uuid, attributes=format_attributes(attributes)
)
return AddOrderProduct(order=order)
except Order.DoesNotExist:
@ -119,7 +125,9 @@ class RemoveOrderProduct(BaseMutation):
if not (user.has_perm("core.change_orderproduct") or user == order.user):
raise PermissionDenied(permission_denied_message)
order = order.remove_product(product_uuid=product_uuid, attributes=format_attributes(attributes))
order = order.remove_product(
product_uuid=product_uuid, attributes=format_attributes(attributes)
)
return AddOrderProduct(order=order)
except Order.DoesNotExist:
@ -198,7 +206,11 @@ class BuyOrder(BaseMutation):
billing_address=None,
):
if not any([order_uuid, order_hr_id]) or all([order_uuid, order_hr_id]):
raise BadRequest(_("please provide either order_uuid or order_hr_id - mutually exclusive"))
raise BadRequest(
_(
"please provide either order_uuid or order_hr_id - mutually exclusive"
)
)
user = info.context.user
try:
order = None
@ -222,7 +234,11 @@ class BuyOrder(BaseMutation):
case "<class 'core.models.Order'>":
return BuyOrder(order=instance)
case _:
raise TypeError(_(f"wrong type came from order.buy() method: {type(instance)!s}"))
raise TypeError(
_(
f"wrong type came from order.buy() method: {type(instance)!s}"
)
)
except Order.DoesNotExist:
raise Http404(_(f"order {order_uuid} not found"))
@ -250,7 +266,11 @@ class BulkOrderAction(BaseMutation):
order_hr_id=None,
):
if not any([order_uuid, order_hr_id]) or all([order_uuid, order_hr_id]):
raise BadRequest(_("please provide either order_uuid or order_hr_id - mutually exclusive"))
raise BadRequest(
_(
"please provide either order_uuid or order_hr_id - mutually exclusive"
)
)
user = info.context.user
try:
order = None
@ -432,14 +452,20 @@ class BuyWishlist(BaseMutation):
):
order.add_product(product_uuid=product.pk)
instance = order.buy(force_balance=force_balance, force_payment=force_payment)
instance = order.buy(
force_balance=force_balance, force_payment=force_payment
)
match str(type(instance)):
case "<class 'payments.models.Transaction'>":
return BuyWishlist(transaction=instance)
case "<class 'core.models.Order'>":
return BuyWishlist(order=instance)
case _:
raise TypeError(_(f"wrong type came from order.buy() method: {type(instance)!s}"))
raise TypeError(
_(
f"wrong type came from order.buy() method: {type(instance)!s}"
)
)
except Wishlist.DoesNotExist:
raise Http404(_(f"wishlist {wishlist_uuid} not found"))
@ -453,7 +479,9 @@ class BuyProduct(BaseMutation):
product_uuid = UUID(required=True)
attributes = String(
required=False,
description=_("please send the attributes as the string formatted like attr1=value1,attr2=value2"),
description=_(
"please send the attributes as the string formatted like attr1=value1,attr2=value2"
),
)
force_balance = Boolean(required=False)
force_payment = Boolean(required=False)
@ -462,10 +490,19 @@ class BuyProduct(BaseMutation):
transaction = Field(TransactionType, required=False)
@staticmethod
def mutate(_parent, info, product_uuid, attributes=None, force_balance=False, force_payment=False):
def mutate(
_parent,
info,
product_uuid,
attributes=None,
force_balance=False,
force_payment=False,
):
user = info.context.user
order = Order.objects.create(user=user, status="MOMENTAL")
order.add_product(product_uuid=product_uuid, attributes=format_attributes(attributes))
order.add_product(
product_uuid=product_uuid, attributes=format_attributes(attributes)
)
instance = order.buy(force_balance=force_balance, force_payment=force_payment)
match str(type(instance)):
case "<class 'payments.models.Transaction'>":
@ -473,7 +510,9 @@ class BuyProduct(BaseMutation):
case "<class 'core.models.Order'>":
return BuyProduct(order=instance)
case _:
raise TypeError(_(f"wrong type came from order.buy() method: {type(instance)!s}"))
raise TypeError(
_(f"wrong type came from order.buy() method: {type(instance)!s}")
)
class CreateProduct(BaseMutation):
@ -489,7 +528,9 @@ class CreateProduct(BaseMutation):
if not info.context.user.has_perm("core.add_product"):
raise PermissionDenied(permission_denied_message)
category = Category.objects.get(uuid=category_uuid)
product = Product.objects.create(name=name, description=description, category=category)
product = Product.objects.create(
name=name, description=description, category=category
)
return CreateProduct(product=product)
@ -536,7 +577,9 @@ class DeleteProduct(BaseMutation):
class CreateAddress(BaseMutation):
class Arguments:
raw_data = String(required=True, description=_("original address string provided by the user"))
raw_data = String(
required=True, description=_("original address string provided by the user")
)
address = Field(AddressType)
@ -632,7 +675,7 @@ class Search(BaseMutation):
@staticmethod
def mutate(_parent, info, query):
data = process_query(query)
data = process_query(query=query, request=info.context)
return Search(
results=SearchResultsType(

View file

@ -2,7 +2,18 @@ from django.core.cache import cache
from django.db.models import Max, Min
from django.db.models.functions import Length
from django.utils.translation import gettext_lazy as _
from graphene import UUID, Field, Float, InputObjectType, Int, List, NonNull, ObjectType, String, relay
from graphene import (
UUID,
Field,
Float,
InputObjectType,
Int,
List,
NonNull,
ObjectType,
String,
relay,
)
from graphene.types.generic import GenericScalar
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
@ -90,10 +101,16 @@ class BrandType(DjangoObjectType):
return self.categories.filter(is_active=True)
def resolve_big_logo(self: Brand, info):
return info.context.build_absolute_uri(self.big_logo.url) if self.big_logo else ""
return (
info.context.build_absolute_uri(self.big_logo.url) if self.big_logo else ""
)
def resolve_small_logo(self: Brand, info):
return info.context.build_absolute_uri(self.small_logo.url) if self.small_logo else ""
return (
info.context.build_absolute_uri(self.small_logo.url)
if self.small_logo
else ""
)
class FilterableAttributeType(ObjectType):
@ -115,14 +132,22 @@ class CategoryType(DjangoObjectType):
markup_percent = Float(required=False, description=_("markup percentage"))
filterable_attributes = List(
NonNull(FilterableAttributeType),
description=_("which attributes and values can be used for filtering this category."),
description=_(
"which attributes and values can be used for filtering this category."
),
)
min_max_prices = Field(
NonNull(MinMaxPriceType),
description=_("minimum and maximum prices for products in this category, if available."),
description=_(
"minimum and maximum prices for products in this category, if available."
),
)
tags = DjangoFilterConnectionField(
lambda: CategoryTagType, description=_("tags for this category")
)
products = DjangoFilterConnectionField(
lambda: ProductType, description=_("products in this category")
)
tags = DjangoFilterConnectionField(lambda: CategoryTagType, description=_("tags for this category"))
products = DjangoFilterConnectionField(lambda: ProductType, description=_("products in this category"))
class Meta:
model = Category
@ -198,9 +223,14 @@ class CategoryType(DjangoObjectType):
)
min_max_prices["min_price"] = price_aggregation.get("min_price", 0.0)
min_max_prices["max_price"] = price_aggregation.get("max_price", 0.0)
cache.set(key=f"{self.name}_min_max_prices", value=min_max_prices, timeout=86400)
cache.set(
key=f"{self.name}_min_max_prices", value=min_max_prices, timeout=86400
)
return {"min_price": min_max_prices["min_price"], "max_price": min_max_prices["max_price"]}
return {
"min_price": min_max_prices["min_price"],
"max_price": min_max_prices["max_price"],
}
class VendorType(DjangoObjectType):
@ -245,7 +275,9 @@ class AddressType(DjangoObjectType):
class FeedbackType(DjangoObjectType):
comment = String(description=_("comment"))
rating = Int(description=_("rating value from 1 to 10, inclusive, or 0 if not set."))
rating = Int(
description=_("rating value from 1 to 10, inclusive, or 0 if not set.")
)
class Meta:
model = Feedback
@ -258,7 +290,9 @@ class FeedbackType(DjangoObjectType):
class OrderProductType(DjangoObjectType):
attributes = GenericScalar(description=_("attributes"))
notifications = GenericScalar(description=_("notifications"))
download_url = String(description=_("download url for this order product if applicable"))
download_url = String(
description=_("download url for this order product if applicable")
)
class Meta:
model = OrderProduct
@ -292,7 +326,9 @@ class OrderType(DjangoObjectType):
billing_address = Field(AddressType, description=_("billing address"))
shipping_address = Field(
AddressType,
description=_("shipping address for this order, leave blank if same as billing address or if not applicable"),
description=_(
"shipping address for this order, leave blank if same as billing address or if not applicable"
),
)
total_price = Float(description=_("total price of this order"))
total_quantity = Int(description=_("total quantity of products in order"))
@ -350,7 +386,9 @@ class ProductType(DjangoObjectType):
images = DjangoFilterConnectionField(ProductImageType, description=_("images"))
feedbacks = DjangoFilterConnectionField(FeedbackType, description=_("feedbacks"))
brand = Field(BrandType, description=_("brand"))
attribute_groups = DjangoFilterConnectionField(AttributeGroupType, description=_("attribute groups"))
attribute_groups = DjangoFilterConnectionField(
AttributeGroupType, description=_("attribute groups")
)
price = Float(description=_("price"))
quantity = Float(description=_("quantity"))
feedbacks_count = Int(description=_("number of feedbacks"))
@ -387,7 +425,9 @@ class ProductType(DjangoObjectType):
def resolve_attribute_groups(self: Product, info):
info.context._product_uuid = self.uuid
return AttributeGroup.objects.filter(attributes__values__product=self).distinct()
return AttributeGroup.objects.filter(
attributes__values__product=self
).distinct()
def resolve_quantity(self, _info) -> int:
return self.quantity or 0
@ -422,14 +462,20 @@ class PromoCodeType(DjangoObjectType):
description = _("promocodes")
def resolve_discount(self: PromoCode, _info) -> float:
return float(self.discount_percent) if self.discount_percent else float(self.discount_amount)
return (
float(self.discount_percent)
if self.discount_percent
else float(self.discount_amount)
)
def resolve_discount_type(self: PromoCode, _info) -> str:
return "percent" if self.discount_percent else "amount"
class PromotionType(DjangoObjectType):
products = DjangoFilterConnectionField(ProductType, description=_("products on sale"))
products = DjangoFilterConnectionField(
ProductType, description=_("products on sale")
)
class Meta:
model = Promotion
@ -452,7 +498,9 @@ class StockType(DjangoObjectType):
class WishlistType(DjangoObjectType):
products = DjangoFilterConnectionField(ProductType, description=_("wishlisted products"))
products = DjangoFilterConnectionField(
ProductType, description=_("wishlisted products")
)
class Meta:
model = Wishlist
@ -462,7 +510,9 @@ class WishlistType(DjangoObjectType):
class ProductTagType(DjangoObjectType):
product_set = DjangoFilterConnectionField(ProductType, description=_("tagged products"))
product_set = DjangoFilterConnectionField(
ProductType, description=_("tagged products")
)
class Meta:
model = ProductTag
@ -473,7 +523,9 @@ class ProductTagType(DjangoObjectType):
class CategoryTagType(DjangoObjectType):
category_set = DjangoFilterConnectionField(CategoryType, description=_("tagged categories"))
category_set = DjangoFilterConnectionField(
CategoryType, description=_("tagged categories")
)
class Meta:
model = CategoryTag
@ -489,7 +541,11 @@ class ConfigType(ObjectType):
company_name = String(description=_("company name"))
company_address = String(description=_("company address"))
company_phone_number = String(description=_("company phone number"))
email_from = String(description=_("email from, sometimes it must be used instead of host user value"))
email_from = String(
description=_(
"email from, sometimes it must be used instead of host user value"
)
)
email_host_user = String(description=_("email host user"))
payment_gateway_maximum = Float(description=_("maximum amount for payment"))
payment_gateway_minimum = Float(description=_("minimum amount for payment"))
@ -513,18 +569,21 @@ class SearchProductsResultsType(ObjectType):
uuid = UUID()
name = String()
slug = String()
image = String()
class SearchCategoriesResultsType(ObjectType):
uuid = UUID()
name = String()
slug = String()
image = String()
class SearchBrandsResultsType(ObjectType):
uuid = UUID()
name = String()
slug = String()
image = String()
class SearchPostsResultsType(ObjectType):
@ -534,9 +593,15 @@ class SearchPostsResultsType(ObjectType):
class SearchResultsType(ObjectType):
products = List(description=_("products search results"), of_type=SearchProductsResultsType)
categories = List(description=_("products search results"), of_type=SearchCategoriesResultsType)
brands = List(description=_("products search results"), of_type=SearchBrandsResultsType)
products = List(
description=_("products search results"), of_type=SearchProductsResultsType
)
categories = List(
description=_("products search results"), of_type=SearchCategoriesResultsType
)
brands = List(
description=_("products search results"), of_type=SearchBrandsResultsType
)
posts = List(description=_("posts search results"), of_type=SearchPostsResultsType)

View file

@ -15,8 +15,10 @@ class Command(BaseCommand):
)
for idx, instance in enumerate(queryset.iterator(), start=1):
try:
if (
queryset.filter(name=instance.name).exclude(pk=instance.pk).count()
while (
queryset.filter(name=instance.name)
.exclude(uuid=instance.uuid)
.count()
>= 1
):
instance.name = f"{instance.name} - {get_random_string(length=3, allowed_chars='0123456789')}"
@ -34,7 +36,8 @@ class Command(BaseCommand):
except Exception as e:
self.stderr.write(
self.style.ERROR(
f"[{idx}/{total}] ({queryset.model._meta.verbose_name_plural}: {instance.pk}) ERROR: {e}"
f"[{idx}/{total}] ({queryset.model._meta.verbose_name_plural}: {instance.name}/{instance.uuid})"
f" ERROR: {e}"
)
)