import functools
from collections import OrderedDict
import django_tables2
from dal_alight_queryset_sequence.views import AlightQuerySetSequenceView
from django import http
from django.template.loader import render_to_string
from django.utils.html import format_html
from django.utils.translation import gettext as _, gettext_lazy
from django.views import generic
from queryset_sequence import QuerySetSequence
from djcrud import tags
from djcrud.view import ViewMixin
from djcrud.views.pagination import PaginationMixin
from djcrud.views.template import TemplateViewMixin
from .lookup import (
build_site_search_queryset,
find_detail_url,
find_model_router,
mixup_querysets,
)
[docs]
class SearchActionsColumn(django_tables2.Column):
empty_values = ()
template_name = "djcrud/_actions_column.html"
[docs]
def render(self, record, table):
obj = record["record"] if isinstance(record, dict) else record
router = find_model_router(table.view.router.root, type(obj))
if router is None:
return ""
actions = router.get_tagged_views(
tags.OBJECT,
request=table.view.request,
object=obj,
)
return render_to_string(
self.template_name,
{"actions": actions, "view": table.view, "record": obj},
request=table.view.request,
)
[docs]
class SearchResultsTable(django_tables2.Table):
type = django_tables2.Column(
accessor="type_label",
verbose_name=gettext_lazy("Type"),
orderable=False,
)
result = django_tables2.Column(
accessor="result_label",
verbose_name=gettext_lazy("Result"),
orderable=False,
)
actions = SearchActionsColumn()
class Meta:
template_name = "djcrud/_tables2.html"
fields = ("type", "result", "actions")
attrs = {
"class": "table is-striped is-hoverable is-fullwidth",
}
[docs]
class SearchView(PaginationMixin, TemplateViewMixin, generic.ListView):
"""Site-wide search results page with paginated table."""
urlpath = "search/"
default_template_name = "djcrud/search.html"
tags = []
paginate_by = 25
pagination_target = "[up-main]"
search_param = "q"
context_object_name = "object_list"
@property
def title(self):
return _("Search")
@property
def q(self):
raw = self.request.GET.get(self.search_param, "")
return raw.strip()
@property
def show_results(self):
return bool(self.q)
@property
def empty_list_message(self):
return _("No results found.")
[docs]
def has_permission(self):
return self.request.user.is_authenticated
[docs]
def get_paginate_by(self, queryset):
if not self.show_results:
return None
return super().get_paginate_by(queryset)
[docs]
def get_queryset(self):
if not self.show_results:
return QuerySetSequence()
return build_site_search_queryset(self.request, self.q, mixup=False)
def _table_row(self, record):
model = type(record)
detail_url = find_detail_url(model, record.pk)
label = str(record)
if detail_url:
result_label = format_html('<a href="{}">{}</a>', detail_url, label)
else:
result_label = label
return {
"type_label": model._meta.verbose_name,
"result_label": result_label,
"record": record,
}
@functools.cached_property
def table(self):
rows = [self._table_row(record) for record in self.object_list]
table = SearchResultsTable(rows)
table.view = self
django_tables2.RequestConfig(self.request, paginate=False).configure(table)
return table
[docs]
class SiteSearchView(ViewMixin, AlightQuerySetSequenceView):
"""Site-wide autocomplete across opted-in model-router list views."""
urlpath = "search/autocomplete/"
tags = []
@property
def codename(self):
return "autocomplete"
mixup = True
paginate_by = 10
[docs]
def has_permission(self):
return self.request.user.is_authenticated
[docs]
def get_queryset(self):
qs = build_site_search_queryset(
self.request,
self.q,
mixup=False,
paginate_by=self.paginate_by,
)
if not self.q or not qs.get_querysets():
return QuerySetSequence()
if self.mixup:
qs = mixup_querysets(qs, self.paginate_by)
return qs
[docs]
def render_to_response(self, context):
groups = OrderedDict()
for result in context["object_list"]:
groups.setdefault(type(result), []).append(result)
html = []
for model, results in groups.items():
html.append(
format_html(
'<div class="autocomplete-light-group djcrud-site-search-group">{}</div>',
model._meta.verbose_name,
)
)
for result in results:
detail_url = find_detail_url(model, result.pk)
if not detail_url:
continue
html.append(
format_html(
'<div data-value="{}" data-url="{}">{}</div>',
self.get_result_value(result),
detail_url,
str(result),
)
)
return http.HttpResponse(
"".join(html),
content_type="text/html; charset=utf-8",
)