Merge branch 'main' into storefront-nuxt

This commit is contained in:
Egor Pavlovich Gorbunov 2025-11-11 15:38:29 +03:00
commit d7f5ed4141
9 changed files with 46 additions and 45 deletions

View file

@ -1,7 +1,7 @@
import logging
import requests
from constance import config
from django.conf import settings
from django.core.cache import cache
from django.db import transaction
@ -51,7 +51,7 @@ class AmoCRM:
payload = {
"client_id": self.client_id,
"client_secret": self.client_secret,
"redirect_uri": f"https://api.{config.BASE_DOMAIN}/",
"redirect_uri": f"https://api.{settings.BASE_DOMAIN}/",
}
if self.refresh_token:
payload["grant_type"] = "refresh_token"

View file

@ -3,6 +3,7 @@ from contextlib import suppress
from typing import Any
from constance import config
from django.conf import settings
from django.core.cache import cache
from django.db.models import Max, Min, QuerySet
from django.utils.translation import gettext_lazy as _
@ -136,7 +137,7 @@ class BrandType(DjangoObjectType): # type: ignore [misc]
def resolve_seo_meta(self: Brand, info) -> dict[str, str | list[Any] | dict[str, str] | None]:
lang = graphene_current_lang()
base = f"https://{config.BASE_DOMAIN}"
base = f"https://{settings.BASE_DOMAIN}"
canonical = f"{base}/{lang}/brand/{self.slug}"
title = f"{self.name} | {config.PROJECT_NAME}"
description = (self.description or "")[:180]
@ -234,7 +235,7 @@ class CategoryType(DjangoObjectType): # type: ignore [misc]
return result
def resolve_image(self: Category, info) -> str:
return info.context.build_absolute_uri(self.image.url) if self.image else ""
return self.image_url
def resolve_markup_percent(self: Category, info) -> float:
if info.context.user.has_perm("core.view_category"):
@ -262,7 +263,7 @@ class CategoryType(DjangoObjectType): # type: ignore [misc]
def resolve_seo_meta(self: Category, info):
lang = graphene_current_lang()
base = f"https://{config.BASE_DOMAIN}"
base = f"https://{settings.BASE_DOMAIN}"
canonical = f"{base}/{lang}/catalog/{self.slug}"
title = f"{self.name} | {config.PROJECT_NAME}"
description = (self.description or "")[:180]
@ -465,8 +466,8 @@ class ProductImageType(DjangoObjectType): # type: ignore [misc]
filter_fields = ["uuid"]
description = _("product's images")
def resolve_image(self: ProductImage, info):
return info.context.build_absolute_uri(self.image.url) if self.image else ""
def resolve_image(self: ProductImage, _info):
return self.image_url
class ProductType(DjangoObjectType): # type: ignore [misc]
@ -534,7 +535,7 @@ class ProductType(DjangoObjectType): # type: ignore [misc]
def resolve_seo_meta(self: Product, info):
lang = graphene_current_lang()
base = f"https://{config.BASE_DOMAIN}"
base = f"https://{settings.BASE_DOMAIN}"
canonical = f"{base}/{lang}/product/{self.slug}"
title = f"{self.name} | {config.PROJECT_NAME}"
description = (self.description or "")[:180]

View file

@ -420,6 +420,13 @@ class Category(ExportModelOperationsMixin("category"), NiceModel, MPTTModel): #
return list(by_attr.values()) # type: ignore [arg-type]
@cached_property
def image_url(self) -> str:
with suppress(ValueError):
url = str(self.image.url)
url = url if "http" in url else f"https://api.{settings.BASE_DOMAIN}{url}"
return ""
class Meta:
verbose_name = _("category")
verbose_name_plural = _("categories")
@ -843,6 +850,13 @@ class ProductImage(ExportModelOperationsMixin("product_image"), NiceModel): # t
def __str__(self) -> str:
return self.alt
@cached_property
def image_url(self) -> str:
with suppress(ValueError):
url = str(self.image.url)
url = url if "http" in url else f"https://api.{settings.BASE_DOMAIN}{url}"
return ""
class Meta:
ordering = ("priority",)
verbose_name = _("product image")
@ -1953,5 +1967,5 @@ class DigitalAssetDownload(ExportModelOperationsMixin("attribute_group"), NiceMo
@property
def url(self):
return (
f"https://api.{config.BASE_DOMAIN}/download/{urlsafe_base64_encode(force_bytes(self.order_product.uuid))}"
f"https://api.{settings.BASE_DOMAIN}/download/{urlsafe_base64_encode(force_bytes(self.order_product.uuid))}"
)

View file

@ -56,7 +56,6 @@ class CategoryDetailListSerializer(ListSerializer):
class CategoryDetailSerializer(ModelSerializer):
children = SerializerMethodField()
image = SerializerMethodField()
filterable_attributes = SerializerMethodField()
class Meta:
@ -75,11 +74,6 @@ class CategoryDetailSerializer(ModelSerializer):
"modified",
]
def get_image(self, obj: Category) -> str | None:
with suppress(ValueError):
return obj.image.url
return None
def get_filterable_attributes(self, obj: Category) -> list[FilterableAttribute]:
return obj.filterable_attributes
@ -161,8 +155,6 @@ class ProductTagDetailSerializer(ModelSerializer):
class ProductImageDetailSerializer(ModelSerializer):
image = SerializerMethodField()
class Meta:
model = ProductImage
fields = [
@ -174,9 +166,6 @@ class ProductImageDetailSerializer(ModelSerializer):
"modified",
]
def get_image(self, obj: ProductImage) -> str:
return obj.image.url or ""
class AttributeDetailSerializer(ModelSerializer):
categories = CategoryDetailSerializer(many=True)

View file

@ -42,7 +42,6 @@ class AttributeGroupSimpleSerializer(ModelSerializer): # type: ignore [type-arg
class CategorySimpleSerializer(ModelSerializer): # type: ignore [type-arg]
children = SerializerMethodField()
image = SerializerMethodField()
class Meta:
model = Category
@ -54,11 +53,6 @@ class CategorySimpleSerializer(ModelSerializer): # type: ignore [type-arg]
"children",
]
def get_image(self, obj: Category) -> str | None:
with suppress(ValueError):
return str(obj.image.url)
return None
def get_children(self, obj: Category) -> dict[str, Any]:
request = self.context.get("request")
if request is not None and request.user.has_perm("view_category"):

View file

@ -1,5 +1,5 @@
from constance import config
from django.conf import settings
def get_flag_by_language(language: str) -> str:
return f"https://api.{config.BASE_DOMAIN}/static/flags/{language}.png"
return f"https://api.{settings.BASE_DOMAIN}/static/flags/{language}.png"

View file

@ -9,8 +9,8 @@ def org_schema():
"@context": "https://schema.org",
"@type": "Organization",
"name": config.COMPANY_NAME,
"url": f"https://{config.BASE_DOMAIN}/",
"logo": f"https://{config.BASE_DOMAIN}/static/logo.png",
"url": f"https://{settings.BASE_DOMAIN}/",
"logo": f"https://{settings.BASE_DOMAIN}/static/logo.png",
}
@ -19,10 +19,10 @@ def website_schema():
"@context": "https://schema.org",
"@type": "WebSite",
"name": config.PROJECT_NAME,
"url": f"https://{config.BASE_DOMAIN}/",
"url": f"https://{settings.BASE_DOMAIN}/",
"potentialAction": {
"@type": "SearchAction",
"target": f"https://{config.BASE_DOMAIN}/search?q={{query}}",
"target": f"https://{settings.BASE_DOMAIN}/search?q={{query}}",
"query-input": "required name=query",
},
}
@ -56,7 +56,7 @@ def product_schema(product, images, rating=None):
"priceCurrency": settings.CURRENCY_CODE,
"availability": "https://schema.org/InStock" if stock.quantity > 0 else "https://schema.org/OutOfStock",
"sku": stock.sku,
"url": f"https://{config.BASE_DOMAIN}/product/{product.slug}",
"url": f"https://{settings.BASE_DOMAIN}/product/{product.slug}",
}
)
data = {

View file

@ -271,7 +271,7 @@ class CategoryViewSet(EvibesViewSet):
title = f"{category.name} | {config.PROJECT_NAME}"
description = (category.description or "")[:180]
canonical = f"https://{config.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/catalog/{category.slug}"
canonical = f"https://{settings.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/catalog/{category.slug}"
og_image = request.build_absolute_uri(category.image.url) if getattr(category, "image", None) else ""
og = {
@ -283,10 +283,10 @@ class CategoryViewSet(EvibesViewSet):
}
tw = {"card": "summary_large_image", "title": title, "description": description}
crumbs = [("Home", f"https://{config.BASE_DOMAIN}/")]
crumbs = [("Home", f"https://{settings.BASE_DOMAIN}/")]
if category.get_ancestors().exists():
for c in category.get_ancestors():
crumbs.append((c.name, f"https://{config.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/catalog/{c.slug}"))
crumbs.append((c.name, f"https://{settings.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/catalog/{c.slug}"))
crumbs.append((category.name, canonical))
json_ld = [org_schema(), website_schema(), breadcrumb_schema(crumbs), category_schema(category, canonical)]
@ -303,7 +303,7 @@ class CategoryViewSet(EvibesViewSet):
.distinct()[:24]
)
for p in qs:
product_urls.append(f"https://{config.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/product/{p.slug}")
product_urls.append(f"https://{settings.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/product/{p.slug}")
if product_urls:
json_ld.append(item_list_schema(product_urls))
@ -388,7 +388,7 @@ class BrandViewSet(EvibesViewSet):
title = f"{brand.name} | {config.PROJECT_NAME}"
description = (brand.description or "")[:180]
canonical = f"https://{config.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/brand/{brand.slug}"
canonical = f"https://{settings.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/brand/{brand.slug}"
logo_url = (
request.build_absolute_uri(brand.big_logo.url)
@ -408,7 +408,7 @@ class BrandViewSet(EvibesViewSet):
tw = {"card": "summary_large_image", "title": title, "description": description}
crumbs = [
("Home", f"https://{config.BASE_DOMAIN}/"),
("Home", f"https://{settings.BASE_DOMAIN}/"),
(brand.name, canonical),
]
@ -528,7 +528,7 @@ class ProductViewSet(EvibesViewSet):
rating = {"value": p.rating, "count": p.feedbacks_count}
title = f"{p.name} | {config.PROJECT_NAME}"
description = (p.description or "")[:180]
canonical = f"https://{config.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/product/{p.slug}"
canonical = f"https://{settings.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/product/{p.slug}"
og = {
"title": title,
"description": description,
@ -538,10 +538,10 @@ class ProductViewSet(EvibesViewSet):
}
tw = {"card": "summary_large_image", "title": title, "description": description}
crumbs = [("Home", f"https://{config.BASE_DOMAIN}/")]
crumbs = [("Home", f"https://{settings.BASE_DOMAIN}/")]
if p.category:
for c in p.category.get_ancestors(include_self=True):
crumbs.append((c.name, f"https://{config.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/catalog/{c.slug}"))
crumbs.append((c.name, f"https://{settings.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/catalog/{c.slug}"))
crumbs.append((p.name, canonical))
json_ld = [org_schema(), website_schema()]

View file

@ -7,13 +7,16 @@ from typing import Any
from django.core.exceptions import ImproperlyConfigured
EVIBES_VERSION = "2025.4"
RELEASE_DATE = datetime(2025, 9, 13)
RELEASE_DATE = datetime(2025, 11, 9)
BASE_DIR = Path(__file__).resolve().parent.parent.parent
INITIALIZED = (BASE_DIR / ".initialized").exists()
SECRET_KEY = getenv("SECRET_KEY", "SUPER_SECRET_KEY")
DEBUG = bool(int(getenv("DEBUG", "1")))
BASE_DOMAIN: str = getenv("EVIBES_BASE_DOMAIN", "localhost")
ALLOWED_HOSTS: set[str] = {
"app",
"worker",
@ -294,10 +297,10 @@ TIME_ZONE: str = getenv("TIME_ZONE", "Europe/London")
WHITENOISE_MANIFEST_STRICT: bool = False
STATIC_URL: str = "/static/"
STATIC_URL: str = f"https://api.{BASE_DOMAIN}/static/" if INITIALIZED else "static/"
STATIC_ROOT: Path = BASE_DIR / "static"
MEDIA_URL: str = "/media/"
MEDIA_URL: str = f"https://api.{BASE_DOMAIN}/media/" if INITIALIZED else "media/"
MEDIA_ROOT: Path = BASE_DIR / "media"
AUTH_USER_MODEL: str = "vibes_auth.User"