mirror of
https://github.com/fastapi-admin/fastapi-admin.git
synced 2026-03-13 10:32:25 +08:00
update filters
This commit is contained in:
@@ -2,6 +2,7 @@ from typing import Dict, List, Optional, Type
|
||||
|
||||
from aioredis import Redis
|
||||
from fastapi import FastAPI
|
||||
from pydantic import HttpUrl
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from starlette.status import HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND, HTTP_500_INTERNAL_SERVER_ERROR
|
||||
from tortoise import Model
|
||||
@@ -29,6 +30,7 @@ class FastAPIAdmin(FastAPI):
|
||||
model_resources: Dict[Type[Model], Type[Resource]] = {}
|
||||
redis: Redis
|
||||
language_switch: bool = True
|
||||
favicon_url: Optional[HttpUrl] = None
|
||||
|
||||
async def configure(
|
||||
self,
|
||||
@@ -39,12 +41,14 @@ class FastAPIAdmin(FastAPI):
|
||||
admin_path: str = "/admin",
|
||||
template_folders: Optional[List[str]] = None,
|
||||
providers: Optional[List[Provider]] = None,
|
||||
favicon_url: Optional[HttpUrl] = None,
|
||||
):
|
||||
self.redis = redis
|
||||
i18n.set_locale(default_locale)
|
||||
self.admin_path = admin_path
|
||||
self.language_switch = language_switch
|
||||
self.logo_url = logo_url
|
||||
self.favicon_url = favicon_url
|
||||
if template_folders:
|
||||
template.add_template_folder(*template_folders)
|
||||
await self._register_providers(providers)
|
||||
|
||||
@@ -7,6 +7,7 @@ from tortoise import ForeignKeyFieldInstance, ManyToManyFieldInstance
|
||||
from tortoise import Model as TortoiseModel
|
||||
from tortoise.fields import BooleanField, DateField, DatetimeField, JSONField
|
||||
from tortoise.fields.data import CharEnumFieldInstance, IntEnumFieldInstance, IntField, TextField
|
||||
from tortoise.queryset import QuerySet
|
||||
|
||||
from fastapi_admin.exceptions import NoSuchFieldFound
|
||||
from fastapi_admin.i18n import _
|
||||
@@ -107,14 +108,15 @@ class Model(Resource):
|
||||
return ret
|
||||
|
||||
@classmethod
|
||||
async def resolve_query_params(cls, request: Request, values: dict):
|
||||
async def resolve_query_params(cls, request: Request, values: dict, qs: QuerySet):
|
||||
ret = {}
|
||||
for f in cls.filters:
|
||||
name = f.context.get("name")
|
||||
v = values.get(name)
|
||||
if v is not None and v != "":
|
||||
ret[name] = await f.parse_value(request, v)
|
||||
return ret
|
||||
qs = await f.get_queryset(request, v, qs)
|
||||
return ret, qs
|
||||
|
||||
@classmethod
|
||||
async def resolve_data(cls, request: Request, data: FormData):
|
||||
|
||||
@@ -28,9 +28,9 @@ async def list_view(
|
||||
fields_name = model_resource.get_fields_name()
|
||||
fields_label = model_resource.get_fields_label()
|
||||
fields = model_resource.get_fields()
|
||||
params = await model_resource.resolve_query_params(request, dict(request.query_params))
|
||||
qs = model.all()
|
||||
params, qs = await model_resource.resolve_query_params(request, dict(request.query_params), qs)
|
||||
filters = await model_resource.get_filters(request, params)
|
||||
qs = model.filter(**params)
|
||||
total = await qs.count()
|
||||
if page_size:
|
||||
qs = qs.limit(page_size)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<link rel="stylesheet" href="https://unpkg.com/@tabler/core@latest/dist/css/tabler-vendors.min.css">
|
||||
<link rel="stylesheet" href="https://unpkg.com/@tabler/icons@latest/iconfont/tabler-icons.min.css">
|
||||
<script src="https://kit.fontawesome.com/65694932fa.js" crossorigin="anonymous"></script>
|
||||
<link rel="icon" href="{{ request.app.favicon_url }}">
|
||||
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.js"></script>
|
||||
<script src="https://unpkg.com/@tabler/core@latest/dist/js/tabler.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></script>
|
||||
|
||||
@@ -3,35 +3,21 @@
|
||||
<div class="text-muted">
|
||||
{{ label }}:
|
||||
<div class="d-inline-block">
|
||||
<input class="form-control" type="text" id="{{ name }}" name="{{ name }}" value="{{ value }}">
|
||||
<input {% if not null %}
|
||||
required
|
||||
{% endif %} class="form-control" type="text" id="{{ name }}" name="{{ name }}" value="{{ value }}">
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(function () {
|
||||
let value = "{{value}}";
|
||||
let start, end;
|
||||
if (value !== '') {
|
||||
let s = value.split(' - ')
|
||||
start = moment(s[0]);
|
||||
end = moment(s[1]);
|
||||
} else {
|
||||
start = moment().subtract(7, 'days');
|
||||
end = moment().subtract(-1, 'days');
|
||||
}
|
||||
|
||||
let format = '{{ format }}';
|
||||
|
||||
function cb(start, end) {
|
||||
$('#{{name}} span').html(start.format(format) + ' - ' + end.format(format));
|
||||
}
|
||||
|
||||
$('#{{name}}').daterangepicker({
|
||||
startDate: start,
|
||||
endDate: end,
|
||||
let option = {
|
||||
autoUpdateInput: false,
|
||||
timePicker: true,
|
||||
timePicker24Hour: true,
|
||||
locale: {
|
||||
format: format
|
||||
format: format,
|
||||
},
|
||||
ranges: {
|
||||
'Today': [moment(), moment()],
|
||||
@@ -41,7 +27,26 @@
|
||||
'This Month': [moment().startOf('month'), moment().endOf('month')],
|
||||
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
|
||||
}
|
||||
}, cb);
|
||||
cb(start, end);
|
||||
};
|
||||
if (value !== '') {
|
||||
let s = value.split(' - ')
|
||||
option.startDate = moment(s[0]);
|
||||
option.endDate = moment(s[1]);
|
||||
}
|
||||
|
||||
function cb(start, end) {
|
||||
$('#{{name}} span').html(start.format(format) + ' - ' + end.format(format));
|
||||
}
|
||||
|
||||
let element = $('#{{name}}');
|
||||
element.daterangepicker(option, cb);
|
||||
element.on('apply.daterangepicker', function (ev, picker) {
|
||||
$(this).val(picker.startDate.format(format) + ' - ' + picker.endDate.format(format));
|
||||
});
|
||||
|
||||
element.on('cancel.daterangepicker', function (ev, picker) {
|
||||
$(this).val('');
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -4,19 +4,25 @@ from typing import Any, Optional, Type
|
||||
|
||||
from starlette.requests import Request
|
||||
from tortoise import Model
|
||||
from tortoise.queryset import QuerySet
|
||||
|
||||
from fastapi_admin import constants
|
||||
from fastapi_admin.widgets.inputs import Input
|
||||
|
||||
|
||||
class Filter(Input):
|
||||
def __init__(self, name: str, label: str, placeholder: str = ""):
|
||||
def __init__(self, name: str, label: str, placeholder: str = "", null: bool = True, **context):
|
||||
"""
|
||||
Parent class for all filters
|
||||
:param name: model field name
|
||||
:param label:
|
||||
"""
|
||||
super().__init__(name=name, label=label, placeholder=placeholder)
|
||||
super().__init__(name=name, label=label, placeholder=placeholder, null=null, **context)
|
||||
|
||||
async def get_queryset(self, request: Request, value: Any, qs: QuerySet):
|
||||
value = await self.parse_value(request, value)
|
||||
filters = {self.context.get("name"): value}
|
||||
return qs.filter(**filters)
|
||||
|
||||
|
||||
class Search(Filter):
|
||||
@@ -28,6 +34,7 @@ class Search(Filter):
|
||||
label: str,
|
||||
search_mode: str = "equal",
|
||||
placeholder: str = "",
|
||||
null: bool = True,
|
||||
):
|
||||
"""
|
||||
Search for keyword
|
||||
@@ -36,7 +43,7 @@ class Search(Filter):
|
||||
:param search_mode: equal,contains,icontains,startswith,istartswith,endswith,iendswith,iexact,search
|
||||
"""
|
||||
if search_mode == "equal":
|
||||
super().__init__(name, label, placeholder)
|
||||
super().__init__(name, label, placeholder, null)
|
||||
else:
|
||||
super().__init__(name + "__" + search_mode, label, placeholder)
|
||||
self.context.update(search_mode=search_mode)
|
||||
@@ -46,10 +53,7 @@ class Datetime(Filter):
|
||||
template = "widgets/filters/datetime.html"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
label: str,
|
||||
format_: str = constants.DATETIME_FORMAT_MOMENT,
|
||||
self, name: str, label: str, format_: str = constants.DATE_FORMAT_MOMENT, null: bool = True
|
||||
):
|
||||
"""
|
||||
Datetime filter
|
||||
@@ -57,15 +61,10 @@ class Datetime(Filter):
|
||||
:param label:
|
||||
:param format_: the format of moment.js
|
||||
"""
|
||||
super().__init__(
|
||||
name + "__range",
|
||||
label,
|
||||
)
|
||||
self.context.update(format=format_)
|
||||
super().__init__(name + "__range", label, null=null, format=format_)
|
||||
|
||||
async def parse_value(self, request: Request, value: Optional[str]):
|
||||
if value:
|
||||
return value.split(" - ")
|
||||
return value.split(" - ")
|
||||
|
||||
async def render(self, request: Request, value: Any):
|
||||
if value is not None:
|
||||
@@ -75,24 +74,16 @@ class Datetime(Filter):
|
||||
|
||||
class Date(Datetime):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
label: str,
|
||||
format_: str = constants.DATE_FORMAT_MOMENT,
|
||||
self, name: str, label: str, format_: str = constants.DATE_FORMAT_MOMENT, null: bool = True
|
||||
):
|
||||
super().__init__(
|
||||
name,
|
||||
label,
|
||||
format_,
|
||||
)
|
||||
super().__init__(name=name, label=label, format_=format_, null=null)
|
||||
|
||||
|
||||
class Select(Filter):
|
||||
template = "widgets/filters/select.html"
|
||||
|
||||
def __init__(self, name: str, label: str, null: bool = True):
|
||||
super().__init__(name, label)
|
||||
self.null = null
|
||||
super().__init__(name, label, null=null)
|
||||
|
||||
@abc.abstractmethod
|
||||
async def get_options(self):
|
||||
@@ -119,7 +110,7 @@ class Enum(Select):
|
||||
enum_type: Type = int,
|
||||
null: bool = True,
|
||||
):
|
||||
super().__init__(name, label, null)
|
||||
super().__init__(name=name, label=label, null=null)
|
||||
self.enum = enum
|
||||
self.enum_type = enum_type
|
||||
|
||||
@@ -128,7 +119,7 @@ class Enum(Select):
|
||||
|
||||
async def get_options(self):
|
||||
options = [(v.name, v.value) for v in self.enum]
|
||||
if self.null:
|
||||
if self.context.get("null"):
|
||||
options = [("", "")] + options
|
||||
return options
|
||||
|
||||
@@ -139,7 +130,7 @@ class ForeignKey(Select):
|
||||
self.model = model
|
||||
|
||||
async def get_options(self):
|
||||
ret = await self.get_queryset()
|
||||
ret = await self.get_models()
|
||||
options = [
|
||||
(
|
||||
str(x),
|
||||
@@ -147,11 +138,11 @@ class ForeignKey(Select):
|
||||
)
|
||||
for x in ret
|
||||
]
|
||||
if self.null:
|
||||
if self.context.get("null"):
|
||||
options = [("", "")] + options
|
||||
return options
|
||||
|
||||
async def get_queryset(self):
|
||||
async def get_models(self):
|
||||
return await self.model.all()
|
||||
|
||||
async def render(self, request: Request, value: Any):
|
||||
|
||||
Reference in New Issue
Block a user