diff --git a/README.rst b/README.rst
index 29070ac..cfb1275 100644
--- a/README.rst
+++ b/README.rst
@@ -173,10 +173,43 @@ Search Fields
~~~~~~~~~~~~~
Defined ``menu.search_fields`` in ``menu`` will render a search form by fields.
-Excel Export
-~~~~~~~~~~~~
+Xlsx Export
+~~~~~~~~~~~
FastAPI-admin can export searched data to excel file when define ``{export : True}`` in ``menu.actions``.
+Bulk Actions
+~~~~~~~~~~~~
+Current FastAPI-admin support builtin bulk action ``delete_all``,if you want write your own bulk actions:
+
+1. pass ``bulk_actions`` in ``Menu``,example:
+
+.. code-block:: python
+
+ Menu(
+ ...
+ bulk_actions=[{
+ 'value': 'delete', # this is fastapi router path param.
+ 'text': 'delete_all', # this will show in front.
+ }]
+ )
+
+2. write fastapi route,example:
+
+.. code-block:: python
+
+ from fastapi_admin.schemas import BulkIn
+ from fastapi_admin.factory import app as admin_app
+
+ @admin_app.post(
+ '/{resource}/bulk/delete' # delete is defined before.
+ )
+ async def bulk_delete(
+ bulk_in: BulkIn,
+ model=Depends(get_model)
+ ):
+ await model.filter(pk__in=bulk_in.pk_list).delete()
+ return {'success': True}
+
Deployment
==========
1. Deploy fastapi app by gunicorn+uvicorn or reference https://fastapi.tiangolo.com/deployment/.
diff --git a/examples/main.py b/examples/main.py
index 4dfb427..19bb3af 100644
--- a/examples/main.py
+++ b/examples/main.py
@@ -50,7 +50,7 @@ def create_app():
url='/rest/App',
icon='fa fa-pencil',
sort_fields=('uaid',),
- search_fields=('uaid',)
+ search_fields=('uaid',),
),
Menu(
name='多对多测试',
@@ -97,7 +97,8 @@ def create_app():
Menu(
name='请求日志',
url='/rest/ApiLog',
- icon='fa fa-sticky-note'
+ icon='fa fa-sticky-note',
+ search_fields=('app',),
),
Menu(
name='App版本',
diff --git a/fastapi_admin/__init__.py b/fastapi_admin/__init__.py
index 7b2765b..8c4fc73 100644
--- a/fastapi_admin/__init__.py
+++ b/fastapi_admin/__init__.py
@@ -1,3 +1,3 @@
from . import routes
-__version__ = '0.1.7'
+__version__ = '0.1.8'
diff --git a/fastapi_admin/depends.py b/fastapi_admin/depends.py
index 9eb2d99..78807fc 100644
--- a/fastapi_admin/depends.py
+++ b/fastapi_admin/depends.py
@@ -27,7 +27,7 @@ class QueryItem(BaseModel):
page: int = 1
sort: dict
where: dict
- with_: dict
+ with_: dict = {}
size: int = 10
sort: dict = {}
diff --git a/fastapi_admin/factory.py b/fastapi_admin/factory.py
index 7300680..7a839bd 100644
--- a/fastapi_admin/factory.py
+++ b/fastapi_admin/factory.py
@@ -1,5 +1,5 @@
import importlib
-from typing import Type
+from typing import Type, List, Dict
from fastapi import FastAPI
from tortoise import Model, Tortoise
@@ -26,7 +26,7 @@ class AdminApp(FastAPI):
'SmallIntField': 'number',
'JSONField': 'json',
}
- model_menu_mapping = {}
+ model_menu_mapping: Dict[str, Menu] = {}
def _get_model_menu_mapping(self):
for menu in filter(lambda x: x.url, self.site.menus):
@@ -179,7 +179,8 @@ class AdminApp(FastAPI):
title=model_describe.get('description') or resource.title(),
fields=fields,
searchFields=search_fields,
- pk=pk
+ pk=pk,
+ bulk_actions=self.model_menu_mapping[resource].bulk_actions,
)
diff --git a/fastapi_admin/routes/index.py b/fastapi_admin/routes/index.py
index abdbcd9..5a2e519 100644
--- a/fastapi_admin/routes/index.py
+++ b/fastapi_admin/routes/index.py
@@ -10,6 +10,7 @@ from ..common import handle_m2m_fields_create_or_update
from ..depends import QueryItem, get_query, parse_body, get_model
from ..factory import app
from ..responses import GetManyOut
+from ..schemas import BulkIn
from ..shortcuts import get_object_or_404
router = APIRouter()
@@ -73,6 +74,17 @@ async def view(
return resource.dict(by_alias=True, exclude_unset=True)
+@router.post(
+ '/{resource}/bulk/delete'
+)
+async def bulk_delete(
+ bulk_in: BulkIn,
+ model=Depends(get_model)
+):
+ await model.filter(pk__in=bulk_in.pk_list).delete()
+ return {'success': True}
+
+
@router.delete(
'/{resource}/{id}'
)
diff --git a/fastapi_admin/schemas.py b/fastapi_admin/schemas.py
index 5fb5c2f..7618803 100644
--- a/fastapi_admin/schemas.py
+++ b/fastapi_admin/schemas.py
@@ -1,3 +1,5 @@
+from typing import List
+
from fastapi import Body
from pydantic import BaseModel
@@ -5,3 +7,7 @@ from pydantic import BaseModel
class LoginIn(BaseModel):
username: str = Body(..., example='long2ice')
password: str = Body(..., example='123456')
+
+
+class BulkIn(BaseModel):
+ pk_list: List = Body(..., example=[1, 2, 3])
diff --git a/fastapi_admin/site.py b/fastapi_admin/site.py
index 66c266d..7153a17 100644
--- a/fastapi_admin/site.py
+++ b/fastapi_admin/site.py
@@ -23,11 +23,12 @@ class Menu(BaseModel):
# define field type,like select,radiolist,text,date
fields_type: Dict = {}
actions: Dict = {
- 'toolbar': {
- 'delete_all': True
- },
'export': True
}
+ bulk_actions: List[Dict] = [{
+ 'value': 'delete',
+ 'text': 'delete_all',
+ }]
class Site(BaseModel):
@@ -72,6 +73,7 @@ class Resource(BaseModel):
pk: str
resource_fields: Dict[str, Union[Field, Dict]]
searchFields: Optional[Dict[str, Field]]
+ bulk_actions: Optional[List[Dict]]
class Config:
fields = {
diff --git a/front/src/i18n/en-US.json b/front/src/i18n/en-US.json
index d01b44c..6c0dec5 100644
--- a/front/src/i18n/en-US.json
+++ b/front/src/i18n/en-US.json
@@ -1,6 +1,8 @@
{
"actions": {
-
+ "select_all": "Select all",
+ "clear_selected": "Clear selected",
+ "delete_all": "Delete all"
},
"messages": {
"paginate": "Total: {total}",
@@ -9,10 +11,10 @@
"deleted_all": "All deleted",
"confirm_delete": "Are you sure you want to delete?",
"image_size": "Size limit: {width}x{height}",
- "confirm_delete_all": "Are you sure you want to delete all data?",
- "no_options_text": "No Options Here!"
+ "confirm_bulk_action": "Are you sure you want to do bulk action?",
+ "no_options_text": "No Options Here!",
+ "bulk_success": "Bulk action success!"
},
"errors": {
-
}
-}
\ No newline at end of file
+}
diff --git a/front/src/i18n/zh-CN.json b/front/src/i18n/zh-CN.json
index e567b1e..cfc1ce9 100644
--- a/front/src/i18n/zh-CN.json
+++ b/front/src/i18n/zh-CN.json
@@ -16,10 +16,11 @@
"change": "更换",
"choose": "选择",
"login": "登录",
- "edit": "编辑"
+ "edit": "编辑",
+ "select_all": "选择全部",
+ "clear_selected": "清除选择"
},
"texts": {
-
},
"fields": {
"username": "用户名",
@@ -38,8 +39,9 @@
"deleted_all": "全部删除成功",
"confirm_delete": "确定要删除吗?",
"image_size": "尺寸要求:{width}x{height}",
- "confirm_delete_all": "此次操作不可恢复,确定要全部删除吗?",
- "no_options_text": "暂无可选项"
+ "confirm_bulk_action": "确定执行批量操作?",
+ "no_options_text": "暂无可选项",
+ "bulk_success": "批量操作成功!"
},
"errors": {
"too_large": "请上传小于{limit}KB的文件",
diff --git a/front/src/scss/skin.scss b/front/src/scss/skin.scss
index 16bd66f..5c6f779 100644
--- a/front/src/scss/skin.scss
+++ b/front/src/scss/skin.scss
@@ -1,62 +1,71 @@
/**
* See https://bootswatch.com/
* Change `lumen` to any other words blow:
- *
+ *
* cerulean darkly litera materia sandstone slate superhero
* cosmo flatly lumen minty simplex solar united
* cyborg journal lux pulse sketchy spacelab yeti
*/
-
+
// @import "node_modules/bootswatch/dist/lumen/bootstrap";
- .app-footer{
- border-top: none;
- color: #666;
- }
- .card {
+.app-footer {
+ border-top: none;
+ color: #666;
+}
+
+.card {
border: none;
box-shadow: 0 0 5px #ccc;
}
-.card-header{
- border:none;
+
+.card-header {
+ border: none;
font-weight: bold;
}
-.card-footer{
+
+.card-footer {
border: none;
}
+
.nav-tabs .nav-link {
- border-color:transparent !important;
+ border-color: transparent !important;
border-radius: 4px;
margin: 0 0.2em;
padding: 0.3em 1em !important;
font-weight: bold;
-
-}
-.data-form .tabs .nav-tabs {
- margin: 0 1em 1em 1em;
- border:none;
-}
-.nav-tabs .nav-link.active{
- border-color:#ccc !important;
- border-radius: 2em;
-
-}
-.tab-content{
- border-color:transparent;
+
}
-.btn{
+.data-form .tabs .nav-tabs {
+ margin: 0 1em 1em 1em;
+ border: none;
+}
+
+.nav-tabs .nav-link.active {
+ border-color: #ccc !important;
+ border-radius: 2em;
+
+}
+
+.tab-content {
+ border-color: transparent;
+}
+
+.btn {
padding: 0.5em 1.2em;
- // border-radius: 2em;
+ //border-radius: 2em;
}
.table th, .table td {
border-top: 1px solid #dee2e65c;
}
+
.table thead th {
- vertical-align: bottom;
- border-bottom: 2px solid #dee2e6;
+ vertical-align: bottom;
+ border-bottom: 2px solid #dee2e6;
}
+
.table thead th {
border-bottom: 1px solid #dee2e6;
}
diff --git a/front/src/views/CustomTable.vue b/front/src/views/CustomTable.vue
index 3b98796..2e4a19c 100644
--- a/front/src/views/CustomTable.vue
+++ b/front/src/views/CustomTable.vue
@@ -30,15 +30,6 @@
v-bind="button"
>{{button.label}}
-