Features: 1) Added vendor, product tag, category tag models and metadata; 2) Introduced proper noinspection comments for Mypy warnings; 3) Extended Markdown linting rules.
Fixes: 1) Corrected `ForeignKey` type assertions across models; 2) Resolved typos and formatting inconsistencies in `.env` and README; 3) Fixed explicit boolean checks in user manager methods. Extra: Updated type hints in multiple models, serializers, and views.
This commit is contained in:
parent
328ccaa615
commit
a33be30098
30 changed files with 716 additions and 690 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -87,6 +87,7 @@ wheels/
|
||||||
share/python-wheels/
|
share/python-wheels/
|
||||||
pip-log.txt
|
pip-log.txt
|
||||||
pip-delete-this-directory.txt
|
pip-delete-this-directory.txt
|
||||||
|
desktop.ini
|
||||||
|
|
||||||
# Node build artifacts
|
# Node build artifacts
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
|
|
|
||||||
|
|
@ -103,12 +103,12 @@ decomment previously commented lines and enjoy eVibes over HTTPS!
|
||||||
|
|
||||||
### .env
|
### .env
|
||||||
|
|
||||||
After .env file generation, you may want to edit some of its values, such as macroservices' API keys, database password,
|
After .env file generation, you may want to edit some of its values, such as macroservices` API keys, database password,
|
||||||
redis password, etc.
|
redis password, etc.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
- Add necessary subdomains to DNS-settings of your domain, those are:
|
- Add the necessary subdomains to DNS-settings of your domain, those are:
|
||||||
|
|
||||||
1. @.your-domain.com
|
1. @.your-domain.com
|
||||||
2. www.your-domain.com
|
2. www.your-domain.com
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,13 @@ from .models import Post, PostTag
|
||||||
|
|
||||||
@admin.register(Post)
|
@admin.register(Post)
|
||||||
class PostAdmin(admin.ModelAdmin):
|
class PostAdmin(admin.ModelAdmin):
|
||||||
|
def preview_html(self, obj):
|
||||||
|
html = obj.content.html or "<em>{}</em>".format(_("(no content yet)"))
|
||||||
|
# noinspection DjangoSafeString
|
||||||
|
return mark_safe(html)
|
||||||
|
|
||||||
|
preview_html.short_description = _("rendered HTML") # type: ignore
|
||||||
|
|
||||||
form = PostAdminForm
|
form = PostAdminForm
|
||||||
list_display = ("title", "author", "slug", "created", "modified")
|
list_display = ("title", "author", "slug", "created", "modified")
|
||||||
list_filter = ("author", "tags", "created", "modified")
|
list_filter = ("author", "tags", "created", "modified")
|
||||||
|
|
@ -33,13 +40,6 @@ class PostAdmin(admin.ModelAdmin):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def preview_html(self, obj):
|
|
||||||
html = obj.content.html or "<em>{}</em>".format(_("(no content yet)"))
|
|
||||||
# noinspection DjangoSafeString
|
|
||||||
return mark_safe(html)
|
|
||||||
|
|
||||||
preview_html.short_description = _("rendered HTML") # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(PostTag)
|
@admin.register(PostTag)
|
||||||
class PostTagAdmin(admin.ModelAdmin):
|
class PostTagAdmin(admin.ModelAdmin):
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
from constance.admin import Config
|
from constance.admin import Config
|
||||||
from constance.admin import ConstanceAdmin as BaseConstanceAdmin
|
from constance.admin import ConstanceAdmin as BaseConstanceAdmin
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,7 @@ class BulkOrderAction(BaseMutation):
|
||||||
elif order_hr_id:
|
elif order_hr_id:
|
||||||
order = Order.objects.get(user=user, human_readable_id=order_hr_id)
|
order = Order.objects.get(user=user, human_readable_id=order_hr_id)
|
||||||
|
|
||||||
|
# noinspection PyUnreachableCode
|
||||||
match action:
|
match action:
|
||||||
case "add":
|
case "add":
|
||||||
order = order.bulk_add_products(products)
|
order = order.bulk_add_products(products)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class Command(BaseCommand):
|
||||||
for product in Product.objects.filter(stocks__isnull=False):
|
for product in Product.objects.filter(stocks__isnull=False):
|
||||||
for stock in product.stocks.all():
|
for stock in product.stocks.all():
|
||||||
try:
|
try:
|
||||||
stock.price = AbstractVendor.round_price_marketologically(stock.price)
|
stock.price = AbstractVendor.round_price_marketologically(stock.price) # type: ignore
|
||||||
stock.save()
|
stock.save()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.stdout.write(self.style.WARNING(f"Couldn't fix price on {stock.uuid}"))
|
self.stdout.write(self.style.WARNING(f"Couldn't fix price on {stock.uuid}"))
|
||||||
|
|
|
||||||
|
|
@ -40,13 +40,13 @@ class Command(BaseCommand):
|
||||||
"-t",
|
"-t",
|
||||||
"--target",
|
"--target",
|
||||||
required=True,
|
required=True,
|
||||||
help=("Dotted path to the field to translate, e.g. core.models.Product.description"),
|
help="Dotted path to the field to translate, e.g. core.models.Product.description",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-l",
|
"-l",
|
||||||
"--language",
|
"--language",
|
||||||
required=True,
|
required=True,
|
||||||
help=("Modeltranslation language code to translate into, e.g. de-de, fr-fr, zh-hans"),
|
help="Modeltranslation language code to translate into, e.g. de-de, fr-fr, zh-hans",
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
|
|
||||||
1066
core/models.py
1066
core/models.py
File diff suppressed because it is too large
Load diff
|
|
@ -27,8 +27,8 @@ from core.serializers.utility import AddressSerializer
|
||||||
|
|
||||||
|
|
||||||
class AttributeGroupSimpleSerializer(ModelSerializer):
|
class AttributeGroupSimpleSerializer(ModelSerializer):
|
||||||
parent: PrimaryKeyRelatedField = PrimaryKeyRelatedField(read_only=True)
|
parent: PrimaryKeyRelatedField = PrimaryKeyRelatedField(read_only=True) # type: ignore
|
||||||
children: PrimaryKeyRelatedField = PrimaryKeyRelatedField(many=True, read_only=True)
|
children: PrimaryKeyRelatedField = PrimaryKeyRelatedField(many=True, read_only=True) # type: ignore
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AttributeGroup
|
model = AttributeGroup
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<!--suppress JSSuspiciousNameCombination -->
|
<!--suppress JSSuspiciousNameCombination, JSUnresolvedReference -->
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,6 @@ def process_promotions() -> tuple[bool, str]:
|
||||||
|
|
||||||
:return: A tuple where the first element is a boolean indicating success, and the second
|
:return: A tuple where the first element is a boolean indicating success, and the second
|
||||||
element is a message describing the operation's outcome.
|
element is a message describing the operation's outcome.
|
||||||
:rtype: tuple[bool, str]
|
|
||||||
"""
|
"""
|
||||||
if not config.ABSTRACT_API_KEY or config.ABSTRACT_API_KEY == "example key":
|
if not config.ABSTRACT_API_KEY or config.ABSTRACT_API_KEY == "example key":
|
||||||
return False, "Abstract features disabled."
|
return False, "Abstract features disabled."
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ def send_order_created_email(order_pk: str) -> tuple[bool, str]:
|
||||||
except Order.DoesNotExist:
|
except Order.DoesNotExist:
|
||||||
return False, f"Order not found with the given pk: {order_pk}"
|
return False, f"Order not found with the given pk: {order_pk}"
|
||||||
|
|
||||||
activate(order.user.language)
|
activate(order.user.language) # type: ignore
|
||||||
|
|
||||||
set_email_settings()
|
set_email_settings()
|
||||||
connection = mail.get_connection()
|
connection = mail.get_connection()
|
||||||
|
|
@ -64,7 +64,7 @@ def send_order_created_email(order_pk: str) -> tuple[bool, str]:
|
||||||
"total_price": order.total_price,
|
"total_price": order.total_price,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
to=[order.user.email],
|
to=[order.user.email], # type: ignore
|
||||||
from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>",
|
from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>",
|
||||||
connection=connection,
|
connection=connection,
|
||||||
)
|
)
|
||||||
|
|
@ -80,7 +80,7 @@ def send_order_finished_email(order_pk: str) -> tuple[bool, str]:
|
||||||
if len(ops) <= 0:
|
if len(ops) <= 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
activate(order.user.language)
|
activate(order.user.language) # type: ignore
|
||||||
|
|
||||||
set_email_settings()
|
set_email_settings()
|
||||||
connection = mail.get_connection()
|
connection = mail.get_connection()
|
||||||
|
|
@ -91,16 +91,16 @@ def send_order_finished_email(order_pk: str) -> tuple[bool, str]:
|
||||||
template_name="digital_order_delivered_email.html",
|
template_name="digital_order_delivered_email.html",
|
||||||
context={
|
context={
|
||||||
"order_uuid": order.human_readable_id,
|
"order_uuid": order.human_readable_id,
|
||||||
"user_first_name": order.user.first_name,
|
"user_first_name": order.user.first_name, # type: ignore
|
||||||
"order_products": ops,
|
"order_products": ops,
|
||||||
"project_name": config.PROJECT_NAME,
|
"project_name": config.PROJECT_NAME,
|
||||||
"contact_email": config.EMAIL_FROM,
|
"contact_email": config.EMAIL_FROM,
|
||||||
"total_price": round(sum(op.buy_price for op in ops), 2),
|
"total_price": round(sum(op.buy_price for op in ops), 2),
|
||||||
"display_system_attributes": order.user.has_perm("core.view_order"),
|
"display_system_attributes": order.user.has_perm("core.view_order"), # type: ignore
|
||||||
"today": datetime.today(),
|
"today": datetime.today(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
to=[order.user.email],
|
to=[order.user.email], # type: ignore
|
||||||
from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>",
|
from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>",
|
||||||
connection=connection,
|
connection=connection,
|
||||||
)
|
)
|
||||||
|
|
@ -108,7 +108,7 @@ def send_order_finished_email(order_pk: str) -> tuple[bool, str]:
|
||||||
email.send()
|
email.send()
|
||||||
|
|
||||||
def send_thank_you_email(ops: list[OrderProduct]):
|
def send_thank_you_email(ops: list[OrderProduct]):
|
||||||
activate(order.user.language)
|
activate(order.user.language) # type: ignore
|
||||||
|
|
||||||
set_email_settings()
|
set_email_settings()
|
||||||
|
|
||||||
|
|
|
||||||
2
core/vendors/__init__.py
vendored
2
core/vendors/__init__.py
vendored
|
|
@ -197,7 +197,7 @@ class AbstractVendor:
|
||||||
price = float(original_price)
|
price = float(original_price)
|
||||||
|
|
||||||
if category and category.markup_percent:
|
if category and category.markup_percent:
|
||||||
price *= 1 + category.markup_percent / 100.0
|
price *= 1 + float(category.markup_percent) / 100.0 # type: ignore
|
||||||
elif vendor and vendor.markup_percent:
|
elif vendor and vendor.markup_percent:
|
||||||
price *= 1 + vendor.markup_percent / 100.0
|
price *= 1 + vendor.markup_percent / 100.0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ class CustomLocaleMiddleware(LocaleMiddleware):
|
||||||
request.LANGUAGE_CODE = normalized
|
request.LANGUAGE_CODE = normalized
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyShadowingBuiltins
|
||||||
class GrapheneJWTAuthorizationMiddleware:
|
class GrapheneJWTAuthorizationMiddleware:
|
||||||
def resolve(self, next, root, info, **args):
|
def resolve(self, next, root, info, **args):
|
||||||
context = info.context
|
context = info.context
|
||||||
|
|
@ -92,6 +93,7 @@ class BlockInvalidHostMiddleware:
|
||||||
return self.get_response(request)
|
return self.get_response(request)
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyShadowingBuiltins
|
||||||
class GrapheneLoggingErrorsDebugMiddleware:
|
class GrapheneLoggingErrorsDebugMiddleware:
|
||||||
def resolve(self, next, root, info, **args):
|
def resolve(self, next, root, info, **args):
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@ class CustomPagination(PageNumberPagination):
|
||||||
{
|
{
|
||||||
"links": {"forward": self.get_next_link(), "backward": self.get_previous_link()},
|
"links": {"forward": self.get_next_link(), "backward": self.get_previous_link()},
|
||||||
"counts": {
|
"counts": {
|
||||||
"total_pages": None or self.page.paginator.num_pages,
|
"total_pages": None or self.page.paginator.num_pages, # type: ignore
|
||||||
"page_size": None or self.page_size,
|
"page_size": None or self.page_size, # type: ignore
|
||||||
"total_items": None or self.page.paginator.count,
|
"total_items": None or self.page.paginator.count, # type: ignore
|
||||||
},
|
},
|
||||||
"data": data,
|
"data": data,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -227,7 +227,7 @@ CURRENCIES: tuple[tuple[str, str], ...] = (
|
||||||
("zh-hans", "CNY"),
|
("zh-hans", "CNY"),
|
||||||
)
|
)
|
||||||
|
|
||||||
CURRENCY_CODE: str = dict(CURRENCIES).get(LANGUAGE_CODE)
|
CURRENCY_CODE: str | None = dict(CURRENCIES).get(LANGUAGE_CODE)
|
||||||
|
|
||||||
MODELTRANSLATION_FALLBACK_LANGUAGES: tuple = (LANGUAGE_CODE, "en-us", "de-de")
|
MODELTRANSLATION_FALLBACK_LANGUAGES: tuple = (LANGUAGE_CODE, "en-us", "de-de")
|
||||||
|
|
||||||
|
|
@ -274,7 +274,7 @@ INTERNAL_IPS: list[str] = [
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
DAISY_SETTINGS: dict[str, str | list[str] | dict[str, str] | None | bool | int | float | list[dict[str, str]]] = {
|
DAISY_SETTINGS: dict = {
|
||||||
"SITE_LOGO": "/static/favicon.ico",
|
"SITE_LOGO": "/static/favicon.ico",
|
||||||
"EXTRA_STYLES": [],
|
"EXTRA_STYLES": [],
|
||||||
"EXTRA_SCRIPTS": [],
|
"EXTRA_SCRIPTS": [],
|
||||||
|
|
@ -283,6 +283,10 @@ DAISY_SETTINGS: dict[str, str | list[str] | dict[str, str] | None | bool | int |
|
||||||
"DONT_SUPPORT_ME": True,
|
"DONT_SUPPORT_ME": True,
|
||||||
"SIDEBAR_FOOTNOTE": "eVibes by Wiseless",
|
"SIDEBAR_FOOTNOTE": "eVibes by Wiseless",
|
||||||
"APPS_REORDER": {
|
"APPS_REORDER": {
|
||||||
|
"django_celery_results": {
|
||||||
|
"hide": True,
|
||||||
|
"app": "django_celery_results",
|
||||||
|
},
|
||||||
"django_celery_beat": {
|
"django_celery_beat": {
|
||||||
"icon": "fa fa-solid fa-timeline",
|
"icon": "fa fa-solid fa-timeline",
|
||||||
"hide": False,
|
"hide": False,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from evibes.settings.base import * # noqa: F403
|
from evibes.settings.base import * # noqa: F403
|
||||||
from evibes.settings.constance import CONSTANCE_CONFIG
|
from evibes.settings.constance import CONSTANCE_CONFIG
|
||||||
|
|
||||||
REST_FRAMEWORK: dict[str, int | str | dict[str, str | bool]] = {
|
REST_FRAMEWORK: dict = {
|
||||||
"DEFAULT_PAGINATION_CLASS": "evibes.pagination.CustomPagination",
|
"DEFAULT_PAGINATION_CLASS": "evibes.pagination.CustomPagination",
|
||||||
"PAGE_SIZE": 30,
|
"PAGE_SIZE": 30,
|
||||||
"DEFAULT_AUTHENTICATION_CLASSES": [
|
"DEFAULT_AUTHENTICATION_CLASSES": [
|
||||||
|
|
@ -45,6 +45,7 @@ SIMPLE_JWT: dict[str, timedelta | str | bool] = {
|
||||||
}
|
}
|
||||||
|
|
||||||
# type: ignore
|
# type: ignore
|
||||||
|
# noinspection Mypy
|
||||||
SPECTACULAR_B2B_DESCRIPTION = _(f"""
|
SPECTACULAR_B2B_DESCRIPTION = _(f"""
|
||||||
Welcome to the {CONSTANCE_CONFIG.get("PROJECT_NAME")[0]} B2B API documentation.
|
Welcome to the {CONSTANCE_CONFIG.get("PROJECT_NAME")[0]} B2B API documentation.
|
||||||
|
|
||||||
|
|
@ -69,6 +70,7 @@ Current API version: {EVIBES_VERSION}
|
||||||
""") # noqa: E501, F405
|
""") # noqa: E501, F405
|
||||||
|
|
||||||
# type: ignore
|
# type: ignore
|
||||||
|
# noinspection Mypy
|
||||||
SPECTACULAR_PLATFORM_DESCRIPTION = _(f"""
|
SPECTACULAR_PLATFORM_DESCRIPTION = _(f"""
|
||||||
Welcome to the {CONSTANCE_CONFIG.get("PROJECT_NAME")[0]} Platform API documentation.
|
Welcome to the {CONSTANCE_CONFIG.get("PROJECT_NAME")[0]} Platform API documentation.
|
||||||
|
|
||||||
|
|
@ -95,6 +97,7 @@ The {CONSTANCE_CONFIG.get("PROJECT_NAME")[0]} API is the central hub for managin
|
||||||
Current API version: {EVIBES_VERSION}
|
Current API version: {EVIBES_VERSION}
|
||||||
""") # noqa: E501, F405
|
""") # noqa: E501, F405
|
||||||
|
|
||||||
|
# noinspection Mypy
|
||||||
SPECTACULAR_PLATFORM_SETTINGS = {
|
SPECTACULAR_PLATFORM_SETTINGS = {
|
||||||
"TITLE": f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]} API",
|
"TITLE": f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]} API",
|
||||||
"DESCRIPTION": SPECTACULAR_PLATFORM_DESCRIPTION,
|
"DESCRIPTION": SPECTACULAR_PLATFORM_DESCRIPTION,
|
||||||
|
|
@ -146,6 +149,7 @@ SPECTACULAR_PLATFORM_SETTINGS = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# noinspection Mypy
|
||||||
SPECTACULAR_B2B_SETTINGS = {
|
SPECTACULAR_B2B_SETTINGS = {
|
||||||
"TITLE": f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]} API",
|
"TITLE": f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]} API",
|
||||||
"DESCRIPTION": SPECTACULAR_B2B_DESCRIPTION,
|
"DESCRIPTION": SPECTACULAR_B2B_DESCRIPTION,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
from evibes.settings import CONSTANCE_CONFIG
|
from evibes.settings import CONSTANCE_CONFIG
|
||||||
|
|
||||||
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
||||||
EMAIL_HOST = CONSTANCE_CONFIG.get("EMAIL_HOST")[0]
|
EMAIL_HOST = CONSTANCE_CONFIG.get("EMAIL_HOST")[0] # type: ignore
|
||||||
EMAIL_PORT = CONSTANCE_CONFIG.get("EMAIL_PORT")[0]
|
EMAIL_PORT = CONSTANCE_CONFIG.get("EMAIL_PORT")[0] # type: ignore
|
||||||
EMAIL_USE_TLS = CONSTANCE_CONFIG.get("EMAIL_USE_TLS")[0]
|
EMAIL_USE_TLS = CONSTANCE_CONFIG.get("EMAIL_USE_TLS")[0] # type: ignore
|
||||||
EMAIL_USE_SSL = CONSTANCE_CONFIG.get("EMAIL_USE_SSL")[0]
|
EMAIL_USE_SSL = CONSTANCE_CONFIG.get("EMAIL_USE_SSL")[0] # type: ignore
|
||||||
EMAIL_HOST_USER = CONSTANCE_CONFIG.get("EMAIL_HOST_USER")[0]
|
EMAIL_HOST_USER = CONSTANCE_CONFIG.get("EMAIL_HOST_USER")[0] # type: ignore
|
||||||
EMAIL_HOST_PASSWORD = CONSTANCE_CONFIG.get("EMAIL_HOST_PASSWORD")[0]
|
EMAIL_HOST_PASSWORD = CONSTANCE_CONFIG.get("EMAIL_HOST_PASSWORD")[0] # type: ignore
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ LOGGING = {
|
||||||
"formatters": {
|
"formatters": {
|
||||||
"color": {
|
"color": {
|
||||||
"()": "colorlog.ColoredFormatter",
|
"()": "colorlog.ColoredFormatter",
|
||||||
"format": ("%(asctime)s %(log_color)s[%(levelname)s]%(reset)s %(name)s: %(message)s"),
|
"format": "%(asctime)s %(log_color)s[%(levelname)s]%(reset)s %(name)s: %(message)s",
|
||||||
"datefmt": "%Y-%m-%d %H:%M:%S",
|
"datefmt": "%Y-%m-%d %H:%M:%S",
|
||||||
"log_colors": {
|
"log_colors": {
|
||||||
"DEBUG": "cyan",
|
"DEBUG": "cyan",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,13 @@ from django.db.models import CASCADE, CharField, FloatField, ForeignKey, JSONFie
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from core.abstract import NiceModel
|
from core.abstract import NiceModel
|
||||||
|
from core.models import Order
|
||||||
|
from vibes_auth.models import User
|
||||||
|
|
||||||
|
|
||||||
class Balance(NiceModel):
|
class Balance(NiceModel):
|
||||||
amount: FloatField = FloatField(null=False, blank=False, default=0)
|
amount: float = FloatField(null=False, blank=False, default=0)
|
||||||
user: OneToOneField = OneToOneField(
|
user: User = OneToOneField(
|
||||||
to="vibes_auth.User", on_delete=CASCADE, blank=True, null=True, related_name="payments_balance"
|
to="vibes_auth.User", on_delete=CASCADE, blank=True, null=True, related_name="payments_balance"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -26,11 +28,11 @@ class Balance(NiceModel):
|
||||||
|
|
||||||
|
|
||||||
class Transaction(NiceModel):
|
class Transaction(NiceModel):
|
||||||
amount: FloatField = FloatField(null=False, blank=False)
|
amount: float = FloatField(null=False, blank=False)
|
||||||
balance: ForeignKey = ForeignKey(Balance, on_delete=CASCADE, blank=True, null=True, related_name="transactions")
|
balance: Balance = ForeignKey(Balance, on_delete=CASCADE, blank=True, null=True, related_name="transactions")
|
||||||
currency: CharField = CharField(max_length=3, null=False, blank=False)
|
currency: str = CharField(max_length=3, null=False, blank=False)
|
||||||
payment_method: CharField = CharField(max_length=20, null=True, blank=True)
|
payment_method: str = CharField(max_length=20, null=True, blank=True)
|
||||||
order: ForeignKey = ForeignKey(
|
order: Order = ForeignKey(
|
||||||
"core.Order",
|
"core.Order",
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
blank=True,
|
blank=True,
|
||||||
|
|
@ -38,7 +40,7 @@ class Transaction(NiceModel):
|
||||||
help_text=_("order to process after paid"),
|
help_text=_("order to process after paid"),
|
||||||
related_name="payments_transactions",
|
related_name="payments_transactions",
|
||||||
)
|
)
|
||||||
process: JSONField = JSONField(verbose_name=_("processing details"), default=dict)
|
process: dict = JSONField(verbose_name=_("processing details"), default=dict)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.balance.user.email} | {self.amount}"
|
return f"{self.balance.user.email} | {self.amount}"
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,10 @@ class TransactionProcessSerializer(ModelSerializer):
|
||||||
order_uuid = SerializerMethodField(read_only=True, required=False)
|
order_uuid = SerializerMethodField(read_only=True, required=False)
|
||||||
|
|
||||||
def get_order_hr_id(self, obj: Transaction):
|
def get_order_hr_id(self, obj: Transaction):
|
||||||
return obj.order.human_readable_id if obj.order else None
|
return obj.order.human_readable_id if obj.order else None # type: ignore
|
||||||
|
|
||||||
def get_order_uuid(self, obj: Transaction):
|
def get_order_uuid(self, obj: Transaction):
|
||||||
return obj.order.uuid if obj.order else None
|
return obj.order.uuid if obj.order else None # type: ignore
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Transaction
|
model = Transaction
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ from payments.views import CallbackAPIView, DepositView
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyArgumentList
|
||||||
class BalanceModelTests(TestCase):
|
class BalanceModelTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user_model = get_user_model()
|
self.user_model = get_user_model()
|
||||||
|
|
@ -34,9 +35,10 @@ class BalanceModelTests(TestCase):
|
||||||
self.balance.save()
|
self.balance.save()
|
||||||
self.balance.refresh_from_db()
|
self.balance.refresh_from_db()
|
||||||
# round(10.129, 2) == 10.13
|
# round(10.129, 2) == 10.13
|
||||||
self.assertAlmostEqual(self.balance.amount, 10.13, places=2)
|
self.assertAlmostEqual(float(self.balance.amount), 10.13, places=2)
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyArgumentList
|
||||||
class TransactionModelTests(TestCase):
|
class TransactionModelTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user_model = get_user_model()
|
self.user_model = get_user_model()
|
||||||
|
|
@ -66,6 +68,7 @@ class TransactionModelTests(TestCase):
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyArgumentList
|
||||||
class DepositViewTests(TestCase):
|
class DepositViewTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.factory = APIRequestFactory()
|
self.factory = APIRequestFactory()
|
||||||
|
|
@ -115,6 +118,7 @@ class CallbackViewTests(TestCase):
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyArgumentList
|
||||||
class SignalTests(TestCase):
|
class SignalTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user_model = get_user_model()
|
self.user_model = get_user_model()
|
||||||
|
|
@ -133,6 +137,7 @@ class SignalTests(TestCase):
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyArgumentList
|
||||||
class GraphQLDepositTests(TestCase):
|
class GraphQLDepositTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user_model = get_user_model()
|
self.user_model = get_user_model()
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ def get_rates(provider: str):
|
||||||
if not provider:
|
if not provider:
|
||||||
raise ValueError(_("a provider to get rates from is required"))
|
raise ValueError(_("a provider to get rates from is required"))
|
||||||
|
|
||||||
|
# noinspection PyUnreachableCode
|
||||||
match provider:
|
match provider:
|
||||||
case "cbr":
|
case "cbr":
|
||||||
return get_rates_cbr()
|
return get_rates_cbr()
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ def balance_email(user_pk: str) -> tuple[bool, str]:
|
||||||
|
|
||||||
email = EmailMessage(
|
email = EmailMessage(
|
||||||
"eVibes | Successful Order",
|
"eVibes | Successful Order",
|
||||||
render_to_string("balance.html", {"user": user, "current_year": timezone.now().year, "config": config}),
|
render_to_string("balance_deposit_email.html",
|
||||||
|
{"user": user, "current_year": timezone.now().year, "config": config}),
|
||||||
to=[user.email],
|
to=[user.email],
|
||||||
from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>",
|
from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ class CallbackAPIView(APIView):
|
||||||
logger.debug(request.__dict__)
|
logger.debug(request.__dict__)
|
||||||
try:
|
try:
|
||||||
gateway = kwargs.get("gateway", "")
|
gateway = kwargs.get("gateway", "")
|
||||||
|
# noinspection PyUnreachableCode
|
||||||
match gateway:
|
match gateway:
|
||||||
case "gateway":
|
case "gateway":
|
||||||
# Gateway.process_callback(request.data)
|
# Gateway.process_callback(request.data)
|
||||||
|
|
|
||||||
173
pyproject.toml
173
pyproject.toml
|
|
@ -7,110 +7,111 @@ readme = "README.md"
|
||||||
package-mode = false
|
package-mode = false
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
aiosmtpd = "1.4.6"
|
aiosmtpd = "1.4.6"
|
||||||
celery = { version = "5.5.2", optional = true }
|
celery = { version = "5.5.2", optional = true }
|
||||||
celery-prometheus-exporter = { version = "1.7.0", optional = true }
|
celery-prometheus-exporter = { version = "1.7.0", optional = true }
|
||||||
colorlog = "6.9.0"
|
colorlog = "6.9.0"
|
||||||
cryptography = "44.0.3"
|
cryptography = "44.0.3"
|
||||||
django = "5.2"
|
django = "5.2"
|
||||||
django-cacheops = "7.2"
|
django-cacheops = "7.2"
|
||||||
django-celery-beat = { version = "2.8.0", optional = true }
|
django-celery-beat = { version = "2.8.0", optional = true }
|
||||||
django-celery-results = { version = "2.6.0", optional = true }
|
django-celery-results = { version = "2.6.0", optional = true }
|
||||||
django-constance = "4.3.2"
|
django-constance = "4.3.2"
|
||||||
django-cors-headers = "4.7.0"
|
django-cors-headers = "4.7.0"
|
||||||
django-daisy = "1.0.23"
|
django-daisy = "1.0.23"
|
||||||
django-dbbackup = "4.2.1"
|
django-dbbackup = "4.2.1"
|
||||||
django-elasticsearch-dsl = "8.0"
|
django-elasticsearch-dsl = "8.0"
|
||||||
django-elasticsearch-dsl-drf = "0.22.5"
|
django-elasticsearch-dsl-drf = "0.22.5"
|
||||||
django-extensions = "4.1"
|
django-extensions = "4.1"
|
||||||
django-filter = "25.1"
|
django-filter = "25.1"
|
||||||
django-health-check = "3.19.1"
|
django-health-check = "3.19.1"
|
||||||
django-hosts = "6.0"
|
django-hosts = "6.0"
|
||||||
django-json-widget = "2.0.1"
|
django-json-widget = "2.0.1"
|
||||||
django-mailbox = "4.10.1"
|
django-mailbox = "4.10.1"
|
||||||
django-model-utils = "5.0.0"
|
django-model-utils = "5.0.0"
|
||||||
django-modeltranslation = "0.19.14"
|
django-modeltranslation = "0.19.14"
|
||||||
django-mptt = "0.17.0"
|
django-mptt = "0.17.0"
|
||||||
django-prometheus = "^2.3.1"
|
django-prometheus = "^2.3.1"
|
||||||
django-redis = "5.4.0"
|
django-redis = "5.4.0"
|
||||||
django-ratelimit = "4.1.0"
|
django-ratelimit = "4.1.0"
|
||||||
django-storages = "1.14.6"
|
django-storages = "1.14.6"
|
||||||
django-stubs = "5.2.0"
|
django-stubs = "5.2.0"
|
||||||
django-widget-tweaks = "1.5.0"
|
django-widget-tweaks = "1.5.0"
|
||||||
django-md-field = "0.1.0"
|
django-md-field = "0.1.0"
|
||||||
djangorestframework = "3.16.0"
|
djangorestframework = "3.16.0"
|
||||||
djangorestframework-camel-case = "1.4.2"
|
djangorestframework-camel-case = "1.4.2"
|
||||||
djangorestframework-recursive = "0.1.2"
|
djangorestframework-recursive = "0.1.2"
|
||||||
djangorestframework-simplejwt = { extras = ["crypto"], version = "5.5.0" }
|
djangorestframework-simplejwt = { extras = ["crypto"], version = "5.5.0" }
|
||||||
djangorestframework-stubs = "3.16.0"
|
djangorestframework-stubs = "3.16.0"
|
||||||
djangorestframework-xml = "2.0.0"
|
djangorestframework-xml = "2.0.0"
|
||||||
djangorestframework-yaml = "2.0.0"
|
djangorestframework-yaml = "2.0.0"
|
||||||
drf-spectacular = { extras = ["sidecar"], version = "0.28.0" }
|
drf-spectacular = { extras = ["sidecar"], version = "0.28.0" }
|
||||||
elasticsearch-dsl = "8.18.0"
|
elasticsearch-dsl = "8.18.0"
|
||||||
filetype = "1.2.0"
|
filetype = "1.2.0"
|
||||||
graphene-django = "3.2.3"
|
graphene-django = "3.2.3"
|
||||||
graphene-file-upload = "1.3.0"
|
graphene-file-upload = "1.3.0"
|
||||||
gunicorn = "23.0.0"
|
gunicorn = "23.0.0"
|
||||||
httpx = "0.28.1"
|
httpx = "0.28.1"
|
||||||
jupyter = { version = "1.1.1", optional = true }
|
jupyter = { version = "1.1.1", optional = true }
|
||||||
mypy = "1.16.1"
|
mypy = "1.16.1"
|
||||||
openai = { version = "1.77.0", optional = true }
|
openai = { version = "1.77.0", optional = true }
|
||||||
paramiko = "3.5.1"
|
paramiko = "3.5.1"
|
||||||
pillow = "11.2.1"
|
pillow = "11.2.1"
|
||||||
polib = "1.2.0"
|
polib = "1.2.0"
|
||||||
poetry-core = "2.1.3"
|
poetry-core = "2.1.3"
|
||||||
python = ">=3.12,<3.13"
|
python = ">=3.12,<3.13"
|
||||||
psutil = "6.1.1"
|
psutil = "6.1.1"
|
||||||
psycopg2 = "2.9.10"
|
psycopg2 = "2.9.10"
|
||||||
pygraphviz = { version = "1.14", optional = true, markers = "sys_platform != 'win32'"}
|
pygraphviz = { version = "1.14", optional = true, markers = "sys_platform != 'win32'" }
|
||||||
pymdown-extensions = "10.15"
|
pymdown-extensions = "10.15"
|
||||||
redis = "6.0.0"
|
redis = "6.0.0"
|
||||||
requests = "2.32.3"
|
requests = "2.32.3"
|
||||||
ruff = "0.11.8"
|
ruff = "0.11.8"
|
||||||
sentry-sdk = { extras = ["django", "celery", "opentelemetry", "redis"], version = "2.27.0" }
|
sentry-sdk = { extras = ["django", "celery", "opentelemetry", "redis"], version = "2.27.0" }
|
||||||
six = "1.17.0"
|
six = "1.17.0"
|
||||||
swapper = "1.4.0"
|
swapper = "1.4.0"
|
||||||
zeep = "4.3.1"
|
zeep = "4.3.1"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = "25.1.0"
|
black = "25.1.0"
|
||||||
celery-stubs = "0.1.3"
|
celery-stubs = "0.1.3"
|
||||||
isort = "5.13.2"
|
isort = "5.13.2"
|
||||||
flake8 = "7.2.0"
|
flake8 = "7.2.0"
|
||||||
mypy-extensions = "1.1.0"
|
mypy-extensions = "1.1.0"
|
||||||
pytest = "8.4.1"
|
pytest = "8.4.1"
|
||||||
pytest-django = "4.11.1"
|
pytest-django = "4.11.1"
|
||||||
coverage = "7.8.2"
|
coverage = "7.8.2"
|
||||||
pre-commit = "4.2.0"
|
pre-commit = "4.2.0"
|
||||||
safety = "3.5.2"
|
safety = "3.5.2"
|
||||||
bandit = "1.2.0"
|
bandit = "1.2.0"
|
||||||
|
|
||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
graph = ["pygraphviz"]
|
graph = ["pygraphviz"]
|
||||||
worker = ["celery", "django-celery-beat", "django-celery-results", "celery-prometheus-exporter"]
|
worker = ["celery", "django-celery-beat", "django-celery-results", "celery-prometheus-exporter"]
|
||||||
openai = ["openai"]
|
openai = ["openai"]
|
||||||
jupyter = ["jupyter"]
|
jupyter = ["jupyter"]
|
||||||
docs = ["sphinx", "sphinx-rtd-theme", "m2r2"]
|
docs = ["sphinx", "sphinx-rtd-theme", "m2r2"]
|
||||||
testing = ["pytest", "pytest-django", "coverage"]
|
testing = ["pytest", "pytest-django", "coverage"]
|
||||||
linting = ["black", "isort", "flake8", "bandit"]
|
linting = ["black", "isort", "flake8", "bandit"]
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
disable_error_code = ["import-untyped", "misc"]
|
disable_error_code = ["import-untyped", "misc"]
|
||||||
|
exclude = ["*/migrations/*"]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 120
|
line-length = 120
|
||||||
target-version = "py38"
|
target-version = "py38"
|
||||||
exclude = ["migrations", "media", "static", "storefront"]
|
exclude = ["migrations", "media", "static", "storefront"]
|
||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
select = ["E", "W", "F", "B", "I", "RUF", "UP", "N", "A", "COM", "C4", "DJ001", "RSE", "SIM", "ISC", "TID252", "PGH004"]
|
select = ["E", "W", "F", "B", "I", "RUF", "UP", "N", "A", "COM", "C4", "DJ001", "RSE", "SIM", "ISC", "TID252", "PGH004"]
|
||||||
ignore = ["B904", "RUF001", "RUF002", "RUF003", "RUF005", "RUF012", "A003", "A002", "COM812", "S603"]
|
ignore = ["B904", "RUF001", "RUF002", "RUF003", "RUF005", "RUF012", "A003", "A002", "COM812", "S603"]
|
||||||
per-file-ignores = { "__init__.py" = ["E402", "F401"] }
|
per-file-ignores = { "__init__.py" = ["E402", "F401"] }
|
||||||
|
|
||||||
[tool.ruff.format]
|
[tool.ruff.format]
|
||||||
quote-style = "double"
|
quote-style = "double"
|
||||||
indent-style = "space"
|
indent-style = "space"
|
||||||
|
|
@ -41,6 +41,7 @@ class UserManager(BaseUserManager):
|
||||||
|
|
||||||
def _create_user(self, email, password, **extra_fields):
|
def _create_user(self, email, password, **extra_fields):
|
||||||
email = self.normalize_email(email)
|
email = self.normalize_email(email)
|
||||||
|
# noinspection PyShadowingNames
|
||||||
user = self.model(email=email, **extra_fields)
|
user = self.model(email=email, **extra_fields)
|
||||||
user.password = make_password(password)
|
user.password = make_password(password)
|
||||||
user.save(using=self._db)
|
user.save(using=self._db)
|
||||||
|
|
@ -55,10 +56,11 @@ class UserManager(BaseUserManager):
|
||||||
def create_superuser(self, email=None, password=None, **extra_fields):
|
def create_superuser(self, email=None, password=None, **extra_fields):
|
||||||
extra_fields.setdefault("is_staff", True)
|
extra_fields.setdefault("is_staff", True)
|
||||||
extra_fields.setdefault("is_superuser", True)
|
extra_fields.setdefault("is_superuser", True)
|
||||||
if extra_fields.get("is_staff") is not True:
|
if not extra_fields.get("is_staff"):
|
||||||
raise ValueError("Superuser must have is_staff=True.")
|
raise ValueError("Superuser must have is_staff=True.")
|
||||||
if extra_fields.get("is_superuser") is not True:
|
if not extra_fields.get("is_superuser"):
|
||||||
raise ValueError("Superuser must have is_superuser=True.")
|
raise ValueError("Superuser must have is_superuser=True.")
|
||||||
|
# noinspection PyShadowingNames
|
||||||
user = self._create_user(email, password, **extra_fields)
|
user = self._create_user(email, password, **extra_fields)
|
||||||
user.is_active = True
|
user.is_active = True
|
||||||
user.is_verified = True
|
user.is_verified = True
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import uuid
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
|
|
@ -29,8 +30,8 @@ class User(AbstractUser, NiceModel):
|
||||||
def get_uuid_as_path(self, *args):
|
def get_uuid_as_path(self, *args):
|
||||||
return str(self.uuid) + "/" + args[0]
|
return str(self.uuid) + "/" + args[0]
|
||||||
|
|
||||||
email: EmailField = EmailField(_("email"), unique=True, help_text=_("user email address"))
|
email: str = EmailField(_("email"), unique=True, help_text=_("user email address"))
|
||||||
phone_number: CharField = CharField(
|
phone_number: str = CharField(
|
||||||
_("phone_number"),
|
_("phone_number"),
|
||||||
max_length=20,
|
max_length=20,
|
||||||
unique=True,
|
unique=True,
|
||||||
|
|
@ -42,9 +43,9 @@ class User(AbstractUser, NiceModel):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
username = None
|
username = None
|
||||||
first_name: CharField = CharField(_("first_name"), max_length=150, blank=True, null=True)
|
first_name: str = CharField(_("first_name"), max_length=150, blank=True, null=True)
|
||||||
last_name: CharField = CharField(_("last_name"), max_length=150, blank=True, null=True)
|
last_name: str = CharField(_("last_name"), max_length=150, blank=True, null=True)
|
||||||
avatar: ImageField = ImageField(
|
avatar = ImageField(
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=_("avatar"),
|
verbose_name=_("avatar"),
|
||||||
upload_to=get_uuid_as_path,
|
upload_to=get_uuid_as_path,
|
||||||
|
|
@ -52,27 +53,28 @@ class User(AbstractUser, NiceModel):
|
||||||
help_text=_("user profile image"),
|
help_text=_("user profile image"),
|
||||||
)
|
)
|
||||||
|
|
||||||
is_verified: BooleanField = BooleanField(
|
is_verified: bool = BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
verbose_name=_("is verified"),
|
verbose_name=_("is verified"),
|
||||||
help_text=_("user verification status"),
|
help_text=_("user verification status"),
|
||||||
)
|
)
|
||||||
is_active: BooleanField = BooleanField(
|
is_active: bool = BooleanField(
|
||||||
_("is_active"),
|
_("is_active"),
|
||||||
default=False,
|
default=False,
|
||||||
help_text=_("unselect this instead of deleting accounts"),
|
help_text=_("unselect this instead of deleting accounts"),
|
||||||
)
|
)
|
||||||
is_subscribed: BooleanField = BooleanField(
|
is_subscribed: bool = BooleanField(
|
||||||
verbose_name=_("is_subscribed"), help_text=_("user's newsletter subscription status"), default=False
|
verbose_name=_("is_subscribed"), help_text=_("user's newsletter subscription status"), default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
activation_token: UUIDField = UUIDField(default=uuid4, verbose_name=_("activation token"))
|
activation_token: uuid = UUIDField(default=uuid4, verbose_name=_("activation token"))
|
||||||
language: CharField = CharField(choices=LANGUAGES, default=LANGUAGE_CODE, null=False, blank=False, max_length=7)
|
language: str = CharField(choices=LANGUAGES, default=LANGUAGE_CODE, null=False, blank=False, max_length=7)
|
||||||
attributes: JSONField = JSONField(verbose_name=_("attributes"), default=dict, blank=True, null=True)
|
attributes: dict = JSONField(verbose_name=_("attributes"), default=dict, blank=True, null=True)
|
||||||
|
|
||||||
USERNAME_FIELD = "email"
|
USERNAME_FIELD = "email"
|
||||||
REQUIRED_FIELDS = []
|
REQUIRED_FIELDS = []
|
||||||
objects = UserManager() # type: ignore
|
# noinspection PyClassVar
|
||||||
|
objects = UserManager()
|
||||||
|
|
||||||
def add_to_recently_viewed(self, product_uuid):
|
def add_to_recently_viewed(self, product_uuid):
|
||||||
recently_viewed = self.recently_viewed
|
recently_viewed = self.recently_viewed
|
||||||
|
|
|
||||||
|
|
@ -122,12 +122,12 @@ class TokenObtainSerializer(Serializer):
|
||||||
with suppress(KeyError):
|
with suppress(KeyError):
|
||||||
authenticate_kwargs["request"] = self.context["request"]
|
authenticate_kwargs["request"] = self.context["request"]
|
||||||
|
|
||||||
self.user = authenticate(**authenticate_kwargs)
|
self.user = authenticate(**authenticate_kwargs) # type: ignore
|
||||||
|
|
||||||
if not api_settings.USER_AUTHENTICATION_RULE(self.user):
|
if not api_settings.USER_AUTHENTICATION_RULE(self.user):
|
||||||
raise AuthenticationFailed(
|
raise AuthenticationFailed(
|
||||||
self.error_messages["no_active_account"],
|
self.error_messages["no_active_account"],
|
||||||
_("no active account"),
|
_("no active account"), # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
|
@ -145,15 +145,15 @@ class TokenObtainPairSerializer(TokenObtainSerializer):
|
||||||
|
|
||||||
logger.debug("Data validated")
|
logger.debug("Data validated")
|
||||||
|
|
||||||
refresh = self.get_token(self.user)
|
refresh = self.get_token(self.user) # type: ignore
|
||||||
data["refresh"] = str(refresh)
|
data["refresh"] = str(refresh)
|
||||||
data["access"] = str(refresh.access_token)
|
data["access"] = str(refresh.access_token) # type: ignore
|
||||||
data["user"] = UserSerializer(self.user).data
|
data["user"] = UserSerializer(self.user).data
|
||||||
|
|
||||||
logger.debug("Data formed")
|
logger.debug("Data formed")
|
||||||
|
|
||||||
if api_settings.UPDATE_LAST_LOGIN:
|
if api_settings.UPDATE_LAST_LOGIN:
|
||||||
update_last_login(self.user, self.user)
|
update_last_login(self.user, self.user) # type: ignore
|
||||||
logger.debug("Updated last login")
|
logger.debug("Updated last login")
|
||||||
|
|
||||||
logger.debug("Returning data")
|
logger.debug("Returning data")
|
||||||
|
|
@ -181,7 +181,7 @@ class TokenRefreshSerializer(Serializer):
|
||||||
|
|
||||||
data["refresh"] = str(refresh)
|
data["refresh"] = str(refresh)
|
||||||
user = User.objects.get(uuid=refresh.payload["user_uuid"])
|
user = User.objects.get(uuid=refresh.payload["user_uuid"])
|
||||||
data["user"] = UserSerializer(user).data
|
data["user"] = UserSerializer(user).data # type: ignore
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
@ -213,7 +213,7 @@ class TokenVerifySerializer(Serializer):
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
raise ValidationError(_("user does not exist"))
|
raise ValidationError(_("user does not exist"))
|
||||||
|
|
||||||
attrs["user"] = UserSerializer(user).data
|
attrs["user"] = UserSerializer(user).data # type: ignore
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue