We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
wagtail 4.0rc1 migration ValueError: Could not find object DocumentChooserBlock
### Issue Summary

```
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, core, custom_documents, custom_images, custom_users, home, ...,
  No migrations to apply.
Traceback (most recent call last):
  File "django/./manage.py", line 12, in <module>
    execute_from_command_line(sys.argv)
  File ".venv/lib/python3.10/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
    utility.execute()
  File ".venv/lib/python3.10/site-packages/django/core/management/__init__.py", line 413, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File ".venv/lib/python3.10/site-packages/django/core/management/base.py", line 354, in run_from_argv
    self.execute(*args, **cmd_options)
  File ".venv/lib/python3.10/site-packages/django/core/management/base.py", line 398, in execute
    output = self.handle(*args, **options)
  File ".venv/lib/python3.10/site-packages/django/core/management/base.py", line 89, in wrapped
    res = handle_func(*args, **kwargs)
  File ".venv/lib/python3.10/site-packages/django/core/management/commands/migrate.py", line 227, in handle
    changes = autodetector.changes(graph=executor.loader.graph)
  File ".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py", line 41, in changes
    changes = self._detect_changes(convert_apps, graph)
  File ".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py", line 184, in _detect_changes
    self.generate_altered_fields()
  File ".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py", line 964, in generate_altered_fields
    old_field_dec = self.deep_deconstruct(old_field)
  File ".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py", line 78, in deep_deconstruct
    [self.deep_deconstruct(value) for value in args],
  File ".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py", line 78, in <listcomp>
    [self.deep_deconstruct(value) for value in args],
  File ".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py", line 54, in deep_deconstruct
    return [self.deep_deconstruct(value) for value in obj]
  File ".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py", line 54, in <listcomp>
    return [self.deep_deconstruct(value) for value in obj]
  File ".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py", line 56, in deep_deconstruct
    return tuple(self.deep_deconstruct(value) for value in obj)
  File ".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py", line 56, in <genexpr>
    return tuple(self.deep_deconstruct(value) for value in obj)
  File ".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py", line 78, in deep_deconstruct
    [self.deep_deconstruct(value) for value in args],
  File ".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py", line 78, in <listcomp>
    [self.deep_deconstruct(value) for value in args],
  File ".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py", line 54, in deep_deconstruct
    return [self.deep_deconstruct(value) for value in obj]
  File ".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py", line 54, in <listcomp>
    return [self.deep_deconstruct(value) for value in obj]
  File ".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py", line 56, in deep_deconstruct
    return tuple(self.deep_deconstruct(value) for value in obj)
  File ".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py", line 56, in <genexpr>
    return tuple(self.deep_deconstruct(value) for value in obj)
  File ".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py", line 71, in deep_deconstruct
    deconstructed = obj.deconstruct()
  File ".venv/lib/python3.10/site-packages/wagtail/blocks/base.py", line 344, in deconstruct
    raise ValueError(
ValueError: Could not find object CustomDocumentChooserBlock in wagtail.blocks.base.
Please note that you cannot serialize things like inner classes. Please move the object into the main module body to use migrations.
```

I'm using a custom document model (`WAGTAILDOCS_DOCUMENT_MODEL = "custom_documents.CustomDocument"`) so I removed `WAGTAILDOCS_DOCUMENT_MODEL` setting it see if the error persisted and when running `./manage.py migrate` the ValueError changes to:

```
ValueError: Could not find object DocumentChooserBlock in wagtail.blocks.base.
```

`CustomDocumentChooserBlock` -> `DocumentChooserBlock` 


### Steps to Reproduce

1. run `./manage.py migrate` or `./manage.py makemigrations`

-  I couldn't reproduce this on a fresh install of wagtail 4.0rc1 however I have been unable to find a cause for this error in my project so far.

### Technical details

-   Python version: 3.10.5
-   Django version: 3.2.15
-   Wagtail version: 4.0rc1
-
Ensure DocumentChooserBlock can be deconstructed for migrations
Fixes #8989. Now that DocumentChooserBlock is constructed dynamically via wagtail.documents.viewsets.chooser, we need to explicitly set its `__module__` attribute so that the result of calling `deconstruct()` for migrations points back to the wagtail.documents.blocks module.

Also update the documentation for defining custom choosers, and add tests for deconstructing the other chooser blocks.


--- 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/documents/blocks.py`
Content:
```
1 from wagtail.documents.views.chooser import viewset as chooser_viewset
2 
3 DocumentChooserBlock = chooser_viewset.block_class
4 
5 # When deconstructing a DocumentChooserBlock instance for migrations, the module path
6 # used in migrations should point to this module
7 DocumentChooserBlock.__module__ = "wagtail.documents.blocks"
8
```

Path: `wagtail/admin/viewsets/chooser.py`
Content:
```
1 from django.db.models import ForeignKey
2 from django.urls import path
3 from django.utils.functional import cached_property
4 from django.utils.translation import gettext as _
5 
6 from wagtail.admin.forms.models import register_form_field_override
7 from wagtail.admin.views.generic import chooser as chooser_views
8 from wagtail.admin.widgets.chooser import BaseChooser
9 from wagtail.blocks import ChooserBlock
10 
11 from .base import ViewSet
12 
13 
14 class ChooserViewSet(ViewSet):
15     """
16     A viewset that creates a chooser modal interface for choosing model instances.
17     """
18 
19     model = None
20 
21     icon = "snippet"  #: The icon to use in the header of the chooser modal, and on the chooser widget
22     choose_one_text = _(
23         "Choose"
24     )  #: Label for the 'choose' button in the chooser widget when choosing an initial item
25     page_title = None  #: Title text for the chooser modal (defaults to the same as ``choose_one_text``)`
26     choose_another_text = _(
27         "Change"
28     )  #: Label for the 'choose' button in the chooser widget, when an item has already been chosen
29     edit_item_text = _("Edit")  #: Label for the 'edit' button in the chooser widget
30 
31     per_page = 10  #: Number of results to show per page
32 
33     #: The view class to use for the overall chooser modal; must be a subclass of ``wagtail.admin.views.generic.chooser.ChooseView``.
34     choose_view_class = chooser_views.ChooseView
35 
36     #: The view class used to render just the results panel within the chooser modal; must be a subclass of ``wagtail.admin.views.generic.chooser.ChooseResultsView``.
37     choose_results_view_class = chooser_views.ChooseResultsView
38 
39     #: The view class used after an item has been chosen; must be a subclass of ``wagtail.admin.views.generic.chooser.ChosenView``.
40     chosen_view_class = chooser_views.ChosenView
41 
42     #: The view class used to handle submissions of the 'create' form; must be a subclass of ``wagtail.admin.views.generic.chooser.CreateView``.
43     create_view_class = chooser_views.CreateView
44 
45     #: The base Widget class that the chooser widget will be derived from.
46     base_widget_class = BaseChooser
47 
48     #: The base ChooserBlock class that the StreamField chooser block will be derived from.
49     base_block_class = ChooserBlock
50 
51     #: Defaults to True; if False, the chooser widget will not automatically be registered for use in admin forms.
52     register_widget = True
53 
54     #: Form class to use for the form in the "Create" tab of the modal.
55     creation_form_class = None
56 
57     #: List of model fields that should be included in the creation form, if creation_form_class is not specified.
58     form_fields = None
59 
60     #: List of model fields that should be excluded from the creation form, if creation_form_class.
61     #: If none of ``creation_form_class``, ``form_fields`` or ``exclude_form_fields`` are specified, the "Create" tab will be omitted.
62     exclude_form_fields = None
63 
64     search_tab_label = _("Search")  #: Label for the 'search' tab in the chooser modal
65     create_action_label = _(
66         "Create"
67     )  #: Label for the submit button on the 'create' form
68     create_action_clicked_label = None  #: Alternative text to display on the submit button after it has been clicked
69     creation_tab_label = None  #: Label for the 'create' tab in the chooser modal (defaults to the same as create_action_label)
70 
71     permission_policy = None
72 
73     def __init__(self, *args, **kwargs):
74         super().__init__(*args, **kwargs)
75         if self.page_title is None:
76             self.page_title = self.choose_one_text
77 
78     @property
79     def choose_view(self):
80         return self.choose_view_class.as_view(
81             model=self.model,
82             chosen_url_name=self.get_url_name("chosen"),
83             results_url_name=self.get_url_name("choose_results"),
84             create_url_name=self.get_url_name("create"),
85             icon=self.icon,
86             page_title=self.page_title,
87             per_page=self.per_page,
88             creation_form_class=self.creation_form_class,
89             form_fields=self.form_fields,
90             exclude_form_fields=self.exclude_form_fields,
91             search_tab_label=self.search_tab_label,
92             creation_tab_label=self.creation_tab_label,
93             create_action_label=self.create_action_label,
94             create_action_clicked_label=self.create_action_clicked_label,
95             permission_policy=self.permission_policy,
96         )
97 
98     @property
99     def choose_results_view(self):
100         return self.choose_results_view_class.as_view(
101             model=self.model,
102             chosen_url_name=self.get_url_name("chosen"),
103             results_url_name=self.get_url_name("choose_results"),
104             per_page=self.per_page,
105             creation_form_class=self.creation_form_class,
106             form_fields=self.form_fields,
107             exclude_form_fields=self.exclude_form_fields,
108             create_action_label=self.create_action_label,
109             create_action_clicked_label=self.create_action_clicked_label,
110             permission_policy=self.permission_policy,
111         )
112 
113     @property
114     def chosen_view(self):
115         return self.chosen_view_class.as_view(
116             model=self.model,
117         )
118 
119     @property
120     def create_view(self):
121         return self.create_view_class.as_view(
122             model=self.model,
123             create_url_name=self.get_url_name("create"),
124             creation_form_class=self.creation_form_class,
125             form_fields=self.form_fields,
126             exclude_form_fields=self.exclude_form_fields,
127             create_action_label=self.create_action_label,
128             create_action_clicked_label=self.create_action_clicked_label,
129             permission_policy=self.permission_policy,
130         )
131 
132     @cached_property
133     def model_name(self):
134         if isinstance(self.model, str):
135             return self.model.split(".")[-1]
136         else:
137             return self.model.__name__
138 
139     @cached_property
140     def widget_class(self):
141         """
142         Returns the form widget class for this chooser.
143         """
144         if self.model is None:
145             widget_class_name = "ChooserWidget"
146         else:
147             if isinstance(self.model, str):
148                 model_name = self.model.split(".")[-1]
149             else:
150                 model_name = self.model.__name__
151             widget_class_name = "%sChooserWidget" % model_name
152 
153         return type(
154             widget_class_name,
155             (self.base_widget_class,),
156             {
157                 "model": self.model,
158                 "choose_one_text": self.choose_one_text,
159                 "choose_another_text": self.choose_another_text,
160                 "link_to_chosen_text": self.edit_item_text,
161                 "chooser_modal_url_name": self.get_url_name("choose"),
162                 "icon": self.icon,
163             },
164         )
165 
166     @cached_property
167     def block_class(self):
168         """
169         Returns a StreamField ChooserBlock class using this chooser.
170         """
171         meta = type(
172             "Meta",
173             (self.base_block_class._meta_class,),
174             {
175                 "icon": self.icon,
176             },
177         )
178         return type(
179             "%sChooserBlock" % self.model_name,
180             (self.base_block_class,),
181             {
182                 "target_model": self.model,
183                 "widget": self.widget_class(),
184                 "Meta": meta,
185             },
186         )
187 
188     def get_urlpatterns(self):
189         return super().get_urlpatterns() + [
190             path("", self.choose_view, name="choose"),
191             path("results/", self.choose_results_view, name="choose_results"),
192             path("chosen/<str:pk>/", self.chosen_view, name="chosen"),
193             path("create/", self.create_view, name="create"),
194         ]
195 
196     def on_register(self):
197         if self.model and self.register_widget:
198             register_form_field_override(
199                 ForeignKey, to=self.model, override={"widget": self.widget_class}
200             )
201
```
--- 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/viewsets/chooser.py b/wagtail/admin/viewsets/chooser.py
--- a/wagtail/admin/viewsets/chooser.py
+++ b/wagtail/admin/viewsets/chooser.py
@@ -163,10 +163,13 @@
             },
         )
 
-    @cached_property
-    def block_class(self):
+    def get_block_class(self, name=None, module_path=None):
         """
         Returns a StreamField ChooserBlock class using this chooser.
+
+        :param name: Name to give to the class; defaults to the model name with "ChooserBlock" appended
+        :param module_path: The dotted path of the module where the class can be imported from; used when
+            deconstructing the block definition for migration files.
         """
         meta = type(
             "Meta",
@@ -175,8 +178,8 @@
                 "icon": self.icon,
             },
         )
-        return type(
-            "%sChooserBlock" % self.model_name,
+        cls = type(
+            name or "%sChooserBlock" % self.model_name,
             (self.base_block_class,),
             {
                 "target_model": self.model,
@@ -184,6 +187,9 @@
                 "Meta": meta,
             },
         )
+        if module_path:
+            cls.__module__ = module_path
+        return cls
 
     def get_urlpatterns(self):
         return super().get_urlpatterns() + [
diff --git a/wagtail/documents/blocks.py b/wagtail/documents/blocks.py
--- a/wagtail/documents/blocks.py
+++ b/wagtail/documents/blocks.py
@@ -1,7 +1,5 @@
 from wagtail.documents.views.chooser import viewset as chooser_viewset
 
-DocumentChooserBlock = chooser_viewset.block_class
-
-# When deconstructing a DocumentChooserBlock instance for migrations, the module path
-# used in migrations should point to this module
-DocumentChooserBlock.__module__ = "wagtail.documents.blocks"
+DocumentChooserBlock = chooser_viewset.get_block_class(
+    name="DocumentChooserBlock", module_path="wagtail.documents.blocks"
+)

{"golden_diff": "diff --git a/wagtail/admin/viewsets/chooser.py b/wagtail/admin/viewsets/chooser.py\n--- a/wagtail/admin/viewsets/chooser.py\n+++ b/wagtail/admin/viewsets/chooser.py\n@@ -163,10 +163,13 @@\n             },\n         )\n \n-    @cached_property\n-    def block_class(self):\n+    def get_block_class(self, name=None, module_path=None):\n         \"\"\"\n         Returns a StreamField ChooserBlock class using this chooser.\n+\n+        :param name: Name to give to the class; defaults to the model name with \"ChooserBlock\" appended\n+        :param module_path: The dotted path of the module where the class can be imported from; used when\n+            deconstructing the block definition for migration files.\n         \"\"\"\n         meta = type(\n             \"Meta\",\n@@ -175,8 +178,8 @@\n                 \"icon\": self.icon,\n             },\n         )\n-        return type(\n-            \"%sChooserBlock\" % self.model_name,\n+        cls = type(\n+            name or \"%sChooserBlock\" % self.model_name,\n             (self.base_block_class,),\n             {\n                 \"target_model\": self.model,\n@@ -184,6 +187,9 @@\n                 \"Meta\": meta,\n             },\n         )\n+        if module_path:\n+            cls.__module__ = module_path\n+        return cls\n \n     def get_urlpatterns(self):\n         return super().get_urlpatterns() + [\ndiff --git a/wagtail/documents/blocks.py b/wagtail/documents/blocks.py\n--- a/wagtail/documents/blocks.py\n+++ b/wagtail/documents/blocks.py\n@@ -1,7 +1,5 @@\n from wagtail.documents.views.chooser import viewset as chooser_viewset\n \n-DocumentChooserBlock = chooser_viewset.block_class\n-\n-# When deconstructing a DocumentChooserBlock instance for migrations, the module path\n-# used in migrations should point to this module\n-DocumentChooserBlock.__module__ = \"wagtail.documents.blocks\"\n+DocumentChooserBlock = chooser_viewset.get_block_class(\n+    name=\"DocumentChooserBlock\", module_path=\"wagtail.documents.blocks\"\n+)\n", "issue": "wagtail 4.0rc1 migration ValueError: Could not find object DocumentChooserBlock\n### Issue Summary\r\n\r\n```\r\nOperations to perform:\r\n  Apply all migrations: admin, auth, contenttypes, core, custom_documents, custom_images, custom_users, home, ...,\r\n  No migrations to apply.\r\nTraceback (most recent call last):\r\n  File \"django/./manage.py\", line 12, in <module>\r\n    execute_from_command_line(sys.argv)\r\n  File \".venv/lib/python3.10/site-packages/django/core/management/__init__.py\", line 419, in execute_from_command_line\r\n    utility.execute()\r\n  File \".venv/lib/python3.10/site-packages/django/core/management/__init__.py\", line 413, in execute\r\n    self.fetch_command(subcommand).run_from_argv(self.argv)\r\n  File \".venv/lib/python3.10/site-packages/django/core/management/base.py\", line 354, in run_from_argv\r\n    self.execute(*args, **cmd_options)\r\n  File \".venv/lib/python3.10/site-packages/django/core/management/base.py\", line 398, in execute\r\n    output = self.handle(*args, **options)\r\n  File \".venv/lib/python3.10/site-packages/django/core/management/base.py\", line 89, in wrapped\r\n    res = handle_func(*args, **kwargs)\r\n  File \".venv/lib/python3.10/site-packages/django/core/management/commands/migrate.py\", line 227, in handle\r\n    changes = autodetector.changes(graph=executor.loader.graph)\r\n  File \".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py\", line 41, in changes\r\n    changes = self._detect_changes(convert_apps, graph)\r\n  File \".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py\", line 184, in _detect_changes\r\n    self.generate_altered_fields()\r\n  File \".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py\", line 964, in generate_altered_fields\r\n    old_field_dec = self.deep_deconstruct(old_field)\r\n  File \".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py\", line 78, in deep_deconstruct\r\n    [self.deep_deconstruct(value) for value in args],\r\n  File \".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py\", line 78, in <listcomp>\r\n    [self.deep_deconstruct(value) for value in args],\r\n  File \".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py\", line 54, in deep_deconstruct\r\n    return [self.deep_deconstruct(value) for value in obj]\r\n  File \".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py\", line 54, in <listcomp>\r\n    return [self.deep_deconstruct(value) for value in obj]\r\n  File \".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py\", line 56, in deep_deconstruct\r\n    return tuple(self.deep_deconstruct(value) for value in obj)\r\n  File \".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py\", line 56, in <genexpr>\r\n    return tuple(self.deep_deconstruct(value) for value in obj)\r\n  File \".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py\", line 78, in deep_deconstruct\r\n    [self.deep_deconstruct(value) for value in args],\r\n  File \".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py\", line 78, in <listcomp>\r\n    [self.deep_deconstruct(value) for value in args],\r\n  File \".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py\", line 54, in deep_deconstruct\r\n    return [self.deep_deconstruct(value) for value in obj]\r\n  File \".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py\", line 54, in <listcomp>\r\n    return [self.deep_deconstruct(value) for value in obj]\r\n  File \".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py\", line 56, in deep_deconstruct\r\n    return tuple(self.deep_deconstruct(value) for value in obj)\r\n  File \".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py\", line 56, in <genexpr>\r\n    return tuple(self.deep_deconstruct(value) for value in obj)\r\n  File \".venv/lib/python3.10/site-packages/django/db/migrations/autodetector.py\", line 71, in deep_deconstruct\r\n    deconstructed = obj.deconstruct()\r\n  File \".venv/lib/python3.10/site-packages/wagtail/blocks/base.py\", line 344, in deconstruct\r\n    raise ValueError(\r\nValueError: Could not find object CustomDocumentChooserBlock in wagtail.blocks.base.\r\nPlease note that you cannot serialize things like inner classes. Please move the object into the main module body to use migrations.\r\n```\r\n\r\nI'm using a custom document model (`WAGTAILDOCS_DOCUMENT_MODEL = \"custom_documents.CustomDocument\"`) so I removed `WAGTAILDOCS_DOCUMENT_MODEL` setting it see if the error persisted and when running `./manage.py migrate` the ValueError changes to:\r\n\r\n```\r\nValueError: Could not find object DocumentChooserBlock in wagtail.blocks.base.\r\n```\r\n\r\n`CustomDocumentChooserBlock` -> `DocumentChooserBlock` \r\n\r\n\r\n### Steps to Reproduce\r\n\r\n1. run `./manage.py migrate` or `./manage.py makemigrations`\r\n\r\n-  I couldn't reproduce this on a fresh install of wagtail 4.0rc1 however I have been unable to find a cause for this error in my project so far.\r\n\r\n### Technical details\r\n\r\n-   Python version: 3.10.5\r\n-   Django version: 3.2.15\r\n-   Wagtail version: 4.0rc1\r\n-\nEnsure DocumentChooserBlock can be deconstructed for migrations\nFixes #8989. Now that DocumentChooserBlock is constructed dynamically via wagtail.documents.viewsets.chooser, we need to explicitly set its `__module__` attribute so that the result of calling `deconstruct()` for migrations points back to the wagtail.documents.blocks module.\r\n\r\nAlso update the documentation for defining custom choosers, and add tests for deconstructing the other chooser blocks.\r\n\n", "before_files": [{"content": "from wagtail.documents.views.chooser import viewset as chooser_viewset\n\nDocumentChooserBlock = chooser_viewset.block_class\n\n# When deconstructing a DocumentChooserBlock instance for migrations, the module path\n# used in migrations should point to this module\nDocumentChooserBlock.__module__ = \"wagtail.documents.blocks\"\n", "path": "wagtail/documents/blocks.py"}, {"content": "from django.db.models import ForeignKey\nfrom django.urls import path\nfrom django.utils.functional import cached_property\nfrom django.utils.translation import gettext as _\n\nfrom wagtail.admin.forms.models import register_form_field_override\nfrom wagtail.admin.views.generic import chooser as chooser_views\nfrom wagtail.admin.widgets.chooser import BaseChooser\nfrom wagtail.blocks import ChooserBlock\n\nfrom .base import ViewSet\n\n\nclass ChooserViewSet(ViewSet):\n    \"\"\"\n    A viewset that creates a chooser modal interface for choosing model instances.\n    \"\"\"\n\n    model = None\n\n    icon = \"snippet\"  #: The icon to use in the header of the chooser modal, and on the chooser widget\n    choose_one_text = _(\n        \"Choose\"\n    )  #: Label for the 'choose' button in the chooser widget when choosing an initial item\n    page_title = None  #: Title text for the chooser modal (defaults to the same as ``choose_one_text``)`\n    choose_another_text = _(\n        \"Change\"\n    )  #: Label for the 'choose' button in the chooser widget, when an item has already been chosen\n    edit_item_text = _(\"Edit\")  #: Label for the 'edit' button in the chooser widget\n\n    per_page = 10  #: Number of results to show per page\n\n    #: The view class to use for the overall chooser modal; must be a subclass of ``wagtail.admin.views.generic.chooser.ChooseView``.\n    choose_view_class = chooser_views.ChooseView\n\n    #: The view class used to render just the results panel within the chooser modal; must be a subclass of ``wagtail.admin.views.generic.chooser.ChooseResultsView``.\n    choose_results_view_class = chooser_views.ChooseResultsView\n\n    #: The view class used after an item has been chosen; must be a subclass of ``wagtail.admin.views.generic.chooser.ChosenView``.\n    chosen_view_class = chooser_views.ChosenView\n\n    #: The view class used to handle submissions of the 'create' form; must be a subclass of ``wagtail.admin.views.generic.chooser.CreateView``.\n    create_view_class = chooser_views.CreateView\n\n    #: The base Widget class that the chooser widget will be derived from.\n    base_widget_class = BaseChooser\n\n    #: The base ChooserBlock class that the StreamField chooser block will be derived from.\n    base_block_class = ChooserBlock\n\n    #: Defaults to True; if False, the chooser widget will not automatically be registered for use in admin forms.\n    register_widget = True\n\n    #: Form class to use for the form in the \"Create\" tab of the modal.\n    creation_form_class = None\n\n    #: List of model fields that should be included in the creation form, if creation_form_class is not specified.\n    form_fields = None\n\n    #: List of model fields that should be excluded from the creation form, if creation_form_class.\n    #: If none of ``creation_form_class``, ``form_fields`` or ``exclude_form_fields`` are specified, the \"Create\" tab will be omitted.\n    exclude_form_fields = None\n\n    search_tab_label = _(\"Search\")  #: Label for the 'search' tab in the chooser modal\n    create_action_label = _(\n        \"Create\"\n    )  #: Label for the submit button on the 'create' form\n    create_action_clicked_label = None  #: Alternative text to display on the submit button after it has been clicked\n    creation_tab_label = None  #: Label for the 'create' tab in the chooser modal (defaults to the same as create_action_label)\n\n    permission_policy = None\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        if self.page_title is None:\n            self.page_title = self.choose_one_text\n\n    @property\n    def choose_view(self):\n        return self.choose_view_class.as_view(\n            model=self.model,\n            chosen_url_name=self.get_url_name(\"chosen\"),\n            results_url_name=self.get_url_name(\"choose_results\"),\n            create_url_name=self.get_url_name(\"create\"),\n            icon=self.icon,\n            page_title=self.page_title,\n            per_page=self.per_page,\n            creation_form_class=self.creation_form_class,\n            form_fields=self.form_fields,\n            exclude_form_fields=self.exclude_form_fields,\n            search_tab_label=self.search_tab_label,\n            creation_tab_label=self.creation_tab_label,\n            create_action_label=self.create_action_label,\n            create_action_clicked_label=self.create_action_clicked_label,\n            permission_policy=self.permission_policy,\n        )\n\n    @property\n    def choose_results_view(self):\n        return self.choose_results_view_class.as_view(\n            model=self.model,\n            chosen_url_name=self.get_url_name(\"chosen\"),\n            results_url_name=self.get_url_name(\"choose_results\"),\n            per_page=self.per_page,\n            creation_form_class=self.creation_form_class,\n            form_fields=self.form_fields,\n            exclude_form_fields=self.exclude_form_fields,\n            create_action_label=self.create_action_label,\n            create_action_clicked_label=self.create_action_clicked_label,\n            permission_policy=self.permission_policy,\n        )\n\n    @property\n    def chosen_view(self):\n        return self.chosen_view_class.as_view(\n            model=self.model,\n        )\n\n    @property\n    def create_view(self):\n        return self.create_view_class.as_view(\n            model=self.model,\n            create_url_name=self.get_url_name(\"create\"),\n            creation_form_class=self.creation_form_class,\n            form_fields=self.form_fields,\n            exclude_form_fields=self.exclude_form_fields,\n            create_action_label=self.create_action_label,\n            create_action_clicked_label=self.create_action_clicked_label,\n            permission_policy=self.permission_policy,\n        )\n\n    @cached_property\n    def model_name(self):\n        if isinstance(self.model, str):\n            return self.model.split(\".\")[-1]\n        else:\n            return self.model.__name__\n\n    @cached_property\n    def widget_class(self):\n        \"\"\"\n        Returns the form widget class for this chooser.\n        \"\"\"\n        if self.model is None:\n            widget_class_name = \"ChooserWidget\"\n        else:\n            if isinstance(self.model, str):\n                model_name = self.model.split(\".\")[-1]\n            else:\n                model_name = self.model.__name__\n            widget_class_name = \"%sChooserWidget\" % model_name\n\n        return type(\n            widget_class_name,\n            (self.base_widget_class,),\n            {\n                \"model\": self.model,\n                \"choose_one_text\": self.choose_one_text,\n                \"choose_another_text\": self.choose_another_text,\n                \"link_to_chosen_text\": self.edit_item_text,\n                \"chooser_modal_url_name\": self.get_url_name(\"choose\"),\n                \"icon\": self.icon,\n            },\n        )\n\n    @cached_property\n    def block_class(self):\n        \"\"\"\n        Returns a StreamField ChooserBlock class using this chooser.\n        \"\"\"\n        meta = type(\n            \"Meta\",\n            (self.base_block_class._meta_class,),\n            {\n                \"icon\": self.icon,\n            },\n        )\n        return type(\n            \"%sChooserBlock\" % self.model_name,\n            (self.base_block_class,),\n            {\n                \"target_model\": self.model,\n                \"widget\": self.widget_class(),\n                \"Meta\": meta,\n            },\n        )\n\n    def get_urlpatterns(self):\n        return super().get_urlpatterns() + [\n            path(\"\", self.choose_view, name=\"choose\"),\n            path(\"results/\", self.choose_results_view, name=\"choose_results\"),\n            path(\"chosen/<str:pk>/\", self.chosen_view, name=\"chosen\"),\n            path(\"create/\", self.create_view, name=\"create\"),\n        ]\n\n    def on_register(self):\n        if self.model and self.register_widget:\n            register_form_field_override(\n                ForeignKey, to=self.model, override={\"widget\": self.widget_class}\n            )\n", "path": "wagtail/admin/viewsets/chooser.py"}], "after_files": [{"content": "from wagtail.documents.views.chooser import viewset as chooser_viewset\n\nDocumentChooserBlock = chooser_viewset.get_block_class(\n    name=\"DocumentChooserBlock\", module_path=\"wagtail.documents.blocks\"\n)\n", "path": "wagtail/documents/blocks.py"}, {"content": "from django.db.models import ForeignKey\nfrom django.urls import path\nfrom django.utils.functional import cached_property\nfrom django.utils.translation import gettext as _\n\nfrom wagtail.admin.forms.models import register_form_field_override\nfrom wagtail.admin.views.generic import chooser as chooser_views\nfrom wagtail.admin.widgets.chooser import BaseChooser\nfrom wagtail.blocks import ChooserBlock\n\nfrom .base import ViewSet\n\n\nclass ChooserViewSet(ViewSet):\n    \"\"\"\n    A viewset that creates a chooser modal interface for choosing model instances.\n    \"\"\"\n\n    model = None\n\n    icon = \"snippet\"  #: The icon to use in the header of the chooser modal, and on the chooser widget\n    choose_one_text = _(\n        \"Choose\"\n    )  #: Label for the 'choose' button in the chooser widget when choosing an initial item\n    page_title = None  #: Title text for the chooser modal (defaults to the same as ``choose_one_text``)`\n    choose_another_text = _(\n        \"Change\"\n    )  #: Label for the 'choose' button in the chooser widget, when an item has already been chosen\n    edit_item_text = _(\"Edit\")  #: Label for the 'edit' button in the chooser widget\n\n    per_page = 10  #: Number of results to show per page\n\n    #: The view class to use for the overall chooser modal; must be a subclass of ``wagtail.admin.views.generic.chooser.ChooseView``.\n    choose_view_class = chooser_views.ChooseView\n\n    #: The view class used to render just the results panel within the chooser modal; must be a subclass of ``wagtail.admin.views.generic.chooser.ChooseResultsView``.\n    choose_results_view_class = chooser_views.ChooseResultsView\n\n    #: The view class used after an item has been chosen; must be a subclass of ``wagtail.admin.views.generic.chooser.ChosenView``.\n    chosen_view_class = chooser_views.ChosenView\n\n    #: The view class used to handle submissions of the 'create' form; must be a subclass of ``wagtail.admin.views.generic.chooser.CreateView``.\n    create_view_class = chooser_views.CreateView\n\n    #: The base Widget class that the chooser widget will be derived from.\n    base_widget_class = BaseChooser\n\n    #: The base ChooserBlock class that the StreamField chooser block will be derived from.\n    base_block_class = ChooserBlock\n\n    #: Defaults to True; if False, the chooser widget will not automatically be registered for use in admin forms.\n    register_widget = True\n\n    #: Form class to use for the form in the \"Create\" tab of the modal.\n    creation_form_class = None\n\n    #: List of model fields that should be included in the creation form, if creation_form_class is not specified.\n    form_fields = None\n\n    #: List of model fields that should be excluded from the creation form, if creation_form_class.\n    #: If none of ``creation_form_class``, ``form_fields`` or ``exclude_form_fields`` are specified, the \"Create\" tab will be omitted.\n    exclude_form_fields = None\n\n    search_tab_label = _(\"Search\")  #: Label for the 'search' tab in the chooser modal\n    create_action_label = _(\n        \"Create\"\n    )  #: Label for the submit button on the 'create' form\n    create_action_clicked_label = None  #: Alternative text to display on the submit button after it has been clicked\n    creation_tab_label = None  #: Label for the 'create' tab in the chooser modal (defaults to the same as create_action_label)\n\n    permission_policy = None\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        if self.page_title is None:\n            self.page_title = self.choose_one_text\n\n    @property\n    def choose_view(self):\n        return self.choose_view_class.as_view(\n            model=self.model,\n            chosen_url_name=self.get_url_name(\"chosen\"),\n            results_url_name=self.get_url_name(\"choose_results\"),\n            create_url_name=self.get_url_name(\"create\"),\n            icon=self.icon,\n            page_title=self.page_title,\n            per_page=self.per_page,\n            creation_form_class=self.creation_form_class,\n            form_fields=self.form_fields,\n            exclude_form_fields=self.exclude_form_fields,\n            search_tab_label=self.search_tab_label,\n            creation_tab_label=self.creation_tab_label,\n            create_action_label=self.create_action_label,\n            create_action_clicked_label=self.create_action_clicked_label,\n            permission_policy=self.permission_policy,\n        )\n\n    @property\n    def choose_results_view(self):\n        return self.choose_results_view_class.as_view(\n            model=self.model,\n            chosen_url_name=self.get_url_name(\"chosen\"),\n            results_url_name=self.get_url_name(\"choose_results\"),\n            per_page=self.per_page,\n            creation_form_class=self.creation_form_class,\n            form_fields=self.form_fields,\n            exclude_form_fields=self.exclude_form_fields,\n            create_action_label=self.create_action_label,\n            create_action_clicked_label=self.create_action_clicked_label,\n            permission_policy=self.permission_policy,\n        )\n\n    @property\n    def chosen_view(self):\n        return self.chosen_view_class.as_view(\n            model=self.model,\n        )\n\n    @property\n    def create_view(self):\n        return self.create_view_class.as_view(\n            model=self.model,\n            create_url_name=self.get_url_name(\"create\"),\n            creation_form_class=self.creation_form_class,\n            form_fields=self.form_fields,\n            exclude_form_fields=self.exclude_form_fields,\n            create_action_label=self.create_action_label,\n            create_action_clicked_label=self.create_action_clicked_label,\n            permission_policy=self.permission_policy,\n        )\n\n    @cached_property\n    def model_name(self):\n        if isinstance(self.model, str):\n            return self.model.split(\".\")[-1]\n        else:\n            return self.model.__name__\n\n    @cached_property\n    def widget_class(self):\n        \"\"\"\n        Returns the form widget class for this chooser.\n        \"\"\"\n        if self.model is None:\n            widget_class_name = \"ChooserWidget\"\n        else:\n            if isinstance(self.model, str):\n                model_name = self.model.split(\".\")[-1]\n            else:\n                model_name = self.model.__name__\n            widget_class_name = \"%sChooserWidget\" % model_name\n\n        return type(\n            widget_class_name,\n            (self.base_widget_class,),\n            {\n                \"model\": self.model,\n                \"choose_one_text\": self.choose_one_text,\n                \"choose_another_text\": self.choose_another_text,\n                \"link_to_chosen_text\": self.edit_item_text,\n                \"chooser_modal_url_name\": self.get_url_name(\"choose\"),\n                \"icon\": self.icon,\n            },\n        )\n\n    def get_block_class(self, name=None, module_path=None):\n        \"\"\"\n        Returns a StreamField ChooserBlock class using this chooser.\n\n        :param name: Name to give to the class; defaults to the model name with \"ChooserBlock\" appended\n        :param module_path: The dotted path of the module where the class can be imported from; used when\n            deconstructing the block definition for migration files.\n        \"\"\"\n        meta = type(\n            \"Meta\",\n            (self.base_block_class._meta_class,),\n            {\n                \"icon\": self.icon,\n            },\n        )\n        cls = type(\n            name or \"%sChooserBlock\" % self.model_name,\n            (self.base_block_class,),\n            {\n                \"target_model\": self.model,\n                \"widget\": self.widget_class(),\n                \"Meta\": meta,\n            },\n        )\n        if module_path:\n            cls.__module__ = module_path\n        return cls\n\n    def get_urlpatterns(self):\n        return super().get_urlpatterns() + [\n            path(\"\", self.choose_view, name=\"choose\"),\n            path(\"results/\", self.choose_results_view, name=\"choose_results\"),\n            path(\"chosen/<str:pk>/\", self.chosen_view, name=\"chosen\"),\n            path(\"create/\", self.create_view, name=\"create\"),\n        ]\n\n    def on_register(self):\n        if self.model and self.register_widget:\n            register_form_field_override(\n                ForeignKey, to=self.model, override={\"widget\": self.widget_class}\n            )\n", "path": "wagtail/admin/viewsets/chooser.py"}]}