Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 35 additions & 10 deletions froide/helper/search/registry.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,44 @@
from typing import Callable, NotRequired, Optional, TypedDict

from django.http import HttpRequest

from django_stubs_ext import StrOrPromise


class SearchItem(TypedDict):
name: str
title: StrOrPromise
url: StrOrPromise
menu_title: NotRequired[StrOrPromise]
order: NotRequired[int]


type SearchResponse = SearchItem | None
type SearchItemCallback = Callable[[HttpRequest], SearchResponse]


class SearchRegistry(object):
def __init__(self):
self.searches = []
self.named_searches: set[str] = set()
self.searches: list[SearchItemCallback] = []

# TODO: in the next breaking release, make `name` required
def register(self, func: SearchItemCallback, name: Optional[str] = None):
if name in self.named_searches:
return

if name:
self.named_searches.add(name)

def register(self, func):
self.searches.append(func)

def get_searches(self, request):
sections = []
def get_searches(self, request: HttpRequest) -> list[SearchItem]:
sections: list[SearchItem] = []
for callback in self.searches:
menu_item = callback(request)
if menu_item is None:
continue
sections.append(menu_item)
sections = sorted(sections, key=lambda x: (x.get("order", 5), x["title"]))
return sections
if menu_item := callback(request):
sections.append(menu_item)

return sorted(sections, key=lambda x: (x.get("order", 5), x["title"]))


search_registry = SearchRegistry()
44 changes: 44 additions & 0 deletions froide/helper/search/tests/test_search_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from django.http import HttpRequest
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from froide.helper.search.registry import SearchItem, SearchRegistry


def test_no_duplicates():
registry = SearchRegistry()

def add_search(request) -> SearchItem:
return {
"name": "foirequest",
"title": _("Requests"),
"url": reverse("foirequest-list"),
}

def add_empty_search(request):
return None

r = HttpRequest()

registry.register(add_search, "foirequest")
assert len(registry.searches) == 1
assert len(registry.get_searches(r)) == 1

# doesn't get added twice
registry.register(add_search, "foirequest")
assert len(registry.searches) == 1
assert len(registry.get_searches(r)) == 1

# but works with different name
registry.register(add_search, "document")
assert len(registry.searches) == 2
assert len(registry.get_searches(r)) == 2

registry.register(add_empty_search, "empty")
assert len(registry.searches) == 3
assert len(registry.get_searches(r)) == 2

# or without a name
registry.register(add_search)
assert len(registry.searches) == 4
assert len(registry.get_searches(r)) == 3
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ dev = [
"beautifulsoup4",
"django-coverage-plugin",
"django-extended-makemessages>=1.7.1",
"django-stubs",
"django-stubs<5.3",
"factory-boy",
"faker",
"monkeytype",
Expand All @@ -93,6 +93,7 @@ dev = [
"prek>=0.3.5",
"pytest-xdist>=3.8.0",
"pytest-cov>=7.1.0",
"django-stubs-ext<5.3",
]

[build-system]
Expand Down
4 changes: 3 additions & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading