Skip to content

Commit

Permalink
Global admin search
Browse files Browse the repository at this point in the history
  • Loading branch information
davegaeddert committed Jan 11, 2024
1 parent 30e0484 commit 5dfc900
Show file tree
Hide file tree
Showing 10 changed files with 273 additions and 162 deletions.
25 changes: 23 additions & 2 deletions bolt-admin/bolt/admin/templates/admin/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
{%- endif -%}
</title>
{% tailwind_css %}
{% htmx_js %}
<link href="{{ asset('admin/admin.css') }}" rel="stylesheet">
<script src="{{ asset('admin/jquery-3.6.1.slim.min.js') }}"></script>
<script src="{{ asset('admin/chart.js') }}" defer></script>
Expand Down Expand Up @@ -83,7 +84,27 @@
<span class="text-stone-400">/</span>
<a class="text-stone-600" href="{{ request.path }}">{{ title }}</a>
</div>
<div class="flex items-center">
<div class="flex items-center space-x-5">
<div class="flex justify-end">
<form method="GET" action="{{ url('admin:search') }}" class="flex">
<div class="relative max-w-xs">
<label for="query" class="sr-only">Search</label>
<input
type="text"
name="query"
id="query"
class="block w-full px-3 pl-10 text-sm border-gray-200 rounded-md focus:border-blue-500 focus:ring-blue-500"
placeholder="Search everything"
value="{{ global_search_query|default('') }}"
>
<div class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none">
<svg class="h-3.5 w-3.5 text-gray-400" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"></path>
</svg>
</div>
</div>
</form>
</div>
<a href="/">Back to app</a>
</div>
</div>
Expand All @@ -92,7 +113,7 @@
<div>
{% block header %}
<h1 class="text-2xl font-semibold text-stone-700">
{{ title }}
{% block title %}{{ title }}{% endblock %}
</h1>
{% if description %}
<p class="mt-2 text-sm text-gray-500">{{ description }}</p>
Expand Down
319 changes: 166 additions & 153 deletions bolt-admin/bolt/admin/templates/admin/list.html

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions bolt-admin/bolt/admin/templates/admin/search.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{% extends "admin/base.html" %}

{% block title %}
{%- if global_search_query -%}
Search results for "{{ global_search_query }}"
{%- else -%}
Search
{%- endif -%}
{% endblock %}

{% block content %}

{% if global_search_query %}
<div class="mt-6">
{% for view in searchable_views %}
<div
class="*:mb-14"
hx-get="{{ view.get_absolute_url() }}?search={{ global_search_query }}&page_size=5"
hx-trigger="bhxLoad from:body"
bhx-fragment="list">
</div>
{% endfor %}
</div>
{% else %}
<p class="text-stone-500">Enter a search query in the top bar</p>
{% endif %}

{% endblock %}
3 changes: 2 additions & 1 deletion bolt-admin/bolt/admin/urls.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from bolt.urls import include, path

from .views.default import AdminIndexView
from .views.default import AdminIndexView, AdminSearchView
from .views.registry import registry

default_namespace = "admin"


urlpatterns = [
path("search/", AdminSearchView.as_view(), name="search"),
path("", include(registry.get_urls())),
path("", AdminIndexView.as_view(), name="index"),
]
34 changes: 29 additions & 5 deletions bolt-admin/bolt/admin/views/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import TYPE_CHECKING

from bolt.db import models
from bolt.htmx.views import HTMXViewMixin
from bolt.http import HttpResponse, HttpResponseRedirect
from bolt.paginator import Paginator
from bolt.urls import reverse
Expand Down Expand Up @@ -115,25 +116,28 @@ def render_card(self, card: "Card"):
return card().render(self.request)


class AdminListView(AdminView):
class AdminListView(HTMXViewMixin, AdminView):
template_name = "admin/list.html"
fields: list[str]
actions: list[str] = []
filters: list[str] = []
page_size = 100
show_search = False
allow_global_search = False

def get_context(self):
context = super().get_context()

# Make this available on self for usage in get_objects and other methods
self.filter = self.request.GET.get("filter", "")

objects = self.get_objects()
page_size = self.request.GET.get("page_size", self.page_size)
paginator = Paginator(self.get_objects(), page_size)
self._page = paginator.get_page(self.request.GET.get("page", 1))

context["paginator"] = Paginator(objects, self.page_size)
context["page"] = context["paginator"].get_page(self.request.GET.get("page", 1))
context["objects"] = context["page"] # alias
context["paginator"] = paginator
context["page"] = self._page
context["objects"] = self._page # alias
context["fields"] = self.get_fields()
context["actions"] = self.get_actions()
context["filters"] = self.get_filters()
Expand All @@ -144,6 +148,8 @@ def get_context(self):
context["search_query"] = self.request.GET.get("search", "")
context["show_search"] = self.show_search

context["table_style"] = getattr(self, "_table_style", "default")

context["get_object_pk"] = self.get_object_pk
context["get_object_field"] = self.get_object_field

Expand All @@ -153,6 +159,24 @@ def get_context(self):

return context

def get(self) -> HttpResponse:
if self.is_htmx_request:
hx_from_this_page = self.request.path in self.request.headers.get(
"HX-Current-Url", ""
)
if not hx_from_this_page:
self._table_style = "simple"
else:
hx_from_this_page = False

response = super().get()

if self.is_htmx_request and not hx_from_this_page and not self._page:
# Don't render anything
return HttpResponse(status=204)

return response

def post(self) -> HttpResponse:
# won't be "key" anymore, just list
action_name = self.request.POST.get("action_name")
Expand Down
12 changes: 12 additions & 0 deletions bolt-admin/bolt/admin/views/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,15 @@ def get_context(self):
context = super().get_context()
context["dashboards"] = registry.registered_dashboards
return context


class AdminSearchView(AdminView):
template_name = "admin/search.html"
title = "Search"
slug = "search"

def get_context(self):
context = super().get_context()
context["searchable_views"] = registry.get_searchable_views()
context["global_search_query"] = self.request.GET.get("query", "")
return context
1 change: 1 addition & 0 deletions bolt-admin/bolt/admin/views/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def get_model_field(instance, field):

class AdminModelListView(AdminListView):
show_search = True
allow_global_search = True

model: "models.Model"

Expand Down
9 changes: 9 additions & 0 deletions bolt-admin/bolt/admin/views/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ def add_view_path(view, _path):

return urlpatterns

def get_searchable_views(self):
views = [
view
for view in self.registered_views
if getattr(view, "allow_global_search", False)
]
views.sort(key=lambda v: v.get_title())
return views


registry = AdminViewRegistry()
register_view = registry.register_view
Expand Down
1 change: 1 addition & 0 deletions bolt-admin/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ license = "MIT"

[tool.poetry.dependencies]
python = "^3.8"
# TODO bolt-htmx required

[tool.poetry.dev-dependencies]
pytest = "^7.1.2"
Expand Down
3 changes: 2 additions & 1 deletion bolt-htmx/bolt/htmx/assets/htmx/bolthtmx.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@ htmx.defineExtension("error-classes", {
});

// Our own load event, to support lazy loading
// *after* our fragment extension is added
// *after* our fragment extension is added.
// Use with hx-trigger="bhxLoad from:body"
htmx.trigger(document.body, "bhxLoad");

0 comments on commit 5dfc900

Please sign in to comment.