Source code for wildewidgets.views.json
from __future__ import annotations
import importlib
from pathlib import Path
from typing import Any
from django.apps import apps
from django.http import Http404, HttpRequest, HttpResponseBase, JsonResponse
from django.views.generic import View
from django.views.generic.base import TemplateView
from .mixins import JSONResponseMixin, WidgetInitKwargsMixin
[docs]class JSONResponseView(JSONResponseMixin, TemplateView): # type: ignore[misc]
"""
A view that renders a template and returns the result as a JSON response.
This view combines Django's TemplateView with JSONResponseMixin to provide a
simple way to return template-rendered content within a JSON response. This
is useful for AJAX requests that need to return HTML fragments within a JSON
structure.
When subclassing, you typically need to override the template_name attribute
and optionally the get_context_data method.
Attributes:
template_name: The template to render (must be set by subclasses)
Example:
.. code-block:: python
class MyJSONView(JSONResponseView):
template_name = "my_template.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['extra_data'] = get_some_data()
return context
"""
[docs]class JSONDataView(View):
"""
A view that returns JSON data in response to HTTP requests.
This class provides a simple framework for views that need to return JSON data.
It handles the HTTP request/response cycle and serializes the context data
to JSON. By default, it only responds to GET requests.
To use this class, subclass it and override the get_context_data method to
provide the data you want to return as JSON.
Example:
.. code-block:: python
class UserDataView(JSONDataView):
def get_context_data(self, **kwargs):
user_id = self.kwargs.get('user_id')
user = User.objects.get(id=user_id)
return {
'username': user.username,
'email': user.email,
'date_joined': user.date_joined.isoformat()
}
"""
[docs] def get(self, request: HttpRequest, *args, **kwargs) -> JsonResponse: # noqa: ARG002
"""
Handle GET requests by returning JSON data.
This method retrieves the context data and returns it as a JSON response.
Args:
request: The HTTP request object
*args: Additional positional arguments
**kwargs: Additional keyword arguments
Returns:
JsonResponse: HTTP response containing the JSON-serialized context data
"""
context = self.get_context_data()
return self.render_to_response(context)
[docs] def get_context_data(self, **kwargs) -> dict[str, Any]: # noqa: ARG002
"""
Get the data to include in the JSON response.
Override this method to provide the data you want to include in the response.
By default, it returns an empty dictionary.
Args:
**kwargs: Additional context data
Returns:
dict: The data to serialize to JSON
"""
return {}
[docs] def render_to_response(self, context, **response_kwargs) -> JsonResponse: # noqa: ARG002
"""
Create a JSON response from the context data.
This method serializes the context data to JSON and returns it as an
HTTP response.
Args:
context: The data to serialize to JSON
**response_kwargs: Additional response parameters (unused)
Returns:
JsonResponse: HTTP response containing the JSON-serialized context data
"""
return JsonResponse(context)
[docs]class WildewidgetDispatch(WidgetInitKwargsMixin, View):
"""
A view that dynamically dispatches requests to widget classes.
This view acts as a central dispatcher for widget AJAX requests. It examines
the request parameters to determine which widget class to instantiate, then
delegates the request handling to that widget's dispatch method.
The view searches for widget classes in all installed Django apps by looking
for a 'wildewidgets.py' file or a 'wildewidgets' directory within each app.
This approach allows widgets to handle their own AJAX requests without
requiring explicit URL routing for each widget type.
Example usage in URLs:
.. code-block:: python
path(
'wildewidget/',
WildewidgetDispatch.as_view(),
name='wildewidget_dispatch'
)
Example client-side code:
.. code-block:: javascript
$.ajax({
url: '/wildewidget/',
data: {
'wildewidgetclass': 'MyWidget',
'extra_data': encodeURIComponent(JSON.stringify({
args: [],
kwargs: {param1: 'value1'}
}))
}
});
"""
[docs] def dispatch( # type: ignore[override]
self, request: HttpRequest, *args, **kwargs
) -> HttpResponseBase | Http404:
"""
Dispatch the request to the appropriate widget class.
This method:
1. Extracts the widget class name from the request
2. Searches for the widget class in all installed apps
3. Instantiates the widget class with the provided arguments
4. Delegates to the widget's dispatch method
Args:
request: The HTTP request object
*args: Additional positional arguments
Keyword Arguments:
**kwargs: Additional keyword arguments
Returns:
HttpResponseBase: The response from the widget's dispatch method
Http404: If the widget class cannot be found
Note:
The request must include:
- 'wildewidgetclass': The name of the widget class to instantiate
The request may include:
- 'csrf_token': Optional CSRF token for protected requests
- 'extra_data': Optional base64 encoded JSON encoded string
containing a dict with 'args' and 'kwargs' keys for widget
initialization
"""
wildewidgetclass = request.GET.get("wildewidgetclass", None)
csrf_token = request.GET.get("csrf_token", "")
if wildewidgetclass:
configs = apps.get_app_configs()
for config in configs:
check_file = Path(config.path) / "wildewidgets.py"
check_dir = Path(config.path) / "wildewidgets"
if check_file.is_file() or check_dir.is_dir():
module = importlib.import_module(f"{config.name}.wildewidgets")
if hasattr(module, wildewidgetclass):
class_ = getattr(module, wildewidgetclass)
extra_data = self.get_decoded_extra_data(request)
initargs = extra_data.get("args", [])
initkwargs = extra_data.get("kwargs", {})
instance = class_(*initargs, **initkwargs)
instance.request = request
instance.csrf_token = csrf_token
instance.args = initargs
instance.kwargs = initkwargs
return instance.dispatch(request, *args, **kwargs)
msg = "Not Found: Wildewidget class not found"
raise Http404(msg)