django-fast-modeladmin 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,63 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-fast-modeladmin
3
+ Version: 1.0.0
4
+ Summary: Faster Admin model control via a settings dictionary
5
+ Author: SimplePythonCoder
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/SimplePythonCoder/django-fast-modeladmin
8
+ Project-URL: Source, https://github.com/SimplePythonCoder/django-fast-modeladmin
9
+ Project-URL: Changelog, https://github.com/SimplePythonCoder/django-fast-modeladmin/blob/main/CHANGELOG.md
10
+ Project-URL: Documentation, https://github.com/SimplePythonCoder/django-fast-modeladmin/wiki
11
+ Project-URL: Issues, https://github.com/SimplePythonCoder/django-fast-modeladmin/issues
12
+ Keywords: django,django-admin
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Operating System :: Microsoft :: Windows :: Windows 10
17
+ Classifier: Topic :: Utilities
18
+ Requires-Python: >=3.14
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: django
22
+ Dynamic: license-file
23
+
24
+ # django-fast-modeladmin
25
+
26
+ [![downloads](https://static.pepy.tech/badge/django-fast-modeladmin)](https://www.pepy.tech/projects/django-fast-modeladmin) [![changelog](https://raw.githubusercontent.com/SimplePythonCoder/zipmanager/main/images/Changelog.svg)](https://github.com/SimplePythonCoder/django-fast-modeladmin/blob/main/CHANGELOG.md) [![wiki](https://raw.githubusercontent.com/SimplePythonCoder/zipmanager/main/images/Wiki.svg)](https://github.com/SimplePythonCoder/django-fast-modeladmin/wiki)
27
+
28
+
29
+ A fast way to customize your django admin site.
30
+
31
+ ### Installation & Usage
32
+
33
+ ```
34
+ pip install django-fast-modeladmin
35
+ ```
36
+
37
+ Add fast_modeladmin to INSTALLED_APPS:
38
+ ```python
39
+ # settings.py
40
+ INSTALLED_APPS = [
41
+ ...,
42
+ 'fast_modeladmin'
43
+ ]
44
+
45
+ ...
46
+
47
+ FAST_MODELADMIN = (...)
48
+ ```
49
+
50
+ ## Features
51
+
52
+ Allows you to control many of the Django admin sited functionalities with a dict for each model instead of creating many classes.
53
+
54
+ Including:
55
+ - edit columns shown in the model table page
56
+ - a search box for specific fields (including foreign keys)
57
+ - select which fields are editable or not
58
+
59
+ and many more.
60
+
61
+ The package also include an error system for easier readability.
62
+
63
+ more info at the [wiki](https://github.com/SimplePythonCoder/django-fast-modeladmin/wiki).
@@ -0,0 +1,10 @@
1
+ django_fast_modeladmin-1.0.0.dist-info/licenses/LICENSE,sha256=kAeeE4HF_j5MduozghXRLfWDe7sHW43rUjhG41SDKtA,1074
2
+ fast_modeladmin/__init__.py,sha256=w7J8kHoWbaPZlLHAcYE81OlFjMak5FdeLiTRGuxE6_Q,82
3
+ fast_modeladmin/apps.py,sha256=H-CekTvM_mSWNKTQOWUXe0NjKJmHbGcxBse5aTv2MAg,980
4
+ fast_modeladmin/exceptions.py,sha256=-qeNqNRXXo3gZdbPgxZenvsMqdvZgMvvBhNrEKJRewg,2275
5
+ fast_modeladmin/form.py,sha256=00UYmnuggCfQhPyp3kdd8y7sac_teuP9fQ5vUlEAfJY,318
6
+ fast_modeladmin/main.py,sha256=9GXLwZoVWA1_4lSBuRxrjjf3jFvAyYVM8DxukyYnPCU,16545
7
+ django_fast_modeladmin-1.0.0.dist-info/METADATA,sha256=Eeq2lPkYTgFPWOBFCbj4PaZFYRd67xwukgoAquBFuss,2288
8
+ django_fast_modeladmin-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
9
+ django_fast_modeladmin-1.0.0.dist-info/top_level.txt,sha256=0Z1fxrA9AhYaNIDz4ln6wD_KqKF8458gFRcNS8DmFak,16
10
+ django_fast_modeladmin-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SimplePythonCoder
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ fast_modeladmin
@@ -0,0 +1,2 @@
1
+ from .main import _register_models, get_dict
2
+ from .form import template_model_form
@@ -0,0 +1,28 @@
1
+ from django.apps import AppConfig
2
+ from django.conf import settings
3
+ from django.core.checks import Warning, register, Tags
4
+
5
+ from .main import _register_models
6
+
7
+
8
+ def check_my_custom_setting(**kwargs):
9
+ errors = []
10
+ if not hasattr(settings, 'FAST_MODELADMIN'):
11
+ errors.append(
12
+ Warning(
13
+ 'FAST_MODELADMIN setting is missing.',
14
+ hint='Add "FAST_MODELADMIN = ()" to settings.py',
15
+ obj='fast_modeladmin'
16
+ )
17
+ )
18
+ return errors
19
+
20
+
21
+ class CustomAdminAppConfig(AppConfig):
22
+ name = 'fast_modeladmin'
23
+
24
+ def ready(self):
25
+ register(check_my_custom_setting, Tags.compatibility)
26
+ _register_models(admin_model=getattr(settings, 'FAST_MODELADMIN', None),
27
+ register_all=getattr(settings, 'FAST_MODELADMIN_REGISTER_ALL', None),
28
+ files=getattr(settings, 'FAST_MODELADMIN_MODULES', {}))
@@ -0,0 +1,75 @@
1
+ class BaseFastAdminException(Exception):
2
+ def __init__(self, message):
3
+ self.message = message
4
+
5
+ def __str__(self):
6
+ return self.message
7
+
8
+
9
+ class ImproperlyConfigured(BaseFastAdminException):
10
+ pass
11
+
12
+
13
+ class CustomSyntaxError(BaseFastAdminException):
14
+ def __init__(self, message, model=None):
15
+ if model:
16
+ message = f'{model.__name__} Model: {message}'
17
+ super().__init__(message)
18
+
19
+
20
+ class ModuleError(CustomSyntaxError):
21
+ pass
22
+
23
+
24
+ class ClassError(CustomSyntaxError):
25
+ pass
26
+
27
+
28
+ class DecoratorError(CustomSyntaxError):
29
+ def __init__(self, function, decorator, model=None):
30
+ super().__init__(message=f'action "{function}" does not use the "{decorator}" decorator', model=model)
31
+
32
+
33
+ class ValueTypeError(CustomSyntaxError):
34
+ @staticmethod
35
+ def get_str_type(types):
36
+ match types:
37
+ case list():
38
+ return ' or '.join([t.__name__ if t is not None else "None" for t in types])
39
+ case type():
40
+ return types.__name__
41
+ case None:
42
+ return 'None'
43
+ case _:
44
+ return type(types).__name__
45
+
46
+ def __init__(self, value, types, kind, model=None):
47
+ if type(value) is str:
48
+ value = f'"{value}"'
49
+ super().__init__(model=model,
50
+ message=f'{kind} type is {self.get_str_type(value)} instead of '
51
+ f'{self.get_str_type(types)}' +
52
+ (f' [{kind}: {value}]' if model else '')
53
+ )
54
+
55
+
56
+ class OptionError(CustomSyntaxError):
57
+ pass
58
+
59
+
60
+ class ModelNotFound(BaseFastAdminException):
61
+ def __init__(self, model):
62
+ if hasattr(model, '__name__'):
63
+ model = model.__name__
64
+ super().__init__(f'Model "{model}" not found')
65
+
66
+
67
+ class ModelFieldError(BaseFastAdminException):
68
+ def __init__(self, model, field=None, message=None):
69
+ if type(field) is str:
70
+ super().__init__(f'field "{field}" not found in {model.__name__} model')
71
+ elif type(message) is str:
72
+ super().__init__(message)
73
+
74
+ def _raise(error_type, *args, **kwargs):
75
+ raise error_type(*args, **kwargs) from None
@@ -0,0 +1,9 @@
1
+ from django import forms
2
+
3
+ def template_model_form(model_to_render, fields_to_render: list | None = None):
4
+ class GenericModelForm(forms.ModelForm):
5
+ class Meta:
6
+ model = model_to_render
7
+ fields = fields_to_render if fields_to_render else '__all__'
8
+
9
+ return GenericModelForm
@@ -0,0 +1,296 @@
1
+ from typing import cast
2
+
3
+ from django.apps import apps
4
+ from django.contrib import admin
5
+ import re
6
+ from importlib import import_module
7
+ from django.contrib.admin import ShowFacets
8
+ from django.db import models
9
+
10
+ from .exceptions import (ModelFieldError, OptionError, ModelNotFound, ValueTypeError,
11
+ CustomSyntaxError, ModuleError, ClassError,
12
+ _raise)
13
+
14
+ from .form import template_model_form
15
+
16
+
17
+ def _get_import(model, field_list: list, index=0):
18
+ if index < len(field_list):
19
+ if getattr(model, field_list[index], None) is not None:
20
+ match getattr(model, field_list[index]).__class__.__name__:
21
+ case 'ForwardManyToOneDescriptor' | 'ForwardOneToOneDescriptor':
22
+ return _get_import(getattr(model, field_list[index]).field.remote_field.model, field_list,
23
+ index + 1)
24
+ case 'DeferredAttribute':
25
+ if getattr(getattr(model, field_list[index]), 'field', None) is not None:
26
+ return _get_import(getattr(getattr(model, field_list[index]), 'field'), field_list,
27
+ index + 1)
28
+ else:
29
+ _raise(model=model.__name__, error_type=ModelFieldError, field=field_list[index])
30
+ else:
31
+ _raise(model=model.__name__, error_type=ModelFieldError, message=f'field "{field_list[index]}" in {"__".join(field_list)} '
32
+ f'cannot be found')
33
+ return model
34
+
35
+
36
+ def get_dict(model=None, columns=None, parts=None,
37
+ actions=None, form=None, read_only_fields=None,
38
+ sort=None, search=None, editables=None, links=None,
39
+ filters=None):
40
+ """
41
+ This function builds a dictionary to be used in the FAST_MODELADMIN setting
42
+
43
+ :param model: app and model name (app_name.ModelName)
44
+ :type model: str
45
+ :param columns: list of fields to be columns of list page
46
+ :type columns: list[str]
47
+ :param parts: parts and fields to render on entry page
48
+ :type parts: dict [(str, str), (str, list)]
49
+ :param actions: list of function names in the admin module
50
+ :type actions: list[str]
51
+ :param form: list of class names in the forms module
52
+ :type form: str
53
+ :param read_only_fields: list of fields to be non-editables in the form page
54
+ :type read_only_fields: list[str]
55
+ :param sort: name of field to sort by
56
+ :type sort: list[str]
57
+ :param search: list of fields and sub-fields to search in
58
+ :type search: list[str]
59
+ :param editables: list of column fields to be editable on the list page
60
+ :type editables: list[str]
61
+ :param links: list of column fields to be clickable on the list page
62
+ :type links: list[str]
63
+ :param filters: dict with list of fields in 'fields' and 'show_count' that is either 'show',
64
+ 'hide' or 'disable'.
65
+ :type filters: dict[str, list] | dict[str, str]
66
+ :return: dict to be used in settings for CUSTOM_MODELADMIN variable
67
+ :rtype: dict
68
+ """
69
+ return {'model': model,
70
+ 'columns': columns,
71
+ 'parts': parts,
72
+ 'actions': actions,
73
+ 'form': form,
74
+ 'read_only_fields': read_only_fields,
75
+ 'sort': sort,
76
+ 'search': search,
77
+ 'editables': editables,
78
+ 'links': links,
79
+ 'filters': filters}
80
+
81
+
82
+ def _part_creator(**kwargs):
83
+ match kwargs.get('classes'):
84
+ case None:
85
+ return kwargs['label'], {'fields': kwargs['fields']}
86
+ case list() | tuple():
87
+ return kwargs['label'], {'fields': kwargs['fields'], 'classes': kwargs['classes']}
88
+
89
+ return None
90
+
91
+ def _get_app(app_model):
92
+ if type(app_model) is str:
93
+ if not re.match(pattern=r'.+\..+', string=app_model):
94
+ _raise(error_type=OptionError, message='model pattern must be app_label.ModelName')
95
+ app_model = {'app_label': app_model.split('.')[0], 'model_name': app_model.split('.')[1]}
96
+ try:
97
+ return apps.get_model(**app_model)
98
+ except LookupError:
99
+ _raise(model=f'{app_model["app_label"]}.{app_model["model_name"]}', error_type=ModelNotFound)
100
+
101
+
102
+ def _is_type(model, value, types, kind):
103
+ match types:
104
+ case list():
105
+ if type(value) not in types:
106
+ _raise(model=model, error_type=ValueTypeError, kind=kind, value=value, types=types)
107
+ case type():
108
+ if type(value) is not types:
109
+ _raise(model=model, error_type=ValueTypeError, kind=kind, value=value, types=types)
110
+
111
+
112
+ def _interpreter(values, files, model):
113
+ model_fields = [field for field in model.__dict__.keys() if not field.startswith('_')]
114
+ non_editable_fields = [field.name for field in getattr(model, '_meta').fields if not field.editable]
115
+
116
+ if values.get('columns') is not None:
117
+ _is_type(model, value=values['columns'], types=[list, tuple], kind='columns')
118
+ for field in values['columns']:
119
+ _is_type(model, value=field, types=str, kind='columns field')
120
+ if field not in model_fields and field != 'pk':
121
+ _raise(model=model, error_type=ModelFieldError, field=field)
122
+ if values.get('read_only_fields') is not None:
123
+ _is_type(model, value=values['read_only_fields'], types=[list, tuple], kind='read only fields')
124
+ for field in values['read_only_fields']:
125
+ _is_type(model, value=field, types=str, kind='read only field')
126
+ if field not in model_fields and field != 'pk':
127
+ _raise(model=model, error_type=ModelFieldError, field=field)
128
+ if values.get('parts') is not None:
129
+ _is_type(model, value=values.get('parts'), types=[list, tuple], kind='parts')
130
+ field_list = []
131
+ for index, part in enumerate(values['parts']):
132
+ _is_type(model, value=part, types=dict, kind='parts')
133
+ if part.get('label') is None:
134
+ _raise(model=model, error_type=OptionError, message=f'missing label in parts[{index}]')
135
+ _is_type(model, value=part.get('label'), types=str, kind='label')
136
+ if part.get('fields') is None:
137
+ _raise(model=model, error_type=OptionError, message=f'missing fields in parts[{index}]')
138
+ _is_type(model, value=part.get('fields'), types=[list, tuple], kind='part fields')
139
+ _is_type(model, value=part.get('classes'), types=[list, tuple, type(None)], kind='classes')
140
+ for field in part['fields']:
141
+ _is_type(model, value=field, types=str, kind='field')
142
+ if field not in model_fields:
143
+ _raise(model=model, error_type=ModelFieldError, field=field)
144
+ if field in non_editable_fields and field not in values.get('read_only_fields'):
145
+ _raise(model=model, error_type=OptionError,
146
+ message=f'cannot use non-editable field "{field}" in parts.'
147
+ f'\nHINT: you can add it to the "read_only_fields" to avoid this error.')
148
+ if field in field_list:
149
+ _raise(model=model, error_type=OptionError,
150
+ message=f'cannot use field "{field}" more than once in "parts"')
151
+ field_list.append(field)
152
+ values['parts'][index] = _part_creator(**part)
153
+ del field_list
154
+ if values.get('sort') is not None:
155
+ _is_type(model, value=values['sort'], types=[list, tuple], kind='sort')
156
+ for sort in values['sort']:
157
+ _is_type(model, value=sort, types=str, kind='sort field')
158
+ if not re.match(pattern=r'-?\w+', string=sort):
159
+ _raise(model=model, error_type=CustomSyntaxError,
160
+ message=f'sort pattern must be "field_name" or "-field_name"')
161
+ _get_import(model, sort[1:].split('__') if sort[0] == '-' else sort.split('__'))
162
+ if values.get('actions') is not None:
163
+ _is_type(model, value=values['actions'], types=[list, tuple], kind='actions')
164
+ try:
165
+ admin_module = import_module(values['model'].split('.')[0] + '.' +
166
+ (files['ADMIN'].replace('.py', '') if files.get('ADMIN') else 'admin'))
167
+ except ModuleNotFoundError:
168
+ _raise(model=model, error_type=CustomSyntaxError,
169
+ message=f'could not find "{files.get("ADMIN")}" admin module')
170
+ return None
171
+ for index, action in enumerate(values['actions']):
172
+ _is_type(model, value=action, types=str, kind='action')
173
+ values['actions'][index] = getattr(admin_module, action, None)
174
+ if not callable(values['actions'][index]):
175
+ _raise(model=model, error_type=ModuleError,
176
+ message=f'action "{action}" was not found in {values["model"].split(".")[0]}.admin')
177
+ if values.get('form') is not None:
178
+ _is_type(model, value=values['form'], types=str, kind='form')
179
+ try:
180
+ values['form'] = getattr(import_module(values['model'].split('.')[0] + '.' +
181
+ (files['FORMS'].replace('.py', '')
182
+ if files.get('FORMS') else 'forms')),
183
+ values['form'])
184
+ except ModuleNotFoundError:
185
+ _raise(model=model, error_type=CustomSyntaxError, message=f'could not find "{files.get("FORMS")}" forms module')
186
+ except AttributeError:
187
+ _raise(model=model, error_type=ModuleError,
188
+ message=f'form "{values["form"]}" not found in {values["model"].split(".")[0]}.forms')
189
+ if not callable(values['form']):
190
+ _is_type(model=model, value=values['form'], types=str, kind='form')
191
+ if values['form'].__class__.__name__ != 'ModelFormMetaclass':
192
+ _raise(model=model, error_type=ClassError,
193
+ message='form must inherit from forms.ModelForm (including multilevel inheritance)')
194
+ if values.get('search') is not None:
195
+ _is_type(model, value=values['search'], types=[list, tuple], kind='search')
196
+ for field in values['search']:
197
+ _is_type(model, value=field, types=str, kind='search field')
198
+ if (error_field:=field.split('__')[0]) not in model_fields:
199
+ _raise(model=model, error_type=ModelFieldError, field=error_field)
200
+ del error_field
201
+ _get_import(model, field.split('__'))
202
+ if values.get('editables') is not None:
203
+ _is_type(model, value=values['editables'], types=[list, tuple], kind='editables')
204
+ if not values.get('columns'):
205
+ _raise(model=model, error_type=CustomSyntaxError,
206
+ message='cannot use editables without columns')
207
+ for editable in values['editables']:
208
+ if editable not in model_fields:
209
+ _raise(model=model, error_type=ModelFieldError,
210
+ field=editable)
211
+ if editable not in values['columns']:
212
+ _raise(model=model, error_type=ModelFieldError,
213
+ message=f'editable field "{editable}" must be one of the columns')
214
+ if editable in values['columns'][0]:
215
+ _raise(model=model, error_type=ModelFieldError,
216
+ message=f'editable field "{editable}" cannot be the first column')
217
+ if values.get('links') and editable in values['links']:
218
+ _raise(model=model, error_type=ModelFieldError,
219
+ message=f'field "{editable}" cannot be in both editables and links')
220
+ if values.get('filters') is not None:
221
+ _is_type(model, value=values['filters'], types=dict, kind='filter')
222
+ _is_type(model, value=values['filters'].get('fields'), types=[list, tuple], kind='filter fields')
223
+ for filter_name in values['filters']['fields']:
224
+ _is_type(model, value=filter_name, types=str, kind='filter field')
225
+ _get_import(model=model, field_list=filter_name.split('__'))
226
+ if values['filters'].get('show_count'):
227
+ _is_type(model, value=values['filters']['show_count'], types=str, kind='filter show count')
228
+ match values['filters']['show_count']:
229
+ case 'show':
230
+ values['filters']['show_count'] = ShowFacets.ALWAYS
231
+ case 'hide':
232
+ values['filters']['show_count'] = ShowFacets.ALLOW
233
+ case 'disable':
234
+ values['filters']['show_count'] = ShowFacets.NEVER
235
+ case _:
236
+ _raise(model=model, error_type=CustomSyntaxError,
237
+ message='show_count must be "show", "hide" or "disable"')
238
+ if values.get('search_text') is not None:
239
+ if not values.get('search'):
240
+ _raise(model=model, error_type=CustomSyntaxError,
241
+ message='cannot use search text without search')
242
+ _is_type(model, value=values['search text'], types=str, kind='search text')
243
+ if values.get('links') is not None:
244
+ if not values.get('columns'):
245
+ _raise(model=model, error_type=CustomSyntaxError,
246
+ message='cannot use links without columns')
247
+ _is_type(model, value=values['links'], types=[list, tuple], kind='links')
248
+ for link in values['links']:
249
+ _is_type(model, value=link, types=str, kind='link')
250
+ if link not in values['columns']:
251
+ _raise(model=model, error_type=ModelFieldError,
252
+ message=f'link "{link}" must be one of {values["model"]}\'s current columns')
253
+
254
+ return values
255
+
256
+
257
+ def _create_modeladmin(**kwargs: dict):
258
+ class FastModelAdminClass(admin.ModelAdmin):
259
+ list_display = kwargs.get('columns', ['__str__'])
260
+ fieldsets = kwargs.get('parts', [])
261
+ form = kwargs.get('form', template_model_form(_get_app(kwargs['model'])))
262
+ actions = kwargs.get('actions', [])
263
+ search_fields = kwargs.get('search', [])
264
+ readonly_fields = kwargs.get('read_only_fields', [])
265
+ list_filter = kwargs.get('filters', {}).get('fields', [])
266
+ show_facets = kwargs.get('filters', {}).get('show_count', ShowFacets.NEVER)
267
+ list_editable = kwargs.get('editables', [])
268
+ ordering = kwargs.get('sort', [])
269
+ search_help_text = kwargs.get('search_text')
270
+ list_display_links = kwargs.get('links', [])
271
+
272
+ return FastModelAdminClass
273
+
274
+
275
+ def _register_models(admin_model, register_all, files=None):
276
+ if type(files) is not dict:
277
+ _raise(error_type=ValueTypeError, kind='FAST_MODELADMIN_MODULES', value=files, types=dict)
278
+ match admin_model:
279
+ case list() | tuple():
280
+ for values in admin_model:
281
+ if type(values) is not dict:
282
+ _raise(error_type=OptionError, message='FAST_MODELADMIN elements must be dict')
283
+ if type(values.get('model')) is not str:
284
+ _raise(error_type=ValueTypeError, kind='model', value=values.get('model'), types=str)
285
+ model = cast(type[models.Model], _get_app(values['model']))
286
+ if admin.site.is_registered(model):
287
+ _raise(error_type=OptionError, message=f'Model {values["model"]} cannot be registered again.')
288
+ admin.site.register(model, _create_modeladmin(**_interpreter(values, files, model=model) or {}))
289
+ case None:
290
+ pass
291
+ case _:
292
+ _raise(error_type=ValueTypeError, kind='FAST_MODELADMIN', value=type(admin_model), types=[list, tuple])
293
+ if register_all:
294
+ for model in apps.get_models():
295
+ if not admin.site.is_registered(model) and 'django' not in model.__module__:
296
+ admin.site.register(model)