Source code for duck.backend.django.views

"""
Module containing the default view to handle all Django requests through
redirecting them to the appropriate Duck view to produce a response.
"""
from typing import Callable
from functools import wraps

from django.http.request import HttpRequest as DjangoHttpRequest
from django.http.response import HttpResponse as DjangoHttpResponse

from duck.http.response import BaseResponse
from duck.backend.django.utils import (
    django_to_duck_request,
    duck_to_django_response,
)
from duck.contrib.sync import convert_to_sync_if_needed
from duck.contrib.websockets import WebSocketView, View
from duck.exceptions.all import RouteNotFoundError, ExpectingNoResponse
from duck.routes import RouteRegistry
from duck.meta import Meta
from duck.logging import logger
from duck.shortcuts import (not_found404, to_response,)


[docs] def request_meta_update(func: Callable): """ Decorator to ensure that the Django request.META gets updated with the Duck request.META before processing the function. Why Do This: - Keeps the Django request.META in sync with the Duck request.META. - Ensures consistency in metadata between the two request objects. Args: func (Callable): The function to be wrapped. Returns: Callable: The wrapped function with synchronized request.META. """ @wraps(func) def wrapper(request, *args, **kwargs): try: # Call the wrapped function response = func(request, *args, **kwargs) return response finally: if hasattr(request, 'duck_request'): # Ensure the `duck_request` attribute exists on the request # Synchronize Duck's request.META with Django's request.META for key, value in request.duck_request.META.items(): if key != "HTTP_ORIGIN" and key != "HTTP_HOST" and key != "HTTP_REFERER": # Don't remodify Origin, Referer and Host headers, they may cause CSRF issues. request.META[key] = value return wrapper
[docs] @request_meta_update def duck_django_view( request: DjangoHttpRequest, ) -> DjangoHttpResponse: """ Django default view to handle requests meant for urlpatterns registered within Duck. Args: request (DjangoHttpRequest): The Django HTTP request. Returns: DjangoHttpResponse: The corresponding Django HTTP response. """ return _duck_django_view(request)
[docs] def _duck_django_view( request: DjangoHttpRequest, ) -> DjangoHttpResponse: """ Django default view to handle requests meant for urlpatterns registered within Duck. Args: request (DjangoHttpRequest): The Django HTTP request. Returns: DjangoHttpResponse: The corresponding Django HTTP response. """ # Convert Django request to Duck request duck_request = django_to_duck_request(request) request.duck_request = duck_request # Retrieve route details from Duck app's route registry try: route_info = RouteRegistry.fetch_route_info_by_url(duck_request.path) url = route_info["url"] view_kwargs = route_info["handler_kwargs"] # Call the Duck view with the necessary parameters view_callable = route_info["handler"] if type(view_callable) == type and issubclass(view_callable, WebSocketView): raise TypeError(f"A websocket View cannot be accessed at Django side. Add `{duck_request.path}` to DUCK_EXPLICIT_URLS in settings.py.") if (type(view_callable) == type and issubclass(view_callable, View)): view = view_callable(duck_request, **view_kwargs) if view.strictly_async(): # View needs to be run asynchonously. raise TypeError("View is set to be strictly asynchronous and cannot be accessed at Django side.") else: # Convert the run method to sync if not. run = convert_to_sync_if_needed(view.run) duck_response = run() else: view_callable = convert_to_sync_if_needed(view_callable) duck_response = view_callable(request=duck_request, **view_kwargs) except RouteNotFoundError: duck_response = not_found404(request=duck_request) except Exception as e: if isinstance(e, ExpectingNoResponse): error_msg = f'ExpectingNoResponse exception have no effect on Django side": {str(e)}' else: error_msg = f'Error invoking response view for URL "{duck_request.path}": {str(e)}' logger.log_raw( error_msg, level=logger.ERROR, custom_color=logger.Fore.YELLOW, ) raise e try: duck_response = to_response(duck_response) except TypeError as e: raise TypeError(f"View for URL '{url}' returned object of unallowed type.") from e # Convert Duck Response to DjangoHttpResponse django_response = duck_to_django_response(duck_response) return django_response