From e6d8bb062ef6b880b842ba9ef967fde0c6683511 Mon Sep 17 00:00:00 2001 From: vsdudakov Date: Sat, 3 Aug 2024 19:35:22 +0300 Subject: [PATCH] Fix dashboard widgets and auto register inlines. --- docs/build.py | 11 +++- docs/code/dashboard/djangoorm.py | 25 ++++---- docs/code/dashboard/tortoise.py | 21 +++---- docs/code/inlines/tortoise.py | 6 -- docs/index.html | 60 +++++++++---------- fastadmin/models/helpers.py | 12 ++-- fastadmin/static/index.min.js | 4 +- .../src/components/dashboard-widget/index.tsx | 46 +++++++------- .../src/components/filter-column/index.tsx | 17 +++--- frontend/src/interfaces/configuration.ts | 8 ++- pyproject.toml | 2 +- 11 files changed, 106 insertions(+), 106 deletions(-) diff --git a/docs/build.py b/docs/build.py index e4272a5..8e39c92 100644 --- a/docs/build.py +++ b/docs/build.py @@ -30,6 +30,7 @@ class App: NAME = "FastAdmin" AUTHOR_NAME = "Seva D." AUTHOR_EMAIL = "vsdudakov@gmail.com" +ANTD_CHARTS_EXAMPLES = "https://ant-design-charts.antgroup.com/en/examples" def read_cls_docstring(cls): @@ -38,6 +39,12 @@ def read_cls_docstring(cls): def get_versions(): return [ + { + "version": "0.2.4", + "changes": [ + "Fix dashboard widgets and auto register inlines.", + ], + }, { "version": "0.2.3", "changes": [ @@ -444,7 +451,7 @@ def get_page_context(page_url): {"type": "code-python", "content": inspect.getsource(DashboardWidgetAdmin)}, { "type": "alert-warning", - "content": "Note: Please see antd charts for x_field_filter_widget_props.", + "content": f"Note: Please see antd charts for x_field_filter_widget_props.", }, ] case "#widget-chart-types": @@ -456,7 +463,7 @@ def get_page_context(page_url): {"type": "code-python", "content": inspect.getsource(DashboardWidgetType)}, { "type": "alert-warning", - "content": "Note: Please see antd charts for more details (e.g. to see how they look like).", + "content": f"Note: Please see antd charts for more details (e.g. to see how they look like).", }, ] # models diff --git a/docs/code/dashboard/djangoorm.py b/docs/code/dashboard/djangoorm.py index 6349fb1..a273112 100644 --- a/docs/code/dashboard/djangoorm.py +++ b/docs/code/dashboard/djangoorm.py @@ -1,5 +1,4 @@ -from datetime import datetime, timedelta, timezone -from typing import Any +import datetime from django.db import connection, models @@ -20,33 +19,35 @@ def __str__(self): class UsersDashboardWidgetAdmin(DashboardWidgetAdmin): title = "Users" dashboard_widget_type = DashboardWidgetType.ChartLine + x_field = "date" - y_field = "count" x_field_filter_widget_type = WidgetType.DatePicker - x_field_filter_widget_props = {"picker": "month"} # noqa: RUF012 + x_field_filter_widget_props: dict[str, str] = {"picker": "month"} # noqa: RUF012 x_field_periods = ["day", "week", "month", "year"] # noqa: RUF012 + y_field = "count" + def get_data( # type: ignore [override] self, min_x_field: str | None = None, max_x_field: str | None = None, period_x_field: str | None = None, - ) -> dict[str, Any]: + ) -> dict: def dictfetchall(cursor): columns = [col[0] for col in cursor.description] return [dict(zip(columns, row, strict=True)) for row in cursor.fetchall()] with connection.cursor() as c: if not min_x_field: - min_x_field_date = datetime.now(timezone.utc) - timedelta(days=360) + min_x_field_date = datetime.datetime.now(tz=datetime.UTC) - datetime.timedelta(days=360) else: - min_x_field_date = datetime.fromisoformat(min_x_field.replace("Z", "+00:00")) + min_x_field_date = datetime.datetime.fromisoformat(min_x_field) if not max_x_field: - max_x_field_date = datetime.now(timezone.utc) + timedelta(days=1) + max_x_field_date = datetime.datetime.now(tz=datetime.UTC) + datetime.timedelta(days=1) else: - max_x_field_date = datetime.fromisoformat(max_x_field.replace("Z", "+00:00")) + max_x_field_date = datetime.datetime.fromisoformat(max_x_field) - if not period_x_field or period_x_field not in self.x_field_periods: + if not period_x_field or period_x_field not in (self.x_field_periods or []): period_x_field = "month" c.execute( @@ -63,7 +64,7 @@ def dictfetchall(cursor): results = dictfetchall(c) return { "results": results, - "min_x_field": min_x_field_date.isoformat().replace("+00:00", "Z"), - "max_x_field": max_x_field_date.isoformat().replace("+00:00", "Z"), + "min_x_field": min_x_field_date.isoformat(), + "max_x_field": max_x_field_date.isoformat(), "period_x_field": period_x_field, } diff --git a/docs/code/dashboard/tortoise.py b/docs/code/dashboard/tortoise.py index 31ca4cd..56915d7 100644 --- a/docs/code/dashboard/tortoise.py +++ b/docs/code/dashboard/tortoise.py @@ -1,5 +1,4 @@ -from datetime import datetime, timedelta, timezone -from typing import Any +import datetime from tortoise import Tortoise, fields from tortoise.models import Model @@ -21,12 +20,14 @@ def __str__(self): class UsersDashboardWidgetAdmin(DashboardWidgetAdmin): title = "Users" dashboard_widget_type = DashboardWidgetType.ChartLine + x_field = "date" - y_field = "count" x_field_filter_widget_type = WidgetType.DatePicker - x_field_filter_widget_props: dict[str, Any] = {"picker": "month"} # noqa: RUF012 + x_field_filter_widget_props: dict[str, str] = {"picker": "month"} # noqa: RUF012 x_field_periods = ["day", "week", "month", "year"] # noqa: RUF012 + y_field = "count" + async def get_data( self, min_x_field: str | None = None, @@ -36,13 +37,13 @@ async def get_data( conn = Tortoise.get_connection("default") if not min_x_field: - min_x_field_date = datetime.now(timezone.utc) - timedelta(days=360) + min_x_field_date = datetime.datetime.now(tz=datetime.UTC) - datetime.timedelta(days=360) else: - min_x_field_date = datetime.fromisoformat(min_x_field.replace("Z", "+00:00")) + min_x_field_date = datetime.datetime.fromisoformat(min_x_field) if not max_x_field: - max_x_field_date = datetime.now(timezone.utc) + timedelta(days=1) + max_x_field_date = datetime.datetime.now(tz=datetime.UTC) + datetime.timedelta(days=1) else: - max_x_field_date = datetime.fromisoformat(max_x_field.replace("Z", "+00:00")) + max_x_field_date = datetime.datetime.fromisoformat(max_x_field) if not period_x_field or period_x_field not in (self.x_field_periods or []): period_x_field = "month" @@ -60,7 +61,7 @@ async def get_data( ) return { "results": results, - "min_x_field": min_x_field_date.isoformat().replace("+00:00", "Z"), - "max_x_field": max_x_field_date.isoformat().replace("+00:00", "Z"), + "min_x_field": min_x_field_date.isoformat(), + "max_x_field": max_x_field_date.isoformat(), "period_x_field": period_x_field, } diff --git a/docs/code/inlines/tortoise.py b/docs/code/inlines/tortoise.py index 396a694..9513f2e 100644 --- a/docs/code/inlines/tortoise.py +++ b/docs/code/inlines/tortoise.py @@ -25,12 +25,6 @@ def __str__(self): return self.message -@register(InlineUser) -class InlineUserAdmin(TortoiseModelAdmin): - # we have to register model for inline usage - pass - - class UserMessageAdminInline(TortoiseInlineModelAdmin): model = InlineUserMessage list_display = ("user", "message") diff --git a/docs/index.html b/docs/index.html index 6edbebc..d20ebbe 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2,7 +2,7 @@
  • - -->

    FastAdmin | Documentation

    • Created: 7 March 2023
    • Updated: 02 August 2024

    Introduction

    FastAdmin is an easy-to-use Admin Dashboard App for FastAPI/Django/Flask inspired by Django Admin.

    FastAdmin was built with relations in mind and admiration for the excellent and popular Django Admin. It's engraved in its design that you may configure your admin dashboard for FastAPI/Django/Flask easiest way.

    FastAdmin is designed to be minimalistic, functional and yet familiar.


    Getting Started

    If you have any questions that are beyond the scope of the documentation, Please feel free to email us.

    Installation

    Follow the steps below to setup FastAdmin:

    Install the package using pip:

    Note: For zsh and macos use: pip install fastadmin[fastapi,django]

    +              -->  

    FastAdmin | Documentation

    • Created: 7 March 2023
    • Updated: 03 August 2024

    Introduction

    FastAdmin is an easy-to-use Admin Dashboard App for FastAPI/Django/Flask inspired by Django Admin.

    FastAdmin was built with relations in mind and admiration for the excellent and popular Django Admin. It's engraved in its design that you may configure your admin dashboard for FastAPI/Django/Flask easiest way.

    FastAdmin is designed to be minimalistic, functional and yet familiar.


    Getting Started

    If you have any questions that are beyond the scope of the documentation, Please feel free to email us.

    Installation

    Follow the steps below to setup FastAdmin:

    Install the package using pip:

    Note: For zsh and macos use: pip install fastadmin[fastapi,django]

       
     
     pip install fastadmin[fastapi,django]  # for fastapi with django orm
    @@ -286,8 +286,7 @@
       
     

    Note: Settings without default values are required.


    Dashboard Widget Admins

    Registering Widgets

    Register Dashboard widgets

       
    -from datetime import datetime, timedelta, timezone
    -from typing import Any
    +import datetime
     
     from tortoise import Tortoise, fields
     from tortoise.models import Model
    @@ -309,12 +308,14 @@
     class UsersDashboardWidgetAdmin(DashboardWidgetAdmin):
         title = "Users"
         dashboard_widget_type = DashboardWidgetType.ChartLine
    +
         x_field = "date"
    -    y_field = "count"
         x_field_filter_widget_type = WidgetType.DatePicker
    -    x_field_filter_widget_props: dict[str, Any] = {"picker": "month"}  # noqa: RUF012
    +    x_field_filter_widget_props: dict[str, str] = {"picker": "month"}  # noqa: RUF012
         x_field_periods = ["day", "week", "month", "year"]  # noqa: RUF012
     
    +    y_field = "count"
    +
         async def get_data(
             self,
             min_x_field: str | None = None,
    @@ -324,13 +325,13 @@
             conn = Tortoise.get_connection("default")
     
             if not min_x_field:
    -            min_x_field_date = datetime.now(timezone.utc) - timedelta(days=360)
    +            min_x_field_date = datetime.datetime.now(tz=datetime.UTC) - datetime.timedelta(days=360)
             else:
    -            min_x_field_date = datetime.fromisoformat(min_x_field.replace("Z", "+00:00"))
    +            min_x_field_date = datetime.datetime.fromisoformat(min_x_field)
             if not max_x_field:
    -            max_x_field_date = datetime.now(timezone.utc) + timedelta(days=1)
    +            max_x_field_date = datetime.datetime.now(tz=datetime.UTC) + datetime.timedelta(days=1)
             else:
    -            max_x_field_date = datetime.fromisoformat(max_x_field.replace("Z", "+00:00"))
    +            max_x_field_date = datetime.datetime.fromisoformat(max_x_field)
     
             if not period_x_field or period_x_field not in (self.x_field_periods or []):
                 period_x_field = "month"
    @@ -348,16 +349,15 @@
             )
             return {
                 "results": results,
    -            "min_x_field": min_x_field_date.isoformat().replace("+00:00", "Z"),
    -            "max_x_field": max_x_field_date.isoformat().replace("+00:00", "Z"),
    +            "min_x_field": min_x_field_date.isoformat(),
    +            "max_x_field": max_x_field_date.isoformat(),
                 "period_x_field": period_x_field,
             }
     
       
     
       
    -from datetime import datetime, timedelta, timezone
    -from typing import Any
    +import datetime
     
     from django.db import connection, models
     
    @@ -378,33 +378,35 @@
     class UsersDashboardWidgetAdmin(DashboardWidgetAdmin):
         title = "Users"
         dashboard_widget_type = DashboardWidgetType.ChartLine
    +
         x_field = "date"
    -    y_field = "count"
         x_field_filter_widget_type = WidgetType.DatePicker
    -    x_field_filter_widget_props = {"picker": "month"}  # noqa: RUF012
    +    x_field_filter_widget_props: dict[str, str] = {"picker": "month"}  # noqa: RUF012
         x_field_periods = ["day", "week", "month", "year"]  # noqa: RUF012
     
    +    y_field = "count"
    +
         def get_data(  # type: ignore [override]
             self,
             min_x_field: str | None = None,
             max_x_field: str | None = None,
             period_x_field: str | None = None,
    -    ) -> dict[str, Any]:
    +    ) -> dict:
             def dictfetchall(cursor):
                 columns = [col[0] for col in cursor.description]
                 return [dict(zip(columns, row, strict=True)) for row in cursor.fetchall()]
     
             with connection.cursor() as c:
                 if not min_x_field:
    -                min_x_field_date = datetime.now(timezone.utc) - timedelta(days=360)
    +                min_x_field_date = datetime.datetime.now(tz=datetime.UTC) - datetime.timedelta(days=360)
                 else:
    -                min_x_field_date = datetime.fromisoformat(min_x_field.replace("Z", "+00:00"))
    +                min_x_field_date = datetime.datetime.fromisoformat(min_x_field)
                 if not max_x_field:
    -                max_x_field_date = datetime.now(timezone.utc) + timedelta(days=1)
    +                max_x_field_date = datetime.datetime.now(tz=datetime.UTC) + datetime.timedelta(days=1)
                 else:
    -                max_x_field_date = datetime.fromisoformat(max_x_field.replace("Z", "+00:00"))
    +                max_x_field_date = datetime.datetime.fromisoformat(max_x_field)
     
    -            if not period_x_field or period_x_field not in self.x_field_periods:
    +            if not period_x_field or period_x_field not in (self.x_field_periods or []):
                     period_x_field = "month"
     
                 c.execute(
    @@ -421,8 +423,8 @@
                 results = dictfetchall(c)
                 return {
                     "results": results,
    -                "min_x_field": min_x_field_date.isoformat().replace("+00:00", "Z"),
    -                "max_x_field": max_x_field_date.isoformat().replace("+00:00", "Z"),
    +                "min_x_field": min_x_field_date.isoformat(),
    +                "max_x_field": max_x_field_date.isoformat(),
                     "period_x_field": period_x_field,
                 }
     
    @@ -463,7 +465,7 @@
             raise NotImplementedError
     
       
    -

    Note: Please see antd charts for x_field_filter_widget_props.

    Chart Types

    There are widget types which fastadmin dashboard supports:

    +

    Note: Please see antd charts for x_field_filter_widget_props.

    Chart Types

    There are widget types which fastadmin dashboard supports:

       
     class DashboardWidgetType(str, Enum):
         """Dashboard Widget type"""
    @@ -475,7 +477,7 @@
         ChartPie = "ChartPie"
     
       
    -

    Note: Please see antd charts for more details (e.g. to see how they look like).


    Model Admins

    Registering Models

    +

    Note: Please see antd charts for more details (e.g. to see how they look like).


    Model Admins

    Registering Models

       
     from uuid import UUID
     
    @@ -1197,12 +1199,6 @@
             return self.message
     
     
    -@register(InlineUser)
    -class InlineUserAdmin(TortoiseModelAdmin):
    -    # we have to register model for inline usage
    -    pass
    -
    -
     class UserMessageAdminInline(TortoiseInlineModelAdmin):
         model = InlineUserMessage
         list_display = ("user", "message")
    @@ -1286,7 +1282,7 @@
         min_num: int = 1
     
       
    -

    Changelog

    See what's new added, changed, fixed, improved or updated in the latest versions.

    v0.2.3

    Fix filters issue on lists. Remove jinja from dependencies.

    v0.2.2

    Fix bugs with datetime.

    v0.2.1

    Update packages. Fix linters and tests in vite frontend. Removed pydantic from dependencies.

    v0.2.0

    Update packages. Use vite instead obsolete react-scripts.

    v0.1.41

    Fixed bug with datetime. Added verbose name logic for models and inlines. Updated frontend libraries.

    v0.1.40

    Added RUFF linter. Cleaned up code. Removed pydantic dependency.

    v0.1.39

    Bug fixes.

    v0.1.38

    Bug fixes.

    v0.1.37

    Bug fixes.

    v0.1.36

    Added autogeneration of documentation and examples.

    v0.1.35

    Added DashboardWidgetAdmin class and charts for dashboard.

    v0.1.34

    Added SlugInput, EmailInput, PhoneInput, UrlInput, JsonTextArea widget types.

    v0.1.33

    Added list_display_widths parameter.

    v0.1.32

    Added Upload widget type.

    v0.1.31

    Added PasswordInput widget type.