**Fixes**: 1) Correct handling of non-dict `attributes` for users and orders; 2) Resolve serializer field inconsistencies in `BuyUnregisteredOrderSerializer` and others. Extra: 1) Clean up redundant error handling and unused local variables; 2) Rename and align method arguments for clarity.
557 lines
20 KiB
Python
557 lines
20 KiB
Python
import logging
|
|
import mimetypes
|
|
import os
|
|
import traceback
|
|
|
|
import requests
|
|
from django.contrib.sitemaps.views import index as _sitemap_index_view
|
|
from django.contrib.sitemaps.views import sitemap as _sitemap_detail_view
|
|
from django.core.cache import cache
|
|
from django.core.exceptions import BadRequest
|
|
from django.http import FileResponse, Http404, JsonResponse
|
|
from django.shortcuts import redirect
|
|
from django.utils.http import urlsafe_base64_decode
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.views.decorators.cache import cache_page
|
|
from django.views.decorators.vary import vary_on_headers
|
|
from django_ratelimit.decorators import ratelimit
|
|
from djangorestframework_camel_case.render import CamelCaseJSONRenderer
|
|
from djangorestframework_camel_case.util import camelize
|
|
from drf_spectacular.utils import extend_schema_view
|
|
from drf_spectacular.views import SpectacularRedocView, SpectacularSwaggerView
|
|
from graphene_file_upload.django import FileUploadGraphQLView
|
|
from rest_framework import status
|
|
from rest_framework.permissions import AllowAny
|
|
from rest_framework.renderers import MultiPartRenderer
|
|
from rest_framework.response import Response
|
|
from rest_framework.views import APIView
|
|
from rest_framework_xml.renderers import XMLRenderer
|
|
from rest_framework_yaml.renderers import YAMLRenderer
|
|
from sentry_sdk import capture_exception
|
|
|
|
from core.docs.drf.views import (
|
|
BUY_AS_BUSINESS_SCHEMA,
|
|
CACHE_SCHEMA,
|
|
CONTACT_US_SCHEMA,
|
|
LANGUAGE_SCHEMA,
|
|
PARAMETERS_SCHEMA,
|
|
REQUEST_CURSED_URL_SCHEMA,
|
|
SEARCH_SCHEMA,
|
|
)
|
|
from core.elasticsearch import process_query
|
|
from core.models import DigitalAssetDownload, Order
|
|
from core.serializers import (
|
|
BuyAsBusinessOrderSerializer,
|
|
CacheOperatorSerializer,
|
|
ContactUsSerializer,
|
|
LanguageSerializer,
|
|
)
|
|
from core.utils import get_project_parameters, is_url_safe
|
|
from core.utils.caching import web_cache
|
|
from core.utils.emailing import contact_us_email
|
|
from core.utils.languages import get_flag_by_language
|
|
from evibes import settings
|
|
from evibes.settings import LANGUAGES
|
|
from payments.serializers import TransactionProcessSerializer
|
|
|
|
logger = logging.getLogger("django")
|
|
|
|
|
|
@cache_page(60 * 60 * 12)
|
|
@vary_on_headers("Host")
|
|
def sitemap_index(request, *args, **kwargs):
|
|
"""
|
|
Handles the request for the sitemap index and returns an XML response. It ensures the response includes
|
|
the appropriate content type header for XML.
|
|
|
|
Args:
|
|
request: The HTTP request object.
|
|
*args: Additional positional arguments passed to the view.
|
|
**kwargs: Additional keyword arguments passed to the view.
|
|
|
|
Returns:
|
|
A response object containing the sitemap index in XML format, with the proper content type set as
|
|
"application/xml; charset=utf-8".
|
|
"""
|
|
response = _sitemap_index_view(request, *args, **kwargs)
|
|
response["Content-Type"] = "application/xml; charset=utf-8"
|
|
return response
|
|
|
|
|
|
@cache_page(60 * 60 * 24)
|
|
@vary_on_headers("Host")
|
|
def sitemap_detail(request, *args, **kwargs):
|
|
"""
|
|
Handles the detailed view response for a sitemap. This function processes
|
|
the request, fetches the appropriate sitemap detail response, and sets the
|
|
Content-Type header for XML responses.
|
|
|
|
Args:
|
|
request: An HTTP request object containing request metadata, such as
|
|
headers and HTTP method.
|
|
*args: Additional positional arguments provided dynamically to the
|
|
underlying sitemap detail view function.
|
|
**kwargs: Additional keyword arguments provided dynamically to the
|
|
underlying sitemap detail view function.
|
|
|
|
Returns:
|
|
HttpResponse: A response object with content representing the requested
|
|
sitemap details. The Content-Type header is explicitly set to
|
|
"application/xml; charset=utf-8".
|
|
"""
|
|
response = _sitemap_detail_view(request, *args, **kwargs)
|
|
response["Content-Type"] = "application/xml; charset=utf-8"
|
|
return response
|
|
|
|
|
|
class CustomGraphQLView(FileUploadGraphQLView):
|
|
"""
|
|
A custom GraphQL view class that extends the functionality of FileUploadGraphQLView.
|
|
|
|
This class serves as a customization extension of FileUploadGraphQLView that allows modification
|
|
or enhancement of specific behaviors, particularly the context handling for GraphQL requests.
|
|
|
|
"""
|
|
|
|
def get_context(self, request):
|
|
return request
|
|
|
|
|
|
class CustomSwaggerView(SpectacularSwaggerView):
|
|
"""
|
|
CustomSwaggerView is a subclass of SpectacularSwaggerView.
|
|
|
|
This class overrides the `get_context_data` method to
|
|
add extra context to the response. It modifies the context by
|
|
including the absolute URI of the current request as the `script_url`.
|
|
This can be useful in scenarios where the script or reference
|
|
URL needs to be dynamically generated and included in the context.
|
|
"""
|
|
|
|
def get_context_data(self, **kwargs):
|
|
# noinspection PyUnresolvedReferences
|
|
context = super().get_context_data(**kwargs)
|
|
context["script_url"] = self.request.build_absolute_uri()
|
|
return context
|
|
|
|
|
|
class CustomRedocView(SpectacularRedocView):
|
|
"""
|
|
CustomRedocView provides a customized version of the SpectacularRedocView.
|
|
|
|
This class extends the SpectacularRedocView to include additional
|
|
functionality, such as dynamically setting the `script_url` in the
|
|
context data. It is designed to be used where customized behavior
|
|
for rendering ReDoc UI is required, specifically adapting the script
|
|
URL for the current request environment.
|
|
"""
|
|
|
|
def get_context_data(self, **kwargs):
|
|
# noinspection PyUnresolvedReferences
|
|
context = super().get_context_data(**kwargs)
|
|
context["script_url"] = self.request.build_absolute_uri()
|
|
return context
|
|
|
|
|
|
@extend_schema_view(**LANGUAGE_SCHEMA)
|
|
class SupportedLanguagesView(APIView):
|
|
"""
|
|
Handles retrieving the list of supported languages.
|
|
|
|
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,
|
|
and flags.
|
|
|
|
Attributes:
|
|
serializer_class (Serializer): Serializer used for formatting the response data.
|
|
permission_classes (list): Permissions applied to restrict the endpoint access.
|
|
renderer_classes (list): Renderers available for formatting response output.
|
|
|
|
Methods:
|
|
get(self, request): Retrieves the list of supported languages.
|
|
|
|
"""
|
|
|
|
serializer_class = LanguageSerializer
|
|
permission_classes = [
|
|
AllowAny,
|
|
]
|
|
renderer_classes = [
|
|
CamelCaseJSONRenderer,
|
|
MultiPartRenderer,
|
|
XMLRenderer,
|
|
YAMLRenderer,
|
|
]
|
|
|
|
def get(self, request):
|
|
return Response(
|
|
data=self.serializer_class(
|
|
[
|
|
{
|
|
"code": lang[0],
|
|
"name": lang[1],
|
|
"flag": get_flag_by_language(lang[0]),
|
|
}
|
|
for lang in LANGUAGES
|
|
],
|
|
many=True,
|
|
).data,
|
|
status=status.HTTP_200_OK,
|
|
)
|
|
|
|
|
|
@extend_schema_view(**PARAMETERS_SCHEMA)
|
|
class WebsiteParametersView(APIView):
|
|
"""
|
|
Handles operations related to website parameters.
|
|
|
|
This class is a Django Rest Framework view that allows clients to retrieve
|
|
the parameters of a website. It uses different renderers to present the data
|
|
in various formats. The view is publicly accessible.
|
|
|
|
Attributes
|
|
----------
|
|
serializer_class
|
|
A placeholder for a DRF serializer, it is set to None since no serializer
|
|
is explicitly used in this view.
|
|
permission_classes
|
|
A list indicating the permissions required to access this view. In this case,
|
|
`AllowAny`, meaning the view is open to everyone.
|
|
renderer_classes
|
|
A list of renderers available for this view, supporting CamelCase JSON,
|
|
multipart forms, XML, and YAML formats.
|
|
|
|
Methods
|
|
-------
|
|
get(request)
|
|
Handles HTTP GET requests to fetch website parameters.
|
|
"""
|
|
|
|
serializer_class = None
|
|
permission_classes = [
|
|
AllowAny,
|
|
]
|
|
renderer_classes = [
|
|
CamelCaseJSONRenderer,
|
|
MultiPartRenderer,
|
|
XMLRenderer,
|
|
YAMLRenderer,
|
|
]
|
|
|
|
def get(self, request):
|
|
return Response(data=camelize(get_project_parameters()), status=status.HTTP_200_OK)
|
|
|
|
|
|
@extend_schema_view(**CACHE_SCHEMA)
|
|
class CacheOperatorView(APIView):
|
|
"""
|
|
View for managing cache operations.
|
|
|
|
This class provides an API view for handling cache operations such as setting cache
|
|
data with a specified key and timeout. It leverages multiple renderer classes for
|
|
serializing outputs in various formats.
|
|
|
|
Attributes:
|
|
serializer_class (type): Serializer to validate and deserialize input data.
|
|
permission_classes (list): List of permission classes to apply access
|
|
restrictions.
|
|
renderer_classes (list): List of renderer classes to serialize the output
|
|
in desired formats.
|
|
|
|
Methods:
|
|
post(request, *args, **kwargs): Handles HTTP POST requests to set cache
|
|
data based on the provided key and timeout.
|
|
"""
|
|
|
|
serializer_class = CacheOperatorSerializer
|
|
permission_classes = [
|
|
AllowAny,
|
|
]
|
|
renderer_classes = [
|
|
CamelCaseJSONRenderer,
|
|
MultiPartRenderer,
|
|
XMLRenderer,
|
|
YAMLRenderer,
|
|
]
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
return Response(
|
|
data=web_cache(
|
|
request,
|
|
request.data.get("key"),
|
|
request.data.get("data"),
|
|
request.data.get("timeout"),
|
|
),
|
|
status=status.HTTP_200_OK,
|
|
)
|
|
|
|
|
|
@extend_schema_view(**CONTACT_US_SCHEMA)
|
|
class ContactUsView(APIView):
|
|
"""
|
|
Handles contact us form submissions via a REST API.
|
|
|
|
This view processes user submissions for a "Contact Us" form. It validates the received
|
|
data using a serializer, applies rate limiting for IP-based requests, and sends emails
|
|
asynchronously. The view is prepared to handle multiple response formats using configured
|
|
renderers.
|
|
|
|
Attributes:
|
|
serializer_class: The serializer class used to validate incoming data.
|
|
renderer_classes: A list of renderers to support multiple response formats.
|
|
|
|
Methods:
|
|
post: Handles POST requests to process form submissions.
|
|
|
|
"""
|
|
|
|
serializer_class = ContactUsSerializer
|
|
renderer_classes = [
|
|
CamelCaseJSONRenderer,
|
|
MultiPartRenderer,
|
|
XMLRenderer,
|
|
YAMLRenderer,
|
|
]
|
|
|
|
@ratelimit(key="ip", rate="2/h")
|
|
def post(self, request, *args, **kwargs):
|
|
serializer = self.serializer_class(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
contact_us_email.delay(serializer.validated_data)
|
|
|
|
return Response(data=serializer.data, status=status.HTTP_200_OK)
|
|
|
|
|
|
@extend_schema_view(**REQUEST_CURSED_URL_SCHEMA)
|
|
class RequestCursedURLView(APIView):
|
|
"""
|
|
Handles requests for processing and validating URLs from incoming POST requests.
|
|
|
|
Particularly intended for validating and processing URLs provided by clients. It uses rate-limiting, caching, and
|
|
various response format renderers to optimize performance and ensure safe handling of external data.
|
|
|
|
Attributes:
|
|
permission_classes (list): Specifies the permissions required to access this view.
|
|
renderer_classes (list): Configures the response format renderers available for this view.
|
|
|
|
Methods:
|
|
post: Handles the POST request to validate the URL, fetches its data if valid,
|
|
and returns the processed response.
|
|
"""
|
|
|
|
permission_classes = [
|
|
AllowAny,
|
|
]
|
|
renderer_classes = [
|
|
CamelCaseJSONRenderer,
|
|
MultiPartRenderer,
|
|
XMLRenderer,
|
|
YAMLRenderer,
|
|
]
|
|
|
|
@ratelimit(key="ip", rate="10/h")
|
|
def post(self, request, *args, **kwargs):
|
|
url = request.data.get("url")
|
|
if not is_url_safe(url):
|
|
return Response(
|
|
data={"error": _("only URLs starting with http(s):// are allowed")},
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
)
|
|
try:
|
|
data = cache.get(url, None)
|
|
if not data:
|
|
response = requests.get(url, headers={"content-type": "application/json"})
|
|
response.raise_for_status()
|
|
data = camelize(response.json())
|
|
cache.set(url, data, 86400)
|
|
return Response(
|
|
data=data,
|
|
status=status.HTTP_200_OK,
|
|
)
|
|
except Exception as e:
|
|
return Response(
|
|
data={"error": str(e)},
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
)
|
|
|
|
|
|
@extend_schema_view(**SEARCH_SCHEMA)
|
|
class GlobalSearchView(APIView):
|
|
"""
|
|
Class-based view for handling global search functionality.
|
|
|
|
This class is designed to process search queries from HTTP GET requests. It is
|
|
capable of rendering results in multiple formats including CamelCase JSON,
|
|
MultiPart, XML, and YAML. The class uses a custom schema for API documentation
|
|
and processes search queries passed as parameters in the request.
|
|
|
|
Attributes:
|
|
renderer_classes (list): List of renderer classes used to serialize responses
|
|
into various formats such as CamelCase JSON, MultiPart, XML, and YAML.
|
|
|
|
Methods:
|
|
get: Handles HTTP GET requests by processing the search query and returning
|
|
formatted search results.
|
|
|
|
"""
|
|
|
|
renderer_classes = [
|
|
CamelCaseJSONRenderer,
|
|
MultiPartRenderer,
|
|
XMLRenderer,
|
|
YAMLRenderer,
|
|
]
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
return Response(camelize({"results": process_query(query=request.GET.get("q", "").strip(), request=request)}))
|
|
|
|
|
|
@extend_schema_view(**BUY_AS_BUSINESS_SCHEMA)
|
|
class BuyAsBusinessView(APIView):
|
|
"""
|
|
View for buying as a business.
|
|
|
|
This view handles the logic of creating orders and processing transactions when a
|
|
business makes a purchase. It ensures that the request data is properly validated
|
|
and processed, handles the creation of the order, and starts the transaction process.
|
|
The view also restricts the rate of requests based on the client's IP address
|
|
to prevent abuse.
|
|
|
|
Attributes:
|
|
schema (class): Extended schema for API documentation.
|
|
|
|
Methods:
|
|
post(request, *_args, **kwargs):
|
|
Handles the "POST" request to process a business purchase.
|
|
"""
|
|
|
|
@ratelimit(key="ip", rate="2/h", block=True)
|
|
def post(self, request, *_args, **kwargs):
|
|
serializer = BuyAsBusinessOrderSerializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
order = Order.objects.create(status="MOMENTAL")
|
|
products = [product.get("product_uuid") for product in serializer.validated_data.get("products")]
|
|
transaction = order.buy_without_registration(
|
|
products=products,
|
|
promocode_uuid=serializer.validated_data.get("promocode_uuid"),
|
|
customer_name=serializer.validated_data.get("business_identificator"),
|
|
customer_email=serializer.validated_data.get("business_email"),
|
|
customer_phone_number=serializer.validated_data.get("business_phone_number"),
|
|
billing_customer_address=serializer.validated_data.get("billing_business_address_uuid"),
|
|
shipping_customer_address=serializer.validated_data.get("shipping_business_address_uuid"),
|
|
payment_method=serializer.validated_data.get("payment_method"),
|
|
is_business=True,
|
|
)
|
|
return Response(
|
|
status=status.HTTP_201_CREATED,
|
|
data=TransactionProcessSerializer(transaction).data,
|
|
)
|
|
|
|
|
|
def download_digital_asset_view(request, *args, **kwargs):
|
|
"""
|
|
Handles the downloading of a digital asset associated with an order. Ensures that users
|
|
are permitted to download the asset only once. Validates the request, retrieves the file,
|
|
and serves it as a downloadable response. Returns appropriate error responses for different
|
|
failure scenarios.
|
|
|
|
Args:
|
|
request: The HTTP request object containing information about the client request.
|
|
*args: Additional positional arguments.
|
|
**kwargs: Additional keyword arguments.
|
|
|
|
Raises:
|
|
BadRequest: If the digital asset has already been downloaded.
|
|
DigitalAssetDownload.DoesNotExist: If the requested digital asset cannot be found.
|
|
|
|
Returns:
|
|
A FileResponse containing the digital asset file if the request is valid. Returns
|
|
a JsonResponse with an error message if an error occurs during the process.
|
|
"""
|
|
try:
|
|
logger.debug(f"download_digital_asset_view: {kwargs}")
|
|
uuid = urlsafe_base64_decode(str(kwargs.get("order_product_uuid"))).decode("utf-8")
|
|
|
|
download = DigitalAssetDownload.objects.get(order_product__uuid=uuid)
|
|
|
|
if download.num_downloads >= 1:
|
|
raise BadRequest(_("you can only download the digital asset once"))
|
|
|
|
download.num_downloads += 1
|
|
download.save()
|
|
|
|
file_path = download.order_product.product.stocks.first().digital_asset.path
|
|
|
|
content_type, encoding = mimetypes.guess_type(file_path)
|
|
if not content_type:
|
|
content_type = "application/octet-stream"
|
|
|
|
response = FileResponse(open(file_path, "rb"), content_type=content_type)
|
|
filename = os.path.basename(file_path)
|
|
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
|
return response
|
|
|
|
except BadRequest as e:
|
|
return JsonResponse(camelize({"error": str(e)}), status=400)
|
|
|
|
except DigitalAssetDownload.DoesNotExist:
|
|
return JsonResponse(camelize({"error": "Digital asset not found"}), status=404)
|
|
|
|
except Exception as e:
|
|
capture_exception(e)
|
|
return JsonResponse(
|
|
camelize(
|
|
{
|
|
"error": "An error occurred while trying to download the digital asset",
|
|
"traceback": traceback.format_exc() if settings.DEBUG else None,
|
|
"received": {"order_product_uuid": kwargs.get("order_product_uuid", "")},
|
|
}
|
|
),
|
|
status=500,
|
|
)
|
|
|
|
|
|
def favicon_view(request, *args, **kwargs):
|
|
"""
|
|
Handles requests for the favicon of a website. This function attempts to serve the favicon
|
|
file located in the static directory of the project. If the favicon file is not found,
|
|
an HTTP 404 error is raised to indicate the resource is unavailable.
|
|
|
|
Args:
|
|
request: The HTTP request object.
|
|
*args: Additional positional arguments that are ignored in this function.
|
|
**kwargs: Additional keyword arguments that are ignored in this function.
|
|
|
|
Returns:
|
|
FileResponse: A file response containing the favicon image with the content-type
|
|
"image/x-icon".
|
|
|
|
Raises:
|
|
Http404: Raised if the favicon file is not found.
|
|
"""
|
|
try:
|
|
favicon_path = os.path.join(settings.BASE_DIR, "static/favicon.png")
|
|
return FileResponse(open(favicon_path, "rb"), content_type="image/x-icon")
|
|
except FileNotFoundError as fnfe:
|
|
raise Http404(_("favicon not found")) from fnfe
|
|
|
|
|
|
def index(request, *args, **kwargs):
|
|
"""
|
|
Redirects the request to the admin index page.
|
|
|
|
The function handles incoming HTTP requests and redirects them to the Django
|
|
admin interface index page. It uses Django's `redirect` function for handling
|
|
the HTTP redirection.
|
|
|
|
Args:
|
|
request: The HttpRequest object representing the incoming request.
|
|
*args: Additional positional arguments, if any.
|
|
**kwargs: Additional keyword arguments, if any.
|
|
|
|
Returns:
|
|
HttpResponseRedirect: An HTTP response that redirects to the admin index.
|
|
"""
|
|
return redirect("admin:index")
|