Features: 1) Introduced AddressFilter and AddressType to enable filtering and querying addresses via GraphQL; 2) Added resolve_addresses method with permission check in GraphQL schema; 3) Updated DRF settings to improve API documentation structure.
Fixes: 1) Corrected `apply_addresses` logic to handle address validation more robustly; 2) Fixed incorrect imports and annotations for better type safety; 3) Resolved typos and clarified docstrings for various views and methods. Extra: Adjusted formatting, added `# type: ignore` for stricter type checks, and removed unused `dist/` directory from `.dockerignore`.
This commit is contained in:
parent
661f9f2fe6
commit
09366213b6
10 changed files with 62 additions and 42 deletions
|
|
@ -38,7 +38,6 @@ __pypackages__/
|
||||||
# Packaging and distribution
|
# Packaging and distribution
|
||||||
# ──────────────────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────────────────
|
||||||
build/
|
build/
|
||||||
dist/
|
|
||||||
dist-ssr/
|
dist-ssr/
|
||||||
*.egg
|
*.egg
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from django.utils import timezone
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from graphene import Field, List, ObjectType, Schema
|
from graphene import Field, List, ObjectType, Schema
|
||||||
from graphene_django.filter import DjangoFilterConnectionField
|
from graphene_django.filter import DjangoFilterConnectionField
|
||||||
|
|
@ -14,6 +15,7 @@ from core.filters import (
|
||||||
OrderFilter,
|
OrderFilter,
|
||||||
ProductFilter,
|
ProductFilter,
|
||||||
WishlistFilter,
|
WishlistFilter,
|
||||||
|
AddressFilter,
|
||||||
)
|
)
|
||||||
from core.graphene.mutations import (
|
from core.graphene.mutations import (
|
||||||
AddOrderProduct,
|
AddOrderProduct,
|
||||||
|
|
@ -58,6 +60,7 @@ from core.graphene.object_types import (
|
||||||
StockType,
|
StockType,
|
||||||
VendorType,
|
VendorType,
|
||||||
WishlistType,
|
WishlistType,
|
||||||
|
AddressType,
|
||||||
)
|
)
|
||||||
from core.models import (
|
from core.models import (
|
||||||
AttributeGroup,
|
AttributeGroup,
|
||||||
|
|
@ -75,6 +78,7 @@ from core.models import (
|
||||||
Stock,
|
Stock,
|
||||||
Vendor,
|
Vendor,
|
||||||
Wishlist,
|
Wishlist,
|
||||||
|
Address,
|
||||||
)
|
)
|
||||||
from core.utils import get_project_parameters
|
from core.utils import get_project_parameters
|
||||||
from core.utils.languages import get_flag_by_language
|
from core.utils.languages import get_flag_by_language
|
||||||
|
|
@ -106,6 +110,7 @@ class Query(ObjectType):
|
||||||
products = DjangoFilterConnectionField(ProductType, filterset_class=ProductFilter)
|
products = DjangoFilterConnectionField(ProductType, filterset_class=ProductFilter)
|
||||||
orders = DjangoFilterConnectionField(OrderType, filterset_class=OrderFilter)
|
orders = DjangoFilterConnectionField(OrderType, filterset_class=OrderFilter)
|
||||||
users = DjangoFilterConnectionField(UserType, filterset_class=UserFilter)
|
users = DjangoFilterConnectionField(UserType, filterset_class=UserFilter)
|
||||||
|
addresses = DjangoFilterConnectionField(AddressType, filterset_class=AddressFilter)
|
||||||
attribute_groups = DjangoFilterConnectionField(AttributeGroupType)
|
attribute_groups = DjangoFilterConnectionField(AttributeGroupType)
|
||||||
categories = DjangoFilterConnectionField(CategoryType, filterset_class=CategoryFilter)
|
categories = DjangoFilterConnectionField(CategoryType, filterset_class=CategoryFilter)
|
||||||
vendors = DjangoFilterConnectionField(VendorType)
|
vendors = DjangoFilterConnectionField(VendorType)
|
||||||
|
|
@ -294,8 +299,8 @@ class Query(ObjectType):
|
||||||
is_active=True,
|
is_active=True,
|
||||||
user=info.context.user,
|
user=info.context.user,
|
||||||
used_on__isnull=True,
|
used_on__isnull=True,
|
||||||
start_time__lte=info.context.now,
|
start_time__lte=timezone.now(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_product_tags(_parent, info, **_kwargs):
|
def resolve_product_tags(_parent, info, **_kwargs):
|
||||||
|
|
@ -309,6 +314,12 @@ class Query(ObjectType):
|
||||||
return CategoryTag.objects.all()
|
return CategoryTag.objects.all()
|
||||||
return CategoryTag.objects.filter(is_active=True)
|
return CategoryTag.objects.filter(is_active=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_addresses(_parent, info, **_kwargs):
|
||||||
|
if info.context.user.has_perm("core.view_address"):
|
||||||
|
return Address.objects.all()
|
||||||
|
return Address.objects.filter(is_active=True, user=info.context.user)
|
||||||
|
|
||||||
|
|
||||||
class Mutation(ObjectType):
|
class Mutation(ObjectType):
|
||||||
search = Search.Field()
|
search = Search.Field()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional, Self, Any
|
from typing import Any, Optional, Self
|
||||||
|
|
||||||
from constance import config
|
from constance import config
|
||||||
from django.contrib.gis.db.models import PointField
|
from django.contrib.gis.db.models import PointField
|
||||||
|
|
@ -28,8 +28,8 @@ from django.db.models import (
|
||||||
Max,
|
Max,
|
||||||
OneToOneField,
|
OneToOneField,
|
||||||
PositiveIntegerField,
|
PositiveIntegerField,
|
||||||
TextField,
|
|
||||||
QuerySet,
|
QuerySet,
|
||||||
|
TextField,
|
||||||
)
|
)
|
||||||
from django.db.models.indexes import Index
|
from django.db.models.indexes import Index
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
|
|
@ -1536,13 +1536,12 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): # type: ignore [mi
|
||||||
raise Http404(_("promocode does not exist")) from dne
|
raise Http404(_("promocode does not exist")) from dne
|
||||||
return promocode.use(self)
|
return promocode.use(self)
|
||||||
|
|
||||||
def apply_addresses(self, billing_address_uuid: str | None = None, shipping_address_uuid: str | None = None):
|
def apply_addresses(
|
||||||
|
self, billing_address_uuid: str | None = None, shipping_address_uuid: str | None = None
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
if not any([shipping_address_uuid, billing_address_uuid]):
|
if not any([shipping_address_uuid, billing_address_uuid]) and not self.is_whole_digital:
|
||||||
if self.is_whole_digital:
|
raise ValueError(_("you can only buy physical products with shipping address specified"))
|
||||||
return
|
|
||||||
else:
|
|
||||||
raise ValueError(_("you can only buy physical products with shipping address specified"))
|
|
||||||
|
|
||||||
if billing_address_uuid and not shipping_address_uuid:
|
if billing_address_uuid and not shipping_address_uuid:
|
||||||
shipping_address = Address.objects.get(uuid=billing_address_uuid)
|
shipping_address = Address.objects.get(uuid=billing_address_uuid)
|
||||||
|
|
@ -1553,8 +1552,8 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): # type: ignore [mi
|
||||||
shipping_address = billing_address
|
shipping_address = billing_address
|
||||||
|
|
||||||
else:
|
else:
|
||||||
billing_address = Address.objects.get(uuid=billing_address_uuid)
|
billing_address = Address.objects.get(uuid=billing_address_uuid) # type: ignore [misc]
|
||||||
shipping_address = Address.objects.get(uuid=shipping_address_uuid)
|
shipping_address = Address.objects.get(uuid=shipping_address_uuid) # type: ignore [misc]
|
||||||
|
|
||||||
self.billing_address = billing_address
|
self.billing_address = billing_address
|
||||||
self.shipping_address = shipping_address
|
self.shipping_address = shipping_address
|
||||||
|
|
|
||||||
6
core/vendors/__init__.py
vendored
6
core/vendors/__init__.py
vendored
|
|
@ -301,13 +301,11 @@ class AbstractVendor:
|
||||||
raise ValueError(f"Invalid method {method!r} for products update...")
|
raise ValueError(f"Invalid method {method!r} for products update...")
|
||||||
|
|
||||||
def delete_inactives(self, inactivation_method: str = "deactivate", size: int = 5000):
|
def delete_inactives(self, inactivation_method: str = "deactivate", size: int = 5000):
|
||||||
filter_kwargs: dict[str, Any] = {}
|
|
||||||
# noinspection PyUnreachableCode
|
|
||||||
match inactivation_method:
|
match inactivation_method:
|
||||||
case "deactivate":
|
case "deactivate":
|
||||||
filter_kwargs = {"is_active": False}
|
filter_kwargs: dict[str, Any] = {"is_active": False}
|
||||||
case "description":
|
case "description":
|
||||||
filter_kwargs = {"description__exact": "EVIBES_DELETED_PRODUCT"}
|
filter_kwargs: dict[str, Any] = {"description__exact": "EVIBES_DELETED_PRODUCT"}
|
||||||
case _:
|
case _:
|
||||||
raise ValueError(f"Invalid method {inactivation_method!r} for products cleaner...")
|
raise ValueError(f"Invalid method {inactivation_method!r} for products cleaner...")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -421,7 +421,7 @@ class BuyAsBusinessView(APIView):
|
||||||
|
|
||||||
Methods:
|
Methods:
|
||||||
post(request, *_args, **kwargs):
|
post(request, *_args, **kwargs):
|
||||||
Handles the post request to process a business purchase.
|
Handles the "POST" request to process a business purchase.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ratelimit(key="ip", rate="2/h", block=True)
|
@ratelimit(key="ip", rate="2/h", block=True)
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,15 @@ SIMPLE_JWT: dict[str, timedelta | str | bool] = {
|
||||||
"AUTH_HEADER_NAME": "HTTP_X_EVIBES_AUTH",
|
"AUTH_HEADER_NAME": "HTTP_X_EVIBES_AUTH",
|
||||||
}
|
}
|
||||||
|
|
||||||
# type: ignore
|
SPECTACULAR_B2B_DESCRIPTION = _( # type: ignore [index]
|
||||||
SPECTACULAR_B2B_DESCRIPTION = _(f"""
|
f"""
|
||||||
Welcome to the {CONSTANCE_CONFIG.get("PROJECT_NAME")[0]} B2B API documentation.
|
Welcome to the {
|
||||||
|
CONSTANCE_CONFIG.get("PROJECT_NAME")[0] # type: ignore [index]
|
||||||
|
} B2B API documentation.
|
||||||
|
|
||||||
The {CONSTANCE_CONFIG.get("PROJECT_NAME")[0]} B2B API is designed to provide seamless integration for merchants selling a wide range of electronics. Through this API, partnered merchants can manage products, orders, and inventory with ease, while accessing real-time stock levels.
|
The {
|
||||||
|
CONSTANCE_CONFIG.get("PROJECT_NAME")[0] # type: ignore [index]
|
||||||
|
} B2B API is designed to provide seamless integration for merchants selling a wide range of electronics. Through this API, partnered merchants can manage products, orders, and inventory with ease, while accessing real-time stock levels.
|
||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
- **Product Management:** Easily create, update, and manage your product listings with detailed specifications.
|
- **Product Management:** Easily create, update, and manage your product listings with detailed specifications.
|
||||||
|
|
@ -67,14 +71,17 @@ The {CONSTANCE_CONFIG.get("PROJECT_NAME")[0]} B2B API is designed to provide sea
|
||||||
|
|
||||||
## Version
|
## Version
|
||||||
Current API version: {EVIBES_VERSION}
|
Current API version: {EVIBES_VERSION}
|
||||||
""") # noqa: E501, F405
|
"""
|
||||||
|
) # noqa: E501, F405
|
||||||
|
|
||||||
# type: ignore
|
|
||||||
# noinspection Mypy
|
|
||||||
SPECTACULAR_PLATFORM_DESCRIPTION = _(f"""
|
SPECTACULAR_PLATFORM_DESCRIPTION = _(f"""
|
||||||
Welcome to the {CONSTANCE_CONFIG.get("PROJECT_NAME")[0]} Platform API documentation.
|
Welcome to the {
|
||||||
|
CONSTANCE_CONFIG.get("PROJECT_NAME")[0] # type: ignore [index]
|
||||||
|
} Platform API documentation.
|
||||||
|
|
||||||
The {CONSTANCE_CONFIG.get("PROJECT_NAME")[0]} API is the central hub for managing product listings, monitoring orders, and accessing analytics for your electronics store. It provides RESTful endpoints for managing your store’s backend operations and includes both REST and GraphQL options.
|
The {
|
||||||
|
CONSTANCE_CONFIG.get("PROJECT_NAME")[0] # type: ignore [index]
|
||||||
|
} API is the central hub for managing product listings, monitoring orders, and accessing analytics for your electronics store. It provides RESTful endpoints for managing your store’s backend operations and includes both REST and GraphQL options.
|
||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
- **Product Catalog:** Manage product details, pricing, and availability.
|
- **Product Catalog:** Manage product details, pricing, and availability.
|
||||||
|
|
@ -86,8 +93,12 @@ The {CONSTANCE_CONFIG.get("PROJECT_NAME")[0]} API is the central hub for managin
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
- Authentication is handled via JWT tokens. Include the token in the `X-EVIBES-AUTH` header of your requests in the format `Bearer <your_token>`.
|
- Authentication is handled via JWT tokens. Include the token in the `X-EVIBES-AUTH` header of your requests in the format `Bearer <your_token>`.
|
||||||
- Access token lifetime is {SIMPLE_JWT.get("ACCESS_TOKEN_LIFETIME").total_seconds() // 60 if not DEBUG else 3600} {"minutes" if not DEBUG else "hours"}.
|
- Access token lifetime is {
|
||||||
- Refresh token lifetime is {SIMPLE_JWT.get("ACCESS_TOKEN_LIFETIME").total_seconds() // 3600} hours.
|
SIMPLE_JWT.get("ACCESS_TOKEN_LIFETIME").total_seconds() // 60 if not DEBUG else 3600 # type: ignore [union-attr]
|
||||||
|
} {"minutes" if not DEBUG else "hours"}.
|
||||||
|
- Refresh token lifetime is {
|
||||||
|
SIMPLE_JWT.get("ACCESS_TOKEN_LIFETIME").total_seconds() // 3600 # type: ignore [union-attr]
|
||||||
|
} hours.
|
||||||
- Refresh tokens are automatically invalidated after usage.
|
- Refresh tokens are automatically invalidated after usage.
|
||||||
|
|
||||||
## I18N
|
## I18N
|
||||||
|
|
@ -98,7 +109,7 @@ Current API version: {EVIBES_VERSION}
|
||||||
""") # noqa: E501, F405
|
""") # noqa: E501, F405
|
||||||
|
|
||||||
SPECTACULAR_PLATFORM_SETTINGS = {
|
SPECTACULAR_PLATFORM_SETTINGS = {
|
||||||
"TITLE": f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]} API",
|
"TITLE": f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]} API", # type: ignore [index]
|
||||||
"DESCRIPTION": SPECTACULAR_PLATFORM_DESCRIPTION,
|
"DESCRIPTION": SPECTACULAR_PLATFORM_DESCRIPTION,
|
||||||
"VERSION": EVIBES_VERSION, # noqa: F405
|
"VERSION": EVIBES_VERSION, # noqa: F405
|
||||||
"TOS": "https://wiseless.xyz/evibes/terms-of-service",
|
"TOS": "https://wiseless.xyz/evibes/terms-of-service",
|
||||||
|
|
@ -136,7 +147,7 @@ SPECTACULAR_PLATFORM_SETTINGS = {
|
||||||
},
|
},
|
||||||
"SERVERS": [
|
"SERVERS": [
|
||||||
{
|
{
|
||||||
"url": f"https://api.{CONSTANCE_CONFIG.get('BASE_DOMAIN')[0]}/",
|
"url": f"https://api.{CONSTANCE_CONFIG.get('BASE_DOMAIN')[0]}/", # type: ignore [index]
|
||||||
"description": "Production Server",
|
"description": "Production Server",
|
||||||
},
|
},
|
||||||
{"url": "http://api.localhost:8000/", "description": "Development Server"},
|
{"url": "http://api.localhost:8000/", "description": "Development Server"},
|
||||||
|
|
@ -150,7 +161,7 @@ SPECTACULAR_PLATFORM_SETTINGS = {
|
||||||
|
|
||||||
# noinspection Mypy
|
# noinspection Mypy
|
||||||
SPECTACULAR_B2B_SETTINGS = {
|
SPECTACULAR_B2B_SETTINGS = {
|
||||||
"TITLE": f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]} API",
|
"TITLE": f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]} API", # type: ignore [index]
|
||||||
"DESCRIPTION": SPECTACULAR_B2B_DESCRIPTION,
|
"DESCRIPTION": SPECTACULAR_B2B_DESCRIPTION,
|
||||||
"VERSION": EVIBES_VERSION, # noqa: F405
|
"VERSION": EVIBES_VERSION, # noqa: F405
|
||||||
"TOS": "https://wiseless.xyz/evibes/terms-of-service",
|
"TOS": "https://wiseless.xyz/evibes/terms-of-service",
|
||||||
|
|
@ -188,17 +199,17 @@ SPECTACULAR_B2B_SETTINGS = {
|
||||||
},
|
},
|
||||||
"SERVERS": [
|
"SERVERS": [
|
||||||
{
|
{
|
||||||
"url": f"https://b2b.{CONSTANCE_CONFIG.get('BASE_DOMAIN')[0]}/",
|
"url": f"https://b2b.{CONSTANCE_CONFIG.get('BASE_DOMAIN')[0]}/", # type: ignore [index]
|
||||||
"description": "Production Server",
|
"description": "Production Server",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": f"https://beta.b2b.{CONSTANCE_CONFIG.get('BASE_DOMAIN')[0]}/",
|
"url": f"https://beta.b2b.{CONSTANCE_CONFIG.get('BASE_DOMAIN')[0]}/", # type: ignore [index]
|
||||||
"description": "Beta Solutions Server",
|
"description": "Beta Solutions Server",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"CONTACT": {
|
"CONTACT": {
|
||||||
"name": f"{CONSTANCE_CONFIG.get('COMPANY_NAME')[0]}",
|
"name": f"{CONSTANCE_CONFIG.get('COMPANY_NAME')[0]}", # type: ignore [index]
|
||||||
"email": f"{CONSTANCE_CONFIG.get('EMAIL_HOST_USER')[0]}",
|
"email": f"{CONSTANCE_CONFIG.get('EMAIL_HOST_USER')[0]}", # type: ignore [index]
|
||||||
"URL": f"https://www.{CONSTANCE_CONFIG.get('BASE_DOMAIN')[0]}/help",
|
"URL": f"https://www.{CONSTANCE_CONFIG.get('BASE_DOMAIN')[0]}/help", # type: ignore [index]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
from evibes.settings.base import * # noqa: F403
|
from evibes.settings.base import * # noqa: F403
|
||||||
|
|
||||||
GRAPH_MODELS = {
|
GRAPH_MODELS = {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from graphene.types.generic import GenericScalar
|
||||||
from graphene_django import DjangoObjectType
|
from graphene_django import DjangoObjectType
|
||||||
from graphql_relay.connection.array_connection import connection_from_array
|
from graphql_relay.connection.array_connection import connection_from_array
|
||||||
|
|
||||||
from core.graphene.object_types import OrderType, ProductType, WishlistType
|
from core.graphene.object_types import OrderType, ProductType, WishlistType, AddressType
|
||||||
from core.models import Product, Wishlist
|
from core.models import Product, Wishlist
|
||||||
from evibes.settings import LANGUAGE_CODE, LANGUAGES
|
from evibes.settings import LANGUAGE_CODE, LANGUAGES
|
||||||
from payments.graphene.object_types import BalanceType
|
from payments.graphene.object_types import BalanceType
|
||||||
|
|
@ -47,6 +47,7 @@ class UserType(DjangoObjectType):
|
||||||
avatar = String(description=_("avatar"))
|
avatar = String(description=_("avatar"))
|
||||||
attributes = GenericScalar(description=_("attributes may be used to store custom data"))
|
attributes = GenericScalar(description=_("attributes may be used to store custom data"))
|
||||||
language = String(description=_(f"language is one of the {LANGUAGES} with default {LANGUAGE_CODE}"))
|
language = String(description=_(f"language is one of the {LANGUAGES} with default {LANGUAGE_CODE}"))
|
||||||
|
addresses = Field(lambda: AddressType, source="address_set", description=_("address set"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,10 @@ logger = logging.getLogger("django")
|
||||||
@extend_schema_view(**TOKEN_OBTAIN_SCHEMA)
|
@extend_schema_view(**TOKEN_OBTAIN_SCHEMA)
|
||||||
class TokenObtainPairView(TokenViewBase):
|
class TokenObtainPairView(TokenViewBase):
|
||||||
"""
|
"""
|
||||||
Represents a view for obtaining a pair of access and refresh tokens.
|
Represents a view for getting a pair of access and refresh tokens.
|
||||||
|
|
||||||
This view manages the process of handling token-based authentication where
|
This view manages the process of handling token-based authentication where
|
||||||
clients can obtain a pair of JWT tokens (access and refresh) using provided
|
clients can get a pair of JWT tokens (access and refresh) using provided
|
||||||
credentials. It is built on top of a base token view and ensures proper
|
credentials. It is built on top of a base token view and ensures proper
|
||||||
rate limiting to protect against brute force attacks.
|
rate limiting to protect against brute force attacks.
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ class TokenObtainPairView(TokenViewBase):
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
This view should be used in authentication-related APIs where clients
|
This view should be used in authentication-related APIs where clients
|
||||||
need to obtain new sets of tokens. It incorporates both a serializer for
|
need to get new sets of tokens. It incorporates both a serializer for
|
||||||
processing incoming data and also rate limiting to enforce request limits.
|
processing incoming data and also rate limiting to enforce request limits.
|
||||||
|
|
||||||
Methods:
|
Methods:
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ class UserViewSet(
|
||||||
GenericViewSet,
|
GenericViewSet,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
User view set implementation using Django REST framework.
|
User view set implementation.
|
||||||
|
|
||||||
Provides a set of actions that manage user-related data such as creation,
|
Provides a set of actions that manage user-related data such as creation,
|
||||||
retrieval, updates, deletion, and custom actions including password reset,
|
retrieval, updates, deletion, and custom actions including password reset,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue