Add "DISABLED_COMMERCE" feature to disable buying functionality

Introduce a global config flag `DISABLED_COMMERCE` to toggle buy functionality availability. Raise specific `DisabledCommerceError` when buying is disabled, ensuring end-users are informed. Additionally, reformat code for readability and consistency, improving overall maintainability.
This commit is contained in:
Egor Pavlovich Gorbunov 2025-05-05 17:32:57 +03:00
parent 96c3fe23b0
commit 85a49c4e8b
3 changed files with 92 additions and 69 deletions

View file

@ -1,5 +1,9 @@
from django.core.exceptions import BadRequest from django.core.exceptions import BadRequest, ImproperlyConfigured
class NotEnoughMoneyError(BadRequest): class NotEnoughMoneyError(BadRequest):
pass pass
class DisabledCommerceError(ImproperlyConfigured):
pass

View file

@ -39,7 +39,7 @@ from mptt.models import MPTTModel
from core.abstract import NiceModel from core.abstract import NiceModel
from core.choices import ORDER_PRODUCT_STATUS_CHOICES, ORDER_STATUS_CHOICES from core.choices import ORDER_PRODUCT_STATUS_CHOICES, ORDER_STATUS_CHOICES
from core.errors import NotEnoughMoneyError from core.errors import NotEnoughMoneyError, DisabledCommerceError
from core.utils import get_product_uuid_as_path, get_random_code from core.utils import get_product_uuid_as_path, get_random_code
from core.utils.lists import FAILED_STATUSES from core.utils.lists import FAILED_STATUSES
from core.validators import validate_category_image_dimensions from core.validators import validate_category_image_dimensions
@ -219,20 +219,22 @@ class Brand(NiceModel):
verbose_name=_("brand name"), verbose_name=_("brand name"),
unique=True, unique=True,
) )
small_logo = ImageField(upload_to="brands/", small_logo = ImageField(
blank=True, upload_to="brands/",
null=True, blank=True,
help_text=_("upload a logo representing this brand"), null=True,
validators=[validate_category_image_dimensions], help_text=_("upload a logo representing this brand"),
verbose_name=_("brand small image"), validators=[validate_category_image_dimensions],
) verbose_name=_("brand small image"),
big_logo = ImageField(upload_to="brands/", )
blank=True, big_logo = ImageField(
null=True, upload_to="brands/",
help_text=_("upload a big logo representing this brand"), blank=True,
validators=[validate_category_image_dimensions], null=True,
verbose_name=_("brand big image"), help_text=_("upload a big logo representing this brand"),
) validators=[validate_category_image_dimensions],
verbose_name=_("brand big image"),
)
description = TextField( # noqa: DJ001 description = TextField( # noqa: DJ001
blank=True, blank=True,
null=True, null=True,
@ -330,7 +332,7 @@ class Product(NiceModel):
@rating.setter @rating.setter
def rating(self, value): def rating(self, value):
self.__dict__['rating'] = value self.__dict__["rating"] = value
@property @property
def feedbacks_count(self): def feedbacks_count(self):
@ -502,16 +504,16 @@ class Order(NiceModel):
@property @property
def total_price(self) -> float: def total_price(self) -> float:
return ( return (
round( round(
sum( sum(
order_product.buy_price * order_product.quantity order_product.buy_price * order_product.quantity
if order_product.status not in FAILED_STATUSES and order_product.buy_price is not None if order_product.status not in FAILED_STATUSES and order_product.buy_price is not None
else 0.0 else 0.0
for order_product in self.order_products.all() for order_product in self.order_products.all()
), ),
2, 2,
) )
or 0.0 or 0.0
) )
@property @property
@ -606,10 +608,11 @@ class Order(NiceModel):
raise Http404(_("promocode does not exist")) raise Http404(_("promocode does not exist"))
return promocode.use(self) return promocode.use(self)
def buy(self, def buy(
force_balance: bool = False, self, force_balance: bool = False, force_payment: bool = False, promocode_uuid: str | None = None
force_payment: bool = False, ) -> Self | Transaction | None:
promocode_uuid: str | None = None) -> Self | Transaction | None: if config.DISABLED_COMMERCE:
raise DisabledCommerceError(_("you can not buy at this moment, please try again in a few minutes"))
if (not force_balance and not force_payment) or (force_balance and force_payment): if (not force_balance and not force_payment) or (force_balance and force_payment):
raise ValueError(_("invalid force value")) raise ValueError(_("invalid force value"))
@ -647,8 +650,11 @@ class Order(NiceModel):
return self return self
def buy_without_registration(self, products: list, promocode_uuid: str, **kwargs) -> Transaction | None: def buy_without_registration(self, products: list, promocode_uuid: str, **kwargs) -> Transaction | None:
if config.DISABLED_COMMERCE:
raise DisabledCommerceError(_("you can not buy at this moment, please try again in a few minutes"))
if len(products) < 1: if len(products) < 1:
raise ValueError(_("you cannot purchase without registration an empty order!")) raise ValueError(_("you cannot purchase an empty order!"))
customer_name = kwargs.pop("customer_name") customer_name = kwargs.pop("customer_name")
customer_email = kwargs.pop("customer_email") customer_email = kwargs.pop("customer_email")
@ -656,8 +662,11 @@ class Order(NiceModel):
if not all([customer_name, customer_email, customer_phone_number]): if not all([customer_name, customer_email, customer_phone_number]):
raise ValueError( raise ValueError(
_("you cannot buy without registration, please provide the following information:" _(
" customer name, customer email, customer phone number")) "you cannot buy without registration, please provide the following information:"
" customer name, customer email, customer phone number"
)
)
payment_method = kwargs.get("payment_method") payment_method = kwargs.get("payment_method")
@ -670,17 +679,24 @@ class Order(NiceModel):
billing_customer_postal_code = billing_customer_address.pop("customer_postal_code") billing_customer_postal_code = billing_customer_address.pop("customer_postal_code")
billing_customer_address_line = billing_customer_address.pop("customer_address_line") billing_customer_address_line = billing_customer_address.pop("customer_address_line")
if not all([billing_customer_city, billing_customer_country, billing_customer_postal_code, if not all(
billing_customer_address_line]): [
billing_customer_city,
billing_customer_country,
billing_customer_postal_code,
billing_customer_address_line,
]
):
raise ValueError(_("you cannot create a momental order without providing a billing address")) raise ValueError(_("you cannot create a momental order without providing a billing address"))
billing_address = Address.objects.get_or_create(user=None, billing_address = Address.objects.get_or_create(
country=Country.objects.get(code=billing_customer_country), user=None,
region=Region.objects.get(code=billing_customer_city), country=Country.objects.get(code=billing_customer_country),
city=City.objects.get(name=billing_customer_city), region=Region.objects.get(code=billing_customer_city),
postal_code=PostalCode.objects.get( city=City.objects.get(name=billing_customer_city),
code=billing_customer_postal_code), postal_code=PostalCode.objects.get(code=billing_customer_postal_code),
street=billing_customer_address_line) street=billing_customer_address_line,
)
shipping_customer_address = kwargs.pop("shipping_customer_address") shipping_customer_address = kwargs.pop("shipping_customer_address")
shipping_customer_city = shipping_customer_address.pop("customer_city") shipping_customer_city = shipping_customer_address.pop("customer_city")
@ -692,14 +708,14 @@ class Order(NiceModel):
shipping_address = billing_address shipping_address = billing_address
else: else:
shipping_address = Address.objects.get_or_create(user=None, shipping_address = Address.objects.get_or_create(
country=Country.objects.get( user=None,
code=shipping_customer_country), country=Country.objects.get(code=shipping_customer_country),
region=Region.objects.get(code=shipping_customer_city), region=Region.objects.get(code=shipping_customer_city),
city=City.objects.get(name=billing_customer_city), city=City.objects.get(name=billing_customer_city),
postal_code=PostalCode.objects.get( postal_code=PostalCode.objects.get(code=shipping_customer_postal_code),
code=shipping_customer_postal_code), street=shipping_customer_address_line,
street=shipping_customer_address_line) )
for product_uuid in products: for product_uuid in products:
self.add_product(product_uuid) self.add_product(product_uuid)
@ -709,9 +725,13 @@ class Order(NiceModel):
self.status = "CREATED" self.status = "CREATED"
self.shipping_address = shipping_address self.shipping_address = shipping_address
self.billing_address = billing_address self.billing_address = billing_address
self.attributes.update({"customer_name": customer_name, self.attributes.update(
"customer_email": customer_email, {
"customer_phone_number": customer_phone_number}) "customer_name": customer_name,
"customer_email": customer_email,
"customer_phone_number": customer_phone_number,
}
)
self.save() self.save()
return Transaction.objects.create( return Transaction.objects.create(
@ -723,16 +743,16 @@ class Order(NiceModel):
def finalize(self): def finalize(self):
if ( if (
self.order_products.filter( self.order_products.filter(
status__in=[ status__in=[
"ACCEPTED", "ACCEPTED",
"FAILED", "FAILED",
"RETURNED", "RETURNED",
"CANCELED", "CANCELED",
"FINISHED", "FINISHED",
] ]
).count() ).count()
== self.order_products.count() == self.order_products.count()
): ):
self.status = "FINISHED" self.status = "FINISHED"
self.save() self.save()
@ -968,7 +988,7 @@ class PromoCode(NiceModel):
def save(self, **kwargs): def save(self, **kwargs):
if (self.discount_amount is not None and self.discount_percent is not None) or ( if (self.discount_amount is not None and self.discount_percent is not None) or (
self.discount_amount is None and self.discount_percent is None self.discount_amount is None and self.discount_percent is None
): ):
raise ValidationError( raise ValidationError(
_("only one type of discount should be defined (amount or percent), but not both or neither.") _("only one type of discount should be defined (amount or percent), but not both or neither.")
@ -991,13 +1011,11 @@ class PromoCode(NiceModel):
match self.discount_type: match self.discount_type:
case "percent": case "percent":
amount -= round(amount * (self.discount_percent / 100), 2) amount -= round(amount * (self.discount_percent / 100), 2)
order.attributes.update({"promocode": str(self.uuid), order.attributes.update({"promocode": str(self.uuid), "final_price": amount})
"final_price": amount})
order.save() order.save()
case "amount": case "amount":
amount -= round(float(self.discount_amount), 2) amount -= round(float(self.discount_amount), 2)
order.attributes.update({"promocode": str(self.uuid), order.attributes.update({"promocode": str(self.uuid), "final_price": amount})
"final_price": amount})
order.save() order.save()
case _: case _:
raise ValueError(_(f"invalid discount type for promocode {self.uuid}")) raise ValueError(_(f"invalid discount type for promocode {self.uuid}"))

View file

@ -28,6 +28,7 @@ CONSTANCE_CONFIG = {
"Abstract API Key, if empty - no Abstract features provided", "Abstract API Key, if empty - no Abstract features provided",
), ),
"HTTP_PROXY": (getenv("DJANGO_HTTP_PROXY", "http://username:password@proxy_address:port"), "HTTP Proxy"), "HTTP_PROXY": (getenv("DJANGO_HTTP_PROXY", "http://username:password@proxy_address:port"), "HTTP Proxy"),
"DISABLED_COMMERCE": (getenv("DISABLED_COMMERCE", False), "Disable buy functionality"),
} }
EXPOSABLE_KEYS = [ EXPOSABLE_KEYS = [