schon/core/utils/__init__.py
Egor fureunoir Gorbunov ab10a7a0b7 Features: 1) Add SEO meta utilities and builder functions for schemas; 2) Implement SEO-related APIs in viewsets for categories, brands, and products; 3) Add SeoMeta model and serializer to manage SEO metadata.
Fixes: 1) Resolve annotations compatibility with mypy `type: ignore` adjustments; 2) Correct `resolve_price_with_currency` method to ensure proper float returns; 3) Handle scenarios for empty or null queryset in vendor methods.

Extra: 1) Refactor utility functions for better type annotations and robustness; 2) Minor formatting corrections and removal of redundant mypy strict setting; 3) Improve method return types for consistency.
2025-08-18 14:26:09 +03:00

156 lines
4.5 KiB
Python

import logging
import re
import secrets
from contextlib import contextmanager
from constance import config
from django.core.cache import cache
from django.db import transaction
from django.utils.crypto import get_random_string
from evibes.settings import DEBUG, EXPOSABLE_KEYS, LANGUAGE_CODE
logger = logging.getLogger("django")
def get_random_code() -> str:
"""
Generates a random string of a specified length. This method calls the
get_random_string function to create a random alphanumeric string of
20 characters in length.
Returns
-------
str
A 20-character-long alphanumeric string.
"""
return get_random_string(20)
def get_product_uuid_as_path(instance, filename: str = "") -> str:
"""
Generates a unique file path for a product using its UUID.
This function constructs a file path that includes the product UUID
in its directory structure. The path format is "products/{product_uuid}/{filename}",
where `product_uuid` is derived from the instance's product attribute, and
`filename` corresponds to the original name of the file being processed.
Parameters:
instance: Object
The model instance containing the product attribute with the desired UUID.
filename: str
The original name of the file for which the path is being generated.
Returns:
str
A string representing the generated unique file path that adheres to the
format "products/{product_uuid}/{filename}".
"""
return "products" + "/" + str(instance.product.uuid) + "/" + filename
def get_brand_name_as_path(instance, filename: str = "") -> str:
return "brands/" + str(instance.name) + "/" + filename
@contextmanager
def atomic_if_not_debug():
"""
A context manager to execute a database operation within an atomic transaction
when the `DEBUG` setting is disabled. If `DEBUG` is enabled, it bypasses
transactional behavior. This allows safe rollback in production and easier
debugging in development.
Yields
------
None
Yields control to the enclosed block of code.
"""
if not DEBUG:
with transaction.atomic():
yield
else:
yield
def is_url_safe(url: str) -> bool:
"""
Determine if a given URL is safe. This function evaluates whether
the provided URL starts with "https://", making it a potentially
secure resource by evaluating its prefix using a regular expression.
Arguments:
url (str): The URL to evaluate.
Returns:
bool: True if the URL starts with "https://", indicating it may
be considered safe. False otherwise.
"""
return bool(re.match(r"^https://", url, re.IGNORECASE))
def format_attributes(attributes: str | None = None) -> dict:
if not attributes:
return {}
try:
attribute_pairs = attributes.split(",")
except AttributeError:
return {}
result = {}
for attr_pair in attribute_pairs:
try:
key, value = attr_pair.split("=")
result[key] = value
except ValueError:
continue
return result
def get_project_parameters() -> dict:
parameters = cache.get("parameters", {})
if not parameters:
for key in EXPOSABLE_KEYS:
parameters[key.lower()] = getattr(config, key)
cache.set("parameters", parameters, 60 * 60)
return parameters
def resolve_translations_for_elasticsearch(instance, field_name) -> None:
field = getattr(instance, f"{field_name}_{LANGUAGE_CODE}", "")
filled_field = getattr(instance, field_name, "")
if not field:
setattr(instance, f"{field_name}_{LANGUAGE_CODE}", filled_field)
CROCKFORD = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
def generate_human_readable_id(length: int = 6) -> str:
"""
Generate a human-readable ID of `length` characters (from the Crockford set),
with a single hyphen inserted:
- 50% chance at the exact middle
- 50% chance at a random position between characters (1 to length-1)
The final string length will be `length + 1` (including the hyphen).
"""
chars = [secrets.choice(CROCKFORD) for _ in range(length)]
pos = (secrets.randbelow(length - 1) + 1) if secrets.choice([True, False]) else (length // 2)
chars.insert(pos, "-")
return "".join(chars)
def generate_human_readable_token() -> str:
"""
Generate a human-readable token of 20 characters (from the Crockford set),
"""
return "".join([secrets.choice(CROCKFORD) for _ in range(20)])