We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Replace `total_ordering` usage with comparison functions implementation
### Is your proposal related to a problem?

We have two instances of `total_ordering` usage within the codebase:

https://github.com/wagtail/wagtail/blob/cd5200c8e1ac0d7299fd9c398b2b994606b3c7d2/wagtail/admin/search.py#L12-L13

https://github.com/wagtail/wagtail/blob/cd5200c8e1ac0d7299fd9c398b2b994606b3c7d2/wagtail/admin/widgets/button.py#L11-L12

Even though it's convenient, `total_ordering` is known to be slow. According to [Python's docs](https://docs.python.org/3/library/functools.html#functools.total_ordering):

> **Note**
> While this decorator makes it easy to create well behaved totally ordered types, it does come at the cost of slower execution and more complex stack traces for the derived comparison methods. If performance benchmarking indicates this is a bottleneck for a given application, implementing all six rich comparison methods instead is likely to provide an easy speed boost.

Django recently removed their usage of `total_ordering` in https://github.com/django/django/pull/16958/commits/ee36e101e8f8c0acde4bb148b738ab7034e902a0 (probably not all usages, I haven't checked).

### Describe the solution you'd like

<!--
  Provide a clear and concise description of what you want to happen.
-->

Replace `total_ordering` with implementations of `__eq__()`, `__ne__()`, `__lt__()`, `__le__()`, `__gt__()`, and `__ge__()`.

### Describe alternatives you've considered

<!--
  Let us know about other solutions you've tried or researched.
-->

Keep using `total_ordering`

### Additional context

I found this while fixing an incorrect import of `total_ordering` in #10525.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `wagtail/admin/widgets/button.py`
Content:
```
1 from functools import total_ordering
2 
3 from django.forms.utils import flatatt
4 from django.template.loader import render_to_string
5 from django.utils.functional import cached_property
6 from django.utils.html import format_html
7 
8 from wagtail import hooks
9 
10 
11 @total_ordering
12 class Button:
13     show = True
14 
15     def __init__(
16         self, label, url, classes=set(), icon_name=None, attrs={}, priority=1000
17     ):
18         self.label = label
19         self.url = url
20         self.classes = classes
21         self.icon_name = icon_name
22         self.attrs = attrs.copy()
23         self.priority = priority
24 
25     def render(self):
26         attrs = {
27             "href": self.url,
28             "class": " ".join(sorted(self.classes)),
29             "title": self.label,
30         }
31         attrs.update(self.attrs)
32         return format_html("<a{}>{}</a>", flatatt(attrs), self.label)
33 
34     def __str__(self):
35         return self.render()
36 
37     def __repr__(self):
38         return f"<Button: {self.label}>"
39 
40     def __lt__(self, other):
41         if not isinstance(other, Button):
42             return NotImplemented
43         return (self.priority, self.label) < (other.priority, other.label)
44 
45     def __eq__(self, other):
46         if not isinstance(other, Button):
47             return NotImplemented
48         return (
49             self.label == other.label
50             and self.url == other.url
51             and self.classes == other.classes
52             and self.attrs == other.attrs
53             and self.priority == other.priority
54         )
55 
56 
57 # Base class for all listing buttons
58 # This is also used by SnippetListingButton defined in wagtail.snippets.widgets
59 class ListingButton(Button):
60     def __init__(self, label, url, classes=set(), **kwargs):
61         classes = {"button", "button-small", "button-secondary"} | set(classes)
62         super().__init__(label, url, classes=classes, **kwargs)
63 
64 
65 class PageListingButton(ListingButton):
66     pass
67 
68 
69 class BaseDropdownMenuButton(Button):
70     def __init__(self, *args, **kwargs):
71         super().__init__(*args, url=None, **kwargs)
72 
73     @cached_property
74     def dropdown_buttons(self):
75         raise NotImplementedError
76 
77     def get_context_data(self):
78         return {
79             "buttons": self.dropdown_buttons,
80             "label": self.label,
81             "title": self.attrs.get("title"),
82             "classes": self.classes,
83         }
84 
85     def render(self):
86         return render_to_string(self.template_name, self.get_context_data())
87 
88 
89 class ButtonWithDropdown(BaseDropdownMenuButton):
90     template_name = "wagtailadmin/pages/listing/_button_with_dropdown.html"
91 
92     def __init__(self, *args, **kwargs):
93         self.button_classes = kwargs.pop("button_classes", set())
94         self.buttons_data = kwargs.pop("buttons_data", [])
95         super().__init__(*args, **kwargs)
96 
97     def get_context_data(self):
98         context = super().get_context_data()
99         context["button_classes"] = self.button_classes
100         return context
101 
102     @cached_property
103     def dropdown_buttons(self):
104         return [Button(**button) for button in self.buttons_data]
105 
106 
107 class ButtonWithDropdownFromHook(BaseDropdownMenuButton):
108     template_name = "wagtailadmin/pages/listing/_button_with_dropdown.html"
109 
110     def __init__(self, label, hook_name, page, page_perms, next_url=None, **kwargs):
111         self.hook_name = hook_name
112         self.page = page
113         self.page_perms = page_perms
114         self.next_url = next_url
115 
116         super().__init__(label, **kwargs)
117 
118     @property
119     def show(self):
120         return bool(self.dropdown_buttons)
121 
122     @cached_property
123     def dropdown_buttons(self):
124         button_hooks = hooks.get_hooks(self.hook_name)
125 
126         buttons = []
127         for hook in button_hooks:
128             buttons.extend(hook(self.page, self.page_perms, self.next_url))
129 
130         buttons.sort()
131         return buttons
132
```

Path: `wagtail/admin/search.py`
Content:
```
1 from functools import total_ordering
2 
3 from django.forms import Media, MediaDefiningClass
4 from django.forms.utils import flatatt
5 from django.template.loader import render_to_string
6 from django.utils.functional import cached_property
7 from django.utils.safestring import mark_safe
8 from django.utils.text import slugify
9 
10 from wagtail import hooks
11 from wagtail.admin.forms.search import SearchForm
12 
13 
14 @total_ordering
15 class SearchArea(metaclass=MediaDefiningClass):
16     template = "wagtailadmin/shared/search_area.html"
17 
18     def __init__(
19         self, label, url, name=None, classnames="", icon_name="", attrs=None, order=1000
20     ):
21         self.label = label
22         self.url = url
23         self.classnames = classnames
24         self.icon_name = icon_name
25         self.name = name or slugify(str(label))
26         self.order = order
27 
28         if attrs:
29             self.attr_string = flatatt(attrs)
30         else:
31             self.attr_string = ""
32 
33     def __lt__(self, other):
34         return (self.order, self.label) < (other.order, other.label)
35 
36     def __eq__(self, other):
37         return (self.order, self.label) == (other.order, other.label)
38 
39     def is_shown(self, request):
40         """
41         Whether this search area should be shown for the given request; permission
42         checks etc should go here. By default, search areas are shown all the time
43         """
44         return True
45 
46     def is_active(self, request, current=None):
47         if current is None:
48             return request.path.startswith(self.url)
49         else:
50             return self.name == current
51 
52     def render_html(self, request, query, current=None):
53         return render_to_string(
54             self.template,
55             {
56                 "name": self.name,
57                 "url": self.url,
58                 "classnames": self.classnames,
59                 "icon_name": self.icon_name,
60                 "attr_string": self.attr_string,
61                 "label": self.label,
62                 "active": self.is_active(request, current),
63                 "query_string": query,
64             },
65             request=request,
66         )
67 
68 
69 class Search:
70     def __init__(self, register_hook_name, construct_hook_name=None):
71         self.register_hook_name = register_hook_name
72         self.construct_hook_name = construct_hook_name
73 
74     @cached_property
75     def registered_search_areas(self):
76         return sorted([fn() for fn in hooks.get_hooks(self.register_hook_name)])
77 
78     def search_items_for_request(self, request):
79         return [item for item in self.registered_search_areas if item.is_shown(request)]
80 
81     def active_search(self, request, current=None):
82         return [
83             item
84             for item in self.search_items_for_request(request)
85             if item.is_active(request, current)
86         ]
87 
88     @property
89     def media(self):
90         media = Media()
91         for item in self.registered_search_areas:
92             media += item.media
93         return media
94 
95     def render_html(self, request, current=None):
96         search_areas = self.search_items_for_request(request)
97 
98         # Get query parameter
99         form = SearchForm(request.GET)
100         query = ""
101         if form.is_valid():
102             query = form.cleaned_data["q"]
103 
104         # provide a hook for modifying the search area, if construct_hook_name has been set
105         if self.construct_hook_name:
106             for fn in hooks.get_hooks(self.construct_hook_name):
107                 fn(request, search_areas)
108 
109         rendered_search_areas = []
110         for item in search_areas:
111             rendered_search_areas.append(item.render_html(request, query, current))
112 
113         return mark_safe("".join(rendered_search_areas))
114 
115 
116 admin_search_areas = Search(
117     register_hook_name="register_admin_search_area",
118     construct_hook_name="construct_search",
119 )
120
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/wagtail/admin/search.py b/wagtail/admin/search.py
--- a/wagtail/admin/search.py
+++ b/wagtail/admin/search.py
@@ -1,5 +1,3 @@
-from functools import total_ordering
-
 from django.forms import Media, MediaDefiningClass
 from django.forms.utils import flatatt
 from django.template.loader import render_to_string
@@ -11,7 +9,6 @@
 from wagtail.admin.forms.search import SearchForm
 
 
-@total_ordering
 class SearchArea(metaclass=MediaDefiningClass):
     template = "wagtailadmin/shared/search_area.html"
 
@@ -31,9 +28,28 @@
             self.attr_string = ""
 
     def __lt__(self, other):
+        if not isinstance(other, SearchArea):
+            return NotImplemented
         return (self.order, self.label) < (other.order, other.label)
 
+    def __le__(self, other):
+        if not isinstance(other, SearchArea):
+            return NotImplemented
+        return (self.order, self.label) <= (other.order, other.label)
+
+    def __gt__(self, other):
+        if not isinstance(other, SearchArea):
+            return NotImplemented
+        return (self.order, self.label) > (other.order, other.label)
+
+    def __ge__(self, other):
+        if not isinstance(other, SearchArea):
+            return NotImplemented
+        return (self.order, self.label) >= (other.order, other.label)
+
     def __eq__(self, other):
+        if not isinstance(other, SearchArea):
+            return NotImplemented
         return (self.order, self.label) == (other.order, other.label)
 
     def is_shown(self, request):
diff --git a/wagtail/admin/widgets/button.py b/wagtail/admin/widgets/button.py
--- a/wagtail/admin/widgets/button.py
+++ b/wagtail/admin/widgets/button.py
@@ -1,5 +1,3 @@
-from functools import total_ordering
-
 from django.forms.utils import flatatt
 from django.template.loader import render_to_string
 from django.utils.functional import cached_property
@@ -8,7 +6,6 @@
 from wagtail import hooks
 
 
-@total_ordering
 class Button:
     show = True
 
@@ -42,6 +39,21 @@
             return NotImplemented
         return (self.priority, self.label) < (other.priority, other.label)
 
+    def __le__(self, other):
+        if not isinstance(other, Button):
+            return NotImplemented
+        return (self.priority, self.label) <= (other.priority, other.label)
+
+    def __gt__(self, other):
+        if not isinstance(other, Button):
+            return NotImplemented
+        return (self.priority, self.label) > (other.priority, other.label)
+
+    def __ge__(self, other):
+        if not isinstance(other, Button):
+            return NotImplemented
+        return (self.priority, self.label) >= (other.priority, other.label)
+
     def __eq__(self, other):
         if not isinstance(other, Button):
             return NotImplemented

{"golden_diff": "diff --git a/wagtail/admin/search.py b/wagtail/admin/search.py\n--- a/wagtail/admin/search.py\n+++ b/wagtail/admin/search.py\n@@ -1,5 +1,3 @@\n-from functools import total_ordering\n-\n from django.forms import Media, MediaDefiningClass\n from django.forms.utils import flatatt\n from django.template.loader import render_to_string\n@@ -11,7 +9,6 @@\n from wagtail.admin.forms.search import SearchForm\n \n \n-@total_ordering\n class SearchArea(metaclass=MediaDefiningClass):\n     template = \"wagtailadmin/shared/search_area.html\"\n \n@@ -31,9 +28,28 @@\n             self.attr_string = \"\"\n \n     def __lt__(self, other):\n+        if not isinstance(other, SearchArea):\n+            return NotImplemented\n         return (self.order, self.label) < (other.order, other.label)\n \n+    def __le__(self, other):\n+        if not isinstance(other, SearchArea):\n+            return NotImplemented\n+        return (self.order, self.label) <= (other.order, other.label)\n+\n+    def __gt__(self, other):\n+        if not isinstance(other, SearchArea):\n+            return NotImplemented\n+        return (self.order, self.label) > (other.order, other.label)\n+\n+    def __ge__(self, other):\n+        if not isinstance(other, SearchArea):\n+            return NotImplemented\n+        return (self.order, self.label) >= (other.order, other.label)\n+\n     def __eq__(self, other):\n+        if not isinstance(other, SearchArea):\n+            return NotImplemented\n         return (self.order, self.label) == (other.order, other.label)\n \n     def is_shown(self, request):\ndiff --git a/wagtail/admin/widgets/button.py b/wagtail/admin/widgets/button.py\n--- a/wagtail/admin/widgets/button.py\n+++ b/wagtail/admin/widgets/button.py\n@@ -1,5 +1,3 @@\n-from functools import total_ordering\n-\n from django.forms.utils import flatatt\n from django.template.loader import render_to_string\n from django.utils.functional import cached_property\n@@ -8,7 +6,6 @@\n from wagtail import hooks\n \n \n-@total_ordering\n class Button:\n     show = True\n \n@@ -42,6 +39,21 @@\n             return NotImplemented\n         return (self.priority, self.label) < (other.priority, other.label)\n \n+    def __le__(self, other):\n+        if not isinstance(other, Button):\n+            return NotImplemented\n+        return (self.priority, self.label) <= (other.priority, other.label)\n+\n+    def __gt__(self, other):\n+        if not isinstance(other, Button):\n+            return NotImplemented\n+        return (self.priority, self.label) > (other.priority, other.label)\n+\n+    def __ge__(self, other):\n+        if not isinstance(other, Button):\n+            return NotImplemented\n+        return (self.priority, self.label) >= (other.priority, other.label)\n+\n     def __eq__(self, other):\n         if not isinstance(other, Button):\n             return NotImplemented\n", "issue": "Replace `total_ordering` usage with comparison functions implementation\n### Is your proposal related to a problem?\r\n\r\nWe have two instances of `total_ordering` usage within the codebase:\r\n\r\nhttps://github.com/wagtail/wagtail/blob/cd5200c8e1ac0d7299fd9c398b2b994606b3c7d2/wagtail/admin/search.py#L12-L13\r\n\r\nhttps://github.com/wagtail/wagtail/blob/cd5200c8e1ac0d7299fd9c398b2b994606b3c7d2/wagtail/admin/widgets/button.py#L11-L12\r\n\r\nEven though it's convenient, `total_ordering` is known to be slow. According to [Python's docs](https://docs.python.org/3/library/functools.html#functools.total_ordering):\r\n\r\n> **Note**\r\n> While this decorator makes it easy to create well behaved totally ordered types, it does come at the cost of slower execution and more complex stack traces for the derived comparison methods. If performance benchmarking indicates this is a bottleneck for a given application, implementing all six rich comparison methods instead is likely to provide an easy speed boost.\r\n\r\nDjango recently removed their usage of `total_ordering` in https://github.com/django/django/pull/16958/commits/ee36e101e8f8c0acde4bb148b738ab7034e902a0 (probably not all usages, I haven't checked).\r\n\r\n### Describe the solution you'd like\r\n\r\n<!--\r\n  Provide a clear and concise description of what you want to happen.\r\n-->\r\n\r\nReplace `total_ordering` with implementations of `__eq__()`, `__ne__()`, `__lt__()`, `__le__()`, `__gt__()`, and `__ge__()`.\r\n\r\n### Describe alternatives you've considered\r\n\r\n<!--\r\n  Let us know about other solutions you've tried or researched.\r\n-->\r\n\r\nKeep using `total_ordering`\r\n\r\n### Additional context\r\n\r\nI found this while fixing an incorrect import of `total_ordering` in #10525.\r\n\n", "before_files": [{"content": "from functools import total_ordering\n\nfrom django.forms.utils import flatatt\nfrom django.template.loader import render_to_string\nfrom django.utils.functional import cached_property\nfrom django.utils.html import format_html\n\nfrom wagtail import hooks\n\n\n@total_ordering\nclass Button:\n    show = True\n\n    def __init__(\n        self, label, url, classes=set(), icon_name=None, attrs={}, priority=1000\n    ):\n        self.label = label\n        self.url = url\n        self.classes = classes\n        self.icon_name = icon_name\n        self.attrs = attrs.copy()\n        self.priority = priority\n\n    def render(self):\n        attrs = {\n            \"href\": self.url,\n            \"class\": \" \".join(sorted(self.classes)),\n            \"title\": self.label,\n        }\n        attrs.update(self.attrs)\n        return format_html(\"<a{}>{}</a>\", flatatt(attrs), self.label)\n\n    def __str__(self):\n        return self.render()\n\n    def __repr__(self):\n        return f\"<Button: {self.label}>\"\n\n    def __lt__(self, other):\n        if not isinstance(other, Button):\n            return NotImplemented\n        return (self.priority, self.label) < (other.priority, other.label)\n\n    def __eq__(self, other):\n        if not isinstance(other, Button):\n            return NotImplemented\n        return (\n            self.label == other.label\n            and self.url == other.url\n            and self.classes == other.classes\n            and self.attrs == other.attrs\n            and self.priority == other.priority\n        )\n\n\n# Base class for all listing buttons\n# This is also used by SnippetListingButton defined in wagtail.snippets.widgets\nclass ListingButton(Button):\n    def __init__(self, label, url, classes=set(), **kwargs):\n        classes = {\"button\", \"button-small\", \"button-secondary\"} | set(classes)\n        super().__init__(label, url, classes=classes, **kwargs)\n\n\nclass PageListingButton(ListingButton):\n    pass\n\n\nclass BaseDropdownMenuButton(Button):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, url=None, **kwargs)\n\n    @cached_property\n    def dropdown_buttons(self):\n        raise NotImplementedError\n\n    def get_context_data(self):\n        return {\n            \"buttons\": self.dropdown_buttons,\n            \"label\": self.label,\n            \"title\": self.attrs.get(\"title\"),\n            \"classes\": self.classes,\n        }\n\n    def render(self):\n        return render_to_string(self.template_name, self.get_context_data())\n\n\nclass ButtonWithDropdown(BaseDropdownMenuButton):\n    template_name = \"wagtailadmin/pages/listing/_button_with_dropdown.html\"\n\n    def __init__(self, *args, **kwargs):\n        self.button_classes = kwargs.pop(\"button_classes\", set())\n        self.buttons_data = kwargs.pop(\"buttons_data\", [])\n        super().__init__(*args, **kwargs)\n\n    def get_context_data(self):\n        context = super().get_context_data()\n        context[\"button_classes\"] = self.button_classes\n        return context\n\n    @cached_property\n    def dropdown_buttons(self):\n        return [Button(**button) for button in self.buttons_data]\n\n\nclass ButtonWithDropdownFromHook(BaseDropdownMenuButton):\n    template_name = \"wagtailadmin/pages/listing/_button_with_dropdown.html\"\n\n    def __init__(self, label, hook_name, page, page_perms, next_url=None, **kwargs):\n        self.hook_name = hook_name\n        self.page = page\n        self.page_perms = page_perms\n        self.next_url = next_url\n\n        super().__init__(label, **kwargs)\n\n    @property\n    def show(self):\n        return bool(self.dropdown_buttons)\n\n    @cached_property\n    def dropdown_buttons(self):\n        button_hooks = hooks.get_hooks(self.hook_name)\n\n        buttons = []\n        for hook in button_hooks:\n            buttons.extend(hook(self.page, self.page_perms, self.next_url))\n\n        buttons.sort()\n        return buttons\n", "path": "wagtail/admin/widgets/button.py"}, {"content": "from functools import total_ordering\n\nfrom django.forms import Media, MediaDefiningClass\nfrom django.forms.utils import flatatt\nfrom django.template.loader import render_to_string\nfrom django.utils.functional import cached_property\nfrom django.utils.safestring import mark_safe\nfrom django.utils.text import slugify\n\nfrom wagtail import hooks\nfrom wagtail.admin.forms.search import SearchForm\n\n\n@total_ordering\nclass SearchArea(metaclass=MediaDefiningClass):\n    template = \"wagtailadmin/shared/search_area.html\"\n\n    def __init__(\n        self, label, url, name=None, classnames=\"\", icon_name=\"\", attrs=None, order=1000\n    ):\n        self.label = label\n        self.url = url\n        self.classnames = classnames\n        self.icon_name = icon_name\n        self.name = name or slugify(str(label))\n        self.order = order\n\n        if attrs:\n            self.attr_string = flatatt(attrs)\n        else:\n            self.attr_string = \"\"\n\n    def __lt__(self, other):\n        return (self.order, self.label) < (other.order, other.label)\n\n    def __eq__(self, other):\n        return (self.order, self.label) == (other.order, other.label)\n\n    def is_shown(self, request):\n        \"\"\"\n        Whether this search area should be shown for the given request; permission\n        checks etc should go here. By default, search areas are shown all the time\n        \"\"\"\n        return True\n\n    def is_active(self, request, current=None):\n        if current is None:\n            return request.path.startswith(self.url)\n        else:\n            return self.name == current\n\n    def render_html(self, request, query, current=None):\n        return render_to_string(\n            self.template,\n            {\n                \"name\": self.name,\n                \"url\": self.url,\n                \"classnames\": self.classnames,\n                \"icon_name\": self.icon_name,\n                \"attr_string\": self.attr_string,\n                \"label\": self.label,\n                \"active\": self.is_active(request, current),\n                \"query_string\": query,\n            },\n            request=request,\n        )\n\n\nclass Search:\n    def __init__(self, register_hook_name, construct_hook_name=None):\n        self.register_hook_name = register_hook_name\n        self.construct_hook_name = construct_hook_name\n\n    @cached_property\n    def registered_search_areas(self):\n        return sorted([fn() for fn in hooks.get_hooks(self.register_hook_name)])\n\n    def search_items_for_request(self, request):\n        return [item for item in self.registered_search_areas if item.is_shown(request)]\n\n    def active_search(self, request, current=None):\n        return [\n            item\n            for item in self.search_items_for_request(request)\n            if item.is_active(request, current)\n        ]\n\n    @property\n    def media(self):\n        media = Media()\n        for item in self.registered_search_areas:\n            media += item.media\n        return media\n\n    def render_html(self, request, current=None):\n        search_areas = self.search_items_for_request(request)\n\n        # Get query parameter\n        form = SearchForm(request.GET)\n        query = \"\"\n        if form.is_valid():\n            query = form.cleaned_data[\"q\"]\n\n        # provide a hook for modifying the search area, if construct_hook_name has been set\n        if self.construct_hook_name:\n            for fn in hooks.get_hooks(self.construct_hook_name):\n                fn(request, search_areas)\n\n        rendered_search_areas = []\n        for item in search_areas:\n            rendered_search_areas.append(item.render_html(request, query, current))\n\n        return mark_safe(\"\".join(rendered_search_areas))\n\n\nadmin_search_areas = Search(\n    register_hook_name=\"register_admin_search_area\",\n    construct_hook_name=\"construct_search\",\n)\n", "path": "wagtail/admin/search.py"}], "after_files": [{"content": "from django.forms.utils import flatatt\nfrom django.template.loader import render_to_string\nfrom django.utils.functional import cached_property\nfrom django.utils.html import format_html\n\nfrom wagtail import hooks\n\n\nclass Button:\n    show = True\n\n    def __init__(\n        self, label, url, classes=set(), icon_name=None, attrs={}, priority=1000\n    ):\n        self.label = label\n        self.url = url\n        self.classes = classes\n        self.icon_name = icon_name\n        self.attrs = attrs.copy()\n        self.priority = priority\n\n    def render(self):\n        attrs = {\n            \"href\": self.url,\n            \"class\": \" \".join(sorted(self.classes)),\n            \"title\": self.label,\n        }\n        attrs.update(self.attrs)\n        return format_html(\"<a{}>{}</a>\", flatatt(attrs), self.label)\n\n    def __str__(self):\n        return self.render()\n\n    def __repr__(self):\n        return f\"<Button: {self.label}>\"\n\n    def __lt__(self, other):\n        if not isinstance(other, Button):\n            return NotImplemented\n        return (self.priority, self.label) < (other.priority, other.label)\n\n    def __le__(self, other):\n        if not isinstance(other, Button):\n            return NotImplemented\n        return (self.priority, self.label) <= (other.priority, other.label)\n\n    def __gt__(self, other):\n        if not isinstance(other, Button):\n            return NotImplemented\n        return (self.priority, self.label) > (other.priority, other.label)\n\n    def __ge__(self, other):\n        if not isinstance(other, Button):\n            return NotImplemented\n        return (self.priority, self.label) >= (other.priority, other.label)\n\n    def __eq__(self, other):\n        if not isinstance(other, Button):\n            return NotImplemented\n        return (\n            self.label == other.label\n            and self.url == other.url\n            and self.classes == other.classes\n            and self.attrs == other.attrs\n            and self.priority == other.priority\n        )\n\n\n# Base class for all listing buttons\n# This is also used by SnippetListingButton defined in wagtail.snippets.widgets\nclass ListingButton(Button):\n    def __init__(self, label, url, classes=set(), **kwargs):\n        classes = {\"button\", \"button-small\", \"button-secondary\"} | set(classes)\n        super().__init__(label, url, classes=classes, **kwargs)\n\n\nclass PageListingButton(ListingButton):\n    pass\n\n\nclass BaseDropdownMenuButton(Button):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, url=None, **kwargs)\n\n    @cached_property\n    def dropdown_buttons(self):\n        raise NotImplementedError\n\n    def get_context_data(self):\n        return {\n            \"buttons\": self.dropdown_buttons,\n            \"label\": self.label,\n            \"title\": self.attrs.get(\"title\"),\n            \"classes\": self.classes,\n        }\n\n    def render(self):\n        return render_to_string(self.template_name, self.get_context_data())\n\n\nclass ButtonWithDropdown(BaseDropdownMenuButton):\n    template_name = \"wagtailadmin/pages/listing/_button_with_dropdown.html\"\n\n    def __init__(self, *args, **kwargs):\n        self.button_classes = kwargs.pop(\"button_classes\", set())\n        self.buttons_data = kwargs.pop(\"buttons_data\", [])\n        super().__init__(*args, **kwargs)\n\n    def get_context_data(self):\n        context = super().get_context_data()\n        context[\"button_classes\"] = self.button_classes\n        return context\n\n    @cached_property\n    def dropdown_buttons(self):\n        return [Button(**button) for button in self.buttons_data]\n\n\nclass ButtonWithDropdownFromHook(BaseDropdownMenuButton):\n    template_name = \"wagtailadmin/pages/listing/_button_with_dropdown.html\"\n\n    def __init__(self, label, hook_name, page, page_perms, next_url=None, **kwargs):\n        self.hook_name = hook_name\n        self.page = page\n        self.page_perms = page_perms\n        self.next_url = next_url\n\n        super().__init__(label, **kwargs)\n\n    @property\n    def show(self):\n        return bool(self.dropdown_buttons)\n\n    @cached_property\n    def dropdown_buttons(self):\n        button_hooks = hooks.get_hooks(self.hook_name)\n\n        buttons = []\n        for hook in button_hooks:\n            buttons.extend(hook(self.page, self.page_perms, self.next_url))\n\n        buttons.sort()\n        return buttons\n", "path": "wagtail/admin/widgets/button.py"}, {"content": "from django.forms import Media, MediaDefiningClass\nfrom django.forms.utils import flatatt\nfrom django.template.loader import render_to_string\nfrom django.utils.functional import cached_property\nfrom django.utils.safestring import mark_safe\nfrom django.utils.text import slugify\n\nfrom wagtail import hooks\nfrom wagtail.admin.forms.search import SearchForm\n\n\nclass SearchArea(metaclass=MediaDefiningClass):\n    template = \"wagtailadmin/shared/search_area.html\"\n\n    def __init__(\n        self, label, url, name=None, classnames=\"\", icon_name=\"\", attrs=None, order=1000\n    ):\n        self.label = label\n        self.url = url\n        self.classnames = classnames\n        self.icon_name = icon_name\n        self.name = name or slugify(str(label))\n        self.order = order\n\n        if attrs:\n            self.attr_string = flatatt(attrs)\n        else:\n            self.attr_string = \"\"\n\n    def __lt__(self, other):\n        if not isinstance(other, SearchArea):\n            return NotImplemented\n        return (self.order, self.label) < (other.order, other.label)\n\n    def __le__(self, other):\n        if not isinstance(other, SearchArea):\n            return NotImplemented\n        return (self.order, self.label) <= (other.order, other.label)\n\n    def __gt__(self, other):\n        if not isinstance(other, SearchArea):\n            return NotImplemented\n        return (self.order, self.label) > (other.order, other.label)\n\n    def __ge__(self, other):\n        if not isinstance(other, SearchArea):\n            return NotImplemented\n        return (self.order, self.label) >= (other.order, other.label)\n\n    def __eq__(self, other):\n        if not isinstance(other, SearchArea):\n            return NotImplemented\n        return (self.order, self.label) == (other.order, other.label)\n\n    def is_shown(self, request):\n        \"\"\"\n        Whether this search area should be shown for the given request; permission\n        checks etc should go here. By default, search areas are shown all the time\n        \"\"\"\n        return True\n\n    def is_active(self, request, current=None):\n        if current is None:\n            return request.path.startswith(self.url)\n        else:\n            return self.name == current\n\n    def render_html(self, request, query, current=None):\n        return render_to_string(\n            self.template,\n            {\n                \"name\": self.name,\n                \"url\": self.url,\n                \"classnames\": self.classnames,\n                \"icon_name\": self.icon_name,\n                \"attr_string\": self.attr_string,\n                \"label\": self.label,\n                \"active\": self.is_active(request, current),\n                \"query_string\": query,\n            },\n            request=request,\n        )\n\n\nclass Search:\n    def __init__(self, register_hook_name, construct_hook_name=None):\n        self.register_hook_name = register_hook_name\n        self.construct_hook_name = construct_hook_name\n\n    @cached_property\n    def registered_search_areas(self):\n        return sorted([fn() for fn in hooks.get_hooks(self.register_hook_name)])\n\n    def search_items_for_request(self, request):\n        return [item for item in self.registered_search_areas if item.is_shown(request)]\n\n    def active_search(self, request, current=None):\n        return [\n            item\n            for item in self.search_items_for_request(request)\n            if item.is_active(request, current)\n        ]\n\n    @property\n    def media(self):\n        media = Media()\n        for item in self.registered_search_areas:\n            media += item.media\n        return media\n\n    def render_html(self, request, current=None):\n        search_areas = self.search_items_for_request(request)\n\n        # Get query parameter\n        form = SearchForm(request.GET)\n        query = \"\"\n        if form.is_valid():\n            query = form.cleaned_data[\"q\"]\n\n        # provide a hook for modifying the search area, if construct_hook_name has been set\n        if self.construct_hook_name:\n            for fn in hooks.get_hooks(self.construct_hook_name):\n                fn(request, search_areas)\n\n        rendered_search_areas = []\n        for item in search_areas:\n            rendered_search_areas.append(item.render_html(request, query, current))\n\n        return mark_safe(\"\".join(rendered_search_areas))\n\n\nadmin_search_areas = Search(\n    register_hook_name=\"register_admin_search_area\",\n    construct_hook_name=\"construct_search\",\n)\n", "path": "wagtail/admin/search.py"}]}