Features: 1) Add support for shipping and billing addresses in serializers, mutations, and models; 2) Validate address inputs during order purchasing; 3) Auto-fill billing address if only shipping address is provided and vice-versa;

Fixes: 1) Correct redundant variable and file handling in geo management commands; 2) Fix formatting inconsistencies in tqdm loops across geo management commands; 3) Remove unnecessary decorator in token verification view;

Extra: Clean up imports, line breaks, and redundant code for better readability and maintainability in multiple files;
This commit is contained in:
Egor Pavlovich Gorbunov 2025-05-16 01:47:45 +03:00
parent 11edfb9d4f
commit 43c8df0c05
6 changed files with 111 additions and 83 deletions

View file

@ -172,26 +172,32 @@ class BuyOrder(BaseMutation):
force_balance = Boolean(required=False)
force_payment = Boolean(required=False)
promocode_uuid = UUID(required=False)
shipping_address = UUID(required=False)
billing_address = UUID(required=False)
order = Field(OrderType, required=False)
transaction = Field(TransactionType, required=False)
@staticmethod
def mutate(_parent, info, order_uuid=None, order_hr_id=None, force_balance=False, force_payment=False,
promocode_uuid=None):
promocode_uuid=None, shipping_address=None, 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"))
user = info.context.user
try:
order = None
if order_uuid:
order = Order.objects.get(user=user, uuid=order_uuid)
if order_hr_id:
elif order_hr_id:
order = Order.objects.get(user=user, human_readable_id=order_hr_id)
instance = order.buy(
force_balance=force_balance, force_payment=force_payment, promocode_uuid=promocode_uuid
force_balance=force_balance, force_payment=force_payment, promocode_uuid=promocode_uuid,
shipping_address=shipping_address, billing_address=billing_address
)
match str(type(instance)):
case "<class 'payments.models.Transaction'>":
return BuyOrder(transaction=instance)
@ -199,6 +205,7 @@ class BuyOrder(BaseMutation):
return BuyOrder(order=instance)
case _:
raise TypeError(_(f"wrong type came from order.buy() method: {type(instance)!s}"))
except Order.DoesNotExist:
raise Http404(_(f"order {order_uuid} not found"))

View file

@ -625,7 +625,8 @@ class Order(NiceModel):
return promocode.use(self)
def buy(
self, force_balance: bool = False, force_payment: bool = False, promocode_uuid: str | None = None
self, force_balance: bool = False, force_payment: bool = False, promocode_uuid: str | None = None,
billing_address: str | None = None, shipping_address: str | None = None, **kwargs
) -> Self | Transaction | None:
if config.DISABLED_COMMERCE:
raise DisabledCommerceError(_("you can not buy at this moment, please try again in a few minutes"))
@ -633,6 +634,25 @@ class Order(NiceModel):
if (not force_balance and not force_payment) or (force_balance and force_payment):
raise ValueError(_("invalid force value"))
if not self.is_whole_digital and not any([shipping_address, billing_address]):
raise ValueError(_("you can only buy physical products with shipping address specified"))
if shipping_address and not billing_address:
shipping_address = Address.objects.get(uuid=shipping_address)
billing_address = shipping_address
elif billing_address and not shipping_address:
billing_address = Address.objects.get(uuid=billing_address)
shipping_address = billing_address
else:
billing_address = Address.objects.get(uuid=billing_address)
shipping_address = Address.objects.get(uuid=shipping_address)
self.billing_address = billing_address
self.shipping_address = shipping_address
self.save()
if self.total_quantity < 1:
raise ValueError(_("you cannot purchase an empty order!"))

View file

@ -70,6 +70,8 @@ class BuyOrderSerializer(Serializer):
force_balance = BooleanField(required=False, default=False)
force_payment = BooleanField(required=False, default=False)
promocode_uuid = CharField(required=False)
shipping_address_uuid = CharField(required=False)
billing_address_uuid = CharField(required=False)
class BuyUnregisteredOrderSerializer(Serializer):

View file

@ -243,6 +243,8 @@ class OrderViewSet(EvibesViewSet):
force_balance=serializer.validated_data.get("force_balance"),
force_payment=serializer.validated_data.get("force_payment"),
promocode_uuid=serializer.validated_data.get("promocode_uuid"),
shipping_address=serializer.validated_data.get("shipping_address"),
billing_address=serializer.validated_data.get("billing_address"),
)
match str(type(instance)):
case "<class 'payments.models.Transaction'>":

View file

@ -67,7 +67,6 @@ Continent = load_model("geo", "Continent")
Country = load_model("geo", "Country")
City = load_model("geo", "City")
# Only log errors during Travis tests
LOGGER_NAME = os.environ.get("TRAVIS_LOGGER_NAME", "geo")
@ -198,9 +197,8 @@ class Command(BaseCommand):
if not os.path.exists(self.data_dir):
os.makedirs(self.data_dir)
with open(os.path.join(self.data_dir, filename), "wb") as f:
file = f
file.write(web_file.read())
file.close()
f.write(web_file.read())
f.close()
elif not os.path.exists(os.path.join(self.data_dir, filename)):
raise Exception(f"File not found and download failed: {filename} [{url}]")

View file

@ -47,7 +47,6 @@ class TokenVerifyView(TokenViewBase):
serializer_class = TokenVerifySerializer
_serializer_class = TokenVerifySerializer
@method_decorator(ratelimit(key="ip", rate="10/h" if not DEBUG else "888/h"))
def post(self, request, *args, **kwargs):
try:
serializer = self.get_serializer(data=request.data)