schon/engine/core/signals.py

232 lines
8 KiB
Python

import logging
from contextlib import suppress
from datetime import timedelta
from typing import Any
from django.db import IntegrityError
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.crypto import get_random_string
from django.utils.http import urlsafe_base64_decode
from django.utils.timezone import now
from sentry_sdk import capture_exception
from engine.core.crm import any_crm_integrations
from engine.core.crm.exceptions import CRMException
from engine.core.models import (
Category,
DigitalAssetDownload,
Order,
Product,
PromoCode,
Wishlist,
)
from engine.core.utils import (
generate_human_readable_id,
resolve_translations_for_elasticsearch,
)
from engine.core.utils.emailing import (
send_order_created_email,
send_order_finished_email,
send_promocode_created_email,
)
from engine.vibes_auth.models import User
from evibes.utils.misc import create_object
logger = logging.getLogger(__name__)
# noinspection PyUnusedLocal
@receiver(post_save, sender=User)
def create_order_on_user_creation_signal(
instance: User, created: bool, **kwargs: dict[Any, Any]
) -> None:
if created:
try:
Order.objects.create(user=instance, status="PENDING")
except IntegrityError:
human_readable_id = generate_human_readable_id()
while True:
if Order.objects.filter(human_readable_id=human_readable_id).exists():
human_readable_id = generate_human_readable_id()
continue
Order.objects.create(
user=instance, status="PENDING", human_readable_id=human_readable_id
)
break
# noinspection PyUnusedLocal
@receiver(post_save, sender=User)
def create_wishlist_on_user_creation_signal(
instance: User, created: bool, **kwargs: dict[Any, Any]
) -> None:
if created:
Wishlist.objects.create(user=instance)
# noinspection PyUnusedLocal
@receiver(post_save, sender=User)
def create_promocode_on_user_referring(
instance: User, created: bool, **kwargs: dict[Any, Any]
) -> None:
try:
if type(instance.attributes) is not dict:
instance.attributes = {}
instance.save()
if created and instance.attributes.get("referrer", ""):
referrer_uuid = urlsafe_base64_decode(
instance.attributes.get("referrer", "")
).decode()
referrer = User.objects.get(uuid=referrer_uuid)
code = f"WELCOME-{get_random_string(6)}"
PromoCode.objects.create(
user=referrer,
code=code if len(code) <= 20 else code[:20],
discount_percent=10,
start_time=now(),
end_time=now() + timedelta(days=30),
)
except Exception as e:
capture_exception(e)
logger.error("Could not create PromoCode: %s", str(e))
# noinspection PyUnusedLocal
@receiver(post_save, sender=Order)
def process_order_changes(instance: Order, created: bool, **kwargs: dict[Any, Any]):
if type(instance.attributes) is not dict:
instance.attributes = {}
if any_crm_integrations() and instance.status != "PENDING":
with suppress(CRMException):
instance.trigger_crm()
if not created:
if instance.status != "PENDING" and instance.user:
pending_orders = Order.objects.filter(user=instance.user, status="PENDING")
if not pending_orders.exists():
try:
Order.objects.create(user=instance.user, status="PENDING")
except IntegrityError:
human_readable_id = generate_human_readable_id()
while True:
try:
if Order.objects.filter(
human_readable_id=human_readable_id
).exists():
human_readable_id = generate_human_readable_id()
continue
Order.objects.create(
user=instance,
status="PENDING",
human_readable_id=human_readable_id,
)
break
except IntegrityError:
continue
if instance.status in ["CREATED", "PAYMENT"]:
if not instance.is_whole_digital:
send_order_created_email.delay(str(instance.uuid))
for order_product in instance.order_products.filter(
status="DELIVERING", product__is_digital=True
):
if not order_product.product:
continue
stocks_qs = order_product.product.stocks.filter(
digital_asset__isnull=False
).exclude(digital_asset="")
stock = stocks_qs.first()
has_file = False
if stock:
f = stock.digital_asset
has_file = bool(getattr(f, "name", "")) and f.storage.exists(f.name)
if has_file:
order_product.status = "FINISHED"
DigitalAssetDownload.objects.get_or_create(
order_product=order_product
)
order_product.order.user.payments_balance.amount -= (
order_product.buy_price
) # type: ignore [union-attr, operator]
order_product.order.user.payments_balance.save() # type: ignore [union-attr]
order_product.save()
continue
order_product.save()
try:
vendor_name = (
order_product.product.stocks.filter(
price=order_product.buy_price
)
.first()
.vendor.name.lower() # type: ignore [union-attr, attr-defined, misc]
)
vendor = create_object(
f"core.vendors.{vendor_name}", f"{vendor_name.title()}Vendor"
)
vendor.buy_order_product(order_product) # type: ignore [attr-defined]
except Exception as e:
order_product.add_error(
f"Failed to buy {order_product.uuid}. Reason: {e}..."
)
else:
instance.finalize()
if (
instance.order_products.filter(status="FAILED").count()
== instance.order_products.count()
):
instance.status = "FAILED"
instance.save()
if instance.status == "FINISHED" and not instance.attributes.get(
"system_email_sent", False
):
instance.attributes["system_email_sent"] = True
instance.save()
send_order_finished_email.delay(str(instance.uuid))
# noinspection PyUnusedLocal
@receiver(post_save, sender=Product)
def update_product_name_lang(
instance: Product, created: bool, **kwargs: dict[Any, Any]
) -> None:
if created:
pass
resolve_translations_for_elasticsearch(instance, "name")
resolve_translations_for_elasticsearch(instance, "description")
# noinspection PyUnusedLocal
@receiver(post_save, sender=Category)
def update_category_name_lang(
instance: Category, created: bool, **kwargs: dict[Any, Any]
) -> None:
if created:
pass
resolve_translations_for_elasticsearch(instance, "name")
resolve_translations_for_elasticsearch(instance, "description")
# noinspection PyUnusedLocal
@receiver(post_save, sender=PromoCode)
def send_promocode_creation_email(
instance: PromoCode, created: bool, **kwargs: dict[Any, Any]
) -> None:
if created:
send_promocode_created_email.delay(str(instance.uuid))