Features: 1) Update all locale translations for token-related messages; 2) Update all locale translations with read-only Transaction ViewSet description; 3) Update all locale translations for reset password and token validations;
Fixes: 1) Correct line references in locale files for consistency; Extra: Improve formatting and alignment of string segments for clarity and maintainability;
This commit is contained in:
parent
64113648eb
commit
9e40323823
191 changed files with 18567 additions and 9430 deletions
|
|
@ -7,7 +7,7 @@ from .models import Post, PostTag
|
|||
|
||||
|
||||
@register(Post)
|
||||
class PostAdmin(SummernoteModelAdminMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc]
|
||||
class PostAdmin(SummernoteModelAdminMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg]
|
||||
list_display = ("title", "author", "slug", "created", "modified")
|
||||
list_filter = ("author", "tags", "created", "modified")
|
||||
search_fields = ("title", "content", "slug")
|
||||
|
|
@ -34,7 +34,7 @@ class PostAdmin(SummernoteModelAdminMixin, FieldsetsMixin, ActivationActionsMixi
|
|||
|
||||
|
||||
@register(PostTag)
|
||||
class PostTagAdmin(ModelAdmin):
|
||||
class PostTagAdmin(ModelAdmin): # type: ignore [misc, type-arg]
|
||||
list_display = ("tag_name", "name")
|
||||
search_fields = ("tag_name", "name")
|
||||
ordering = ("tag_name",)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import uuid
|
||||
from typing import Collection
|
||||
|
||||
from django.db.models import BooleanField, Model, UUIDField
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
|
@ -22,9 +23,19 @@ class NiceModel(Model):
|
|||
created = CreationDateTimeField(_("created"), help_text=_("when the object first appeared on the database"))
|
||||
modified = ModificationDateTimeField(_("modified"), help_text=_("when the object was last modified"))
|
||||
|
||||
def save(self, **kwargs):
|
||||
self.update_modified = kwargs.pop("update_modified", getattr(self, "update_modified", True))
|
||||
super().save(**kwargs)
|
||||
def save(
|
||||
self,
|
||||
*,
|
||||
force_insert: bool = False,
|
||||
force_update: bool = False,
|
||||
using: str | None = None,
|
||||
update_fields: Collection | None = None,
|
||||
update_modified: bool = True,
|
||||
) -> None:
|
||||
self.update_modified = update_modified
|
||||
return super().save(
|
||||
force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from core.models import CustomerRelationshipManagementProvider
|
||||
|
||||
|
||||
def any_crm_integrations():
|
||||
def any_crm_integrations() -> bool:
|
||||
return CustomerRelationshipManagementProvider.objects.exists()
|
||||
|
|
|
|||
|
|
@ -89,15 +89,12 @@ class AmoCRM:
|
|||
if type(order.attributes) is not dict:
|
||||
raise ValueError("order.attributes must be a dict")
|
||||
|
||||
business_identificator = (
|
||||
order.attributes.get("business_identificator")
|
||||
or order.attributes.get("businessIdentificator")
|
||||
or order.user.attributes.get("business_identificator")
|
||||
or order.user.attributes.get("businessIdentificator")
|
||||
or ""
|
||||
)
|
||||
if order.user:
|
||||
if type(order.user.attributes) is not dict:
|
||||
order.user.attributes = {}
|
||||
order.user.save()
|
||||
|
||||
if not business_identificator:
|
||||
if not order.business_identificator:
|
||||
return (
|
||||
order.user.get_full_name()
|
||||
if order.user
|
||||
|
|
@ -109,7 +106,7 @@ class AmoCRM:
|
|||
)
|
||||
try:
|
||||
r = requests.get(
|
||||
f"https://api-fns.ru/api/egr?req={business_identificator}&key={self.fns_api_key}", timeout=15
|
||||
f"https://api-fns.ru/api/egr?req={order.business_identificator}&key={self.fns_api_key}", timeout=15
|
||||
)
|
||||
r.raise_for_status()
|
||||
body = r.json()
|
||||
|
|
@ -122,9 +119,9 @@ class AmoCRM:
|
|||
ip = body.get("ИП")
|
||||
|
||||
if ul and not ip:
|
||||
return f"{ul.get('НаимСокрЮЛ')} | {business_identificator}"
|
||||
return f"{ul.get('НаимСокрЮЛ')} | {order.business_identificator}"
|
||||
if ip and not ul:
|
||||
return f"ИП {ip.get('ФИОПолн')} | {business_identificator}"
|
||||
return f"ИП {ip.get('ФИОПолн')} | {order.business_identificator}"
|
||||
|
||||
return ""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import re
|
||||
from typing import Any
|
||||
|
||||
from blib2to3.pgen2.parse import Callable
|
||||
from django.conf import settings
|
||||
from django.db.models import QuerySet
|
||||
from django.http import Http404
|
||||
from django.utils.text import slugify
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
|
@ -182,7 +185,7 @@ def process_query(
|
|||
minimum_should_match=1,
|
||||
)
|
||||
|
||||
def build_search(idxs, size):
|
||||
def build_search(idxs: list[str], size: int) -> Search:
|
||||
return (
|
||||
Search(index=idxs)
|
||||
.query(query_base)
|
||||
|
|
@ -221,9 +224,9 @@ def process_query(
|
|||
search_products = build_search(["products"], size=44)
|
||||
resp_products = search_products.execute()
|
||||
|
||||
results: dict = {"products": [], "categories": [], "brands": [], "posts": []}
|
||||
uuids_by_index: dict[str, list] = {"products": [], "categories": [], "brands": []}
|
||||
hit_cache: list = []
|
||||
results: dict[str, list[dict[str, Any]]] = {"products": [], "categories": [], "brands": [], "posts": []}
|
||||
uuids_by_index: dict[str, list[dict[str, Any]]] = {"products": [], "categories": [], "brands": []}
|
||||
hit_cache: list[Any] = []
|
||||
|
||||
for h in (
|
||||
list(resp_cats.hits[:12] if resp_cats else [])
|
||||
|
|
@ -325,10 +328,10 @@ def _lang_analyzer(lang_code: str) -> str:
|
|||
|
||||
|
||||
class ActiveOnlyMixin:
|
||||
def get_queryset(self):
|
||||
def get_queryset(self) -> QuerySet[Any]:
|
||||
return super().get_queryset().filter(is_active=True)
|
||||
|
||||
def should_index_object(self, obj):
|
||||
def should_index_object(self, obj) -> bool:
|
||||
return getattr(obj, "is_active", False)
|
||||
|
||||
|
||||
|
|
@ -433,7 +436,7 @@ COMMON_ANALYSIS = {
|
|||
}
|
||||
|
||||
|
||||
def add_multilang_fields(cls):
|
||||
def add_multilang_fields(cls) -> None:
|
||||
for code, _lang in settings.LANGUAGES:
|
||||
lc = code.replace("-", "_").lower()
|
||||
name_field = f"name_{lc}"
|
||||
|
|
@ -453,7 +456,7 @@ def add_multilang_fields(cls):
|
|||
),
|
||||
)
|
||||
|
||||
def make_prepare(attr):
|
||||
def make_prepare(attr: str) -> Callable[[Any, Any], str]:
|
||||
return lambda self, instance: getattr(instance, attr, "") or ""
|
||||
|
||||
setattr(cls, f"prepare_{name_field}", make_prepare(name_field))
|
||||
|
|
|
|||
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
|
|
@ -2,7 +2,7 @@ from django.conf import settings
|
|||
|
||||
|
||||
class RootDirectory:
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
self.label = "root"
|
||||
self.path = settings.BASE_DIR / "evibes"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import importlib
|
||||
import os
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import requests
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
|
@ -16,7 +17,7 @@ class Command(BaseCommand):
|
|||
"in the translated_<lang> field created by django-modeltranslation."
|
||||
)
|
||||
|
||||
def add_arguments(self, parser):
|
||||
def add_arguments(self, parser: ArgumentParser) -> None:
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--target",
|
||||
|
|
@ -30,7 +31,7 @@ class Command(BaseCommand):
|
|||
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) -> None:
|
||||
target = options["target"]
|
||||
lang = options["language"].lower()
|
||||
|
||||
|
|
|
|||
|
|
@ -139,7 +139,15 @@ class Vendor(ExportModelOperationsMixin("vendor"), NiceModel): # type: ignore [
|
|||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
def save(self, **kwargs) -> None:
|
||||
def save(
|
||||
self,
|
||||
*,
|
||||
force_insert=False,
|
||||
force_update=False,
|
||||
using=None,
|
||||
update_fields=None,
|
||||
update_modified: bool = True,
|
||||
) -> None:
|
||||
users = self.users.filter(is_active=True)
|
||||
users = users.exclude(attributes__icontains="is_business")
|
||||
if users.count() > 0:
|
||||
|
|
@ -148,7 +156,13 @@ class Vendor(ExportModelOperationsMixin("vendor"), NiceModel): # type: ignore [
|
|||
user.attributes = {}
|
||||
user.attributes.update({"is_business": True})
|
||||
user.save()
|
||||
return super().save(**kwargs)
|
||||
return super().save(
|
||||
force_insert=force_insert,
|
||||
force_update=force_update,
|
||||
using=using,
|
||||
update_fields=update_fields,
|
||||
update_modified=update_modified,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("vendor")
|
||||
|
|
@ -1014,14 +1028,28 @@ class PromoCode(ExportModelOperationsMixin("promocode"), NiceModel): # type: ig
|
|||
verbose_name = _("promo code")
|
||||
verbose_name_plural = _("promo codes")
|
||||
|
||||
def save(self, **kwargs):
|
||||
def save(
|
||||
self,
|
||||
*args,
|
||||
force_insert=False,
|
||||
force_update=False,
|
||||
using=None,
|
||||
update_fields=None,
|
||||
update_modified: bool = True,
|
||||
) -> None:
|
||||
if (self.discount_amount is not None and self.discount_percent is not None) or (
|
||||
self.discount_amount is None and self.discount_percent is None
|
||||
):
|
||||
raise ValidationError(
|
||||
_("only one type of discount should be defined (amount or percent), but not both or neither.")
|
||||
)
|
||||
super().save(**kwargs)
|
||||
return super().save(
|
||||
force_insert=force_insert,
|
||||
force_update=force_update,
|
||||
using=using,
|
||||
update_fields=update_fields,
|
||||
update_modified=update_modified,
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.code
|
||||
|
|
@ -1152,21 +1180,40 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): # type: ignore [mi
|
|||
self.attributes = {}
|
||||
self.save()
|
||||
return False
|
||||
if self.user:
|
||||
if type(self.user.attributes) is not dict:
|
||||
self.user.attributes = {}
|
||||
self.user.save()
|
||||
return False
|
||||
with suppress(Exception):
|
||||
return (self.attributes.get("is_business", False) if self.attributes else False) or (
|
||||
(self.user.attributes.get("is_business", False) and self.user.attributes.get("business_identificator"))
|
||||
(self.user.attributes.get("is_business", False) and self.user.attributes.get("business_identificator")) # type: ignore [union-attr]
|
||||
if self.user
|
||||
else False
|
||||
)
|
||||
return False
|
||||
|
||||
def save(self, **kwargs) -> Self:
|
||||
def save(
|
||||
self,
|
||||
*args,
|
||||
force_insert=False,
|
||||
force_update=False,
|
||||
using=None,
|
||||
update_fields=None,
|
||||
update_modified: bool = True,
|
||||
) -> None:
|
||||
pending_orders = 0
|
||||
if self.user:
|
||||
pending_orders = self.user.orders.filter(status="PENDING").count()
|
||||
if self.status == "PENDING" and pending_orders > 1:
|
||||
raise ValueError(_("a user must have only one pending order at a time"))
|
||||
return super().save(**kwargs)
|
||||
return super().save(
|
||||
force_insert=force_insert,
|
||||
force_update=force_update,
|
||||
using=using,
|
||||
update_fields=update_fields,
|
||||
update_modified=update_modified,
|
||||
)
|
||||
|
||||
@property
|
||||
def total_price(self) -> float:
|
||||
|
|
@ -1313,8 +1360,8 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): # type: ignore [mi
|
|||
shipping_address = billing_address
|
||||
|
||||
else:
|
||||
billing_address = Address.objects.get(uuid=billing_address_uuid)
|
||||
shipping_address = Address.objects.get(uuid=shipping_address_uuid)
|
||||
billing_address = Address.objects.get(uuid=str(billing_address_uuid))
|
||||
shipping_address = Address.objects.get(uuid=str(shipping_address_uuid))
|
||||
|
||||
self.billing_address = billing_address
|
||||
self.shipping_address = shipping_address
|
||||
|
|
@ -1527,6 +1574,17 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): # type: ignore [mi
|
|||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
@property
|
||||
def business_identificator(self) -> str | None:
|
||||
if self.attributes:
|
||||
return self.attributes.get("business_identificator") or self.attributes.get("businessIdentificator")
|
||||
if self.user:
|
||||
if self.user.attributes:
|
||||
return self.user.attributes.get("business_identificator") or self.user.attributes.get(
|
||||
"businessIdentificator"
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel): # type: ignore [misc, django-manager-missing]
|
||||
__doc__ = _( # type: ignore
|
||||
|
|
@ -1679,7 +1737,7 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel): # t
|
|||
return None
|
||||
|
||||
|
||||
class CustomerRelationshipManagementProvider(ExportModelOperationsMixin("crm_provider"), NiceModel):
|
||||
class CustomerRelationshipManagementProvider(ExportModelOperationsMixin("crm_provider"), NiceModel): # type: ignore [misc, django-manager-missing]
|
||||
name = CharField(max_length=128, unique=True, verbose_name=_("name"))
|
||||
integration_url = URLField(blank=True, null=True, help_text=_("URL of the integration"))
|
||||
authentication = JSONField(blank=True, null=True, help_text=_("authentication credentials"))
|
||||
|
|
@ -1690,14 +1748,28 @@ class CustomerRelationshipManagementProvider(ExportModelOperationsMixin("crm_pro
|
|||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
def save(self, **kwargs):
|
||||
def save(
|
||||
self,
|
||||
*args,
|
||||
force_insert=False,
|
||||
force_update=False,
|
||||
using=None,
|
||||
update_fields=None,
|
||||
update_modified: bool = True,
|
||||
) -> None:
|
||||
if self.default:
|
||||
qs = type(self).objects.all()
|
||||
if self.pk:
|
||||
qs = qs.exclude(pk=self.pk)
|
||||
if qs.filter(default=True).exists():
|
||||
raise ValueError(_("you can only have one default CRM provider"))
|
||||
super().save(**kwargs)
|
||||
super().save(
|
||||
force_insert=force_insert,
|
||||
force_update=force_update,
|
||||
using=using,
|
||||
update_fields=update_fields,
|
||||
update_modified=update_modified,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("CRM")
|
||||
|
|
@ -1741,9 +1813,6 @@ class DigitalAssetDownload(ExportModelOperationsMixin("attribute_group"), NiceMo
|
|||
|
||||
@property
|
||||
def url(self):
|
||||
if self.order_product.status != "FINISHED":
|
||||
raise ValueError(_("you can not download a digital asset for a non-finished order"))
|
||||
|
||||
return (
|
||||
f"https://api.{config.BASE_DOMAIN}/download/{urlsafe_base64_encode(force_bytes(self.order_product.uuid))}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -84,15 +84,18 @@ def process_order_changes(instance, created, **_kwargs):
|
|||
except IntegrityError:
|
||||
human_readable_id = generate_human_readable_id()
|
||||
while True:
|
||||
if Order.objects.filter(human_readable_id=human_readable_id).exists():
|
||||
human_readable_id = generate_human_readable_id()
|
||||
try:
|
||||
if Order.objects.filter(human_readable_id=human_readable_id).exists():
|
||||
human_readable_id = generate_human_readable_id()
|
||||
continue
|
||||
Order.objects.create(
|
||||
user=instance,
|
||||
status="PENDING",
|
||||
human_readable_id=human_readable_id,
|
||||
)
|
||||
break
|
||||
except IntegrityError:
|
||||
continue
|
||||
Order.objects.create(
|
||||
user=instance,
|
||||
status="PENDING",
|
||||
human_readable_id=human_readable_id,
|
||||
)
|
||||
break
|
||||
|
||||
if instance.status in ["CREATED", "PAYMENT"]:
|
||||
if not instance.is_whole_digital:
|
||||
|
|
@ -110,12 +113,15 @@ def process_order_changes(instance, created, **_kwargs):
|
|||
|
||||
if has_file:
|
||||
order_product.status = "FINISHED"
|
||||
download = DigitalAssetDownload.objects.create(order_product=order_product)
|
||||
order_product.download = download
|
||||
order_product.save()
|
||||
order_product.order.user.payments_balance.amount -= order_product.buy_price
|
||||
order_product.order.user.payments_balance.save()
|
||||
continue
|
||||
if not order_product.download:
|
||||
DigitalAssetDownload.objects.create(order_product=order_product)
|
||||
order_product.order.user.payments_balance.amount -= order_product.buy_price
|
||||
order_product.order.user.payments_balance.save()
|
||||
order_product.save()
|
||||
continue
|
||||
|
||||
order_product.save()
|
||||
|
||||
try:
|
||||
vendor_name = (
|
||||
order_product.product.stocks.filter(price=order_product.buy_price).first().vendor.name.lower()
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from django.core.cache import cache
|
|||
|
||||
from core.models import Product, Promotion
|
||||
from core.utils.caching import set_default_cache
|
||||
from core.vendors import delete_stale, VendorInactiveError
|
||||
from core.vendors import VendorInactiveError, delete_stale
|
||||
from evibes.settings import MEDIA_ROOT
|
||||
|
||||
logger = get_task_logger(__name__)
|
||||
|
|
@ -38,7 +38,7 @@ def update_products_task() -> tuple[bool, str]:
|
|||
|
||||
if not update_products_task_running:
|
||||
cache.set("update_products_task_running", True, 86400)
|
||||
vendors_classes = []
|
||||
vendors_classes: list = []
|
||||
|
||||
for vendor_class in vendors_classes:
|
||||
vendor = vendor_class()
|
||||
|
|
@ -69,7 +69,7 @@ def update_orderproducts_task() -> tuple[bool, str]:
|
|||
message confirming the successful execution of the task.
|
||||
:rtype: Tuple[bool, str]
|
||||
"""
|
||||
vendors_classes = []
|
||||
vendors_classes: list = []
|
||||
|
||||
for vendor_class in vendors_classes:
|
||||
vendor = vendor_class()
|
||||
|
|
@ -95,7 +95,7 @@ def set_default_caches_task() -> tuple[bool, str]:
|
|||
|
||||
|
||||
@shared_task(queue="default")
|
||||
def remove_stale_product_images() -> tuple[bool, str] | None:
|
||||
def remove_stale_product_images() -> tuple[bool, str]:
|
||||
"""
|
||||
Removes stale product images from the products directory by identifying directories
|
||||
whose names do not match any UUIDs currently present in the database.
|
||||
|
|
@ -114,7 +114,7 @@ def remove_stale_product_images() -> tuple[bool, str] | None:
|
|||
products_dir = os.path.join(MEDIA_ROOT, "products")
|
||||
if not os.path.isdir(products_dir):
|
||||
logger.info("The products directory does not exist: %s", products_dir)
|
||||
return
|
||||
return True, "The products directory does not exist."
|
||||
|
||||
# Load all current product UUIDs into a set.
|
||||
# This query returns all product UUIDs (as strings or UUID objects).
|
||||
|
|
@ -139,6 +139,7 @@ def remove_stale_product_images() -> tuple[bool, str] | None:
|
|||
logger.info("Removed stale product images directory: %s", entry_path)
|
||||
except Exception as e:
|
||||
logger.error("Error removing directory %s: %s", entry_path, e)
|
||||
return True, "Successfully removed stale product images."
|
||||
|
||||
|
||||
@shared_task(queue="default")
|
||||
|
|
|
|||
|
|
@ -6,31 +6,31 @@
|
|||
<th>{% blocktrans %}value{% endblocktrans %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="json-fields-{{ widget.attrs.id }}">
|
||||
{% for key, value in widget.value.items %}
|
||||
<tr data-row-index="{{ forloop.counter }}">
|
||||
<tbody id="json-fields-{{ widget.attrs.id }}" data-name="{{ widget.name }}">
|
||||
{% for idx, item in widget.value.items %}
|
||||
<tr data-row-index="{{ forloop.counter0 }}">
|
||||
<td><label>
|
||||
<input type="text" name="{{ widget.name }}_key" value="{{ key }}">
|
||||
<input type="text" name="{{ widget.name }}[{{ forloop.counter0 }}][key]" value="{{ item.0 }}">
|
||||
</label></td>
|
||||
<td>
|
||||
{% if value is list %}
|
||||
{% if item.1 is list %}
|
||||
<label>
|
||||
<input type="text" name="{{ widget.name }}_value" value="{{ value|join:', ' }}">
|
||||
<input type="text" name="{{ widget.name }}[{{ forloop.counter0 }}][value]" value="{{ item.1|join:', ' }}">
|
||||
</label>
|
||||
{% else %}
|
||||
<label>
|
||||
<input type="text" name="{{ widget.name }}_value" value="{{ value }}">
|
||||
<input type="text" name="{{ widget.name }}[{{ forloop.counter0 }}][value]" value="{{ item.1 }}">
|
||||
</label>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr data-row-index="{{ widget.value.items|length|default:0|add:1 }}">
|
||||
<tr data-row-index="{{ widget.value.items|length|default_if_none:0|default:0 }}">
|
||||
<td><label>
|
||||
<input type="text" name="{{ widget.name }}_key">
|
||||
<input type="text" name="{{ widget.name }}[{{ widget.value.items|length|default_if_none:0|default:0 }}][key]">
|
||||
</label></td>
|
||||
<td><label>
|
||||
<input type="text" name="{{ widget.name }}_value">
|
||||
<input type="text" name="{{ widget.name }}[{{ widget.value.items|length|default_if_none:0|default:0 }}][value]">
|
||||
</label></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
@ -49,23 +49,38 @@
|
|||
});
|
||||
|
||||
function addRow(event) {
|
||||
let tableId = event.target.getAttribute("data-table-id");
|
||||
let table = document.getElementById(tableId);
|
||||
const tableBodyId = event.target.getAttribute("data-table-id");
|
||||
const tbody = document.getElementById(tableBodyId);
|
||||
if (!tbody) return;
|
||||
|
||||
if (table) {
|
||||
let lastRow = table.querySelector("tr:last-child");
|
||||
let rowIndex = (parseInt(lastRow.getAttribute("data-row-index"), 10) + 1).toString();
|
||||
const lastRow = tbody.querySelector("tr:last-child");
|
||||
const lastIndex = lastRow ? parseInt(lastRow.getAttribute("data-row-index"), 10) : -1;
|
||||
const rowIndex = Number.isFinite(lastIndex) ? lastIndex + 1 : 0;
|
||||
|
||||
let row = table.insertRow();
|
||||
row.setAttribute("data-row-index", rowIndex);
|
||||
const namePrefix = tbody.getAttribute("data-name");
|
||||
if (!namePrefix) return;
|
||||
|
||||
let keyCell = row.insertCell(0);
|
||||
let valueCell = row.insertCell(1);
|
||||
const tr = document.createElement("tr");
|
||||
tr.setAttribute("data-row-index", String(rowIndex));
|
||||
|
||||
let namePrefix = tableId.replace("json-fields-", "");
|
||||
const tdKey = document.createElement("td");
|
||||
const labelKey = document.createElement("label");
|
||||
const inputKey = document.createElement("input");
|
||||
inputKey.type = "text";
|
||||
inputKey.name = `${namePrefix}[${rowIndex}][key]`;
|
||||
labelKey.appendChild(inputKey);
|
||||
tdKey.appendChild(labelKey);
|
||||
|
||||
keyCell.innerHTML = `<input type="text" name="${namePrefix}_key_${rowIndex}">`;
|
||||
valueCell.innerHTML = `<input type="text" name="${namePrefix}_value_${rowIndex}">`;
|
||||
}
|
||||
const tdVal = document.createElement("td");
|
||||
const labelVal = document.createElement("label");
|
||||
const inputVal = document.createElement("input");
|
||||
inputVal.type = "text";
|
||||
inputVal.name = `${namePrefix}[${rowIndex}][value]`;
|
||||
labelVal.appendChild(inputVal);
|
||||
tdVal.appendChild(labelVal);
|
||||
|
||||
tr.appendChild(tdKey);
|
||||
tr.appendChild(tdVal);
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
</script>
|
||||
|
|
@ -15,7 +15,7 @@ from evibes.settings import DEBUG, EXPOSABLE_KEYS, LANGUAGE_CODE
|
|||
logger = logging.getLogger("django")
|
||||
|
||||
|
||||
def graphene_current_lang():
|
||||
def graphene_current_lang() -> str:
|
||||
"""
|
||||
Determines the currently active language code.
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ def graphene_abs(request, path_or_url: str) -> str:
|
|||
Returns:
|
||||
str: The absolute URI corresponding to the provided path or URL.
|
||||
"""
|
||||
return request.build_absolute_uri(path_or_url)
|
||||
return str(request.build_absolute_uri(path_or_url))
|
||||
|
||||
|
||||
def get_random_code() -> str:
|
||||
|
|
@ -64,40 +64,10 @@ def get_random_code() -> str:
|
|||
|
||||
|
||||
def get_product_uuid_as_path(instance, filename: str = "") -> str:
|
||||
"""
|
||||
Generates a file path for a product using its UUID.
|
||||
|
||||
This function constructs a standardized file path where an uploaded file
|
||||
is saved for a product. The path includes a `products` directory, followed
|
||||
by the product's UUID, and concludes with the original filename. It can be
|
||||
utilized in file storage applications to ensure unique and organized file
|
||||
storage based on the product's identity.
|
||||
|
||||
Args:
|
||||
instance: The object instance that contains a reference to the product.
|
||||
filename: str, optional. The name of the file being uploaded. Default is an
|
||||
empty string.
|
||||
|
||||
Returns:
|
||||
str: A string that represents the constructed file path.
|
||||
"""
|
||||
return "products" + "/" + str(instance.product.uuid) + "/" + filename
|
||||
|
||||
|
||||
def get_brand_name_as_path(instance, filename: str = "") -> str:
|
||||
"""
|
||||
Generates a file path for a brand based on its name and the provided filename.
|
||||
|
||||
This function constructs a unique file path within the 'brands/' directory using
|
||||
the name of the given instance and appends the supplied filename.
|
||||
|
||||
Parameters:
|
||||
instance: An object containing a 'name' attribute.
|
||||
filename: str, optional. The name of the file to be appended to the path.
|
||||
|
||||
Returns:
|
||||
str: A string representing the constructed file path.
|
||||
"""
|
||||
return "brands/" + str(instance.name) + "/" + filename
|
||||
|
||||
|
||||
|
|
@ -111,9 +81,6 @@ def atomic_if_not_debug():
|
|||
database transaction, preventing partial updates to the database in case of
|
||||
an exception. If the DEBUG setting is enabled, no transaction is enforced,
|
||||
allowing for easier debugging.
|
||||
|
||||
Yields:
|
||||
None: This context manager does not return any values.
|
||||
"""
|
||||
if not DEBUG:
|
||||
with transaction.atomic():
|
||||
|
|
@ -123,18 +90,6 @@ def atomic_if_not_debug():
|
|||
|
||||
|
||||
def is_url_safe(url: str) -> bool:
|
||||
"""
|
||||
Determines if a given URL starts with "https://" indicating it is a secure URL.
|
||||
|
||||
This function checks if the provided URL adheres to secure HTTPS protocol.
|
||||
It uses a regular expression to validate the URL prefix.
|
||||
|
||||
Parameters:
|
||||
url (str): The URL string to validate.
|
||||
|
||||
Returns:
|
||||
bool: True if the URL starts with "https://", False otherwise.
|
||||
"""
|
||||
return bool(re.match(r"^https://", url, re.IGNORECASE))
|
||||
|
||||
|
||||
|
|
@ -146,14 +101,6 @@ def format_attributes(attributes: str | None = None) -> dict:
|
|||
formatted as `key=value` pairs separated by commas, and converts it into a
|
||||
dictionary. It returns an empty dictionary if the input is `None` or invalid.
|
||||
Invalid key-value pairs within the input string are skipped.
|
||||
|
||||
Parameters:
|
||||
attributes (str | None): A comma-separated string of key-value pairs in the
|
||||
format `key=value`, or None.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary where keys are the attribute names and values are their
|
||||
corresponding values.
|
||||
"""
|
||||
if not attributes:
|
||||
return {}
|
||||
|
|
@ -182,9 +129,6 @@ def get_project_parameters() -> dict:
|
|||
If they are not cached, it collects the parameters from a designated
|
||||
configuration source, formats their keys to lowercase, and then stores
|
||||
them in the cache for a limited period.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the project parameters with lowercase keys.
|
||||
"""
|
||||
parameters = cache.get("parameters", {})
|
||||
|
||||
|
|
@ -197,19 +141,11 @@ def get_project_parameters() -> dict:
|
|||
return parameters
|
||||
|
||||
|
||||
def resolve_translations_for_elasticsearch(instance, field_name) -> None:
|
||||
def resolve_translations_for_elasticsearch(instance, field_name: str) -> None:
|
||||
"""
|
||||
Resolves translations for a given field in an Elasticsearch-compatible
|
||||
format. It checks if the localized version of the field contains data,
|
||||
and if not, sets it to the value of the default field.
|
||||
|
||||
Parameters:
|
||||
instance: The object instance containing the field to resolve.
|
||||
field_name (str): The base name of the field for which translations
|
||||
are being resolved.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
field = getattr(instance, f"{field_name}_{LANGUAGE_CODE}", "")
|
||||
filled_field = getattr(instance, field_name, "")
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from django.contrib.auth.base_user import AbstractBaseUser
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import BadRequest
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from graphene import Context
|
||||
from rest_framework.request import Request
|
||||
|
||||
from evibes.settings import UNSAFE_CACHE_KEYS
|
||||
from vibes_auth.models import User
|
||||
|
|
@ -26,7 +31,9 @@ def get_cached_value(user: User, key: str, default=None) -> None | object:
|
|||
return None
|
||||
|
||||
|
||||
def set_cached_value(user: User, key: str, value: object, timeout: int = 3600) -> None | object:
|
||||
def set_cached_value(
|
||||
user: User | AbstractBaseUser | AnonymousUser, key: str, value: object, timeout: int = 3600
|
||||
) -> None | object:
|
||||
if user.is_staff or user.is_superuser:
|
||||
cache.set(key, value, timeout)
|
||||
return value
|
||||
|
|
@ -34,7 +41,7 @@ def set_cached_value(user: User, key: str, value: object, timeout: int = 3600) -
|
|||
return None
|
||||
|
||||
|
||||
def web_cache(request, key, data, timeout):
|
||||
def web_cache(request: Request | Context, key: str, data: dict[str, Any], timeout: int):
|
||||
if not data and not timeout:
|
||||
return {"data": get_cached_value(request.user, key)}
|
||||
if (data and not timeout) or (timeout and not data):
|
||||
|
|
@ -44,7 +51,7 @@ def web_cache(request, key, data, timeout):
|
|||
return {"data": set_cached_value(request.user, key, data, timeout)}
|
||||
|
||||
|
||||
def set_default_cache():
|
||||
def set_default_cache() -> None:
|
||||
data_dir = Path(__file__).resolve().parent.parent / "data"
|
||||
for json_file in data_dir.glob("*.json"):
|
||||
with json_file.open("r", encoding="utf-8") as f:
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import re
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.files.images import get_image_dimensions
|
||||
from django.core.files.images import ImageFile, get_image_dimensions
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
def validate_category_image_dimensions(image):
|
||||
def validate_category_image_dimensions(image: ImageFile) -> None:
|
||||
max_width = 99999
|
||||
max_height = 99999
|
||||
|
||||
|
|
@ -14,9 +12,3 @@ def validate_category_image_dimensions(image):
|
|||
|
||||
if width > max_width or height > max_height:
|
||||
raise ValidationError(_(f"image dimensions should not exceed w{max_width} x h{max_height} pixels"))
|
||||
|
||||
|
||||
def validate_phone_number(value, **_kwargs):
|
||||
phone_regex = re.compile(r"^\+?1?\d{9,15}$")
|
||||
if not phone_regex.match(value):
|
||||
raise ValidationError(_("invalid phone number format"))
|
||||
|
|
|
|||
9
core/vendors/__init__.py
vendored
9
core/vendors/__init__.py
vendored
|
|
@ -5,6 +5,7 @@ from math import ceil, log10
|
|||
from typing import Any
|
||||
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db.models import QuerySet
|
||||
|
||||
from core.elasticsearch import process_system_query
|
||||
from core.models import (
|
||||
|
|
@ -235,7 +236,7 @@ class AbstractVendor:
|
|||
if not rate:
|
||||
raise RatesError(f"No rate found for {currency or self.currency} in {rates} with probider {provider}...")
|
||||
|
||||
return float(round(price / rate, 2)) if rate else round(price, 2)
|
||||
return float(round(price / rate, 2)) if rate else float(round(price, 2)) # type: ignore [arg-type]
|
||||
|
||||
@staticmethod
|
||||
def round_price_marketologically(price: float) -> float:
|
||||
|
|
@ -282,13 +283,13 @@ class AbstractVendor:
|
|||
def get_products(self) -> None:
|
||||
pass
|
||||
|
||||
def get_products_queryset(self):
|
||||
def get_products_queryset(self) -> QuerySet[Product]:
|
||||
return Product.objects.filter(stocks__vendor=self.get_vendor_instance(), orderproduct__isnull=True)
|
||||
|
||||
def get_stocks_queryset(self):
|
||||
def get_stocks_queryset(self) -> QuerySet[Stock]:
|
||||
return Stock.objects.filter(product__in=self.get_products_queryset(), product__orderproduct__isnull=True)
|
||||
|
||||
def get_attribute_values_queryset(self):
|
||||
def get_attribute_values_queryset(self) -> QuerySet[AttributeValue]:
|
||||
return AttributeValue.objects.filter(
|
||||
product__in=self.get_products_queryset(), product__orderproduct__isnull=True
|
||||
)
|
||||
|
|
|
|||
300
core/views.py
300
core/views.py
|
|
@ -8,7 +8,7 @@ from django.contrib.sitemaps.views import index as _sitemap_index_view
|
|||
from django.contrib.sitemaps.views import sitemap as _sitemap_detail_view
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import BadRequest
|
||||
from django.http import FileResponse, Http404, JsonResponse
|
||||
from django.http import FileResponse, Http404, JsonResponse, HttpRequest, HttpResponseRedirect
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.http import urlsafe_base64_decode
|
||||
|
|
@ -24,6 +24,7 @@ from graphene_file_upload.django import FileUploadGraphQLView
|
|||
from rest_framework import status
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.renderers import MultiPartRenderer
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework_xml.renderers import XMLRenderer
|
||||
|
|
@ -61,74 +62,40 @@ logger = logging.getLogger("django")
|
|||
@cache_page(60 * 60 * 12)
|
||||
@vary_on_headers("Host")
|
||||
def sitemap_index(request, *args, **kwargs):
|
||||
"""
|
||||
Handles the request for the sitemap index and returns an XML response. It ensures the response includes
|
||||
the appropriate content type header for XML.
|
||||
|
||||
Args:
|
||||
request: The HTTP request object.
|
||||
*args: Additional positional arguments passed to the view.
|
||||
**kwargs: Additional keyword arguments passed to the view.
|
||||
|
||||
Returns:
|
||||
A response object containing the sitemap index in XML format, with the proper content type set as
|
||||
"application/xml; charset=utf-8".
|
||||
"""
|
||||
response = _sitemap_index_view(request, *args, **kwargs)
|
||||
response["Content-Type"] = "application/xml; charset=utf-8"
|
||||
return response
|
||||
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
sitemap_index.__doc__ = _( # type: ignore [assignment]
|
||||
"Handles the request for the sitemap index and returns an XML response. "
|
||||
"It ensures the response includes the appropriate content type header for XML."
|
||||
)
|
||||
|
||||
|
||||
@cache_page(60 * 60 * 24)
|
||||
@vary_on_headers("Host")
|
||||
def sitemap_detail(request, *args, **kwargs):
|
||||
"""
|
||||
Handles the detailed view response for a sitemap. This function processes
|
||||
the request, fetches the appropriate sitemap detail response, and sets the
|
||||
Content-Type header for XML responses.
|
||||
|
||||
Args:
|
||||
request: An HTTP request object containing request metadata, such as
|
||||
headers and HTTP method.
|
||||
*args: Additional positional arguments provided dynamically to the
|
||||
underlying sitemap detail view function.
|
||||
**kwargs: Additional keyword arguments provided dynamically to the
|
||||
underlying sitemap detail view function.
|
||||
|
||||
Returns:
|
||||
HttpResponse: A response object with content representing the requested
|
||||
sitemap details. The Content-Type header is explicitly set to
|
||||
"application/xml; charset=utf-8".
|
||||
"""
|
||||
response = _sitemap_detail_view(request, *args, **kwargs)
|
||||
response["Content-Type"] = "application/xml; charset=utf-8"
|
||||
return response
|
||||
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
sitemap_detail.__doc__ = _( # type: ignore [assignment]
|
||||
"Handles the detailed view response for a sitemap. "
|
||||
"This function processes the request, fetches the appropriate "
|
||||
"sitemap detail response, and sets the Content-Type header for XML."
|
||||
)
|
||||
|
||||
|
||||
class CustomGraphQLView(FileUploadGraphQLView):
|
||||
"""
|
||||
A custom GraphQL view class that extends the functionality of FileUploadGraphQLView.
|
||||
|
||||
This class serves as a customization extension of FileUploadGraphQLView that allows modification
|
||||
or enhancement of specific behaviors, particularly the context handling for GraphQL requests.
|
||||
|
||||
"""
|
||||
|
||||
def get_context(self, request):
|
||||
return request
|
||||
|
||||
|
||||
class CustomSwaggerView(SpectacularSwaggerView):
|
||||
"""
|
||||
CustomSwaggerView is a subclass of SpectacularSwaggerView.
|
||||
|
||||
This class overrides the `get_context_data` method to
|
||||
add extra context to the response. It modifies the context by
|
||||
including the absolute URI of the current request as the `script_url`.
|
||||
This can be useful in scenarios where the script or reference
|
||||
URL needs to be dynamically generated and included in the context.
|
||||
"""
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
# noinspection PyUnresolvedReferences
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
|
@ -137,16 +104,6 @@ class CustomSwaggerView(SpectacularSwaggerView):
|
|||
|
||||
|
||||
class CustomRedocView(SpectacularRedocView):
|
||||
"""
|
||||
CustomRedocView provides a customized version of the SpectacularRedocView.
|
||||
|
||||
This class extends the SpectacularRedocView to include additional
|
||||
functionality, such as dynamically setting the `script_url` in the
|
||||
context data. It is designed to be used where customized behavior
|
||||
for rendering ReDoc UI is required, specifically adapting the script
|
||||
URL for the current request environment.
|
||||
"""
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
# noinspection PyUnresolvedReferences
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
|
@ -156,24 +113,7 @@ class CustomRedocView(SpectacularRedocView):
|
|||
|
||||
@extend_schema_view(**LANGUAGE_SCHEMA)
|
||||
class SupportedLanguagesView(APIView):
|
||||
"""
|
||||
Handles retrieving the list of supported languages.
|
||||
|
||||
This class provides an endpoint to return information about available languages.
|
||||
It is configured with relevant serializers, permission classes, and renderers
|
||||
for flexibility in response formats and access permissions. The endpoint
|
||||
supports retrieving the list of languages with their respective codes, names,
|
||||
and flags.
|
||||
|
||||
Attributes:
|
||||
serializer_class (Serializer): Serializer used for formatting the response data.
|
||||
permission_classes (list): Permissions applied to restrict the endpoint access.
|
||||
renderer_classes (list): Renderers available for formatting response output.
|
||||
|
||||
Methods:
|
||||
get(self, request): Retrieves the list of supported languages.
|
||||
|
||||
"""
|
||||
__doc__ = _("Returns a list of supported languages and their corresponding information.") # type: ignore [assignment]
|
||||
|
||||
serializer_class = LanguageSerializer
|
||||
permission_classes = [
|
||||
|
|
@ -186,7 +126,7 @@ class SupportedLanguagesView(APIView):
|
|||
YAMLRenderer,
|
||||
]
|
||||
|
||||
def get(self, request):
|
||||
def get(self, request: Request, *args, **kwargs) -> Response:
|
||||
return Response(
|
||||
data=self.serializer_class(
|
||||
[
|
||||
|
|
@ -205,30 +145,7 @@ class SupportedLanguagesView(APIView):
|
|||
|
||||
@extend_schema_view(**PARAMETERS_SCHEMA)
|
||||
class WebsiteParametersView(APIView):
|
||||
"""
|
||||
Handles operations related to website parameters.
|
||||
|
||||
This class is a Django Rest Framework view that allows clients to retrieve
|
||||
the parameters of a website. It uses different renderers to present the data
|
||||
in various formats. The view is publicly accessible.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
serializer_class
|
||||
A placeholder for a DRF serializer, it is set to None since no serializer
|
||||
is explicitly used in this view.
|
||||
permission_classes
|
||||
A list indicating the permissions required to access this view. In this case,
|
||||
`AllowAny`, meaning the view is open to everyone.
|
||||
renderer_classes
|
||||
A list of renderers available for this view, supporting CamelCase JSON,
|
||||
multipart forms, XML, and YAML formats.
|
||||
|
||||
Methods
|
||||
-------
|
||||
get(request)
|
||||
Handles HTTP GET requests to fetch website parameters.
|
||||
"""
|
||||
__doc__ = _("Returns the parameters of the website as a JSON object.") # type: ignore [assignment]
|
||||
|
||||
serializer_class = None
|
||||
permission_classes = [
|
||||
|
|
@ -241,30 +158,13 @@ class WebsiteParametersView(APIView):
|
|||
YAMLRenderer,
|
||||
]
|
||||
|
||||
def get(self, request):
|
||||
def get(self, request: Request, *args, **kwargs) -> Response:
|
||||
return Response(data=camelize(get_project_parameters()), status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@extend_schema_view(**CACHE_SCHEMA)
|
||||
class CacheOperatorView(APIView):
|
||||
"""
|
||||
View for managing cache operations.
|
||||
|
||||
This class provides an API view for handling cache operations such as setting cache
|
||||
data with a specified key and timeout. It leverages multiple renderer classes for
|
||||
serializing outputs in various formats.
|
||||
|
||||
Attributes:
|
||||
serializer_class (type): Serializer to validate and deserialize input data.
|
||||
permission_classes (list): List of permission classes to apply access
|
||||
restrictions.
|
||||
renderer_classes (list): List of renderer classes to serialize the output
|
||||
in desired formats.
|
||||
|
||||
Methods:
|
||||
post(request, *args, **kwargs): Handles HTTP POST requests to set cache
|
||||
data based on the provided key and timeout.
|
||||
"""
|
||||
__doc__ = _("Handles cache operations such as reading and setting cache data with a specified key and timeout.") # type: ignore [assignment]
|
||||
|
||||
serializer_class = CacheOperatorSerializer
|
||||
permission_classes = [
|
||||
|
|
@ -277,7 +177,7 @@ class CacheOperatorView(APIView):
|
|||
YAMLRenderer,
|
||||
]
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
def post(self, request: Request, *args, **kwargs) -> Response:
|
||||
return Response(
|
||||
data=web_cache(
|
||||
request,
|
||||
|
|
@ -291,22 +191,7 @@ class CacheOperatorView(APIView):
|
|||
|
||||
@extend_schema_view(**CONTACT_US_SCHEMA)
|
||||
class ContactUsView(APIView):
|
||||
"""
|
||||
Handles contact us form submissions via a REST API.
|
||||
|
||||
This view processes user submissions for a "Contact Us" form. It validates the received
|
||||
data using a serializer, applies rate limiting for IP-based requests, and sends emails
|
||||
asynchronously. The view is prepared to handle multiple response formats using configured
|
||||
renderers.
|
||||
|
||||
Attributes:
|
||||
serializer_class: The serializer class used to validate incoming data.
|
||||
renderer_classes: A list of renderers to support multiple response formats.
|
||||
|
||||
Methods:
|
||||
post: Handles POST requests to process form submissions.
|
||||
|
||||
"""
|
||||
__doc__ = _("Handles `contact us` form submissions.") # type: ignore [assignment]
|
||||
|
||||
serializer_class = ContactUsSerializer
|
||||
renderer_classes = [
|
||||
|
|
@ -317,7 +202,7 @@ class ContactUsView(APIView):
|
|||
]
|
||||
|
||||
@method_decorator(ratelimit(key="ip", rate="2/h", method="POST", block=True))
|
||||
def post(self, request, *args, **kwargs):
|
||||
def post(self, request: Request, *args, **kwargs) -> Response:
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
contact_us_email.delay(serializer.validated_data)
|
||||
|
|
@ -327,20 +212,7 @@ class ContactUsView(APIView):
|
|||
|
||||
@extend_schema_view(**REQUEST_CURSED_URL_SCHEMA)
|
||||
class RequestCursedURLView(APIView):
|
||||
"""
|
||||
Handles requests for processing and validating URLs from incoming POST requests.
|
||||
|
||||
Particularly intended for validating and processing URLs provided by clients. It uses rate-limiting, caching, and
|
||||
various response format renderers to optimize performance and ensure safe handling of external data.
|
||||
|
||||
Attributes:
|
||||
permission_classes (list): Specifies the permissions required to access this view.
|
||||
renderer_classes (list): Configures the response format renderers available for this view.
|
||||
|
||||
Methods:
|
||||
post: Handles the POST request to validate the URL, fetches its data if valid,
|
||||
and returns the processed response.
|
||||
"""
|
||||
__doc__ = _("Handles requests for processing and validating URLs from incoming POST requests.") # type: ignore [assignment]
|
||||
|
||||
permission_classes = [
|
||||
AllowAny,
|
||||
|
|
@ -353,7 +225,7 @@ class RequestCursedURLView(APIView):
|
|||
]
|
||||
|
||||
@method_decorator(ratelimit(key="ip", rate="10/h"))
|
||||
def post(self, request, *args, **kwargs):
|
||||
def post(self, request: Request, *args, **kwargs) -> Response:
|
||||
url = request.data.get("url")
|
||||
if not is_url_safe(url):
|
||||
return Response(
|
||||
|
|
@ -380,23 +252,7 @@ class RequestCursedURLView(APIView):
|
|||
|
||||
@extend_schema_view(**SEARCH_SCHEMA)
|
||||
class GlobalSearchView(APIView):
|
||||
"""
|
||||
Class-based view for handling global search functionality.
|
||||
|
||||
This class is designed to process search queries from HTTP GET requests. It is
|
||||
capable of rendering results in multiple formats including CamelCase JSON,
|
||||
MultiPart, XML, and YAML. The class uses a custom schema for API documentation
|
||||
and processes search queries passed as parameters in the request.
|
||||
|
||||
Attributes:
|
||||
renderer_classes (list): List of renderer classes used to serialize responses
|
||||
into various formats such as CamelCase JSON, MultiPart, XML, and YAML.
|
||||
|
||||
Methods:
|
||||
get: Handles HTTP GET requests by processing the search query and returning
|
||||
formatted search results.
|
||||
|
||||
"""
|
||||
__doc__ = _("Handles global search queries.") # type: ignore [assignment]
|
||||
|
||||
renderer_classes = [
|
||||
CamelCaseJSONRenderer,
|
||||
|
|
@ -405,31 +261,17 @@ class GlobalSearchView(APIView):
|
|||
YAMLRenderer,
|
||||
]
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
def get(self, request: Request, *args, **kwargs) -> Response:
|
||||
return Response(camelize({"results": process_query(query=request.GET.get("q", "").strip(), request=request)}))
|
||||
|
||||
|
||||
@extend_schema_view(**BUY_AS_BUSINESS_SCHEMA)
|
||||
class BuyAsBusinessView(APIView):
|
||||
"""
|
||||
View for buying as a business.
|
||||
|
||||
This view handles the logic of creating orders and processing transactions when a
|
||||
business makes a purchase. It ensures that the request data is properly validated
|
||||
and processed, handles the creation of the order, and starts the transaction process.
|
||||
The view also restricts the rate of requests based on the client's IP address
|
||||
to prevent abuse.
|
||||
|
||||
Attributes:
|
||||
schema (class): Extended schema for API documentation.
|
||||
|
||||
Methods:
|
||||
post(request, *_args, **kwargs):
|
||||
Handles the "POST" request to process a business purchase.
|
||||
"""
|
||||
__doc__ = _("Handles the logic of buying as a business without registration.") # type: ignore [assignment]
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@method_decorator(ratelimit(key="ip", rate="10/h" if not settings.DEBUG else "888/h"))
|
||||
def post(self, request, *_args, **kwargs):
|
||||
def post(self, request: Request, *args, **kwargs) -> Response:
|
||||
serializer = BuyAsBusinessOrderSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
order = Order.objects.create(status="MOMENTAL")
|
||||
|
|
@ -459,26 +301,7 @@ class BuyAsBusinessView(APIView):
|
|||
)
|
||||
|
||||
|
||||
def download_digital_asset_view(request, *args, **kwargs):
|
||||
"""
|
||||
Handles the downloading of a digital asset associated with an order. Ensures that users
|
||||
are permitted to download the asset only once. Validates the request, retrieves the file,
|
||||
and serves it as a downloadable response. Returns appropriate error responses for different
|
||||
failure scenarios.
|
||||
|
||||
Args:
|
||||
request: The HTTP request object containing information about the client request.
|
||||
*args: Additional positional arguments.
|
||||
**kwargs: Additional keyword arguments.
|
||||
|
||||
Raises:
|
||||
BadRequest: If the digital asset has already been downloaded.
|
||||
DigitalAssetDownload.DoesNotExist: If the requested digital asset cannot be found.
|
||||
|
||||
Returns:
|
||||
A FileResponse containing the digital asset file if the request is valid. Returns
|
||||
a JsonResponse with an error message if an error occurs during the process.
|
||||
"""
|
||||
def download_digital_asset_view(request: HttpRequest, *args, **kwargs) -> FileResponse | JsonResponse:
|
||||
try:
|
||||
logger.debug(f"download_digital_asset_view: {kwargs}")
|
||||
uuid = urlsafe_base64_decode(str(kwargs.get("order_product_uuid"))).decode("utf-8")
|
||||
|
|
@ -488,6 +311,9 @@ def download_digital_asset_view(request, *args, **kwargs):
|
|||
if download.num_downloads >= 1:
|
||||
raise BadRequest(_("you can only download the digital asset once"))
|
||||
|
||||
if download.order_product.status != "FINISHED":
|
||||
raise BadRequest(_("the order must be paid before downloading the digital asset"))
|
||||
|
||||
download.num_downloads += 1
|
||||
download.save()
|
||||
|
||||
|
|
@ -522,24 +348,16 @@ def download_digital_asset_view(request, *args, **kwargs):
|
|||
)
|
||||
|
||||
|
||||
def favicon_view(request, *args, **kwargs):
|
||||
"""
|
||||
Handles requests for the favicon of a website. This function attempts to serve the favicon
|
||||
file located in the static directory of the project. If the favicon file is not found,
|
||||
an HTTP 404 error is raised to indicate the resource is unavailable.
|
||||
# noinspection PyTypeChecker
|
||||
download_digital_asset_view.__doc__ = _( # type: ignore [assignment]
|
||||
"Handles the downloading of a digital asset associated with an order.\n"
|
||||
"This function attempts to serve the digital asset file located in the "
|
||||
"storage directory of the project. If the file is not found, an HTTP 404 "
|
||||
"error is raised to indicate the resource is unavailable."
|
||||
)
|
||||
|
||||
Args:
|
||||
request: The HTTP request object.
|
||||
*args: Additional positional arguments that are ignored in this function.
|
||||
**kwargs: Additional keyword arguments that are ignored in this function.
|
||||
|
||||
Returns:
|
||||
FileResponse: A file response containing the favicon image with the content-type
|
||||
"image/x-icon".
|
||||
|
||||
Raises:
|
||||
Http404: Raised if the favicon file is not found.
|
||||
"""
|
||||
def favicon_view(request: HttpRequest, *args, **kwargs) -> FileResponse | Http404:
|
||||
try:
|
||||
favicon_path = os.path.join(settings.BASE_DIR, "static/favicon.png")
|
||||
return FileResponse(open(favicon_path, "rb"), content_type="image/x-icon")
|
||||
|
|
@ -547,20 +365,22 @@ def favicon_view(request, *args, **kwargs):
|
|||
raise Http404(_("favicon not found")) from fnfe
|
||||
|
||||
|
||||
def index(request, *args, **kwargs):
|
||||
"""
|
||||
Redirects the request to the admin index page.
|
||||
# noinspection PyTypeChecker
|
||||
favicon_view.__doc__ = _( # type: ignore [assignment]
|
||||
"Handles requests for the favicon of a website.\n"
|
||||
"This function attempts to serve the favicon file located in the static directory of the project. "
|
||||
"If the favicon file is not found, an HTTP 404 error is raised to indicate the resource is unavailable."
|
||||
)
|
||||
|
||||
The function handles incoming HTTP requests and redirects them to the Django
|
||||
admin interface index page. It uses Django's `redirect` function for handling
|
||||
the HTTP redirection.
|
||||
|
||||
Args:
|
||||
request: The HttpRequest object representing the incoming request.
|
||||
*args: Additional positional arguments, if any.
|
||||
**kwargs: Additional keyword arguments, if any.
|
||||
|
||||
Returns:
|
||||
HttpResponseRedirect: An HTTP response that redirects to the admin index.
|
||||
"""
|
||||
def index(request: HttpRequest, *args, **kwargs) -> HttpResponseRedirect:
|
||||
return redirect("admin:index")
|
||||
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
index.__doc__ = _( # type: ignore [assignment]
|
||||
"Redirects the request to the admin index page. "
|
||||
"The function handles incoming HTTP requests and redirects them to the Django "
|
||||
"admin interface index page. It uses Django's `redirect` function for handling "
|
||||
"the HTTP redirection."
|
||||
)
|
||||
|
|
|
|||
573
core/viewsets.py
573
core/viewsets.py
|
|
@ -18,6 +18,7 @@ from rest_framework.decorators import action
|
|||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.renderers import MultiPartRenderer
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
from rest_framework_xml.renderers import XMLRenderer
|
||||
|
|
@ -123,26 +124,13 @@ logger = logging.getLogger("django")
|
|||
|
||||
|
||||
class EvibesViewSet(ModelViewSet):
|
||||
"""
|
||||
Defines a viewset for managing Evibes-related operations.
|
||||
|
||||
The EvibesViewSet class inherits from ModelViewSet and provides functionality
|
||||
for handling actions and operations on Evibes entities. It includes support
|
||||
for dynamic serializer classes based on the current action, customizable
|
||||
permissions, and rendering formats.
|
||||
|
||||
Attributes:
|
||||
action_serializer_classes: Dictionary mapping action names to their specific
|
||||
serializer classes.
|
||||
additional: Dictionary to hold additional data related to the view.
|
||||
permission_classes: List of permission classes applicable to this viewset.
|
||||
renderer_classes: List of renderer classes supported for response formatting.
|
||||
|
||||
Methods:
|
||||
get_serializer_class(self):
|
||||
Returns the serializer class for the current action or the default
|
||||
serializer class from the parent ModelViewSet.
|
||||
"""
|
||||
__doc__ = _( # type: ignore
|
||||
"Defines a viewset for managing Evibes-related operations. "
|
||||
"The EvibesViewSet class inherits from ModelViewSet and provides functionality "
|
||||
"for handling actions and operations on Evibes entities. It includes support "
|
||||
"for dynamic serializer classes based on the current action, customizable "
|
||||
"permissions, and rendering formats."
|
||||
)
|
||||
|
||||
action_serializer_classes: dict = {}
|
||||
additional: dict = {}
|
||||
|
|
@ -154,28 +142,14 @@ class EvibesViewSet(ModelViewSet):
|
|||
|
||||
|
||||
@extend_schema_view(**ATTRIBUTE_GROUP_SCHEMA)
|
||||
# noinspection PyUnusedLocal
|
||||
class AttributeGroupViewSet(EvibesViewSet):
|
||||
"""
|
||||
Represents a viewset for managing AttributeGroup objects.
|
||||
|
||||
Handles operations related to AttributeGroup, including filtering,
|
||||
serialization, and retrieval of data. This class is part of the
|
||||
application's API layer and provides a standardized way to process
|
||||
requests and responses for AttributeGroup data.
|
||||
|
||||
Attributes:
|
||||
queryset (QuerySet): QuerySet for retrieving all AttributeGroup objects.
|
||||
filter_backends (list): List of filter backends used to process filters
|
||||
in requests.
|
||||
filterset_fields (list): List of fields on which filtering operations
|
||||
can be performed.
|
||||
serializer_class (Serializer): Default serializer class used for
|
||||
processing AttributeGroup data during non-list view operations.
|
||||
action_serializer_classes (dict): Mapping of view actions to their
|
||||
specific serializer classes, allowing customization of serialization
|
||||
behavior for certain actions.
|
||||
"""
|
||||
__doc__ = _(
|
||||
"Represents a viewset for managing AttributeGroup objects. "
|
||||
"Handles operations related to AttributeGroup, including filtering, "
|
||||
"serialization, and retrieval of data. This class is part of the "
|
||||
"application's API layer and provides a standardized way to process "
|
||||
"requests and responses for AttributeGroup data."
|
||||
)
|
||||
|
||||
queryset = AttributeGroup.objects.all()
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
|
|
@ -187,28 +161,14 @@ class AttributeGroupViewSet(EvibesViewSet):
|
|||
|
||||
|
||||
@extend_schema_view(**ATTRIBUTE_SCHEMA)
|
||||
# noinspection PyUnusedLocal
|
||||
class AttributeViewSet(EvibesViewSet):
|
||||
"""
|
||||
Handles operations related to Attribute objects within the application.
|
||||
|
||||
Provides a set of API endpoints to interact with Attribute data. This class
|
||||
manages querying, filtering, and serialization of Attribute objects, allowing
|
||||
dynamic control over the data returned, such as filtering by specific fields
|
||||
or retrieving detailed versus simplified information depending on the request.
|
||||
|
||||
Attributes:
|
||||
queryset: The base QuerySet used to represent the set of Attribute
|
||||
objects available to this viewset.
|
||||
filter_backends: Defines the backends used for filtering request data,
|
||||
enabling query flexibility.
|
||||
filterset_fields: A list of model fields that can be filtered via the API.
|
||||
serializer_class: Represents the serializer used by default for
|
||||
serialization and deserialization of Attribute data.
|
||||
action_serializer_classes: A mapping that defines serializers used for
|
||||
specific actions, such as returning less detailed data for a `list`
|
||||
action.
|
||||
"""
|
||||
__doc__ = _(
|
||||
"Handles operations related to Attribute objects within the application. "
|
||||
"Provides a set of API endpoints to interact with Attribute data. This class "
|
||||
"manages querying, filtering, and serialization of Attribute objects, allowing "
|
||||
"dynamic control over the data returned, such as filtering by specific fields "
|
||||
"or retrieving detailed versus simplified information depending on the request."
|
||||
)
|
||||
|
||||
queryset = Attribute.objects.all()
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
|
|
@ -220,24 +180,14 @@ class AttributeViewSet(EvibesViewSet):
|
|||
|
||||
|
||||
@extend_schema_view(**ATTRIBUTE_VALUE_SCHEMA)
|
||||
# noinspection PyUnusedLocal
|
||||
class AttributeValueViewSet(EvibesViewSet):
|
||||
"""
|
||||
A viewset for managing AttributeValue objects.
|
||||
|
||||
This viewset provides functionality for listing, retrieving, creating, updating, and deleting
|
||||
AttributeValue objects. It integrates with Django REST Framework's viewset mechanisms and uses
|
||||
appropriate serializers for different actions. Filtering capabilities are provided through the
|
||||
DjangoFilterBackend.
|
||||
|
||||
Attributes:
|
||||
queryset (QuerySet): The base queryset for AttributeValue objects.
|
||||
filter_backends (list): A list of filtering backends applied to the viewset.
|
||||
filterset_fields (list): Fields of the model that can be used for filtering.
|
||||
serializer_class (Serializer): The default serializer class for the viewset.
|
||||
action_serializer_classes (dict): A dictionary mapping action names to their corresponding
|
||||
serializer classes.
|
||||
"""
|
||||
__doc__ = _(
|
||||
"A viewset for managing AttributeValue objects. "
|
||||
"This viewset provides functionality for listing, retrieving, creating, updating, and deleting "
|
||||
"AttributeValue objects. It integrates with Django REST Framework's viewset mechanisms and uses "
|
||||
"appropriate serializers for different actions. Filtering capabilities are provided through the "
|
||||
"DjangoFilterBackend."
|
||||
)
|
||||
|
||||
queryset = AttributeValue.objects.all()
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
|
|
@ -249,35 +199,14 @@ class AttributeValueViewSet(EvibesViewSet):
|
|||
|
||||
|
||||
@extend_schema_view(**CATEGORY_SCHEMA)
|
||||
# noinspection PyUnusedLocal
|
||||
class CategoryViewSet(EvibesViewSet):
|
||||
"""
|
||||
Manages views for Category-related operations.
|
||||
|
||||
The CategoryViewSet class is responsible for handling operations related to
|
||||
the Category model in the system. It supports retrieving, filtering, and
|
||||
serializing category data. The viewset also enforces permissions to ensure
|
||||
that only authorized users can access specific data.
|
||||
|
||||
Attributes:
|
||||
queryset: The base queryset used to retrieve category data, including
|
||||
prefetching related objects such as parents, children, attributes,
|
||||
and tags.
|
||||
filter_backends: A list of backends for applying filters to the category
|
||||
data.
|
||||
filterset_class: The filter class used to define filtering behavior for
|
||||
the category queryset.
|
||||
serializer_class: The default serializer class used for category objects
|
||||
when no specific action serializer is applied.
|
||||
action_serializer_classes: A dictionary mapping specific viewset actions
|
||||
(e.g., "list") to their corresponding serializer classes.
|
||||
|
||||
Methods:
|
||||
get_queryset():
|
||||
Retrieves the queryset for the viewset, applying permission checks
|
||||
and filtering out inactive categories for users without sufficient
|
||||
permissions.
|
||||
"""
|
||||
__doc__ = _(
|
||||
"Manages views for Category-related operations. "
|
||||
"The CategoryViewSet class is responsible for handling operations related to "
|
||||
"the Category model in the system. It supports retrieving, filtering, and "
|
||||
"serializing category data. The viewset also enforces permissions to ensure "
|
||||
"that only authorized users can access specific data."
|
||||
)
|
||||
|
||||
queryset = Category.objects.all().prefetch_related("parent", "children", "attributes", "tags")
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
|
|
@ -318,6 +247,7 @@ class CategoryViewSet(EvibesViewSet):
|
|||
return qs
|
||||
return qs.filter(is_active=True)
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@action(
|
||||
detail=True,
|
||||
methods=["get"],
|
||||
|
|
@ -326,7 +256,7 @@ class CategoryViewSet(EvibesViewSet):
|
|||
AllowAny,
|
||||
],
|
||||
)
|
||||
def seo_meta(self, request, **kwargs):
|
||||
def seo_meta(self, request: Request, *args, **kwargs) -> Response:
|
||||
category = self.get_object()
|
||||
|
||||
title = f"{category.name} | {config.PROJECT_NAME}"
|
||||
|
|
@ -380,28 +310,13 @@ class CategoryViewSet(EvibesViewSet):
|
|||
return Response(SeoSnapshotSerializer(payload).data)
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
class BrandViewSet(EvibesViewSet):
|
||||
"""
|
||||
Represents a viewset for managing Brand instances.
|
||||
|
||||
This class provides functionality for querying, filtering, and
|
||||
serializing Brand objects. It uses Django's ViewSet framework
|
||||
to simplify the implementation of API endpoints for Brand objects.
|
||||
|
||||
Attributes:
|
||||
queryset: The base queryset containing all Brand instances.
|
||||
filter_backends: A list of filtering backends to apply to the
|
||||
queryset. The default is [DjangoFilterBackend].
|
||||
filterset_class: The filter class used to define the filtering
|
||||
logic for this viewset. The default is BrandFilter.
|
||||
serializer_class: The default serializer class used for the
|
||||
detailed representation of Brand objects. The default is
|
||||
BrandDetailSerializer.
|
||||
action_serializer_classes: A dictionary mapping specific actions
|
||||
to their corresponding serializer classes. The "list" action
|
||||
uses the BrandSimpleSerializer class.
|
||||
"""
|
||||
__doc__ = _(
|
||||
"Represents a viewset for managing Brand instances. "
|
||||
"This class provides functionality for querying, filtering, and "
|
||||
"serializing Brand objects. It uses Django's ViewSet framework "
|
||||
"to simplify the implementation of API endpoints for Brand objects."
|
||||
)
|
||||
|
||||
queryset = Brand.objects.all()
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
|
|
@ -448,6 +363,7 @@ class BrandViewSet(EvibesViewSet):
|
|||
|
||||
return queryset
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@action(
|
||||
detail=True,
|
||||
methods=["get"],
|
||||
|
|
@ -456,7 +372,7 @@ class BrandViewSet(EvibesViewSet):
|
|||
AllowAny,
|
||||
],
|
||||
)
|
||||
def seo_meta(self, request, **kwargs):
|
||||
def seo_meta(self, request: Request, *args, **kwargs) -> Response:
|
||||
brand = self.get_object()
|
||||
|
||||
title = f"{brand.name} | {config.PROJECT_NAME}"
|
||||
|
|
@ -506,31 +422,15 @@ class BrandViewSet(EvibesViewSet):
|
|||
|
||||
|
||||
@extend_schema_view(**PRODUCT_SCHEMA)
|
||||
# noinspection PyUnusedLocal
|
||||
class ProductViewSet(EvibesViewSet):
|
||||
"""
|
||||
Manages operations related to the `Product` model in the system.
|
||||
|
||||
This class provides a viewset for managing products, including their filtering, serialization,
|
||||
and operations on specific instances. It extends from `EvibesViewSet` to use common
|
||||
functionality and integrates with the Django REST framework for RESTful API operations.
|
||||
Includes methods for retrieving product details, applying permissions, and accessing
|
||||
related feedback of a product.
|
||||
|
||||
Attributes:
|
||||
queryset: The base queryset to retrieve `Product` objects with prefetch optimization.
|
||||
filter_backends: Specifies the filtering mechanism for the list views.
|
||||
filterset_class: Defines the filter class to be used for filtering products.
|
||||
serializer_class: The default serializer class for product details.
|
||||
action_serializer_classes: Specific serializer mappings for action methods.
|
||||
lookup_field: Field representing the object's lookup value in URLs.
|
||||
lookup_url_kwarg: Field key used to extract the object's lookup value from URL.
|
||||
|
||||
Methods:
|
||||
get_queryset: Retrieves the queryset with user-specific filtering applied.
|
||||
get_object: Fetches a single object based on its identifier, applying permissions.
|
||||
feedbacks: Fetches feedback associated with a specific product.
|
||||
"""
|
||||
__doc__ = _(
|
||||
"Manages operations related to the `Product` model in the system. "
|
||||
"This class provides a viewset for managing products, including their filtering, serialization, "
|
||||
"and operations on specific instances. It extends from `EvibesViewSet` to use common "
|
||||
"functionality and integrates with the Django REST framework for RESTful API operations. "
|
||||
"Includes methods for retrieving product details, applying permissions, and accessing "
|
||||
"related feedback of a product."
|
||||
)
|
||||
|
||||
queryset = Product.objects.prefetch_related("tags", "attributes", "stocks", "images").all()
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
|
|
@ -584,14 +484,16 @@ class ProductViewSet(EvibesViewSet):
|
|||
self.check_object_permissions(self.request, obj)
|
||||
return obj
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@action(detail=True, methods=["get"], url_path="feedbacks")
|
||||
def feedbacks(self, request, **kwargs):
|
||||
def feedbacks(self, request: Request, *args, **kwargs) -> Response:
|
||||
product = self.get_object()
|
||||
qs = Feedback.objects.filter(order_product__product=product)
|
||||
if not request.user.has_perm("core.view_feedback"):
|
||||
qs = qs.filter(is_active=True)
|
||||
return Response(data=FeedbackSimpleSerializer(qs, many=True).data)
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@action(
|
||||
detail=True,
|
||||
methods=["get"],
|
||||
|
|
@ -600,7 +502,7 @@ class ProductViewSet(EvibesViewSet):
|
|||
AllowAny,
|
||||
],
|
||||
)
|
||||
def seo_meta(self, request, **kwargs):
|
||||
def seo_meta(self, request: Request, *args, **kwargs) -> Response:
|
||||
p = self.get_object()
|
||||
images = list(p.images.all()[:6])
|
||||
rating = {"value": p.rating, "count": p.feedbacks_count}
|
||||
|
|
@ -640,27 +542,15 @@ class ProductViewSet(EvibesViewSet):
|
|||
return Response(SeoSnapshotSerializer(payload).data)
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
class VendorViewSet(EvibesViewSet):
|
||||
"""
|
||||
Represents a viewset for managing Vendor objects.
|
||||
|
||||
This viewset allows fetching, filtering, and serializing Vendor data.
|
||||
It defines the queryset, filter configurations, and serializer classes
|
||||
used to handle different actions. The purpose of this class is to
|
||||
provide streamlined access to Vendor-related resources through the
|
||||
Django REST framework.
|
||||
|
||||
Attributes:
|
||||
queryset: A QuerySet containing all Vendor objects.
|
||||
filter_backends: A list containing configured filter backends.
|
||||
filterset_fields: A list of fields that can be used for filtering
|
||||
Vendor records.
|
||||
serializer_class: The default serializer class used for this
|
||||
viewset.
|
||||
action_serializer_classes: A dictionary mapping specific actions
|
||||
(e.g., "list") to custom serializer classes for those actions.
|
||||
"""
|
||||
__doc__ = _(
|
||||
"Represents a viewset for managing Vendor objects. "
|
||||
"This viewset allows fetching, filtering, and serializing Vendor data. "
|
||||
"It defines the queryset, filter configurations, and serializer classes "
|
||||
"used to handle different actions. The purpose of this class is to "
|
||||
"provide streamlined access to Vendor-related resources through the "
|
||||
"Django REST framework."
|
||||
)
|
||||
|
||||
queryset = Vendor.objects.all()
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
|
|
@ -672,28 +562,15 @@ class VendorViewSet(EvibesViewSet):
|
|||
|
||||
|
||||
@extend_schema_view(**FEEDBACK_SCHEMA)
|
||||
# noinspection PyUnusedLocal
|
||||
class FeedbackViewSet(EvibesViewSet):
|
||||
"""
|
||||
Representation of a view set handling Feedback objects.
|
||||
|
||||
This class manages operations related to Feedback objects, including listing,
|
||||
filtering, and retrieving details. The purpose of this view set is to provide
|
||||
different serializers for different actions and implement permission-based
|
||||
handling of accessible Feedback objects. It extends the base `EvibesViewSet`
|
||||
and makes use of Django's filtering system for querying data.
|
||||
|
||||
Attributes:
|
||||
queryset: The base queryset for Feedback objects used in this view set.
|
||||
filter_backends: List of filter backends to apply, specifically
|
||||
`DjangoFilterBackend` for this view set.
|
||||
filterset_class: Class specifying the filter set used for querying
|
||||
Feedback objects.
|
||||
serializer_class: Default serializer class used for this view set.
|
||||
action_serializer_classes: A dictionary mapping action names to specific
|
||||
serializer classes. For example, the "list" action uses
|
||||
`FeedbackSimpleSerializer`.
|
||||
"""
|
||||
__doc__ = _(
|
||||
"Representation of a view set handling Feedback objects. "
|
||||
"This class manages operations related to Feedback objects, including listing, "
|
||||
"filtering, and retrieving details. The purpose of this view set is to provide "
|
||||
"different serializers for different actions and implement permission-based "
|
||||
"handling of accessible Feedback objects. It extends the base `EvibesViewSet` "
|
||||
"and makes use of Django's filtering system for querying data."
|
||||
)
|
||||
|
||||
queryset = Feedback.objects.all()
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
|
|
@ -711,53 +588,16 @@ class FeedbackViewSet(EvibesViewSet):
|
|||
|
||||
|
||||
@extend_schema_view(**ORDER_SCHEMA)
|
||||
# noinspection PyUnusedLocal
|
||||
class OrderViewSet(EvibesViewSet):
|
||||
"""
|
||||
ViewSet for managing orders and related operations.
|
||||
|
||||
This class provides functionality to retrieve, modify, and manage order objects.
|
||||
It includes various endpoints for handling order operations such as adding or
|
||||
removing products, performing purchases for registered as well as unregistered
|
||||
users, and retrieving the current authenticated user's pending orders.
|
||||
|
||||
The ViewSet uses multiple serializers based on the specific action being
|
||||
performed and enforces permissions accordingly while interacting with order data.
|
||||
|
||||
Attributes:
|
||||
lookup_field (str): Field name used for performing object lookup.
|
||||
lookup_url_kwarg (str): URL keyword argument used for object lookup. Defaults
|
||||
to `lookup_field`.
|
||||
queryset (QuerySet): Default queryset for retrieving order objects, with
|
||||
prefetched related order products.
|
||||
filter_backends (list): List of backends applied for filtering the queryset.
|
||||
filterset_class (type): Filtering class applied to the queryset for request-based
|
||||
customizations.
|
||||
serializer_class (type): Default serializer used if no specific serializer is
|
||||
defined for an action.
|
||||
action_serializer_classes (dict): Mapping of actions to their respective serializers.
|
||||
Used to determine the serializer dynamically based on the requested action.
|
||||
additional (dict): Additional settings for specific actions.
|
||||
|
||||
Methods:
|
||||
get_serializer_class: Returns the serializer class based on the specific
|
||||
action being requested.
|
||||
get_queryset: Adjusts the queryset based on the request user's permissions,
|
||||
favoring anonymous or limited query access for unauthenticated users.
|
||||
get_object: Retrieves a specific order object based on the lookup value, either
|
||||
its UUID or a human-readable ID.
|
||||
current: Retrieves the authenticated user's current pending order.
|
||||
buy: Processes an order purchase for an authenticated user with optional parameters
|
||||
such as balance and payment overrides, promocodes, and billing/shipping addresses.
|
||||
buy_unregistered: Processes an order purchase for unauthenticated users with product,
|
||||
customer details, and payment information.
|
||||
add_order_product: Adds a product, with optional attributes, to an order specified by UUID.
|
||||
remove_order_product: Removes a product, with optional attributes, from an order specified
|
||||
by UUID.
|
||||
bulk_add_order_products: Adds multiple products with optional attributes to an order.
|
||||
bulk_remove_order_products: Removes multiple products with optional attributes from
|
||||
an order.
|
||||
"""
|
||||
__doc__ = _(
|
||||
"ViewSet for managing orders and related operations. "
|
||||
"This class provides functionality to retrieve, modify, and manage order objects. "
|
||||
"It includes various endpoints for handling order operations such as adding or "
|
||||
"removing products, performing purchases for registered as well as unregistered "
|
||||
"users, and retrieving the current authenticated user's pending orders. "
|
||||
"The ViewSet uses multiple serializers based on the specific action being "
|
||||
"performed and enforces permissions accordingly while interacting with order data."
|
||||
)
|
||||
|
||||
lookup_field = "lookup_value"
|
||||
lookup_url_kwarg = "lookup_value"
|
||||
|
|
@ -803,7 +643,7 @@ class OrderViewSet(EvibesViewSet):
|
|||
return obj
|
||||
|
||||
@action(detail=False, methods=["get"], url_path="current")
|
||||
def current(self, request):
|
||||
def current(self, request: Request, *args, **kwargs) -> Response:
|
||||
if not request.user.is_authenticated:
|
||||
raise PermissionDenied(permission_denied_message)
|
||||
try:
|
||||
|
|
@ -816,7 +656,7 @@ class OrderViewSet(EvibesViewSet):
|
|||
)
|
||||
|
||||
@action(detail=True, methods=["post"], url_path="buy")
|
||||
def buy(self, request, **kwargs):
|
||||
def buy(self, request: Request, *args, **kwargs) -> Response:
|
||||
serializer = BuyOrderSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
lookup_val = kwargs.get(self.lookup_field)
|
||||
|
|
@ -845,7 +685,7 @@ class OrderViewSet(EvibesViewSet):
|
|||
|
||||
@action(detail=False, methods=["post"], url_path="buy_unregistered")
|
||||
@method_decorator(ratelimit(key="ip", rate="10/h" if not settings.DEBUG else "888/h"))
|
||||
def buy_unregistered(self, request):
|
||||
def buy_unregistered(self, request: Request, *args, **kwargs) -> Response:
|
||||
serializer = BuyUnregisteredOrderSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
order = Order.objects.create(status="MOMENTAL")
|
||||
|
|
@ -866,7 +706,7 @@ class OrderViewSet(EvibesViewSet):
|
|||
return Response(status=status.HTTP_400_BAD_REQUEST, data={"detail": str(e)})
|
||||
|
||||
@action(detail=True, methods=["post"], url_path="add_order_product")
|
||||
def add_order_product(self, request, **kwargs):
|
||||
def add_order_product(self, request: Request, *args, **kwargs) -> Response:
|
||||
serializer = AddOrderProductSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
lookup_val = kwargs.get(self.lookup_field)
|
||||
|
|
@ -884,7 +724,7 @@ class OrderViewSet(EvibesViewSet):
|
|||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@action(detail=True, methods=["post"], url_path="remove_order_product")
|
||||
def remove_order_product(self, request, **kwargs):
|
||||
def remove_order_product(self, request: Request, *args, **kwargs) -> Response:
|
||||
serializer = RemoveOrderProductSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
lookup_val = kwargs.get(self.lookup_field)
|
||||
|
|
@ -902,7 +742,7 @@ class OrderViewSet(EvibesViewSet):
|
|||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@action(detail=True, methods=["post"], url_path="bulk_add_order_products")
|
||||
def bulk_add_order_products(self, request, **kwargs):
|
||||
def bulk_add_order_products(self, request: Request, *args, **kwargs) -> Response:
|
||||
serializer = BulkAddOrderProductsSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
lookup_val = kwargs.get(self.lookup_field)
|
||||
|
|
@ -919,7 +759,7 @@ class OrderViewSet(EvibesViewSet):
|
|||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@action(detail=True, methods=["post"], url_path="bulk_remove_order_products")
|
||||
def bulk_remove_order_products(self, request, **kwargs):
|
||||
def bulk_remove_order_products(self, request: Request, *args, **kwargs) -> Response:
|
||||
serializer = BulkRemoveOrderProductsSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
lookup_val = kwargs.get(self.lookup_field)
|
||||
|
|
@ -937,35 +777,14 @@ class OrderViewSet(EvibesViewSet):
|
|||
|
||||
|
||||
@extend_schema_view(**ORDER_PRODUCT_SCHEMA)
|
||||
# noinspection PyUnusedLocal
|
||||
class OrderProductViewSet(EvibesViewSet):
|
||||
"""
|
||||
Provides a viewset for managing OrderProduct entities.
|
||||
|
||||
This viewset enables CRUD operations and custom actions specific to the
|
||||
OrderProduct model. It includes filtering, permission checks, and
|
||||
serializer switching based on the requested action. Additionally, it
|
||||
provides a detailed action for handling feedback on OrderProduct
|
||||
instances.
|
||||
|
||||
Attributes:
|
||||
queryset (QuerySet): The base queryset for OrderProduct objects.
|
||||
filter_backends (list): Backends responsible for handling filtering
|
||||
mechanisms.
|
||||
filterset_fields (list[str]): Fields available for API filtering.
|
||||
serializer_class (Serializer): Default serializer class for CRUD
|
||||
operations.
|
||||
action_serializer_classes (dict[str, Serializer]): Mapping of
|
||||
specific actions to their corresponding serializer classes.
|
||||
|
||||
Methods:
|
||||
get_queryset: Overrides the default queryset to enforce user
|
||||
permissions.
|
||||
|
||||
Actions:
|
||||
do_feedback: Custom action to add, remove, or manage feedback for
|
||||
an OrderProduct instance.
|
||||
"""
|
||||
__doc__ = _(
|
||||
"Provides a viewset for managing OrderProduct entities. "
|
||||
"This viewset enables CRUD operations and custom actions specific to the "
|
||||
"OrderProduct model. It includes filtering, permission checks, and "
|
||||
"serializer switching based on the requested action. Additionally, it "
|
||||
"provides a detailed action for handling feedback on OrderProduct instances"
|
||||
)
|
||||
|
||||
queryset = OrderProduct.objects.all()
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
|
|
@ -987,7 +806,7 @@ class OrderProductViewSet(EvibesViewSet):
|
|||
return qs.filter(user=user)
|
||||
|
||||
@action(detail=True, methods=["post"], url_path="do_feedback")
|
||||
def do_feedback(self, request, **kwargs):
|
||||
def do_feedback(self, request: Request, *args, **kwargs) -> Response:
|
||||
serializer = self.get_serializer(request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
try:
|
||||
|
|
@ -1010,29 +829,8 @@ class OrderProductViewSet(EvibesViewSet):
|
|||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
class ProductImageViewSet(EvibesViewSet):
|
||||
"""
|
||||
Manages operations related to Product images in the application.
|
||||
|
||||
This class-based view set provides endpoints to manage and access ProductImage
|
||||
objects. It supports filtering, serialization, and customized serializers for
|
||||
different actions to handle ProductImage data.
|
||||
|
||||
Attributes:
|
||||
queryset (QuerySet): A Django QuerySet consisting of all ProductImage
|
||||
instances within the system.
|
||||
filter_backends (list): A list of filter backends that determine the
|
||||
filtering behavior on querysets. Set to [DjangoFilterBackend].
|
||||
filterset_fields (list): Fields that can be used for filtering data.
|
||||
Includes "product", "priority", and "is_active".
|
||||
serializer_class (Serializer): The default serializer class used for
|
||||
serializing and deserializing ProductImage data. Set to
|
||||
ProductImageDetailSerializer.
|
||||
action_serializer_classes (dict): A mapping of action names to specific
|
||||
serializer classes. For the "list" action, ProductImageSimpleSerializer
|
||||
is used.
|
||||
"""
|
||||
__doc__ = _("Manages operations related to Product images in the application. ")
|
||||
|
||||
queryset = ProductImage.objects.all()
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
|
|
@ -1043,27 +841,8 @@ class ProductImageViewSet(EvibesViewSet):
|
|||
}
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
class PromoCodeViewSet(EvibesViewSet):
|
||||
"""
|
||||
Manages the retrieval and handling of PromoCode instances through various
|
||||
API actions.
|
||||
|
||||
This class extends the functionality of the EvibesViewSet to provide a
|
||||
customized view set for PromoCode objects. It includes filtering capabilities,
|
||||
uses specific serializers for different actions, and limits data access
|
||||
based on user permissions. The primary purpose is to enable API operations
|
||||
related to PromoCodes while enforcing security and filtering.
|
||||
|
||||
Attributes:
|
||||
queryset: A queryset of all PromoCode objects in the database.
|
||||
filter_backends: Backend classes responsible for filtering queryset data.
|
||||
filterset_fields: Fields supported for filtering PromoCode data.
|
||||
serializer_class: The default serializer class used for instances when no
|
||||
specific action-based serializer is defined.
|
||||
action_serializer_classes: A dictionary mapping specific actions (like
|
||||
"list") to their corresponding serializer classes.
|
||||
"""
|
||||
__doc__ = _("Manages the retrieval and handling of PromoCode instances through various API actions.")
|
||||
|
||||
queryset = PromoCode.objects.all()
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
|
|
@ -1083,16 +862,8 @@ class PromoCodeViewSet(EvibesViewSet):
|
|||
return qs.filter(user=user)
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
class PromotionViewSet(EvibesViewSet):
|
||||
"""
|
||||
Represents a view set for managing promotions.
|
||||
|
||||
This class provides operations to handle retrieval, filtering, and serialization
|
||||
of promotion objects. It leverages Django REST framework capabilities such as
|
||||
queryset management, filter backends, and serializer customization for handling
|
||||
different views or actions efficiently.
|
||||
"""
|
||||
__doc__ = _("Represents a view set for managing promotions. ")
|
||||
|
||||
queryset = Promotion.objects.all()
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
|
|
@ -1103,28 +874,8 @@ class PromotionViewSet(EvibesViewSet):
|
|||
}
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
class StockViewSet(EvibesViewSet):
|
||||
"""
|
||||
Handles operations related to Stock data in the system.
|
||||
|
||||
The StockViewSet class is a viewset that provides methods for retrieving,
|
||||
filtering, and serializing Stock data. It uses Django's filter
|
||||
backends to enable filtering based on specified fields and supports
|
||||
custom serializers for different actions.
|
||||
|
||||
Attributes:
|
||||
queryset (QuerySet): A queryset of all Stock objects.
|
||||
filter_backends (list): A list of filter backends to be applied.
|
||||
filterset_fields (list of str): Fields on which the filtering
|
||||
is permitted. These fields include "vendor", "product", "sku",
|
||||
and "is_active".
|
||||
serializer_class (Serializer): The primary serializer used
|
||||
for Stock detail representation.
|
||||
action_serializer_classes (dict): A dictionary mapping action names
|
||||
to their respective serializers. For the "list" action,
|
||||
StockSimpleSerializer is used.
|
||||
"""
|
||||
__doc__ = _("Handles operations related to Stock data in the system.")
|
||||
|
||||
queryset = Stock.objects.all()
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
|
|
@ -1136,46 +887,16 @@ class StockViewSet(EvibesViewSet):
|
|||
|
||||
|
||||
@extend_schema_view(**WISHLIST_SCHEMA)
|
||||
# noinspection PyUnusedLocal
|
||||
class WishlistViewSet(EvibesViewSet):
|
||||
"""
|
||||
ViewSet for managing Wishlist operations.
|
||||
|
||||
The WishlistViewSet provides endpoints for interacting with a user's wish list,
|
||||
allowing for the retrieval, modification, and customization of products within
|
||||
the wish list. This ViewSet facilitates functionality such as adding, removing,
|
||||
and bulk actions for wishlist products. Permission checks are integrated to
|
||||
ensure that users can only manage their own wishlists unless explicit permissions
|
||||
are granted.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
queryset : QuerySet
|
||||
The base queryset for retrieving wishlist objects.
|
||||
filter_backends : list
|
||||
List of backend filters to apply to the queryset.
|
||||
filterset_fields : list
|
||||
Fields for which filtering is allowed in queries.
|
||||
serializer_class : Serializer
|
||||
The default serializer class used for wishlist objects.
|
||||
action_serializer_classes : dict
|
||||
A map of serializers used for specific actions.
|
||||
|
||||
Methods
|
||||
-------
|
||||
get_queryset()
|
||||
Retrieves the queryset, filtered based on the user's permissions.
|
||||
current(request)
|
||||
Retrieves the currently authenticated user's wishlist.
|
||||
add_wishlist_product(request, **kwargs)
|
||||
Adds a product to a specific wishlist.
|
||||
remove_wishlist_product(request, **kwargs)
|
||||
Removes a product from a specific wishlist.
|
||||
bulk_add_wishlist_products(request, **kwargs)
|
||||
Adds multiple products to a specific wishlist.
|
||||
bulk_remove_wishlist_products(request, **kwargs)
|
||||
Removes multiple products from a specific wishlist.
|
||||
"""
|
||||
__doc__ = _(
|
||||
"ViewSet for managing Wishlist operations. "
|
||||
"The WishlistViewSet provides endpoints for interacting with a user's wish list, "
|
||||
"allowing for the retrieval, modification, and customization of products within "
|
||||
"the wish list. This ViewSet facilitates functionality such as adding, removing, "
|
||||
"and bulk actions for wishlist products. Permission checks are integrated to "
|
||||
"ensure that users can only manage their own wishlists unless explicit permissions "
|
||||
"are granted."
|
||||
)
|
||||
|
||||
queryset = Wishlist.objects.all()
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
|
|
@ -1194,8 +915,9 @@ class WishlistViewSet(EvibesViewSet):
|
|||
|
||||
return qs.filter(user=user)
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@action(detail=False, methods=["get"], url_path="current")
|
||||
def current(self, request):
|
||||
def current(self, request: Request, *args, **kwargs) -> Response:
|
||||
if not request.user.is_authenticated:
|
||||
raise PermissionDenied(permission_denied_message)
|
||||
wishlist = Wishlist.objects.get(user=request.user)
|
||||
|
|
@ -1206,8 +928,9 @@ class WishlistViewSet(EvibesViewSet):
|
|||
data=WishlistDetailSerializer(wishlist).data,
|
||||
)
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@action(detail=True, methods=["post"], url_path="add_wishlist_product")
|
||||
def add_wishlist_product(self, request, **kwargs):
|
||||
def add_wishlist_product(self, request: Request, *args, **kwargs) -> Response:
|
||||
serializer = AddWishlistProductSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
try:
|
||||
|
|
@ -1223,8 +946,9 @@ class WishlistViewSet(EvibesViewSet):
|
|||
except Wishlist.DoesNotExist:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@action(detail=True, methods=["post"], url_path="remove_wishlist_product")
|
||||
def remove_wishlist_product(self, request, **kwargs):
|
||||
def remove_wishlist_product(self, request: Request, *args, **kwargs) -> Response:
|
||||
serializer = RemoveWishlistProductSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
try:
|
||||
|
|
@ -1240,8 +964,9 @@ class WishlistViewSet(EvibesViewSet):
|
|||
except Wishlist.DoesNotExist:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@action(detail=True, methods=["post"], url_path="bulk_add_wishlist_product")
|
||||
def bulk_add_wishlist_products(self, request, **kwargs):
|
||||
def bulk_add_wishlist_products(self, request: Request, *args, **kwargs) -> Response:
|
||||
serializer = BulkAddWishlistProductSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
try:
|
||||
|
|
@ -1257,8 +982,9 @@ class WishlistViewSet(EvibesViewSet):
|
|||
except Order.DoesNotExist:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@action(detail=True, methods=["post"], url_path="bulk_remove_wishlist_product")
|
||||
def bulk_remove_wishlist_products(self, request, **kwargs):
|
||||
def bulk_remove_wishlist_products(self, request: Request, *args, **kwargs) -> Response:
|
||||
serializer = BulkRemoveWishlistProductSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
try:
|
||||
|
|
@ -1276,23 +1002,13 @@ class WishlistViewSet(EvibesViewSet):
|
|||
|
||||
|
||||
@extend_schema_view(**ADDRESS_SCHEMA)
|
||||
# noinspection PyUnusedLocal
|
||||
class AddressViewSet(EvibesViewSet):
|
||||
"""
|
||||
This class provides viewset functionality for managing `Address` objects.
|
||||
|
||||
The AddressViewSet class enables CRUD operations, filtering, and custom actions
|
||||
related to address entities. It includes specialized behaviors for different HTTP
|
||||
methods, serializer overrides, and permission handling based on the request context.
|
||||
|
||||
Attributes:
|
||||
pagination_class: Specifies pagination class for the viewset, set to None.
|
||||
filter_backends: List of backend classes for filtering querysets.
|
||||
filterset_class: Specifies the filter class for filtering address objects.
|
||||
queryset: Default queryset containing all Address objects.
|
||||
serializer_class: Default serializer class for address objects.
|
||||
additional: Dictionary of additional options for this viewset.
|
||||
"""
|
||||
__doc__ = _(
|
||||
"This class provides viewset functionality for managing `Address` objects. "
|
||||
"The AddressViewSet class enables CRUD operations, filtering, and custom actions "
|
||||
"related to address entities. It includes specialized behaviors for different HTTP "
|
||||
"methods, serializer overrides, and permission handling based on the request context."
|
||||
)
|
||||
|
||||
pagination_class = None
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
|
|
@ -1317,14 +1033,14 @@ class AddressViewSet(EvibesViewSet):
|
|||
|
||||
return Address.objects.none()
|
||||
|
||||
def retrieve(self, request, **kwargs):
|
||||
def retrieve(self, request: Request, *args, **kwargs) -> Response:
|
||||
try:
|
||||
address = Address.objects.get(uuid=kwargs.get("pk"))
|
||||
return Response(status=status.HTTP_200_OK, data=self.get_serializer(address).data)
|
||||
except Address.DoesNotExist:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def create(self, request, **kwargs):
|
||||
def create(self, request: Request, *args, **kwargs) -> Response:
|
||||
create_serializer = AddressCreateSerializer(data=request.data, context={"request": request})
|
||||
create_serializer.is_valid(raise_exception=True)
|
||||
|
||||
|
|
@ -1337,8 +1053,9 @@ class AddressViewSet(EvibesViewSet):
|
|||
data=output_serializer.data,
|
||||
)
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@action(detail=False, methods=["get"], url_path="autocomplete")
|
||||
def autocomplete(self, request):
|
||||
def autocomplete(self, request: Request, *args, **kwargs) -> Response:
|
||||
serializer = AddressAutocompleteInputSerializer(data=request.query_params)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
|
|
@ -1359,28 +1076,14 @@ class AddressViewSet(EvibesViewSet):
|
|||
)
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
class ProductTagViewSet(EvibesViewSet):
|
||||
"""
|
||||
Handles operations related to Product Tags within the application.
|
||||
|
||||
This class provides functionality for retrieving, filtering, and serializing
|
||||
Product Tag objects. It supports flexible filtering on specific attributes
|
||||
using the specified filter backend and dynamically uses different serializers
|
||||
based on the action being performed.
|
||||
|
||||
Attributes:
|
||||
queryset: The base queryset containing all ProductTag objects.
|
||||
filter_backends: A list of backends used to filter the queryset.
|
||||
filterset_fields: Fields available for filtering the queryset. Includes
|
||||
'tag_name' for filtering by the name of the tag, and 'is_active' for
|
||||
filtering active/inactive tags.
|
||||
serializer_class: The default serializer class is used when no specific
|
||||
serializer is defined for an action.
|
||||
action_serializer_classes: A dictionary mapping specific actions (e.g.,
|
||||
'list') to custom serializers. Uses ProductTagSimpleSerializer
|
||||
for the 'list' action.
|
||||
"""
|
||||
__doc__ = _(
|
||||
"Handles operations related to Product Tags within the application. "
|
||||
"This class provides functionality for retrieving, filtering, and serializing "
|
||||
"Product Tag objects. It supports flexible filtering on specific attributes "
|
||||
"using the specified filter backend and dynamically uses different serializers "
|
||||
"based on the action being performed."
|
||||
)
|
||||
|
||||
queryset = ProductTag.objects.all()
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import json
|
||||
from typing import Any
|
||||
|
||||
from django import forms
|
||||
from django.forms.renderers import DjangoTemplates
|
||||
|
||||
|
||||
class JSONTableWidget(forms.Widget):
|
||||
template_name = "json_table_widget.html"
|
||||
|
||||
def format_value(self, value):
|
||||
def format_value(self, value: str | dict[str, Any]):
|
||||
if isinstance(value, dict):
|
||||
return value
|
||||
try:
|
||||
|
|
@ -16,12 +18,14 @@ class JSONTableWidget(forms.Widget):
|
|||
value = {}
|
||||
return value
|
||||
|
||||
def render(self, name, value, attrs=None, renderer=None):
|
||||
def render(
|
||||
self, name: str, value: str | dict[str, Any], attrs: dict | None = None, renderer: DjangoTemplates | None = None
|
||||
):
|
||||
value = self.format_value(value)
|
||||
return super().render(name, value, attrs, renderer)
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
def value_from_datadict(self, data, files, name):
|
||||
def value_from_datadict(self, data: dict[str, Any], files: list, name: str):
|
||||
json_data = {}
|
||||
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -61,8 +61,6 @@ services:
|
|||
image: redis:7.4
|
||||
restart: always
|
||||
command: redis-server --save "" --appendonly no --slave-read-only no --requirepass "$REDIS_PASSWORD"
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- ./services_data/redis:/data
|
||||
env_file:
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|||
SECRET_KEY = getenv("SECRET_KEY", "SUPER_SECRET_KEY")
|
||||
DEBUG = bool(int(getenv("DEBUG", "1")))
|
||||
|
||||
ALLOWED_HOSTS: set = {
|
||||
ALLOWED_HOSTS: set[str] = {
|
||||
"app",
|
||||
"worker",
|
||||
"beat",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from django.urls import include, path
|
||||
from django.urls import URLResolver, include, path
|
||||
|
||||
urlpatterns: list = [
|
||||
urlpatterns: list[URLResolver] = [
|
||||
path(r"i18n/", include("django.conf.urls.i18n"), name="i18n"),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import os
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from evibes.settings.base import LANGUAGE_CODE, LANGUAGES
|
||||
|
||||
|
||||
def get_language_from_header(accept_language):
|
||||
def get_language_from_header(accept_language: str | None = None) -> str:
|
||||
language_codes = {lang.split("-")[0]: lang for lang, _ in LANGUAGES}
|
||||
languages_dict = dict(LANGUAGES)
|
||||
|
||||
|
|
@ -29,7 +30,7 @@ def get_language_from_header(accept_language):
|
|||
return LANGUAGE_CODE.lower()
|
||||
|
||||
|
||||
def evibes_summernote_upload_to_func(instance, filename: str) -> str:
|
||||
def evibes_summernote_upload_to_func(instance: Any, filename: str) -> str:
|
||||
ext = filename.split(".")[-1]
|
||||
filename = f"{uuid.uuid4()}.{ext}"
|
||||
today = datetime.now().strftime("%Y-%m-%d")
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ class UnknownGatewayError(Exception):
|
|||
|
||||
class AbstractGateway:
|
||||
@staticmethod
|
||||
def process_transaction(transaction):
|
||||
def process_transaction(transaction) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def process_callback(transaction):
|
||||
def process_callback(transaction) -> None:
|
||||
raise NotImplementedError
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import graphene
|
||||
from django.db.models import QuerySet
|
||||
from graphene import relay
|
||||
from graphene.types.generic import GenericScalar
|
||||
from graphene_django import DjangoObjectType
|
||||
|
|
@ -31,7 +32,7 @@ class BalanceType(DjangoObjectType):
|
|||
interfaces = (relay.Node,)
|
||||
filter_fields = ["is_active"]
|
||||
|
||||
def resolve_transactions(self: Balance, info) -> list:
|
||||
def resolve_transactions(self: Balance, info) -> list | QuerySet:
|
||||
if info.context.user == self.user:
|
||||
# noinspection Mypy
|
||||
return self.transactions.all() or []
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -3,7 +3,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: EVIBES 3.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-04 01:50+0300\n"
|
||||
"POT-Creation-Date: 2025-10-06 15:52+0300\n"
|
||||
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
|
||||
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
|
||||
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
|
||||
|
|
@ -52,11 +52,11 @@ msgstr "تفاصيل المعالجة"
|
|||
#: payments/models.py:41
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"transaction amount must fit into {config.PAYMENT_GATEWAY_MINIMUM}-{config."
|
||||
"PAYMENT_GATEWAY_MAXIMUM}"
|
||||
"transaction amount must fit into "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}"
|
||||
msgstr ""
|
||||
"يجب أن يتناسب مبلغ المعاملة مع {config.PAYMENT_GATEWAY_MINIMUM}-{config."
|
||||
"PAYMENT_GATEWAY_MAXIMUM}"
|
||||
"يجب أن يتناسب مبلغ المعاملة مع "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}"
|
||||
|
||||
#: payments/models.py:63
|
||||
msgid "balance"
|
||||
|
|
@ -120,3 +120,16 @@ msgstr "تعذر العثور على مزود {provider}"
|
|||
#, python-brace-format
|
||||
msgid "{config.PROJECT_NAME} | balance deposit"
|
||||
msgstr "{config.PROJECT_NAME} | إيداع الرصيد"
|
||||
|
||||
#: payments/viewsets.py:10
|
||||
msgid ""
|
||||
"ViewSet for handling read-only operations on the Transaction model. This "
|
||||
"class provides a read-only interface for interacting with transaction data. "
|
||||
"It uses the TransactionSerializer for serializing and deserializing the "
|
||||
"data. The class ensures that only authorized users, who meet specific "
|
||||
"permissions, can access the transactions."
|
||||
msgstr ""
|
||||
"ViewSet لمعالجة عمليات القراءة فقط على نموذج المعاملة. توفر هذه الفئة واجهة "
|
||||
"للقراءة فقط للتفاعل مع بيانات المعاملات. وتستخدم أداة TransactionSerializer "
|
||||
"لتسلسل البيانات وإلغاء تسلسلها. تضمن الفئة أن المستخدمين المصرح لهم فقط، "
|
||||
"الذين يستوفون أذونات محددة، يمكنهم الوصول إلى المعاملات."
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -3,7 +3,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: EVIBES 3.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-04 01:50+0300\n"
|
||||
"POT-Creation-Date: 2025-10-06 15:52+0300\n"
|
||||
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
|
||||
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
|
||||
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
|
||||
|
|
@ -52,11 +52,11 @@ msgstr "Podrobnosti o zpracování"
|
|||
#: payments/models.py:41
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"transaction amount must fit into {config.PAYMENT_GATEWAY_MINIMUM}-{config."
|
||||
"PAYMENT_GATEWAY_MAXIMUM}"
|
||||
"transaction amount must fit into "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}"
|
||||
msgstr ""
|
||||
"Částka transakce se musí vejít do rozmezí {config.PAYMENT_GATEWAY_MINIMUM}-"
|
||||
"{config.PAYMENT_GATEWAY_MAXIMUM}."
|
||||
"Částka transakce se musí vejít do rozmezí "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}."
|
||||
|
||||
#: payments/models.py:63
|
||||
msgid "balance"
|
||||
|
|
@ -120,3 +120,17 @@ msgstr "Nepodařilo se najít poskytovatele {provider}"
|
|||
#, python-brace-format
|
||||
msgid "{config.PROJECT_NAME} | balance deposit"
|
||||
msgstr "{config.PROJECT_NAME} | Zůstatek vkladu"
|
||||
|
||||
#: payments/viewsets.py:10
|
||||
msgid ""
|
||||
"ViewSet for handling read-only operations on the Transaction model. This "
|
||||
"class provides a read-only interface for interacting with transaction data. "
|
||||
"It uses the TransactionSerializer for serializing and deserializing the "
|
||||
"data. The class ensures that only authorized users, who meet specific "
|
||||
"permissions, can access the transactions."
|
||||
msgstr ""
|
||||
"ViewSet pro zpracování operací pouze pro čtení na modelu Transakce. Tato "
|
||||
"třída poskytuje rozhraní pouze pro čtení pro interakci s transakčními daty. "
|
||||
"Pro serializaci a deserializaci dat používá TransactionSerializer. Třída "
|
||||
"zajišťuje, že k transakcím mohou přistupovat pouze oprávnění uživatelé, "
|
||||
"kteří splňují určitá oprávnění."
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -3,7 +3,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: EVIBES 3.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-04 01:50+0300\n"
|
||||
"POT-Creation-Date: 2025-10-06 15:52+0300\n"
|
||||
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
|
||||
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
|
||||
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
|
||||
|
|
@ -52,11 +52,11 @@ msgstr "Behandling af detaljer"
|
|||
#: payments/models.py:41
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"transaction amount must fit into {config.PAYMENT_GATEWAY_MINIMUM}-{config."
|
||||
"PAYMENT_GATEWAY_MAXIMUM}"
|
||||
"transaction amount must fit into "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}"
|
||||
msgstr ""
|
||||
"Transaktionsbeløbet skal passe ind i {config.PAYMENT_GATEWAY_MINIMUM}-"
|
||||
"{config.PAYMENT_GATEWAY_MAXIMUM}."
|
||||
"Transaktionsbeløbet skal passe ind i "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}."
|
||||
|
||||
#: payments/models.py:63
|
||||
msgid "balance"
|
||||
|
|
@ -120,3 +120,18 @@ msgstr "Kunne ikke finde udbyder {provider}."
|
|||
#, python-brace-format
|
||||
msgid "{config.PROJECT_NAME} | balance deposit"
|
||||
msgstr "{config.PROJECT_NAME} | Saldoindbetaling"
|
||||
|
||||
#: payments/viewsets.py:10
|
||||
msgid ""
|
||||
"ViewSet for handling read-only operations on the Transaction model. This "
|
||||
"class provides a read-only interface for interacting with transaction data. "
|
||||
"It uses the TransactionSerializer for serializing and deserializing the "
|
||||
"data. The class ensures that only authorized users, who meet specific "
|
||||
"permissions, can access the transactions."
|
||||
msgstr ""
|
||||
"ViewSet til håndtering af skrivebeskyttede operationer på "
|
||||
"transaktionsmodellen. Denne klasse giver en skrivebeskyttet grænseflade til "
|
||||
"interaktion med transaktionsdata. Den bruger TransactionSerializer til at "
|
||||
"serialisere og deserialisere data. Klassen sikrer, at kun autoriserede "
|
||||
"brugere, der opfylder specifikke tilladelser, kan få adgang til "
|
||||
"transaktionerne."
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -3,7 +3,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: EVIBES 3.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-04 01:50+0300\n"
|
||||
"POT-Creation-Date: 2025-10-06 15:52+0300\n"
|
||||
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
|
||||
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
|
||||
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
|
||||
|
|
@ -52,11 +52,11 @@ msgstr "Details zur Verarbeitung"
|
|||
#: payments/models.py:41
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"transaction amount must fit into {config.PAYMENT_GATEWAY_MINIMUM}-{config."
|
||||
"PAYMENT_GATEWAY_MAXIMUM}"
|
||||
"transaction amount must fit into "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}"
|
||||
msgstr ""
|
||||
"Der Transaktionsbetrag muss zwischen {config.PAYMENT_GATEWAY_MINIMUM}-"
|
||||
"{config.PAYMENT_GATEWAY_MAXIMUM} liegen"
|
||||
"Der Transaktionsbetrag muss zwischen "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM} liegen"
|
||||
|
||||
#: payments/models.py:63
|
||||
msgid "balance"
|
||||
|
|
@ -120,3 +120,18 @@ msgstr "Anbieter {provider} konnte nicht gefunden werden"
|
|||
#, python-brace-format
|
||||
msgid "{config.PROJECT_NAME} | balance deposit"
|
||||
msgstr "{config.PROJECT_NAME} | Saldo Einzahlung"
|
||||
|
||||
#: payments/viewsets.py:10
|
||||
msgid ""
|
||||
"ViewSet for handling read-only operations on the Transaction model. This "
|
||||
"class provides a read-only interface for interacting with transaction data. "
|
||||
"It uses the TransactionSerializer for serializing and deserializing the "
|
||||
"data. The class ensures that only authorized users, who meet specific "
|
||||
"permissions, can access the transactions."
|
||||
msgstr ""
|
||||
"ViewSet für die Handhabung von Nur-Lese-Operationen auf dem "
|
||||
"Transaktionsmodell. Diese Klasse bietet eine schreibgeschützte Schnittstelle"
|
||||
" für die Interaktion mit Transaktionsdaten. Sie verwendet den "
|
||||
"TransactionSerializer zur Serialisierung und Deserialisierung der Daten. Die"
|
||||
" Klasse stellt sicher, dass nur autorisierte Benutzer, die bestimmte "
|
||||
"Berechtigungen erfüllen, auf die Transaktionen zugreifen können."
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: EVIBES 3.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-04 01:50+0300\n"
|
||||
"POT-Creation-Date: 2025-10-06 15:52+0300\n"
|
||||
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
|
||||
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
|
||||
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
|
||||
|
|
@ -56,11 +56,11 @@ msgstr "Processing details"
|
|||
#: payments/models.py:41
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"transaction amount must fit into {config.PAYMENT_GATEWAY_MINIMUM}-{config."
|
||||
"PAYMENT_GATEWAY_MAXIMUM}"
|
||||
"transaction amount must fit into "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}"
|
||||
msgstr ""
|
||||
"Transaction amount must fit into {config.PAYMENT_GATEWAY_MINIMUM}-{config."
|
||||
"PAYMENT_GATEWAY_MAXIMUM}"
|
||||
"Transaction amount must fit into "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}"
|
||||
|
||||
#: payments/models.py:63
|
||||
msgid "balance"
|
||||
|
|
@ -124,3 +124,17 @@ msgstr "Couldn't find provider {provider}"
|
|||
#, python-brace-format
|
||||
msgid "{config.PROJECT_NAME} | balance deposit"
|
||||
msgstr "{config.PROJECT_NAME} | Balance Deposit"
|
||||
|
||||
#: payments/viewsets.py:10
|
||||
msgid ""
|
||||
"ViewSet for handling read-only operations on the Transaction model. This "
|
||||
"class provides a read-only interface for interacting with transaction data. "
|
||||
"It uses the TransactionSerializer for serializing and deserializing the "
|
||||
"data. The class ensures that only authorized users, who meet specific "
|
||||
"permissions, can access the transactions."
|
||||
msgstr ""
|
||||
"ViewSet for handling read-only operations on the Transaction model. This "
|
||||
"class provides a read-only interface for interacting with transaction data. "
|
||||
"It uses the TransactionSerializer for serializing and deserializing the "
|
||||
"data. The class ensures that only authorized users, who meet specific "
|
||||
"permissions, can access the transactions."
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -3,7 +3,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: EVIBES 3.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-04 01:50+0300\n"
|
||||
"POT-Creation-Date: 2025-10-06 15:52+0300\n"
|
||||
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
|
||||
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
|
||||
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
|
||||
|
|
@ -52,11 +52,11 @@ msgstr "Processing details"
|
|||
#: payments/models.py:41
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"transaction amount must fit into {config.PAYMENT_GATEWAY_MINIMUM}-{config."
|
||||
"PAYMENT_GATEWAY_MAXIMUM}"
|
||||
"transaction amount must fit into "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}"
|
||||
msgstr ""
|
||||
"Transaction amount must fit into {config.PAYMENT_GATEWAY_MINIMUM}-{config."
|
||||
"PAYMENT_GATEWAY_MAXIMUM}"
|
||||
"Transaction amount must fit into "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}"
|
||||
|
||||
#: payments/models.py:63
|
||||
msgid "balance"
|
||||
|
|
@ -120,3 +120,17 @@ msgstr "Couldn't find provider {provider}"
|
|||
#, python-brace-format
|
||||
msgid "{config.PROJECT_NAME} | balance deposit"
|
||||
msgstr "{config.PROJECT_NAME} | Balance Deposit"
|
||||
|
||||
#: payments/viewsets.py:10
|
||||
msgid ""
|
||||
"ViewSet for handling read-only operations on the Transaction model. This "
|
||||
"class provides a read-only interface for interacting with transaction data. "
|
||||
"It uses the TransactionSerializer for serializing and deserializing the "
|
||||
"data. The class ensures that only authorized users, who meet specific "
|
||||
"permissions, can access the transactions."
|
||||
msgstr ""
|
||||
"ViewSet for handling read-only operations on the Transaction model. This "
|
||||
"class provides a read-only interface for interacting with transaction data. "
|
||||
"It uses the TransactionSerializer for serializing and deserializing the "
|
||||
"data. The class ensures that only authorized users, who meet specific "
|
||||
"permissions, can access the transactions."
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -3,7 +3,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: EVIBES 3.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-04 01:50+0300\n"
|
||||
"POT-Creation-Date: 2025-10-06 15:52+0300\n"
|
||||
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
|
||||
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
|
||||
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
|
||||
|
|
@ -52,11 +52,11 @@ msgstr "Detalles del proceso"
|
|||
#: payments/models.py:41
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"transaction amount must fit into {config.PAYMENT_GATEWAY_MINIMUM}-{config."
|
||||
"PAYMENT_GATEWAY_MAXIMUM}"
|
||||
"transaction amount must fit into "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}"
|
||||
msgstr ""
|
||||
"El importe de la transacción debe ajustarse a {config."
|
||||
"PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}"
|
||||
"El importe de la transacción debe ajustarse a "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}"
|
||||
|
||||
#: payments/models.py:63
|
||||
msgid "balance"
|
||||
|
|
@ -95,8 +95,7 @@ msgid ""
|
|||
"if you have any questions, feel free to contact our support at\n"
|
||||
" %(contact_email)s."
|
||||
msgstr ""
|
||||
"Si tiene alguna pregunta, no dude en ponerse en contacto con nuestro "
|
||||
"servicio de asistencia en\n"
|
||||
"Si tiene alguna pregunta, no dude en ponerse en contacto con nuestro servicio de asistencia en\n"
|
||||
" %(contact_email)s."
|
||||
|
||||
#: payments/templates/balance_deposit_email.html:100
|
||||
|
|
@ -121,3 +120,18 @@ msgstr "No se pudo encontrar el proveedor {provider}"
|
|||
#, python-brace-format
|
||||
msgid "{config.PROJECT_NAME} | balance deposit"
|
||||
msgstr "{config.PROJECT_NAME} | Depósito de saldo"
|
||||
|
||||
#: payments/viewsets.py:10
|
||||
msgid ""
|
||||
"ViewSet for handling read-only operations on the Transaction model. This "
|
||||
"class provides a read-only interface for interacting with transaction data. "
|
||||
"It uses the TransactionSerializer for serializing and deserializing the "
|
||||
"data. The class ensures that only authorized users, who meet specific "
|
||||
"permissions, can access the transactions."
|
||||
msgstr ""
|
||||
"ViewSet para manejar operaciones de sólo lectura en el modelo Transaction. "
|
||||
"Esta clase proporciona una interfaz de sólo lectura para interactuar con los"
|
||||
" datos de la transacción. Utiliza TransactionSerializer para serializar y "
|
||||
"deserializar los datos. La clase garantiza que sólo los usuarios "
|
||||
"autorizados, que cumplan determinados permisos, puedan acceder a las "
|
||||
"transacciones."
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-04 01:50+0300\n"
|
||||
"POT-Creation-Date: 2025-10-06 15:52+0300\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
@ -117,3 +117,12 @@ msgstr ""
|
|||
#, python-brace-format
|
||||
msgid "{config.PROJECT_NAME} | balance deposit"
|
||||
msgstr ""
|
||||
|
||||
#: payments/viewsets.py:10
|
||||
msgid ""
|
||||
"ViewSet for handling read-only operations on the Transaction model. This "
|
||||
"class provides a read-only interface for interacting with transaction data. "
|
||||
"It uses the TransactionSerializer for serializing and deserializing the "
|
||||
"data. The class ensures that only authorized users, who meet specific "
|
||||
"permissions, can access the transactions."
|
||||
msgstr ""
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -3,7 +3,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: EVIBES 3.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-04 01:50+0300\n"
|
||||
"POT-Creation-Date: 2025-10-06 15:52+0300\n"
|
||||
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
|
||||
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
|
||||
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
|
||||
|
|
@ -52,11 +52,11 @@ msgstr "Détails du traitement"
|
|||
#: payments/models.py:41
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"transaction amount must fit into {config.PAYMENT_GATEWAY_MINIMUM}-{config."
|
||||
"PAYMENT_GATEWAY_MAXIMUM}"
|
||||
"transaction amount must fit into "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}"
|
||||
msgstr ""
|
||||
"Le montant de la transaction doit être compris entre {config."
|
||||
"PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}."
|
||||
"Le montant de la transaction doit être compris entre "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}."
|
||||
|
||||
#: payments/models.py:63
|
||||
msgid "balance"
|
||||
|
|
@ -95,8 +95,7 @@ msgid ""
|
|||
"if you have any questions, feel free to contact our support at\n"
|
||||
" %(contact_email)s."
|
||||
msgstr ""
|
||||
"Si vous avez des questions, n'hésitez pas à contacter notre service "
|
||||
"d'assistance à l'adresse suivante\n"
|
||||
"Si vous avez des questions, n'hésitez pas à contacter notre service d'assistance à l'adresse suivante\n"
|
||||
" %(contact_email)s."
|
||||
|
||||
#: payments/templates/balance_deposit_email.html:100
|
||||
|
|
@ -110,7 +109,8 @@ msgstr "Tous droits réservés"
|
|||
|
||||
#: payments/utils/__init__.py:8
|
||||
msgid "a provider to get rates from is required"
|
||||
msgstr "Il est nécessaire de disposer d'un fournisseur pour obtenir des tarifs"
|
||||
msgstr ""
|
||||
"Il est nécessaire de disposer d'un fournisseur pour obtenir des tarifs"
|
||||
|
||||
#: payments/utils/__init__.py:15
|
||||
#, python-brace-format
|
||||
|
|
@ -121,3 +121,18 @@ msgstr "Impossible de trouver le fournisseur {provider}"
|
|||
#, python-brace-format
|
||||
msgid "{config.PROJECT_NAME} | balance deposit"
|
||||
msgstr "{config.PROJECT_NAME} | Dépôt de solde"
|
||||
|
||||
#: payments/viewsets.py:10
|
||||
msgid ""
|
||||
"ViewSet for handling read-only operations on the Transaction model. This "
|
||||
"class provides a read-only interface for interacting with transaction data. "
|
||||
"It uses the TransactionSerializer for serializing and deserializing the "
|
||||
"data. The class ensures that only authorized users, who meet specific "
|
||||
"permissions, can access the transactions."
|
||||
msgstr ""
|
||||
"ViewSet pour gérer les opérations en lecture seule sur le modèle de "
|
||||
"transaction. Cette classe fournit une interface en lecture seule pour "
|
||||
"interagir avec les données de la transaction. Elle utilise le "
|
||||
"TransactionSerializer pour sérialiser et désérialiser les données. Cette "
|
||||
"classe garantit que seuls les utilisateurs autorisés, qui disposent de "
|
||||
"permissions spécifiques, peuvent accéder aux transactions."
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -3,7 +3,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: EVIBES 3.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-04 01:50+0300\n"
|
||||
"POT-Creation-Date: 2025-10-06 15:52+0300\n"
|
||||
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
|
||||
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
|
||||
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
|
||||
|
|
@ -52,11 +52,11 @@ msgstr "פרטי העיבוד"
|
|||
#: payments/models.py:41
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"transaction amount must fit into {config.PAYMENT_GATEWAY_MINIMUM}-{config."
|
||||
"PAYMENT_GATEWAY_MAXIMUM}"
|
||||
"transaction amount must fit into "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}"
|
||||
msgstr ""
|
||||
"סכום העסקה חייב להתאים ל-{config.PAYMENT_GATEWAY_MINIMUM}-{config."
|
||||
"PAYMENT_GATEWAY_MAXIMUM}"
|
||||
"סכום העסקה חייב להתאים "
|
||||
"ל-{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}"
|
||||
|
||||
#: payments/models.py:63
|
||||
msgid "balance"
|
||||
|
|
@ -117,3 +117,16 @@ msgstr "לא ניתן למצוא את הספק {provider}"
|
|||
#, python-brace-format
|
||||
msgid "{config.PROJECT_NAME} | balance deposit"
|
||||
msgstr "{config.PROJECT_NAME} | הפקדת יתרה"
|
||||
|
||||
#: payments/viewsets.py:10
|
||||
msgid ""
|
||||
"ViewSet for handling read-only operations on the Transaction model. This "
|
||||
"class provides a read-only interface for interacting with transaction data. "
|
||||
"It uses the TransactionSerializer for serializing and deserializing the "
|
||||
"data. The class ensures that only authorized users, who meet specific "
|
||||
"permissions, can access the transactions."
|
||||
msgstr ""
|
||||
"ViewSet לטיפול בפעולות לקריאה בלבד במודל העסקה. מחלקה זו מספקת ממשק לקריאה "
|
||||
"בלבד לצורך אינטראקציה עם נתוני העסקה. היא משתמשת ב-TransactionSerializer "
|
||||
"לצורך סידור סדרתי ופירוק סדרתי של הנתונים. המחלקה מבטיחה שרק משתמשים מורשים,"
|
||||
" העומדים בהרשאות ספציפיות, יוכלו לגשת לעסקאות."
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: EVIBES 3.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-04 01:50+0300\n"
|
||||
"POT-Creation-Date: 2025-10-06 15:52+0300\n"
|
||||
"PO-Revision-Date: 2025-06-16 08:59+0100\n"
|
||||
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
|
||||
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
|
||||
|
|
@ -117,3 +117,12 @@ msgstr ""
|
|||
#, python-brace-format
|
||||
msgid "{config.PROJECT_NAME} | balance deposit"
|
||||
msgstr ""
|
||||
|
||||
#: payments/viewsets.py:10
|
||||
msgid ""
|
||||
"ViewSet for handling read-only operations on the Transaction model. This "
|
||||
"class provides a read-only interface for interacting with transaction data. "
|
||||
"It uses the TransactionSerializer for serializing and deserializing the "
|
||||
"data. The class ensures that only authorized users, who meet specific "
|
||||
"permissions, can access the transactions."
|
||||
msgstr ""
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-04 01:50+0300\n"
|
||||
"POT-Creation-Date: 2025-10-06 15:52+0300\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
@ -117,3 +117,12 @@ msgstr ""
|
|||
#, python-brace-format
|
||||
msgid "{config.PROJECT_NAME} | balance deposit"
|
||||
msgstr ""
|
||||
|
||||
#: payments/viewsets.py:10
|
||||
msgid ""
|
||||
"ViewSet for handling read-only operations on the Transaction model. This "
|
||||
"class provides a read-only interface for interacting with transaction data. "
|
||||
"It uses the TransactionSerializer for serializing and deserializing the "
|
||||
"data. The class ensures that only authorized users, who meet specific "
|
||||
"permissions, can access the transactions."
|
||||
msgstr ""
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -3,7 +3,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: EVIBES 3.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-04 01:50+0300\n"
|
||||
"POT-Creation-Date: 2025-10-06 15:52+0300\n"
|
||||
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
|
||||
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
|
||||
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
|
||||
|
|
@ -52,11 +52,11 @@ msgstr "Detail pemrosesan"
|
|||
#: payments/models.py:41
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"transaction amount must fit into {config.PAYMENT_GATEWAY_MINIMUM}-{config."
|
||||
"PAYMENT_GATEWAY_MAXIMUM}"
|
||||
"transaction amount must fit into "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}"
|
||||
msgstr ""
|
||||
"Jumlah transaksi harus sesuai dengan {config.PAYMENT_GATEWAY_MINIMUM}-"
|
||||
"{config.PAYMENT_GATEWAY_MAXIMUM}"
|
||||
"Jumlah transaksi harus sesuai dengan "
|
||||
"{config.PAYMENT_GATEWAY_MINIMUM}-{config.PAYMENT_GATEWAY_MAXIMUM}"
|
||||
|
||||
#: payments/models.py:63
|
||||
msgid "balance"
|
||||
|
|
@ -86,8 +86,7 @@ msgid ""
|
|||
"we have successfully credited your account with %(amount)s. your current\n"
|
||||
" balance is %(balance)s."
|
||||
msgstr ""
|
||||
"Kami telah berhasil mengkreditkan akun Anda dengan %(amount)s. Saldo Anda "
|
||||
"saat ini\n"
|
||||
"Kami telah berhasil mengkreditkan akun Anda dengan %(amount)s. Saldo Anda saat ini\n"
|
||||
" saldo Anda saat ini adalah %(balance)s."
|
||||
|
||||
#: payments/templates/balance_deposit_email.html:98
|
||||
|
|
@ -96,8 +95,7 @@ msgid ""
|
|||
"if you have any questions, feel free to contact our support at\n"
|
||||
" %(contact_email)s."
|
||||
msgstr ""
|
||||
"Jika Anda memiliki pertanyaan, jangan ragu untuk menghubungi tim dukungan "
|
||||
"kami di\n"
|
||||
"Jika Anda memiliki pertanyaan, jangan ragu untuk menghubungi tim dukungan kami di\n"
|
||||
" %(contact_email)s."
|
||||
|
||||
#: payments/templates/balance_deposit_email.html:100
|
||||
|
|
@ -122,3 +120,17 @@ msgstr "Tidak dapat menemukan penyedia {provider}"
|
|||
#, python-brace-format
|
||||
msgid "{config.PROJECT_NAME} | balance deposit"
|
||||
msgstr "{config.PROJECT_NAME} | Setoran Saldo"
|
||||
|
||||
#: payments/viewsets.py:10
|
||||
msgid ""
|
||||
"ViewSet for handling read-only operations on the Transaction model. This "
|
||||
"class provides a read-only interface for interacting with transaction data. "
|
||||
"It uses the TransactionSerializer for serializing and deserializing the "
|
||||
"data. The class ensures that only authorized users, who meet specific "
|
||||
"permissions, can access the transactions."
|
||||
msgstr ""
|
||||
"ViewSet untuk menangani operasi hanya-baca pada model Transaksi. Kelas ini "
|
||||
"menyediakan antarmuka hanya-baca untuk berinteraksi dengan data transaksi. "
|
||||
"Kelas ini menggunakan TransactionSerializer untuk melakukan serialisasi dan "
|
||||
"deserialisasi data. Kelas ini memastikan bahwa hanya pengguna yang "
|
||||
"berwenang, yang memenuhi izin tertentu, yang dapat mengakses transaksi."
|
||||
|
|
|
|||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue