From 2a8a4a25991bade36ab025535857e07ed6fe9cfc Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Fri, 23 Sep 2022 03:44:12 -0700 Subject: [PATCH 1/7] concept API for django tables --- CHANGELOG.md | 4 ++- requirements/test-env.txt | 1 + src/django_idom/components.py | 11 +++++- src/django_idom/types.py | 56 +++++++++++++++++++++++++++++- tests/test_app/components.py | 7 ++++ tests/test_app/templates/base.html | 1 + 6 files changed, 77 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 894a1390..837d0154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,9 @@ Using the following categories, list your changes in this order: ## [Unreleased] -- Nothing (Yet) +### Added + +- `django_table` component to generate HTML tables. ## [1.2.0] - 2022-09-19 diff --git a/requirements/test-env.txt b/requirements/test-env.txt index 32187f96..49e44476 100644 --- a/requirements/test-env.txt +++ b/requirements/test-env.txt @@ -1,3 +1,4 @@ django playwright twisted +django_filter diff --git a/src/django_idom/components.py b/src/django_idom/components.py index 34ce359c..64e10c38 100644 --- a/src/django_idom/components.py +++ b/src/django_idom/components.py @@ -14,7 +14,7 @@ from idom.types import VdomDict from django_idom.config import IDOM_CACHE, IDOM_VIEW_COMPONENT_IFRAMES -from django_idom.types import ViewComponentIframe +from django_idom.types import TableConfig, ViewComponentIframe # TODO: Might want to intercept href clicks and form submit events. @@ -119,6 +119,15 @@ async def async_renderer(): return rendered_view +def django_table(table_config: TableConfig): + @component + def new_component(): + print(table_config) + return None + + return new_component() + + @component def django_css(static_path: str): """Fetches a CSS static file for use within IDOM. This allows for deferred CSS loading. diff --git a/src/django_idom/types.py b/src/django_idom/types.py index 1f4bd406..f6d6274e 100644 --- a/src/django_idom/types.py +++ b/src/django_idom/types.py @@ -1,7 +1,17 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Any, Awaitable, Callable, Generic, Iterable, Optional, TypeVar, Union +from typing import ( + Any, + Awaitable, + Callable, + Generic, + Iterable, + Optional, + Tuple, + TypeVar, + Union, +) from django.db.models.base import Model from django.db.models.query import QuerySet @@ -9,6 +19,11 @@ from typing_extensions import ParamSpec +try: + from django_filters import FilterSet +except ImportError: + FilterSet = TypeVar("FilterSet") + __all__ = ["_Result", "_Params", "_Data", "IdomWebsocket", "Query", "Mutation"] _Result = TypeVar("_Result", bound=Union[Model, QuerySet[Any]]) @@ -51,3 +66,42 @@ class ViewComponentIframe: view: View | Callable args: Iterable kwargs: dict + + +@dataclass +class TableConfig: + # Will check if value exists in either the model or TableConfig class + fields: Iterable[str] + + model: Model | None = None + + # Allows for renaming columns + column_names: Iterable[Tuple[str, str]] | None = None + + # By default, all fields are sortable + sortable_fields: Iterable[str] | None = None + + # https://django-tables2.readthedocs.io/en/latest/pages/column-attributes.html#id1 + # Probably want a callable API similar to this `func(value:Any, node_type:str)`` + column_attrs: dict[Callable] | None = None + + # https://django-tables2.readthedocs.io/en/latest/pages/column-attributes.html#row-attributes + # Probably want a callable API similar to this `func(record:Model, node_type:str)`` + row_attrs: dict[Callable] | None = None + + # https://sparkbyexamples.com/pandas/pandas-sort-dataframe-by-multiple-columns/ + order_by: Iterable[str] | None = None + + # https://django-tables2.readthedocs.io/en/latest/pages/filtering.html + filterset: FilterSet | None = None + + # Zero means no pagination. + # https://docs.djangoproject.com/en/4.1/ref/paginator/#django.core.paginator.Paginator + pagination: int = 0 + + # Allows for a custom render function to change render layout + renderer: Callable | None = None + + +bs_table_column_attrs = {} +bs_table_row_attrs = {} diff --git a/tests/test_app/components.py b/tests/test_app/components.py index c103e01c..2be07114 100644 --- a/tests/test_app/components.py +++ b/tests/test_app/components.py @@ -85,6 +85,13 @@ def use_location(): ) +@component +def django_table(): + return django_idom.components.django_table( + table_config=django_idom.types.TableConfig(fields=["id", "text"]) + ) + + @component def django_css(): return html.div( diff --git a/tests/test_app/templates/base.html b/tests/test_app/templates/base.html index bea9893a..34b09eae 100644 --- a/tests/test_app/templates/base.html +++ b/tests/test_app/templates/base.html @@ -27,6 +27,7 @@

IDOM Test Page

{% component "test_app.components.use_websocket" %}
{% component "test_app.components.use_scope" %}
{% component "test_app.components.use_location" %}
+
{% component "test_app.components.django_table" %}
{% component "test_app.components.django_css" %}
{% component "test_app.components.django_js" %}
{% component "test_app.components.unauthorized_user" %}
From 265c4ed25e0f35ebc61bdf998e05b61e932aab1a Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Fri, 23 Sep 2022 03:47:03 -0700 Subject: [PATCH 2/7] see if keepdb helps with random test failures --- noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index c0c63855..9af1b2a1 100644 --- a/noxfile.py +++ b/noxfile.py @@ -56,7 +56,7 @@ def test_suite(session: Session) -> None: posargs.append("--debug-mode") session.run("playwright", "install", "chromium") - session.run("python", "manage.py", "test", *posargs) + session.run("python", "manage.py", "test", "--keepdb", *posargs) @nox.session From 30d3ca527e309f5311df048809e1599411c96404 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Thu, 29 Sep 2022 03:18:26 -0700 Subject: [PATCH 3/7] reorganization --- docs/src/features/components.md | 8 +++++ src/django_idom/tables.py | 55 ++++++++++++++++++++++++++++++++ src/django_idom/types.py | 56 +-------------------------------- 3 files changed, 64 insertions(+), 55 deletions(-) create mode 100644 src/django_idom/tables.py diff --git a/docs/src/features/components.md b/docs/src/features/components.md index 8d95c062..dbf4dbea 100644 --- a/docs/src/features/components.md +++ b/docs/src/features/components.md @@ -181,6 +181,14 @@ Convert any Django view into a IDOM component by usng this decorator. Compatible return HttpResponse("
Hello World!
") ``` +??? warning "Limitations" + + Does not currently utilize any HTML contained with a `` tag + + Does not automatically route HTTP requests beyond `GET` + + Does not intercept `` clicks work work as a SPA + ## Django CSS Allows you to defer loading a CSS stylesheet until a component begins rendering. This stylesheet must be stored within [Django's static files](https://docs.djangoproject.com/en/dev/howto/static-files/). diff --git a/src/django_idom/tables.py b/src/django_idom/tables.py new file mode 100644 index 00000000..9e4f0347 --- /dev/null +++ b/src/django_idom/tables.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Callable, Iterable, Mapping, TypeVar + +from django.db.models.base import Model + + +try: + from django_filters import FilterSet +except ImportError: + FilterSet = TypeVar("FilterSet") + + +__all__ = ["FilterSet", "TableConfig", "bs_table_column_attrs", "bs_table_row_attrs"] + + +@dataclass +class TableConfig: + # Will check if value exists in either the model or TableConfig class + # Automatically tries to get all model and TableConfig fields if `None` + fields: Iterable[str] | None = None + + model: Model | None = None + + # Allows for renaming columns in the form {old_name: new_name} + column_names: Mapping[str, str] | None = None + + # By default, all fields are sortable + sortable_fields: Iterable[str] | None = None + + # https://django-tables2.readthedocs.io/en/latest/pages/column-attributes.html#id1 + # Probably want a callable API similar to this `func(value:Any, node_type:str)`` + column_attrs: dict[Callable] | None = None + + # https://django-tables2.readthedocs.io/en/latest/pages/column-attributes.html#row-attributes + # Probably want a callable API similar to this `func(record:Model, node_type:str)`` + row_attrs: dict[Callable] | None = None + + # https://sparkbyexamples.com/pandas/pandas-sort-dataframe-by-multiple-columns/ + order_by: Iterable[str] | None = None + + # https://django-tables2.readthedocs.io/en/latest/pages/filtering.html + filterset: FilterSet | None = None + + # Zero means no pagination. + # https://docs.djangoproject.com/en/4.1/ref/paginator/#django.core.paginator.Paginator + pagination: int = 0 + + # Allows for a custom render function to change render layout + renderer: Callable | None = None + + +bs_table_column_attrs = {} +bs_table_row_attrs = {} diff --git a/src/django_idom/types.py b/src/django_idom/types.py index f6d6274e..1f4bd406 100644 --- a/src/django_idom/types.py +++ b/src/django_idom/types.py @@ -1,17 +1,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import ( - Any, - Awaitable, - Callable, - Generic, - Iterable, - Optional, - Tuple, - TypeVar, - Union, -) +from typing import Any, Awaitable, Callable, Generic, Iterable, Optional, TypeVar, Union from django.db.models.base import Model from django.db.models.query import QuerySet @@ -19,11 +9,6 @@ from typing_extensions import ParamSpec -try: - from django_filters import FilterSet -except ImportError: - FilterSet = TypeVar("FilterSet") - __all__ = ["_Result", "_Params", "_Data", "IdomWebsocket", "Query", "Mutation"] _Result = TypeVar("_Result", bound=Union[Model, QuerySet[Any]]) @@ -66,42 +51,3 @@ class ViewComponentIframe: view: View | Callable args: Iterable kwargs: dict - - -@dataclass -class TableConfig: - # Will check if value exists in either the model or TableConfig class - fields: Iterable[str] - - model: Model | None = None - - # Allows for renaming columns - column_names: Iterable[Tuple[str, str]] | None = None - - # By default, all fields are sortable - sortable_fields: Iterable[str] | None = None - - # https://django-tables2.readthedocs.io/en/latest/pages/column-attributes.html#id1 - # Probably want a callable API similar to this `func(value:Any, node_type:str)`` - column_attrs: dict[Callable] | None = None - - # https://django-tables2.readthedocs.io/en/latest/pages/column-attributes.html#row-attributes - # Probably want a callable API similar to this `func(record:Model, node_type:str)`` - row_attrs: dict[Callable] | None = None - - # https://sparkbyexamples.com/pandas/pandas-sort-dataframe-by-multiple-columns/ - order_by: Iterable[str] | None = None - - # https://django-tables2.readthedocs.io/en/latest/pages/filtering.html - filterset: FilterSet | None = None - - # Zero means no pagination. - # https://docs.djangoproject.com/en/4.1/ref/paginator/#django.core.paginator.Paginator - pagination: int = 0 - - # Allows for a custom render function to change render layout - renderer: Callable | None = None - - -bs_table_column_attrs = {} -bs_table_row_attrs = {} From 25346a063bb3448c7a7f0ed9ff71865c8cbcf698 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 2 Oct 2022 17:08:19 -0700 Subject: [PATCH 4/7] put component decorator on top --- src/django_idom/components.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/django_idom/components.py b/src/django_idom/components.py index 64e10c38..0319dc57 100644 --- a/src/django_idom/components.py +++ b/src/django_idom/components.py @@ -119,13 +119,10 @@ async def async_renderer(): return rendered_view +@component def django_table(table_config: TableConfig): - @component - def new_component(): - print(table_config) - return None - - return new_component() + print(table_config) + return None @component From cb910ccfb7984001b9046006df21c6794e22bfab Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 2 Oct 2022 17:13:26 -0700 Subject: [PATCH 5/7] fix test --- src/django_idom/components.py | 3 ++- src/django_idom/tables.py | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/django_idom/components.py b/src/django_idom/components.py index 0319dc57..619370ca 100644 --- a/src/django_idom/components.py +++ b/src/django_idom/components.py @@ -14,7 +14,8 @@ from idom.types import VdomDict from django_idom.config import IDOM_CACHE, IDOM_VIEW_COMPONENT_IFRAMES -from django_idom.types import TableConfig, ViewComponentIframe +from django_idom.tables import TableConfig +from django_idom.types import ViewComponentIframe # TODO: Might want to intercept href clicks and form submit events. diff --git a/src/django_idom/tables.py b/src/django_idom/tables.py index 9e4f0347..fff839ed 100644 --- a/src/django_idom/tables.py +++ b/src/django_idom/tables.py @@ -9,7 +9,7 @@ try: from django_filters import FilterSet except ImportError: - FilterSet = TypeVar("FilterSet") + FilterSet = TypeVar("FilterSet") # type: ignore __all__ = ["FilterSet", "TableConfig", "bs_table_column_attrs", "bs_table_row_attrs"] @@ -31,11 +31,11 @@ class TableConfig: # https://django-tables2.readthedocs.io/en/latest/pages/column-attributes.html#id1 # Probably want a callable API similar to this `func(value:Any, node_type:str)`` - column_attrs: dict[Callable] | None = None + column_attrs: dict[str, Callable | str] | None = None # https://django-tables2.readthedocs.io/en/latest/pages/column-attributes.html#row-attributes # Probably want a callable API similar to this `func(record:Model, node_type:str)`` - row_attrs: dict[Callable] | None = None + row_attrs: dict[str, Callable | str] | None = None # https://sparkbyexamples.com/pandas/pandas-sort-dataframe-by-multiple-columns/ order_by: Iterable[str] | None = None @@ -51,5 +51,5 @@ class TableConfig: renderer: Callable | None = None -bs_table_column_attrs = {} -bs_table_row_attrs = {} +bs_table_column_attrs: dict[str, Callable | str] = {} +bs_table_row_attrs: dict[str, Callable | str] = {} From d8f899e4cd23b0299820b3da2aaf469bb9836448 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 2 Oct 2022 17:57:47 -0700 Subject: [PATCH 6/7] remove vtc docs --- docs/src/features/components.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/src/features/components.md b/docs/src/features/components.md index dbf4dbea..8d95c062 100644 --- a/docs/src/features/components.md +++ b/docs/src/features/components.md @@ -181,14 +181,6 @@ Convert any Django view into a IDOM component by usng this decorator. Compatible return HttpResponse("
Hello World!
") ``` -??? warning "Limitations" - - Does not currently utilize any HTML contained with a `` tag - - Does not automatically route HTTP requests beyond `GET` - - Does not intercept `` clicks work work as a SPA - ## Django CSS Allows you to defer loading a CSS stylesheet until a component begins rendering. This stylesheet must be stored within [Django's static files](https://docs.djangoproject.com/en/dev/howto/static-files/). From caf2dbd08a2a2e3deea3528733a797153f625faf Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 2 Oct 2022 23:03:34 -0700 Subject: [PATCH 7/7] model -> data --- src/django_idom/tables.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/django_idom/tables.py b/src/django_idom/tables.py index fff839ed..93734fa7 100644 --- a/src/django_idom/tables.py +++ b/src/django_idom/tables.py @@ -1,9 +1,10 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Callable, Iterable, Mapping, TypeVar +from typing import Any, Callable, Iterable, Mapping, TypeVar from django.db.models.base import Model +from django.db.models.query import QuerySet try: @@ -17,11 +18,13 @@ @dataclass class TableConfig: - # Will check if value exists in either the model or TableConfig class + # Typically fields are contained within `data`, but they also can be defined as properties within a TableConfig subclass # Automatically tries to get all model and TableConfig fields if `None` fields: Iterable[str] | None = None - model: Model | None = None + # Data can be a model, QuerySet, or list of dictionaries + # If no data is provided, only fields declared within the user's TableConfig will be used + data: Model | QuerySet | Iterable[dict[str, Any]] | None = None # Allows for renaming columns in the form {old_name: new_name} column_names: Mapping[str, str] | None = None