Features: 1) Add type annotations for various models and methods; 2) Introduce refined graphene resolvers to enhance permission handling; 3) Include type checking suppression with # type: ignore for unsupported cases.
Fixes: 1) Correct `urlsafe_base64_encode` decoding logic in tests; 2) Fix queryset access issues in resolvers; 3) Address missing or incorrect imports across multiple files. Extra: Improve code readability with consistent naming and formatting; Add `# noinspection` annotations to suppress IDE warnings; Update `pyproject.toml` to exclude `drf.py` in MyPy checks.
This commit is contained in:
parent
a33be30098
commit
8fb4ca3362
30 changed files with 220 additions and 176 deletions
|
|
@ -24,6 +24,7 @@ class PostAdmin(admin.ModelAdmin):
|
||||||
autocomplete_fields = ("author", "tags")
|
autocomplete_fields = ("author", "tags")
|
||||||
|
|
||||||
readonly_fields = ("preview_html",)
|
readonly_fields = ("preview_html",)
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(
|
(
|
||||||
None,
|
None,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ class BlogConfig(AppConfig):
|
||||||
name = "blog"
|
name = "blog"
|
||||||
verbose_name = _("blog")
|
verbose_name = _("blog")
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
def ready(self):
|
def ready(self):
|
||||||
import blog.elasticsearch.documents
|
import blog.elasticsearch.documents
|
||||||
import blog.signals # noqa: F401
|
import blog.signals # noqa: F401
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class PostType(DjangoObjectType):
|
||||||
fields = ["tags", "content", "title", "slug"]
|
fields = ["tags", "content", "title", "slug"]
|
||||||
interfaces = (relay.Node,)
|
interfaces = (relay.Node,)
|
||||||
|
|
||||||
def resolve_content(self, info):
|
def resolve_content(self: Post, _info):
|
||||||
return self.content.html.replace("\n", "<br/>")
|
return self.content.html.replace("\n", "<br/>")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@ class CategoryChildrenInline(admin.TabularInline):
|
||||||
class CategoryAdmin(DraggableMPTTAdmin, BasicModelAdmin, TabbedTranslationAdmin):
|
class CategoryAdmin(DraggableMPTTAdmin, BasicModelAdmin, TabbedTranslationAdmin):
|
||||||
mptt_indent_field = "name"
|
mptt_indent_field = "name"
|
||||||
list_display = ("indented_title", "parent", "is_active", "modified")
|
list_display = ("indented_title", "parent", "is_active", "modified")
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
list_filter = ("is_active", "level", "created", "modified")
|
list_filter = ("is_active", "level", "created", "modified")
|
||||||
list_display_links = ("indented_title",)
|
list_display_links = ("indented_title",)
|
||||||
search_fields = (
|
search_fields = (
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ class CoreConfig(AppConfig):
|
||||||
name = "core"
|
name = "core"
|
||||||
verbose_name = _("core")
|
verbose_name = _("core")
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
def ready(self):
|
def ready(self):
|
||||||
import core.elasticsearch.documents
|
import core.elasticsearch.documents
|
||||||
import core.signals # noqa: F401
|
import core.signals # noqa: F401
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ class ProductFilter(FilterSet):
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def filter_category(self, queryset, name, value):
|
def filter_category(self, queryset, _name, value):
|
||||||
if not value:
|
if not value:
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -422,7 +422,7 @@ class PromoCodeType(DjangoObjectType):
|
||||||
description = _("promocodes")
|
description = _("promocodes")
|
||||||
|
|
||||||
def resolve_discount(self: PromoCode, _info) -> float:
|
def resolve_discount(self: PromoCode, _info) -> float:
|
||||||
return self.discount_percent if self.discount_percent else self.discount_amount
|
return float(self.discount_percent) if self.discount_percent else float(self.discount_amount)
|
||||||
|
|
||||||
def resolve_discount_type(self: PromoCode, _info) -> str:
|
def resolve_discount_type(self: PromoCode, _info) -> str:
|
||||||
return "percent" if self.discount_percent else "amount"
|
return "percent" if self.discount_percent else "amount"
|
||||||
|
|
|
||||||
|
|
@ -176,20 +176,20 @@ class Query(ObjectType):
|
||||||
return orders
|
return orders
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_users(_parent, info, **kwargs):
|
def resolve_users(_parent, info, **_kwargs):
|
||||||
if info.context.user.has_perm("vibes_auth.view_user"):
|
if info.context.user.has_perm("vibes_auth.view_user"):
|
||||||
return User.objects.all()
|
return User.objects.all()
|
||||||
users = User.objects.filter(uuid=info.context.user.pk)
|
users = User.objects.filter(uuid=info.context.user.pk)
|
||||||
return users if users.exists() else User.objects.none()
|
return users if users.exists() else User.objects.none()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_attribute_groups(_parent, info, **kwargs):
|
def resolve_attribute_groups(_parent, info, **_kwargs):
|
||||||
if info.context.user.has_perm("core.view_attributegroup"):
|
if info.context.user.has_perm("core.view_attributegroup"):
|
||||||
return AttributeGroup.objects.all()
|
return AttributeGroup.objects.all()
|
||||||
return AttributeGroup.objects.filter(is_active=True)
|
return AttributeGroup.objects.filter(is_active=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_categories(_parent, info, **kwargs):
|
def resolve_categories(_parent, info, **_kwargs):
|
||||||
categories = Category.objects.all()
|
categories = Category.objects.all()
|
||||||
if info.context.user.has_perm("core.view_category"):
|
if info.context.user.has_perm("core.view_category"):
|
||||||
return categories
|
return categories
|
||||||
|
|
@ -202,13 +202,13 @@ class Query(ObjectType):
|
||||||
return Vendor.objects.all()
|
return Vendor.objects.all()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_brands(_parent, info, **kwargs):
|
def resolve_brands(_parent, info, **_kwargs):
|
||||||
if not info.context.user.has_perm("core.view_brand"):
|
if not info.context.user.has_perm("core.view_brand"):
|
||||||
return Brand.objects.filter(is_active=True)
|
return Brand.objects.filter(is_active=True)
|
||||||
return Brand.objects.all()
|
return Brand.objects.all()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_feedbacks(_parent, info, **kwargs):
|
def resolve_feedbacks(_parent, info, **_kwargs):
|
||||||
if info.context.user.has_perm("core.view_feedback"):
|
if info.context.user.has_perm("core.view_feedback"):
|
||||||
return Feedback.objects.all()
|
return Feedback.objects.all()
|
||||||
return Feedback.objects.filter(is_active=True)
|
return Feedback.objects.filter(is_active=True)
|
||||||
|
|
@ -236,7 +236,7 @@ class Query(ObjectType):
|
||||||
return order_products
|
return order_products
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_product_images(_parent, info, **kwargs):
|
def resolve_product_images(_parent, info, **_kwargs):
|
||||||
if info.context.user.has_perm("core.view_productimage"):
|
if info.context.user.has_perm("core.view_productimage"):
|
||||||
return ProductImage.objects.all()
|
return ProductImage.objects.all()
|
||||||
return ProductImage.objects.filter(is_active=True)
|
return ProductImage.objects.filter(is_active=True)
|
||||||
|
|
@ -273,7 +273,7 @@ class Query(ObjectType):
|
||||||
return wishlists
|
return wishlists
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_promotions(_parent, info, **kwargs):
|
def resolve_promotions(_parent, info, **_kwargs):
|
||||||
promotions = Promotion.objects
|
promotions = Promotion.objects
|
||||||
if info.context.user.has_perm("core.view_promotion"):
|
if info.context.user.has_perm("core.view_promotion"):
|
||||||
return promotions.all()
|
return promotions.all()
|
||||||
|
|
@ -287,13 +287,13 @@ class Query(ObjectType):
|
||||||
return promocodes.filter(is_active=True, user=info.context.user)
|
return promocodes.filter(is_active=True, user=info.context.user)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_product_tags(_parent, info, **kwargs):
|
def resolve_product_tags(_parent, info, **_kwargs):
|
||||||
if info.context.user.has_perm("core.view_producttag"):
|
if info.context.user.has_perm("core.view_producttag"):
|
||||||
return ProductTag.objects.all()
|
return ProductTag.objects.all()
|
||||||
return ProductTag.objects.filter(is_active=True)
|
return ProductTag.objects.filter(is_active=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_category_tags(_parent, info, **kwargs):
|
def resolve_category_tags(_parent, info, **_kwargs):
|
||||||
if info.context.user.has_perm("core.view_categorytag"):
|
if info.context.user.has_perm("core.view_categorytag"):
|
||||||
return CategoryTag.objects.all()
|
return CategoryTag.objects.all()
|
||||||
return CategoryTag.objects.filter(is_active=True)
|
return CategoryTag.objects.filter(is_active=True)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import core.utils
|
||||||
|
|
||||||
|
|
||||||
def fix_duplicates(apps, schema_editor):
|
def fix_duplicates(apps, schema_editor):
|
||||||
|
if schema_editor:
|
||||||
|
pass
|
||||||
Order = apps.get_model("core", "Order")
|
Order = apps.get_model("core", "Order")
|
||||||
duplicates = (
|
duplicates = (
|
||||||
Order.objects.values("human_readable_id")
|
Order.objects.values("human_readable_id")
|
||||||
|
|
@ -24,6 +26,10 @@ def fix_duplicates(apps, schema_editor):
|
||||||
|
|
||||||
|
|
||||||
def reverse_func(apps, schema_editor):
|
def reverse_func(apps, schema_editor):
|
||||||
|
if schema_editor:
|
||||||
|
pass
|
||||||
|
if apps:
|
||||||
|
pass
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
def populate_slugs(apps, schema_editor):
|
def populate_slugs(apps, schema_editor):
|
||||||
|
if schema_editor:
|
||||||
|
pass
|
||||||
Category = apps.get_model('core', 'Category')
|
Category = apps.get_model('core', 'Category')
|
||||||
for category in Category.objects.all():
|
for category in Category.objects.all():
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
163
core/models.py
163
core/models.py
|
|
@ -1,6 +1,7 @@
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
from decimal import Decimal
|
||||||
from typing import Optional, Self
|
from typing import Optional, Self
|
||||||
|
|
||||||
from constance import config
|
from constance import config
|
||||||
|
|
@ -57,7 +58,7 @@ logger = logging.getLogger(__name__)
|
||||||
class AttributeGroup(ExportModelOperationsMixin("attribute_group"), NiceModel):
|
class AttributeGroup(ExportModelOperationsMixin("attribute_group"), NiceModel):
|
||||||
is_publicly_visible = True
|
is_publicly_visible = True
|
||||||
|
|
||||||
parent: Self = ForeignKey(
|
parent: Self = ForeignKey( # type: ignore
|
||||||
"self",
|
"self",
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
null=True,
|
null=True,
|
||||||
|
|
@ -66,7 +67,7 @@ class AttributeGroup(ExportModelOperationsMixin("attribute_group"), NiceModel):
|
||||||
help_text=_("parent of this group"),
|
help_text=_("parent of this group"),
|
||||||
verbose_name=_("parent attribute group"),
|
verbose_name=_("parent attribute group"),
|
||||||
)
|
)
|
||||||
name: str = CharField(
|
name: str = CharField( # type: ignore
|
||||||
max_length=255,
|
max_length=255,
|
||||||
verbose_name=_("attribute group's name"),
|
verbose_name=_("attribute group's name"),
|
||||||
help_text=_("attribute group's name"),
|
help_text=_("attribute group's name"),
|
||||||
|
|
@ -84,19 +85,19 @@ class AttributeGroup(ExportModelOperationsMixin("attribute_group"), NiceModel):
|
||||||
class Vendor(ExportModelOperationsMixin("vendor"), NiceModel):
|
class Vendor(ExportModelOperationsMixin("vendor"), NiceModel):
|
||||||
is_publicly_visible = False
|
is_publicly_visible = False
|
||||||
|
|
||||||
authentication: dict = JSONField(
|
authentication: dict = JSONField( # type: ignore
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("stores credentials and endpoints required for vendor communication"),
|
help_text=_("stores credentials and endpoints required for vendor communication"),
|
||||||
verbose_name=_("authentication info"),
|
verbose_name=_("authentication info"),
|
||||||
)
|
)
|
||||||
markup_percent: int = IntegerField(
|
markup_percent: int = IntegerField( # type: ignore
|
||||||
default=0,
|
default=0,
|
||||||
validators=[MinValueValidator(0), MaxValueValidator(100)],
|
validators=[MinValueValidator(0), MaxValueValidator(100)],
|
||||||
help_text=_("define the markup for products retrieved from this vendor"),
|
help_text=_("define the markup for products retrieved from this vendor"),
|
||||||
verbose_name=_("vendor markup percentage"),
|
verbose_name=_("vendor markup percentage"),
|
||||||
)
|
)
|
||||||
name: str = CharField(
|
name: str = CharField( # type: ignore
|
||||||
max_length=255,
|
max_length=255,
|
||||||
help_text=_("name of this vendor"),
|
help_text=_("name of this vendor"),
|
||||||
verbose_name=_("vendor name"),
|
verbose_name=_("vendor name"),
|
||||||
|
|
@ -119,14 +120,14 @@ class Vendor(ExportModelOperationsMixin("vendor"), NiceModel):
|
||||||
class ProductTag(ExportModelOperationsMixin("product_tag"), NiceModel):
|
class ProductTag(ExportModelOperationsMixin("product_tag"), NiceModel):
|
||||||
is_publicly_visible = True
|
is_publicly_visible = True
|
||||||
|
|
||||||
tag_name: str = CharField(
|
tag_name: str = CharField( # type: ignore
|
||||||
blank=False,
|
blank=False,
|
||||||
null=False,
|
null=False,
|
||||||
max_length=255,
|
max_length=255,
|
||||||
help_text=_("internal tag identifier for the product tag"),
|
help_text=_("internal tag identifier for the product tag"),
|
||||||
verbose_name=_("tag name"),
|
verbose_name=_("tag name"),
|
||||||
)
|
)
|
||||||
name: str = CharField(
|
name: str = CharField( # type: ignore
|
||||||
max_length=255,
|
max_length=255,
|
||||||
help_text=_("user-friendly name for the product tag"),
|
help_text=_("user-friendly name for the product tag"),
|
||||||
verbose_name=_("tag display name"),
|
verbose_name=_("tag display name"),
|
||||||
|
|
@ -144,14 +145,14 @@ class ProductTag(ExportModelOperationsMixin("product_tag"), NiceModel):
|
||||||
class CategoryTag(ExportModelOperationsMixin("category_tag"), NiceModel):
|
class CategoryTag(ExportModelOperationsMixin("category_tag"), NiceModel):
|
||||||
is_publicly_visible = True
|
is_publicly_visible = True
|
||||||
|
|
||||||
tag_name: str = CharField(
|
tag_name: str = CharField( # type: ignore
|
||||||
blank=False,
|
blank=False,
|
||||||
null=False,
|
null=False,
|
||||||
max_length=255,
|
max_length=255,
|
||||||
help_text=_("internal tag identifier for the product tag"),
|
help_text=_("internal tag identifier for the product tag"),
|
||||||
verbose_name=_("tag name"),
|
verbose_name=_("tag name"),
|
||||||
)
|
)
|
||||||
name: str = CharField(
|
name: str = CharField( # type: ignore
|
||||||
max_length=255,
|
max_length=255,
|
||||||
help_text=_("user-friendly name for the product tag"),
|
help_text=_("user-friendly name for the product tag"),
|
||||||
verbose_name=_("tag display name"),
|
verbose_name=_("tag display name"),
|
||||||
|
|
@ -169,7 +170,7 @@ class CategoryTag(ExportModelOperationsMixin("category_tag"), NiceModel):
|
||||||
class Category(ExportModelOperationsMixin("category"), NiceModel, MPTTModel):
|
class Category(ExportModelOperationsMixin("category"), NiceModel, MPTTModel):
|
||||||
is_publicly_visible = True
|
is_publicly_visible = True
|
||||||
|
|
||||||
image = ImageField(
|
image = ImageField( # type: ignore
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("upload an image representing this category"),
|
help_text=_("upload an image representing this category"),
|
||||||
|
|
@ -177,7 +178,7 @@ class Category(ExportModelOperationsMixin("category"), NiceModel, MPTTModel):
|
||||||
validators=[validate_category_image_dimensions],
|
validators=[validate_category_image_dimensions],
|
||||||
verbose_name=_("category image"),
|
verbose_name=_("category image"),
|
||||||
)
|
)
|
||||||
markup_percent: int = IntegerField(
|
markup_percent: int = IntegerField( # type: ignore
|
||||||
default=0,
|
default=0,
|
||||||
validators=[MinValueValidator(0), MaxValueValidator(100)],
|
validators=[MinValueValidator(0), MaxValueValidator(100)],
|
||||||
help_text=_("define a markup percentage for products in this category"),
|
help_text=_("define a markup percentage for products in this category"),
|
||||||
|
|
@ -193,28 +194,28 @@ class Category(ExportModelOperationsMixin("category"), NiceModel, MPTTModel):
|
||||||
verbose_name=_("parent category"),
|
verbose_name=_("parent category"),
|
||||||
)
|
)
|
||||||
|
|
||||||
name: str = CharField(
|
name: str = CharField( # type: ignore
|
||||||
max_length=255,
|
max_length=255,
|
||||||
verbose_name=_("category name"),
|
verbose_name=_("category name"),
|
||||||
help_text=_("provide a name for this category"),
|
help_text=_("provide a name for this category"),
|
||||||
unique=True,
|
unique=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
description: str = TextField(
|
description: str = TextField( # type: ignore
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("add a detailed description for this category"),
|
help_text=_("add a detailed description for this category"),
|
||||||
verbose_name=_("category description"),
|
verbose_name=_("category description"),
|
||||||
)
|
)
|
||||||
|
|
||||||
slug: str = AutoSlugField(
|
slug: str = AutoSlugField( # type: ignore
|
||||||
populate_from=("uuid", "name"),
|
populate_from=("uuid", "name"),
|
||||||
allow_unicode=True,
|
allow_unicode=True,
|
||||||
unique=True,
|
unique=True,
|
||||||
editable=False,
|
editable=False,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
tags: CategoryTag = ManyToManyField(
|
tags: CategoryTag = ManyToManyField( # type: ignore
|
||||||
"core.CategoryTag",
|
"core.CategoryTag",
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("tags that help describe or group this category"),
|
help_text=_("tags that help describe or group this category"),
|
||||||
|
|
@ -238,13 +239,13 @@ class Category(ExportModelOperationsMixin("category"), NiceModel, MPTTModel):
|
||||||
class Brand(ExportModelOperationsMixin("brand"), NiceModel):
|
class Brand(ExportModelOperationsMixin("brand"), NiceModel):
|
||||||
is_publicly_visible = True
|
is_publicly_visible = True
|
||||||
|
|
||||||
name: str = CharField(
|
name: str = CharField( # type: ignore
|
||||||
max_length=255,
|
max_length=255,
|
||||||
help_text=_("name of this brand"),
|
help_text=_("name of this brand"),
|
||||||
verbose_name=_("brand name"),
|
verbose_name=_("brand name"),
|
||||||
unique=True,
|
unique=True,
|
||||||
)
|
)
|
||||||
small_logo = ImageField(
|
small_logo = ImageField( # type: ignore
|
||||||
upload_to="brands/",
|
upload_to="brands/",
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
|
|
@ -252,7 +253,7 @@ class Brand(ExportModelOperationsMixin("brand"), NiceModel):
|
||||||
validators=[validate_category_image_dimensions],
|
validators=[validate_category_image_dimensions],
|
||||||
verbose_name=_("brand small image"),
|
verbose_name=_("brand small image"),
|
||||||
)
|
)
|
||||||
big_logo = ImageField(
|
big_logo = ImageField( # type: ignore
|
||||||
upload_to="brands/",
|
upload_to="brands/",
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
|
|
@ -260,13 +261,13 @@ class Brand(ExportModelOperationsMixin("brand"), NiceModel):
|
||||||
validators=[validate_category_image_dimensions],
|
validators=[validate_category_image_dimensions],
|
||||||
verbose_name=_("brand big image"),
|
verbose_name=_("brand big image"),
|
||||||
)
|
)
|
||||||
description: str = TextField(
|
description: str = TextField( # type: ignore
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("add a detailed description of the brand"),
|
help_text=_("add a detailed description of the brand"),
|
||||||
verbose_name=_("brand description"),
|
verbose_name=_("brand description"),
|
||||||
)
|
)
|
||||||
categories: Category = ManyToManyField(
|
categories: Category = ManyToManyField( # type: ignore
|
||||||
"core.Category",
|
"core.Category",
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("optional categories that this brand is associated with"),
|
help_text=_("optional categories that this brand is associated with"),
|
||||||
|
|
@ -299,31 +300,31 @@ class Product(ExportModelOperationsMixin("product"), NiceModel):
|
||||||
help_text=_("optionally associate this product with a brand"),
|
help_text=_("optionally associate this product with a brand"),
|
||||||
verbose_name=_("brand"),
|
verbose_name=_("brand"),
|
||||||
)
|
)
|
||||||
tags: ProductTag = ManyToManyField(
|
tags: ProductTag = ManyToManyField( # type: ignore
|
||||||
"core.ProductTag",
|
"core.ProductTag",
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("tags that help describe or group this product"),
|
help_text=_("tags that help describe or group this product"),
|
||||||
verbose_name=_("product tags"),
|
verbose_name=_("product tags"),
|
||||||
)
|
)
|
||||||
is_digital: bool = BooleanField(
|
is_digital: bool = BooleanField( # type: ignore
|
||||||
default=False,
|
default=False,
|
||||||
help_text=_("indicates whether this product is digitally delivered"),
|
help_text=_("indicates whether this product is digitally delivered"),
|
||||||
verbose_name=_("is product digital"),
|
verbose_name=_("is product digital"),
|
||||||
blank=False,
|
blank=False,
|
||||||
null=False,
|
null=False,
|
||||||
)
|
)
|
||||||
name: str = CharField(
|
name: str = CharField( # type: ignore
|
||||||
max_length=255,
|
max_length=255,
|
||||||
help_text=_("provide a clear identifying name for the product"),
|
help_text=_("provide a clear identifying name for the product"),
|
||||||
verbose_name=_("product name"),
|
verbose_name=_("product name"),
|
||||||
)
|
)
|
||||||
description: str = TextField(
|
description: str = TextField( # type: ignore
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("add a detailed description of the product"),
|
help_text=_("add a detailed description of the product"),
|
||||||
verbose_name=_("product description"),
|
verbose_name=_("product description"),
|
||||||
)
|
)
|
||||||
partnumber: str = CharField(
|
partnumber: str = CharField( # type: ignore
|
||||||
unique=True,
|
unique=True,
|
||||||
default=None,
|
default=None,
|
||||||
blank=False,
|
blank=False,
|
||||||
|
|
@ -331,7 +332,7 @@ class Product(ExportModelOperationsMixin("product"), NiceModel):
|
||||||
help_text=_("part number for this product"),
|
help_text=_("part number for this product"),
|
||||||
verbose_name=_("part number"),
|
verbose_name=_("part number"),
|
||||||
)
|
)
|
||||||
slug: str | None = AutoSlugField(
|
slug: str | None = AutoSlugField( # type: ignore
|
||||||
populate_from=("uuid", "category__name", "name"),
|
populate_from=("uuid", "category__name", "name"),
|
||||||
allow_unicode=True,
|
allow_unicode=True,
|
||||||
unique=True,
|
unique=True,
|
||||||
|
|
@ -390,21 +391,21 @@ class Product(ExportModelOperationsMixin("product"), NiceModel):
|
||||||
class Attribute(ExportModelOperationsMixin("attribute"), NiceModel):
|
class Attribute(ExportModelOperationsMixin("attribute"), NiceModel):
|
||||||
is_publicly_visible = True
|
is_publicly_visible = True
|
||||||
|
|
||||||
categories: Category = ManyToManyField(
|
categories: Category = ManyToManyField( # type: ignore
|
||||||
"core.Category",
|
"core.Category",
|
||||||
related_name="attributes",
|
related_name="attributes",
|
||||||
help_text=_("category of this attribute"),
|
help_text=_("category of this attribute"),
|
||||||
verbose_name=_("categories"),
|
verbose_name=_("categories"),
|
||||||
)
|
)
|
||||||
|
|
||||||
group: AttributeGroup = ForeignKey(
|
group: AttributeGroup = ForeignKey( # type: ignore
|
||||||
"core.AttributeGroup",
|
"core.AttributeGroup",
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
related_name="attributes",
|
related_name="attributes",
|
||||||
help_text=_("group of this attribute"),
|
help_text=_("group of this attribute"),
|
||||||
verbose_name=_("attribute group"),
|
verbose_name=_("attribute group"),
|
||||||
)
|
)
|
||||||
value_type: str = CharField(
|
value_type: str = CharField( # type: ignore
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=[
|
choices=[
|
||||||
("string", _("string")),
|
("string", _("string")),
|
||||||
|
|
@ -418,7 +419,7 @@ class Attribute(ExportModelOperationsMixin("attribute"), NiceModel):
|
||||||
verbose_name=_("value type"),
|
verbose_name=_("value type"),
|
||||||
)
|
)
|
||||||
|
|
||||||
name: str = CharField(
|
name: str = CharField( # type: ignore
|
||||||
max_length=255,
|
max_length=255,
|
||||||
help_text=_("name of this attribute"),
|
help_text=_("name of this attribute"),
|
||||||
verbose_name=_("attribute's name"),
|
verbose_name=_("attribute's name"),
|
||||||
|
|
@ -452,7 +453,7 @@ class AttributeValue(ExportModelOperationsMixin("attribute_value"), NiceModel):
|
||||||
verbose_name=_("associated product"),
|
verbose_name=_("associated product"),
|
||||||
related_name="attributes",
|
related_name="attributes",
|
||||||
)
|
)
|
||||||
value: str = TextField(
|
value: str = TextField( # type: ignore
|
||||||
verbose_name=_("attribute value"),
|
verbose_name=_("attribute value"),
|
||||||
help_text=_("the specific value for this attribute"),
|
help_text=_("the specific value for this attribute"),
|
||||||
)
|
)
|
||||||
|
|
@ -468,7 +469,7 @@ class AttributeValue(ExportModelOperationsMixin("attribute_value"), NiceModel):
|
||||||
class ProductImage(ExportModelOperationsMixin("product_image"), NiceModel):
|
class ProductImage(ExportModelOperationsMixin("product_image"), NiceModel):
|
||||||
is_publicly_visible = True
|
is_publicly_visible = True
|
||||||
|
|
||||||
alt: str = CharField(
|
alt: str = CharField( # type: ignore
|
||||||
max_length=255,
|
max_length=255,
|
||||||
help_text=_("provide alternative text for the image for accessibility"),
|
help_text=_("provide alternative text for the image for accessibility"),
|
||||||
verbose_name=_("image alt text"),
|
verbose_name=_("image alt text"),
|
||||||
|
|
@ -478,7 +479,7 @@ class ProductImage(ExportModelOperationsMixin("product_image"), NiceModel):
|
||||||
verbose_name=_("product image"),
|
verbose_name=_("product image"),
|
||||||
upload_to=get_product_uuid_as_path,
|
upload_to=get_product_uuid_as_path,
|
||||||
)
|
)
|
||||||
priority: int = IntegerField(
|
priority: int = IntegerField( # type: ignore
|
||||||
default=1,
|
default=1,
|
||||||
validators=[MinValueValidator(1)],
|
validators=[MinValueValidator(1)],
|
||||||
help_text=_("determines the order in which images are displayed"),
|
help_text=_("determines the order in which images are displayed"),
|
||||||
|
|
@ -507,24 +508,24 @@ class ProductImage(ExportModelOperationsMixin("product_image"), NiceModel):
|
||||||
class Promotion(ExportModelOperationsMixin("promotion"), NiceModel):
|
class Promotion(ExportModelOperationsMixin("promotion"), NiceModel):
|
||||||
is_publicly_visible = True
|
is_publicly_visible = True
|
||||||
|
|
||||||
discount_percent: int = IntegerField(
|
discount_percent: int = IntegerField( # type: ignore
|
||||||
validators=[MinValueValidator(1), MaxValueValidator(100)],
|
validators=[MinValueValidator(1), MaxValueValidator(100)],
|
||||||
help_text=_("percentage discount for the selected products"),
|
help_text=_("percentage discount for the selected products"),
|
||||||
verbose_name=_("discount percentage"),
|
verbose_name=_("discount percentage"),
|
||||||
)
|
)
|
||||||
name: str = CharField(
|
name: str = CharField( # type: ignore
|
||||||
max_length=256,
|
max_length=256,
|
||||||
unique=True,
|
unique=True,
|
||||||
help_text=_("provide a unique name for this promotion"),
|
help_text=_("provide a unique name for this promotion"),
|
||||||
verbose_name=_("promotion name"),
|
verbose_name=_("promotion name"),
|
||||||
)
|
)
|
||||||
description: str = TextField(
|
description: str = TextField( # type: ignore
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("add a detailed description of the product"),
|
help_text=_("add a detailed description of the product"),
|
||||||
verbose_name=_("promotion description"),
|
verbose_name=_("promotion description"),
|
||||||
)
|
)
|
||||||
products: ManyToManyField = ManyToManyField(
|
products: ManyToManyField = ManyToManyField( # type: ignore
|
||||||
"core.Product",
|
"core.Product",
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("select which products are included in this promotion"),
|
help_text=_("select which products are included in this promotion"),
|
||||||
|
|
@ -550,12 +551,12 @@ class Stock(ExportModelOperationsMixin("stock"), NiceModel):
|
||||||
help_text=_("the vendor supplying this product stock"),
|
help_text=_("the vendor supplying this product stock"),
|
||||||
verbose_name=_("associated vendor"),
|
verbose_name=_("associated vendor"),
|
||||||
)
|
)
|
||||||
price: float = FloatField(
|
price: float = FloatField( # type: ignore
|
||||||
default=0.0,
|
default=0.0,
|
||||||
help_text=_("final price to the customer after markups"),
|
help_text=_("final price to the customer after markups"),
|
||||||
verbose_name=_("selling price"),
|
verbose_name=_("selling price"),
|
||||||
)
|
)
|
||||||
product: ForeignKey = ForeignKey(
|
product: ForeignKey = ForeignKey( # type: ignore
|
||||||
"core.Product",
|
"core.Product",
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
help_text=_("the product associated with this stock entry"),
|
help_text=_("the product associated with this stock entry"),
|
||||||
|
|
@ -564,17 +565,17 @@ class Stock(ExportModelOperationsMixin("stock"), NiceModel):
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
purchase_price: float = FloatField(
|
purchase_price: float = FloatField( # type: ignore
|
||||||
default=0.0,
|
default=0.0,
|
||||||
help_text=_("the price paid to the vendor for this product"),
|
help_text=_("the price paid to the vendor for this product"),
|
||||||
verbose_name=_("vendor purchase price"),
|
verbose_name=_("vendor purchase price"),
|
||||||
)
|
)
|
||||||
quantity: int = IntegerField(
|
quantity: int = IntegerField( # type: ignore
|
||||||
default=0,
|
default=0,
|
||||||
help_text=_("available quantity of the product in stock"),
|
help_text=_("available quantity of the product in stock"),
|
||||||
verbose_name=_("quantity in stock"),
|
verbose_name=_("quantity in stock"),
|
||||||
)
|
)
|
||||||
sku: str = CharField(
|
sku: str = CharField( # type: ignore
|
||||||
max_length=255,
|
max_length=255,
|
||||||
help_text=_("vendor-assigned SKU for identifying the product"),
|
help_text=_("vendor-assigned SKU for identifying the product"),
|
||||||
verbose_name=_("vendor sku"),
|
verbose_name=_("vendor sku"),
|
||||||
|
|
@ -599,13 +600,13 @@ class Stock(ExportModelOperationsMixin("stock"), NiceModel):
|
||||||
class Wishlist(ExportModelOperationsMixin("wishlist"), NiceModel):
|
class Wishlist(ExportModelOperationsMixin("wishlist"), NiceModel):
|
||||||
is_publicly_visible = False
|
is_publicly_visible = False
|
||||||
|
|
||||||
products: ManyToManyField = ManyToManyField(
|
products: ManyToManyField = ManyToManyField( # type: ignore
|
||||||
"core.Product",
|
"core.Product",
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("products that the user has marked as wanted"),
|
help_text=_("products that the user has marked as wanted"),
|
||||||
verbose_name=_("wishlisted products"),
|
verbose_name=_("wishlisted products"),
|
||||||
)
|
)
|
||||||
user: OneToOneField = OneToOneField(
|
user: OneToOneField = OneToOneField( # type: ignore
|
||||||
"vibes_auth.User",
|
"vibes_auth.User",
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
blank=True,
|
blank=True,
|
||||||
|
|
@ -681,30 +682,30 @@ class Documentary(ExportModelOperationsMixin("attribute_group"), NiceModel):
|
||||||
class Address(ExportModelOperationsMixin("address"), NiceModel):
|
class Address(ExportModelOperationsMixin("address"), NiceModel):
|
||||||
is_publicly_visible = False
|
is_publicly_visible = False
|
||||||
|
|
||||||
address_line: str = TextField(
|
address_line: str = TextField( # type: ignore
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("address line for the customer"),
|
help_text=_("address line for the customer"),
|
||||||
verbose_name=_("address line"),
|
verbose_name=_("address line"),
|
||||||
)
|
)
|
||||||
street: str = CharField(_("street"), max_length=255, null=True)
|
street: str = CharField(_("street"), max_length=255, null=True) # type: ignore
|
||||||
district: str = CharField(_("district"), max_length=255, null=True)
|
district: str = CharField(_("district"), max_length=255, null=True) # type: ignore
|
||||||
city: str = CharField(_("city"), max_length=100, null=True)
|
city: str = CharField(_("city"), max_length=100, null=True) # type: ignore
|
||||||
region: str = CharField(_("region"), max_length=100, null=True)
|
region: str = CharField(_("region"), max_length=100, null=True) # type: ignore
|
||||||
postal_code: str = CharField(_("postal code"), max_length=20, null=True)
|
postal_code: str = CharField(_("postal code"), max_length=20, null=True) # type: ignore
|
||||||
country: str = CharField(_("country"), max_length=40, null=True)
|
country: str = CharField(_("country"), max_length=40, null=True) # type: ignore
|
||||||
|
|
||||||
location: PointField = PointField(
|
location: PointField = PointField( # type: ignore
|
||||||
geography=True, srid=4326, null=True, blank=True, help_text=_("geolocation point: (longitude, latitude)")
|
geography=True, srid=4326, null=True, blank=True, help_text=_("geolocation point: (longitude, latitude)")
|
||||||
)
|
)
|
||||||
|
|
||||||
raw_data: dict = JSONField(blank=True, null=True, help_text=_("full JSON response from geocoder for this address"))
|
raw_data: dict = JSONField(blank=True, null=True, help_text=_("full JSON response from geocoder for this address")) # type: ignore
|
||||||
|
|
||||||
api_response: dict = JSONField(
|
api_response: dict = JSONField( # type: ignore
|
||||||
blank=True, null=True, help_text=_("stored JSON response from the geocoding service")
|
blank=True, null=True, help_text=_("stored JSON response from the geocoding service")
|
||||||
)
|
)
|
||||||
|
|
||||||
user: ForeignKey = ForeignKey(to="vibes_auth.User", on_delete=CASCADE, blank=True, null=True)
|
user: ForeignKey = ForeignKey(to="vibes_auth.User", on_delete=CASCADE, blank=True, null=True) # type: ignore
|
||||||
|
|
||||||
objects = AddressManager()
|
objects = AddressManager()
|
||||||
|
|
||||||
|
|
@ -723,14 +724,14 @@ class Address(ExportModelOperationsMixin("address"), NiceModel):
|
||||||
class PromoCode(ExportModelOperationsMixin("promocode"), NiceModel):
|
class PromoCode(ExportModelOperationsMixin("promocode"), NiceModel):
|
||||||
is_publicly_visible = False
|
is_publicly_visible = False
|
||||||
|
|
||||||
code: str = CharField(
|
code: str = CharField( # type: ignore
|
||||||
max_length=20,
|
max_length=20,
|
||||||
unique=True,
|
unique=True,
|
||||||
default=get_random_code,
|
default=get_random_code,
|
||||||
help_text=_("unique code used by a user to redeem a discount"),
|
help_text=_("unique code used by a user to redeem a discount"),
|
||||||
verbose_name=_("promo code identifier"),
|
verbose_name=_("promo code identifier"),
|
||||||
)
|
)
|
||||||
discount_amount: DecimalField = DecimalField(
|
discount_amount: Decimal = DecimalField( # type: ignore
|
||||||
max_digits=10,
|
max_digits=10,
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
blank=True,
|
blank=True,
|
||||||
|
|
@ -738,32 +739,32 @@ class PromoCode(ExportModelOperationsMixin("promocode"), NiceModel):
|
||||||
help_text=_("fixed discount amount applied if percent is not used"),
|
help_text=_("fixed discount amount applied if percent is not used"),
|
||||||
verbose_name=_("fixed discount amount"),
|
verbose_name=_("fixed discount amount"),
|
||||||
)
|
)
|
||||||
discount_percent: int = IntegerField(
|
discount_percent: int = IntegerField( # type: ignore
|
||||||
validators=[MinValueValidator(1), MaxValueValidator(100)],
|
validators=[MinValueValidator(1), MaxValueValidator(100)],
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("percentage discount applied if fixed amount is not used"),
|
help_text=_("percentage discount applied if fixed amount is not used"),
|
||||||
verbose_name=_("percentage discount"),
|
verbose_name=_("percentage discount"),
|
||||||
)
|
)
|
||||||
end_time: datetime = DateTimeField(
|
end_time: datetime = DateTimeField( # type: ignore
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("timestamp when the promocode expires"),
|
help_text=_("timestamp when the promocode expires"),
|
||||||
verbose_name=_("end validity time"),
|
verbose_name=_("end validity time"),
|
||||||
)
|
)
|
||||||
start_time: datetime = DateTimeField(
|
start_time: datetime = DateTimeField( # type: ignore
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("timestamp from which this promocode is valid"),
|
help_text=_("timestamp from which this promocode is valid"),
|
||||||
verbose_name=_("start validity time"),
|
verbose_name=_("start validity time"),
|
||||||
)
|
)
|
||||||
used_on: datetime = DateTimeField(
|
used_on: datetime = DateTimeField( # type: ignore
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("timestamp when the promocode was used, blank if not used yet"),
|
help_text=_("timestamp when the promocode was used, blank if not used yet"),
|
||||||
verbose_name=_("usage timestamp"),
|
verbose_name=_("usage timestamp"),
|
||||||
)
|
)
|
||||||
user: ForeignKey = ForeignKey(
|
user: ForeignKey = ForeignKey( # type: ignore
|
||||||
"vibes_auth.User",
|
"vibes_auth.User",
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
help_text=_("user assigned to this promocode if applicable"),
|
help_text=_("user assigned to this promocode if applicable"),
|
||||||
|
|
@ -846,26 +847,26 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
|
||||||
help_text=_("the shipping address used for this order"),
|
help_text=_("the shipping address used for this order"),
|
||||||
verbose_name=_("shipping address"),
|
verbose_name=_("shipping address"),
|
||||||
)
|
)
|
||||||
status: str = CharField(
|
status: str = CharField( # type: ignore
|
||||||
default="PENDING",
|
default="PENDING",
|
||||||
max_length=64,
|
max_length=64,
|
||||||
choices=ORDER_STATUS_CHOICES,
|
choices=ORDER_STATUS_CHOICES,
|
||||||
help_text=_("current status of the order in its lifecycle"),
|
help_text=_("current status of the order in its lifecycle"),
|
||||||
verbose_name=_("order status"),
|
verbose_name=_("order status"),
|
||||||
)
|
)
|
||||||
notifications: dict = JSONField(
|
notifications: dict = JSONField( # type: ignore
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("json structure of notifications to display to users"),
|
help_text=_("json structure of notifications to display to users"),
|
||||||
verbose_name=_("notifications"),
|
verbose_name=_("notifications"),
|
||||||
)
|
)
|
||||||
attributes: dict = JSONField(
|
attributes: dict = JSONField( # type: ignore
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("json representation of order attributes for this order"),
|
help_text=_("json representation of order attributes for this order"),
|
||||||
verbose_name=_("attributes"),
|
verbose_name=_("attributes"),
|
||||||
)
|
)
|
||||||
user: User = ForeignKey(
|
user: User = ForeignKey( # type: ignore
|
||||||
"vibes_auth.User",
|
"vibes_auth.User",
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
help_text=_("the user who placed the order"),
|
help_text=_("the user who placed the order"),
|
||||||
|
|
@ -874,14 +875,14 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
buy_time: datetime = DateTimeField(
|
buy_time: datetime = DateTimeField( # type: ignore
|
||||||
help_text=_("the timestamp when the order was finalized"),
|
help_text=_("the timestamp when the order was finalized"),
|
||||||
verbose_name=_("buy time"),
|
verbose_name=_("buy time"),
|
||||||
default=None,
|
default=None,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
human_readable_id: str = CharField(
|
human_readable_id: str = CharField( # type: ignore
|
||||||
max_length=8,
|
max_length=8,
|
||||||
help_text=_("a human-readable identifier for the order"),
|
help_text=_("a human-readable identifier for the order"),
|
||||||
verbose_name=_("human readable id"),
|
verbose_name=_("human readable id"),
|
||||||
|
|
@ -1193,31 +1194,31 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
|
||||||
class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel):
|
class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel):
|
||||||
is_publicly_visible = False
|
is_publicly_visible = False
|
||||||
|
|
||||||
buy_price: float = FloatField(
|
buy_price: float = FloatField( # type: ignore
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("the price paid by the customer for this product at purchase time"),
|
help_text=_("the price paid by the customer for this product at purchase time"),
|
||||||
verbose_name=_("purchase price at order time"),
|
verbose_name=_("purchase price at order time"),
|
||||||
)
|
)
|
||||||
comments: str = TextField(
|
comments: str = TextField( # type: ignore
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("internal comments for admins about this ordered product"),
|
help_text=_("internal comments for admins about this ordered product"),
|
||||||
verbose_name=_("internal comments"),
|
verbose_name=_("internal comments"),
|
||||||
)
|
)
|
||||||
notifications: dict = JSONField(
|
notifications: dict = JSONField( # type: ignore
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("json structure of notifications to display to users"),
|
help_text=_("json structure of notifications to display to users"),
|
||||||
verbose_name=_("user notifications"),
|
verbose_name=_("user notifications"),
|
||||||
)
|
)
|
||||||
attributes: dict = JSONField(
|
attributes: dict = JSONField( # type: ignore
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("json representation of this item's attributes"),
|
help_text=_("json representation of this item's attributes"),
|
||||||
verbose_name=_("ordered product attributes"),
|
verbose_name=_("ordered product attributes"),
|
||||||
)
|
)
|
||||||
order: Order = ForeignKey(
|
order: Order = ForeignKey( # type: ignore
|
||||||
"core.Order",
|
"core.Order",
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
help_text=_("reference to the parent order that contains this product"),
|
help_text=_("reference to the parent order that contains this product"),
|
||||||
|
|
@ -1225,7 +1226,7 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel):
|
||||||
related_name="order_products",
|
related_name="order_products",
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
product: Product = ForeignKey(
|
product: Product = ForeignKey( # type: ignore
|
||||||
"core.Product",
|
"core.Product",
|
||||||
on_delete=PROTECT,
|
on_delete=PROTECT,
|
||||||
blank=True,
|
blank=True,
|
||||||
|
|
@ -1233,14 +1234,14 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel):
|
||||||
help_text=_("the specific product associated with this order line"),
|
help_text=_("the specific product associated with this order line"),
|
||||||
verbose_name=_("associated product"),
|
verbose_name=_("associated product"),
|
||||||
)
|
)
|
||||||
quantity: int = PositiveIntegerField(
|
quantity: int = PositiveIntegerField( # type: ignore
|
||||||
blank=False,
|
blank=False,
|
||||||
null=False,
|
null=False,
|
||||||
default=1,
|
default=1,
|
||||||
help_text=_("quantity of this specific product in the order"),
|
help_text=_("quantity of this specific product in the order"),
|
||||||
verbose_name=_("product quantity"),
|
verbose_name=_("product quantity"),
|
||||||
)
|
)
|
||||||
status: str = CharField(
|
status: str = CharField( # type: ignore
|
||||||
max_length=128,
|
max_length=128,
|
||||||
blank=False,
|
blank=False,
|
||||||
null=False,
|
null=False,
|
||||||
|
|
@ -1313,8 +1314,8 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel):
|
||||||
class DigitalAssetDownload(ExportModelOperationsMixin("attribute_group"), NiceModel):
|
class DigitalAssetDownload(ExportModelOperationsMixin("attribute_group"), NiceModel):
|
||||||
is_publicly_visible = False
|
is_publicly_visible = False
|
||||||
|
|
||||||
order_product: OneToOneField = OneToOneField(to=OrderProduct, on_delete=CASCADE, related_name="download")
|
order_product: OneToOneField = OneToOneField(to=OrderProduct, on_delete=CASCADE, related_name="download") # type: ignore
|
||||||
num_downloads: int = IntegerField(default=0)
|
num_downloads: int = IntegerField(default=0) # type: ignore
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("download")
|
verbose_name = _("download")
|
||||||
|
|
@ -1336,13 +1337,13 @@ class DigitalAssetDownload(ExportModelOperationsMixin("attribute_group"), NiceMo
|
||||||
class Feedback(ExportModelOperationsMixin("feedback"), NiceModel):
|
class Feedback(ExportModelOperationsMixin("feedback"), NiceModel):
|
||||||
is_publicly_visible = True
|
is_publicly_visible = True
|
||||||
|
|
||||||
comment: str = TextField(
|
comment: str = TextField( # type: ignore
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("user-provided comments about their experience with the product"),
|
help_text=_("user-provided comments about their experience with the product"),
|
||||||
verbose_name=_("feedback comments"),
|
verbose_name=_("feedback comments"),
|
||||||
)
|
)
|
||||||
order_product: OrderProduct = OneToOneField(
|
order_product: OrderProduct = OneToOneField( # type: ignore
|
||||||
"core.OrderProduct",
|
"core.OrderProduct",
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
blank=False,
|
blank=False,
|
||||||
|
|
@ -1350,7 +1351,7 @@ class Feedback(ExportModelOperationsMixin("feedback"), NiceModel):
|
||||||
help_text=_("references the specific product in an order that this feedback is about"),
|
help_text=_("references the specific product in an order that this feedback is about"),
|
||||||
verbose_name=_("related order product"),
|
verbose_name=_("related order product"),
|
||||||
)
|
)
|
||||||
rating: float = FloatField(
|
rating: float = FloatField( # type: ignore
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("user-assigned rating for the product"),
|
help_text=_("user-assigned rating for the product"),
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class IsOwnerOrReadOnly(permissions.BasePermission):
|
||||||
return obj.user == request.user
|
return obj.user == request.user
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyProtectedMember
|
# noinspection PyProtectedMember,PyUnresolvedReferences
|
||||||
class EvibesPermission(permissions.BasePermission):
|
class EvibesPermission(permissions.BasePermission):
|
||||||
ACTION_PERM_MAP = {
|
ACTION_PERM_MAP = {
|
||||||
"retrieve": "view",
|
"retrieve": "view",
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=User)
|
@receiver(post_save, sender=User)
|
||||||
def create_order_on_user_creation_signal(instance, created, **kwargs):
|
def create_order_on_user_creation_signal(instance, created, **_kwargs):
|
||||||
if created:
|
if created:
|
||||||
try:
|
try:
|
||||||
Order.objects.create(user=instance, status="PENDING")
|
Order.objects.create(user=instance, status="PENDING")
|
||||||
|
|
@ -35,13 +35,13 @@ def create_order_on_user_creation_signal(instance, created, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=User)
|
@receiver(post_save, sender=User)
|
||||||
def create_wishlist_on_user_creation_signal(instance, created, **kwargs):
|
def create_wishlist_on_user_creation_signal(instance, created, **_kwargs):
|
||||||
if created:
|
if created:
|
||||||
Wishlist.objects.create(user=instance)
|
Wishlist.objects.create(user=instance)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=User)
|
@receiver(post_save, sender=User)
|
||||||
def create_promocode_on_user_referring(instance, created, **kwargs):
|
def create_promocode_on_user_referring(instance, created, **_kwargs):
|
||||||
try:
|
try:
|
||||||
if created and instance.attributes.get("referrer", ""):
|
if created and instance.attributes.get("referrer", ""):
|
||||||
referrer_uuid = urlsafe_base64_decode(instance.attributes.get("referrer", ""))
|
referrer_uuid = urlsafe_base64_decode(instance.attributes.get("referrer", ""))
|
||||||
|
|
@ -60,7 +60,7 @@ def create_promocode_on_user_referring(instance, created, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Order)
|
@receiver(post_save, sender=Order)
|
||||||
def process_order_changes(instance, created, **kwargs):
|
def process_order_changes(instance, created, **_kwargs):
|
||||||
if not created:
|
if not created:
|
||||||
if instance.status != "PENDING" and instance.user:
|
if instance.status != "PENDING" and instance.user:
|
||||||
pending_orders = Order.objects.filter(user=instance.user, status="PENDING")
|
pending_orders = Order.objects.filter(user=instance.user, status="PENDING")
|
||||||
|
|
@ -109,12 +109,12 @@ def process_order_changes(instance, created, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Product)
|
@receiver(post_save, sender=Product)
|
||||||
def update_product_name_lang(instance, created, **kwargs):
|
def update_product_name_lang(instance, _created, **_kwargs):
|
||||||
resolve_translations_for_elasticsearch(instance, "name")
|
resolve_translations_for_elasticsearch(instance, "name")
|
||||||
resolve_translations_for_elasticsearch(instance, "description")
|
resolve_translations_for_elasticsearch(instance, "description")
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Category)
|
@receiver(post_save, sender=Category)
|
||||||
def update_category_name_lang(instance, created, **kwargs):
|
def update_category_name_lang(instance, _created, **_kwargs):
|
||||||
resolve_translations_for_elasticsearch(instance, "name")
|
resolve_translations_for_elasticsearch(instance, "name")
|
||||||
resolve_translations_for_elasticsearch(instance, "description")
|
resolve_translations_for_elasticsearch(instance, "description")
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,8 @@ 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]):
|
||||||
|
if ops:
|
||||||
|
pass
|
||||||
activate(order.user.language) # type: ignore
|
activate(order.user.language) # type: ignore
|
||||||
|
|
||||||
set_email_settings()
|
set_email_settings()
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ def validate_category_image_dimensions(image):
|
||||||
raise ValidationError(_(f"image dimensions should not exceed w{max_width} x h{max_height} pixels"))
|
raise ValidationError(_(f"image dimensions should not exceed w{max_width} x h{max_height} pixels"))
|
||||||
|
|
||||||
|
|
||||||
def validate_phone_number(value, **kwargs):
|
def validate_phone_number(value, **_kwargs):
|
||||||
phone_regex = re.compile(r"^\+?1?\d{9,15}$")
|
phone_regex = re.compile(r"^\+?1?\d{9,15}$")
|
||||||
if not phone_regex.match(value):
|
if not phone_regex.match(value):
|
||||||
raise ValidationError(_("invalid phone number format"))
|
raise ValidationError(_("invalid phone number format"))
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ class CustomGraphQLView(FileUploadGraphQLView):
|
||||||
|
|
||||||
class CustomSwaggerView(SpectacularSwaggerView):
|
class CustomSwaggerView(SpectacularSwaggerView):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["script_url"] = self.request.build_absolute_uri()
|
context["script_url"] = self.request.build_absolute_uri()
|
||||||
return context
|
return context
|
||||||
|
|
@ -78,6 +79,7 @@ class CustomSwaggerView(SpectacularSwaggerView):
|
||||||
|
|
||||||
class CustomRedocView(SpectacularRedocView):
|
class CustomRedocView(SpectacularRedocView):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["script_url"] = self.request.build_absolute_uri()
|
context["script_url"] = self.request.build_absolute_uri()
|
||||||
return context
|
return context
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ class JSONTableWidget(forms.Widget):
|
||||||
value = self.format_value(value)
|
value = self.format_value(value)
|
||||||
return super().render(name, value, attrs, renderer)
|
return super().render(name, value, attrs, renderer)
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
def value_from_datadict(self, data, files, name):
|
def value_from_datadict(self, data, files, name):
|
||||||
json_data = {}
|
json_data = {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# mypy: ignore-errors
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
@ -45,7 +46,6 @@ 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.
|
||||||
|
|
||||||
|
|
@ -97,7 +97,6 @@ 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,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import graphene
|
import graphene
|
||||||
|
from django.db.models import QuerySet
|
||||||
from graphene import relay
|
from graphene import relay
|
||||||
from graphene.types.generic import GenericScalar
|
from graphene.types.generic import GenericScalar
|
||||||
from graphene_django import DjangoObjectType
|
from graphene_django import DjangoObjectType
|
||||||
|
|
@ -9,8 +10,10 @@ from payments.models import Balance, Transaction
|
||||||
class TransactionType(DjangoObjectType):
|
class TransactionType(DjangoObjectType):
|
||||||
process = GenericScalar()
|
process = GenericScalar()
|
||||||
|
|
||||||
def resolve_process(self, info):
|
def resolve_process(self: Transaction, info) -> dict:
|
||||||
return self.process
|
if info.context.user == self.balance.user:
|
||||||
|
return self.process
|
||||||
|
return {}
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Transaction
|
model = Transaction
|
||||||
|
|
@ -20,7 +23,7 @@ class TransactionType(DjangoObjectType):
|
||||||
|
|
||||||
|
|
||||||
class BalanceType(DjangoObjectType):
|
class BalanceType(DjangoObjectType):
|
||||||
transaction_set = graphene.List(lambda: TransactionType)
|
transactions = graphene.List(lambda: TransactionType)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Balance
|
model = Balance
|
||||||
|
|
@ -28,5 +31,8 @@ class BalanceType(DjangoObjectType):
|
||||||
interfaces = (relay.Node,)
|
interfaces = (relay.Node,)
|
||||||
filter_fields = ["is_active"]
|
filter_fields = ["is_active"]
|
||||||
|
|
||||||
def resolve_transaction_set(self, info):
|
def resolve_transaction_set(self: Balance, info) -> QuerySet["Transaction"] | list:
|
||||||
return self.transaction_set.all() or []
|
if info.context.user == self.user:
|
||||||
|
# noinspection Mypy
|
||||||
|
return self.transactions.all() or []
|
||||||
|
return []
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,20 @@
|
||||||
from constance import config
|
from constance import config
|
||||||
from django.contrib.postgres.indexes import GinIndex
|
from django.contrib.postgres.indexes import GinIndex
|
||||||
from django.db.models import CASCADE, CharField, FloatField, ForeignKey, JSONField, OneToOneField
|
from django.db.models import CASCADE, CharField, FloatField, ForeignKey, JSONField, OneToOneField, QuerySet
|
||||||
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 core.models import Order
|
||||||
from vibes_auth.models import User
|
|
||||||
|
|
||||||
|
|
||||||
class Balance(NiceModel):
|
|
||||||
amount: float = FloatField(null=False, blank=False, default=0)
|
|
||||||
user: User = OneToOneField(
|
|
||||||
to="vibes_auth.User", on_delete=CASCADE, blank=True, null=True, related_name="payments_balance"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.user.email} | {self.amount}"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("balance")
|
|
||||||
verbose_name_plural = _("balances")
|
|
||||||
|
|
||||||
def save(self, **kwargs):
|
|
||||||
if self.amount != 0.0 and len(str(self.amount).split(".")[1]) > 2:
|
|
||||||
self.amount = round(self.amount, 2)
|
|
||||||
super().save(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class Transaction(NiceModel):
|
class Transaction(NiceModel):
|
||||||
amount: float = FloatField(null=False, blank=False)
|
amount: float = FloatField(null=False, blank=False) # type: ignore
|
||||||
balance: Balance = ForeignKey(Balance, on_delete=CASCADE, blank=True, null=True, related_name="transactions")
|
balance: "Balance" = ForeignKey(
|
||||||
currency: str = CharField(max_length=3, null=False, blank=False)
|
"payments.Balance", on_delete=CASCADE, blank=True, null=True, related_name="transactions"
|
||||||
payment_method: str = CharField(max_length=20, null=True, blank=True)
|
) # type: ignore
|
||||||
order: Order = ForeignKey(
|
currency: str = CharField(max_length=3, null=False, blank=False) # type: ignore
|
||||||
|
payment_method: str = CharField(max_length=20, null=True, blank=True) # type: ignore
|
||||||
|
order: Order = ForeignKey( # type: ignore
|
||||||
"core.Order",
|
"core.Order",
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
blank=True,
|
blank=True,
|
||||||
|
|
@ -40,7 +22,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: dict = JSONField(verbose_name=_("processing details"), default=dict)
|
process: dict = JSONField(verbose_name=_("processing details"), default=dict) # type: ignore
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.balance.user.email} | {self.amount}"
|
return f"{self.balance.user.email} | {self.amount}"
|
||||||
|
|
@ -64,3 +46,23 @@ class Transaction(NiceModel):
|
||||||
indexes = [
|
indexes = [
|
||||||
GinIndex(fields=["process"]),
|
GinIndex(fields=["process"]),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Balance(NiceModel):
|
||||||
|
amount: float = FloatField(null=False, blank=False, default=0) # type: ignore
|
||||||
|
user = OneToOneField( # type: ignore
|
||||||
|
to="vibes_auth.User", on_delete=CASCADE, blank=True, null=True, related_name="payments_balance"
|
||||||
|
)
|
||||||
|
transactions: QuerySet["Transaction"]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.user.email} | {self.amount}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("balance")
|
||||||
|
verbose_name_plural = _("balances")
|
||||||
|
|
||||||
|
def save(self, **kwargs):
|
||||||
|
if self.amount != 0.0 and len(str(self.amount).split(".")[1]) > 2:
|
||||||
|
self.amount = round(self.amount, 2)
|
||||||
|
super().save(**kwargs)
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@ from vibes_auth.models import User
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=User)
|
@receiver(post_save, sender=User)
|
||||||
def create_balance_on_user_creation_signal(instance, created, **kwargs):
|
def create_balance_on_user_creation_signal(instance, created, **_kwargs):
|
||||||
if created:
|
if created:
|
||||||
Balance.objects.create(user=instance)
|
Balance.objects.create(user=instance)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Transaction)
|
@receiver(post_save, sender=Transaction)
|
||||||
def process_transaction_changes(instance, created, **kwargs):
|
def process_transaction_changes(instance, created, **_kwargs):
|
||||||
if created:
|
if created:
|
||||||
try:
|
try:
|
||||||
gateway = object()
|
gateway = object()
|
||||||
|
|
|
||||||
|
|
@ -166,12 +166,12 @@ class GraphQLDepositTests(TestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
result = self.client.execute(mutation, variable_values={"amount": 100.0}, context_value={"user": self.user})
|
result = self.client.post(mutation, variable_values={"amount": 100.0}, context_value={"user": self.user})
|
||||||
# There should be no errors.
|
# There should be no errors.
|
||||||
self.assertNotIn("errors", result)
|
self.assertNotIn("errors", result)
|
||||||
transaction_data = result.get("data", {}).get("deposit", {}).get("transaction")
|
transaction_data = result.get("data", "")
|
||||||
self.assertIsNotNone(transaction_data)
|
self.assertIsNotNone(transaction_data)
|
||||||
self.assertAlmostEqual(float(transaction_data["amount"]), 100.0, places=2)
|
self.assertAlmostEqual(float(transaction_data), 100.0, places=2)
|
||||||
|
|
||||||
def test_graphql_deposit_unauthenticated(self):
|
def test_graphql_deposit_unauthenticated(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -187,9 +187,7 @@ class GraphQLDepositTests(TestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
result = self.client.execute(
|
result = self.client.post(mutation, variable_values={"amount": 100.0}, context_value={"user": AnonymousUser()})
|
||||||
mutation, variable_values={"amount": 100.0}, context_value={"user": AnonymousUser()}
|
|
||||||
)
|
|
||||||
self.assertIn("errors", result)
|
self.assertIn("errors", result)
|
||||||
error_message = result["errors"][0]["message"]
|
error_message = result["errors"][0]
|
||||||
self.assertIn("permission", error_message.lower())
|
self.assertIn("permission", error_message.lower())
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ linting = ["black", "isort", "flake8", "bandit"]
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
disable_error_code = ["import-untyped", "misc"]
|
disable_error_code = ["import-untyped", "misc"]
|
||||||
exclude = ["*/migrations/*"]
|
exclude = ["*/migrations/*", "./evibes/settings/drf.py"]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 120
|
line-length = 120
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@ from graphene_django import DjangoObjectType
|
||||||
from graphql_relay.connection.array_connection import connection_from_array
|
from graphql_relay.connection.array_connection import connection_from_array
|
||||||
|
|
||||||
from core.graphene.object_types import OrderType, ProductType, WishlistType
|
from core.graphene.object_types import OrderType, ProductType, WishlistType
|
||||||
from core.models import Product
|
from core.models import Product, Wishlist
|
||||||
from evibes.settings import LANGUAGE_CODE, LANGUAGES
|
from evibes.settings import LANGUAGE_CODE, LANGUAGES
|
||||||
from payments.graphene.object_types import BalanceType
|
from payments.graphene.object_types import BalanceType
|
||||||
|
from payments.models import Balance
|
||||||
from vibes_auth.models import User
|
from vibes_auth.models import User
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -84,22 +85,27 @@ class UserType(DjangoObjectType):
|
||||||
"is_staff",
|
"is_staff",
|
||||||
]
|
]
|
||||||
|
|
||||||
def resolve_wishlist(self, info):
|
def resolve_wishlist(self: User, info) -> Wishlist | None:
|
||||||
return self.user_related_wishlist
|
if info.context.user == self:
|
||||||
|
return self.user_related_wishlist
|
||||||
|
return None
|
||||||
|
|
||||||
def resolve_balance(self, info):
|
def resolve_balance(self: User, info) -> Balance | None:
|
||||||
return self.payments_balance
|
if info.context.user == self:
|
||||||
|
return self.payments_balance
|
||||||
|
return None
|
||||||
|
|
||||||
def resolve_avatar(self, info) -> str:
|
def resolve_avatar(self: User, info) -> str:
|
||||||
if self.avatar:
|
if self.avatar:
|
||||||
return info.context.build_absolute_uri(self.avatar.url)
|
return info.context.build_absolute_uri(self.avatar.url)
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def resolve_orders(self, info):
|
def resolve_orders(self: User, _info):
|
||||||
|
# noinspection Mypy
|
||||||
return self.orders.all() if self.orders.count() >= 1 else []
|
return self.orders.all() if self.orders.count() >= 1 else []
|
||||||
|
|
||||||
def resolve_recently_viewed(self, info, **kwargs):
|
def resolve_recently_viewed(self: User, _info, **kwargs):
|
||||||
uuid_list = self.recently_viewed or []
|
uuid_list = self.recently_viewed or []
|
||||||
|
|
||||||
if not uuid_list:
|
if not uuid_list:
|
||||||
|
|
@ -113,8 +119,8 @@ class UserType(DjangoObjectType):
|
||||||
|
|
||||||
return connection_from_array(ordered_products, kwargs)
|
return connection_from_array(ordered_products, kwargs)
|
||||||
|
|
||||||
def resolve_groups(self, info):
|
def resolve_groups(self: User, _info):
|
||||||
return self.groups.all() if self.groups.count() >= 1 else []
|
return self.groups.all() if self.groups.count() >= 1 else []
|
||||||
|
|
||||||
def resolve_user_permissions(self, info):
|
def resolve_user_permissions(self: User, _info):
|
||||||
return self.user_permissions.all() if self.user_permissions.count() >= 1 else []
|
return self.user_permissions.all() if self.user_permissions.count() >= 1 else []
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ class UserManager(BaseUserManager):
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
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
|
# noinspection PyShadowingNames
|
||||||
|
|
@ -48,11 +49,13 @@ class UserManager(BaseUserManager):
|
||||||
self.handle_unregistered_entities(user)
|
self.handle_unregistered_entities(user)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
def create_user(self, email=None, password=None, **extra_fields):
|
def create_user(self, email=None, password=None, **extra_fields):
|
||||||
extra_fields.setdefault("is_staff", False)
|
extra_fields.setdefault("is_staff", False)
|
||||||
extra_fields.setdefault("is_superuser", False)
|
extra_fields.setdefault("is_superuser", False)
|
||||||
return self._create_user(email, password, **extra_fields)
|
return self._create_user(email, password, **extra_fields)
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
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)
|
||||||
|
|
@ -67,6 +70,7 @@ class UserManager(BaseUserManager):
|
||||||
user.save()
|
user.save()
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
def with_perm(self, perm, is_active=True, include_superusers=True, backend=None, obj=None):
|
def with_perm(self, perm, is_active=True, include_superusers=True, backend=None, obj=None):
|
||||||
if backend is None:
|
if backend is None:
|
||||||
# noinspection PyCallingNonCallable
|
# noinspection PyCallingNonCallable
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,15 @@ from django.db.models.functions import Lower
|
||||||
|
|
||||||
|
|
||||||
def forwards(apps, schema_editor):
|
def forwards(apps, schema_editor):
|
||||||
|
if schema_editor:
|
||||||
|
pass
|
||||||
User = apps.get_model('vibes_auth', 'User')
|
User = apps.get_model('vibes_auth', 'User')
|
||||||
User.objects.all().update(language=Lower('language'))
|
User.objects.all().update(language=Lower('language'))
|
||||||
|
|
||||||
|
|
||||||
def backwards(apps, schema_editor):
|
def backwards(apps, schema_editor):
|
||||||
|
if schema_editor:
|
||||||
|
pass
|
||||||
User = apps.get_model('vibes_auth', 'User')
|
User = apps.get_model('vibes_auth', 'User')
|
||||||
for u in User.objects.all():
|
for u in User.objects.all():
|
||||||
parts = u.language.split('-', 1)
|
parts = u.language.split('-', 1)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from django.db.models import (
|
||||||
EmailField,
|
EmailField,
|
||||||
ImageField,
|
ImageField,
|
||||||
JSONField,
|
JSONField,
|
||||||
|
QuerySet,
|
||||||
UUIDField,
|
UUIDField,
|
||||||
)
|
)
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
@ -21,7 +22,9 @@ from rest_framework_simplejwt.token_blacklist.models import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from core.abstract import NiceModel
|
from core.abstract import NiceModel
|
||||||
|
from core.models import Order, Wishlist
|
||||||
from evibes.settings import LANGUAGE_CODE, LANGUAGES
|
from evibes.settings import LANGUAGE_CODE, LANGUAGES
|
||||||
|
from payments.models import Balance
|
||||||
from vibes_auth.managers import UserManager
|
from vibes_auth.managers import UserManager
|
||||||
from vibes_auth.validators import validate_phone_number
|
from vibes_auth.validators import validate_phone_number
|
||||||
|
|
||||||
|
|
@ -30,8 +33,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: str = EmailField(_("email"), unique=True, help_text=_("user email address"))
|
email: str = EmailField(_("email"), unique=True, help_text=_("user email address")) # type: ignore
|
||||||
phone_number: str = CharField(
|
phone_number: str = CharField( # type: ignore
|
||||||
_("phone_number"),
|
_("phone_number"),
|
||||||
max_length=20,
|
max_length=20,
|
||||||
unique=True,
|
unique=True,
|
||||||
|
|
@ -43,8 +46,8 @@ class User(AbstractUser, NiceModel):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
username = None
|
username = None
|
||||||
first_name: str = CharField(_("first_name"), max_length=150, blank=True, null=True)
|
first_name: str = CharField(_("first_name"), max_length=150, blank=True, null=True) # type: ignore
|
||||||
last_name: str = CharField(_("last_name"), max_length=150, blank=True, null=True)
|
last_name: str = CharField(_("last_name"), max_length=150, blank=True, null=True) # type: ignore
|
||||||
avatar = ImageField(
|
avatar = ImageField(
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=_("avatar"),
|
verbose_name=_("avatar"),
|
||||||
|
|
@ -53,28 +56,32 @@ class User(AbstractUser, NiceModel):
|
||||||
help_text=_("user profile image"),
|
help_text=_("user profile image"),
|
||||||
)
|
)
|
||||||
|
|
||||||
is_verified: bool = BooleanField(
|
is_verified: bool = BooleanField( # type: ignore
|
||||||
default=False,
|
default=False,
|
||||||
verbose_name=_("is verified"),
|
verbose_name=_("is verified"),
|
||||||
help_text=_("user verification status"),
|
help_text=_("user verification status"),
|
||||||
)
|
)
|
||||||
is_active: bool = BooleanField(
|
is_active: bool = BooleanField( # type: ignore
|
||||||
_("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: bool = BooleanField(
|
is_subscribed: bool = BooleanField( # type: ignore
|
||||||
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: uuid = UUIDField(default=uuid4, verbose_name=_("activation token"))
|
activation_token: uuid = UUIDField(default=uuid4, verbose_name=_("activation token")) # type: ignore
|
||||||
language: str = 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) # type: ignore
|
||||||
attributes: dict = JSONField(verbose_name=_("attributes"), default=dict, blank=True, null=True)
|
attributes: dict = JSONField(verbose_name=_("attributes"), default=dict, blank=True, null=True) # type: ignore
|
||||||
|
|
||||||
USERNAME_FIELD = "email"
|
USERNAME_FIELD = "email"
|
||||||
REQUIRED_FIELDS = []
|
REQUIRED_FIELDS = []
|
||||||
# noinspection PyClassVar
|
# noinspection PyClassVar
|
||||||
objects = UserManager()
|
objects = UserManager() # type: ignore
|
||||||
|
|
||||||
|
payments_balance: "Balance"
|
||||||
|
user_related_wishlist: "Wishlist"
|
||||||
|
orders: QuerySet["Order"]
|
||||||
|
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from vibes_auth.utils.emailing import send_verification_email_task
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=User)
|
@receiver(post_save, sender=User)
|
||||||
def send_verification_email_signal(instance, created, **kwargs):
|
def send_verification_email_signal(instance, created, **_kwargs):
|
||||||
if not created:
|
if not created:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -15,7 +15,7 @@ def send_verification_email_signal(instance, created, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_save, sender=User)
|
@receiver(pre_save, sender=User)
|
||||||
def send_user_verification_email(instance, **kwargs):
|
def send_user_verification_email(instance, **_kwargs):
|
||||||
if not instance.pk:
|
if not instance.pk:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,10 @@ class AuthTests(TestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
result = self.client.execute(query)
|
result = self.client.post(query)
|
||||||
self.assertIsNone(result.get("errors"))
|
self.assertIsNone(result.get("errors"))
|
||||||
data = result["data"]["createUser"]["user"]
|
data = result["data"]
|
||||||
self.assertEqual(data["email"], "newuser@example.com")
|
self.assertEqual(data, "newuser@example.com")
|
||||||
self.assertEqual(User.objects.count(), 3) # Initial two + new user
|
self.assertEqual(User.objects.count(), 3) # Initial two + new user
|
||||||
|
|
||||||
def test_obtain_token_view(self):
|
def test_obtain_token_view(self):
|
||||||
|
|
@ -84,7 +84,7 @@ class AuthTests(TestCase):
|
||||||
|
|
||||||
def test_confirm_password_reset(self):
|
def test_confirm_password_reset(self):
|
||||||
url = reverse("user-confirm-password-reset")
|
url = reverse("user-confirm-password-reset")
|
||||||
uid = urlsafe_base64_encode(str(self.user.pk).encode()).decode()
|
uid = urlsafe_base64_encode(str(self.user.pk).encode())
|
||||||
token = PasswordResetTokenGenerator().make_token(self.user)
|
token = PasswordResetTokenGenerator().make_token(self.user)
|
||||||
|
|
||||||
response = self.api_client.post(
|
response = self.api_client.post(
|
||||||
|
|
@ -108,7 +108,7 @@ class AuthTests(TestCase):
|
||||||
|
|
||||||
def test_activate_user(self):
|
def test_activate_user(self):
|
||||||
url = reverse("user-activate")
|
url = reverse("user-activate")
|
||||||
uid = urlsafe_base64_encode(str(self.user.pk).encode()).decode()
|
uid = urlsafe_base64_encode(str(self.user.pk).encode())
|
||||||
token = PasswordResetTokenGenerator().make_token(self.user)
|
token = PasswordResetTokenGenerator().make_token(self.user)
|
||||||
|
|
||||||
response = self.api_client.post(url, {"uidb64": uid, "token": token})
|
response = self.api_client.post(url, {"uidb64": uid, "token": token})
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ class UserViewSet(
|
||||||
|
|
||||||
@action(detail=True, methods=["put"], permission_classes=[IsAuthenticated])
|
@action(detail=True, methods=["put"], permission_classes=[IsAuthenticated])
|
||||||
@method_decorator(ratelimit(key="ip", rate="2/h" if not DEBUG else "888/h"))
|
@method_decorator(ratelimit(key="ip", rate="2/h" if not DEBUG else "888/h"))
|
||||||
def upload_avatar(self, request, **kwargs):
|
def upload_avatar(self, request, **_kwargs):
|
||||||
user = self.get_object()
|
user = self.get_object()
|
||||||
if request.user != user:
|
if request.user != user:
|
||||||
return Response(status=status.HTTP_403_FORBIDDEN)
|
return Response(status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
@ -66,7 +66,7 @@ class UserViewSet(
|
||||||
|
|
||||||
@action(detail=False, methods=["post"])
|
@action(detail=False, methods=["post"])
|
||||||
@method_decorator(ratelimit(key="ip", rate="2/h" if not DEBUG else "888/h"))
|
@method_decorator(ratelimit(key="ip", rate="2/h" if not DEBUG else "888/h"))
|
||||||
def confirm_password_reset(self, request, *args, **kwargs):
|
def confirm_password_reset(self, request, *_args, **_kwargs):
|
||||||
try:
|
try:
|
||||||
if not compare_digest(request.data.get("password"), request.data.get("confirm_password")):
|
if not compare_digest(request.data.get("password"), request.data.get("confirm_password")):
|
||||||
return Response(
|
return Response(
|
||||||
|
|
@ -143,7 +143,7 @@ class UserViewSet(
|
||||||
return Response(response_data, status=status.HTTP_200_OK)
|
return Response(response_data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
@action(detail=True, methods=["put"], permission_classes=[IsAuthenticated])
|
@action(detail=True, methods=["put"], permission_classes=[IsAuthenticated])
|
||||||
def merge_recently_viewed(self, request, **kwargs):
|
def merge_recently_viewed(self, request, **_kwargs):
|
||||||
user = self.get_object()
|
user = self.get_object()
|
||||||
if request.user != user:
|
if request.user != user:
|
||||||
return Response(status=status.HTTP_403_FORBIDDEN)
|
return Response(status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue