diff --git a/core/graphene/mutations.py b/core/graphene/mutations.py index 85131820..37ccbb5b 100644 --- a/core/graphene/mutations.py +++ b/core/graphene/mutations.py @@ -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 "": 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")) diff --git a/core/models.py b/core/models.py index 32f6ae03..50a765d4 100644 --- a/core/models.py +++ b/core/models.py @@ -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!")) diff --git a/core/serializers/__init__.py b/core/serializers/__init__.py index 2d4a4998..85c80e94 100644 --- a/core/serializers/__init__.py +++ b/core/serializers/__init__.py @@ -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): diff --git a/core/viewsets.py b/core/viewsets.py index 15ad363e..6e373ec9 100644 --- a/core/viewsets.py +++ b/core/viewsets.py @@ -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 "": diff --git a/geo/management/commands/cities.py b/geo/management/commands/cities.py index 79f318ca..ac2e9c6a 100644 --- a/geo/management/commands/cities.py +++ b/geo/management/commands/cities.py @@ -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") @@ -93,7 +92,7 @@ class Command(BaseCommand): metavar="DATA_TYPES", default="all", help="Selectively import data. Comma separated list of data types: " - + str(import_opts).replace("'", ""), + + str(import_opts).replace("'", ""), ), make_option( "--flush", @@ -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}]") @@ -258,10 +256,10 @@ class Command(BaseCommand): import_continents_as_fks = isinstance(Country._meta.get_field("continent"), ForeignKey) for item in tqdm( - [d for d in data if d["code"] not in NO_LONGER_EXISTENT_COUNTRY_CODES], - disable=self.options.get("quiet"), - total=total, - desc="Importing countries", + [d for d in data if d["code"] not in NO_LONGER_EXISTENT_COUNTRY_CODES], + disable=self.options.get("quiet"), + total=total, + desc="Importing countries", ): if not self.call_hook("country_pre", item): continue @@ -314,10 +312,10 @@ class Command(BaseCommand): continue for country, neighbour_codes in tqdm( - list(neighbours.items()), - disable=self.options.get("quiet"), - total=len(neighbours), - desc="Importing country neighbours", + list(neighbours.items()), + disable=self.options.get("quiet"), + total=len(neighbours), + desc="Importing country neighbours", ): neighbours = [x for x in [countries.get(x) for x in neighbour_codes if x] if x] country.neighbours.add(*neighbours) @@ -328,10 +326,10 @@ class Command(BaseCommand): self.country_index = {} for obj in tqdm( - Country.objects.all(), - disable=self.options.get("quiet"), - total=Country.objects.all().count(), - desc="Building country index", + Country.objects.all(), + disable=self.options.get("quiet"), + total=Country.objects.all().count(), + desc="Building country index", ): self.country_index[obj.code] = obj @@ -347,10 +345,10 @@ class Command(BaseCommand): countries_not_found = {} for item in tqdm( - data, - disable=self.options.get("quiet"), - total=total, - desc="Importing regions", + data, + disable=self.options.get("quiet"), + total=total, + desc="Importing regions", ): if not self.call_hook("region_pre", item): continue @@ -409,13 +407,13 @@ class Command(BaseCommand): self.region_index = {} for obj in tqdm( - chain( - Region.objects.all().prefetch_related("country"), - Subregion.objects.all().prefetch_related("region__country"), - ), - disable=self.options.get("quiet"), - total=Region.objects.all().count() + Subregion.objects.all().count(), - desc="Building region index", + chain( + Region.objects.all().prefetch_related("country"), + Subregion.objects.all().prefetch_related("region__country"), + ), + disable=self.options.get("quiet"), + total=Region.objects.all().count() + Subregion.objects.all().count(), + desc="Building region index", ): self.region_index[obj.full_code()] = obj @@ -432,10 +430,10 @@ class Command(BaseCommand): regions_not_found = {} for item in tqdm( - data, - disable=self.options.get("quiet"), - total=total, - desc="Importing subregions", + data, + disable=self.options.get("quiet"), + total=total, + desc="Importing subregions", ): if not self.call_hook("subregion_pre", item): continue @@ -503,10 +501,10 @@ class Command(BaseCommand): self.build_region_index() for item in tqdm( - data, - disable=self.options.get("quiet"), - total=total, - desc="Importing cities", + data, + disable=self.options.get("quiet"), + total=total, + desc="Importing cities", ): if not self.call_hook("city_pre", item): continue @@ -616,10 +614,10 @@ class Command(BaseCommand): self.hierarchy = {} for item in tqdm( - data, - disable=self.options.get("quiet"), - total=total, - desc="Building hierarchy index", + data, + disable=self.options.get("quiet"), + total=total, + desc="Building hierarchy index", ): parent_id = int(item["parent"]) child_id = int(item["child"]) @@ -639,18 +637,18 @@ class Command(BaseCommand): city_index = {} for obj in tqdm( - City.objects.all(), - disable=self.options.get("quiet"), - total=City.objects.all().count(), - desc="Building city index", + City.objects.all(), + disable=self.options.get("quiet"), + total=City.objects.all().count(), + desc="Building city index", ): city_index[obj.id] = obj for item in tqdm( - data, - disable=self.options.get("quiet"), - total=total, - desc="Importing districts", + data, + disable=self.options.get("quiet"), + total=total, + desc="Importing districts", ): if not self.call_hook("district_pre", item): continue @@ -766,10 +764,10 @@ class Command(BaseCommand): 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" for obj in tqdm( - type_.objects.all(), - disable=self.options.get("quiet"), - total=type_.objects.all().count(), - desc=f"Building geo index for {plural_type_name.lower()}", + type_.objects.all(), + disable=self.options.get("quiet"), + total=type_.objects.all().count(), + desc=f"Building geo index for {plural_type_name.lower()}", ): geo_index[obj.id] = { "type": type_, @@ -777,10 +775,10 @@ class Command(BaseCommand): } for item in tqdm( - data, - disable=self.options.get("quiet"), - total=total, - desc="Importing data for alternative names", + data, + disable=self.options.get("quiet"), + total=total, + desc="Importing data for alternative names", ): if not self.call_hook("alt_name_pre", item): continue @@ -897,10 +895,10 @@ class Command(BaseCommand): self.postal_code_regex_index = {} for code, country in tqdm( - self.country_index.items(), - disable=self.options.get("quiet"), - total=len(self.country_index), - desc="Building postal code regex index", + self.country_index.items(), + disable=self.options.get("quiet"), + total=len(self.country_index), + desc="Building postal code regex index", ): try: 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: self.logger.debug("Zero postal codes found - using only-create postal code optimization") for item in tqdm( - data, - disable=self.options.get("quiet"), - total=total, - desc="Importing postal codes", + data, + disable=self.options.get("quiet"), + total=total, + desc="Importing postal codes", ): if not self.call_hook("postal_code_pre", item): continue @@ -1137,16 +1135,16 @@ class Command(BaseCommand): ) # If they're both part of the same city if ( - District.objects.filter( - Q(city__region__name_std__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), - city__country=pc.country, - ) - .values_list("city") - .distinct() - .count() - == 1 + District.objects.filter( + Q(city__region__name_std__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), + city__country=pc.country, + ) + .values_list("city") + .distinct() + .count() + == 1 ): # Use the one with the lower ID pc.district = ( @@ -1244,9 +1242,9 @@ class Command(BaseCommand): 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" for obj in tqdm( - type_.objects.all(), - disable=self.options.get("quiet"), - total=type_.objects.count(), - desc=f"Flushing alternative names for {plural_type_name}", + type_.objects.all(), + disable=self.options.get("quiet"), + total=type_.objects.count(), + desc=f"Flushing alternative names for {plural_type_name}", ): obj.alt_names.all().delete() diff --git a/vibes_auth/views.py b/vibes_auth/views.py index e0bc579f..1112af20 100644 --- a/vibes_auth/views.py +++ b/vibes_auth/views.py @@ -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)