Source code for smarter.apps.infrastructure.views.listview

# pylint: disable=W0613
"""
Views of the React Infrastructure Resource list in the Smarter web console.

The ledger, :class:`~smarter.apps.infrastructure.models.InfrastructureResource`, describes the
platform's cloud infrastructure and what it costs, so it is for superusers only. It is written
by the infrastructure signals' receivers, so the list is read-only.
"""

from http import HTTPStatus

from django.conf import settings
from django.core.handlers.asgi import ASGIRequest
from django.db.models import Count, Q
from django.http import JsonResponse
from django.shortcuts import render

from smarter.lib import json, logging
from smarter.lib.django.http.shortcuts import SmarterHttpResponseForbidden
from smarter.lib.django.shortcuts import reverse
from smarter.lib.django.views import SmarterAuthenticatedNeverCachedWebView
from smarter.lib.django.waffle import SmarterWaffleSwitches, switch_is_active

from ..models import InfrastructureResource
from ..serializers import InfrastructureResourceSerializer

logger = logging.getSmarterLogger(__name__, any_switches=[SmarterWaffleSwitches.INFRASTRUCTURE_LOGGING])

DEFAULT_LIMIT = 1000
"""The most recent resources that the list returns, unless it asks for fewer."""

FORBIDDEN = "Only superusers may see the platform's infrastructure resources."


[docs] def summary() -> dict[str, int]: """Counts of the ledger's resources: in total, active, active and billable, and destroyed.""" active = Q(status=InfrastructureResource.Status.ACTIVE) return InfrastructureResource.objects.aggregate( total=Count("id"), active=Count("id", filter=active), activeBillable=Count("id", filter=active & Q(billable=True)), destroyed=Count("id", filter=Q(status=InfrastructureResource.Status.DESTROYED)), )
[docs] class InfrastructureResourceListView(SmarterAuthenticatedNeverCachedWebView): """Render the Infrastructure Resource list of the Smarter web console.""" template_path = "react/infrastructure-resource-list.html" @property def formatted_class_name(self) -> str: """Returns a formatted string of the class name for logging purposes.""" return self.formatted_text(f"{__name__}.{InfrastructureResourceListView.__name__}[{id(self)}]")
[docs] def get(self, request: ASGIRequest, *args, **kwargs): if not request.user.is_superuser: # type: ignore[union-attr] return SmarterHttpResponseForbidden(request=request, error_message=FORBIDDEN) # pylint: disable=C0415 from ..urls import InfrastructureReverseNames context = { "infrastructure_resource_list": { "root_id": "smarter-infrastructure-resource-list-root", "django_csrf_cookie_name": settings.CSRF_COOKIE_NAME, "django_session_cookie_name": settings.SESSION_COOKIE_NAME, "cookie_domain": settings.SESSION_COOKIE_DOMAIN, "infrastructure_resource_list_api_url": reverse( InfrastructureReverseNames.namespace, InfrastructureReverseNames.listview_api ), "react_debug_mode": switch_is_active(SmarterWaffleSwitches.ENABLE_REACTAPP_DEBUG_MODE), "smarter_request_id": self.generate_smarter_request_id(), } } return render(request, template_name=self.template_path, context=context)
[docs] class InfrastructureResourceListApiView(SmarterAuthenticatedNeverCachedWebView): """ The ledger's most recent resources, newest first, and a summary of all of them. The request's JSON body may have ``limit``, the number of resources, up to :data:`DEFAULT_LIMIT`. **Response**:: { "summary": {"total": 12, "active": 9, "activeBillable": 3, "destroyed": 3}, "objects": [{"id": 12, "provider": "aws", "resourceType": "dns.zone", ...}] } """ @property def formatted_class_name(self) -> str: return self.formatted_text(f"{__name__}.{InfrastructureResourceListApiView.__name__}[{id(self)}]")
[docs] def post(self, request: ASGIRequest, *args, **kwargs) -> JsonResponse: if not request.user.is_superuser: # type: ignore[union-attr] return JsonResponse({"error": FORBIDDEN}, status=HTTPStatus.FORBIDDEN) try: body = json.loads(request.body or b"{}") except json.JSONDecodeError: body = {} limit = body.get("limit") if isinstance(body, dict) else None if not isinstance(limit, int) or not 0 < limit <= DEFAULT_LIMIT: limit = DEFAULT_LIMIT resources = InfrastructureResource.objects.order_by("-created_at", "-id")[:limit] retval = { "summary": summary(), "objects": InfrastructureResourceSerializer(resources, many=True).data, } logger.debug("%s.post() returning %s resources", self.formatted_class_name, len(retval["objects"])) return JsonResponse(retval)
__all__ = ["InfrastructureResourceListApiView", "InfrastructureResourceListView", "summary"]