Merge branch 'main' into storefront-nuxt
This commit is contained in:
commit
949e077942
24 changed files with 204 additions and 79 deletions
|
|
@ -100,7 +100,7 @@ before running installment scripts
|
||||||
### nginx
|
### nginx
|
||||||
|
|
||||||
Please comment-out SSL-related lines, then apply necessary configurations, run `certbot --cert-only --nginx`,
|
Please comment-out SSL-related lines, then apply necessary configurations, run `certbot --cert-only --nginx`,
|
||||||
decomment previously commented lines and enjoy eVibes over HTTPS!
|
decomment previously commented lines, and enjoy eVibes over HTTPS!
|
||||||
|
|
||||||
### .env
|
### .env
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@ import traceback
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from constance import config
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
from core.crm.exceptions import CRMException
|
from core.crm.exceptions import CRMException
|
||||||
from core.models import CustomerRelationshipManagementProvider, Order, OrderCrmLink
|
from core.models import CustomerRelationshipManagementProvider, Order, OrderCrmLink
|
||||||
|
from core.utils import is_status_code_success
|
||||||
|
|
||||||
logger = logging.getLogger("django")
|
logger = logging.getLogger("django")
|
||||||
|
|
||||||
|
|
@ -25,7 +27,7 @@ class AmoCRM:
|
||||||
logger.warning("Multiple AMO CRM providers found")
|
logger.warning("Multiple AMO CRM providers found")
|
||||||
raise CRMException("Multiple AMO CRM providers found") from mre
|
raise CRMException("Multiple AMO CRM providers found") from mre
|
||||||
|
|
||||||
self.base = f"https://{self.instance.integration_url}"
|
self.base = f"{self.instance.integration_url}"
|
||||||
|
|
||||||
self.client_id = self.instance.authentication.get("client_id")
|
self.client_id = self.instance.authentication.get("client_id")
|
||||||
self.client_secret = self.instance.authentication.get("client_secret")
|
self.client_secret = self.instance.authentication.get("client_secret")
|
||||||
|
|
@ -51,6 +53,7 @@ class AmoCRM:
|
||||||
payload = {
|
payload = {
|
||||||
"client_id": self.client_id,
|
"client_id": self.client_id,
|
||||||
"client_secret": self.client_secret,
|
"client_secret": self.client_secret,
|
||||||
|
"redirect_uri": f"https://api.{config.BASE_DOMAIN}/",
|
||||||
}
|
}
|
||||||
if self.refresh_token:
|
if self.refresh_token:
|
||||||
payload["grant_type"] = "refresh_token"
|
payload["grant_type"] = "refresh_token"
|
||||||
|
|
@ -59,7 +62,9 @@ class AmoCRM:
|
||||||
payload["grant_type"] = "authorization_code"
|
payload["grant_type"] = "authorization_code"
|
||||||
payload["code"] = self.authorization_code
|
payload["code"] = self.authorization_code
|
||||||
r = requests.post(f"{self.base}/oauth2/access_token", json=payload, timeout=15)
|
r = requests.post(f"{self.base}/oauth2/access_token", json=payload, timeout=15)
|
||||||
r.raise_for_status()
|
if not is_status_code_success(r.status_code):
|
||||||
|
logger.error(f"Unable to get AMO access token: {r.status_code} {r.text}")
|
||||||
|
raise CRMException("Unable to get AMO access token")
|
||||||
data = r.json()
|
data = r.json()
|
||||||
self.access_token = data["access_token"]
|
self.access_token = data["access_token"]
|
||||||
cache.set("amo_refresh_token", data["refresh_token"], 604800)
|
cache.set("amo_refresh_token", data["refresh_token"], 604800)
|
||||||
|
|
@ -81,16 +86,32 @@ class AmoCRM:
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
def _get_customer_name(self, order: Order) -> str:
|
def _get_customer_name(self, order: Order) -> str:
|
||||||
if not order.attributes.get("business_identificator"):
|
if type(order.attributes) is not dict:
|
||||||
return order.user.get_full_name() or (
|
raise ValueError("order.attributes must be a dict")
|
||||||
f"{order.attributes.get('customer_name')} | "
|
|
||||||
f"{order.attributes.get('customer_phone_number') or order.attributes.get('customer_email')}"
|
business_identificator = (
|
||||||
|
order.attributes.get("business_identificator")
|
||||||
|
or order.attributes.get("businessIdentificator")
|
||||||
|
or order.user.attributes.get("business_identificator")
|
||||||
|
or order.user.attributes.get("businessIdentificator")
|
||||||
|
or ""
|
||||||
|
)
|
||||||
|
|
||||||
|
if not business_identificator:
|
||||||
|
return (
|
||||||
|
order.user.get_full_name()
|
||||||
|
if order.user
|
||||||
|
else None
|
||||||
|
or (
|
||||||
|
f"{order.attributes.get('customer_name')} | "
|
||||||
|
f"{order.attributes.get('customer_phone_number') or order.attributes.get('customer_email')}"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
business_identificator = order.attributes.get("business_identificator")
|
|
||||||
r = requests.get(
|
r = requests.get(
|
||||||
f"https://api-fns.ru/api/egr?req={business_identificator}&key={self.fns_api_key}", timeout=15
|
f"https://api-fns.ru/api/egr?req={business_identificator}&key={self.fns_api_key}", timeout=15
|
||||||
)
|
)
|
||||||
|
r.raise_for_status()
|
||||||
body = r.json()
|
body = r.json()
|
||||||
except requests.exceptions.RequestException as rex:
|
except requests.exceptions.RequestException as rex:
|
||||||
logger.error(f"Unable to get company info with FNS: {rex}")
|
logger.error(f"Unable to get company info with FNS: {rex}")
|
||||||
|
|
@ -137,6 +158,8 @@ class AmoCRM:
|
||||||
body = r.json()
|
body = r.json()
|
||||||
return body.get("_embedded", {}).get("contacts", [{}])[0].get("id", None)
|
return body.get("_embedded", {}).get("contacts", [{}])[0].get("id", None)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -175,5 +198,5 @@ class AmoCRM:
|
||||||
|
|
||||||
if link.order.status == new_status:
|
if link.order.status == new_status:
|
||||||
return
|
return
|
||||||
link.order.status = self.STATUS_MAP.get(new_status)
|
link.order.status = self.STATUS_MAP[new_status]
|
||||||
link.order.save(update_fields=["status"])
|
link.order.save(update_fields=["status"])
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ from django.db.models import (
|
||||||
Case,
|
Case,
|
||||||
Exists,
|
Exists,
|
||||||
FloatField,
|
FloatField,
|
||||||
IntegerField,
|
|
||||||
Max,
|
Max,
|
||||||
OuterRef,
|
OuterRef,
|
||||||
Prefetch,
|
Prefetch,
|
||||||
|
|
@ -61,6 +60,7 @@ class CaseInsensitiveListFilter(BaseInFilter, CharFilter):
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class ProductFilter(FilterSet):
|
class ProductFilter(FilterSet):
|
||||||
search = CharFilter(field_name="name", method="search_products", label=_("Search"))
|
search = CharFilter(field_name="name", method="search_products", label=_("Search"))
|
||||||
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact", label=_("UUID"))
|
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact", label=_("UUID"))
|
||||||
|
|
@ -95,7 +95,6 @@ class ProductFilter(FilterSet):
|
||||||
("price_order", "price"),
|
("price_order", "price"),
|
||||||
("sku", "sku"),
|
("sku", "sku"),
|
||||||
("?", "random"),
|
("?", "random"),
|
||||||
("personal_order_only", "personal_order_only"),
|
|
||||||
),
|
),
|
||||||
initial="uuid",
|
initial="uuid",
|
||||||
)
|
)
|
||||||
|
|
@ -153,7 +152,7 @@ class ProductFilter(FilterSet):
|
||||||
if not value:
|
if not value:
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
uuids = [product.get("uuid") for product in process_query(query=value, indexes=("products",))["products"]]
|
uuids = [product.get("uuid") for product in process_query(query=value, indexes=("products",))["products"]] # type: ignore
|
||||||
|
|
||||||
return queryset.filter(uuid__in=uuids)
|
return queryset.filter(uuid__in=uuids)
|
||||||
|
|
||||||
|
|
@ -280,21 +279,21 @@ class ProductFilter(FilterSet):
|
||||||
qs = qs.annotate(
|
qs = qs.annotate(
|
||||||
has_stock=Max(
|
has_stock=Max(
|
||||||
Case(
|
Case(
|
||||||
When(stocks__quantity__gt=0, then=Value(1)),
|
When(stocks__quantity__gt=0, then=Value(True)),
|
||||||
default=Value(0),
|
default=Value(False),
|
||||||
output_field=IntegerField(),
|
output_field=BooleanField(),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
has_price=Max(
|
has_price=Max(
|
||||||
Case(
|
Case(
|
||||||
When(stocks__price__gt=0, then=Value(1)),
|
When(stocks__price__gt=0, then=Value(True)),
|
||||||
default=Value(0),
|
default=Value(False),
|
||||||
output_field=IntegerField(),
|
output_field=BooleanField(),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
).annotate(
|
).annotate(
|
||||||
personal_order_only=Case(
|
personal_orders_only=Case(
|
||||||
When(has_stock=0, has_price=1, then=Value(True)),
|
When(has_stock=False, has_price=False, then=Value(True)),
|
||||||
default=Value(False),
|
default=Value(False),
|
||||||
output_field=BooleanField(),
|
output_field=BooleanField(),
|
||||||
)
|
)
|
||||||
|
|
@ -311,10 +310,11 @@ class ProductFilter(FilterSet):
|
||||||
key = "?"
|
key = "?"
|
||||||
mapped_requested.append(key)
|
mapped_requested.append(key)
|
||||||
continue
|
continue
|
||||||
|
if key == "personal_orders_only":
|
||||||
|
continue
|
||||||
mapped_requested.append(f"-{key}" if desc else key)
|
mapped_requested.append(f"-{key}" if desc else key)
|
||||||
|
|
||||||
has_personal_in_request = any(p.lstrip("-") == "personal_order_only" for p in mapped_requested)
|
final_ordering = mapped_requested + ["personal_orders_only"]
|
||||||
final_ordering = (["personal_order_only"] if not has_personal_in_request else []) + mapped_requested
|
|
||||||
|
|
||||||
if final_ordering:
|
if final_ordering:
|
||||||
qs = qs.order_by(*final_ordering)
|
qs = qs.order_by(*final_ordering)
|
||||||
|
|
@ -396,6 +396,7 @@ class WishlistFilter(FilterSet):
|
||||||
fields = ["uuid", "user_email", "user", "order_by"]
|
fields = ["uuid", "user_email", "user", "order_by"]
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class CategoryFilter(FilterSet):
|
class CategoryFilter(FilterSet):
|
||||||
search = CharFilter(field_name="name", method="search_categories", label=_("Search"))
|
search = CharFilter(field_name="name", method="search_categories", label=_("Search"))
|
||||||
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact")
|
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact")
|
||||||
|
|
@ -429,7 +430,7 @@ class CategoryFilter(FilterSet):
|
||||||
if not value:
|
if not value:
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
uuids = [category.get("uuid") for category in process_query(query=value, indexes=("categories",))["categories"]]
|
uuids = [category.get("uuid") for category in process_query(query=value, indexes=("categories",))["categories"]] # type: ignore
|
||||||
|
|
||||||
return queryset.filter(uuid__in=uuids)
|
return queryset.filter(uuid__in=uuids)
|
||||||
|
|
||||||
|
|
@ -522,6 +523,7 @@ class CategoryFilter(FilterSet):
|
||||||
return queryset.filter(parent__uuid=uuid_val)
|
return queryset.filter(parent__uuid=uuid_val)
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class BrandFilter(FilterSet):
|
class BrandFilter(FilterSet):
|
||||||
search = CharFilter(field_name="name", method="search_brands", label=_("Search"))
|
search = CharFilter(field_name="name", method="search_brands", label=_("Search"))
|
||||||
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact")
|
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact")
|
||||||
|
|
@ -547,7 +549,7 @@ class BrandFilter(FilterSet):
|
||||||
if not value:
|
if not value:
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
uuids = [brand.get("uuid") for brand in process_query(query=value, indexes=("brands",))["brands"]]
|
uuids = [brand.get("uuid") for brand in process_query(query=value, indexes=("brands",))["brands"]] # type: ignore
|
||||||
|
|
||||||
return queryset.filter(uuid__in=uuids)
|
return queryset.filter(uuid__in=uuids)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -546,6 +546,7 @@ class FeedbackProductAction(BaseMutation):
|
||||||
order_product = OrderProduct.objects.get(uuid=order_product_uuid)
|
order_product = OrderProduct.objects.get(uuid=order_product_uuid)
|
||||||
if user != order_product.order.user:
|
if user != order_product.order.user:
|
||||||
raise PermissionDenied(permission_denied_message)
|
raise PermissionDenied(permission_denied_message)
|
||||||
|
feedback = None
|
||||||
match action:
|
match action:
|
||||||
case "add":
|
case "add":
|
||||||
feedback = order_product.do_feedback(comment=comment, rating=rating, action="add")
|
feedback = order_product.do_feedback(comment=comment, rating=rating, action="add")
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from constance import config
|
from constance import config
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db.models import Max, Min, QuerySet
|
from django.db.models import Max, Min
|
||||||
from django.db.models.functions import Length
|
from django.db.models.functions import Length
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from graphene import (
|
from graphene import (
|
||||||
|
|
@ -55,7 +56,6 @@ from core.utils.seo_builders import (
|
||||||
website_schema,
|
website_schema,
|
||||||
)
|
)
|
||||||
from payments.graphene.object_types import TransactionType
|
from payments.graphene.object_types import TransactionType
|
||||||
from payments.models import Transaction
|
|
||||||
|
|
||||||
logger = logging.getLogger("django")
|
logger = logging.getLogger("django")
|
||||||
|
|
||||||
|
|
@ -464,19 +464,19 @@ class OrderType(DjangoObjectType):
|
||||||
)
|
)
|
||||||
description = _("orders")
|
description = _("orders")
|
||||||
|
|
||||||
def resolve_total_price(self: Order, _info):
|
def resolve_total_price(self: Order, _info) -> float:
|
||||||
return self.total_price
|
return self.total_price
|
||||||
|
|
||||||
def resolve_total_quantity(self: Order, _info):
|
def resolve_total_quantity(self: Order, _info) -> int:
|
||||||
return self.total_quantity
|
return self.total_quantity
|
||||||
|
|
||||||
def resolve_notifications(self: Order, _info):
|
def resolve_notifications(self: Order, _info) -> dict[str, Any]:
|
||||||
return camelize(self.notifications)
|
return camelize(self.notifications)
|
||||||
|
|
||||||
def resolve_attributes(self: Order, _info):
|
def resolve_attributes(self: Order, _info) -> dict[str, Any]:
|
||||||
return camelize(self.attributes)
|
return camelize(self.attributes)
|
||||||
|
|
||||||
def resolve_payments_transactions(self: Order, _info) -> QuerySet[Transaction] | None:
|
def resolve_payments_transactions(self: Order, _info):
|
||||||
if self.payments_transactions:
|
if self.payments_transactions:
|
||||||
return self.payments_transactions.all()
|
return self.payments_transactions.all()
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from django.db.models import Max, Case, When, Value, IntegerField, BooleanField
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from graphene import Field, List, ObjectType, Schema
|
from graphene import Field, List, ObjectType, Schema
|
||||||
|
|
@ -148,7 +149,7 @@ class Query(ObjectType):
|
||||||
product = Product.objects.get(uuid=kwargs["uuid"])
|
product = Product.objects.get(uuid=kwargs["uuid"])
|
||||||
if product.is_active and product.brand.is_active and product.category.is_active:
|
if product.is_active and product.brand.is_active and product.category.is_active:
|
||||||
info.context.user.add_to_recently_viewed(product.uuid)
|
info.context.user.add_to_recently_viewed(product.uuid)
|
||||||
return (
|
base_qs = (
|
||||||
Product.objects.all().select_related("brand", "category").prefetch_related("images", "stocks")
|
Product.objects.all().select_related("brand", "category").prefetch_related("images", "stocks")
|
||||||
if info.context.user.has_perm("core.view_product")
|
if info.context.user.has_perm("core.view_product")
|
||||||
else Product.objects.filter(
|
else Product.objects.filter(
|
||||||
|
|
@ -162,6 +163,35 @@ class Query(ObjectType):
|
||||||
.prefetch_related("images", "stocks")
|
.prefetch_related("images", "stocks")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
base_qs = (
|
||||||
|
base_qs.annotate(
|
||||||
|
has_stock=Max(
|
||||||
|
Case(
|
||||||
|
When(stocks__quantity__gt=0, then=Value(1)),
|
||||||
|
default=Value(0),
|
||||||
|
output_field=IntegerField(),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
has_price=Max(
|
||||||
|
Case(
|
||||||
|
When(stocks__price__gt=0, then=Value(1)),
|
||||||
|
default=Value(0),
|
||||||
|
output_field=IntegerField(),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
personal_order_only=Case(
|
||||||
|
When(has_stock=0, has_price=1, then=Value(True)),
|
||||||
|
default=Value(False),
|
||||||
|
output_field=BooleanField(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.order_by("personal_order_only")
|
||||||
|
)
|
||||||
|
|
||||||
|
return base_qs
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_orders(_parent, info, **kwargs):
|
def resolve_orders(_parent, info, **kwargs):
|
||||||
orders = Order.objects
|
orders = Order.objects
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -2133,7 +2133,7 @@ msgstr "человекочитаемый идентификатор"
|
||||||
|
|
||||||
#: core/models.py:1261
|
#: core/models.py:1261
|
||||||
msgid "order"
|
msgid "order"
|
||||||
msgstr "Заказать"
|
msgstr "Заказ"
|
||||||
|
|
||||||
#: core/models.py:1282
|
#: core/models.py:1282
|
||||||
msgid "a user must have only one pending order at a time"
|
msgid "a user must have only one pending order at a time"
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ def generate_unique_sku(make_candidate, taken):
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
def backfill_sku(apps, schema_editor):
|
def backfill_sku(apps, schema_editor):
|
||||||
Product = apps.get_model("core", "Product")
|
Product = apps.get_model("core", "Product")
|
||||||
from core.utils import generate_human_readable_id as make_candidate
|
from core.utils import generate_human_readable_id as make_candidate
|
||||||
|
|
@ -35,6 +36,7 @@ def backfill_sku(apps, schema_editor):
|
||||||
last_pk = ids[-1]
|
last_pk = ids[-1]
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
def noop(apps, schema_editor):
|
def noop(apps, schema_editor):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -541,7 +541,7 @@ class Product(ExportModelOperationsMixin("product"), NiceModel): # type: ignore
|
||||||
default=generate_human_readable_id,
|
default=generate_human_readable_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
objects: ProductManager = ProductManager()
|
objects = ProductManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("product")
|
verbose_name = _("product")
|
||||||
|
|
@ -599,7 +599,7 @@ class Product(ExportModelOperationsMixin("product"), NiceModel): # type: ignore
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def personal_orders_only(self) -> bool:
|
def personal_orders_only(self) -> bool:
|
||||||
return not self.quantity > 0 and self.price > 0.0
|
return not (self.quantity > 0 and self.price > 0.0)
|
||||||
|
|
||||||
|
|
||||||
class Attribute(ExportModelOperationsMixin("attribute"), NiceModel): # type: ignore [misc, django-manager-missing]
|
class Attribute(ExportModelOperationsMixin("attribute"), NiceModel): # type: ignore [misc, django-manager-missing]
|
||||||
|
|
@ -1266,6 +1266,10 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): # type: ignore [mi
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_business(self) -> bool:
|
def is_business(self) -> bool:
|
||||||
|
if type(self.attributes) is not dict:
|
||||||
|
self.attributes = {}
|
||||||
|
self.save()
|
||||||
|
return False
|
||||||
with suppress(Exception):
|
with suppress(Exception):
|
||||||
return (self.attributes.get("is_business", False) if self.attributes else False) or (
|
return (self.attributes.get("is_business", False) if self.attributes else False) or (
|
||||||
(self.user.attributes.get("is_business", False) and self.user.attributes.get("business_identificator"))
|
(self.user.attributes.get("is_business", False) and self.user.attributes.get("business_identificator"))
|
||||||
|
|
@ -1306,7 +1310,7 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): # type: ignore [mi
|
||||||
def add_product(
|
def add_product(
|
||||||
self,
|
self,
|
||||||
product_uuid=None,
|
product_uuid=None,
|
||||||
attributes: list | None = None,
|
attributes: list | dict | None = None,
|
||||||
update_quantity=True,
|
update_quantity=True,
|
||||||
):
|
):
|
||||||
if attributes is None:
|
if attributes is None:
|
||||||
|
|
@ -1776,7 +1780,10 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel): # t
|
||||||
def download_url(self: Self) -> str:
|
def download_url(self: Self) -> str:
|
||||||
if self.product and self.product.stocks:
|
if self.product and self.product.stocks:
|
||||||
if self.product.is_digital and self.product.stocks.first().digital_asset: # type: ignore [union-attr]
|
if self.product.is_digital and self.product.stocks.first().digital_asset: # type: ignore [union-attr]
|
||||||
return self.download.url
|
try:
|
||||||
|
return self.download.url
|
||||||
|
except self.download.RelatedObjectDoesNotExist:
|
||||||
|
return DigitalAssetDownload.objects.create(order_product=self).url
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def do_feedback(self, rating=10, comment="", action="add") -> Optional["Feedback"] | int:
|
def do_feedback(self, rating=10, comment="", action="add") -> Optional["Feedback"] | int:
|
||||||
|
|
@ -1794,7 +1801,7 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel): # t
|
||||||
|
|
||||||
if action == "add":
|
if action == "add":
|
||||||
if not feedback_exists:
|
if not feedback_exists:
|
||||||
if self.order.status not in ["MOMENTAL", "PENDING", "FAILED"]:
|
if self.order.status == "FINISHED":
|
||||||
return Feedback.objects.create(rating=rating, comment=comment, order_product=self)
|
return Feedback.objects.create(rating=rating, comment=comment, order_product=self)
|
||||||
else:
|
else:
|
||||||
raise ValueError(_("you cannot feedback an order which is not received"))
|
raise ValueError(_("you cannot feedback an order which is not received"))
|
||||||
|
|
@ -1827,7 +1834,7 @@ class CustomerRelationshipManagementProvider(ExportModelOperationsMixin("crm_pro
|
||||||
verbose_name_plural = _("CRMs")
|
verbose_name_plural = _("CRMs")
|
||||||
|
|
||||||
|
|
||||||
class OrderCrmLink(ExportModelOperationsMixin("order_crm_link"), NiceModel):
|
class OrderCrmLink(ExportModelOperationsMixin("order_crm_link"), NiceModel): # type: ignore
|
||||||
order = ForeignKey(to=Order, on_delete=PROTECT, related_name="crm_links")
|
order = ForeignKey(to=Order, on_delete=PROTECT, related_name="crm_links")
|
||||||
crm = ForeignKey(to=CustomerRelationshipManagementProvider, on_delete=PROTECT, related_name="order_links")
|
crm = ForeignKey(to=CustomerRelationshipManagementProvider, on_delete=PROTECT, related_name="order_links")
|
||||||
crm_lead_id = CharField(max_length=30, unique=True, db_index=True)
|
crm_lead_id = CharField(max_length=30, unique=True, db_index=True)
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ class CategoryDetailSerializer(ModelSerializer):
|
||||||
|
|
||||||
filterable_results = []
|
filterable_results = []
|
||||||
for attr in attributes:
|
for attr in attributes:
|
||||||
vals = grouped.get(attr.id, [])
|
vals = grouped.get(attr.id, []) # type: ignore
|
||||||
slice_vals = vals[:128] if len(vals) > 128 else vals
|
slice_vals = vals[:128] if len(vals) > 128 else vals
|
||||||
filterable_results.append(
|
filterable_results.append(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ class AddressCreateSerializer(ModelSerializer):
|
||||||
write_only=True,
|
write_only=True,
|
||||||
max_length=512,
|
max_length=512,
|
||||||
)
|
)
|
||||||
address_line_1 = CharField(write_only=True, max_length=128, required=False)
|
address_line_1 = CharField(write_only=True, max_length=128, required=True)
|
||||||
address_line_2 = CharField(write_only=True, max_length=128, required=False)
|
address_line_2 = CharField(write_only=True, max_length=128, required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -177,6 +177,7 @@ class BuyUnregisteredOrderSerializer(Serializer):
|
||||||
class BuyAsBusinessOrderSerializer(Serializer):
|
class BuyAsBusinessOrderSerializer(Serializer):
|
||||||
products = AddOrderProductSerializer(many=True, required=True)
|
products = AddOrderProductSerializer(many=True, required=True)
|
||||||
business_identificator = CharField(required=True)
|
business_identificator = CharField(required=True)
|
||||||
|
promocode_uuid = UUIDField(required=False)
|
||||||
business_email = CharField(required=True)
|
business_email = CharField(required=True)
|
||||||
business_phone_number = CharField(required=True)
|
business_phone_number = CharField(required=True)
|
||||||
billing_business_address_uuid = CharField(required=False)
|
billing_business_address_uuid = CharField(required=False)
|
||||||
|
|
|
||||||
|
|
@ -255,3 +255,7 @@ def generate_human_readable_token() -> str:
|
||||||
str: A 20-character random token.
|
str: A 20-character random token.
|
||||||
"""
|
"""
|
||||||
return "".join([secrets.choice(CROCKFORD) for _ in range(20)])
|
return "".join([secrets.choice(CROCKFORD) for _ in range(20)])
|
||||||
|
|
||||||
|
|
||||||
|
def is_status_code_success(status_code: int) -> bool:
|
||||||
|
return 200 <= status_code < 300
|
||||||
|
|
|
||||||
|
|
@ -51,10 +51,20 @@ def send_order_created_email(order_pk: str) -> tuple[bool, str]:
|
||||||
except Order.DoesNotExist:
|
except Order.DoesNotExist:
|
||||||
return False, f"Order not found with the given pk: {order_pk}"
|
return False, f"Order not found with the given pk: {order_pk}"
|
||||||
|
|
||||||
|
if type(order.attributes) is not dict:
|
||||||
|
order.attributes = {}
|
||||||
|
|
||||||
if not any([order.user, order.attributes.get("email", None), order.attributes.get("customer_email", None)]):
|
if not any([order.user, order.attributes.get("email", None), order.attributes.get("customer_email", None)]):
|
||||||
return False, f"Order's user not found with the given pk: {order_pk}"
|
return False, f"Order's user not found with the given pk: {order_pk}"
|
||||||
|
|
||||||
activate(order.user.language)
|
language = settings.LANGUAGE_CODE
|
||||||
|
recipient = order.attributes.get("customer_email", "")
|
||||||
|
|
||||||
|
if order.user:
|
||||||
|
recipient = order.user.email
|
||||||
|
language = order.user.language
|
||||||
|
|
||||||
|
activate(language)
|
||||||
|
|
||||||
set_email_settings()
|
set_email_settings()
|
||||||
connection = mail.get_connection()
|
connection = mail.get_connection()
|
||||||
|
|
@ -71,7 +81,7 @@ def send_order_created_email(order_pk: str) -> tuple[bool, str]:
|
||||||
"total_price": order.total_price,
|
"total_price": order.total_price,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
to=[order.user.email],
|
to=[recipient],
|
||||||
from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>",
|
from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>",
|
||||||
connection=connection,
|
connection=connection,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
2
core/vendors/__init__.py
vendored
2
core/vendors/__init__.py
vendored
|
|
@ -235,7 +235,7 @@ class AbstractVendor:
|
||||||
if not rate:
|
if not rate:
|
||||||
raise RatesError(f"No rate found for {currency or self.currency} in {rates} with probider {provider}...")
|
raise RatesError(f"No rate found for {currency or self.currency} in {rates} with probider {provider}...")
|
||||||
|
|
||||||
return float(round(price / rate, 2)) if rate else float(round(price, 2))
|
return float(round(price / rate, 2)) if rate else round(price, 2)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def round_price_marketologically(price: float) -> float:
|
def round_price_marketologically(price: float) -> float:
|
||||||
|
|
|
||||||
|
|
@ -428,27 +428,35 @@ class BuyAsBusinessView(APIView):
|
||||||
Handles the "POST" request to process a business purchase.
|
Handles the "POST" request to process a business purchase.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@method_decorator(ratelimit(key="ip", rate="2/h", block=True))
|
@method_decorator(ratelimit(key="ip", rate="10/h" if not settings.DEBUG else "888/h"))
|
||||||
def post(self, request, *_args, **kwargs):
|
def post(self, request, *_args, **kwargs):
|
||||||
serializer = BuyAsBusinessOrderSerializer(data=request.data)
|
serializer = BuyAsBusinessOrderSerializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
order = Order.objects.create(status="MOMENTAL")
|
order = Order.objects.create(status="MOMENTAL")
|
||||||
products = [product.get("product_uuid") for product in serializer.validated_data.get("products")]
|
products = [product.get("product_uuid") for product in serializer.validated_data.get("products")]
|
||||||
transaction = order.buy_without_registration(
|
try:
|
||||||
products=products,
|
transaction = order.buy_without_registration(
|
||||||
promocode_uuid=serializer.validated_data.get("promocode_uuid"),
|
products=products,
|
||||||
customer_name=serializer.validated_data.get("business_identificator"),
|
promocode_uuid=serializer.validated_data.get("promocode_uuid"),
|
||||||
customer_email=serializer.validated_data.get("business_email"),
|
customer_name=serializer.validated_data.get("business_identificator"),
|
||||||
customer_phone_number=serializer.validated_data.get("business_phone_number"),
|
customer_email=serializer.validated_data.get("business_email"),
|
||||||
billing_customer_address=serializer.validated_data.get("billing_business_address_uuid"),
|
customer_phone_number=serializer.validated_data.get("business_phone_number"),
|
||||||
shipping_customer_address=serializer.validated_data.get("shipping_business_address_uuid"),
|
billing_customer_address=serializer.validated_data.get("billing_business_address_uuid"),
|
||||||
payment_method=serializer.validated_data.get("payment_method"),
|
shipping_customer_address=serializer.validated_data.get("shipping_business_address_uuid"),
|
||||||
is_business=True,
|
payment_method=serializer.validated_data.get("payment_method"),
|
||||||
)
|
is_business=True,
|
||||||
return Response(
|
)
|
||||||
status=status.HTTP_201_CREATED,
|
return Response(
|
||||||
data=TransactionProcessSerializer(transaction).data,
|
status=status.HTTP_201_CREATED,
|
||||||
)
|
data=TransactionProcessSerializer(transaction).data,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
order.is_active = False
|
||||||
|
order.save()
|
||||||
|
return Response(
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
data={"detail": str(e)},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def download_digital_asset_view(request, *args, **kwargs):
|
def download_digital_asset_view(request, *args, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,6 @@ from core.utils.seo_builders import (
|
||||||
product_schema,
|
product_schema,
|
||||||
website_schema,
|
website_schema,
|
||||||
)
|
)
|
||||||
from evibes.settings import DEBUG
|
|
||||||
from payments.serializers import TransactionProcessSerializer
|
from payments.serializers import TransactionProcessSerializer
|
||||||
|
|
||||||
logger = logging.getLogger("django")
|
logger = logging.getLogger("django")
|
||||||
|
|
@ -155,6 +154,7 @@ class EvibesViewSet(ModelViewSet):
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(**ATTRIBUTE_GROUP_SCHEMA)
|
@extend_schema_view(**ATTRIBUTE_GROUP_SCHEMA)
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class AttributeGroupViewSet(EvibesViewSet):
|
class AttributeGroupViewSet(EvibesViewSet):
|
||||||
"""
|
"""
|
||||||
Represents a viewset for managing AttributeGroup objects.
|
Represents a viewset for managing AttributeGroup objects.
|
||||||
|
|
@ -187,6 +187,7 @@ class AttributeGroupViewSet(EvibesViewSet):
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(**ATTRIBUTE_SCHEMA)
|
@extend_schema_view(**ATTRIBUTE_SCHEMA)
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class AttributeViewSet(EvibesViewSet):
|
class AttributeViewSet(EvibesViewSet):
|
||||||
"""
|
"""
|
||||||
Handles operations related to Attribute objects within the application.
|
Handles operations related to Attribute objects within the application.
|
||||||
|
|
@ -219,6 +220,7 @@ class AttributeViewSet(EvibesViewSet):
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(**ATTRIBUTE_VALUE_SCHEMA)
|
@extend_schema_view(**ATTRIBUTE_VALUE_SCHEMA)
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class AttributeValueViewSet(EvibesViewSet):
|
class AttributeValueViewSet(EvibesViewSet):
|
||||||
"""
|
"""
|
||||||
A viewset for managing AttributeValue objects.
|
A viewset for managing AttributeValue objects.
|
||||||
|
|
@ -247,6 +249,7 @@ class AttributeValueViewSet(EvibesViewSet):
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(**CATEGORY_SCHEMA)
|
@extend_schema_view(**CATEGORY_SCHEMA)
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class CategoryViewSet(EvibesViewSet):
|
class CategoryViewSet(EvibesViewSet):
|
||||||
"""
|
"""
|
||||||
Manages views for Category-related operations.
|
Manages views for Category-related operations.
|
||||||
|
|
@ -377,6 +380,7 @@ class CategoryViewSet(EvibesViewSet):
|
||||||
return Response(SeoSnapshotSerializer(payload).data)
|
return Response(SeoSnapshotSerializer(payload).data)
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class BrandViewSet(EvibesViewSet):
|
class BrandViewSet(EvibesViewSet):
|
||||||
"""
|
"""
|
||||||
Represents a viewset for managing Brand instances.
|
Represents a viewset for managing Brand instances.
|
||||||
|
|
@ -502,6 +506,7 @@ class BrandViewSet(EvibesViewSet):
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(**PRODUCT_SCHEMA)
|
@extend_schema_view(**PRODUCT_SCHEMA)
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class ProductViewSet(EvibesViewSet):
|
class ProductViewSet(EvibesViewSet):
|
||||||
"""
|
"""
|
||||||
Manages operations related to the `Product` model in the system.
|
Manages operations related to the `Product` model in the system.
|
||||||
|
|
@ -635,6 +640,7 @@ class ProductViewSet(EvibesViewSet):
|
||||||
return Response(SeoSnapshotSerializer(payload).data)
|
return Response(SeoSnapshotSerializer(payload).data)
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class VendorViewSet(EvibesViewSet):
|
class VendorViewSet(EvibesViewSet):
|
||||||
"""
|
"""
|
||||||
Represents a viewset for managing Vendor objects.
|
Represents a viewset for managing Vendor objects.
|
||||||
|
|
@ -666,6 +672,7 @@ class VendorViewSet(EvibesViewSet):
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(**FEEDBACK_SCHEMA)
|
@extend_schema_view(**FEEDBACK_SCHEMA)
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class FeedbackViewSet(EvibesViewSet):
|
class FeedbackViewSet(EvibesViewSet):
|
||||||
"""
|
"""
|
||||||
Representation of a view set handling Feedback objects.
|
Representation of a view set handling Feedback objects.
|
||||||
|
|
@ -704,6 +711,7 @@ class FeedbackViewSet(EvibesViewSet):
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(**ORDER_SCHEMA)
|
@extend_schema_view(**ORDER_SCHEMA)
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class OrderViewSet(EvibesViewSet):
|
class OrderViewSet(EvibesViewSet):
|
||||||
"""
|
"""
|
||||||
ViewSet for managing orders and related operations.
|
ViewSet for managing orders and related operations.
|
||||||
|
|
@ -798,7 +806,10 @@ class OrderViewSet(EvibesViewSet):
|
||||||
def current(self, request):
|
def current(self, request):
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
raise PermissionDenied(permission_denied_message)
|
raise PermissionDenied(permission_denied_message)
|
||||||
order = Order.objects.get(user=request.user, status="PENDING")
|
try:
|
||||||
|
order = Order.objects.get(user=request.user, status="PENDING")
|
||||||
|
except Order.DoesNotExist:
|
||||||
|
order = Order.objects.create(user=request.user)
|
||||||
return Response(
|
return Response(
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
data=OrderDetailSerializer(order).data,
|
data=OrderDetailSerializer(order).data,
|
||||||
|
|
@ -829,25 +840,30 @@ class OrderViewSet(EvibesViewSet):
|
||||||
except Order.DoesNotExist:
|
except Order.DoesNotExist:
|
||||||
name = "Order"
|
name = "Order"
|
||||||
return Response(status=status.HTTP_404_NOT_FOUND, data={"detail": _(f"{name} does not exist: {uuid}")})
|
return Response(status=status.HTTP_404_NOT_FOUND, data={"detail": _(f"{name} does not exist: {uuid}")})
|
||||||
|
except Exception as e:
|
||||||
|
return Response(status=status.HTTP_400_BAD_REQUEST, data={"detail": str(e)})
|
||||||
|
|
||||||
@action(detail=False, methods=["post"], url_path="buy_unregistered")
|
@action(detail=False, methods=["post"], url_path="buy_unregistered")
|
||||||
@method_decorator(ratelimit(key="ip", rate="5/h" if not DEBUG else "888/h"))
|
@method_decorator(ratelimit(key="ip", rate="10/h" if not settings.DEBUG else "888/h"))
|
||||||
def buy_unregistered(self, request):
|
def buy_unregistered(self, request):
|
||||||
serializer = BuyUnregisteredOrderSerializer(data=request.data)
|
serializer = BuyUnregisteredOrderSerializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
order = Order.objects.create(status="MOMENTAL")
|
order = Order.objects.create(status="MOMENTAL")
|
||||||
products = [p["product_uuid"] for p in serializer.validated_data["products"]]
|
products = [p["product_uuid"] for p in serializer.validated_data["products"]]
|
||||||
transaction = order.buy_without_registration(
|
try:
|
||||||
products=products,
|
transaction = order.buy_without_registration(
|
||||||
promocode_uuid=serializer.validated_data.get("promocode_uuid"),
|
products=products,
|
||||||
customer_name=serializer.validated_data.get("customer_name"),
|
promocode_uuid=serializer.validated_data.get("promocode_uuid"),
|
||||||
customer_email=serializer.validated_data.get("customer_email"),
|
customer_name=serializer.validated_data.get("customer_name"),
|
||||||
customer_phone_number=serializer.validated_data.get("customer_phone_number"),
|
customer_email=serializer.validated_data.get("customer_email"),
|
||||||
billing_customer_address=serializer.validated_data.get("billing_customer_address_uuid"),
|
customer_phone_number=serializer.validated_data.get("customer_phone_number"),
|
||||||
shipping_customer_address=serializer.validated_data.get("shipping_customer_address_uuid"),
|
billing_customer_address=serializer.validated_data.get("billing_customer_address_uuid"),
|
||||||
payment_method=serializer.validated_data.get("payment_method"),
|
shipping_customer_address=serializer.validated_data.get("shipping_customer_address_uuid"),
|
||||||
)
|
payment_method=serializer.validated_data.get("payment_method"),
|
||||||
return Response(status=status.HTTP_202_ACCEPTED, data=TransactionProcessSerializer(transaction).data)
|
)
|
||||||
|
return Response(status=status.HTTP_201_CREATED, data=TransactionProcessSerializer(transaction).data)
|
||||||
|
except Exception as e:
|
||||||
|
return Response(status=status.HTTP_400_BAD_REQUEST, data={"detail": str(e)})
|
||||||
|
|
||||||
@action(detail=True, methods=["post"], url_path="add_order_product")
|
@action(detail=True, methods=["post"], url_path="add_order_product")
|
||||||
def add_order_product(self, request, **kwargs):
|
def add_order_product(self, request, **kwargs):
|
||||||
|
|
@ -921,6 +937,7 @@ class OrderViewSet(EvibesViewSet):
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(**ORDER_PRODUCT_SCHEMA)
|
@extend_schema_view(**ORDER_PRODUCT_SCHEMA)
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class OrderProductViewSet(EvibesViewSet):
|
class OrderProductViewSet(EvibesViewSet):
|
||||||
"""
|
"""
|
||||||
Provides a viewset for managing OrderProduct entities.
|
Provides a viewset for managing OrderProduct entities.
|
||||||
|
|
@ -993,6 +1010,7 @@ class OrderProductViewSet(EvibesViewSet):
|
||||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class ProductImageViewSet(EvibesViewSet):
|
class ProductImageViewSet(EvibesViewSet):
|
||||||
"""
|
"""
|
||||||
Manages operations related to Product images in the application.
|
Manages operations related to Product images in the application.
|
||||||
|
|
@ -1025,6 +1043,7 @@ class ProductImageViewSet(EvibesViewSet):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class PromoCodeViewSet(EvibesViewSet):
|
class PromoCodeViewSet(EvibesViewSet):
|
||||||
"""
|
"""
|
||||||
Manages the retrieval and handling of PromoCode instances through various
|
Manages the retrieval and handling of PromoCode instances through various
|
||||||
|
|
@ -1064,6 +1083,7 @@ class PromoCodeViewSet(EvibesViewSet):
|
||||||
return qs.filter(user=user)
|
return qs.filter(user=user)
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class PromotionViewSet(EvibesViewSet):
|
class PromotionViewSet(EvibesViewSet):
|
||||||
"""
|
"""
|
||||||
Represents a view set for managing promotions.
|
Represents a view set for managing promotions.
|
||||||
|
|
@ -1083,6 +1103,7 @@ class PromotionViewSet(EvibesViewSet):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class StockViewSet(EvibesViewSet):
|
class StockViewSet(EvibesViewSet):
|
||||||
"""
|
"""
|
||||||
Handles operations related to Stock data in the system.
|
Handles operations related to Stock data in the system.
|
||||||
|
|
@ -1115,6 +1136,7 @@ class StockViewSet(EvibesViewSet):
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(**WISHLIST_SCHEMA)
|
@extend_schema_view(**WISHLIST_SCHEMA)
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class WishlistViewSet(EvibesViewSet):
|
class WishlistViewSet(EvibesViewSet):
|
||||||
"""
|
"""
|
||||||
ViewSet for managing Wishlist operations.
|
ViewSet for managing Wishlist operations.
|
||||||
|
|
@ -1254,6 +1276,7 @@ class WishlistViewSet(EvibesViewSet):
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(**ADDRESS_SCHEMA)
|
@extend_schema_view(**ADDRESS_SCHEMA)
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class AddressViewSet(EvibesViewSet):
|
class AddressViewSet(EvibesViewSet):
|
||||||
"""
|
"""
|
||||||
This class provides viewset functionality for managing `Address` objects.
|
This class provides viewset functionality for managing `Address` objects.
|
||||||
|
|
@ -1276,7 +1299,7 @@ class AddressViewSet(EvibesViewSet):
|
||||||
filterset_class = AddressFilter
|
filterset_class = AddressFilter
|
||||||
queryset = Address.objects.all()
|
queryset = Address.objects.all()
|
||||||
serializer_class = AddressSerializer
|
serializer_class = AddressSerializer
|
||||||
additional = {"create": "ALLOW"}
|
additional = {"create": "ALLOW", "retrieve": "ALLOW"}
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
if self.action == "create":
|
if self.action == "create":
|
||||||
|
|
@ -1294,6 +1317,13 @@ class AddressViewSet(EvibesViewSet):
|
||||||
|
|
||||||
return Address.objects.none()
|
return Address.objects.none()
|
||||||
|
|
||||||
|
def retrieve(self, request, **kwargs):
|
||||||
|
try:
|
||||||
|
address = Address.objects.get(uuid=kwargs.get("pk"))
|
||||||
|
return Response(status=status.HTTP_200_OK, data=self.get_serializer(address).data)
|
||||||
|
except Address.DoesNotExist:
|
||||||
|
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
def create(self, request, **kwargs):
|
def create(self, request, **kwargs):
|
||||||
create_serializer = AddressCreateSerializer(data=request.data, context={"request": request})
|
create_serializer = AddressCreateSerializer(data=request.data, context={"request": request})
|
||||||
create_serializer.is_valid(raise_exception=True)
|
create_serializer.is_valid(raise_exception=True)
|
||||||
|
|
@ -1329,6 +1359,7 @@ class AddressViewSet(EvibesViewSet):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
class ProductTagViewSet(EvibesViewSet):
|
class ProductTagViewSet(EvibesViewSet):
|
||||||
"""
|
"""
|
||||||
Handles operations related to Product Tags within the application.
|
Handles operations related to Product Tags within the application.
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ from storages.backends.ftp import FTPStorage
|
||||||
|
|
||||||
class AbsoluteFTPStorage(FTPStorage): # type: ignore
|
class AbsoluteFTPStorage(FTPStorage): # type: ignore
|
||||||
# noinspection PyProtectedMember
|
# noinspection PyProtectedMember
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
|
||||||
def _get_config(self):
|
def _get_config(self):
|
||||||
cfg = super()._get_config()
|
cfg = super()._get_config()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import graphene
|
import graphene
|
||||||
from django.db.models import QuerySet
|
|
||||||
from graphene import relay
|
from graphene import relay
|
||||||
from graphene.types.generic import GenericScalar
|
from graphene.types.generic import GenericScalar
|
||||||
from graphene_django import DjangoObjectType
|
from graphene_django import DjangoObjectType
|
||||||
|
|
@ -32,7 +31,7 @@ class BalanceType(DjangoObjectType):
|
||||||
interfaces = (relay.Node,)
|
interfaces = (relay.Node,)
|
||||||
filter_fields = ["is_active"]
|
filter_fields = ["is_active"]
|
||||||
|
|
||||||
def resolve_transaction_set(self: Balance, info) -> QuerySet["Transaction"] | list:
|
def resolve_transactions(self: Balance, info) -> list:
|
||||||
if info.context.user == self.user:
|
if info.context.user == self.user:
|
||||||
# noinspection Mypy
|
# noinspection Mypy
|
||||||
return self.transactions.all() or []
|
return self.transactions.all() or []
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,11 @@ class Transaction(NiceModel):
|
||||||
process = JSONField(verbose_name=_("processing details"), default=dict)
|
process = JSONField(verbose_name=_("processing details"), default=dict)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.balance.user.email} | {self.amount}"
|
return (
|
||||||
|
f"{self.balance.user.email} | {self.amount}"
|
||||||
|
if self.balance
|
||||||
|
else f"{self.order.attributes.get('customer_email')} | {self.amount}"
|
||||||
|
)
|
||||||
|
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
if self.amount != 0.0 and (
|
if self.amount != 0.0 and (
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ def create_balance_on_user_creation_signal(instance, created, **_kwargs):
|
||||||
def process_transaction_changes(instance, created, **_kwargs):
|
def process_transaction_changes(instance, created, **_kwargs):
|
||||||
if created:
|
if created:
|
||||||
try:
|
try:
|
||||||
|
gateway = None
|
||||||
match instance.process.get("gateway", "default"):
|
match instance.process.get("gateway", "default"):
|
||||||
case "gateway":
|
case "gateway":
|
||||||
gateway = AbstractGateway()
|
gateway = AbstractGateway()
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -20,7 +20,7 @@ msgstr "Баланс"
|
||||||
|
|
||||||
#: vibes_auth/admin.py:45
|
#: vibes_auth/admin.py:45
|
||||||
msgid "order"
|
msgid "order"
|
||||||
msgstr "Заказать"
|
msgstr "Заказ"
|
||||||
|
|
||||||
#: vibes_auth/admin.py:46 vibes_auth/graphene/object_types.py:44
|
#: vibes_auth/admin.py:46 vibes_auth/graphene/object_types.py:44
|
||||||
msgid "orders"
|
msgid "orders"
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ from vibes_auth.utils.emailing import send_reset_password_email_task
|
||||||
logger = logging.getLogger("django")
|
logger = logging.getLogger("django")
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection GrazieInspection
|
||||||
@extend_schema_view(**USER_SCHEMA)
|
@extend_schema_view(**USER_SCHEMA)
|
||||||
class UserViewSet(
|
class UserViewSet(
|
||||||
mixins.CreateModelMixin,
|
mixins.CreateModelMixin,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue