Features: 1) RUFFVCKED

This commit is contained in:
Egor Pavlovich Gorbunov 2025-07-03 15:00:18 +03:00
parent aa8f15d0ee
commit 29005527bb
15 changed files with 126 additions and 106 deletions

View file

@ -9,6 +9,9 @@
<option name="TEMPLATE_FOLDERS"> <option name="TEMPLATE_FOLDERS">
<list> <list>
<option value="$MODULE_DIR$/blog/templates" /> <option value="$MODULE_DIR$/blog/templates" />
<option value="$MODULE_DIR$/core/templates" />
<option value="$MODULE_DIR$/payments/templates" />
<option value="$MODULE_DIR$/vibes_auth/templates" />
</list> </list>
</option> </option>
</component> </component>

View file

@ -7,7 +7,7 @@ from markdown_field import MarkdownField
from core.abstract import NiceModel from core.abstract import NiceModel
class Post(NiceModel): class Post(NiceModel): # type: ignore [django-manager-missing]
""" """
Represents a blog post model extending NiceModel. Represents a blog post model extending NiceModel.
@ -21,8 +21,8 @@ class Post(NiceModel):
Attributes: Attributes:
is_publicly_visible (bool): Specifies whether the post is visible to the public. is_publicly_visible (bool): Specifies whether the post is visible to the public.
author (ForeignKey): A reference to the user who authored the post. author (ForeignKey): A reference to the user who authored the post.
title (CharField): The title of the post, must be unique and non-empty. title (CharField): The title of the post. Must be unique and non-empty.
content (MarkdownField): The content of the post written in markdown format. content (MarkdownField): The content of the post written in Markdown format.
file (FileField): An optional file attachment for the post. file (FileField): An optional file attachment for the post.
slug (AutoSlugField): A unique, automatically generated slug based on the title. slug (AutoSlugField): A unique, automatically generated slug based on the title.
tags (ManyToManyField): Tags associated with the post for categorization. tags (ManyToManyField): Tags associated with the post for categorization.
@ -102,9 +102,9 @@ class PostTag(NiceModel):
Attributes: Attributes:
is_publicly_visible (bool): Determines if the tag is visible publicly. is_publicly_visible (bool): Determines if the tag is visible publicly.
tag_name (CharField): An internal tag identifier for the post tag. It is a required tag_name (CharField): An internal tag identifier for the post's tag. It is a required
field with a maximum length of 255 characters. field with a maximum length of 255 characters.
name (CharField): A user-friendly, unique display name for the post tag name (CharField): A user-friendly, unique display name for the post's tag
with a maximum length of 255 characters. with a maximum length of 255 characters.
Meta: Meta:

View file

@ -82,40 +82,17 @@ class FieldsetsMixin:
return fieldsets return fieldsets
# noinspection PyUnresolvedReferences
class ActivationActionsMixin: class ActivationActionsMixin:
@action(description=str(_("activate selected %(verbose_name_plural)s"))) @action(description=_("activate selected %(verbose_name_plural)s"))
def activate_selected(self, request, queryset, **kwargs) -> str: def activate_selected(self, request, queryset):
if kwargs:
pass
if request:
pass
queryset.update(is_active=True) queryset.update(is_active=True)
return str(_("%(verbose_name_plural)s activated successfully!")) self.message_user(request, _("selected items have been activated."))
@action(description=str(_("deactivate selected %(verbose_name_plural)s"))) @action(description=_("deactivate selected %(verbose_name_plural)s"))
def deactivate_selected(self, request, queryset, **kwargs) -> str: def deactivate_selected(self, request, queryset):
if kwargs:
pass
if request:
pass
queryset.update(is_active=False) queryset.update(is_active=False)
return str(_("%(verbose_name_plural)s deactivated successfully.")) self.message_user(request, _("selected items have been deactivated."))
def get_actions(self, request, **kwargs):
if kwargs:
pass
actions = super().get_actions(request)
actions["activate_selected"] = (
self.activate_selected,
"activate_selected",
str(_("activate selected %(verbose_name_plural)s")),
)
actions["deactivate_selected"] = (
self.deactivate_selected,
"deactivate_selected",
str(_("deactivate selected %(verbose_name_plural)s")),
)
return actions
class AttributeValueInline(TabularInline): class AttributeValueInline(TabularInline):
@ -173,7 +150,8 @@ class CategoryChildrenInline(TabularInline):
@register(AttributeGroup) @register(AttributeGroup)
class AttributeGroupAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class AttributeGroupAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = AttributeGroup # noinspection PyClassVar
model = AttributeGroup # type: ignore [misc]
list_display = ("name", "modified") list_display = ("name", "modified")
search_fields = ("uuid", "name") search_fields = ("uuid", "name")
readonly_fields = ("uuid", "modified", "created") readonly_fields = ("uuid", "modified", "created")
@ -184,7 +162,8 @@ class AttributeGroupAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Attribute) @register(Attribute)
class AttributeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class AttributeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = Attribute # noinspection PyClassVar
model = Attribute # type: ignore [misc]
list_display = ("name", "group", "value_type", "modified") list_display = ("name", "group", "value_type", "modified")
list_filter = ("value_type", "group", "is_active") list_filter = ("value_type", "group", "is_active")
search_fields = ("uuid", "name", "group__name") search_fields = ("uuid", "name", "group__name")
@ -197,7 +176,8 @@ class AttributeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(AttributeValue) @register(AttributeValue)
class AttributeValueAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class AttributeValueAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = AttributeValue # noinspection PyClassVar
model = AttributeValue # type: ignore [misc]
list_display = ("attribute", "value", "modified") list_display = ("attribute", "value", "modified")
list_filter = ("attribute__group", "is_active") list_filter = ("attribute__group", "is_active")
search_fields = ("uuid", "value", "attribute__name") search_fields = ("uuid", "value", "attribute__name")
@ -210,6 +190,7 @@ class AttributeValueAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Category) @register(Category)
class CategoryAdmin(FieldsetsMixin, ActivationActionsMixin, DraggableMPTTAdmin): class CategoryAdmin(FieldsetsMixin, ActivationActionsMixin, DraggableMPTTAdmin):
# noinspection PyClassVar
model = Category model = Category
list_display = ("indented_title", "parent", "is_active", "modified") list_display = ("indented_title", "parent", "is_active", "modified")
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
@ -225,7 +206,8 @@ class CategoryAdmin(FieldsetsMixin, ActivationActionsMixin, DraggableMPTTAdmin):
@register(Brand) @register(Brand)
class BrandAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class BrandAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = Brand # noinspection PyClassVar
model = Brand # type: ignore [misc]
list_display = ("name",) list_display = ("name",)
list_filter = ("categories", "is_active") list_filter = ("categories", "is_active")
search_fields = ("uuid", "name", "categories__name") search_fields = ("uuid", "name", "categories__name")
@ -237,7 +219,8 @@ class BrandAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Product) @register(Product)
class ProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class ProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = Product # noinspection PyClassVar
model = Product # type: ignore [misc]
list_display = ( list_display = (
"name", "name",
"partnumber", "partnumber",
@ -276,7 +259,8 @@ class ProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(ProductTag) @register(ProductTag)
class ProductTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class ProductTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = ProductTag # noinspection PyClassVar
model = ProductTag # type: ignore [misc]
list_display = ("tag_name",) list_display = ("tag_name",)
search_fields = ("tag_name",) search_fields = ("tag_name",)
readonly_fields = ("uuid", "modified", "created") readonly_fields = ("uuid", "modified", "created")
@ -287,7 +271,8 @@ class ProductTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(CategoryTag) @register(CategoryTag)
class CategoryTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class CategoryTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = CategoryTag # noinspection PyClassVar
model = CategoryTag # type: ignore [misc]
list_display = ("tag_name",) list_display = ("tag_name",)
search_fields = ("tag_name",) search_fields = ("tag_name",)
readonly_fields = ("uuid", "modified", "created") readonly_fields = ("uuid", "modified", "created")
@ -298,7 +283,8 @@ class CategoryTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Vendor) @register(Vendor)
class VendorAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class VendorAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = Vendor # noinspection PyClassVar
model = Vendor # type: ignore [misc]
list_display = ("name", "markup_percent", "modified") list_display = ("name", "markup_percent", "modified")
list_filter = ("markup_percent", "is_active") list_filter = ("markup_percent", "is_active")
search_fields = ("name",) search_fields = ("name",)
@ -311,7 +297,8 @@ class VendorAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Feedback) @register(Feedback)
class FeedbackAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class FeedbackAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = Feedback # noinspection PyClassVar
model = Feedback # type: ignore [misc]
list_display = ("order_product", "rating", "comment", "modified") list_display = ("order_product", "rating", "comment", "modified")
list_filter = ("rating", "is_active") list_filter = ("rating", "is_active")
search_fields = ("order_product__product__name", "comment") search_fields = ("order_product__product__name", "comment")
@ -323,7 +310,8 @@ class FeedbackAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Order) @register(Order)
class OrderAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class OrderAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = Order # noinspection PyClassVar
model = Order # type: ignore [misc]
list_display = ( list_display = (
"human_readable_id", "human_readable_id",
"user", "user",
@ -351,7 +339,8 @@ class OrderAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(OrderProduct) @register(OrderProduct)
class OrderProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class OrderProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = OrderProduct # noinspection PyClassVar
model = OrderProduct # type: ignore [misc]
list_display = ("order", "product", "quantity", "buy_price", "status", "modified") list_display = ("order", "product", "quantity", "buy_price", "status", "modified")
list_filter = ("status",) list_filter = ("status",)
search_fields = ("order__user__email", "product__name") search_fields = ("order__user__email", "product__name")
@ -364,7 +353,8 @@ class OrderProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(PromoCode) @register(PromoCode)
class PromoCodeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class PromoCodeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = PromoCode # noinspection PyClassVar
model = PromoCode # type: ignore [misc]
list_display = ( list_display = (
"code", "code",
"discount_percent", "discount_percent",
@ -392,7 +382,8 @@ class PromoCodeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Promotion) @register(Promotion)
class PromotionAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class PromotionAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = Promotion # noinspection PyClassVar
model = Promotion # type: ignore [misc]
list_display = ("name", "discount_percent", "modified") list_display = ("name", "discount_percent", "modified")
search_fields = ("name",) search_fields = ("name",)
readonly_fields = ("uuid", "modified", "created") readonly_fields = ("uuid", "modified", "created")
@ -404,7 +395,8 @@ class PromotionAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Stock) @register(Stock)
class StockAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class StockAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = Stock # noinspection PyClassVar
model = Stock # type: ignore [misc]
list_display = ("product", "vendor", "sku", "quantity", "price", "modified") list_display = ("product", "vendor", "sku", "quantity", "price", "modified")
list_filter = ("vendor", "quantity") list_filter = ("vendor", "quantity")
search_fields = ("product__name", "vendor__name", "sku") search_fields = ("product__name", "vendor__name", "sku")
@ -424,7 +416,8 @@ class StockAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Wishlist) @register(Wishlist)
class WishlistAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class WishlistAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = Wishlist # noinspection PyClassVar
model = Wishlist # type: ignore [misc]
list_display = ("user", "modified") list_display = ("user", "modified")
search_fields = ("user__email",) search_fields = ("user__email",)
readonly_fields = ("uuid", "modified", "created") readonly_fields = ("uuid", "modified", "created")
@ -435,7 +428,8 @@ class WishlistAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(ProductImage) @register(ProductImage)
class ProductImageAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class ProductImageAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = ProductImage # noinspection PyClassVar
model = ProductImage # type: ignore [misc]
list_display = ("alt", "product", "priority", "modified") list_display = ("alt", "product", "priority", "modified")
list_filter = ("priority",) list_filter = ("priority",)
search_fields = ("alt", "product__name") search_fields = ("alt", "product__name")
@ -448,7 +442,8 @@ class ProductImageAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Address) @register(Address)
class AddressAdmin(FieldsetsMixin, GISModelAdmin): class AddressAdmin(FieldsetsMixin, GISModelAdmin):
model = Address # noinspection PyClassVar
model = Address # type: ignore [misc]
list_display = ("street", "city", "region", "country", "user") list_display = ("street", "city", "region", "country", "user")
list_filter = ("country", "region") list_filter = ("country", "region")
search_fields = ("street", "city", "postal_code", "user__email") search_fields = ("street", "city", "postal_code", "user__email")
@ -511,7 +506,7 @@ class ConstanceConfig:
# noinspection PyTypeChecker # noinspection PyTypeChecker
site.unregister([Config]) site.unregister([Config])
# noinspection PyTypeChecker # noinspection PyTypeChecker
site.register([ConstanceConfig], BaseConstanceAdmin) site.register([ConstanceConfig], BaseConstanceAdmin) # type: ignore [list-item]
site.site_title = CONSTANCE_CONFIG["PROJECT_NAME"][0] site.site_title = CONSTANCE_CONFIG["PROJECT_NAME"][0] # type: ignore [assignment]
site.site_header = "eVibes" site.site_header = "eVibes"
site.index_title = CONSTANCE_CONFIG["PROJECT_NAME"][0] site.index_title = CONSTANCE_CONFIG["PROJECT_NAME"][0] # type: ignore [assignment]

View file

@ -860,7 +860,7 @@ class ProductImage(ExportModelOperationsMixin("product_image"), NiceModel): # t
image (ImageField): The image file associated with the product. image (ImageField): The image file associated with the product.
priority (int): The display priority of the image. Images with lower priority (int): The display priority of the image. Images with lower
priority values are displayed first. priority values are displayed first.
product (ForeignKey): The product to which this image is associated. product (ForeignKey): The product associated with this image.
""" """
is_publicly_visible = True is_publicly_visible = True
@ -1278,11 +1278,11 @@ class PromoCode(ExportModelOperationsMixin("promocode"), NiceModel): # type: ig
promo_amount = order.total_price promo_amount = order.total_price
if self.discount_type == "percent": if self.discount_type == "percent":
promo_amount -= round(promo_amount * (float(self.discount_percent) / 100), 2) promo_amount -= round(promo_amount * (float(self.discount_percent) / 100), 2) # type: ignore [arg-type]
order.attributes.update({"promocode": str(self.uuid), "final_price": promo_amount}) order.attributes.update({"promocode": str(self.uuid), "final_price": promo_amount})
order.save() order.save()
elif self.discount_type == "amount": elif self.discount_type == "amount":
promo_amount -= round(float(self.discount_amount), 2) promo_amount -= round(float(self.discount_amount), 2) # type: ignore [arg-type]
order.attributes.update({"promocode": str(self.uuid), "final_price": promo_amount}) order.attributes.update({"promocode": str(self.uuid), "final_price": promo_amount})
order.save() order.save()
else: else:
@ -1447,7 +1447,7 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): # type: ignore [mi
promotions = Promotion.objects.filter(is_active=True, products__in=[product]).order_by("discount_percent") promotions = Promotion.objects.filter(is_active=True, products__in=[product]).order_by("discount_percent")
if promotions.exists(): if promotions.exists():
buy_price -= round(product.price * (promotions.first().discount_percent / 100), 2) buy_price -= round(product.price * (promotions.first().discount_percent / 100), 2) # type: ignore [union-attr]
order_product, is_created = OrderProduct.objects.get_or_create( order_product, is_created = OrderProduct.objects.get_or_create(
product=product, product=product,
@ -1588,6 +1588,12 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): # type: ignore [mi
amount = self.apply_promocode(promocode_uuid) if promocode_uuid else self.total_price amount = self.apply_promocode(promocode_uuid) if promocode_uuid else self.total_price
if not self.user:
raise ValueError(_("you cannot buy an order without a user"))
if not self.user.payments_balance:
raise ValueError(_("a user without a balance cannot buy with balance"))
match force: match force:
case "balance": case "balance":
if self.user.payments_balance.amount < amount: if self.user.payments_balance.amount < amount:
@ -1786,7 +1792,10 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel): # t
) )
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.product.name} for ({self.order.user.email if self.order.user else 'unregistered user'})" return (
f"{self.product.name if self.product else self.uuid}"
f" for ({self.order.user.email if self.order and self.order.user else 'unregistered user'})"
)
class Meta: class Meta:
verbose_name = _("order product") verbose_name = _("order product")
@ -1827,16 +1836,18 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel): # t
@property @property
def total_price(self: Self) -> float: def total_price(self: Self) -> float:
return round(float(self.buy_price) * self.quantity, 2) return round(float(self.buy_price) * self.quantity, 2) # type: ignore [arg-type]
@property @property
def download_url(self: Self) -> str: def download_url(self: Self) -> str:
if self.product: if self.product and self.product.stocks:
if self.product.is_digital and self.product.stocks.first().digital_asset: if self.product.is_digital and self.product.stocks.first().digital_asset: # type: ignore [union-attr]
return self.download.url return self.download.url
return "" return ""
def do_feedback(self, rating=10, comment="", action="add") -> Optional["Feedback"]: def do_feedback(self, rating=10, comment="", action="add") -> Optional["Feedback"]:
if not self.order:
raise ValueError(_("order product must have an order"))
if action not in ["add", "remove"]: if action not in ["add", "remove"]:
raise ValueError(_(f"wrong action specified for feedback: {action}")) raise ValueError(_(f"wrong action specified for feedback: {action}"))
if action == "remove" and self.feedback: if action == "remove" and self.feedback:
@ -1904,7 +1915,7 @@ class Feedback(ExportModelOperationsMixin("feedback"), NiceModel): # type: igno
This class is designed to capture and store user feedback for specific products This class is designed to capture and store user feedback for specific products
that they have purchased. It contains attributes to store user comments, that they have purchased. It contains attributes to store user comments,
a reference to the related product in the order, and a user-assigned rating. The a reference to the related product in the order, and a user-assigned rating. The
class utilizes database fields to effectively model and manage feedback data. class uses database fields to effectively model and manage feedback data.
Attributes: Attributes:
is_publicly_visible (bool): Indicates whether the feedback is visible to the public. is_publicly_visible (bool): Indicates whether the feedback is visible to the public.
@ -1940,7 +1951,9 @@ class Feedback(ExportModelOperationsMixin("feedback"), NiceModel): # type: igno
) )
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.rating} by {self.order_product.order.user.email}" if self.order_product and self.order_product.order and self.order_product.order.user:
return f"{self.rating} by {self.order_product.order.user.email}"
return f"{self.rating} | {self.uuid}"
class Meta: class Meta:
verbose_name = _("feedback") verbose_name = _("feedback")

View file

@ -1,4 +1,5 @@
from contextlib import suppress from contextlib import suppress
from typing import Collection, Any
from rest_framework.fields import JSONField, SerializerMethodField from rest_framework.fields import JSONField, SerializerMethodField
from rest_framework.relations import PrimaryKeyRelatedField from rest_framework.relations import PrimaryKeyRelatedField
@ -26,8 +27,8 @@ from core.serializers.utility import AddressSerializer
class AttributeGroupSimpleSerializer(ModelSerializer): class AttributeGroupSimpleSerializer(ModelSerializer):
parent = PrimaryKeyRelatedField(read_only=True) parent = PrimaryKeyRelatedField(read_only=True) # type: ignore [assignment, var-annotated]
children = PrimaryKeyRelatedField(many=True, read_only=True) children = PrimaryKeyRelatedField(many=True, read_only=True) # type: ignore [assignment, var-annotated]
class Meta: class Meta:
model = AttributeGroup model = AttributeGroup
@ -58,7 +59,7 @@ class CategorySimpleSerializer(ModelSerializer):
return obj.image.url return obj.image.url
return None return None
def get_children(self, obj) -> list: def get_children(self, obj) -> Collection[Any]:
request = self.context.get("request") request = self.context.get("request")
if request is not None and request.user.has_perm("view_category"): if request is not None and request.user.has_perm("view_category"):
children = obj.children.all() children = obj.children.all()

View file

@ -89,7 +89,7 @@ class DoFeedbackSerializer(Serializer):
class CacheOperatorSerializer(Serializer): class CacheOperatorSerializer(Serializer):
key = CharField(required=True) key = CharField(required=True)
data = JSONField(required=False) data = JSONField(required=False) # type: ignore [assignment]
timeout = IntegerField(required=False) timeout = IntegerField(required=False)

View file

@ -83,6 +83,9 @@ def send_order_finished_email(order_pk: str) -> tuple[bool, str]:
if len(ops) <= 0: if len(ops) <= 0:
return return
if not order.user:
return
activate(order.user.language) activate(order.user.language)
set_email_settings() set_email_settings()
@ -94,11 +97,11 @@ 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": "" or order.user.first_name,
"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(0.0 or op.buy_price for op in ops), 2), # type: ignore [misc]
"display_system_attributes": order.user.has_perm("core.view_order"), "display_system_attributes": order.user.has_perm("core.view_order"),
"today": datetime.today(), "today": datetime.today(),
}, },
@ -113,6 +116,8 @@ def send_order_finished_email(order_pk: str) -> tuple[bool, str]:
def send_thank_you_email(ops: list[OrderProduct]): def send_thank_you_email(ops: list[OrderProduct]):
if ops: if ops:
pass pass
if not order.user:
return
activate(order.user.language) activate(order.user.language)
set_email_settings() set_email_settings()

View file

@ -149,7 +149,7 @@ class SupportedLanguagesView(APIView):
""" """
Handles retrieving the list of supported languages. Handles retrieving the list of supported languages.
This class provides an endpoint to return available languages information. This class provides an endpoint to return information about available languages.
It is configured with relevant serializers, permission classes, and renderers It is configured with relevant serializers, permission classes, and renderers
for flexibility in response formats and access permissions. The endpoint for flexibility in response formats and access permissions. The endpoint
supports retrieving the list of languages with their respective codes, names, supports retrieving the list of languages with their respective codes, names,
@ -328,7 +328,8 @@ class RequestCursedURLView(APIView):
renderer_classes (list): Configures the response format renderers available for this view. renderer_classes (list): Configures the response format renderers available for this view.
Methods: Methods:
post: Handles the POST request to validate the URL, fetch its data if valid, and returns the processed response. post: Handles the POST request to validate the URL, fetches its data if valid,
and returns the processed response.
""" """
permission_classes = [ permission_classes = [

View file

@ -245,7 +245,7 @@ class CategoryViewSet(EvibesViewSet):
Attributes: Attributes:
queryset: The base queryset used to retrieve category data, including queryset: The base queryset used to retrieve category data, including
prefetching related objects such as parent, children, attributes, prefetching related objects such as parents, children, attributes,
and tags. and tags.
filter_backends: A list of backends for applying filters to the category filter_backends: A list of backends for applying filters to the category
data. data.
@ -315,8 +315,8 @@ class ProductViewSet(EvibesViewSet):
Manages operations related to the `Product` model in the system. Manages operations related to the `Product` model in the system.
This class provides a viewset for managing products, including their filtering, serialization, This class provides a viewset for managing products, including their filtering, serialization,
and operations on specific instances. It extends from `EvibesViewSet` to utilize common and operations on specific instances. It extends from `EvibesViewSet` to use common
functionality and integrates with Django REST framework for RESTful API operations. functionality and integrates with the Django REST framework for RESTful API operations.
Includes methods for retrieving product details, applying permissions, and accessing Includes methods for retrieving product details, applying permissions, and accessing
related feedback of a product. related feedback of a product.
@ -383,6 +383,7 @@ class ProductViewSet(EvibesViewSet):
if request.user.has_perm("core.view_feedback") if request.user.has_perm("core.view_feedback")
else Feedback.objects.filter(order_product__product=product, is_active=True) else Feedback.objects.filter(order_product__product=product, is_active=True)
) )
# noinspection PyTypeChecker
return Response(data=FeedbackDetailSerializer(feedbacks, many=True).data) return Response(data=FeedbackDetailSerializer(feedbacks, many=True).data)
except Product.DoesNotExist: except Product.DoesNotExist:
name = "Product" name = "Product"
@ -784,7 +785,7 @@ class PromoCodeViewSet(EvibesViewSet):
This class extends the functionality of the EvibesViewSet to provide a This class extends the functionality of the EvibesViewSet to provide a
customized view set for PromoCode objects. It includes filtering capabilities, customized view set for PromoCode objects. It includes filtering capabilities,
utilizes specific serializers for different actions, and limits data access uses specific serializers for different actions, and limits data access
based on user permissions. The primary purpose is to enable API operations based on user permissions. The primary purpose is to enable API operations
related to PromoCodes while enforcing security and filtering. related to PromoCodes while enforcing security and filtering.
@ -840,7 +841,7 @@ class StockViewSet(EvibesViewSet):
Handles operations related to Stock data in the system. Handles operations related to Stock data in the system.
The StockViewSet class is a viewset that provides methods for retrieving, The StockViewSet class is a viewset that provides methods for retrieving,
filtering, and serializing Stock data. It utilizes Django's filter filtering, and serializing Stock data. It uses Django's filter
backends to enable filtering based on specified fields and supports backends to enable filtering based on specified fields and supports
custom serializers for different actions. custom serializers for different actions.
@ -875,7 +876,7 @@ class WishlistViewSet(EvibesViewSet):
allowing for the retrieval, modification, and customization of products within allowing for the retrieval, modification, and customization of products within
the wish list. This ViewSet facilitates functionality such as adding, removing, the wish list. This ViewSet facilitates functionality such as adding, removing,
and bulk actions for wishlist products. Permission checks are integrated to and bulk actions for wishlist products. Permission checks are integrated to
ensure that users can only manage their own wishlists, unless explicit permissions ensure that users can only manage their own wishlists unless explicit permissions
are granted. are granted.
Attributes Attributes
@ -1096,10 +1097,10 @@ class ProductTagViewSet(EvibesViewSet):
filterset_fields: Fields available for filtering the queryset. Includes filterset_fields: Fields available for filtering the queryset. Includes
'tag_name' for filtering by the name of the tag, and 'is_active' for 'tag_name' for filtering by the name of the tag, and 'is_active' for
filtering active/inactive tags. filtering active/inactive tags.
serializer_class: The default serializer class used when no specific serializer_class: The default serializer class is used when no specific
serializer is defined for an action. serializer is defined for an action.
action_serializer_classes: A dictionary mapping specific actions (e.g., action_serializer_classes: A dictionary mapping specific actions (e.g.,
'list') to custom serializers. Utilizes ProductTagSimpleSerializer 'list') to custom serializers. Uses ProductTagSimpleSerializer
for the 'list' action. for the 'list' action.
""" """

View file

@ -227,7 +227,7 @@ CURRENCIES: tuple[tuple[str, str], ...] = (
("zh-hans", "CNY"), ("zh-hans", "CNY"),
) )
CURRENCY_CODE: str | None = dict(CURRENCIES).get(LANGUAGE_CODE) CURRENCY_CODE: str = dict(CURRENCIES).get(LANGUAGE_CODE) # type: ignore [assignment]
MODELTRANSLATION_FALLBACK_LANGUAGES: tuple = (LANGUAGE_CODE, "en-us", "de-de") MODELTRANSLATION_FALLBACK_LANGUAGES: tuple = (LANGUAGE_CODE, "en-us", "de-de")

View file

@ -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 [index]
EMAIL_PORT = CONSTANCE_CONFIG.get("EMAIL_PORT")[0] EMAIL_PORT = CONSTANCE_CONFIG.get("EMAIL_PORT")[0] # type: ignore [index]
EMAIL_USE_TLS = CONSTANCE_CONFIG.get("EMAIL_USE_TLS")[0] EMAIL_USE_TLS = CONSTANCE_CONFIG.get("EMAIL_USE_TLS")[0] # type: ignore [index]
EMAIL_USE_SSL = CONSTANCE_CONFIG.get("EMAIL_USE_SSL")[0] EMAIL_USE_SSL = CONSTANCE_CONFIG.get("EMAIL_USE_SSL")[0] # type: ignore [index]
EMAIL_HOST_USER = CONSTANCE_CONFIG.get("EMAIL_HOST_USER")[0] EMAIL_HOST_USER = CONSTANCE_CONFIG.get("EMAIL_HOST_USER")[0] # type: ignore [index]
EMAIL_HOST_PASSWORD = CONSTANCE_CONFIG.get("EMAIL_HOST_PASSWORD")[0] EMAIL_HOST_PASSWORD = CONSTANCE_CONFIG.get("EMAIL_HOST_PASSWORD")[0] # type: ignore [index]

View file

@ -6,10 +6,10 @@ from payments.serializers import TransactionSerializer
class TransactionViewSet(ReadOnlyModelViewSet): class TransactionViewSet(ReadOnlyModelViewSet):
""" """
ViewSet for handling read-only operations on Transaction model. ViewSet for handling read-only operations on the Transaction model.
This class provides a read-only interface for interacting with transaction This class provides a read-only interface for interacting with transaction
data. It utilizes the TransactionSerializer for serializing and deserializing data. It uses the TransactionSerializer for serializing and deserializing
the data. The class ensures that only authorized users, who meet specific the data. The class ensures that only authorized users, who meet specific
permissions, can access the transactions. permissions, can access the transactions.

View file

@ -20,7 +20,6 @@ from rest_framework_simplejwt.token_blacklist.models import (
OutstandingToken as BaseOutstandingToken, OutstandingToken as BaseOutstandingToken,
) )
from blog.models import Post
from core.abstract import NiceModel from core.abstract import NiceModel
from core.models import Order, Wishlist from core.models import Order, Wishlist
from evibes.settings import LANGUAGE_CODE, LANGUAGES from evibes.settings import LANGUAGE_CODE, LANGUAGES
@ -29,7 +28,7 @@ from vibes_auth.managers import UserManager
from vibes_auth.validators import validate_phone_number from vibes_auth.validators import validate_phone_number
class User(AbstractUser, NiceModel): class User(AbstractUser, NiceModel): # type: ignore [django-manager-missing]
""" """
Represents a User entity with customized fields and methods for extended functionality. Represents a User entity with customized fields and methods for extended functionality.
@ -54,9 +53,9 @@ class User(AbstractUser, NiceModel):
attributes: JSONField for custom storage of user-specific additional attributes. attributes: JSONField for custom storage of user-specific additional attributes.
USERNAME_FIELD: Specifies the unique identifier for the user (email in this case). USERNAME_FIELD: Specifies the unique identifier for the user (email in this case).
REQUIRED_FIELDS: A list of fields required when creating a user via createsuperuser, left empty here. REQUIRED_FIELDS: A list of fields required when creating a user via createsuperuser, left empty here.
objects: Custom manager for User model providing additional methods for user creation. objects: Custom manager for the User model providing additional methods for user creation.
payments_balance: Reference to the user's payment balance (related to external model). payments_balance: Reference to the user's payment balance (related to the external model).
user_related_wishlist: Reference to the user's wishlist (related to external model). user_related_wishlist: Reference to the user's wishlist (related to the external model).
orders: QuerySet representing the user's associated orders. orders: QuerySet representing the user's associated orders.
Methods: Methods:
@ -96,7 +95,6 @@ class User(AbstractUser, NiceModel):
], ],
) )
username: None = None # type: ignore [assignment] username: None = None # type: ignore [assignment]
posts: "Post"
first_name = CharField(_("first_name"), max_length=150, blank=True, null=True) # type: ignore [assignment] first_name = CharField(_("first_name"), max_length=150, blank=True, null=True) # type: ignore [assignment]
last_name = CharField(_("last_name"), max_length=150, blank=True, null=True) # type: ignore [assignment] last_name = CharField(_("last_name"), max_length=150, blank=True, null=True) # type: ignore [assignment]
avatar = ImageField( avatar = ImageField(
@ -207,7 +205,7 @@ class BlacklistedToken(BaseBlacklistedToken):
blacklisted tokens. It inherits from a base class provided for this purpose, blacklisted tokens. It inherits from a base class provided for this purpose,
allowing for the extension of functionality or customization of behavior allowing for the extension of functionality or customization of behavior
without altering the original base class's structure. It also defines the without altering the original base class's structure. It also defines the
meta options for the model, affecting its database and administrative Meta options for the model, affecting its database and administrative
representation. representation.
""" """

View file

@ -162,7 +162,10 @@ class TokenObtainPairSerializer(TokenObtainSerializer):
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) if not self.user:
raise ValidationError(_("no active account"))
# noinspection PyTypeChecker
update_last_login(User, self.user)
logger.debug("Updated last login") logger.debug("Updated last login")
logger.debug("Returning data") logger.debug("Returning data")
@ -191,7 +194,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"])
# noinspection PyTypeChecker # noinspection PyTypeChecker
data["user"] = UserSerializer(user).data data["user"] = UserSerializer(user).data # type: ignore [assignment]
return data return data
@ -224,7 +227,7 @@ class TokenVerifySerializer(Serializer):
raise ValidationError(_("user does not exist")) from dne raise ValidationError(_("user does not exist")) from dne
# noinspection PyTypeChecker # noinspection PyTypeChecker
attrs["user"] = UserSerializer(user).data attrs["user"] = UserSerializer(user).data # type: ignore [assignment]
return attrs return attrs

View file

@ -47,8 +47,8 @@ class TokenObtainPairView(TokenViewBase):
subject to rate limiting depending on the global DEBUG setting. subject to rate limiting depending on the global DEBUG setting.
""" """
serializer_class = TokenObtainPairSerializer serializer_class = TokenObtainPairSerializer # type: ignore [assignment]
_serializer_class = TokenObtainPairSerializer _serializer_class = TokenObtainPairSerializer # type: ignore [assignment]
@method_decorator(ratelimit(key="ip", rate="10/h" if not DEBUG else "888/h")) @method_decorator(ratelimit(key="ip", rate="10/h" if not DEBUG else "888/h"))
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
@ -82,8 +82,8 @@ class TokenRefreshView(TokenViewBase):
whether the application is in DEBUG mode or not. whether the application is in DEBUG mode or not.
""" """
serializer_class = TokenRefreshSerializer serializer_class = TokenRefreshSerializer # type: ignore [assignment]
_serializer_class = TokenRefreshSerializer _serializer_class = TokenRefreshSerializer # type: ignore [assignment]
@method_decorator(ratelimit(key="ip", rate="10/h" if not DEBUG else "888/h")) @method_decorator(ratelimit(key="ip", rate="10/h" if not DEBUG else "888/h"))
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
@ -104,8 +104,8 @@ class TokenVerifyView(TokenViewBase):
error response. error response.
""" """
serializer_class = TokenVerifySerializer serializer_class = TokenVerifySerializer # type: ignore [assignment]
_serializer_class = TokenVerifySerializer _serializer_class = TokenVerifySerializer # type: ignore [assignment]
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
try: try: