From 1705eb7c81cc4f29dc1cb5b56450196f02968c2a Mon Sep 17 00:00:00 2001 From: skelmis Date: Thu, 16 Jan 2025 19:14:43 +1300 Subject: [PATCH 1/6] Support Enum types within Forms --- .gitignore | 1 + admin_ui/src/components/NewForm.vue | 25 ++++++++++++++++++++++++- admin_ui/src/interfaces.ts | 12 ++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b005cd56..1a8c145d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.pyc .vscode/ +.idea/ .mypy_cache/ .DS_Store node_modules/ diff --git a/admin_ui/src/components/NewForm.vue b/admin_ui/src/components/NewForm.vue index 110a67b4..44bbeff8 100644 --- a/admin_ui/src/components/NewForm.vue +++ b/admin_ui/src/components/NewForm.vue @@ -4,6 +4,20 @@ v-bind:key="property.title" v-for="(property, columnName) in schema.properties" > +
+ + +
import { defineComponent, type PropType } from "vue" import InputField from "./InputField.vue" -import { type Schema, getType } from "@/interfaces" +import { Enum, Choices, type Schema, getType } from "@/interfaces" + +export function enumToChoices(data: Enum): Choices { + var choices: Choices = {} + for (let index = 0; index < data.enum.length; index++) { + const element = data.enum[index]; + choices[element] = {display_name: element, value: element} + } + return choices +} export default defineComponent({ props: { diff --git a/admin_ui/src/interfaces.ts b/admin_ui/src/interfaces.ts index 0962eafa..316ec0b7 100644 --- a/admin_ui/src/interfaces.ts +++ b/admin_ui/src/interfaces.ts @@ -114,12 +114,23 @@ export interface SchemaExtra { time_resolution: { [key: string]: number } } +export interface Enum { + title: string + type: string + enum: string[] +} + +export interface SchemaEnum { + [key: string]: Enum +} + export interface Schema { extra: SchemaExtra properties: Properties required: string[] title: string type: string + $defs: SchemaEnum } export interface Properties { @@ -145,6 +156,7 @@ export interface Property { anyOf?: AnyOf[] format?: string maxLength?: number + $ref: string | null } export interface ForeignKey { From 7617b699fe919ac986cac6a42a230459879b2f59 Mon Sep 17 00:00:00 2001 From: skelmis Date: Thu, 16 Jan 2025 19:17:53 +1300 Subject: [PATCH 2/6] Fix? v-if v-else --- admin_ui/src/components/NewForm.vue | 54 +++++++++++++++-------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/admin_ui/src/components/NewForm.vue b/admin_ui/src/components/NewForm.vue index 44bbeff8..c0cdd4ad 100644 --- a/admin_ui/src/components/NewForm.vue +++ b/admin_ui/src/components/NewForm.vue @@ -4,32 +4,34 @@ v-bind:key="property.title" v-for="(property, columnName) in schema.properties" > -
- - -
- - +
+ + +
+
+ + +
From 139b8c3e4ac0fc4f48ae43b05ca8618ee7cbdc9c Mon Sep 17 00:00:00 2001 From: skelmis Date: Thu, 16 Jan 2025 19:25:14 +1300 Subject: [PATCH 3/6] Fix function passthrough --- admin_ui/src/components/FormAdd.vue | 12 ++++++++++-- admin_ui/src/components/NewForm.vue | 15 +++++---------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/admin_ui/src/components/FormAdd.vue b/admin_ui/src/components/FormAdd.vue index 08f2266b..edb83780 100644 --- a/admin_ui/src/components/FormAdd.vue +++ b/admin_ui/src/components/FormAdd.vue @@ -33,7 +33,7 @@ v-on:submit.prevent="submitForm($event)" ref="form" > - + @@ -47,7 +47,7 @@ import axios from "axios" import { defineComponent, type PropType } from "vue" import NewForm from "./NewForm.vue" -import type { APIResponseMessage, FormConfig, Schema } from "../interfaces" +import type { APIResponseMessage, Enum, Choices, FormConfig, Schema } from "../interfaces" import { convertFormValue, parseErrorResponse } from "@/utils" import FormErrors from "./FormErrors.vue" @@ -92,6 +92,14 @@ export default defineComponent({ } }, methods: { + enumToChoices (data: Enum): Choices { + var choices: Choices = {} + for (let index = 0; index < data.enum.length; index++) { + const element = data.enum[index]; + choices[element] = {display_name: element, value: element} + } + return choices + }, resetForm() { const form = this.$refs.form as HTMLFormElement form.reset() diff --git a/admin_ui/src/components/NewForm.vue b/admin_ui/src/components/NewForm.vue index c0cdd4ad..b90dfab5 100644 --- a/admin_ui/src/components/NewForm.vue +++ b/admin_ui/src/components/NewForm.vue @@ -7,7 +7,7 @@
import { defineComponent, type PropType } from "vue" import InputField from "./InputField.vue" -import { Enum, Choices, type Schema, getType } from "@/interfaces" +import { type Schema, getType } from "@/interfaces" -export function enumToChoices(data: Enum): Choices { - var choices: Choices = {} - for (let index = 0; index < data.enum.length; index++) { - const element = data.enum[index]; - choices[element] = {display_name: element, value: element} - } - return choices -} export default defineComponent({ props: { schema: { type: Object as PropType, required: true + }, + etc: { + type: Function, } }, components: { From 2529c253ef91f512532d634cce25417df04810b3 Mon Sep 17 00:00:00 2001 From: skelmis Date: Thu, 16 Jan 2025 19:36:01 +1300 Subject: [PATCH 4/6] Working base impl --- admin_ui/src/components/NewForm.vue | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/admin_ui/src/components/NewForm.vue b/admin_ui/src/components/NewForm.vue index b90dfab5..fbe4214a 100644 --- a/admin_ui/src/components/NewForm.vue +++ b/admin_ui/src/components/NewForm.vue @@ -4,20 +4,20 @@ v-bind:key="property.title" v-for="(property, columnName) in schema.properties" > -
- + + -
+
From 7fe5d0b93b8398aeb15c22c022f87eae3820de89 Mon Sep 17 00:00:00 2001 From: skelmis Date: Thu, 16 Jan 2025 19:37:51 +1300 Subject: [PATCH 5/6] Fix conditional bindings --- admin_ui/src/components/NewForm.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/admin_ui/src/components/NewForm.vue b/admin_ui/src/components/NewForm.vue index fbe4214a..83ae4044 100644 --- a/admin_ui/src/components/NewForm.vue +++ b/admin_ui/src/components/NewForm.vue @@ -4,7 +4,7 @@ v-bind:key="property.title" v-for="(property, columnName) in schema.properties" > - +
- +
From 2391e9edf2384c1858e10629f9c511bf4aa097e9 Mon Sep 17 00:00:00 2001 From: skelmis Date: Fri, 17 Jan 2025 23:08:09 +1300 Subject: [PATCH 6/6] Work towards test cases --- admin_ui/src/components/NewForm.vue | 12 ++++++--- docs/source/sidebar_links/examples/app.py | 2 +- e2e/test_forms.py | 20 ++++++++++++++ piccolo_admin/example/forms/__init__.py | 2 ++ piccolo_admin/example/forms/selections.py | 33 +++++++++++++++++++++++ 5 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 piccolo_admin/example/forms/selections.py diff --git a/admin_ui/src/components/NewForm.vue b/admin_ui/src/components/NewForm.vue index 83ae4044..f721a895 100644 --- a/admin_ui/src/components/NewForm.vue +++ b/admin_ui/src/components/NewForm.vue @@ -4,17 +4,21 @@ v-bind:key="property.title" v-for="(property, columnName) in schema.properties" > -
- + {{ property }} +
+
diff --git a/docs/source/sidebar_links/examples/app.py b/docs/source/sidebar_links/examples/app.py index e8e7ed39..72bef042 100644 --- a/docs/source/sidebar_links/examples/app.py +++ b/docs/source/sidebar_links/examples/app.py @@ -14,7 +14,7 @@ tables=[Director, Movie], sidebar_links={ "Top Movies": "/admin/#/movie?__order=-box_office", - "Google": "https://google.com" + "Google": "https://google.com", }, ), ), diff --git a/e2e/test_forms.py b/e2e/test_forms.py index 933a5278..279e3a7f 100644 --- a/e2e/test_forms.py +++ b/e2e/test_forms.py @@ -2,6 +2,7 @@ from piccolo_admin.example.forms.csv import FORM as CSV_FORM from piccolo_admin.example.forms.image import FORM as IMAGE_FORM +from piccolo_admin.example.forms.selections import FORM as SELECTION_FORM from .pages import FormPage, LoginPage @@ -54,3 +55,22 @@ def test_image_form(page: Page, dev_server): download = download_info.value assert download.suggested_filename == "movie_listings.jpg" + +def test_form_enum_support(page: Page, dev_server): + """ + Make sure custom forms support the usage of Enum's. + """ + login_page = LoginPage(page=page) + login_page.reset() + login_page.login() + + form_page = FormPage( + page=page, + form_slug=SELECTION_FORM.slug, + ) + form_page.reset() + + page.locator('input[name="option"]').select_option("One") + form_page.submit_form() + + assert f"You selected Options.ONE from the drop down." in page.content() \ No newline at end of file diff --git a/piccolo_admin/example/forms/__init__.py b/piccolo_admin/example/forms/__init__.py index e5be730c..bcbf773e 100644 --- a/piccolo_admin/example/forms/__init__.py +++ b/piccolo_admin/example/forms/__init__.py @@ -2,10 +2,12 @@ from .csv import FORM as CSV_FORM from .email import FORM as EMAIL_FORM from .image import FORM as IMAGE_FORM +from .selections import FORM as SELECTION_FORM FORMS = [ CALCULATOR_FORM, CSV_FORM, EMAIL_FORM, IMAGE_FORM, + SELECTION_FORM, ] diff --git a/piccolo_admin/example/forms/selections.py b/piccolo_admin/example/forms/selections.py new file mode 100644 index 00000000..4d003651 --- /dev/null +++ b/piccolo_admin/example/forms/selections.py @@ -0,0 +1,33 @@ +from enum import Enum + +from pydantic import BaseModel +from starlette.requests import Request + +from piccolo_admin.endpoints import FormConfig + + +class Options(Enum): + ONE = "One" + TWO = "Two" + THREE = "Three" + + +class SelectionModel(BaseModel): + option: Options = Options.ONE + + +def selection(request: Request, data: SelectionModel): + """ + A very simple example of a form which provides a selection drop down. + """ + return ( + f"You selected {data.option} from the drop down." + ) + + +FORM = FormConfig( + name="Selection", + pydantic_model=SelectionModel, + endpoint=selection, + description="Provides an example usage of drop downs within custom forms.", +)