From 29005527bb9f3fcc9da130318ad97bd590d0b837 Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Thu, 3 Jul 2025 15:00:18 +0300 Subject: [PATCH] Features: 1) RUFFVCKED --- .idea/evibes.iml | 3 ++ blog/models.py | 10 ++-- core/admin.py | 95 ++++++++++++++++++------------------- core/models.py | 33 +++++++++---- core/serializers/simple.py | 7 +-- core/serializers/utility.py | 2 +- core/utils/emailing.py | 9 +++- core/views.py | 5 +- core/viewsets.py | 17 +++---- evibes/settings/base.py | 2 +- evibes/settings/emailing.py | 12 ++--- payments/viewsets.py | 4 +- vibes_auth/models.py | 12 ++--- vibes_auth/serializers.py | 9 ++-- vibes_auth/views.py | 12 ++--- 15 files changed, 126 insertions(+), 106 deletions(-) diff --git a/.idea/evibes.iml b/.idea/evibes.iml index 5fe1663e..7db85b5f 100644 --- a/.idea/evibes.iml +++ b/.idea/evibes.iml @@ -9,6 +9,9 @@ diff --git a/blog/models.py b/blog/models.py index a8fd89cd..18878a5a 100644 --- a/blog/models.py +++ b/blog/models.py @@ -7,7 +7,7 @@ from markdown_field import MarkdownField from core.abstract import NiceModel -class Post(NiceModel): +class Post(NiceModel): # type: ignore [django-manager-missing] """ Represents a blog post model extending NiceModel. @@ -21,8 +21,8 @@ class Post(NiceModel): Attributes: is_publicly_visible (bool): Specifies whether the post is visible to the public. author (ForeignKey): A reference to the user who authored the post. - title (CharField): The title of the post, must be unique and non-empty. - content (MarkdownField): The content of the post written in markdown format. + title (CharField): The title of the post. Must be unique and non-empty. + content (MarkdownField): The content of the post written in Markdown format. file (FileField): An optional file attachment for the post. slug (AutoSlugField): A unique, automatically generated slug based on the title. tags (ManyToManyField): Tags associated with the post for categorization. @@ -102,9 +102,9 @@ class PostTag(NiceModel): Attributes: 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. - 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. Meta: diff --git a/core/admin.py b/core/admin.py index 7e5ddf32..3e6e0c70 100644 --- a/core/admin.py +++ b/core/admin.py @@ -82,40 +82,17 @@ class FieldsetsMixin: return fieldsets +# noinspection PyUnresolvedReferences class ActivationActionsMixin: - @action(description=str(_("activate selected %(verbose_name_plural)s"))) - def activate_selected(self, request, queryset, **kwargs) -> str: - if kwargs: - pass - if request: - pass + @action(description=_("activate selected %(verbose_name_plural)s")) + def activate_selected(self, request, queryset): 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"))) - def deactivate_selected(self, request, queryset, **kwargs) -> str: - if kwargs: - pass - if request: - pass + @action(description=_("deactivate selected %(verbose_name_plural)s")) + def deactivate_selected(self, request, queryset): queryset.update(is_active=False) - return str(_("%(verbose_name_plural)s deactivated successfully.")) - - 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 + self.message_user(request, _("selected items have been deactivated.")) class AttributeValueInline(TabularInline): @@ -173,7 +150,8 @@ class CategoryChildrenInline(TabularInline): @register(AttributeGroup) class AttributeGroupAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): - model = AttributeGroup + # noinspection PyClassVar + model = AttributeGroup # type: ignore [misc] list_display = ("name", "modified") search_fields = ("uuid", "name") readonly_fields = ("uuid", "modified", "created") @@ -184,7 +162,8 @@ class AttributeGroupAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): @register(Attribute) class AttributeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): - model = Attribute + # noinspection PyClassVar + model = Attribute # type: ignore [misc] list_display = ("name", "group", "value_type", "modified") list_filter = ("value_type", "group", "is_active") search_fields = ("uuid", "name", "group__name") @@ -197,7 +176,8 @@ class AttributeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): @register(AttributeValue) class AttributeValueAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): - model = AttributeValue + # noinspection PyClassVar + model = AttributeValue # type: ignore [misc] list_display = ("attribute", "value", "modified") list_filter = ("attribute__group", "is_active") search_fields = ("uuid", "value", "attribute__name") @@ -210,6 +190,7 @@ class AttributeValueAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): @register(Category) class CategoryAdmin(FieldsetsMixin, ActivationActionsMixin, DraggableMPTTAdmin): + # noinspection PyClassVar model = Category list_display = ("indented_title", "parent", "is_active", "modified") # noinspection PyUnresolvedReferences @@ -225,7 +206,8 @@ class CategoryAdmin(FieldsetsMixin, ActivationActionsMixin, DraggableMPTTAdmin): @register(Brand) class BrandAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): - model = Brand + # noinspection PyClassVar + model = Brand # type: ignore [misc] list_display = ("name",) list_filter = ("categories", "is_active") search_fields = ("uuid", "name", "categories__name") @@ -237,7 +219,8 @@ class BrandAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): @register(Product) class ProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): - model = Product + # noinspection PyClassVar + model = Product # type: ignore [misc] list_display = ( "name", "partnumber", @@ -276,7 +259,8 @@ class ProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): @register(ProductTag) class ProductTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): - model = ProductTag + # noinspection PyClassVar + model = ProductTag # type: ignore [misc] list_display = ("tag_name",) search_fields = ("tag_name",) readonly_fields = ("uuid", "modified", "created") @@ -287,7 +271,8 @@ class ProductTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): @register(CategoryTag) class CategoryTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): - model = CategoryTag + # noinspection PyClassVar + model = CategoryTag # type: ignore [misc] list_display = ("tag_name",) search_fields = ("tag_name",) readonly_fields = ("uuid", "modified", "created") @@ -298,7 +283,8 @@ class CategoryTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): @register(Vendor) class VendorAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): - model = Vendor + # noinspection PyClassVar + model = Vendor # type: ignore [misc] list_display = ("name", "markup_percent", "modified") list_filter = ("markup_percent", "is_active") search_fields = ("name",) @@ -311,7 +297,8 @@ class VendorAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): @register(Feedback) class FeedbackAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): - model = Feedback + # noinspection PyClassVar + model = Feedback # type: ignore [misc] list_display = ("order_product", "rating", "comment", "modified") list_filter = ("rating", "is_active") search_fields = ("order_product__product__name", "comment") @@ -323,7 +310,8 @@ class FeedbackAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): @register(Order) class OrderAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): - model = Order + # noinspection PyClassVar + model = Order # type: ignore [misc] list_display = ( "human_readable_id", "user", @@ -351,7 +339,8 @@ class OrderAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): @register(OrderProduct) class OrderProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): - model = OrderProduct + # noinspection PyClassVar + model = OrderProduct # type: ignore [misc] list_display = ("order", "product", "quantity", "buy_price", "status", "modified") list_filter = ("status",) search_fields = ("order__user__email", "product__name") @@ -364,7 +353,8 @@ class OrderProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): @register(PromoCode) class PromoCodeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): - model = PromoCode + # noinspection PyClassVar + model = PromoCode # type: ignore [misc] list_display = ( "code", "discount_percent", @@ -392,7 +382,8 @@ class PromoCodeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): @register(Promotion) class PromotionAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): - model = Promotion + # noinspection PyClassVar + model = Promotion # type: ignore [misc] list_display = ("name", "discount_percent", "modified") search_fields = ("name",) readonly_fields = ("uuid", "modified", "created") @@ -404,7 +395,8 @@ class PromotionAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): @register(Stock) class StockAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): - model = Stock + # noinspection PyClassVar + model = Stock # type: ignore [misc] list_display = ("product", "vendor", "sku", "quantity", "price", "modified") list_filter = ("vendor", "quantity") search_fields = ("product__name", "vendor__name", "sku") @@ -424,7 +416,8 @@ class StockAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): @register(Wishlist) class WishlistAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): - model = Wishlist + # noinspection PyClassVar + model = Wishlist # type: ignore [misc] list_display = ("user", "modified") search_fields = ("user__email",) readonly_fields = ("uuid", "modified", "created") @@ -435,7 +428,8 @@ class WishlistAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): @register(ProductImage) class ProductImageAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): - model = ProductImage + # noinspection PyClassVar + model = ProductImage # type: ignore [misc] list_display = ("alt", "product", "priority", "modified") list_filter = ("priority",) search_fields = ("alt", "product__name") @@ -448,7 +442,8 @@ class ProductImageAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): @register(Address) class AddressAdmin(FieldsetsMixin, GISModelAdmin): - model = Address + # noinspection PyClassVar + model = Address # type: ignore [misc] list_display = ("street", "city", "region", "country", "user") list_filter = ("country", "region") search_fields = ("street", "city", "postal_code", "user__email") @@ -511,7 +506,7 @@ class ConstanceConfig: # noinspection PyTypeChecker site.unregister([Config]) # noinspection PyTypeChecker -site.register([ConstanceConfig], BaseConstanceAdmin) -site.site_title = CONSTANCE_CONFIG["PROJECT_NAME"][0] +site.register([ConstanceConfig], BaseConstanceAdmin) # type: ignore [list-item] +site.site_title = CONSTANCE_CONFIG["PROJECT_NAME"][0] # type: ignore [assignment] site.site_header = "eVibes" -site.index_title = CONSTANCE_CONFIG["PROJECT_NAME"][0] +site.index_title = CONSTANCE_CONFIG["PROJECT_NAME"][0] # type: ignore [assignment] diff --git a/core/models.py b/core/models.py index f1d326b6..f6b1f2df 100644 --- a/core/models.py +++ b/core/models.py @@ -860,7 +860,7 @@ class ProductImage(ExportModelOperationsMixin("product_image"), NiceModel): # t image (ImageField): The image file associated with the product. priority (int): The display priority of the image. Images with lower 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 @@ -1278,11 +1278,11 @@ class PromoCode(ExportModelOperationsMixin("promocode"), NiceModel): # type: ig promo_amount = order.total_price 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.save() 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.save() 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") 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( 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 + 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: case "balance": if self.user.payments_balance.amount < amount: @@ -1786,7 +1792,10 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel): # t ) 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: verbose_name = _("order product") @@ -1827,16 +1836,18 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel): # t @property 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 def download_url(self: Self) -> str: - if self.product: - if self.product.is_digital and self.product.stocks.first().digital_asset: + if self.product and self.product.stocks: + if self.product.is_digital and self.product.stocks.first().digital_asset: # type: ignore [union-attr] return self.download.url return "" 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"]: raise ValueError(_(f"wrong action specified for feedback: {action}")) 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 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 - class utilizes database fields to effectively model and manage feedback data. + class uses database fields to effectively model and manage feedback data. Attributes: 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: - 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: verbose_name = _("feedback") diff --git a/core/serializers/simple.py b/core/serializers/simple.py index bba6acf5..072f3792 100644 --- a/core/serializers/simple.py +++ b/core/serializers/simple.py @@ -1,4 +1,5 @@ from contextlib import suppress +from typing import Collection, Any from rest_framework.fields import JSONField, SerializerMethodField from rest_framework.relations import PrimaryKeyRelatedField @@ -26,8 +27,8 @@ from core.serializers.utility import AddressSerializer class AttributeGroupSimpleSerializer(ModelSerializer): - parent = PrimaryKeyRelatedField(read_only=True) - children = PrimaryKeyRelatedField(many=True, read_only=True) + parent = PrimaryKeyRelatedField(read_only=True) # type: ignore [assignment, var-annotated] + children = PrimaryKeyRelatedField(many=True, read_only=True) # type: ignore [assignment, var-annotated] class Meta: model = AttributeGroup @@ -58,7 +59,7 @@ class CategorySimpleSerializer(ModelSerializer): return obj.image.url return None - def get_children(self, obj) -> list: + def get_children(self, obj) -> Collection[Any]: request = self.context.get("request") if request is not None and request.user.has_perm("view_category"): children = obj.children.all() diff --git a/core/serializers/utility.py b/core/serializers/utility.py index 37d14d95..ca4e6df1 100644 --- a/core/serializers/utility.py +++ b/core/serializers/utility.py @@ -89,7 +89,7 @@ class DoFeedbackSerializer(Serializer): class CacheOperatorSerializer(Serializer): key = CharField(required=True) - data = JSONField(required=False) + data = JSONField(required=False) # type: ignore [assignment] timeout = IntegerField(required=False) diff --git a/core/utils/emailing.py b/core/utils/emailing.py index c70441b5..c262bf50 100644 --- a/core/utils/emailing.py +++ b/core/utils/emailing.py @@ -83,6 +83,9 @@ def send_order_finished_email(order_pk: str) -> tuple[bool, str]: if len(ops) <= 0: return + if not order.user: + return + activate(order.user.language) 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", context={ "order_uuid": order.human_readable_id, - "user_first_name": order.user.first_name, + "user_first_name": "" or order.user.first_name, "order_products": ops, "project_name": config.PROJECT_NAME, "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"), "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]): if ops: pass + if not order.user: + return activate(order.user.language) set_email_settings() diff --git a/core/views.py b/core/views.py index 706aa34e..90726a74 100644 --- a/core/views.py +++ b/core/views.py @@ -149,7 +149,7 @@ class SupportedLanguagesView(APIView): """ 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 for flexibility in response formats and access permissions. The endpoint 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. 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 = [ diff --git a/core/viewsets.py b/core/viewsets.py index 66345185..44e68436 100644 --- a/core/viewsets.py +++ b/core/viewsets.py @@ -245,7 +245,7 @@ class CategoryViewSet(EvibesViewSet): Attributes: 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. filter_backends: A list of backends for applying filters to the category data. @@ -315,8 +315,8 @@ class ProductViewSet(EvibesViewSet): Manages operations related to the `Product` model in the system. This class provides a viewset for managing products, including their filtering, serialization, - and operations on specific instances. It extends from `EvibesViewSet` to utilize common - functionality and integrates with Django REST framework for RESTful API operations. + and operations on specific instances. It extends from `EvibesViewSet` to use common + functionality and integrates with the Django REST framework for RESTful API operations. Includes methods for retrieving product details, applying permissions, and accessing related feedback of a product. @@ -383,6 +383,7 @@ class ProductViewSet(EvibesViewSet): if request.user.has_perm("core.view_feedback") else Feedback.objects.filter(order_product__product=product, is_active=True) ) + # noinspection PyTypeChecker return Response(data=FeedbackDetailSerializer(feedbacks, many=True).data) except Product.DoesNotExist: name = "Product" @@ -784,7 +785,7 @@ class PromoCodeViewSet(EvibesViewSet): This class extends the functionality of the EvibesViewSet to provide a 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 related to PromoCodes while enforcing security and filtering. @@ -840,7 +841,7 @@ class StockViewSet(EvibesViewSet): Handles operations related to Stock data in the system. The StockViewSet class is a viewset that provides methods for retrieving, - filtering, and serializing Stock data. It utilizes Django's filter + filtering, and serializing Stock data. It uses Django's filter backends to enable filtering based on specified fields and supports custom serializers for different actions. @@ -875,7 +876,7 @@ class WishlistViewSet(EvibesViewSet): allowing for the retrieval, modification, and customization of products within the wish list. This ViewSet facilitates functionality such as adding, removing, and bulk actions for wishlist products. Permission checks are integrated to - ensure that users can only manage their own wishlists, unless explicit permissions + ensure that users can only manage their own wishlists unless explicit permissions are granted. Attributes @@ -1096,10 +1097,10 @@ class ProductTagViewSet(EvibesViewSet): filterset_fields: Fields available for filtering the queryset. Includes 'tag_name' for filtering by the name of the tag, and 'is_active' for filtering active/inactive tags. - serializer_class: The default serializer class used when no specific + serializer_class: The default serializer class is used when no specific serializer is defined for an action. action_serializer_classes: A dictionary mapping specific actions (e.g., - 'list') to custom serializers. Utilizes ProductTagSimpleSerializer + 'list') to custom serializers. Uses ProductTagSimpleSerializer for the 'list' action. """ diff --git a/evibes/settings/base.py b/evibes/settings/base.py index 06818deb..d8902e16 100644 --- a/evibes/settings/base.py +++ b/evibes/settings/base.py @@ -227,7 +227,7 @@ CURRENCIES: tuple[tuple[str, str], ...] = ( ("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") diff --git a/evibes/settings/emailing.py b/evibes/settings/emailing.py index 8a90b999..82dbcd65 100644 --- a/evibes/settings/emailing.py +++ b/evibes/settings/emailing.py @@ -1,9 +1,9 @@ from evibes.settings import CONSTANCE_CONFIG EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" -EMAIL_HOST = CONSTANCE_CONFIG.get("EMAIL_HOST")[0] -EMAIL_PORT = CONSTANCE_CONFIG.get("EMAIL_PORT")[0] -EMAIL_USE_TLS = CONSTANCE_CONFIG.get("EMAIL_USE_TLS")[0] -EMAIL_USE_SSL = CONSTANCE_CONFIG.get("EMAIL_USE_SSL")[0] -EMAIL_HOST_USER = CONSTANCE_CONFIG.get("EMAIL_HOST_USER")[0] -EMAIL_HOST_PASSWORD = CONSTANCE_CONFIG.get("EMAIL_HOST_PASSWORD")[0] +EMAIL_HOST = CONSTANCE_CONFIG.get("EMAIL_HOST")[0] # type: ignore [index] +EMAIL_PORT = CONSTANCE_CONFIG.get("EMAIL_PORT")[0] # type: ignore [index] +EMAIL_USE_TLS = CONSTANCE_CONFIG.get("EMAIL_USE_TLS")[0] # type: ignore [index] +EMAIL_USE_SSL = CONSTANCE_CONFIG.get("EMAIL_USE_SSL")[0] # type: ignore [index] +EMAIL_HOST_USER = CONSTANCE_CONFIG.get("EMAIL_HOST_USER")[0] # type: ignore [index] +EMAIL_HOST_PASSWORD = CONSTANCE_CONFIG.get("EMAIL_HOST_PASSWORD")[0] # type: ignore [index] diff --git a/payments/viewsets.py b/payments/viewsets.py index 43d2a360..3342cecc 100644 --- a/payments/viewsets.py +++ b/payments/viewsets.py @@ -6,10 +6,10 @@ from payments.serializers import TransactionSerializer 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 - 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 permissions, can access the transactions. diff --git a/vibes_auth/models.py b/vibes_auth/models.py index dfad3b7c..fe7f0dda 100644 --- a/vibes_auth/models.py +++ b/vibes_auth/models.py @@ -20,7 +20,6 @@ from rest_framework_simplejwt.token_blacklist.models import ( OutstandingToken as BaseOutstandingToken, ) -from blog.models import Post from core.abstract import NiceModel from core.models import Order, Wishlist 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 -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. @@ -54,9 +53,9 @@ class User(AbstractUser, NiceModel): attributes: JSONField for custom storage of user-specific additional attributes. 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. - objects: Custom manager for User model providing additional methods for user creation. - payments_balance: Reference to the user's payment balance (related to external model). - user_related_wishlist: Reference to the user's wishlist (related to external model). + objects: Custom manager for the User model providing additional methods for user creation. + 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 the external model). orders: QuerySet representing the user's associated orders. Methods: @@ -96,7 +95,6 @@ class User(AbstractUser, NiceModel): ], ) username: None = None # type: ignore [assignment] - posts: "Post" 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] avatar = ImageField( @@ -207,7 +205,7 @@ class BlacklistedToken(BaseBlacklistedToken): blacklisted tokens. It inherits from a base class provided for this purpose, allowing for the extension of functionality or customization of behavior 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. """ diff --git a/vibes_auth/serializers.py b/vibes_auth/serializers.py index a182cb03..4e6747db 100644 --- a/vibes_auth/serializers.py +++ b/vibes_auth/serializers.py @@ -162,7 +162,10 @@ class TokenObtainPairSerializer(TokenObtainSerializer): logger.debug("Data formed") 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("Returning data") @@ -191,7 +194,7 @@ class TokenRefreshSerializer(Serializer): data["refresh"] = str(refresh) user = User.objects.get(uuid=refresh.payload["user_uuid"]) # noinspection PyTypeChecker - data["user"] = UserSerializer(user).data + data["user"] = UserSerializer(user).data # type: ignore [assignment] return data @@ -224,7 +227,7 @@ class TokenVerifySerializer(Serializer): raise ValidationError(_("user does not exist")) from dne # noinspection PyTypeChecker - attrs["user"] = UserSerializer(user).data + attrs["user"] = UserSerializer(user).data # type: ignore [assignment] return attrs diff --git a/vibes_auth/views.py b/vibes_auth/views.py index 18f8b1c5..b497da1c 100644 --- a/vibes_auth/views.py +++ b/vibes_auth/views.py @@ -47,8 +47,8 @@ class TokenObtainPairView(TokenViewBase): subject to rate limiting depending on the global DEBUG setting. """ - serializer_class = TokenObtainPairSerializer - _serializer_class = TokenObtainPairSerializer + serializer_class = TokenObtainPairSerializer # type: ignore [assignment] + _serializer_class = TokenObtainPairSerializer # type: ignore [assignment] @method_decorator(ratelimit(key="ip", rate="10/h" if not DEBUG else "888/h")) def post(self, request, *args, **kwargs): @@ -82,8 +82,8 @@ class TokenRefreshView(TokenViewBase): whether the application is in DEBUG mode or not. """ - serializer_class = TokenRefreshSerializer - _serializer_class = TokenRefreshSerializer + serializer_class = TokenRefreshSerializer # type: ignore [assignment] + _serializer_class = TokenRefreshSerializer # type: ignore [assignment] @method_decorator(ratelimit(key="ip", rate="10/h" if not DEBUG else "888/h")) def post(self, request, *args, **kwargs): @@ -104,8 +104,8 @@ class TokenVerifyView(TokenViewBase): error response. """ - serializer_class = TokenVerifySerializer - _serializer_class = TokenVerifySerializer + serializer_class = TokenVerifySerializer # type: ignore [assignment] + _serializer_class = TokenVerifySerializer # type: ignore [assignment] def post(self, request, *args, **kwargs): try: