Skip to content

Commit

Permalink
Swap out worf debug response for toolbar
Browse files Browse the repository at this point in the history
  • Loading branch information
stevelacey committed Jan 16, 2023
1 parent 6587eb1 commit c4a6f74
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 41 deletions.
53 changes: 50 additions & 3 deletions worf/renderers.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,58 @@
from django.http import HttpResponse, JsonResponse
from django.template.response import TemplateResponse

from worf.casing import snake_to_camel
from worf.conf import settings
from worf.shortcuts import field_list


def browsable_response(request, response, status_code=200):
def browsable_response(request, response, status_code, view):
template = "worf/api.html"
serializer = view.get_serializer()

include = field_list(view.bundle.get("include", []))
search = field_list(view.bundle.get("search", []), delimiter="__")

filter_fields = [
(transform_field(field), bool(field in view.bundle))
for field in getattr(view, "filter_fields", [])
]
include_fields = [
(transform_field(field), bool(field in include or not include))
for field in getattr(view, "include_fields", {}).keys()
]
search_fields = [
(transform_field(field), bool(field in search or not search))
for field in getattr(view, "search_fields", [])
]

context = dict(
content=response.content.decode("utf-8"),
fields=[
(
"Filters",
sorted(filter_fields),
len([field for field, active in filter_fields if active]),
),
(
"Include",
sorted(include_fields),
len(include),
),
(
"Search",
sorted(search_fields),
len(search or search_fields) if view.bundle.get("q") else 0,
),
],
lookup_kwargs=getattr(view, "lookup_kwargs", {}),
payload=view.bundle,
response=response,
serializer=serializer,
serializer_name=type(serializer).__name__,
settings=settings,
view=view,
view_name=type(view).__name__,
)

response = TemplateResponse(request, template, context=context)
Expand All @@ -19,7 +62,7 @@ def browsable_response(request, response, status_code=200):
return response


def render_response(request, data, status_code=200):
def render_response(request, data, status_code, view):
is_browsable = (
settings.WORF_BROWSABLE_API
and "text/html" in request.headers.get("Accept", "")
Expand All @@ -35,6 +78,10 @@ def render_response(request, data, status_code=200):
response.status_code = status_code

if is_browsable:
response = browsable_response(request, response, status_code)
response = browsable_response(request, response, status_code, view)

return response


def transform_field(field):
return "__".join(map(snake_to_camel, field.split("__")))
15 changes: 5 additions & 10 deletions worf/serializers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import marshmallow
from marshmallow.decorators import * # noqa: F401, F403

from django.core.exceptions import ImproperlyConfigured
from django.db.models.fields.files import FieldFile

from worf import fields
Expand All @@ -16,26 +15,22 @@ class SerializeModels:
serializer = None
staff_serializer = None

def get_serializer(self):
def get_serializer(self, **kwargs):
serializer = self.serializer

if self.staff_serializer and self.request.user.is_staff: # pragma: no cover
serializer = self.staff_serializer

if not serializer: # pragma: no cover
msg = f"{self.__class__.__name__}.get_serializer() did not return a serializer"
raise ImproperlyConfigured(msg)

return serializer(**self.get_serializer_kwargs())
return serializer and serializer(**self.get_serializer_kwargs(**kwargs))

def get_serializer_context(self):
return {}

def get_serializer_kwargs(self):
def get_serializer_kwargs(self, include=[], exclude=[]):
context = dict(request=self.request, **self.get_serializer_context())
only = set(field_list(self.bundle.get("fields", [])))
include = field_list(self.bundle.get("include", []))
exclude = set(self.include_fields.keys()) - set(include)
include = include or field_list(self.bundle.get("include", []))
exclude = exclude or set(self.include_fields.keys()) - set(include)
return dict(context=context, only=only, exclude=exclude)

def load_serializer(self):
Expand Down
90 changes: 86 additions & 4 deletions worf/templates/worf/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@
<meta name="robots" content="none,noarchive"/>
{% endblock %}

<title>{{ settings.WORF_API_NAME }}: {{ request.get_full_path }}</title>
<title>{{ view_name }} | {{ settings.WORF_API_NAME }}: {{ request.get_full_path }}</title>

{% block style %}
<link href="https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" rel="stylesheet">
<link href="https://unpkg.com/prism-js-fold@1.0.1/prism-js-fold.css" crossorigin="anonymous" referrerpolicy="no-referrer" rel="stylesheet">
<style>
body { background: #131417; color: #fff; }
a { color: #58A6FF; }
a:hover { text-decoration: underline; }
code, pre { font-family: inconsolata, monaco, consolas, courier, monospace; color: #c5c8c6; }
nav, pre { background: #202126; }
details, nav, pre { background: #202126; }
details { box-shadow: 0 0 50px #131417 }
strong { font-weight: 500; }
.token a { color: #58a6ff; }
.token.boolean { color: #fbbf24; }
Expand All @@ -32,6 +32,7 @@
{% endblock %}

{% block script %}
<script src="https://cdn.tailwindcss.com" referrerpolicy="no-referrer"></script>
<script src="https://unpkg.com/prismjs@1.28.0" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://unpkg.com/prismjs@1.28.0/components/prism-json.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://unpkg.com/prismjs@1.28.0/plugins/keep-markup/prism-keep-markup.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
Expand All @@ -57,17 +58,98 @@
<div class="max-w-5xl mx-auto">
<main>
{% block content %}
<h2 class="sr-only">Request</h2>

<nav class="font-mono break-all my-4 px-4 py-2">
<strong>{{ request.method }}</strong> {{ request.get_full_path }}
</nav>

<div class="my-4">
<h2 class="sr-only">Response</h2>
<pre class="w-full p-4 overflow-auto"><strong>HTTP {{ response.status_code }} {{ response.status_text }}</strong>{% for key, value in response.headers.items %}
<strong>{{ key }}:</strong> {{ value }}{% endfor %}

<code class="language-json">{{ content }}</code></pre>
</div>
{% endblock content %}
{% endblock %}

{% if settings.WORF_DEBUG %}
{% block toolbar %}
<div class="fixed max-w-sm max-h-screen bottom-0 right-0 pl-8 pt-8 overflow-auto">
{% if view %}
<details class="m-2">
<summary class="font-medium px-4 py-2 cursor-pointer">
<span class="px-1">{{ view_name }}</span>
</summary>
<div class="px-4 py-2">
{{ view }}
</div>
</details>
{% endif %}

{% if serializer %}
<details class="m-2">
<summary class="font-medium px-4 py-2 cursor-pointer">
<span class="px-1">{{ serializer_name }}</span>
</summary>
<div class="px-4 py-2">
{{ serializer }}
</div>
</details>
{% endif %}

{% if payload %}
<details class="m-2">
<summary class="font-medium px-4 py-2 cursor-pointer">
<span class="px-1">
Payload
<span class="bg-zinc-700 mx-1 px-1">{{ payload|length }}</span>
</span>
</summary>
<div class="px-4 py-2 break-all">
{{ payload }}
</div>
</details>
{% endif %}

{% if lookup_kwargs %}
<details class="m-2">
<summary class="font-medium px-4 py-2 cursor-pointer">
<span class="px-1">
Lookups
<span class="bg-zinc-700 mx-1 px-1">{{ lookup_kwargs|length }}</span>
</span>
</summary>
<div class="px-4 py-2 break-all">
{{ lookup_kwargs }}
</div>
</details>
{% endif %}

{% for name, items, count in fields %}
{% if items %}
<details class="m-2">
<summary class="font-medium px-4 py-2 cursor-pointer">
<span class="px-1">
{{ name }}
{% if count %}<span class="bg-zinc-700 mx-1 px-1">{{ count }}</span>{% endif %}
</span>
</summary>
<div class="px-4 py-2">
<table>
{% for field, active in items %}
<tr class="{% if count and not active %}opacity-50{% endif %}">
<th class="text-left pr-4">{{ field }}</th>
</tr>
{% endfor %}
</table>
</div>
</details>
{% endif %}
{% endfor %}
</div>
{% endblock %}
{% endif %}
</main>
</div>
{% endblock %}
Expand Down
2 changes: 1 addition & 1 deletion worf/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def render_to_response(self, data=None, status_code=200):
msg += "render_to_response, nor did its serializer method"
raise ImproperlyConfigured(msg)

return render_response(self.request, data, status_code)
return render_response(self.request, data, status_code, self)


class AbstractBaseAPI(SerializeModels, ValidateFields, APIResponse):
Expand Down
6 changes: 3 additions & 3 deletions worf/views/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ def create(self, *args, **kwargs):
self.instance.refresh_from_db()
return self.instance

def get_serializer(self):
def get_serializer(self, **kwargs):
if self.create_serializer and self.request.method == "POST":
return self.create_serializer(**self.get_serializer_kwargs())
return super().get_serializer()
return self.create_serializer(**self.get_serializer_kwargs(**kwargs))
return super().get_serializer(**kwargs)

def new_instance(self):
return self.model()
Expand Down
18 changes: 1 addition & 17 deletions worf/views/list.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import operator
from functools import reduce

from django.core.exceptions import EmptyResultSet, ImproperlyConfigured
from django.core.exceptions import ImproperlyConfigured
from django.core.paginator import EmptyPage, Paginator
from django.db.models import F, OrderBy, Prefetch, Q

from worf.casing import camel_to_snake
from worf.conf import settings
from worf.exceptions import FieldError
from worf.filters import apply_filterset, generate_filterset
from worf.shortcuts import field_list, string_list
Expand Down Expand Up @@ -174,12 +173,6 @@ def paginated_results(self):
queryset = self.get_processed_queryset()
request = self.request

if settings.WORF_DEBUG: # pragma: no cover
try:
self.query = str(queryset.query)
except EmptyResultSet:
self.query = None

default_per_page = getattr(self, "results_per_page", self.per_page)
per_page = max(int(request.GET.get("perPage") or default_per_page), 1)
max_per_page = self.max_per_page or default_per_page
Expand Down Expand Up @@ -209,15 +202,6 @@ def serialize(self):
page=self.page_num,
)

if settings.WORF_DEBUG: # pragma: no cover
payload["debug"] = {
"bundle": self.bundle,
"lookup_kwargs": getattr(self, "lookup_kwargs", {}),
"query": self.query,
"search_query": str(self.search_query),
"serializer": str(serializer).strip("<>"),
}

return payload


Expand Down
6 changes: 3 additions & 3 deletions worf/views/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
class UpdateAPI(AssignAttributes, FindInstance, AbstractBaseAPI):
update_serializer = None

def get_serializer(self):
def get_serializer(self, **kwargs):
if self.update_serializer and self.request.method in ("PATCH", "PUT"):
return self.update_serializer(**self.get_serializer_kwargs())
return super().get_serializer()
return self.update_serializer(**self.get_serializer_kwargs(**kwargs))
return super().get_serializer(**kwargs)

def patch(self, *args, **kwargs):
self.update(*args, **kwargs)
Expand Down

0 comments on commit c4a6f74

Please sign in to comment.