brilliance-admin 0.44.0__tar.gz → 0.44.2__tar.gz

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.
Files changed (124) hide show
  1. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/PKG-INFO +4 -1
  2. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/api/views/table.py +2 -1
  3. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/integrations/sqlalchemy/auth.py +1 -2
  4. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/integrations/sqlalchemy/fields.py +22 -9
  5. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/schema/table/fields/base.py +21 -6
  6. brilliance_admin-0.44.0/brilliance_admin/static/index-MLuDem5W.js → brilliance_admin-0.44.2/brilliance_admin/static/index-rBvEkjGg.js +2 -2
  7. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/templates/index.html +1 -1
  8. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin.egg-info/PKG-INFO +4 -1
  9. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin.egg-info/SOURCES.txt +1 -41
  10. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin.egg-info/requires.txt +3 -0
  11. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/pyproject.toml +4 -1
  12. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/tests/test_action.py +1 -1
  13. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/tests/test_settings.py +1 -1
  14. brilliance_admin-0.44.0/.configs/docker/Dockerfile +0 -22
  15. brilliance_admin-0.44.0/.configs/docker/docker-compose.yml +0 -35
  16. brilliance_admin-0.44.0/.configs/nginx/example.conf +0 -25
  17. brilliance_admin-0.44.0/.env +0 -0
  18. brilliance_admin-0.44.0/.github/workflows/certbot.yml +0 -36
  19. brilliance_admin-0.44.0/.github/workflows/deploy.yml +0 -127
  20. brilliance_admin-0.44.0/.github/workflows/install-docker.yml +0 -26
  21. brilliance_admin-0.44.0/.gitignore +0 -11
  22. brilliance_admin-0.44.0/.isort.cfg +0 -7
  23. brilliance_admin-0.44.0/.python-version +0 -1
  24. brilliance_admin-0.44.0/brilliance_admin/locales/en.yml +0 -25
  25. brilliance_admin-0.44.0/brilliance_admin/locales/ru.yml +0 -26
  26. brilliance_admin-0.44.0/example/README.md +0 -32
  27. brilliance_admin-0.44.0/example/__init__.py +0 -0
  28. brilliance_admin-0.44.0/example/locales/en.yml +0 -57
  29. brilliance_admin-0.44.0/example/locales/ru.yml +0 -57
  30. brilliance_admin-0.44.0/example/main.py +0 -155
  31. brilliance_admin-0.44.0/example/sections/__init__.py +0 -0
  32. brilliance_admin-0.44.0/example/sections/currency.py +0 -21
  33. brilliance_admin-0.44.0/example/sections/graphs.py +0 -136
  34. brilliance_admin-0.44.0/example/sections/merchant.py +0 -45
  35. brilliance_admin-0.44.0/example/sections/models.py +0 -260
  36. brilliance_admin-0.44.0/example/sections/payments.py +0 -183
  37. brilliance_admin-0.44.0/example/sections/terminal.py +0 -43
  38. brilliance_admin-0.44.0/example/sections/users.py +0 -25
  39. brilliance_admin-0.44.0/example/sqlite.py +0 -45
  40. brilliance_admin-0.44.0/example/static/favicon.ico +0 -0
  41. brilliance_admin-0.44.0/example/static/favicon.jpg +0 -0
  42. brilliance_admin-0.44.0/example/static/logo-outline.png +0 -0
  43. brilliance_admin-0.44.0/example/static/logo.png +0 -0
  44. brilliance_admin-0.44.0/example/utils.py +0 -26
  45. brilliance_admin-0.44.0/screenshots/PC-graphs.jpeg +0 -0
  46. brilliance_admin-0.44.0/screenshots/PC-table.jpeg +0 -0
  47. brilliance_admin-0.44.0/screenshots/iPad-edit.jpeg +0 -0
  48. brilliance_admin-0.44.0/screenshots/iPhone 15-edit.jpeg +0 -0
  49. brilliance_admin-0.44.0/screenshots/iPhone 15-login.jpeg +0 -0
  50. brilliance_admin-0.44.0/screenshots/websitemockupgenerator.png +0 -0
  51. brilliance_admin-0.44.0/tests/__init__.py +0 -0
  52. brilliance_admin-0.44.0/tests/conftest.py +0 -26
  53. brilliance_admin-0.44.0/uv.lock +0 -886
  54. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/LICENSE +0 -0
  55. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/README.md +0 -0
  56. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/__init__.py +0 -0
  57. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/api/__init__.py +0 -0
  58. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/api/routers.py +0 -0
  59. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/api/utils.py +0 -0
  60. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/api/views/__init__.py +0 -0
  61. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/api/views/auth.py +0 -0
  62. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/api/views/autocomplete.py +0 -0
  63. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/api/views/graphs.py +0 -0
  64. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/api/views/index.py +0 -0
  65. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/api/views/schema.py +0 -0
  66. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/api/views/settings.py +0 -0
  67. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/auth.py +0 -0
  68. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/docs.py +0 -0
  69. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/exceptions.py +0 -0
  70. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/integrations/__init__.py +0 -0
  71. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/integrations/sqlalchemy/__init__.py +0 -0
  72. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/integrations/sqlalchemy/autocomplete.py +0 -0
  73. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/integrations/sqlalchemy/fields_schema.py +0 -0
  74. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/integrations/sqlalchemy/table/__init__.py +0 -0
  75. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/integrations/sqlalchemy/table/base.py +0 -0
  76. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/integrations/sqlalchemy/table/create.py +0 -0
  77. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/integrations/sqlalchemy/table/delete.py +0 -0
  78. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/integrations/sqlalchemy/table/list.py +0 -0
  79. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/integrations/sqlalchemy/table/retrieve.py +0 -0
  80. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/integrations/sqlalchemy/table/update.py +0 -0
  81. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/schema/__init__.py +0 -0
  82. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/schema/admin_schema.py +0 -0
  83. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/schema/category.py +0 -0
  84. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/schema/graphs/__init__.py +0 -0
  85. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/schema/graphs/category_graphs.py +0 -0
  86. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/schema/table/__init__.py +0 -0
  87. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/schema/table/admin_action.py +0 -0
  88. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/schema/table/category_table.py +0 -0
  89. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/schema/table/fields/__init__.py +0 -0
  90. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/schema/table/fields/function_field.py +0 -0
  91. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/schema/table/fields_schema.py +0 -0
  92. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/schema/table/table_models.py +0 -0
  93. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/index-P_wdMBbz.css +0 -0
  94. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/materialdesignicons-webfont-CYDMK1kx.woff2 +0 -0
  95. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/materialdesignicons-webfont-CgCzGbLl.woff +0 -0
  96. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/materialdesignicons-webfont-D3kAzl71.ttf +0 -0
  97. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/materialdesignicons-webfont-DttUABo4.eot +0 -0
  98. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/tinymce/dark-first/content.min.css +0 -0
  99. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/tinymce/dark-first/skin.min.css +0 -0
  100. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/tinymce/dark-slim/content.min.css +0 -0
  101. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/tinymce/dark-slim/skin.min.css +0 -0
  102. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/tinymce/img/example.png +0 -0
  103. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/tinymce/img/tinymce.woff2 +0 -0
  104. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/tinymce/lightgray/content.min.css +0 -0
  105. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/tinymce/lightgray/fonts/tinymce.woff +0 -0
  106. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/tinymce/lightgray/skin.min.css +0 -0
  107. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/tinymce/plugins/accordion/css/accordion.css +0 -0
  108. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/tinymce/plugins/accordion/plugin.js +0 -0
  109. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/tinymce/plugins/codesample/css/prism.css +0 -0
  110. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/tinymce/plugins/customLink/css/link.css +0 -0
  111. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/tinymce/plugins/customLink/plugin.js +0 -0
  112. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/tinymce/tinymce.min.js +0 -0
  113. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/static/vanilla-picker-B6E6ObS_.js +0 -0
  114. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/translations.py +0 -0
  115. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin/utils.py +0 -0
  116. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin.egg-info/dependency_links.txt +0 -0
  117. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/brilliance_admin.egg-info/top_level.txt +0 -0
  118. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/setup.cfg +0 -0
  119. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/tests/test_payments_fields_schema.py +0 -0
  120. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/tests/test_sqlalcmeny_auth.py +0 -0
  121. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/tests/test_sqlalcmeny_crud.py +0 -0
  122. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/tests/test_sqlalcmeny_filters.py +0 -0
  123. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/tests/test_sqlalcmeny_schema.py +0 -0
  124. {brilliance_admin-0.44.0 → brilliance_admin-0.44.2}/tests/test_translations.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brilliance-admin
3
- Version: 0.44.0
3
+ Version: 0.44.2
4
4
  Summary: Simple and lightweight data managment framework powered by FastAPI and Vue3 Vuetify all-in-one. Some call it heavenly in its brilliance.
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.10
@@ -16,6 +16,9 @@ Requires-Dist: faker>=38.2.0; extra == "example"
16
16
  Requires-Dist: pyjwt>=2.10.1; extra == "example"
17
17
  Requires-Dist: structlog>=25.5.0; extra == "example"
18
18
  Requires-Dist: rich>=14.2.0; extra == "example"
19
+ Requires-Dist: asyncpg>=0.31.0; extra == "example"
20
+ Requires-Dist: pydantic-settings>=2.12.0; extra == "example"
21
+ Requires-Dist: twine; extra == "example"
19
22
  Provides-Extra: tests
20
23
  Requires-Dist: pytest>=8.4.2; extra == "tests"
21
24
  Requires-Dist: pytest-asyncio>=1.2.0; extra == "tests"
@@ -8,7 +8,8 @@ from brilliance_admin.exceptions import AdminAPIException, APIError
8
8
  from brilliance_admin.schema import AdminSchema
9
9
  from brilliance_admin.schema.table.admin_action import ActionData, ActionResult
10
10
  from brilliance_admin.schema.table.category_table import CategoryTable
11
- from brilliance_admin.schema.table.table_models import CreateResult, ListData, RetrieveResult, TableListResult, UpdateResult
11
+ from brilliance_admin.schema.table.table_models import (
12
+ CreateResult, ListData, RetrieveResult, TableListResult, UpdateResult)
12
13
  from brilliance_admin.translations import LanguageContext
13
14
  from brilliance_admin.utils import get_logger
14
15
 
@@ -122,6 +122,7 @@ class SQLAlchemyJWTAdminAuthentication(AdminAuthentication):
122
122
  try:
123
123
  async with self.db_async_session() as session:
124
124
  result = await session.execute(stmt)
125
+ user = result.scalar_one_or_none()
125
126
 
126
127
  except ConnectionRefusedError as e:
127
128
  logger.exception(
@@ -133,8 +134,6 @@ class SQLAlchemyJWTAdminAuthentication(AdminAuthentication):
133
134
  status_code=500,
134
135
  ) from e
135
136
 
136
- user = result.scalar_one_or_none()
137
-
138
137
  if not user:
139
138
  raise AdminAPIException(
140
139
  APIError(message="User not found", code="user_not_found"),
@@ -6,7 +6,7 @@ from brilliance_admin.auth import UserABC
6
6
  from brilliance_admin.exceptions import AdminAPIException, APIError, FieldError
7
7
  from brilliance_admin.schema.category import FieldSchemaData
8
8
  from brilliance_admin.schema.table.fields.base import TableField
9
- from brilliance_admin.schema.table.table_models import Record
9
+ from brilliance_admin.schema.table.table_models import AutocompleteData, Record
10
10
  from brilliance_admin.translations import LanguageContext
11
11
  from brilliance_admin.translations import TranslateText as _
12
12
  from brilliance_admin.utils import DeserializeAction
@@ -96,10 +96,9 @@ class SQLAlchemyRelatedField(TableField):
96
96
  msg = f'Cannot resolve target model for FK "{field_slug}"'
97
97
  raise AttributeError(msg)
98
98
 
99
- async def autocomplete(self, model, data, user, *, extra: dict | None = None) -> List[Record]:
99
+ async def autocomplete(self, model, data: AutocompleteData, user, *, extra: dict | None = None) -> List[Record]:
100
100
  # pylint: disable=import-outside-toplevel
101
101
  from sqlalchemy import select
102
- from sqlalchemy.sql import expression
103
102
 
104
103
  if extra is None or extra.get('db_async_session') is None:
105
104
  msg = f'SQLAlchemyRelatedField.autocomplete {type(self).__name__} requires extra["db_async_session"] (AsyncSession)'
@@ -113,23 +112,37 @@ class SQLAlchemyRelatedField(TableField):
113
112
  limit = min(150, data.limit)
114
113
  stmt = select(target_model).limit(limit)
115
114
 
115
+ pk = get_pk(target_model)
116
+ python_pk_type = pk.property.columns[0].type.python_type
117
+
116
118
  if data.search_string:
117
- if hasattr(target_model, 'id'):
118
- stmt = stmt.where(getattr(target_model, 'id') == data.search_string)
119
+ try:
120
+ value = python_pk_type(data.search_string)
121
+ except (ValueError, TypeError):
122
+ # Search string cannot be cast to primary key type, skip id filter
123
+ value = None
124
+
125
+ stmt = stmt.where(pk == value)
119
126
 
120
127
  # Add already selected choices
121
- existed_choices = []
122
128
  if data.existed_choices:
123
129
  existed_choices = [i['key'] for i in data.existed_choices if 'key' in i]
124
130
 
125
- if existed_choices and hasattr(target_model, 'id'):
126
- stmt = stmt.where(getattr(target_model, 'id').in_(existed_choices) | expression.true())
131
+ values = []
132
+ for value in existed_choices:
133
+ try:
134
+ values.append(python_pk_type(value))
135
+ except (ValueError, TypeError) as e:
136
+ msg = f'Invalid existed_choices value "{value}" for pk {pk} python_pk_type:{python_pk_type.__name__}'
137
+ raise AdminAPIException(APIError(message=msg), status_code=500) from e
138
+
139
+ stmt = stmt.where(pk.in_(values))
127
140
 
128
141
  async with db_async_session() as session:
129
142
  records = (await session.execute(stmt)).scalars().all()
130
143
 
131
144
  for record in records:
132
- results.append(Record(key=getattr(record, 'id'), title=str(record)))
145
+ results.append(Record(key=getattr(record, pk.key), title=str(record)))
133
146
 
134
147
  return results
135
148
 
@@ -8,6 +8,7 @@ from pydantic.dataclasses import dataclass
8
8
  from brilliance_admin.exceptions import FieldError
9
9
  from brilliance_admin.schema.category import FieldSchemaData
10
10
  from brilliance_admin.translations import LanguageContext
11
+ from brilliance_admin.translations import TranslateText as _
11
12
  from brilliance_admin.utils import DeserializeAction, SupportsStr, humanize_field_name
12
13
 
13
14
 
@@ -80,7 +81,7 @@ class IntegerField(TableField):
80
81
  async def deserialize(self, value, action: DeserializeAction, extra: dict, *args, **kwargs) -> Any:
81
82
  value = await super().deserialize(value, action, extra, *args, **kwargs)
82
83
  if value and not isinstance(value, int):
83
- raise FieldError(f'bad int type: {type(value)}')
84
+ raise FieldError(_('errors.bad_type_error') % {'type': type(value), 'expected': 'init'})
84
85
 
85
86
  return value
86
87
 
@@ -116,7 +117,7 @@ class StringField(TableField):
116
117
  async def deserialize(self, value, action: DeserializeAction, extra: dict, *args, **kwargs) -> Any:
117
118
  value = await super().deserialize(value, action, extra, *args, **kwargs)
118
119
  if value and not isinstance(value, str):
119
- raise FieldError(f'bad string type: {type(value)}')
120
+ raise FieldError(_('errors.bad_type_error') % {'type': type(value), 'expected': 'string'})
120
121
 
121
122
  return value
122
123
 
@@ -151,7 +152,7 @@ class DateTimeField(TableField):
151
152
  return
152
153
 
153
154
  if value and not isinstance(value, (str, dict)):
154
- raise FieldError(f'bad datetime type: {type(value)}')
155
+ raise FieldError(_('errors.bad_type_error') % {'type': type(value), 'expected': 'datetime'})
155
156
 
156
157
  if isinstance(value, str):
157
158
  return datetime.datetime.strptime(value, self.format)
@@ -166,13 +167,24 @@ class DateTimeField(TableField):
166
167
  'to': datetime.datetime.strptime(value.get('to'), self.format),
167
168
  }
168
169
 
169
- raise NotImplementedError(f'Value "{value}" is not supporetd for datetime')
170
+ raise FieldError(_('errors.bad_type_error') % {'type': type(value), 'expected': 'datetime'})
170
171
 
171
172
 
172
173
  @dataclass
173
174
  class JSONField(TableField):
174
175
  _type = 'json'
175
176
 
177
+ async def deserialize(self, value, action: DeserializeAction, extra: dict, *args, **kwargs) -> Any:
178
+ value = await super().deserialize(value, action, extra, *args, **kwargs)
179
+
180
+ if value is None:
181
+ return
182
+
183
+ if not isinstance(value, (dict, list)):
184
+ raise FieldError(_('errors.bad_type_error') % {'type': type(value), 'expected': 'JSON'})
185
+
186
+ return value
187
+
176
188
 
177
189
  @dataclass
178
190
  class ArrayField(TableField):
@@ -190,8 +202,11 @@ class ArrayField(TableField):
190
202
  async def deserialize(self, value, action: DeserializeAction, extra: dict, *args, **kwargs) -> Any:
191
203
  value = await super().deserialize(value, action, extra, *args, **kwargs)
192
204
 
193
- if value and not isinstance(value, list):
194
- raise FieldError(f'bad array type: {type(value)}')
205
+ if value is None:
206
+ return
207
+
208
+ if not isinstance(value, list):
209
+ raise FieldError(_('errors.bad_type_error') % {'type': type(value), 'expected': 'Array'})
195
210
 
196
211
  return value
197
212