"""Deterministic interaction endpoints: no models, storage, or external calls.""" from __future__ import annotations from django.contrib import messages from django.core.paginator import Paginator from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest from django.shortcuts import redirect, render from django.urls import reverse from django.utils.cache import patch_vary_headers from django.utils.safestring import mark_safe from django.utils.translation import gettext as _ from django.views.decorators.http import require_http_methods, require_POST from .forms import FilterForm, LanguageForm, MessageForm, UploadForm LANGUAGES = (("python", "Python"), ("django", "Django"), ("htmx", "HTMX"), ("alpine", "Alpine")) ROWS = tuple({"name": name, "status": status} for name, status in ( ("Modal response", "ready"), ("Combobox endpoint", "draft"), ("Toast delivery", "ready"), ("No-JavaScript floor", "draft"), ("Slide-over focus", "ready"), ("Tooltip focus", "draft"), ("Dropdown links", "ready"), ("Native disclosure", "draft"), ("Tag input carrier", "ready"), ("Dropzone metadata", "draft"), ("Table selection", "ready"), ("Search request", "draft"), )) def _htmx(request: HttpRequest) -> bool: return request.headers.get("HX-Request") == "true" def _branch(request: HttpRequest, partial: str, page: str, context: dict, status: int = 200) -> HttpResponse: response = render(request, partial if _htmx(request) else page, context, status=status) patch_vary_headers(response, ("HX-Request",)) return response def _page(request: HttpRequest, template: str, **context) -> HttpResponse: return render(request, template, context) def index(request): return _page(request, "component_cookbook/index.html") def dropdown(request): return _page(request, "component_cookbook/dropdown.html", items=[ {"label": _("Open cookbook"), "url": reverse("component_cookbook:index"), "icon": "external-link"}, {"divider": True}, {"label": _("Delete draft"), "url": "#delete", "variant": "danger", "icon": "trash"}, ]) def tabs(request): active = request.GET.get("tab", "overview") if active not in {"overview", "api"}: return HttpResponseBadRequest("Unknown cookbook tab.") return _page(request, "component_cookbook/tabs.html", active=active, tabs=[{"key": "overview", "label": _("Overview")}, {"key": "api", "label": "API"}], overview=mark_safe("

Overview content comes from this consumer template.

"), api=mark_safe("

The API panel uses the same package tab relationship.

")) def tooltip(request): return _page(request, "component_cookbook/tooltip.html", tooltip_id="cookbook-tooltip", text=_("Explains the button"), placement="top") def disclosure(request): return _page(request, "component_cookbook/disclosure.html") def tag_input(request): return _page(request, "component_cookbook/tag_input.html") def modal(request): return _branch(request, "component_cookbook/partials/modal.html", "component_cookbook/modal.html", {"form": MessageForm(), "modal_id": "cookbook-message-modal", "close_href": reverse("component_cookbook:index")}) @require_POST def modal_submit(request): form = MessageForm(request.POST) context = {"form": form, "modal_id": "cookbook-message-modal", "close_href": reverse("component_cookbook:index")} if not form.is_valid(): return _branch(request, "component_cookbook/partials/modal.html", "component_cookbook/modal.html", context, 422 if _htmx(request) else 200) if _htmx(request): response = HttpResponse(status=204) response["HX-Trigger"] = '{"bw:modal:close": {"id": "cookbook-message-modal"}}' return response messages.success(request, _("The demo accepted your message without saving it.")) return redirect("component_cookbook:modal") def slide_over(request): size = request.GET.get("size", "md") placement = request.GET.get("placement", "end") if size not in {"sm", "md", "lg"} or placement not in {"start", "end"}: return HttpResponseBadRequest("Invalid slide-over options.") return _branch(request, "component_cookbook/partials/slide_over.html", "component_cookbook/slide_over.html", {"close_href": reverse("component_cookbook:index"), "size": size, "placement": placement}) @require_POST def toggle(request): return _branch(request, "component_cookbook/partials/toggle_outcome.html", "component_cookbook/toggle.html", {"enabled": request.POST.get("notices") == "on"}) @require_POST def toast(request): if not _htmx(request): messages.success(request, _("The server delivered this demonstration outcome.")) response = redirect("component_cookbook:toast-page") else: response = render(request, "component_cookbook/partials/toast_oob.html") patch_vary_headers(response, ("HX-Request",)) return response def toast_page(request): return _page(request, "component_cookbook/toast_region.html") def toast_region(request): return _page(request, "component_cookbook/toast_region.html") def combobox(request): return _page(request, "component_cookbook/combobox.html", form=LanguageForm(), options_url=reverse("component_cookbook:combobox-options")) def combobox_options(request): query = request.GET.get("q", "").strip().casefold()[:40] return render(request, "component_cookbook/partials/combobox_options.html", {"choices": [choice for choice in LANGUAGES if not query or query in choice[1].casefold()]}) @require_POST def combobox_submit(request): form = LanguageForm(request.POST) context = {"form": form, "options_url": reverse("component_cookbook:combobox-options")} if form.is_valid(): context["selected_label"] = dict(LANGUAGES)[form.cleaned_data["language"]] return _branch(request, "component_cookbook/partials/combobox_form.html", "component_cookbook/combobox.html", context, 422 if _htmx(request) and not form.is_valid() else 200) def _rows(query: str, status: str): return [row for row in ROWS if (not query or query.casefold() in row["name"].casefold()) and (not status or row["status"] == status)] def table(request): form = FilterForm(request.GET) query, status = (form.cleaned_data["q"], form.cleaned_data["status"]) if form.is_valid() else ("", "") context = {"filter_form": form, "page_obj": Paginator(_rows(query, status), 4).get_page(request.GET.get("page", 1)), "table_url": reverse("component_cookbook:table"), "form_invalid": not form.is_valid()} return _branch(request, "component_cookbook/partials/table_region.html", "component_cookbook/table.html", context) def search(request): query = request.GET.get("q", "").strip()[:40] return _page(request, "component_cookbook/search.html", query=query, result_count=len(_rows(query, "")), search_url=reverse("component_cookbook:search")) @require_http_methods(["GET", "POST"]) def upload(request): if request.method == "GET": return _page(request, "component_cookbook/upload.html", form=UploadForm()) form = UploadForm(request.POST, request.FILES) context = {"form": form} if form.is_valid(): uploaded = form.cleaned_data["file"] context["outcome"] = _("Validated %(name)s (%(size)s bytes). The application did not retain it.") % {"name": uploaded.name, "size": uploaded.size} context["form"] = UploadForm() return _branch(request, "component_cookbook/partials/upload_form.html", "component_cookbook/upload.html", context, 422 if _htmx(request) and not form.is_valid() else 200)