RUFF
This commit is contained in:
parent
1219067721
commit
d86a6ed3c8
40 changed files with 357 additions and 359 deletions
|
|
@ -18,15 +18,19 @@ class PostAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
readonly_fields = ("preview_html",)
|
readonly_fields = ("preview_html",)
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {
|
(
|
||||||
"fields": (
|
None,
|
||||||
"author", "title",
|
{
|
||||||
"content",
|
"fields": (
|
||||||
"preview_html",
|
"author",
|
||||||
"file",
|
"title",
|
||||||
"tags",
|
"content",
|
||||||
)
|
"preview_html",
|
||||||
}),
|
"file",
|
||||||
|
"tags",
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def preview_html(self, obj):
|
def preview_html(self, obj):
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,12 @@ class PostDocument(ActiveOnlyMixin, Document):
|
||||||
|
|
||||||
class Index:
|
class Index:
|
||||||
name = "posts"
|
name = "posts"
|
||||||
settings = {"number_of_shards": 1, "number_of_replicas": 0,
|
settings = {
|
||||||
"analysis": COMMON_ANALYSIS, "index": {"max_ngram_diff": 18}}
|
"number_of_shards": 1,
|
||||||
|
"number_of_replicas": 0,
|
||||||
|
"analysis": COMMON_ANALYSIS,
|
||||||
|
"index": {"max_ngram_diff": 18},
|
||||||
|
}
|
||||||
|
|
||||||
class Django:
|
class Django:
|
||||||
model = Post
|
model = Post
|
||||||
|
|
@ -28,4 +32,5 @@ class PostDocument(ActiveOnlyMixin, Document):
|
||||||
def prepare_title(self, instance):
|
def prepare_title(self, instance):
|
||||||
return getattr(instance, "title", "") or ""
|
return getattr(instance, "title", "") or ""
|
||||||
|
|
||||||
|
|
||||||
registry.register_document(PostDocument)
|
registry.register_document(PostDocument)
|
||||||
|
|
|
||||||
|
|
@ -10,51 +10,49 @@ from core.abstract import NiceModel
|
||||||
class Post(NiceModel):
|
class Post(NiceModel):
|
||||||
is_publicly_visible = True
|
is_publicly_visible = True
|
||||||
|
|
||||||
author = ForeignKey(
|
author = ForeignKey(to="vibes_auth.User", on_delete=CASCADE, blank=False, null=False, related_name="posts")
|
||||||
to="vibes_auth.User", on_delete=CASCADE, blank=False, null=False, related_name="posts"
|
title = CharField(
|
||||||
|
unique=True, max_length=128, blank=False, null=False, help_text=_("post title"), verbose_name=_("title")
|
||||||
|
)
|
||||||
|
content = MarkdownField(
|
||||||
|
"content",
|
||||||
|
extensions=[
|
||||||
|
TocExtension(toc_depth=3),
|
||||||
|
"pymdownx.arithmatex",
|
||||||
|
"pymdownx.b64",
|
||||||
|
"pymdownx.betterem",
|
||||||
|
"pymdownx.blocks.admonition",
|
||||||
|
"pymdownx.blocks.caption",
|
||||||
|
"pymdownx.blocks.definition",
|
||||||
|
"pymdownx.blocks.details",
|
||||||
|
"pymdownx.blocks.html",
|
||||||
|
"pymdownx.blocks.tab",
|
||||||
|
"pymdownx.caret",
|
||||||
|
"pymdownx.critic",
|
||||||
|
"pymdownx.emoji",
|
||||||
|
"pymdownx.escapeall",
|
||||||
|
"pymdownx.extra",
|
||||||
|
"pymdownx.fancylists",
|
||||||
|
"pymdownx.highlight",
|
||||||
|
"pymdownx.inlinehilite",
|
||||||
|
"pymdownx.keys",
|
||||||
|
"pymdownx.magiclink",
|
||||||
|
"pymdownx.mark",
|
||||||
|
"pymdownx.pathconverter",
|
||||||
|
"pymdownx.progressbar",
|
||||||
|
"pymdownx.saneheaders",
|
||||||
|
"pymdownx.smartsymbols",
|
||||||
|
"pymdownx.snippets",
|
||||||
|
"pymdownx.striphtml",
|
||||||
|
"pymdownx.superfences",
|
||||||
|
"pymdownx.tasklist",
|
||||||
|
"pymdownx.tilde",
|
||||||
|
],
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
)
|
)
|
||||||
title = CharField(unique=True, max_length=128, blank=False, null=False, help_text=_("post title"),
|
|
||||||
verbose_name=_("title"))
|
|
||||||
content = MarkdownField("content",
|
|
||||||
extensions=[
|
|
||||||
TocExtension(toc_depth=3),
|
|
||||||
"pymdownx.arithmatex",
|
|
||||||
"pymdownx.b64",
|
|
||||||
"pymdownx.betterem",
|
|
||||||
"pymdownx.blocks.admonition",
|
|
||||||
"pymdownx.blocks.caption",
|
|
||||||
"pymdownx.blocks.definition",
|
|
||||||
"pymdownx.blocks.details",
|
|
||||||
"pymdownx.blocks.html",
|
|
||||||
"pymdownx.blocks.tab",
|
|
||||||
"pymdownx.caret",
|
|
||||||
"pymdownx.critic",
|
|
||||||
"pymdownx.emoji",
|
|
||||||
"pymdownx.escapeall",
|
|
||||||
"pymdownx.extra",
|
|
||||||
"pymdownx.fancylists",
|
|
||||||
"pymdownx.highlight",
|
|
||||||
"pymdownx.inlinehilite",
|
|
||||||
"pymdownx.keys",
|
|
||||||
"pymdownx.magiclink",
|
|
||||||
"pymdownx.mark",
|
|
||||||
"pymdownx.pathconverter",
|
|
||||||
"pymdownx.progressbar",
|
|
||||||
"pymdownx.saneheaders",
|
|
||||||
"pymdownx.smartsymbols",
|
|
||||||
"pymdownx.snippets",
|
|
||||||
"pymdownx.striphtml",
|
|
||||||
"pymdownx.superfences",
|
|
||||||
"pymdownx.tasklist",
|
|
||||||
"pymdownx.tilde"
|
|
||||||
], blank=True, null=True)
|
|
||||||
file = FileField(upload_to="posts/", blank=True, null=True)
|
file = FileField(upload_to="posts/", blank=True, null=True)
|
||||||
slug = AutoSlugField(
|
slug = AutoSlugField(populate_from="title", allow_unicode=True, unique=True, editable=False)
|
||||||
populate_from='title',
|
|
||||||
allow_unicode=True,
|
|
||||||
unique=True,
|
|
||||||
editable=False
|
|
||||||
)
|
|
||||||
tags = ManyToManyField(to="blog.PostTag", blank=True, related_name="posts")
|
tags = ManyToManyField(to="blog.PostTag", blank=True, related_name="posts")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,12 @@ from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
class MarkdownEditorWidget(forms.Textarea):
|
class MarkdownEditorWidget(forms.Textarea):
|
||||||
class Media:
|
class Media:
|
||||||
css = {
|
css = {"all": ("https://cdnjs.cloudflare.com/ajax/libs/easymde/2.14.0/easymde.min.css",)}
|
||||||
'all': (
|
js = ("https://cdnjs.cloudflare.com/ajax/libs/easymde/2.14.0/easymde.min.js",)
|
||||||
'https://cdnjs.cloudflare.com/ajax/libs/easymde/2.14.0/easymde.min.css',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
js = (
|
|
||||||
'https://cdnjs.cloudflare.com/ajax/libs/easymde/2.14.0/easymde.min.js',
|
|
||||||
)
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None, renderer=None):
|
def render(self, name, value, attrs=None, renderer=None):
|
||||||
textarea_html = super().render(name, value, attrs, renderer)
|
textarea_html = super().render(name, value, attrs, renderer)
|
||||||
textarea_id = attrs.get('id', f'id_{name}')
|
textarea_id = attrs.get("id", f"id_{name}")
|
||||||
init_js = f"""
|
init_js = f"""
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {{
|
document.addEventListener('DOMContentLoaded', function() {{
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ class AttributeValueInline(TabularInline):
|
||||||
is_navtab = True
|
is_navtab = True
|
||||||
verbose_name = _("attribute value")
|
verbose_name = _("attribute value")
|
||||||
verbose_name_plural = _("attribute values")
|
verbose_name_plural = _("attribute values")
|
||||||
autocomplete_fields = ['attribute']
|
autocomplete_fields = ["attribute"]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(AttributeGroup)
|
@admin.register(AttributeGroup)
|
||||||
|
|
|
||||||
|
|
@ -43,15 +43,16 @@ core_router.register(r"promotions", PromotionViewSet, basename="promotions")
|
||||||
core_router.register(r"addresses", AddressViewSet, basename="addresses")
|
core_router.register(r"addresses", AddressViewSet, basename="addresses")
|
||||||
|
|
||||||
sitemaps = {
|
sitemaps = {
|
||||||
'products': ProductSitemap,
|
"products": ProductSitemap,
|
||||||
'categories': CategorySitemap,
|
"categories": CategorySitemap,
|
||||||
'brands': BrandSitemap,
|
"brands": BrandSitemap,
|
||||||
}
|
}
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("core/", include(core_router.urls)),
|
path("core/", include(core_router.urls)),
|
||||||
path("sitemap.xml", sitemap_index, {"sitemaps": sitemaps, "sitemap_url_name": "sitemap-detail"},
|
path(
|
||||||
name="sitemap-index"),
|
"sitemap.xml", sitemap_index, {"sitemaps": sitemaps, "sitemap_url_name": "sitemap-detail"}, name="sitemap-index"
|
||||||
|
),
|
||||||
path("sitemap-<section>.xml", sitemap_detail, {"sitemaps": sitemaps}, name="sitemap-detail"),
|
path("sitemap-<section>.xml", sitemap_detail, {"sitemaps": sitemaps}, name="sitemap-detail"),
|
||||||
path("sitemap-<section>-<int:page>.xml", sitemap_detail, {"sitemaps": sitemaps}, name="sitemap-detail"),
|
path("sitemap-<section>-<int:page>.xml", sitemap_detail, {"sitemaps": sitemaps}, name="sitemap-detail"),
|
||||||
path("download/<str:order_product_uuid>/", download_digital_asset_view, name="download_digital_asset"),
|
path("download/<str:order_product_uuid>/", download_digital_asset_view, name="download_digital_asset"),
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ from rest_framework.fields import CharField, DictField, JSONField, ListField
|
||||||
|
|
||||||
from core.docs.drf import error
|
from core.docs.drf import error
|
||||||
from core.serializers import (
|
from core.serializers import (
|
||||||
|
BuyAsBusinessOrderSerializer,
|
||||||
CacheOperatorSerializer,
|
CacheOperatorSerializer,
|
||||||
ContactUsSerializer,
|
ContactUsSerializer,
|
||||||
LanguageSerializer,
|
LanguageSerializer,
|
||||||
BuyAsBusinessOrderSerializer,
|
|
||||||
)
|
)
|
||||||
from payments.serializers import TransactionProcessSerializer
|
from payments.serializers import TransactionProcessSerializer
|
||||||
|
|
||||||
|
|
@ -16,7 +16,8 @@ CACHE_SCHEMA = {
|
||||||
"post": extend_schema(
|
"post": extend_schema(
|
||||||
summary=_("cache I/O"),
|
summary=_("cache I/O"),
|
||||||
description=_(
|
description=_(
|
||||||
"apply only a key to read permitted data from cache.\napply key, data and timeout with authentication to write data to cache." # noqa: E501
|
"apply only a key to read permitted data from cache.\n"
|
||||||
|
"apply key, data and timeout with authentication to write data to cache."
|
||||||
),
|
),
|
||||||
request=CacheOperatorSerializer,
|
request=CacheOperatorSerializer,
|
||||||
responses={
|
responses={
|
||||||
|
|
@ -97,4 +98,4 @@ BUY_AS_BUSINESS_SCHEMA = {
|
||||||
_("purchase an order as a business, using the provided `products` with `product_uuid` and `attributes`.")
|
_("purchase an order as a business, using the provided `products` with `product_uuid` and `attributes`.")
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -183,9 +183,7 @@ ORDER_SCHEMA = {
|
||||||
),
|
),
|
||||||
"buy_unregistered": extend_schema(
|
"buy_unregistered": extend_schema(
|
||||||
summary=_("purchase an order without account creation"),
|
summary=_("purchase an order without account creation"),
|
||||||
description=_(
|
description=_("finalizes the order purchase for a non-registered user."),
|
||||||
"finalizes the order purchase for a non-registered user."
|
|
||||||
),
|
|
||||||
request=BuyUnregisteredOrderSerializer,
|
request=BuyUnregisteredOrderSerializer,
|
||||||
responses={
|
responses={
|
||||||
status.HTTP_202_ACCEPTED: TransactionProcessSerializer,
|
status.HTTP_202_ACCEPTED: TransactionProcessSerializer,
|
||||||
|
|
@ -270,7 +268,7 @@ ATTRIBUTES_DESC = _(
|
||||||
"`true`/`false` for booleans, integers, floats; otherwise treated as string. \n"
|
"`true`/`false` for booleans, integers, floats; otherwise treated as string. \n"
|
||||||
"• **Base64**: prefix with `b64-` to URL-safe base64-encode the raw value. \n"
|
"• **Base64**: prefix with `b64-` to URL-safe base64-encode the raw value. \n"
|
||||||
"Examples: \n"
|
"Examples: \n"
|
||||||
"`color=exact-red`, `size=gt-10`, `features=in-[\"wifi\",\"bluetooth\"]`, \n"
|
'`color=exact-red`, `size=gt-10`, `features=in-["wifi","bluetooth"]`, \n'
|
||||||
"`b64-description=icontains-aGVhdC1jb2xk`"
|
"`b64-description=icontains-aGVhdC1jb2xk`"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -490,18 +488,20 @@ ADDRESS_SCHEMA = {
|
||||||
),
|
),
|
||||||
"autocomplete": extend_schema(
|
"autocomplete": extend_schema(
|
||||||
summary=_("autocomplete address suggestions"),
|
summary=_("autocomplete address suggestions"),
|
||||||
parameters=[OpenApiParameter(
|
parameters=[
|
||||||
name="q",
|
OpenApiParameter(
|
||||||
location=OpenApiParameter.QUERY,
|
name="q",
|
||||||
description=_("raw data query string, please append with data from geo-IP endpoint"),
|
location=OpenApiParameter.QUERY,
|
||||||
type=str,
|
description=_("raw data query string, please append with data from geo-IP endpoint"),
|
||||||
),
|
type=str,
|
||||||
|
),
|
||||||
OpenApiParameter(
|
OpenApiParameter(
|
||||||
name="limit",
|
name="limit",
|
||||||
location=OpenApiParameter.QUERY,
|
location=OpenApiParameter.QUERY,
|
||||||
description=_("limit the results amount, 1 < limit < 10, default: 5"),
|
description=_("limit the results amount, 1 < limit < 10, default: 5"),
|
||||||
type=int,
|
type=int,
|
||||||
)],
|
),
|
||||||
|
],
|
||||||
responses={
|
responses={
|
||||||
status.HTTP_200_OK: AddressSuggestionSerializer(many=True),
|
status.HTTP_200_OK: AddressSuggestionSerializer(many=True),
|
||||||
**BASE_ERRORS,
|
**BASE_ERRORS,
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ def process_query(query: str = ""):
|
||||||
Q(
|
Q(
|
||||||
"multi_match",
|
"multi_match",
|
||||||
query=query,
|
query=query,
|
||||||
fields=[f for f in SMART_FIELDS if f.endswith('.auto')],
|
fields=[f for f in SMART_FIELDS if f.endswith(".auto")],
|
||||||
type="bool_prefix",
|
type="bool_prefix",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -76,11 +76,13 @@ def process_query(query: str = ""):
|
||||||
|
|
||||||
idx = hit.meta.index
|
idx = hit.meta.index
|
||||||
if idx in results:
|
if idx in results:
|
||||||
results[idx].append({
|
results[idx].append(
|
||||||
"uuid": str(obj_uuid),
|
{
|
||||||
"name": obj_name,
|
"uuid": str(obj_uuid),
|
||||||
"slug": obj_slug,
|
"name": obj_name,
|
||||||
})
|
"slug": obj_slug,
|
||||||
|
}
|
||||||
|
)
|
||||||
return results
|
return results
|
||||||
except NotFoundError:
|
except NotFoundError:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
|
||||||
|
|
@ -83,8 +83,12 @@ class BrandDocument(ActiveOnlyMixin, Document):
|
||||||
|
|
||||||
class Index:
|
class Index:
|
||||||
name = "brands"
|
name = "brands"
|
||||||
settings = {"number_of_shards": 1, "number_of_replicas": 0,
|
settings = {
|
||||||
"analysis": COMMON_ANALYSIS, "index": {"max_ngram_diff": 18}}
|
"number_of_shards": 1,
|
||||||
|
"number_of_replicas": 0,
|
||||||
|
"analysis": COMMON_ANALYSIS,
|
||||||
|
"index": {"max_ngram_diff": 18},
|
||||||
|
}
|
||||||
|
|
||||||
class Django:
|
class Django:
|
||||||
model = Brand
|
model = Brand
|
||||||
|
|
@ -94,4 +98,4 @@ class BrandDocument(ActiveOnlyMixin, Document):
|
||||||
return getattr(instance, "name", "") or ""
|
return getattr(instance, "name", "") or ""
|
||||||
|
|
||||||
|
|
||||||
registry.register_document(BrandDocument)
|
registry.register_document(BrandDocument)
|
||||||
|
|
|
||||||
|
|
@ -179,13 +179,21 @@ class BuyOrder(BaseMutation):
|
||||||
transaction = Field(TransactionType, required=False)
|
transaction = Field(TransactionType, required=False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mutate(_parent, info, order_uuid=None, order_hr_id=None, force_balance=False, force_payment=False,
|
def mutate(
|
||||||
promocode_uuid=None, shipping_address=None, billing_address=None):
|
_parent,
|
||||||
|
info,
|
||||||
|
order_uuid=None,
|
||||||
|
order_hr_id=None,
|
||||||
|
force_balance=False,
|
||||||
|
force_payment=False,
|
||||||
|
promocode_uuid=None,
|
||||||
|
shipping_address=None,
|
||||||
|
billing_address=None,
|
||||||
|
):
|
||||||
if not any([order_uuid, order_hr_id]) or all([order_uuid, order_hr_id]):
|
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"))
|
raise BadRequest(_("please provide either order_uuid or order_hr_id - mutually exclusive"))
|
||||||
user = info.context.user
|
user = info.context.user
|
||||||
try:
|
try:
|
||||||
|
|
||||||
order = None
|
order = None
|
||||||
|
|
||||||
if order_uuid:
|
if order_uuid:
|
||||||
|
|
@ -194,8 +202,11 @@ class BuyOrder(BaseMutation):
|
||||||
order = Order.objects.get(user=user, human_readable_id=order_hr_id)
|
order = Order.objects.get(user=user, human_readable_id=order_hr_id)
|
||||||
|
|
||||||
instance = order.buy(
|
instance = order.buy(
|
||||||
force_balance=force_balance, force_payment=force_payment, promocode_uuid=promocode_uuid,
|
force_balance=force_balance,
|
||||||
shipping_address=shipping_address, billing_address=billing_address
|
force_payment=force_payment,
|
||||||
|
promocode_uuid=promocode_uuid,
|
||||||
|
shipping_address=shipping_address,
|
||||||
|
billing_address=billing_address,
|
||||||
)
|
)
|
||||||
|
|
||||||
match str(type(instance)):
|
match str(type(instance)):
|
||||||
|
|
@ -228,18 +239,31 @@ class BuyUnregisteredOrder(BaseMutation):
|
||||||
transaction = Field(TransactionType, required=False)
|
transaction = Field(TransactionType, required=False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mutate(_parent, info, products, customer_name, customer_email, customer_phone, customer_billing_address,
|
def mutate(
|
||||||
payment_method, customer_shipping_address=None, promocode_uuid=None, is_business=False):
|
_parent,
|
||||||
|
info,
|
||||||
|
products,
|
||||||
|
customer_name,
|
||||||
|
customer_email,
|
||||||
|
customer_phone,
|
||||||
|
customer_billing_address,
|
||||||
|
payment_method,
|
||||||
|
customer_shipping_address=None,
|
||||||
|
promocode_uuid=None,
|
||||||
|
is_business=False,
|
||||||
|
):
|
||||||
order = Order.objects.create(status="MOMENTAL")
|
order = Order.objects.create(status="MOMENTAL")
|
||||||
transaction = order.buy_without_registration(products=products,
|
transaction = order.buy_without_registration(
|
||||||
promocode_uuid=promocode_uuid,
|
products=products,
|
||||||
customer_name=customer_name,
|
promocode_uuid=promocode_uuid,
|
||||||
customer_email=customer_email,
|
customer_name=customer_name,
|
||||||
customer_phone=customer_phone,
|
customer_email=customer_email,
|
||||||
billing_customer_address=customer_billing_address,
|
customer_phone=customer_phone,
|
||||||
shipping_customer_address=customer_shipping_address,
|
billing_customer_address=customer_billing_address,
|
||||||
payment_method=payment_method,
|
shipping_customer_address=customer_shipping_address,
|
||||||
is_business=is_business)
|
payment_method=payment_method,
|
||||||
|
is_business=is_business,
|
||||||
|
)
|
||||||
return BuyUnregisteredOrder(transaction=transaction)
|
return BuyUnregisteredOrder(transaction=transaction)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -458,10 +482,7 @@ class DeleteProduct(BaseMutation):
|
||||||
|
|
||||||
class CreateAddress(BaseMutation):
|
class CreateAddress(BaseMutation):
|
||||||
class Arguments:
|
class Arguments:
|
||||||
raw_data = String(
|
raw_data = String(required=True, description=_("original address string provided by the user"))
|
||||||
required=True,
|
|
||||||
description=_("original address string provided by the user")
|
|
||||||
)
|
|
||||||
|
|
||||||
address = Field(AddressType)
|
address = Field(AddressType)
|
||||||
|
|
||||||
|
|
@ -469,10 +490,7 @@ class CreateAddress(BaseMutation):
|
||||||
def mutate(_parent, info, raw_data):
|
def mutate(_parent, info, raw_data):
|
||||||
user = info.context.user if info.context.user.is_authenticated else None
|
user = info.context.user if info.context.user.is_authenticated else None
|
||||||
|
|
||||||
address = Address.objects.create(
|
address = Address.objects.create(raw_data=raw_data, user=user)
|
||||||
raw_data=raw_data,
|
|
||||||
user=user
|
|
||||||
)
|
|
||||||
return CreateAddress(address=address)
|
return CreateAddress(address=address)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,9 +131,7 @@ class Query(ObjectType):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_products(_parent, info, **kwargs):
|
def resolve_products(_parent, info, **kwargs):
|
||||||
if info.context.user.is_authenticated and kwargs.get("uuid"):
|
if info.context.user.is_authenticated and kwargs.get("uuid"):
|
||||||
product = Product.objects.get(
|
product = Product.objects.get(uuid=kwargs["uuid"])
|
||||||
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 (
|
return (
|
||||||
|
|
@ -141,7 +139,9 @@ class Query(ObjectType):
|
||||||
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(
|
||||||
is_active=True, brand__is_active=True, category__is_active=True, stocks__isnull=False
|
is_active=True, brand__is_active=True, category__is_active=True, stocks__isnull=False
|
||||||
).select_related("brand", "category").prefetch_related("images", "stocks")
|
)
|
||||||
|
.select_related("brand", "category")
|
||||||
|
.prefetch_related("images", "stocks")
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ class Command(BaseCommand):
|
||||||
# 1. Clean up duplicate Stock entries per product and vendor:
|
# 1. Clean up duplicate Stock entries per product and vendor:
|
||||||
# Group stocks by (product, vendor)
|
# Group stocks by (product, vendor)
|
||||||
stocks_by_group = defaultdict(list)
|
stocks_by_group = defaultdict(list)
|
||||||
for stock in Stock.objects.all().order_by('modified'):
|
for stock in Stock.objects.all().order_by("modified"):
|
||||||
key = (stock.product_id, stock.vendor)
|
key = (stock.product_id, stock.vendor)
|
||||||
stocks_by_group[key].append(stock)
|
stocks_by_group[key].append(stock)
|
||||||
|
|
||||||
|
|
@ -37,13 +37,11 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
if stock_deletions:
|
if stock_deletions:
|
||||||
Stock.objects.filter(id__in=stock_deletions).delete()
|
Stock.objects.filter(id__in=stock_deletions).delete()
|
||||||
self.stdout.write(
|
self.stdout.write(self.style.SUCCESS(f"Deleted {len(stock_deletions)} duplicate stock entries."))
|
||||||
self.style.SUCCESS(f"Deleted {len(stock_deletions)} duplicate stock entries.")
|
|
||||||
)
|
|
||||||
|
|
||||||
# 2. Clean up duplicate Category entries based on name (case-insensitive)
|
# 2. Clean up duplicate Category entries based on name (case-insensitive)
|
||||||
category_groups = defaultdict(list)
|
category_groups = defaultdict(list)
|
||||||
for cat in Category.objects.all().order_by('modified'):
|
for cat in Category.objects.all().order_by("modified"):
|
||||||
key = cat.name.lower()
|
key = cat.name.lower()
|
||||||
category_groups[key].append(cat)
|
category_groups[key].append(cat)
|
||||||
|
|
||||||
|
|
@ -80,19 +78,13 @@ class Command(BaseCommand):
|
||||||
count_inactive = inactive_products.count()
|
count_inactive = inactive_products.count()
|
||||||
if count_inactive:
|
if count_inactive:
|
||||||
inactive_products.update(is_active=False)
|
inactive_products.update(is_active=False)
|
||||||
self.stdout.write(self.style.SUCCESS(
|
self.stdout.write(self.style.SUCCESS(f"Set {count_inactive} product(s) as inactive due to missing stocks."))
|
||||||
f"Set {count_inactive} product(s) as inactive due to missing stocks."
|
|
||||||
))
|
|
||||||
|
|
||||||
# 4. Delete stocks without an associated product.
|
# 4. Delete stocks without an associated product.
|
||||||
orphan_stocks = Stock.objects.filter(product__isnull=True)
|
orphan_stocks = Stock.objects.filter(product__isnull=True)
|
||||||
orphan_count = orphan_stocks.count()
|
orphan_count = orphan_stocks.count()
|
||||||
if orphan_count:
|
if orphan_count:
|
||||||
orphan_stocks.delete()
|
orphan_stocks.delete()
|
||||||
self.stdout.write(
|
self.stdout.write(self.style.SUCCESS(f"Deleted {orphan_count} stock(s) without an associated product."))
|
||||||
self.style.SUCCESS(f"Deleted {orphan_count} stock(s) without an associated product.")
|
|
||||||
)
|
|
||||||
|
|
||||||
self.stdout.write(self.style.SUCCESS(
|
self.stdout.write(self.style.SUCCESS("Started fetching products task in worker container without errors!"))
|
||||||
"Started fetching products task in worker container without errors!"
|
|
||||||
))
|
|
||||||
|
|
|
||||||
|
|
@ -77,11 +77,7 @@ def load_po_sanitized(path: str) -> polib.POFile | None:
|
||||||
parts = text.split("\n\n", 1)
|
parts = text.split("\n\n", 1)
|
||||||
header = parts[0]
|
header = parts[0]
|
||||||
rest = parts[1] if len(parts) > 1 else ""
|
rest = parts[1] if len(parts) > 1 else ""
|
||||||
rest_clean = re.sub(
|
rest_clean = re.sub(r"^msgid \"\"\s*\nmsgstr \"\"\s*\n?", "", rest, flags=re.MULTILINE)
|
||||||
r"^msgid \"\"\s*\nmsgstr \"\"\s*\n?", "",
|
|
||||||
rest,
|
|
||||||
flags=re.MULTILINE
|
|
||||||
)
|
|
||||||
sanitized = header + "\n\n" + rest_clean
|
sanitized = header + "\n\n" + rest_clean
|
||||||
tmp = NamedTemporaryFile(mode="w+", delete=False, suffix=".po", encoding="utf-8") # noqa: SIM115
|
tmp = NamedTemporaryFile(mode="w+", delete=False, suffix=".po", encoding="utf-8") # noqa: SIM115
|
||||||
try:
|
try:
|
||||||
|
|
@ -101,35 +97,37 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-l", "--language",
|
"-l",
|
||||||
|
"--language",
|
||||||
dest="target_languages",
|
dest="target_languages",
|
||||||
action="append",
|
action="append",
|
||||||
required=True,
|
required=True,
|
||||||
metavar="LANG",
|
metavar="LANG",
|
||||||
help="Locale code for translation, e.g. de-DE, fr-FR."
|
help="Locale code for translation, e.g. de-DE, fr-FR.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-a", "--app",
|
"-a",
|
||||||
|
"--app",
|
||||||
dest="target_apps",
|
dest="target_apps",
|
||||||
action="append",
|
action="append",
|
||||||
required=True,
|
required=True,
|
||||||
metavar="APP",
|
metavar="APP",
|
||||||
help="App label for translation, e.g. core, payments."
|
help="App label for translation, e.g. core, payments.",
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options) -> None:
|
def handle(self, *args, **options) -> None:
|
||||||
target_langs: list[str] = options['target_languages']
|
target_langs: list[str] = options["target_languages"]
|
||||||
target_apps: set[str] = set(options['target_apps'])
|
target_apps: set[str] = set(options["target_apps"])
|
||||||
auth_key = os.environ.get('DEEPL_AUTH_KEY')
|
auth_key = os.environ.get("DEEPL_AUTH_KEY")
|
||||||
if not auth_key:
|
if not auth_key:
|
||||||
raise CommandError('DEEPL_AUTH_KEY not set')
|
raise CommandError("DEEPL_AUTH_KEY not set")
|
||||||
|
|
||||||
for target_lang in target_langs:
|
for target_lang in target_langs:
|
||||||
api_code = DEEPL_TARGET_LANGUAGES_MAPPING.get(target_lang)
|
api_code = DEEPL_TARGET_LANGUAGES_MAPPING.get(target_lang)
|
||||||
if not api_code:
|
if not api_code:
|
||||||
self.stdout.write(self.style.WARNING(f"Unknown language '{target_lang}'"))
|
self.stdout.write(self.style.WARNING(f"Unknown language '{target_lang}'"))
|
||||||
continue
|
continue
|
||||||
if api_code == 'unsupported':
|
if api_code == "unsupported":
|
||||||
self.stdout.write(self.style.WARNING(f"Unsupported language '{target_lang}'"))
|
self.stdout.write(self.style.WARNING(f"Unsupported language '{target_lang}'"))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -139,7 +137,7 @@ class Command(BaseCommand):
|
||||||
if app_conf.label not in target_apps:
|
if app_conf.label not in target_apps:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
en_path = os.path.join(app_conf.path, 'locale', 'en_GB', 'LC_MESSAGES', 'django.po')
|
en_path = os.path.join(app_conf.path, "locale", "en_GB", "LC_MESSAGES", "django.po")
|
||||||
if not os.path.isfile(en_path):
|
if not os.path.isfile(en_path):
|
||||||
self.stdout.write(self.style.WARNING(f"• {app_conf.label}: no en_GB PO"))
|
self.stdout.write(self.style.WARNING(f"• {app_conf.label}: no en_GB PO"))
|
||||||
continue
|
continue
|
||||||
|
|
@ -162,9 +160,9 @@ class Command(BaseCommand):
|
||||||
entries = [e for e in en_po if e.msgid and not e.obsolete]
|
entries = [e for e in en_po if e.msgid and not e.obsolete]
|
||||||
source_map = {e.msgid: e.msgstr for e in entries}
|
source_map = {e.msgid: e.msgstr for e in entries}
|
||||||
|
|
||||||
tgt_dir = os.path.join(app_conf.path, 'locale', target_lang.replace('-', '_'), 'LC_MESSAGES')
|
tgt_dir = os.path.join(app_conf.path, "locale", target_lang.replace("-", "_"), "LC_MESSAGES")
|
||||||
os.makedirs(tgt_dir, exist_ok=True)
|
os.makedirs(tgt_dir, exist_ok=True)
|
||||||
tgt_path = os.path.join(tgt_dir, 'django.po')
|
tgt_path = os.path.join(tgt_dir, "django.po")
|
||||||
|
|
||||||
old_tgt = None
|
old_tgt = None
|
||||||
if os.path.exists(tgt_path):
|
if os.path.exists(tgt_path):
|
||||||
|
|
@ -176,19 +174,21 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
new_po = polib.POFile()
|
new_po = polib.POFile()
|
||||||
new_po.metadata = en_po.metadata.copy()
|
new_po.metadata = en_po.metadata.copy()
|
||||||
new_po.metadata['Language'] = target_lang
|
new_po.metadata["Language"] = target_lang
|
||||||
|
|
||||||
for e in entries:
|
for e in entries:
|
||||||
prev = old_tgt.find(e.msgid) if old_tgt else None
|
prev = old_tgt.find(e.msgid) if old_tgt else None
|
||||||
new_po.append(polib.POEntry(
|
new_po.append(
|
||||||
msgid=e.msgid,
|
polib.POEntry(
|
||||||
msgstr=prev.msgstr if prev and prev.msgstr else '',
|
msgid=e.msgid,
|
||||||
msgctxt=e.msgctxt,
|
msgstr=prev.msgstr if prev and prev.msgstr else "",
|
||||||
comment=e.comment,
|
msgctxt=e.msgctxt,
|
||||||
tcomment=e.tcomment,
|
comment=e.comment,
|
||||||
occurrences=e.occurrences,
|
tcomment=e.tcomment,
|
||||||
flags=e.flags,
|
occurrences=e.occurrences,
|
||||||
))
|
flags=e.flags,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
to_trans = [e for e in new_po if not e.msgstr]
|
to_trans = [e for e in new_po if not e.msgstr]
|
||||||
if not to_trans:
|
if not to_trans:
|
||||||
|
|
@ -204,22 +204,22 @@ class Command(BaseCommand):
|
||||||
maps.append(p_map)
|
maps.append(p_map)
|
||||||
|
|
||||||
data = [
|
data = [
|
||||||
('auth_key', auth_key),
|
("auth_key", auth_key),
|
||||||
('target_lang', api_code),
|
("target_lang", api_code),
|
||||||
] + [('text', t) for t in protected]
|
] + [("text", t) for t in protected]
|
||||||
resp = requests.post('https://api.deepl.com/v2/translate', data=data)
|
resp = requests.post("https://api.deepl.com/v2/translate", data=data)
|
||||||
try:
|
try:
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
result = resp.json()
|
result = resp.json()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise CommandError(f"DeepL error: {exc} – {resp.text}")
|
raise CommandError(f"DeepL error: {exc} – {resp.text}")
|
||||||
|
|
||||||
trans = result.get('translations', [])
|
trans = result.get("translations", [])
|
||||||
if len(trans) != len(to_trans):
|
if len(trans) != len(to_trans):
|
||||||
raise CommandError(f"Got {len(trans)} translations, expected {len(to_trans)}")
|
raise CommandError(f"Got {len(trans)} translations, expected {len(to_trans)}")
|
||||||
|
|
||||||
for e, obj, pmap in zip(to_trans, trans, maps):
|
for e, obj, pmap in zip(to_trans, trans, maps):
|
||||||
e.msgstr = deplaceholderize(obj['text'], pmap)
|
e.msgstr = deplaceholderize(obj["text"], pmap)
|
||||||
|
|
||||||
new_po.save(tgt_path)
|
new_po.save(tgt_path)
|
||||||
self.stdout.write(self.style.SUCCESS(f"Saved {tgt_path}"))
|
self.stdout.write(self.style.SUCCESS(f"Saved {tgt_path}"))
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import logging
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
from core.models import Product
|
from core.models import Product
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,11 @@ from django.core.management.base import BaseCommand, CommandError
|
||||||
|
|
||||||
# Patterns to identify placeholders
|
# Patterns to identify placeholders
|
||||||
PLACEHOLDER_REGEXES = [
|
PLACEHOLDER_REGEXES = [
|
||||||
re.compile(r"\{[^}]+\}"), # {name}, {type(instance)!s}, etc.
|
re.compile(r"\{[^}]+\}"), # {name}, {type(instance)!s}, etc.
|
||||||
re.compile(r"%\([^)]+\)[sd]"), # %(verbose_name)s, %(count)d
|
re.compile(r"%\([^)]+\)[sd]"), # %(verbose_name)s, %(count)d
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def extract_placeholders(text: str) -> set[str]:
|
def extract_placeholders(text: str) -> set[str]:
|
||||||
"""
|
"""
|
||||||
Extract all placeholders from given text.
|
Extract all placeholders from given text.
|
||||||
|
|
@ -33,7 +34,7 @@ def load_po_sanitized(path: str) -> polib.POFile:
|
||||||
except Exception:
|
except Exception:
|
||||||
# read raw text
|
# read raw text
|
||||||
try:
|
try:
|
||||||
with open(path, encoding='utf-8') as f:
|
with open(path, encoding="utf-8") as f:
|
||||||
text = f.read()
|
text = f.read()
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise CommandError(f"{path}: cannot read file ({e})")
|
raise CommandError(f"{path}: cannot read file ({e})")
|
||||||
|
|
@ -41,10 +42,10 @@ def load_po_sanitized(path: str) -> polib.POFile:
|
||||||
text = re.sub(r"^#,(?!\s)", "#, ", text, flags=re.MULTILINE)
|
text = re.sub(r"^#,(?!\s)", "#, ", text, flags=re.MULTILINE)
|
||||||
parts = text.split("\n\n", 1)
|
parts = text.split("\n\n", 1)
|
||||||
header = parts[0]
|
header = parts[0]
|
||||||
rest = parts[1] if len(parts) > 1 else ''
|
rest = parts[1] if len(parts) > 1 else ""
|
||||||
rest = re.sub(r"^msgid \"\"\s*\nmsgstr \"\"\s*\n?", '', rest, flags=re.MULTILINE)
|
rest = re.sub(r"^msgid \"\"\s*\nmsgstr \"\"\s*\n?", "", rest, flags=re.MULTILINE)
|
||||||
sanitized = header + "\n\n" + rest
|
sanitized = header + "\n\n" + rest
|
||||||
tmp = NamedTemporaryFile(mode='w+', delete=False, suffix='.po', encoding='utf-8') # noqa: SIM115
|
tmp = NamedTemporaryFile(mode="w+", delete=False, suffix=".po", encoding="utf-8") # noqa: SIM115
|
||||||
try:
|
try:
|
||||||
tmp.write(sanitized)
|
tmp.write(sanitized)
|
||||||
tmp.flush()
|
tmp.flush()
|
||||||
|
|
@ -56,40 +57,42 @@ def load_po_sanitized(path: str) -> polib.POFile:
|
||||||
with contextlib.suppress(OSError):
|
with contextlib.suppress(OSError):
|
||||||
os.unlink(tmp.name)
|
os.unlink(tmp.name)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = (
|
help = "Scan target-language .po files and report any placeholder mismatches, grouped by app."
|
||||||
"Scan target-language .po files and report any placeholder mismatches, grouped by app."
|
|
||||||
)
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-l', '--language',
|
"-l",
|
||||||
dest='target_languages',
|
"--language",
|
||||||
action='append',
|
dest="target_languages",
|
||||||
|
action="append",
|
||||||
required=True,
|
required=True,
|
||||||
metavar='LANG',
|
metavar="LANG",
|
||||||
help='Locale code(s) to scan, e.g. de-DE, fr-FR'
|
help="Locale code(s) to scan, e.g. de-DE, fr-FR",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-a', '--app',
|
"-a",
|
||||||
dest='target_apps',
|
"--app",
|
||||||
action='append',
|
dest="target_apps",
|
||||||
|
action="append",
|
||||||
required=True,
|
required=True,
|
||||||
metavar='APP',
|
metavar="APP",
|
||||||
help='App label(s) to scan, e.g. core, payments'
|
help="App label(s) to scan, e.g. core, payments",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-p', '--path',
|
"-p",
|
||||||
dest='root_path',
|
"--path",
|
||||||
|
dest="root_path",
|
||||||
required=False,
|
required=False,
|
||||||
metavar='ROOT_PATH',
|
metavar="ROOT_PATH",
|
||||||
help='Root path prefix to adjust file links'
|
help="Root path prefix to adjust file links",
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options) -> None:
|
def handle(self, *args, **options) -> None:
|
||||||
langs: list[str] = options['target_languages']
|
langs: list[str] = options["target_languages"]
|
||||||
apps_to_scan: set[str] = set(options['target_apps'])
|
apps_to_scan: set[str] = set(options["target_apps"])
|
||||||
root_path: str = options.get('root_path') or '/app/'
|
root_path: str = options.get("root_path") or "/app/"
|
||||||
|
|
||||||
for app_conf in apps.get_app_configs():
|
for app_conf in apps.get_app_configs():
|
||||||
if app_conf.label not in apps_to_scan:
|
if app_conf.label not in apps_to_scan:
|
||||||
|
|
@ -99,10 +102,8 @@ class Command(BaseCommand):
|
||||||
app_issues: list[str] = []
|
app_issues: list[str] = []
|
||||||
|
|
||||||
for lang in langs:
|
for lang in langs:
|
||||||
loc = lang.replace('-', '_')
|
loc = lang.replace("-", "_")
|
||||||
po_path = os.path.join(
|
po_path = os.path.join(app_conf.path, "locale", loc, "LC_MESSAGES", "django.po")
|
||||||
app_conf.path, 'locale', loc, 'LC_MESSAGES', 'django.po'
|
|
||||||
)
|
|
||||||
if not os.path.exists(po_path):
|
if not os.path.exists(po_path):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -121,13 +122,11 @@ class Command(BaseCommand):
|
||||||
missing = src_ph - dst_ph
|
missing = src_ph - dst_ph
|
||||||
extra = dst_ph - src_ph
|
extra = dst_ph - src_ph
|
||||||
if missing or extra:
|
if missing or extra:
|
||||||
line_no = entry.linenum or '?'
|
line_no = entry.linenum or "?"
|
||||||
display = po_path.replace('/app/', root_path)
|
display = po_path.replace("/app/", root_path)
|
||||||
if '\\' in root_path:
|
if "\\" in root_path:
|
||||||
display = display.replace('/', '\\')
|
display = display.replace("/", "\\")
|
||||||
lang_issues.append(
|
lang_issues.append(f" {display}:{line_no}: missing={sorted(missing)} extra={sorted(extra)}")
|
||||||
f" {display}:{line_no}: missing={sorted(missing)} extra={sorted(extra)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if lang_issues:
|
if lang_issues:
|
||||||
# Header for language with issues
|
# Header for language with issues
|
||||||
|
|
@ -142,9 +141,7 @@ class Command(BaseCommand):
|
||||||
self.stdout.write("")
|
self.stdout.write("")
|
||||||
else:
|
else:
|
||||||
# No issues in any language for this app
|
# No issues in any language for this app
|
||||||
self.stdout.write(
|
self.stdout.write(self.style.SUCCESS(f"App {app_conf.label} has no placeholder issues."))
|
||||||
self.style.SUCCESS(f"App {app_conf.label} has no placeholder issues.")
|
|
||||||
)
|
|
||||||
self.stdout.write("")
|
self.stdout.write("")
|
||||||
|
|
||||||
self.stdout.write(self.style.SUCCESS("Done scanning."))
|
self.stdout.write(self.style.SUCCESS("Done scanning."))
|
||||||
|
|
|
||||||
|
|
@ -37,18 +37,16 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-t", "--target", required=True,
|
"-t",
|
||||||
help=(
|
"--target",
|
||||||
"Dotted path to the field to translate, "
|
required=True,
|
||||||
"e.g. core.models.Product.description"
|
help=("Dotted path to the field to translate, e.g. core.models.Product.description"),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-l", "--language", required=True,
|
"-l",
|
||||||
help=(
|
"--language",
|
||||||
"Modeltranslation language code to translate into, "
|
required=True,
|
||||||
"e.g. de-de, fr-fr, zh-hans"
|
help=("Modeltranslation language code to translate into, e.g. de-de, fr-fr, zh-hans"),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
|
@ -87,8 +85,7 @@ class Command(BaseCommand):
|
||||||
if not auth_key:
|
if not auth_key:
|
||||||
raise CommandError("Environment variable DEEPL_AUTH_KEY is not set.")
|
raise CommandError("Environment variable DEEPL_AUTH_KEY is not set.")
|
||||||
|
|
||||||
qs = model.objects.exclude(**{f"{field_name}__isnull": True}) \
|
qs = model.objects.exclude(**{f"{field_name}__isnull": True}).exclude(**{f"{field_name}": ""})
|
||||||
.exclude(**{f"{field_name}": ""})
|
|
||||||
total = qs.count()
|
total = qs.count()
|
||||||
if total == 0:
|
if total == 0:
|
||||||
self.stdout.write("No instances with non-empty source field found.")
|
self.stdout.write("No instances with non-empty source field found.")
|
||||||
|
|
@ -113,9 +110,7 @@ class Command(BaseCommand):
|
||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
self.stderr.write(
|
self.stderr.write(f"DeepL API error for {obj.pk}: {resp.status_code} {resp.text}")
|
||||||
f"DeepL API error for {obj.pk}: {resp.status_code} {resp.text}"
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ class AddressManager(models.Manager):
|
||||||
|
|
||||||
# Query Nominatim
|
# Query Nominatim
|
||||||
params = {
|
params = {
|
||||||
'format': 'json',
|
"format": "json",
|
||||||
'addressdetails': 1,
|
"addressdetails": 1,
|
||||||
'q': raw_data,
|
"q": raw_data,
|
||||||
}
|
}
|
||||||
resp = requests.get(config.NOMINATIM_URL, params=params)
|
resp = requests.get(config.NOMINATIM_URL, params=params)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
|
|
@ -30,18 +30,18 @@ class AddressManager(models.Manager):
|
||||||
data = results[0]
|
data = results[0]
|
||||||
|
|
||||||
# Parse address components
|
# Parse address components
|
||||||
addr = data.get('address', {})
|
addr = data.get("address", {})
|
||||||
street = addr.get('road') or addr.get('pedestrian') or ''
|
street = addr.get("road") or addr.get("pedestrian") or ""
|
||||||
district = addr.get('city_district') or addr.get('suburb') or ''
|
district = addr.get("city_district") or addr.get("suburb") or ""
|
||||||
city = addr.get('city') or addr.get('town') or addr.get('village') or ''
|
city = addr.get("city") or addr.get("town") or addr.get("village") or ""
|
||||||
region = addr.get('state') or addr.get('region') or ''
|
region = addr.get("state") or addr.get("region") or ""
|
||||||
postal_code = addr.get('postcode') or ''
|
postal_code = addr.get("postcode") or ""
|
||||||
country = addr.get('country') or ''
|
country = addr.get("country") or ""
|
||||||
|
|
||||||
# Parse location
|
# Parse location
|
||||||
try:
|
try:
|
||||||
lat = float(data.get('lat'))
|
lat = float(data.get("lat"))
|
||||||
lon = float(data.get('lon'))
|
lon = float(data.get("lon"))
|
||||||
location = Point(lon, lat, srid=4326)
|
location = Point(lon, lat, srid=4326)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
location = None
|
location = None
|
||||||
|
|
@ -57,5 +57,5 @@ class AddressManager(models.Manager):
|
||||||
country=country,
|
country=country,
|
||||||
location=location,
|
location=location,
|
||||||
api_response=data,
|
api_response=data,
|
||||||
**kwargs
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -635,7 +635,6 @@ class Order(NiceModel):
|
||||||
return promocode.use(self)
|
return promocode.use(self)
|
||||||
|
|
||||||
def apply_addresses(self, billing_address_uuid, shipping_address_uuid):
|
def apply_addresses(self, billing_address_uuid, shipping_address_uuid):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not any([shipping_address_uuid, billing_address_uuid]):
|
if not any([shipping_address_uuid, billing_address_uuid]):
|
||||||
if self.is_whole_digital:
|
if self.is_whole_digital:
|
||||||
|
|
@ -663,8 +662,13 @@ class Order(NiceModel):
|
||||||
raise Http404(_("address does not exist"))
|
raise Http404(_("address does not exist"))
|
||||||
|
|
||||||
def buy(
|
def buy(
|
||||||
self, force_balance: bool = False, force_payment: bool = False, promocode_uuid: str | None = None,
|
self,
|
||||||
billing_address: str | None = None, shipping_address: str | None = None, **kwargs
|
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:
|
) -> Self | Transaction | None:
|
||||||
if config.DISABLED_COMMERCE:
|
if config.DISABLED_COMMERCE:
|
||||||
raise DisabledCommerceError(_("you can not buy at this moment, please try again in a few minutes"))
|
raise DisabledCommerceError(_("you can not buy at this moment, please try again in a few minutes"))
|
||||||
|
|
@ -1022,7 +1026,6 @@ class PromoCode(NiceModel):
|
||||||
return "percent"
|
return "percent"
|
||||||
|
|
||||||
def use(self, order: Order) -> float:
|
def use(self, order: Order) -> float:
|
||||||
|
|
||||||
if self.used_on:
|
if self.used_on:
|
||||||
raise ValueError(_("promocode already used"))
|
raise ValueError(_("promocode already used"))
|
||||||
|
|
||||||
|
|
@ -1251,31 +1254,14 @@ class Address(NiceModel):
|
||||||
country = CharField(_("country"), max_length=40, null=True) # noqa: DJ001
|
country = CharField(_("country"), max_length=40, null=True) # noqa: DJ001
|
||||||
|
|
||||||
location = PointField(
|
location = PointField(
|
||||||
geography=True,
|
geography=True, srid=4326, null=True, blank=True, help_text=_("geolocation point: (longitude, latitude)")
|
||||||
srid=4326,
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
help_text=_("geolocation point: (longitude, latitude)")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
raw_data = JSONField(
|
raw_data = JSONField(blank=True, null=True, help_text=_("full JSON response from geocoder for this address"))
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
help_text=_("full JSON response from geocoder for this address")
|
|
||||||
)
|
|
||||||
|
|
||||||
api_response = JSONField(
|
api_response = JSONField(blank=True, null=True, help_text=_("stored JSON response from the geocoding service"))
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
help_text=_("stored JSON response from the geocoding service")
|
|
||||||
)
|
|
||||||
|
|
||||||
user = ForeignKey(
|
user = ForeignKey(to="vibes_auth.User", on_delete=CASCADE, blank=True, null=True)
|
||||||
to="vibes_auth.User",
|
|
||||||
on_delete=CASCADE,
|
|
||||||
blank=True,
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
|
|
||||||
objects = AddressManager()
|
objects = AddressManager()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,29 +25,34 @@ class EvibesPermission(permissions.BasePermission):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ACTION_PERM_MAP = {
|
ACTION_PERM_MAP = {
|
||||||
'retrieve': 'view',
|
"retrieve": "view",
|
||||||
'list': 'view',
|
"list": "view",
|
||||||
'create': 'add',
|
"create": "add",
|
||||||
'update': 'change',
|
"update": "change",
|
||||||
'partial_update': 'change',
|
"partial_update": "change",
|
||||||
'destroy': 'delete',
|
"destroy": "delete",
|
||||||
}
|
}
|
||||||
|
|
||||||
USER_SCOPED_ACTIONS = {
|
USER_SCOPED_ACTIONS = {
|
||||||
'buy', 'buy_unregistered', 'current',
|
"buy",
|
||||||
'add_order_product', 'remove_order_product',
|
"buy_unregistered",
|
||||||
'add_wishlist_product', 'remove_wishlist_product',
|
"current",
|
||||||
'bulk_add_wishlist_products', 'bulk_remove_wishlist_products',
|
"add_order_product",
|
||||||
'autocomplete',
|
"remove_order_product",
|
||||||
|
"add_wishlist_product",
|
||||||
|
"remove_wishlist_product",
|
||||||
|
"bulk_add_wishlist_products",
|
||||||
|
"bulk_remove_wishlist_products",
|
||||||
|
"autocomplete",
|
||||||
}
|
}
|
||||||
|
|
||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
action = getattr(view, 'action', None)
|
action = getattr(view, "action", None)
|
||||||
model = view.queryset.model
|
model = view.queryset.model
|
||||||
app_label = model._meta.app_label
|
app_label = model._meta.app_label
|
||||||
model_name = model._meta.model_name
|
model_name = model._meta.model_name
|
||||||
|
|
||||||
if action == 'create' and view.additional.get('create') == 'ALLOW':
|
if action == "create" and view.additional.get("create") == "ALLOW":
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if action in self.USER_SCOPED_ACTIONS:
|
if action in self.USER_SCOPED_ACTIONS:
|
||||||
|
|
@ -59,7 +64,7 @@ class EvibesPermission(permissions.BasePermission):
|
||||||
if request.user.has_perm(f"{app_label}.{codename}"):
|
if request.user.has_perm(f"{app_label}.{codename}"):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return bool(action in ('list', 'retrieve') and getattr(model, 'is_publicly_visible', False))
|
return bool(action in ("list", "retrieve") and getattr(model, "is_publicly_visible", False))
|
||||||
|
|
||||||
def has_queryset_permission(self, request, view, queryset):
|
def has_queryset_permission(self, request, view, queryset):
|
||||||
"""
|
"""
|
||||||
|
|
@ -73,7 +78,7 @@ class EvibesPermission(permissions.BasePermission):
|
||||||
if view.action in self.USER_SCOPED_ACTIONS:
|
if view.action in self.USER_SCOPED_ACTIONS:
|
||||||
return queryset.filter(user=request.user)
|
return queryset.filter(user=request.user)
|
||||||
|
|
||||||
if view.action in ('list', 'retrieve'):
|
if view.action in ("list", "retrieve"):
|
||||||
if request.user.has_perm(f"{app_label}.view_{model_name}"):
|
if request.user.has_perm(f"{app_label}.view_{model_name}"):
|
||||||
if request.user.is_staff:
|
if request.user.is_staff:
|
||||||
return queryset
|
return queryset
|
||||||
|
|
@ -81,10 +86,15 @@ class EvibesPermission(permissions.BasePermission):
|
||||||
return queryset.none()
|
return queryset.none()
|
||||||
|
|
||||||
base = queryset.filter(is_active=True)
|
base = queryset.filter(is_active=True)
|
||||||
if view.action in ('update', 'partial_update'):
|
match view.action:
|
||||||
if request.user.has_perm(f"{app_label}.change_{model_name}"):
|
case "update":
|
||||||
return base
|
if request.user.has_perm(f"{app_label}.change_{model_name}"):
|
||||||
if view.action == 'destroy':
|
return base
|
||||||
if request.user.has_perm(f"{app_label}.delete_{model_name}"):
|
case "partial_update":
|
||||||
return base
|
if request.user.has_perm(f"{app_label}.change_{model_name}"):
|
||||||
|
return base
|
||||||
|
case "destroy":
|
||||||
|
if request.user.has_perm(f"{app_label}.delete_{model_name}"):
|
||||||
|
return base
|
||||||
|
|
||||||
return queryset.none()
|
return queryset.none()
|
||||||
|
|
|
||||||
|
|
@ -106,15 +106,8 @@ class BuyAsBusinessOrderSerializer(Serializer):
|
||||||
|
|
||||||
|
|
||||||
class AddressAutocompleteInputSerializer(Serializer):
|
class AddressAutocompleteInputSerializer(Serializer):
|
||||||
q = CharField(
|
q = CharField(required=True)
|
||||||
required=True
|
limit = IntegerField(required=False, min_value=1, max_value=10, default=5)
|
||||||
)
|
|
||||||
limit = IntegerField(
|
|
||||||
required=False,
|
|
||||||
min_value=1,
|
|
||||||
max_value=10,
|
|
||||||
default=5
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AddressSuggestionSerializer(Serializer):
|
class AddressSuggestionSerializer(Serializer):
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,15 @@ class ProductSitemap(Sitemap):
|
||||||
limit = 40000
|
limit = 40000
|
||||||
|
|
||||||
def items(self):
|
def items(self):
|
||||||
return Product.objects.filter(
|
return (
|
||||||
is_active=True,
|
Product.objects.filter(
|
||||||
brand__is_active=True,
|
is_active=True,
|
||||||
category__is_active=True,
|
brand__is_active=True,
|
||||||
).only("uuid", "name", "modified", "slug").order_by("-modified")
|
category__is_active=True,
|
||||||
|
)
|
||||||
|
.only("uuid", "name", "modified", "slug")
|
||||||
|
.order_by("-modified")
|
||||||
|
)
|
||||||
|
|
||||||
def lastmod(self, obj):
|
def lastmod(self, obj):
|
||||||
return obj.modified
|
return obj.modified
|
||||||
|
|
|
||||||
|
|
@ -209,8 +209,9 @@ def process_promotions() -> tuple[bool, str]:
|
||||||
product = eligible_products.order_by("?").first()
|
product = eligible_products.order_by("?").first()
|
||||||
selected_products.append(product)
|
selected_products.append(product)
|
||||||
|
|
||||||
promotion = Promotion.objects.update_or_create(name=promotion_name,
|
promotion = Promotion.objects.update_or_create(
|
||||||
defaults={"discount_percent": discount_percent, "is_active": True})
|
name=promotion_name, defaults={"discount_percent": discount_percent, "is_active": True}
|
||||||
|
)
|
||||||
|
|
||||||
promotion.products.set(selected_products)
|
promotion.products.set(selected_products)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ register = template.Library()
|
||||||
def attributes_length(value, arg):
|
def attributes_length(value, arg):
|
||||||
"""Returns True if the value length is more than the argument."""
|
"""Returns True if the value length is more than the argument."""
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
count = int()
|
count = 0
|
||||||
for attribute, _value in value.items():
|
for attribute, _value in value.items():
|
||||||
if attribute.endswith("_system"):
|
if attribute.endswith("_system"):
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ class AttributeValueOptions(TranslationOptions):
|
||||||
|
|
||||||
@register(Brand)
|
@register(Brand)
|
||||||
class BrandTranslationOptions(TranslationOptions):
|
class BrandTranslationOptions(TranslationOptions):
|
||||||
fields = ("description", )
|
fields = ("description",)
|
||||||
|
|
||||||
|
|
||||||
@register(Category)
|
@register(Category)
|
||||||
|
|
|
||||||
|
|
@ -128,8 +128,10 @@ def resolve_translations_for_elasticsearch(instance, field_name):
|
||||||
if not field:
|
if not field:
|
||||||
setattr(instance, f"{field_name}_{LANGUAGE_CODE}", filled_field)
|
setattr(instance, f"{field_name}_{LANGUAGE_CODE}", filled_field)
|
||||||
|
|
||||||
|
|
||||||
CROCKFORD = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
|
CROCKFORD = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
|
||||||
|
|
||||||
|
|
||||||
def generate_human_readable_id(length: int = 6) -> str:
|
def generate_human_readable_id(length: int = 6) -> str:
|
||||||
"""
|
"""
|
||||||
Generate a human-readable ID of `length` characters (from the Crockford set),
|
Generate a human-readable ID of `length` characters (from the Crockford set),
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,16 @@ from constance import config
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
|
||||||
def fetch_address_suggestions(
|
def fetch_address_suggestions(query: str, limit: int = 5) -> List[Dict]:
|
||||||
query: str,
|
|
||||||
limit: int = 5
|
|
||||||
) -> List[Dict]:
|
|
||||||
if not config.NOMINATIM_URL:
|
if not config.NOMINATIM_URL:
|
||||||
raise ValueError(_("NOMINATIM_URL must be configured."))
|
raise ValueError(_("NOMINATIM_URL must be configured."))
|
||||||
|
|
||||||
url = config.NOMINATIM_URL.rstrip('/') + '/search'
|
url = config.NOMINATIM_URL.rstrip("/") + "/search"
|
||||||
params = {
|
params = {
|
||||||
'format': 'json',
|
"format": "json",
|
||||||
'addressdetails': 1,
|
"addressdetails": 1,
|
||||||
'q': query,
|
"q": query,
|
||||||
'limit': limit,
|
"limit": limit,
|
||||||
}
|
}
|
||||||
response = requests.get(url, params=params)
|
response = requests.get(url, params=params)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
@ -25,10 +22,12 @@ def fetch_address_suggestions(
|
||||||
|
|
||||||
suggestions = []
|
suggestions = []
|
||||||
for item in results:
|
for item in results:
|
||||||
suggestions.append({
|
suggestions.append(
|
||||||
'display_name': item.get('display_name'),
|
{
|
||||||
'lat': item.get('lat'),
|
"display_name": item.get("display_name"),
|
||||||
'lon': item.get('lon'),
|
"lat": item.get("lat"),
|
||||||
'address': item.get('address', {}),
|
"lon": item.get("lon"),
|
||||||
})
|
"address": item.get("address", {}),
|
||||||
|
}
|
||||||
|
)
|
||||||
return suggestions
|
return suggestions
|
||||||
|
|
|
||||||
3
core/vendors/__init__.py
vendored
3
core/vendors/__init__.py
vendored
|
|
@ -176,7 +176,7 @@ class AbstractVendor:
|
||||||
def round_price_marketologically(price: float) -> float:
|
def round_price_marketologically(price: float) -> float:
|
||||||
up_int = ceil(price)
|
up_int = ceil(price)
|
||||||
s = str(up_int)
|
s = str(up_int)
|
||||||
s = (s[:-1] if len(s) > 1 else '0') + '9'
|
s = (s[:-1] if len(s) > 1 else "0") + "9"
|
||||||
return float(f"{int(s):.2f}")
|
return float(f"{int(s):.2f}")
|
||||||
|
|
||||||
def get_vendor_instance(self):
|
def get_vendor_instance(self):
|
||||||
|
|
@ -214,7 +214,6 @@ class AbstractVendor:
|
||||||
self.get_attribute_values_queryset().delete()
|
self.get_attribute_values_queryset().delete()
|
||||||
|
|
||||||
def process_attribute(self, key: str, value, product: Product, attr_group: AttributeGroup):
|
def process_attribute(self, key: str, value, product: Product, attr_group: AttributeGroup):
|
||||||
|
|
||||||
if not value:
|
if not value:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -169,8 +169,8 @@ class ProductViewSet(EvibesViewSet):
|
||||||
action_serializer_classes = {
|
action_serializer_classes = {
|
||||||
"list": ProductSimpleSerializer,
|
"list": ProductSimpleSerializer,
|
||||||
}
|
}
|
||||||
lookup_field = 'lookup'
|
lookup_field = "lookup"
|
||||||
lookup_url_kwarg = 'lookup'
|
lookup_url_kwarg = "lookup"
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
|
|
@ -476,9 +476,9 @@ class AddressViewSet(EvibesViewSet):
|
||||||
additional = {"create": "ALLOW"}
|
additional = {"create": "ALLOW"}
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
if self.action == 'create':
|
if self.action == "create":
|
||||||
return AddressCreateSerializer
|
return AddressCreateSerializer
|
||||||
if self.action == 'autocomplete':
|
if self.action == "autocomplete":
|
||||||
return AddressAutocompleteInputSerializer
|
return AddressAutocompleteInputSerializer
|
||||||
return AddressSerializer
|
return AddressSerializer
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ class CustomCommonMiddleware(CommonMiddleware):
|
||||||
class CustomLocaleMiddleware(LocaleMiddleware):
|
class CustomLocaleMiddleware(LocaleMiddleware):
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
lang = translation.get_language_from_request(request)
|
lang = translation.get_language_from_request(request)
|
||||||
parts = lang.replace('_', '-').split('-')
|
parts = lang.replace("_", "-").split("-")
|
||||||
if len(parts) == 2:
|
if len(parts) == 2:
|
||||||
lang_code = parts[0].lower()
|
lang_code = parts[0].lower()
|
||||||
region = parts[1].upper()
|
region = parts[1].upper()
|
||||||
|
|
|
||||||
|
|
@ -304,17 +304,18 @@ if getenv("SENTRY_DSN"):
|
||||||
]
|
]
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
ignore_errors.extend(["billiard.exceptions.WorkerLostError",
|
ignore_errors.extend(["billiard.exceptions.WorkerLostError", "billiard.exceptions.TimeLimitExceeded"])
|
||||||
"billiard.exceptions.TimeLimitExceeded"])
|
|
||||||
|
|
||||||
sentry_sdk.init(
|
sentry_sdk.init(
|
||||||
dsn=getenv("SENTRY_DSN"),
|
dsn=getenv("SENTRY_DSN"),
|
||||||
traces_sample_rate=1.0 if DEBUG else 0.2,
|
traces_sample_rate=1.0 if DEBUG else 0.2,
|
||||||
profiles_sample_rate=1.0 if DEBUG else 0.1,
|
profiles_sample_rate=1.0 if DEBUG else 0.1,
|
||||||
integrations=[DjangoIntegration(), LoggingIntegration(
|
integrations=[
|
||||||
level=logging.INFO,
|
DjangoIntegration(),
|
||||||
event_level=logging.ERROR
|
LoggingIntegration(level=logging.INFO, event_level=logging.ERROR),
|
||||||
), CeleryIntegration(), RedisIntegration()],
|
CeleryIntegration(),
|
||||||
|
RedisIntegration(),
|
||||||
|
],
|
||||||
environment="development" if DEBUG else "production",
|
environment="development" if DEBUG else "production",
|
||||||
debug=DEBUG,
|
debug=DEBUG,
|
||||||
release=f"evibes@{EVIBES_VERSION}",
|
release=f"evibes@{EVIBES_VERSION}",
|
||||||
|
|
@ -327,7 +328,7 @@ LANGUAGE_COOKIE_HTTPONLY = True
|
||||||
|
|
||||||
DATA_UPLOAD_MAX_NUMBER_FIELDS = 8888
|
DATA_UPLOAD_MAX_NUMBER_FIELDS = 8888
|
||||||
|
|
||||||
ADMINS = [('Egor Gorbunov', 'contact@fureunoir.com')]
|
ADMINS = [("Egor Gorbunov", "contact@fureunoir.com")]
|
||||||
|
|
||||||
STORAGES = {
|
STORAGES = {
|
||||||
"default": {
|
"default": {
|
||||||
|
|
@ -338,5 +339,5 @@ STORAGES = {
|
||||||
},
|
},
|
||||||
"dbbackup": {
|
"dbbackup": {
|
||||||
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,5 +41,5 @@ EXPOSABLE_KEYS = [
|
||||||
"EMAIL_HOST_USER",
|
"EMAIL_HOST_USER",
|
||||||
"EMAIL_FROM",
|
"EMAIL_FROM",
|
||||||
"PAYMENT_GATEWAY_MINIMUM",
|
"PAYMENT_GATEWAY_MINIMUM",
|
||||||
"PAYMENT_GATEWAY_MAXIMUM"
|
"PAYMENT_GATEWAY_MAXIMUM",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
from evibes.settings.base import getenv # noqa: I001
|
from evibes.settings.base import getenv
|
||||||
|
|
||||||
DBBACKUP_CONNECTORS = {
|
DBBACKUP_CONNECTORS = {
|
||||||
'default': {
|
"default": {
|
||||||
'SINGLE_TRANSACTION': False,
|
"SINGLE_TRANSACTION": False,
|
||||||
'IF_EXISTS': True,
|
"IF_EXISTS": True,
|
||||||
'RESTORE_SUFFIX': '--set ON_ERROR_STOP=off',
|
"RESTORE_SUFFIX": "--set ON_ERROR_STOP=off",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ Current API version: {EVIBES_VERSION}
|
||||||
SPECTACULAR_PLATFORM_SETTINGS = {
|
SPECTACULAR_PLATFORM_SETTINGS = {
|
||||||
"TITLE": f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]} API",
|
"TITLE": f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]} API",
|
||||||
"DESCRIPTION": SPECTACULAR_PLATFORM_DESCRIPTION,
|
"DESCRIPTION": SPECTACULAR_PLATFORM_DESCRIPTION,
|
||||||
"VERSION": EVIBES_VERSION,
|
"VERSION": EVIBES_VERSION, # noqa: F405
|
||||||
"TOS": "https://wiseless.xyz/evibes/terms-of-service",
|
"TOS": "https://wiseless.xyz/evibes/terms-of-service",
|
||||||
"SWAGGER_UI_DIST": "SIDECAR",
|
"SWAGGER_UI_DIST": "SIDECAR",
|
||||||
"CAMELIZE_NAMES": True,
|
"CAMELIZE_NAMES": True,
|
||||||
|
|
@ -145,7 +145,7 @@ SPECTACULAR_PLATFORM_SETTINGS = {
|
||||||
SPECTACULAR_B2B_SETTINGS = {
|
SPECTACULAR_B2B_SETTINGS = {
|
||||||
"TITLE": f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]} API",
|
"TITLE": f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]} API",
|
||||||
"DESCRIPTION": SPECTACULAR_B2B_DESCRIPTION,
|
"DESCRIPTION": SPECTACULAR_B2B_DESCRIPTION,
|
||||||
"VERSION": EVIBES_VERSION,
|
"VERSION": EVIBES_VERSION, # noqa: F405
|
||||||
"TOS": "https://wiseless.xyz/evibes/terms-of-service",
|
"TOS": "https://wiseless.xyz/evibes/terms-of-service",
|
||||||
"SWAGGER_UI_DIST": "SIDECAR",
|
"SWAGGER_UI_DIST": "SIDECAR",
|
||||||
"CAMELIZE_NAMES": True,
|
"CAMELIZE_NAMES": True,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
from evibes.settings.base import * # noqa: F403
|
from evibes.settings.base import * # noqa: F403
|
||||||
|
|
||||||
ELASTICSEARCH_DSL = {
|
ELASTICSEARCH_DSL = {
|
||||||
'default': {
|
"default": {
|
||||||
'hosts': ['http://elasticsearch:9200'],
|
"hosts": ["http://elasticsearch:9200"],
|
||||||
'basic_auth': ('elastic', getenv("ELASTIC_PASSWORD")), # noqa: F405
|
"basic_auth": ("elastic", getenv("ELASTIC_PASSWORD")), # noqa: F405
|
||||||
'verify_certs': False,
|
"verify_certs": False,
|
||||||
'ssl_show_warn': False,
|
"ssl_show_warn": False,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,12 +73,12 @@ LOGGING = {
|
||||||
},
|
},
|
||||||
"celery.app.trace": {
|
"celery.app.trace": {
|
||||||
"handlers": ["console_debug" if DEBUG else "console_production"], # noqa: F405
|
"handlers": ["console_debug" if DEBUG else "console_production"], # noqa: F405
|
||||||
"level": "DEBUG" if DEBUG else "INFO",
|
"level": "DEBUG" if DEBUG else "INFO", # noqa: F405
|
||||||
"propagate": False,
|
"propagate": False,
|
||||||
},
|
},
|
||||||
"celery.worker.strategy": {
|
"celery.worker.strategy": {
|
||||||
"handlers": ["console_debug" if DEBUG else "console_production"], # noqa: F405
|
"handlers": ["console_debug" if DEBUG else "console_production"], # noqa: F405
|
||||||
"level": "DEBUG" if DEBUG else "INFO",
|
"level": "DEBUG" if DEBUG else "INFO", # noqa: F405
|
||||||
"propagate": False,
|
"propagate": False,
|
||||||
},
|
},
|
||||||
"elastic_transport.transport": {
|
"elastic_transport.transport": {
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,5 @@ def process_transaction_changes(instance, created, **kwargs):
|
||||||
try:
|
try:
|
||||||
gateway = object()
|
gateway = object()
|
||||||
gateway.process_transaction(instance)
|
gateway.process_transaction(instance)
|
||||||
except Exception: # noqa:
|
except Exception: # noqa:
|
||||||
instance.process = {"status": "NOGATEWAY"}
|
instance.process = {"status": "NOGATEWAY"}
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,7 @@ USER_SCHEMA = {
|
||||||
"reset_password": extend_schema(
|
"reset_password": extend_schema(
|
||||||
summary=_("reset a user's password by sending a reset password email"),
|
summary=_("reset a user's password by sending a reset password email"),
|
||||||
request=ResetPasswordSerializer,
|
request=ResetPasswordSerializer,
|
||||||
responses={
|
responses={status.HTTP_200_OK: {}, **BASE_ERRORS},
|
||||||
status.HTTP_200_OK: {},
|
|
||||||
**BASE_ERRORS
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
"upload_avatar": extend_schema(
|
"upload_avatar": extend_schema(
|
||||||
summary=_("handle avatar upload for a user"),
|
summary=_("handle avatar upload for a user"),
|
||||||
|
|
@ -42,7 +39,7 @@ USER_SCHEMA = {
|
||||||
status.HTTP_200_OK: UserSerializer,
|
status.HTTP_200_OK: UserSerializer,
|
||||||
status.HTTP_400_BAD_REQUEST: {"description": "Invalid Request"},
|
status.HTTP_400_BAD_REQUEST: {"description": "Invalid Request"},
|
||||||
status.HTTP_403_FORBIDDEN: {"description": "Bad credentials"},
|
status.HTTP_403_FORBIDDEN: {"description": "Bad credentials"},
|
||||||
**BASE_ERRORS
|
**BASE_ERRORS,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
"confirm_password_reset": extend_schema(
|
"confirm_password_reset": extend_schema(
|
||||||
|
|
@ -51,7 +48,7 @@ USER_SCHEMA = {
|
||||||
responses={
|
responses={
|
||||||
status.HTTP_200_OK: {"description": "Password reset successfully"},
|
status.HTTP_200_OK: {"description": "Password reset successfully"},
|
||||||
status.HTTP_400_BAD_REQUEST: {"description": _("passwords do not match")},
|
status.HTTP_400_BAD_REQUEST: {"description": _("passwords do not match")},
|
||||||
**BASE_ERRORS
|
**BASE_ERRORS,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
"activate": extend_schema(
|
"activate": extend_schema(
|
||||||
|
|
@ -60,7 +57,7 @@ USER_SCHEMA = {
|
||||||
responses={
|
responses={
|
||||||
status.HTTP_200_OK: UserSerializer,
|
status.HTTP_200_OK: UserSerializer,
|
||||||
status.HTTP_400_BAD_REQUEST: {"description": _("activation link is invalid or account already activated")},
|
status.HTTP_400_BAD_REQUEST: {"description": _("activation link is invalid or account already activated")},
|
||||||
**BASE_ERRORS
|
**BASE_ERRORS,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,11 +109,7 @@ class UserType(DjangoObjectType):
|
||||||
|
|
||||||
products_by_uuid = {str(p.uuid): p for p in qs}
|
products_by_uuid = {str(p.uuid): p for p in qs}
|
||||||
|
|
||||||
ordered_products = [
|
ordered_products = [products_by_uuid[u] for u in uuid_list if u in products_by_uuid]
|
||||||
products_by_uuid[u]
|
|
||||||
for u in uuid_list
|
|
||||||
if u in products_by_uuid
|
|
||||||
]
|
|
||||||
|
|
||||||
return connection_from_array(ordered_products, kwargs)
|
return connection_from_array(ordered_products, kwargs)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,4 @@ def send_user_verification_email(instance, **kwargs):
|
||||||
|
|
||||||
if old.email != instance.email:
|
if old.email != instance.email:
|
||||||
instance.is_active = False
|
instance.is_active = False
|
||||||
transaction.on_commit(
|
transaction.on_commit(lambda: send_verification_email_task.delay(instance.pk))
|
||||||
lambda: send_verification_email_task.delay(instance.pk)
|
|
||||||
)
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue