Merge branch 'main' into storefront-nuxt
This commit is contained in:
commit
d7f5ed4141
9 changed files with 46 additions and 45 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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))}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"):
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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()]
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue