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:
parent
11edfb9d4f
commit
43c8df0c05
6 changed files with 111 additions and 83 deletions
|
|
@ -172,26 +172,32 @@ class BuyOrder(BaseMutation):
|
||||||
force_balance = Boolean(required=False)
|
force_balance = Boolean(required=False)
|
||||||
force_payment = Boolean(required=False)
|
force_payment = Boolean(required=False)
|
||||||
promocode_uuid = UUID(required=False)
|
promocode_uuid = UUID(required=False)
|
||||||
|
shipping_address = UUID(required=False)
|
||||||
|
billing_address = UUID(required=False)
|
||||||
|
|
||||||
order = Field(OrderType, required=False)
|
order = Field(OrderType, required=False)
|
||||||
transaction = Field(TransactionType, required=False)
|
transaction = Field(TransactionType, required=False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mutate(_parent, info, order_uuid=None, order_hr_id=None, force_balance=False, force_payment=False,
|
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]):
|
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
|
user = info.context.user
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
order = None
|
||||||
|
|
||||||
if order_uuid:
|
if order_uuid:
|
||||||
order = Order.objects.get(user=user, uuid=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)
|
order = Order.objects.get(user=user, human_readable_id=order_hr_id)
|
||||||
|
|
||||||
instance = order.buy(
|
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)):
|
match str(type(instance)):
|
||||||
case "<class 'payments.models.Transaction'>":
|
case "<class 'payments.models.Transaction'>":
|
||||||
return BuyOrder(transaction=instance)
|
return BuyOrder(transaction=instance)
|
||||||
|
|
@ -199,6 +205,7 @@ class BuyOrder(BaseMutation):
|
||||||
return BuyOrder(order=instance)
|
return BuyOrder(order=instance)
|
||||||
case _:
|
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:
|
except Order.DoesNotExist:
|
||||||
raise Http404(_(f"order {order_uuid} not found"))
|
raise Http404(_(f"order {order_uuid} not found"))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -625,7 +625,8 @@ class Order(NiceModel):
|
||||||
return promocode.use(self)
|
return promocode.use(self)
|
||||||
|
|
||||||
def buy(
|
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:
|
) -> Self | Transaction | None:
|
||||||
if config.DISABLED_COMMERCE:
|
if config.DISABLED_COMMERCE:
|
||||||
raise DisabledCommerceError(_("you can not buy at this moment, please try again in a few minutes"))
|
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):
|
if (not force_balance and not force_payment) or (force_balance and force_payment):
|
||||||
raise ValueError(_("invalid force value"))
|
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:
|
if self.total_quantity < 1:
|
||||||
raise ValueError(_("you cannot purchase an empty order!"))
|
raise ValueError(_("you cannot purchase an empty order!"))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,8 @@ class BuyOrderSerializer(Serializer):
|
||||||
force_balance = BooleanField(required=False, default=False)
|
force_balance = BooleanField(required=False, default=False)
|
||||||
force_payment = BooleanField(required=False, default=False)
|
force_payment = BooleanField(required=False, default=False)
|
||||||
promocode_uuid = CharField(required=False)
|
promocode_uuid = CharField(required=False)
|
||||||
|
shipping_address_uuid = CharField(required=False)
|
||||||
|
billing_address_uuid = CharField(required=False)
|
||||||
|
|
||||||
|
|
||||||
class BuyUnregisteredOrderSerializer(Serializer):
|
class BuyUnregisteredOrderSerializer(Serializer):
|
||||||
|
|
|
||||||
|
|
@ -243,6 +243,8 @@ class OrderViewSet(EvibesViewSet):
|
||||||
force_balance=serializer.validated_data.get("force_balance"),
|
force_balance=serializer.validated_data.get("force_balance"),
|
||||||
force_payment=serializer.validated_data.get("force_payment"),
|
force_payment=serializer.validated_data.get("force_payment"),
|
||||||
promocode_uuid=serializer.validated_data.get("promocode_uuid"),
|
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)):
|
match str(type(instance)):
|
||||||
case "<class 'payments.models.Transaction'>":
|
case "<class 'payments.models.Transaction'>":
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,6 @@ Continent = load_model("geo", "Continent")
|
||||||
Country = load_model("geo", "Country")
|
Country = load_model("geo", "Country")
|
||||||
City = load_model("geo", "City")
|
City = load_model("geo", "City")
|
||||||
|
|
||||||
|
|
||||||
# Only log errors during Travis tests
|
# Only log errors during Travis tests
|
||||||
LOGGER_NAME = os.environ.get("TRAVIS_LOGGER_NAME", "geo")
|
LOGGER_NAME = os.environ.get("TRAVIS_LOGGER_NAME", "geo")
|
||||||
|
|
||||||
|
|
@ -93,7 +92,7 @@ class Command(BaseCommand):
|
||||||
metavar="DATA_TYPES",
|
metavar="DATA_TYPES",
|
||||||
default="all",
|
default="all",
|
||||||
help="Selectively import data. Comma separated list of data types: "
|
help="Selectively import data. Comma separated list of data types: "
|
||||||
+ str(import_opts).replace("'", ""),
|
+ str(import_opts).replace("'", ""),
|
||||||
),
|
),
|
||||||
make_option(
|
make_option(
|
||||||
"--flush",
|
"--flush",
|
||||||
|
|
@ -198,9 +197,8 @@ class Command(BaseCommand):
|
||||||
if not os.path.exists(self.data_dir):
|
if not os.path.exists(self.data_dir):
|
||||||
os.makedirs(self.data_dir)
|
os.makedirs(self.data_dir)
|
||||||
with open(os.path.join(self.data_dir, filename), "wb") as f:
|
with open(os.path.join(self.data_dir, filename), "wb") as f:
|
||||||
file = f
|
f.write(web_file.read())
|
||||||
file.write(web_file.read())
|
f.close()
|
||||||
file.close()
|
|
||||||
elif not os.path.exists(os.path.join(self.data_dir, filename)):
|
elif not os.path.exists(os.path.join(self.data_dir, filename)):
|
||||||
raise Exception(f"File not found and download failed: {filename} [{url}]")
|
raise Exception(f"File not found and download failed: {filename} [{url}]")
|
||||||
|
|
||||||
|
|
@ -258,10 +256,10 @@ class Command(BaseCommand):
|
||||||
import_continents_as_fks = isinstance(Country._meta.get_field("continent"), ForeignKey)
|
import_continents_as_fks = isinstance(Country._meta.get_field("continent"), ForeignKey)
|
||||||
|
|
||||||
for item in tqdm(
|
for item in tqdm(
|
||||||
[d for d in data if d["code"] not in NO_LONGER_EXISTENT_COUNTRY_CODES],
|
[d for d in data if d["code"] not in NO_LONGER_EXISTENT_COUNTRY_CODES],
|
||||||
disable=self.options.get("quiet"),
|
disable=self.options.get("quiet"),
|
||||||
total=total,
|
total=total,
|
||||||
desc="Importing countries",
|
desc="Importing countries",
|
||||||
):
|
):
|
||||||
if not self.call_hook("country_pre", item):
|
if not self.call_hook("country_pre", item):
|
||||||
continue
|
continue
|
||||||
|
|
@ -314,10 +312,10 @@ class Command(BaseCommand):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for country, neighbour_codes in tqdm(
|
for country, neighbour_codes in tqdm(
|
||||||
list(neighbours.items()),
|
list(neighbours.items()),
|
||||||
disable=self.options.get("quiet"),
|
disable=self.options.get("quiet"),
|
||||||
total=len(neighbours),
|
total=len(neighbours),
|
||||||
desc="Importing country neighbours",
|
desc="Importing country neighbours",
|
||||||
):
|
):
|
||||||
neighbours = [x for x in [countries.get(x) for x in neighbour_codes if x] if x]
|
neighbours = [x for x in [countries.get(x) for x in neighbour_codes if x] if x]
|
||||||
country.neighbours.add(*neighbours)
|
country.neighbours.add(*neighbours)
|
||||||
|
|
@ -328,10 +326,10 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
self.country_index = {}
|
self.country_index = {}
|
||||||
for obj in tqdm(
|
for obj in tqdm(
|
||||||
Country.objects.all(),
|
Country.objects.all(),
|
||||||
disable=self.options.get("quiet"),
|
disable=self.options.get("quiet"),
|
||||||
total=Country.objects.all().count(),
|
total=Country.objects.all().count(),
|
||||||
desc="Building country index",
|
desc="Building country index",
|
||||||
):
|
):
|
||||||
self.country_index[obj.code] = obj
|
self.country_index[obj.code] = obj
|
||||||
|
|
||||||
|
|
@ -347,10 +345,10 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
countries_not_found = {}
|
countries_not_found = {}
|
||||||
for item in tqdm(
|
for item in tqdm(
|
||||||
data,
|
data,
|
||||||
disable=self.options.get("quiet"),
|
disable=self.options.get("quiet"),
|
||||||
total=total,
|
total=total,
|
||||||
desc="Importing regions",
|
desc="Importing regions",
|
||||||
):
|
):
|
||||||
if not self.call_hook("region_pre", item):
|
if not self.call_hook("region_pre", item):
|
||||||
continue
|
continue
|
||||||
|
|
@ -409,13 +407,13 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
self.region_index = {}
|
self.region_index = {}
|
||||||
for obj in tqdm(
|
for obj in tqdm(
|
||||||
chain(
|
chain(
|
||||||
Region.objects.all().prefetch_related("country"),
|
Region.objects.all().prefetch_related("country"),
|
||||||
Subregion.objects.all().prefetch_related("region__country"),
|
Subregion.objects.all().prefetch_related("region__country"),
|
||||||
),
|
),
|
||||||
disable=self.options.get("quiet"),
|
disable=self.options.get("quiet"),
|
||||||
total=Region.objects.all().count() + Subregion.objects.all().count(),
|
total=Region.objects.all().count() + Subregion.objects.all().count(),
|
||||||
desc="Building region index",
|
desc="Building region index",
|
||||||
):
|
):
|
||||||
self.region_index[obj.full_code()] = obj
|
self.region_index[obj.full_code()] = obj
|
||||||
|
|
||||||
|
|
@ -432,10 +430,10 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
regions_not_found = {}
|
regions_not_found = {}
|
||||||
for item in tqdm(
|
for item in tqdm(
|
||||||
data,
|
data,
|
||||||
disable=self.options.get("quiet"),
|
disable=self.options.get("quiet"),
|
||||||
total=total,
|
total=total,
|
||||||
desc="Importing subregions",
|
desc="Importing subregions",
|
||||||
):
|
):
|
||||||
if not self.call_hook("subregion_pre", item):
|
if not self.call_hook("subregion_pre", item):
|
||||||
continue
|
continue
|
||||||
|
|
@ -503,10 +501,10 @@ class Command(BaseCommand):
|
||||||
self.build_region_index()
|
self.build_region_index()
|
||||||
|
|
||||||
for item in tqdm(
|
for item in tqdm(
|
||||||
data,
|
data,
|
||||||
disable=self.options.get("quiet"),
|
disable=self.options.get("quiet"),
|
||||||
total=total,
|
total=total,
|
||||||
desc="Importing cities",
|
desc="Importing cities",
|
||||||
):
|
):
|
||||||
if not self.call_hook("city_pre", item):
|
if not self.call_hook("city_pre", item):
|
||||||
continue
|
continue
|
||||||
|
|
@ -616,10 +614,10 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
self.hierarchy = {}
|
self.hierarchy = {}
|
||||||
for item in tqdm(
|
for item in tqdm(
|
||||||
data,
|
data,
|
||||||
disable=self.options.get("quiet"),
|
disable=self.options.get("quiet"),
|
||||||
total=total,
|
total=total,
|
||||||
desc="Building hierarchy index",
|
desc="Building hierarchy index",
|
||||||
):
|
):
|
||||||
parent_id = int(item["parent"])
|
parent_id = int(item["parent"])
|
||||||
child_id = int(item["child"])
|
child_id = int(item["child"])
|
||||||
|
|
@ -639,18 +637,18 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
city_index = {}
|
city_index = {}
|
||||||
for obj in tqdm(
|
for obj in tqdm(
|
||||||
City.objects.all(),
|
City.objects.all(),
|
||||||
disable=self.options.get("quiet"),
|
disable=self.options.get("quiet"),
|
||||||
total=City.objects.all().count(),
|
total=City.objects.all().count(),
|
||||||
desc="Building city index",
|
desc="Building city index",
|
||||||
):
|
):
|
||||||
city_index[obj.id] = obj
|
city_index[obj.id] = obj
|
||||||
|
|
||||||
for item in tqdm(
|
for item in tqdm(
|
||||||
data,
|
data,
|
||||||
disable=self.options.get("quiet"),
|
disable=self.options.get("quiet"),
|
||||||
total=total,
|
total=total,
|
||||||
desc="Importing districts",
|
desc="Importing districts",
|
||||||
):
|
):
|
||||||
if not self.call_hook("district_pre", item):
|
if not self.call_hook("district_pre", item):
|
||||||
continue
|
continue
|
||||||
|
|
@ -766,10 +764,10 @@ class Command(BaseCommand):
|
||||||
for type_ in (Country, Region, Subregion, City, District):
|
for type_ in (Country, Region, Subregion, City, District):
|
||||||
plural_type_name = f"{type_.__name__}s" if type_.__name__[-1] != "y" else f"{type_.__name__[:-1]}ies"
|
plural_type_name = f"{type_.__name__}s" if type_.__name__[-1] != "y" else f"{type_.__name__[:-1]}ies"
|
||||||
for obj in tqdm(
|
for obj in tqdm(
|
||||||
type_.objects.all(),
|
type_.objects.all(),
|
||||||
disable=self.options.get("quiet"),
|
disable=self.options.get("quiet"),
|
||||||
total=type_.objects.all().count(),
|
total=type_.objects.all().count(),
|
||||||
desc=f"Building geo index for {plural_type_name.lower()}",
|
desc=f"Building geo index for {plural_type_name.lower()}",
|
||||||
):
|
):
|
||||||
geo_index[obj.id] = {
|
geo_index[obj.id] = {
|
||||||
"type": type_,
|
"type": type_,
|
||||||
|
|
@ -777,10 +775,10 @@ class Command(BaseCommand):
|
||||||
}
|
}
|
||||||
|
|
||||||
for item in tqdm(
|
for item in tqdm(
|
||||||
data,
|
data,
|
||||||
disable=self.options.get("quiet"),
|
disable=self.options.get("quiet"),
|
||||||
total=total,
|
total=total,
|
||||||
desc="Importing data for alternative names",
|
desc="Importing data for alternative names",
|
||||||
):
|
):
|
||||||
if not self.call_hook("alt_name_pre", item):
|
if not self.call_hook("alt_name_pre", item):
|
||||||
continue
|
continue
|
||||||
|
|
@ -897,10 +895,10 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
self.postal_code_regex_index = {}
|
self.postal_code_regex_index = {}
|
||||||
for code, country in tqdm(
|
for code, country in tqdm(
|
||||||
self.country_index.items(),
|
self.country_index.items(),
|
||||||
disable=self.options.get("quiet"),
|
disable=self.options.get("quiet"),
|
||||||
total=len(self.country_index),
|
total=len(self.country_index),
|
||||||
desc="Building postal code regex index",
|
desc="Building postal code regex index",
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
self.postal_code_regex_index[code] = re.compile(country.postal_code_regex)
|
self.postal_code_regex_index[code] = re.compile(country.postal_code_regex)
|
||||||
|
|
@ -928,10 +926,10 @@ class Command(BaseCommand):
|
||||||
if num_existing_postal_codes == 0:
|
if num_existing_postal_codes == 0:
|
||||||
self.logger.debug("Zero postal codes found - using only-create postal code optimization")
|
self.logger.debug("Zero postal codes found - using only-create postal code optimization")
|
||||||
for item in tqdm(
|
for item in tqdm(
|
||||||
data,
|
data,
|
||||||
disable=self.options.get("quiet"),
|
disable=self.options.get("quiet"),
|
||||||
total=total,
|
total=total,
|
||||||
desc="Importing postal codes",
|
desc="Importing postal codes",
|
||||||
):
|
):
|
||||||
if not self.call_hook("postal_code_pre", item):
|
if not self.call_hook("postal_code_pre", item):
|
||||||
continue
|
continue
|
||||||
|
|
@ -1137,16 +1135,16 @@ class Command(BaseCommand):
|
||||||
)
|
)
|
||||||
# If they're both part of the same city
|
# If they're both part of the same city
|
||||||
if (
|
if (
|
||||||
District.objects.filter(
|
District.objects.filter(
|
||||||
Q(city__region__name_std__iexact=pc.region_name)
|
Q(city__region__name_std__iexact=pc.region_name)
|
||||||
| Q(city__region__name__iexact=pc.region_name),
|
| Q(city__region__name__iexact=pc.region_name),
|
||||||
Q(name_std__iexact=pc.district_name) | Q(name__iexact=pc.district_name),
|
Q(name_std__iexact=pc.district_name) | Q(name__iexact=pc.district_name),
|
||||||
city__country=pc.country,
|
city__country=pc.country,
|
||||||
)
|
)
|
||||||
.values_list("city")
|
.values_list("city")
|
||||||
.distinct()
|
.distinct()
|
||||||
.count()
|
.count()
|
||||||
== 1
|
== 1
|
||||||
):
|
):
|
||||||
# Use the one with the lower ID
|
# Use the one with the lower ID
|
||||||
pc.district = (
|
pc.district = (
|
||||||
|
|
@ -1244,9 +1242,9 @@ class Command(BaseCommand):
|
||||||
for type_ in (Country, Region, Subregion, City, District, PostalCode):
|
for type_ in (Country, Region, Subregion, City, District, PostalCode):
|
||||||
plural_type_name = type_.__name__ if type_.__name__[-1] != "y" else f"{type_.__name__[:-1]}ies"
|
plural_type_name = type_.__name__ if type_.__name__[-1] != "y" else f"{type_.__name__[:-1]}ies"
|
||||||
for obj in tqdm(
|
for obj in tqdm(
|
||||||
type_.objects.all(),
|
type_.objects.all(),
|
||||||
disable=self.options.get("quiet"),
|
disable=self.options.get("quiet"),
|
||||||
total=type_.objects.count(),
|
total=type_.objects.count(),
|
||||||
desc=f"Flushing alternative names for {plural_type_name}",
|
desc=f"Flushing alternative names for {plural_type_name}",
|
||||||
):
|
):
|
||||||
obj.alt_names.all().delete()
|
obj.alt_names.all().delete()
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,6 @@ class TokenVerifyView(TokenViewBase):
|
||||||
serializer_class = TokenVerifySerializer
|
serializer_class = TokenVerifySerializer
|
||||||
_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):
|
def post(self, request, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue