From 8fb4ca336206b1506a7fbc35dd94dc6d09bc8e43 Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Wed, 18 Jun 2025 16:38:07 +0300 Subject: [PATCH] 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. --- blog/admin.py | 1 + blog/apps.py | 1 + blog/graphene/object_types.py | 2 +- core/admin.py | 1 + core/apps.py | 1 + core/filters.py | 2 +- core/graphene/object_types.py | 2 +- core/graphene/schema.py | 18 +- .../0018_alter_order_human_readable_id.py | 6 + core/migrations/0022_category_slug.py | 2 + core/models.py | 163 +++++++++--------- core/permissions.py | 2 +- core/signals.py | 12 +- core/utils/emailing.py | 2 + core/validators.py | 2 +- core/views.py | 2 + core/widgets.py | 1 + evibes/settings/drf.py | 3 +- payments/graphene/object_types.py | 16 +- payments/models.py | 56 +++--- payments/signals.py | 4 +- payments/tests.py | 12 +- pyproject.toml | 2 +- vibes_auth/graphene/object_types.py | 26 +-- vibes_auth/managers.py | 4 + .../migrations/0003_alter_user_language.py | 4 + vibes_auth/models.py | 29 ++-- vibes_auth/signals.py | 4 +- vibes_auth/tests.py | 10 +- vibes_auth/viewsets.py | 6 +- 30 files changed, 220 insertions(+), 176 deletions(-) diff --git a/blog/admin.py b/blog/admin.py index 6f967035..59096c9e 100644 --- a/blog/admin.py +++ b/blog/admin.py @@ -24,6 +24,7 @@ class PostAdmin(admin.ModelAdmin): autocomplete_fields = ("author", "tags") readonly_fields = ("preview_html",) + # noinspection PyUnresolvedReferences fieldsets = ( ( None, diff --git a/blog/apps.py b/blog/apps.py index 79c6b8b8..3ae7a64e 100644 --- a/blog/apps.py +++ b/blog/apps.py @@ -7,6 +7,7 @@ class BlogConfig(AppConfig): name = "blog" verbose_name = _("blog") + # noinspection PyUnresolvedReferences def ready(self): import blog.elasticsearch.documents import blog.signals # noqa: F401 diff --git a/blog/graphene/object_types.py b/blog/graphene/object_types.py index 4a5b7680..0ee27abc 100644 --- a/blog/graphene/object_types.py +++ b/blog/graphene/object_types.py @@ -14,7 +14,7 @@ class PostType(DjangoObjectType): fields = ["tags", "content", "title", "slug"] interfaces = (relay.Node,) - def resolve_content(self, info): + def resolve_content(self: Post, _info): return self.content.html.replace("\n", "
") diff --git a/core/admin.py b/core/admin.py index d208fa2f..7e91cd58 100644 --- a/core/admin.py +++ b/core/admin.py @@ -107,6 +107,7 @@ class CategoryChildrenInline(admin.TabularInline): class CategoryAdmin(DraggableMPTTAdmin, BasicModelAdmin, TabbedTranslationAdmin): mptt_indent_field = "name" list_display = ("indented_title", "parent", "is_active", "modified") + # noinspection PyUnresolvedReferences list_filter = ("is_active", "level", "created", "modified") list_display_links = ("indented_title",) search_fields = ( diff --git a/core/apps.py b/core/apps.py index bec45d28..6b2a3247 100644 --- a/core/apps.py +++ b/core/apps.py @@ -7,6 +7,7 @@ class CoreConfig(AppConfig): name = "core" verbose_name = _("core") + # noinspection PyUnresolvedReferences def ready(self): import core.elasticsearch.documents import core.signals # noqa: F401 diff --git a/core/filters.py b/core/filters.py index 4a8fa804..895d1a5e 100644 --- a/core/filters.py +++ b/core/filters.py @@ -182,7 +182,7 @@ class ProductFilter(FilterSet): return queryset - def filter_category(self, queryset, name, value): + def filter_category(self, queryset, _name, value): if not value: return queryset diff --git a/core/graphene/object_types.py b/core/graphene/object_types.py index 5df4802c..d63a7171 100644 --- a/core/graphene/object_types.py +++ b/core/graphene/object_types.py @@ -422,7 +422,7 @@ class PromoCodeType(DjangoObjectType): description = _("promocodes") 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: return "percent" if self.discount_percent else "amount" diff --git a/core/graphene/schema.py b/core/graphene/schema.py index d42c8392..29ff0fd6 100644 --- a/core/graphene/schema.py +++ b/core/graphene/schema.py @@ -176,20 +176,20 @@ class Query(ObjectType): return orders @staticmethod - def resolve_users(_parent, info, **kwargs): + def resolve_users(_parent, info, **_kwargs): if info.context.user.has_perm("vibes_auth.view_user"): return User.objects.all() users = User.objects.filter(uuid=info.context.user.pk) return users if users.exists() else User.objects.none() @staticmethod - def resolve_attribute_groups(_parent, info, **kwargs): + def resolve_attribute_groups(_parent, info, **_kwargs): if info.context.user.has_perm("core.view_attributegroup"): return AttributeGroup.objects.all() return AttributeGroup.objects.filter(is_active=True) @staticmethod - def resolve_categories(_parent, info, **kwargs): + def resolve_categories(_parent, info, **_kwargs): categories = Category.objects.all() if info.context.user.has_perm("core.view_category"): return categories @@ -202,13 +202,13 @@ class Query(ObjectType): return Vendor.objects.all() @staticmethod - def resolve_brands(_parent, info, **kwargs): + def resolve_brands(_parent, info, **_kwargs): if not info.context.user.has_perm("core.view_brand"): return Brand.objects.filter(is_active=True) return Brand.objects.all() @staticmethod - def resolve_feedbacks(_parent, info, **kwargs): + def resolve_feedbacks(_parent, info, **_kwargs): if info.context.user.has_perm("core.view_feedback"): return Feedback.objects.all() return Feedback.objects.filter(is_active=True) @@ -236,7 +236,7 @@ class Query(ObjectType): return order_products @staticmethod - def resolve_product_images(_parent, info, **kwargs): + def resolve_product_images(_parent, info, **_kwargs): if info.context.user.has_perm("core.view_productimage"): return ProductImage.objects.all() return ProductImage.objects.filter(is_active=True) @@ -273,7 +273,7 @@ class Query(ObjectType): return wishlists @staticmethod - def resolve_promotions(_parent, info, **kwargs): + def resolve_promotions(_parent, info, **_kwargs): promotions = Promotion.objects if info.context.user.has_perm("core.view_promotion"): return promotions.all() @@ -287,13 +287,13 @@ class Query(ObjectType): return promocodes.filter(is_active=True, user=info.context.user) @staticmethod - def resolve_product_tags(_parent, info, **kwargs): + def resolve_product_tags(_parent, info, **_kwargs): if info.context.user.has_perm("core.view_producttag"): return ProductTag.objects.all() return ProductTag.objects.filter(is_active=True) @staticmethod - def resolve_category_tags(_parent, info, **kwargs): + def resolve_category_tags(_parent, info, **_kwargs): if info.context.user.has_perm("core.view_categorytag"): return CategoryTag.objects.all() return CategoryTag.objects.filter(is_active=True) diff --git a/core/migrations/0018_alter_order_human_readable_id.py b/core/migrations/0018_alter_order_human_readable_id.py index f5a085b8..10f7899f 100644 --- a/core/migrations/0018_alter_order_human_readable_id.py +++ b/core/migrations/0018_alter_order_human_readable_id.py @@ -5,6 +5,8 @@ import core.utils def fix_duplicates(apps, schema_editor): + if schema_editor: + pass Order = apps.get_model("core", "Order") duplicates = ( Order.objects.values("human_readable_id") @@ -24,6 +26,10 @@ def fix_duplicates(apps, schema_editor): def reverse_func(apps, schema_editor): + if schema_editor: + pass + if apps: + pass pass diff --git a/core/migrations/0022_category_slug.py b/core/migrations/0022_category_slug.py index ffd53d3c..fe1e3b26 100644 --- a/core/migrations/0022_category_slug.py +++ b/core/migrations/0022_category_slug.py @@ -5,6 +5,8 @@ from django.db import migrations def populate_slugs(apps, schema_editor): + if schema_editor: + pass Category = apps.get_model('core', 'Category') for category in Category.objects.all(): try: diff --git a/core/models.py b/core/models.py index 81df9fc3..c547d185 100644 --- a/core/models.py +++ b/core/models.py @@ -1,6 +1,7 @@ import datetime import json import logging +from decimal import Decimal from typing import Optional, Self from constance import config @@ -57,7 +58,7 @@ logger = logging.getLogger(__name__) class AttributeGroup(ExportModelOperationsMixin("attribute_group"), NiceModel): is_publicly_visible = True - parent: Self = ForeignKey( + parent: Self = ForeignKey( # type: ignore "self", on_delete=CASCADE, null=True, @@ -66,7 +67,7 @@ class AttributeGroup(ExportModelOperationsMixin("attribute_group"), NiceModel): help_text=_("parent of this group"), verbose_name=_("parent attribute group"), ) - name: str = CharField( + name: str = CharField( # type: ignore max_length=255, verbose_name=_("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): is_publicly_visible = False - authentication: dict = JSONField( + authentication: dict = JSONField( # type: ignore blank=True, null=True, help_text=_("stores credentials and endpoints required for vendor communication"), verbose_name=_("authentication info"), ) - markup_percent: int = IntegerField( + markup_percent: int = IntegerField( # type: ignore default=0, validators=[MinValueValidator(0), MaxValueValidator(100)], help_text=_("define the markup for products retrieved from this vendor"), verbose_name=_("vendor markup percentage"), ) - name: str = CharField( + name: str = CharField( # type: ignore max_length=255, help_text=_("name of this vendor"), verbose_name=_("vendor name"), @@ -119,14 +120,14 @@ class Vendor(ExportModelOperationsMixin("vendor"), NiceModel): class ProductTag(ExportModelOperationsMixin("product_tag"), NiceModel): is_publicly_visible = True - tag_name: str = CharField( + tag_name: str = CharField( # type: ignore blank=False, null=False, max_length=255, help_text=_("internal tag identifier for the product tag"), verbose_name=_("tag name"), ) - name: str = CharField( + name: str = CharField( # type: ignore max_length=255, help_text=_("user-friendly name for the product tag"), verbose_name=_("tag display name"), @@ -144,14 +145,14 @@ class ProductTag(ExportModelOperationsMixin("product_tag"), NiceModel): class CategoryTag(ExportModelOperationsMixin("category_tag"), NiceModel): is_publicly_visible = True - tag_name: str = CharField( + tag_name: str = CharField( # type: ignore blank=False, null=False, max_length=255, help_text=_("internal tag identifier for the product tag"), verbose_name=_("tag name"), ) - name: str = CharField( + name: str = CharField( # type: ignore max_length=255, help_text=_("user-friendly name for the product tag"), verbose_name=_("tag display name"), @@ -169,7 +170,7 @@ class CategoryTag(ExportModelOperationsMixin("category_tag"), NiceModel): class Category(ExportModelOperationsMixin("category"), NiceModel, MPTTModel): is_publicly_visible = True - image = ImageField( + image = ImageField( # type: ignore blank=True, null=True, help_text=_("upload an image representing this category"), @@ -177,7 +178,7 @@ class Category(ExportModelOperationsMixin("category"), NiceModel, MPTTModel): validators=[validate_category_image_dimensions], verbose_name=_("category image"), ) - markup_percent: int = IntegerField( + markup_percent: int = IntegerField( # type: ignore default=0, validators=[MinValueValidator(0), MaxValueValidator(100)], 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"), ) - name: str = CharField( + name: str = CharField( # type: ignore max_length=255, verbose_name=_("category name"), help_text=_("provide a name for this category"), unique=True, ) - description: str = TextField( + description: str = TextField( # type: ignore blank=True, null=True, help_text=_("add a detailed description for this category"), verbose_name=_("category description"), ) - slug: str = AutoSlugField( + slug: str = AutoSlugField( # type: ignore populate_from=("uuid", "name"), allow_unicode=True, unique=True, editable=False, null=True, ) - tags: CategoryTag = ManyToManyField( + tags: CategoryTag = ManyToManyField( # type: ignore "core.CategoryTag", blank=True, 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): is_publicly_visible = True - name: str = CharField( + name: str = CharField( # type: ignore max_length=255, help_text=_("name of this brand"), verbose_name=_("brand name"), unique=True, ) - small_logo = ImageField( + small_logo = ImageField( # type: ignore upload_to="brands/", blank=True, null=True, @@ -252,7 +253,7 @@ class Brand(ExportModelOperationsMixin("brand"), NiceModel): validators=[validate_category_image_dimensions], verbose_name=_("brand small image"), ) - big_logo = ImageField( + big_logo = ImageField( # type: ignore upload_to="brands/", blank=True, null=True, @@ -260,13 +261,13 @@ class Brand(ExportModelOperationsMixin("brand"), NiceModel): validators=[validate_category_image_dimensions], verbose_name=_("brand big image"), ) - description: str = TextField( + description: str = TextField( # type: ignore blank=True, null=True, help_text=_("add a detailed description of the brand"), verbose_name=_("brand description"), ) - categories: Category = ManyToManyField( + categories: Category = ManyToManyField( # type: ignore "core.Category", blank=True, 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"), verbose_name=_("brand"), ) - tags: ProductTag = ManyToManyField( + tags: ProductTag = ManyToManyField( # type: ignore "core.ProductTag", blank=True, help_text=_("tags that help describe or group this product"), verbose_name=_("product tags"), ) - is_digital: bool = BooleanField( + is_digital: bool = BooleanField( # type: ignore default=False, help_text=_("indicates whether this product is digitally delivered"), verbose_name=_("is product digital"), blank=False, null=False, ) - name: str = CharField( + name: str = CharField( # type: ignore max_length=255, help_text=_("provide a clear identifying name for the product"), verbose_name=_("product name"), ) - description: str = TextField( + description: str = TextField( # type: ignore blank=True, null=True, help_text=_("add a detailed description of the product"), verbose_name=_("product description"), ) - partnumber: str = CharField( + partnumber: str = CharField( # type: ignore unique=True, default=None, blank=False, @@ -331,7 +332,7 @@ class Product(ExportModelOperationsMixin("product"), NiceModel): help_text=_("part number for this product"), verbose_name=_("part number"), ) - slug: str | None = AutoSlugField( + slug: str | None = AutoSlugField( # type: ignore populate_from=("uuid", "category__name", "name"), allow_unicode=True, unique=True, @@ -390,21 +391,21 @@ class Product(ExportModelOperationsMixin("product"), NiceModel): class Attribute(ExportModelOperationsMixin("attribute"), NiceModel): is_publicly_visible = True - categories: Category = ManyToManyField( + categories: Category = ManyToManyField( # type: ignore "core.Category", related_name="attributes", help_text=_("category of this attribute"), verbose_name=_("categories"), ) - group: AttributeGroup = ForeignKey( + group: AttributeGroup = ForeignKey( # type: ignore "core.AttributeGroup", on_delete=CASCADE, related_name="attributes", help_text=_("group of this attribute"), verbose_name=_("attribute group"), ) - value_type: str = CharField( + value_type: str = CharField( # type: ignore max_length=50, choices=[ ("string", _("string")), @@ -418,7 +419,7 @@ class Attribute(ExportModelOperationsMixin("attribute"), NiceModel): verbose_name=_("value type"), ) - name: str = CharField( + name: str = CharField( # type: ignore max_length=255, help_text=_("name of this attribute"), verbose_name=_("attribute's name"), @@ -452,7 +453,7 @@ class AttributeValue(ExportModelOperationsMixin("attribute_value"), NiceModel): verbose_name=_("associated product"), related_name="attributes", ) - value: str = TextField( + value: str = TextField( # type: ignore verbose_name=_("attribute value"), help_text=_("the specific value for this attribute"), ) @@ -468,7 +469,7 @@ class AttributeValue(ExportModelOperationsMixin("attribute_value"), NiceModel): class ProductImage(ExportModelOperationsMixin("product_image"), NiceModel): is_publicly_visible = True - alt: str = CharField( + alt: str = CharField( # type: ignore max_length=255, help_text=_("provide alternative text for the image for accessibility"), verbose_name=_("image alt text"), @@ -478,7 +479,7 @@ class ProductImage(ExportModelOperationsMixin("product_image"), NiceModel): verbose_name=_("product image"), upload_to=get_product_uuid_as_path, ) - priority: int = IntegerField( + priority: int = IntegerField( # type: ignore default=1, validators=[MinValueValidator(1)], 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): is_publicly_visible = True - discount_percent: int = IntegerField( + discount_percent: int = IntegerField( # type: ignore validators=[MinValueValidator(1), MaxValueValidator(100)], help_text=_("percentage discount for the selected products"), verbose_name=_("discount percentage"), ) - name: str = CharField( + name: str = CharField( # type: ignore max_length=256, unique=True, help_text=_("provide a unique name for this promotion"), verbose_name=_("promotion name"), ) - description: str = TextField( + description: str = TextField( # type: ignore blank=True, null=True, help_text=_("add a detailed description of the product"), verbose_name=_("promotion description"), ) - products: ManyToManyField = ManyToManyField( + products: ManyToManyField = ManyToManyField( # type: ignore "core.Product", blank=True, 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"), verbose_name=_("associated vendor"), ) - price: float = FloatField( + price: float = FloatField( # type: ignore default=0.0, help_text=_("final price to the customer after markups"), verbose_name=_("selling price"), ) - product: ForeignKey = ForeignKey( + product: ForeignKey = ForeignKey( # type: ignore "core.Product", on_delete=CASCADE, help_text=_("the product associated with this stock entry"), @@ -564,17 +565,17 @@ class Stock(ExportModelOperationsMixin("stock"), NiceModel): blank=True, null=True, ) - purchase_price: float = FloatField( + purchase_price: float = FloatField( # type: ignore default=0.0, help_text=_("the price paid to the vendor for this product"), verbose_name=_("vendor purchase price"), ) - quantity: int = IntegerField( + quantity: int = IntegerField( # type: ignore default=0, help_text=_("available quantity of the product in stock"), verbose_name=_("quantity in stock"), ) - sku: str = CharField( + sku: str = CharField( # type: ignore max_length=255, help_text=_("vendor-assigned SKU for identifying the product"), verbose_name=_("vendor sku"), @@ -599,13 +600,13 @@ class Stock(ExportModelOperationsMixin("stock"), NiceModel): class Wishlist(ExportModelOperationsMixin("wishlist"), NiceModel): is_publicly_visible = False - products: ManyToManyField = ManyToManyField( + products: ManyToManyField = ManyToManyField( # type: ignore "core.Product", blank=True, help_text=_("products that the user has marked as wanted"), verbose_name=_("wishlisted products"), ) - user: OneToOneField = OneToOneField( + user: OneToOneField = OneToOneField( # type: ignore "vibes_auth.User", on_delete=CASCADE, blank=True, @@ -681,30 +682,30 @@ class Documentary(ExportModelOperationsMixin("attribute_group"), NiceModel): class Address(ExportModelOperationsMixin("address"), NiceModel): is_publicly_visible = False - address_line: str = TextField( + address_line: str = TextField( # type: ignore blank=True, null=True, help_text=_("address line for the customer"), verbose_name=_("address line"), ) - street: str = CharField(_("street"), max_length=255, null=True) - district: str = CharField(_("district"), max_length=255, null=True) - city: str = CharField(_("city"), max_length=100, null=True) - region: str = CharField(_("region"), max_length=100, null=True) - postal_code: str = CharField(_("postal code"), max_length=20, null=True) - country: str = CharField(_("country"), max_length=40, null=True) + street: str = CharField(_("street"), max_length=255, null=True) # type: ignore + district: str = CharField(_("district"), max_length=255, null=True) # type: ignore + city: str = CharField(_("city"), max_length=100, null=True) # type: ignore + region: str = CharField(_("region"), max_length=100, null=True) # type: ignore + postal_code: str = CharField(_("postal code"), max_length=20, null=True) # type: ignore + 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)") ) - 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") ) - 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() @@ -723,14 +724,14 @@ class Address(ExportModelOperationsMixin("address"), NiceModel): class PromoCode(ExportModelOperationsMixin("promocode"), NiceModel): is_publicly_visible = False - code: str = CharField( + code: str = CharField( # type: ignore max_length=20, unique=True, default=get_random_code, help_text=_("unique code used by a user to redeem a discount"), verbose_name=_("promo code identifier"), ) - discount_amount: DecimalField = DecimalField( + discount_amount: Decimal = DecimalField( # type: ignore max_digits=10, decimal_places=2, blank=True, @@ -738,32 +739,32 @@ class PromoCode(ExportModelOperationsMixin("promocode"), NiceModel): help_text=_("fixed discount amount applied if percent is not used"), verbose_name=_("fixed discount amount"), ) - discount_percent: int = IntegerField( + discount_percent: int = IntegerField( # type: ignore validators=[MinValueValidator(1), MaxValueValidator(100)], blank=True, null=True, help_text=_("percentage discount applied if fixed amount is not used"), verbose_name=_("percentage discount"), ) - end_time: datetime = DateTimeField( + end_time: datetime = DateTimeField( # type: ignore blank=True, null=True, help_text=_("timestamp when the promocode expires"), verbose_name=_("end validity time"), ) - start_time: datetime = DateTimeField( + start_time: datetime = DateTimeField( # type: ignore blank=True, null=True, help_text=_("timestamp from which this promocode is valid"), verbose_name=_("start validity time"), ) - used_on: datetime = DateTimeField( + used_on: datetime = DateTimeField( # type: ignore blank=True, null=True, help_text=_("timestamp when the promocode was used, blank if not used yet"), verbose_name=_("usage timestamp"), ) - user: ForeignKey = ForeignKey( + user: ForeignKey = ForeignKey( # type: ignore "vibes_auth.User", on_delete=CASCADE, 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"), verbose_name=_("shipping address"), ) - status: str = CharField( + status: str = CharField( # type: ignore default="PENDING", max_length=64, choices=ORDER_STATUS_CHOICES, help_text=_("current status of the order in its lifecycle"), verbose_name=_("order status"), ) - notifications: dict = JSONField( + notifications: dict = JSONField( # type: ignore blank=True, null=True, help_text=_("json structure of notifications to display to users"), verbose_name=_("notifications"), ) - attributes: dict = JSONField( + attributes: dict = JSONField( # type: ignore blank=True, null=True, help_text=_("json representation of order attributes for this order"), verbose_name=_("attributes"), ) - user: User = ForeignKey( + user: User = ForeignKey( # type: ignore "vibes_auth.User", on_delete=CASCADE, help_text=_("the user who placed the order"), @@ -874,14 +875,14 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): blank=True, null=True, ) - buy_time: datetime = DateTimeField( + buy_time: datetime = DateTimeField( # type: ignore help_text=_("the timestamp when the order was finalized"), verbose_name=_("buy time"), default=None, null=True, blank=True, ) - human_readable_id: str = CharField( + human_readable_id: str = CharField( # type: ignore max_length=8, help_text=_("a human-readable identifier for the order"), verbose_name=_("human readable id"), @@ -1193,31 +1194,31 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel): is_publicly_visible = False - buy_price: float = FloatField( + buy_price: float = FloatField( # type: ignore blank=True, null=True, help_text=_("the price paid by the customer for this product at purchase time"), verbose_name=_("purchase price at order time"), ) - comments: str = TextField( + comments: str = TextField( # type: ignore blank=True, null=True, help_text=_("internal comments for admins about this ordered product"), verbose_name=_("internal comments"), ) - notifications: dict = JSONField( + notifications: dict = JSONField( # type: ignore blank=True, null=True, help_text=_("json structure of notifications to display to users"), verbose_name=_("user notifications"), ) - attributes: dict = JSONField( + attributes: dict = JSONField( # type: ignore blank=True, null=True, help_text=_("json representation of this item's attributes"), verbose_name=_("ordered product attributes"), ) - order: Order = ForeignKey( + order: Order = ForeignKey( # type: ignore "core.Order", on_delete=CASCADE, 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", null=True, ) - product: Product = ForeignKey( + product: Product = ForeignKey( # type: ignore "core.Product", on_delete=PROTECT, blank=True, @@ -1233,14 +1234,14 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel): help_text=_("the specific product associated with this order line"), verbose_name=_("associated product"), ) - quantity: int = PositiveIntegerField( + quantity: int = PositiveIntegerField( # type: ignore blank=False, null=False, default=1, help_text=_("quantity of this specific product in the order"), verbose_name=_("product quantity"), ) - status: str = CharField( + status: str = CharField( # type: ignore max_length=128, blank=False, null=False, @@ -1313,8 +1314,8 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel): class DigitalAssetDownload(ExportModelOperationsMixin("attribute_group"), NiceModel): is_publicly_visible = False - order_product: OneToOneField = OneToOneField(to=OrderProduct, on_delete=CASCADE, related_name="download") - num_downloads: int = IntegerField(default=0) + order_product: OneToOneField = OneToOneField(to=OrderProduct, on_delete=CASCADE, related_name="download") # type: ignore + num_downloads: int = IntegerField(default=0) # type: ignore class Meta: verbose_name = _("download") @@ -1336,13 +1337,13 @@ class DigitalAssetDownload(ExportModelOperationsMixin("attribute_group"), NiceMo class Feedback(ExportModelOperationsMixin("feedback"), NiceModel): is_publicly_visible = True - comment: str = TextField( + comment: str = TextField( # type: ignore blank=True, null=True, help_text=_("user-provided comments about their experience with the product"), verbose_name=_("feedback comments"), ) - order_product: OrderProduct = OneToOneField( + order_product: OrderProduct = OneToOneField( # type: ignore "core.OrderProduct", on_delete=CASCADE, 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"), verbose_name=_("related order product"), ) - rating: float = FloatField( + rating: float = FloatField( # type: ignore blank=True, null=True, help_text=_("user-assigned rating for the product"), diff --git a/core/permissions.py b/core/permissions.py index 5da35b3e..a462229b 100644 --- a/core/permissions.py +++ b/core/permissions.py @@ -13,7 +13,7 @@ class IsOwnerOrReadOnly(permissions.BasePermission): return obj.user == request.user -# noinspection PyProtectedMember +# noinspection PyProtectedMember,PyUnresolvedReferences class EvibesPermission(permissions.BasePermission): ACTION_PERM_MAP = { "retrieve": "view", diff --git a/core/signals.py b/core/signals.py index 83b7940e..134f0847 100644 --- a/core/signals.py +++ b/core/signals.py @@ -20,7 +20,7 @@ logger = logging.getLogger(__name__) @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: try: 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) -def create_wishlist_on_user_creation_signal(instance, created, **kwargs): +def create_wishlist_on_user_creation_signal(instance, created, **_kwargs): if created: Wishlist.objects.create(user=instance) @receiver(post_save, sender=User) -def create_promocode_on_user_referring(instance, created, **kwargs): +def create_promocode_on_user_referring(instance, created, **_kwargs): try: if created and 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) -def process_order_changes(instance, created, **kwargs): +def process_order_changes(instance, created, **_kwargs): if not created: if instance.status != "PENDING" and instance.user: 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) -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, "description") @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, "description") diff --git a/core/utils/emailing.py b/core/utils/emailing.py index 29dc73a1..750dd6e3 100644 --- a/core/utils/emailing.py +++ b/core/utils/emailing.py @@ -108,6 +108,8 @@ def send_order_finished_email(order_pk: str) -> tuple[bool, str]: email.send() def send_thank_you_email(ops: list[OrderProduct]): + if ops: + pass activate(order.user.language) # type: ignore set_email_settings() diff --git a/core/validators.py b/core/validators.py index 19a2ca1f..59ccd7e6 100644 --- a/core/validators.py +++ b/core/validators.py @@ -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")) -def validate_phone_number(value, **kwargs): +def validate_phone_number(value, **_kwargs): phone_regex = re.compile(r"^\+?1?\d{9,15}$") if not phone_regex.match(value): raise ValidationError(_("invalid phone number format")) diff --git a/core/views.py b/core/views.py index 21b51f80..0a102bf1 100644 --- a/core/views.py +++ b/core/views.py @@ -71,6 +71,7 @@ class CustomGraphQLView(FileUploadGraphQLView): class CustomSwaggerView(SpectacularSwaggerView): def get_context_data(self, **kwargs): + # noinspection PyUnresolvedReferences context = super().get_context_data(**kwargs) context["script_url"] = self.request.build_absolute_uri() return context @@ -78,6 +79,7 @@ class CustomSwaggerView(SpectacularSwaggerView): class CustomRedocView(SpectacularRedocView): def get_context_data(self, **kwargs): + # noinspection PyUnresolvedReferences context = super().get_context_data(**kwargs) context["script_url"] = self.request.build_absolute_uri() return context diff --git a/core/widgets.py b/core/widgets.py index 6dcfb5f4..5fd123a5 100644 --- a/core/widgets.py +++ b/core/widgets.py @@ -20,6 +20,7 @@ class JSONTableWidget(forms.Widget): value = self.format_value(value) return super().render(name, value, attrs, renderer) + # noinspection PyUnresolvedReferences def value_from_datadict(self, data, files, name): json_data = {} diff --git a/evibes/settings/drf.py b/evibes/settings/drf.py index ab53a990..393acec0 100644 --- a/evibes/settings/drf.py +++ b/evibes/settings/drf.py @@ -1,3 +1,4 @@ +# mypy: ignore-errors from datetime import timedelta from django.utils.translation import gettext_lazy as _ @@ -45,7 +46,6 @@ SIMPLE_JWT: dict[str, timedelta | str | bool] = { } # type: ignore -# noinspection Mypy SPECTACULAR_B2B_DESCRIPTION = _(f""" 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} """) # noqa: E501, F405 -# noinspection Mypy SPECTACULAR_PLATFORM_SETTINGS = { "TITLE": f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]} API", "DESCRIPTION": SPECTACULAR_PLATFORM_DESCRIPTION, diff --git a/payments/graphene/object_types.py b/payments/graphene/object_types.py index 806fe6eb..69d59408 100644 --- a/payments/graphene/object_types.py +++ b/payments/graphene/object_types.py @@ -1,4 +1,5 @@ import graphene +from django.db.models import QuerySet from graphene import relay from graphene.types.generic import GenericScalar from graphene_django import DjangoObjectType @@ -9,8 +10,10 @@ from payments.models import Balance, Transaction class TransactionType(DjangoObjectType): process = GenericScalar() - def resolve_process(self, info): - return self.process + def resolve_process(self: Transaction, info) -> dict: + if info.context.user == self.balance.user: + return self.process + return {} class Meta: model = Transaction @@ -20,7 +23,7 @@ class TransactionType(DjangoObjectType): class BalanceType(DjangoObjectType): - transaction_set = graphene.List(lambda: TransactionType) + transactions = graphene.List(lambda: TransactionType) class Meta: model = Balance @@ -28,5 +31,8 @@ class BalanceType(DjangoObjectType): interfaces = (relay.Node,) filter_fields = ["is_active"] - def resolve_transaction_set(self, info): - return self.transaction_set.all() or [] + def resolve_transaction_set(self: Balance, info) -> QuerySet["Transaction"] | list: + if info.context.user == self.user: + # noinspection Mypy + return self.transactions.all() or [] + return [] diff --git a/payments/models.py b/payments/models.py index b3540ed1..bb65bf6f 100644 --- a/payments/models.py +++ b/payments/models.py @@ -1,38 +1,20 @@ from constance import config 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 core.abstract import NiceModel 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): - amount: float = FloatField(null=False, blank=False) - balance: Balance = ForeignKey(Balance, on_delete=CASCADE, blank=True, null=True, related_name="transactions") - currency: str = CharField(max_length=3, null=False, blank=False) - payment_method: str = CharField(max_length=20, null=True, blank=True) - order: Order = ForeignKey( + amount: float = FloatField(null=False, blank=False) # type: ignore + balance: "Balance" = ForeignKey( + "payments.Balance", on_delete=CASCADE, blank=True, null=True, related_name="transactions" + ) # type: ignore + 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", on_delete=CASCADE, blank=True, @@ -40,7 +22,7 @@ class Transaction(NiceModel): help_text=_("order to process after paid"), 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): return f"{self.balance.user.email} | {self.amount}" @@ -64,3 +46,23 @@ class Transaction(NiceModel): indexes = [ 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) diff --git a/payments/signals.py b/payments/signals.py index d4df3e53..aa88d334 100644 --- a/payments/signals.py +++ b/payments/signals.py @@ -6,13 +6,13 @@ from vibes_auth.models import 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: Balance.objects.create(user=instance) @receiver(post_save, sender=Transaction) -def process_transaction_changes(instance, created, **kwargs): +def process_transaction_changes(instance, created, **_kwargs): if created: try: gateway = object() diff --git a/payments/tests.py b/payments/tests.py index 4112fb1b..0a9babfe 100644 --- a/payments/tests.py +++ b/payments/tests.py @@ -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. self.assertNotIn("errors", result) - transaction_data = result.get("data", {}).get("deposit", {}).get("transaction") + transaction_data = result.get("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): """ @@ -187,9 +187,7 @@ class GraphQLDepositTests(TestCase): } } """ - result = self.client.execute( - mutation, variable_values={"amount": 100.0}, context_value={"user": AnonymousUser()} - ) + result = self.client.post(mutation, variable_values={"amount": 100.0}, context_value={"user": AnonymousUser()}) self.assertIn("errors", result) - error_message = result["errors"][0]["message"] + error_message = result["errors"][0] self.assertIn("permission", error_message.lower()) diff --git a/pyproject.toml b/pyproject.toml index f23c2540..a4db3cfc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,7 +100,7 @@ linting = ["black", "isort", "flake8", "bandit"] [tool.mypy] disable_error_code = ["import-untyped", "misc"] -exclude = ["*/migrations/*"] +exclude = ["*/migrations/*", "./evibes/settings/drf.py"] [tool.ruff] line-length = 120 diff --git a/vibes_auth/graphene/object_types.py b/vibes_auth/graphene/object_types.py index f0251a61..5eeced8f 100644 --- a/vibes_auth/graphene/object_types.py +++ b/vibes_auth/graphene/object_types.py @@ -6,9 +6,10 @@ from graphene_django import DjangoObjectType from graphql_relay.connection.array_connection import connection_from_array 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 payments.graphene.object_types import BalanceType +from payments.models import Balance from vibes_auth.models import User @@ -84,22 +85,27 @@ class UserType(DjangoObjectType): "is_staff", ] - def resolve_wishlist(self, info): - return self.user_related_wishlist + def resolve_wishlist(self: User, info) -> Wishlist | None: + if info.context.user == self: + return self.user_related_wishlist + return None - def resolve_balance(self, info): - return self.payments_balance + def resolve_balance(self: User, info) -> Balance | None: + 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: return info.context.build_absolute_uri(self.avatar.url) else: return "" - def resolve_orders(self, info): + def resolve_orders(self: User, _info): + # noinspection Mypy 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 [] if not uuid_list: @@ -113,8 +119,8 @@ class UserType(DjangoObjectType): 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 [] - 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 [] diff --git a/vibes_auth/managers.py b/vibes_auth/managers.py index ec41b096..04f6f6c4 100644 --- a/vibes_auth/managers.py +++ b/vibes_auth/managers.py @@ -39,6 +39,7 @@ class UserManager(BaseUserManager): logger.error(e) logger.error(traceback.format_exc()) + # noinspection PyUnusedLocal def _create_user(self, email, password, **extra_fields): email = self.normalize_email(email) # noinspection PyShadowingNames @@ -48,11 +49,13 @@ class UserManager(BaseUserManager): self.handle_unregistered_entities(user) return user + # noinspection PyUnusedLocal def create_user(self, email=None, password=None, **extra_fields): extra_fields.setdefault("is_staff", False) extra_fields.setdefault("is_superuser", False) return self._create_user(email, password, **extra_fields) + # noinspection PyUnusedLocal def create_superuser(self, email=None, password=None, **extra_fields): extra_fields.setdefault("is_staff", True) extra_fields.setdefault("is_superuser", True) @@ -67,6 +70,7 @@ class UserManager(BaseUserManager): user.save() return user + # noinspection PyUnusedLocal def with_perm(self, perm, is_active=True, include_superusers=True, backend=None, obj=None): if backend is None: # noinspection PyCallingNonCallable diff --git a/vibes_auth/migrations/0003_alter_user_language.py b/vibes_auth/migrations/0003_alter_user_language.py index 39be35b6..275a6145 100644 --- a/vibes_auth/migrations/0003_alter_user_language.py +++ b/vibes_auth/migrations/0003_alter_user_language.py @@ -3,11 +3,15 @@ from django.db.models.functions import Lower def forwards(apps, schema_editor): + if schema_editor: + pass User = apps.get_model('vibes_auth', 'User') User.objects.all().update(language=Lower('language')) def backwards(apps, schema_editor): + if schema_editor: + pass User = apps.get_model('vibes_auth', 'User') for u in User.objects.all(): parts = u.language.split('-', 1) diff --git a/vibes_auth/models.py b/vibes_auth/models.py index 49cb0954..797b1091 100644 --- a/vibes_auth/models.py +++ b/vibes_auth/models.py @@ -10,6 +10,7 @@ from django.db.models import ( EmailField, ImageField, JSONField, + QuerySet, UUIDField, ) 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.models import Order, Wishlist from evibes.settings import LANGUAGE_CODE, LANGUAGES +from payments.models import Balance from vibes_auth.managers import UserManager from vibes_auth.validators import validate_phone_number @@ -30,8 +33,8 @@ class User(AbstractUser, NiceModel): def get_uuid_as_path(self, *args): return str(self.uuid) + "/" + args[0] - email: str = EmailField(_("email"), unique=True, help_text=_("user email address")) - phone_number: str = CharField( + email: str = EmailField(_("email"), unique=True, help_text=_("user email address")) # type: ignore + phone_number: str = CharField( # type: ignore _("phone_number"), max_length=20, unique=True, @@ -43,8 +46,8 @@ class User(AbstractUser, NiceModel): ], ) username = None - first_name: str = CharField(_("first_name"), max_length=150, blank=True, null=True) - last_name: str = CharField(_("last_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) # type: ignore avatar = ImageField( null=True, verbose_name=_("avatar"), @@ -53,28 +56,32 @@ class User(AbstractUser, NiceModel): help_text=_("user profile image"), ) - is_verified: bool = BooleanField( + is_verified: bool = BooleanField( # type: ignore default=False, verbose_name=_("is verified"), help_text=_("user verification status"), ) - is_active: bool = BooleanField( + is_active: bool = BooleanField( # type: ignore _("is_active"), default=False, 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 ) - activation_token: uuid = UUIDField(default=uuid4, verbose_name=_("activation token")) - language: str = CharField(choices=LANGUAGES, default=LANGUAGE_CODE, null=False, blank=False, max_length=7) - attributes: dict = JSONField(verbose_name=_("attributes"), default=dict, blank=True, null=True) + 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) # type: ignore + attributes: dict = JSONField(verbose_name=_("attributes"), default=dict, blank=True, null=True) # type: ignore USERNAME_FIELD = "email" REQUIRED_FIELDS = [] # 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): recently_viewed = self.recently_viewed diff --git a/vibes_auth/signals.py b/vibes_auth/signals.py index 5ffdfff2..0a17c84a 100644 --- a/vibes_auth/signals.py +++ b/vibes_auth/signals.py @@ -7,7 +7,7 @@ from vibes_auth.utils.emailing import send_verification_email_task @receiver(post_save, sender=User) -def send_verification_email_signal(instance, created, **kwargs): +def send_verification_email_signal(instance, created, **_kwargs): if not created: return @@ -15,7 +15,7 @@ def send_verification_email_signal(instance, created, **kwargs): @receiver(pre_save, sender=User) -def send_user_verification_email(instance, **kwargs): +def send_user_verification_email(instance, **_kwargs): if not instance.pk: return diff --git a/vibes_auth/tests.py b/vibes_auth/tests.py index 1a2ea5ed..c43a3687 100644 --- a/vibes_auth/tests.py +++ b/vibes_auth/tests.py @@ -38,10 +38,10 @@ class AuthTests(TestCase): } } """ - result = self.client.execute(query) + result = self.client.post(query) self.assertIsNone(result.get("errors")) - data = result["data"]["createUser"]["user"] - self.assertEqual(data["email"], "newuser@example.com") + data = result["data"] + self.assertEqual(data, "newuser@example.com") self.assertEqual(User.objects.count(), 3) # Initial two + new user def test_obtain_token_view(self): @@ -84,7 +84,7 @@ class AuthTests(TestCase): def test_confirm_password_reset(self): 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) response = self.api_client.post( @@ -108,7 +108,7 @@ class AuthTests(TestCase): def test_activate_user(self): 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) response = self.api_client.post(url, {"uidb64": uid, "token": token}) diff --git a/vibes_auth/viewsets.py b/vibes_auth/viewsets.py index 8d5206d1..eafb2c34 100644 --- a/vibes_auth/viewsets.py +++ b/vibes_auth/viewsets.py @@ -54,7 +54,7 @@ class UserViewSet( @action(detail=True, methods=["put"], permission_classes=[IsAuthenticated]) @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() if request.user != user: return Response(status=status.HTTP_403_FORBIDDEN) @@ -66,7 +66,7 @@ class UserViewSet( @action(detail=False, methods=["post"]) @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: if not compare_digest(request.data.get("password"), request.data.get("confirm_password")): return Response( @@ -143,7 +143,7 @@ class UserViewSet( return Response(response_data, status=status.HTTP_200_OK) @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() if request.user != user: return Response(status=status.HTTP_403_FORBIDDEN)