From 0e95a7b1469e778324a3dcb78e0dbedd01056913 Mon Sep 17 00:00:00 2001 From: long2ice Date: Thu, 15 Oct 2020 12:54:44 +0800 Subject: [PATCH 1/3] Hide menu when no children menu. --- CHANGELOG.md | 4 ++++ fastapi_admin/__init__.py | 2 +- fastapi_admin/routes/site.py | 1 + pyproject.toml | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3512fd0..e13e6fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 0.3 +### 0.3.2 + +- Hide menu when no children menu. + ### 0.3.1 - Auto register permission. diff --git a/fastapi_admin/__init__.py b/fastapi_admin/__init__.py index dcf4f71..0202887 100644 --- a/fastapi_admin/__init__.py +++ b/fastapi_admin/__init__.py @@ -5,4 +5,4 @@ def version(): # with open("pyproject.toml") as f: # ret = re.findall(r'version = "(\d+\.\d+\.\d+)"', f.read()) # return ret[0] - return "0.3.1" + return "0.3.2" diff --git a/fastapi_admin/routes/site.py b/fastapi_admin/routes/site.py index 976b444..a443f08 100644 --- a/fastapi_admin/routes/site.py +++ b/fastapi_admin/routes/site.py @@ -30,4 +30,5 @@ async def site(user_id=Depends(jwt_optional)): model = child.url.split("/")[-1] if not await check_has_permission(user, model): site_copy.menus[index].children.remove(child) + site_copy.menus = list(filter(lambda x: x.children != [], site_copy.menus)) return site_copy.dict(by_alias=True, exclude_unset=True) diff --git a/pyproject.toml b/pyproject.toml index 5a01c98..e3bf45d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "fastapi-admin" -version = "0.3.1" +version = "0.3.2" description = "Fast Admin Dashboard based on fastapi and tortoise-orm." authors = ["long2ice "] license = "Apache-2.0" From 9b4a41b3f91a9142cd8522c12a80acee0668a382 Mon Sep 17 00:00:00 2001 From: long2ice Date: Mon, 19 Oct 2020 17:55:17 +0800 Subject: [PATCH 2/3] Add custom filters --- CHANGELOG.md | 1 + examples/filters.py | 28 ++++++++++++++++++++++++++++ examples/main.py | 4 +++- fastapi_admin/factory.py | 6 ++++++ fastapi_admin/filters.py | 36 ++++++++++++++++++++++++++++++++++++ fastapi_admin/routes/rest.py | 10 +++++++++- fastapi_admin/site.py | 5 +++-- 7 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 examples/filters.py create mode 100644 fastapi_admin/filters.py diff --git a/CHANGELOG.md b/CHANGELOG.md index e13e6fb..59e26be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### 0.3.2 - Hide menu when no children menu. +- Add custom filters. ### 0.3.1 diff --git a/examples/filters.py b/examples/filters.py new file mode 100644 index 0000000..51e7256 --- /dev/null +++ b/examples/filters.py @@ -0,0 +1,28 @@ +from typing import Any + +from tortoise import QuerySet +from tortoise.query_utils import Q + +from fastapi_admin.filters import Filter, SearchFilter, register_filter +from fastapi_admin.site import Field + + +class CustomFilter(Filter): + @classmethod + def get_queryset(cls, queryset: QuerySet) -> QuerySet: + return queryset.filter(~Q(key="test")) + + +@register_filter +class LikeFilter(SearchFilter): + @classmethod + def get_queryset(cls, queryset: QuerySet, value: Any) -> QuerySet: + return queryset.filter(name__icontains=value) + + @classmethod + async def get_field(cls) -> Field: + return Field(label="NameLike", type="text") + + @classmethod + def get_name(cls) -> str: + return "filter" diff --git a/examples/main.py b/examples/main.py index 0ac3367..c8e133d 100644 --- a/examples/main.py +++ b/examples/main.py @@ -7,6 +7,7 @@ from starlette.templating import Jinja2Templates from tortoise.contrib.fastapi import register_tortoise from tortoise.contrib.pydantic import pydantic_queryset_creator +from examples.filters import CustomFilter, LikeFilter from fastapi_admin.depends import get_model from fastapi_admin.factory import app as admin_app from fastapi_admin.schemas import BulkIn @@ -74,7 +75,7 @@ async def start_up(): name="Category", url="/rest/Category", icon="fa fa-list", - search_fields=("slug",), + search_fields=("slug", LikeFilter), ), Menu( name="Config", @@ -82,6 +83,7 @@ async def start_up(): icon="fa fa-gear", import_=True, search_fields=("key",), + custom_filters=[CustomFilter,], ), Menu( name="Product", diff --git a/fastapi_admin/factory.py b/fastapi_admin/factory.py index 0448847..9a145c8 100644 --- a/fastapi_admin/factory.py +++ b/fastapi_admin/factory.py @@ -8,6 +8,7 @@ from tortoise import Model from . import enums from .common import get_all_models, import_obj, pwd_context from .exceptions import exception_handler +from .filters import SearchFilter from .models import AbstractAdminLog, AbstractPermission, AbstractRole, AbstractUser from .schemas import LoginIn from .shortcuts import get_object_or_404 @@ -244,6 +245,11 @@ class AdminApp(FastAPI): sort_fields = menu.sort_fields fields = {} pk = name = pk_field.get("name") + # CustomSearchFilters + for search_filter in filter( + lambda x: type(x).__name__ == "type" and issubclass(x, SearchFilter), search_fields + ): + search_fields_ret[search_filter.get_name()] = await search_filter.get_field() if not exclude_pk and not self._exclude_field(resource, name): field = Field( label=pk_field.get("name").title(), diff --git a/fastapi_admin/filters.py b/fastapi_admin/filters.py new file mode 100644 index 0000000..96083f2 --- /dev/null +++ b/fastapi_admin/filters.py @@ -0,0 +1,36 @@ +from typing import Any + +from tortoise import QuerySet + +from fastapi_admin.site import Field + +_search_filters = {} + + +class Filter: + @classmethod + def get_queryset(cls, queryset: QuerySet) -> QuerySet: + raise NotImplementedError + + +class SearchFilter: + @classmethod + def get_queryset(cls, queryset: QuerySet, option: Any) -> QuerySet: + raise NotImplementedError + + @classmethod + async def get_field(cls) -> Field: + raise NotImplementedError + + @classmethod + def get_name(cls) -> str: + raise NotImplementedError + + +def register_filter(cls: SearchFilter): + _search_filters[cls.get_name()] = cls + return cls + + +def get_filter_by_name(name: str): + return _search_filters.get(name) diff --git a/fastapi_admin/routes/rest.py b/fastapi_admin/routes/rest.py index e624719..babd24d 100644 --- a/fastapi_admin/routes/rest.py +++ b/fastapi_admin/routes/rest.py @@ -27,6 +27,7 @@ from ..depends import ( update_checker, ) from ..factory import app +from ..filters import get_filter_by_name from ..responses import GetManyOut from ..schemas import BulkIn from ..shortcuts import get_object_or_404 @@ -83,8 +84,15 @@ async def get_resource( ): menu = app.model_menu_mapping[resource] qs = model.all() + for filter_ in menu.custom_filters: + qs = filter_.get_queryset(qs) if query.where: - qs = qs.filter(**query.where) + for name, value in query.where.items(): + filter_cls = get_filter_by_name(name) + if filter_cls: + qs = filter_cls.get_queryset(qs, value) + else: + qs = qs.filter(**{name: value}) sort = query.sort for k, v in sort.items(): if k in menu.sort_fields: diff --git a/fastapi_admin/site.py b/fastapi_admin/site.py index c871bcd..3f78e73 100644 --- a/fastapi_admin/site.py +++ b/fastapi_admin/site.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, List, Optional, Tuple, Type, Union from pydantic import BaseModel, HttpUrl @@ -19,7 +19,7 @@ class Menu(BaseModel): # raw id fields raw_id_fields: Optional[Tuple[str, ...]] = tuple() # searchable fields - search_fields: Optional[Tuple[str, ...]] = tuple() + search_fields = tuple() # sortable fields sort_fields: Optional[Tuple[str, ...]] = tuple() # define field type,like select,radiolist,text,date @@ -31,6 +31,7 @@ class Menu(BaseModel): import_: bool = False actions: Optional[Dict] bulk_actions: List[Dict] = [{"value": "delete", "text": "delete_all"}] + custom_filters: List = [] Menu.update_forward_refs() From c6a8409e56bd0bd04998e2986c365458465a37fb Mon Sep 17 00:00:00 2001 From: long2ice Date: Mon, 19 Oct 2020 17:57:13 +0800 Subject: [PATCH 3/3] Fix check --- examples/main.py | 2 +- fastapi_admin/depends.py | 1 - fastapi_admin/site.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/main.py b/examples/main.py index c8e133d..e068ae9 100644 --- a/examples/main.py +++ b/examples/main.py @@ -83,7 +83,7 @@ async def start_up(): icon="fa fa-gear", import_=True, search_fields=("key",), - custom_filters=[CustomFilter,], + custom_filters=[CustomFilter], ), Menu( name="Product", diff --git a/fastapi_admin/depends.py b/fastapi_admin/depends.py index a093d2d..1fcdf24 100644 --- a/fastapi_admin/depends.py +++ b/fastapi_admin/depends.py @@ -1,5 +1,4 @@ import json -from typing import Optional import jwt from fastapi import Depends, HTTPException, Path, Query diff --git a/fastapi_admin/site.py b/fastapi_admin/site.py index 3f78e73..83f657c 100644 --- a/fastapi_admin/site.py +++ b/fastapi_admin/site.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional, Tuple, Type, Union +from typing import Dict, List, Optional, Tuple, Union from pydantic import BaseModel, HttpUrl