sqladmin 0.19.0__tar.gz → 0.20.1__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 (54) hide show
  1. {sqladmin-0.19.0 → sqladmin-0.20.1}/PKG-INFO +1 -1
  2. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/__init__.py +1 -1
  3. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/application.py +9 -10
  4. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/models.py +43 -8
  5. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/pagination.py +4 -0
  6. {sqladmin-0.19.0 → sqladmin-0.20.1}/.gitignore +0 -0
  7. {sqladmin-0.19.0 → sqladmin-0.20.1}/LICENSE.md +0 -0
  8. {sqladmin-0.19.0 → sqladmin-0.20.1}/README.md +0 -0
  9. {sqladmin-0.19.0 → sqladmin-0.20.1}/pyproject.toml +0 -0
  10. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/_menu.py +0 -0
  11. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/_queries.py +0 -0
  12. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/_types.py +0 -0
  13. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/_validators.py +0 -0
  14. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/ajax.py +0 -0
  15. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/authentication.py +0 -0
  16. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/exceptions.py +0 -0
  17. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/fields.py +0 -0
  18. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/formatters.py +0 -0
  19. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/forms.py +0 -0
  20. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/helpers.py +0 -0
  21. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/py.typed +0 -0
  22. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/statics/css/flatpickr.min.css +0 -0
  23. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/statics/css/fontawesome.min.css +0 -0
  24. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/statics/css/main.css +0 -0
  25. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/statics/css/select2.min.css +0 -0
  26. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/statics/css/tabler-icons.min.css +0 -0
  27. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/statics/css/tabler-icons.min.css.map +0 -0
  28. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/statics/css/tabler.min.css +0 -0
  29. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/statics/js/bootstrap.min.js +0 -0
  30. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/statics/js/flatpickr.min.js +0 -0
  31. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/statics/js/jquery.min.js +0 -0
  32. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/statics/js/main.js +0 -0
  33. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/statics/js/popper.min.js +0 -0
  34. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/statics/js/select2.full.min.js +0 -0
  35. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/statics/js/tabler.min.js +0 -0
  36. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/statics/webfonts/fa-brands-400.woff2 +0 -0
  37. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/statics/webfonts/fa-regular-400.woff2 +0 -0
  38. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/statics/webfonts/fa-solid-900.woff2 +0 -0
  39. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/statics/webfonts/tabler-icons.woff2 +0 -0
  40. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/templates/sqladmin/_macros.html +0 -0
  41. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/templates/sqladmin/base.html +0 -0
  42. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/templates/sqladmin/create.html +0 -0
  43. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/templates/sqladmin/details.html +0 -0
  44. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/templates/sqladmin/edit.html +0 -0
  45. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/templates/sqladmin/error.html +0 -0
  46. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/templates/sqladmin/index.html +0 -0
  47. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/templates/sqladmin/layout.html +0 -0
  48. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/templates/sqladmin/list.html +0 -0
  49. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/templates/sqladmin/login.html +0 -0
  50. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/templates/sqladmin/modals/delete.html +0 -0
  51. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/templates/sqladmin/modals/details_action_confirmation.html +0 -0
  52. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/templates/sqladmin/modals/list_action_confirmation.html +0 -0
  53. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/templating.py +0 -0
  54. {sqladmin-0.19.0 → sqladmin-0.20.1}/sqladmin/widgets.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sqladmin
3
- Version: 0.19.0
3
+ Version: 0.20.1
4
4
  Summary: SQLAlchemy admin for FastAPI and Starlette
5
5
  Project-URL: Documentation, https://aminalaee.dev/sqladmin
6
6
  Project-URL: Issues, https://github.com/aminalaee/sqladmin/issues
@@ -1,7 +1,7 @@
1
1
  from sqladmin.application import Admin, action, expose
2
2
  from sqladmin.models import BaseView, ModelView
3
3
 
4
- __version__ = "0.19.0"
4
+ __version__ = "0.20.1"
5
5
 
6
6
  __all__ = [
7
7
  "Admin",
@@ -442,12 +442,13 @@ class Admin(BaseAdminView):
442
442
  pagination = await model_view.list(request)
443
443
  pagination.add_pagination_urls(request.url)
444
444
 
445
- if (
446
- pagination.page * pagination.page_size
447
- > pagination.count + pagination.page_size
448
- ):
449
- raise HTTPException(
450
- status_code=400, detail="Invalid page or pageSize parameter"
445
+ request_page = model_view.validate_page_number(
446
+ request.query_params.get("page"), 1
447
+ )
448
+
449
+ if request_page > pagination.page:
450
+ return RedirectResponse(
451
+ request.url.include_query_params(page=pagination.page), status_code=302
451
452
  )
452
453
 
453
454
  context = {"model_view": model_view, "pagination": pagination}
@@ -510,8 +511,7 @@ class Admin(BaseAdminView):
510
511
  identity = request.path_params["identity"]
511
512
  model_view = self._find_model_view(identity)
512
513
 
513
- Form = await model_view.scaffold_form()
514
- model_view._validate_form_class(model_view._form_create_rules, Form)
514
+ Form = await model_view.scaffold_form(model_view._form_create_rules)
515
515
  form_data = await self._handle_form_data(request)
516
516
  form = Form(form_data)
517
517
 
@@ -561,8 +561,7 @@ class Admin(BaseAdminView):
561
561
  if not model:
562
562
  raise HTTPException(status_code=404)
563
563
 
564
- Form = await model_view.scaffold_form()
565
- model_view._validate_form_class(model_view._form_edit_rules, Form)
564
+ Form = await model_view.scaffold_form(model_view._form_edit_rules)
566
565
  context = {
567
566
  "obj": model,
568
567
  "model_view": model_view,
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import json
3
4
  import time
4
5
  import warnings
5
6
  from enum import Enum
@@ -402,16 +403,16 @@ class ModelView(BaseView, metaclass=ModelViewMeta):
402
403
  Normally, objects have three save options:
403
404
  ``Save`, `Save and continue editing` and `Save and add another`.
404
405
 
405
- If save_as is True, `Save and add another` will be replaced
406
- by a `Save as new` button
407
- that creates a new object (with a new ID)
406
+ If save_as is True, `Save and add another` will be replaced
407
+ by a `Save as new` button
408
+ that creates a new object (with a new ID)
408
409
  rather than updating the existing object.
409
410
 
410
411
  By default, `save_as` is set to `False`.
411
412
  """
412
413
 
413
414
  save_as_continue: ClassVar[bool] = True
414
- """When `save_as=True`, the default redirect after saving the new object
415
+ """When `save_as=True`, the default redirect after saving the new object
415
416
  is to the edit view for that object.
416
417
  If you set `save_as_continue=False`, the redirect will be to the list view.
417
418
 
@@ -454,7 +455,7 @@ class ModelView(BaseView, metaclass=ModelViewMeta):
454
455
  ```
455
456
  """
456
457
 
457
- export_types: ClassVar[List[str]] = ["csv"]
458
+ export_types: ClassVar[List[str]] = ["csv", "json"]
458
459
  """A list of available export filetypes.
459
460
  Currently only `csv` is supported.
460
461
  """
@@ -1015,10 +1016,11 @@ class ModelView(BaseView, metaclass=ModelViewMeta):
1015
1016
  By default do nothing.
1016
1017
  """
1017
1018
 
1018
- async def scaffold_form(self) -> Type[Form]:
1019
+ async def scaffold_form(self, rules: List[str] | None = None) -> Type[Form]:
1019
1020
  if self.form is not None:
1020
1021
  return self.form
1021
- return await get_model_form(
1022
+
1023
+ form = await get_model_form(
1022
1024
  model=self.model,
1023
1025
  session_maker=self.session_maker,
1024
1026
  only=self._form_prop_names,
@@ -1032,6 +1034,11 @@ class ModelView(BaseView, metaclass=ModelViewMeta):
1032
1034
  form_converter=self.form_converter,
1033
1035
  )
1034
1036
 
1037
+ if rules:
1038
+ self._validate_form_class(rules, form)
1039
+
1040
+ return form
1041
+
1035
1042
  def search_placeholder(self) -> str:
1036
1043
  """Return search placeholder text.
1037
1044
 
@@ -1152,7 +1159,9 @@ class ModelView(BaseView, metaclass=ModelViewMeta):
1152
1159
  ) -> StreamingResponse:
1153
1160
  if export_type == "csv":
1154
1161
  return await self._export_csv(data)
1155
- raise NotImplementedError("Only export_type='csv' is implemented.")
1162
+ elif export_type == "json":
1163
+ return await self._export_json(data)
1164
+ raise NotImplementedError("Only export_type='csv' or 'json' is implemented.")
1156
1165
 
1157
1166
  async def _export_csv(
1158
1167
  self,
@@ -1179,6 +1188,32 @@ class ModelView(BaseView, metaclass=ModelViewMeta):
1179
1188
  headers={"Content-Disposition": f"attachment;filename={filename}"},
1180
1189
  )
1181
1190
 
1191
+ async def _export_json(
1192
+ self,
1193
+ data: List[Any],
1194
+ ) -> StreamingResponse:
1195
+ async def generate() -> AsyncGenerator[str, None]:
1196
+ yield "["
1197
+ len_data = len(data)
1198
+ last_idx = len_data - 1
1199
+ separator = "," if len_data > 1 else ""
1200
+
1201
+ for idx, row in enumerate(data):
1202
+ row_dict = {
1203
+ name: str(await self.get_prop_value(row, name))
1204
+ for name in self._export_prop_names
1205
+ }
1206
+ yield json.dumps(row_dict) + (separator if idx < last_idx else "")
1207
+
1208
+ yield "]"
1209
+
1210
+ filename = secure_filename(self.get_export_name(export_type="json"))
1211
+ return StreamingResponse(
1212
+ content=generate(),
1213
+ media_type="application/json",
1214
+ headers={"Content-Disposition": f"attachment;filename={filename}"},
1215
+ )
1216
+
1182
1217
  def _refresh_form_rules_cache(self) -> None:
1183
1218
  if self.form_rules:
1184
1219
  self._form_create_rules = self.form_rules
@@ -46,6 +46,10 @@ class Pagination:
46
46
 
47
47
  raise RuntimeError("Next page not found.")
48
48
 
49
+ def __post_init__(self) -> None:
50
+ # Clamp page
51
+ self.page = min(self.page, max(1, self.count // self.page_size + 1))
52
+
49
53
  def resize(self, page_size: int) -> Pagination:
50
54
  self.page = (self.page - 1) * self.page_size // page_size + 1
51
55
  self.page_size = page_size
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes