Skip to content

Commit 27afa0b

Browse files
committed
Init
1 parent 43dab52 commit 27afa0b

5 files changed

Lines changed: 121 additions & 23 deletions

File tree

fastapi_forge/frontend/constants.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from typing import Any
22

3+
from fastapi_forge.dtos import ModelField
4+
from fastapi_forge.enums import FieldDataType
5+
36
SELECTED_MODEL_TEXT_COLOR = "text-black-500 dark:text-amber-300"
47

58
FIELD_COLUMNS: list[dict[str, Any]] = [
@@ -46,3 +49,17 @@
4649
{"name": "index", "label": "Index", "field": "index", "align": "center"},
4750
{"name": "unique", "label": "Unique", "field": "unique", "align": "center"},
4851
]
52+
53+
54+
DEFAULT_AUTH_USER_FIELDS: list[ModelField] = [
55+
ModelField(
56+
name="email",
57+
type=FieldDataType.STRING,
58+
unique=True,
59+
index=True,
60+
),
61+
ModelField(
62+
name="password",
63+
type=FieldDataType.STRING,
64+
),
65+
]

fastapi_forge/frontend/modals/field_modal.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ def remove_row():
122122

123123
def _show_field_preview(self) -> None:
124124
if not self.field_name.value:
125+
ui.notify("Set a field name first", type="warning")
126+
return
127+
if not self.field_type.value:
128+
ui.notify("Select a field type first", type="warning")
125129
return
126130
try:
127131
with ui.dialog() as modal, ui.card().classes("no-shadow border-[1px]"):

fastapi_forge/frontend/panels/model_editor_panel.py

Lines changed: 95 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55

66
from fastapi_forge.dtos import Model, ModelField, ModelFieldMetadata, ModelRelationship
77
from fastapi_forge.enums import FieldDataType
8-
from fastapi_forge.frontend.constants import FIELD_COLUMNS, RELATIONSHIP_COLUMNS
8+
from fastapi_forge.frontend.constants import (
9+
DEFAULT_AUTH_USER_FIELDS,
10+
FIELD_COLUMNS,
11+
RELATIONSHIP_COLUMNS,
12+
)
913
from fastapi_forge.frontend.modals import (
1014
AddFieldModal,
1115
AddRelationModal,
@@ -28,6 +32,7 @@ def __init__(self):
2832
state.select_model_fn = self.set_selected_model
2933
state.deselect_model_fn = self.deselect_model
3034
state.render_model_editor_fn = self.refresh
35+
state.render_actions_fn = self._render_action_group
3136

3237
self.add_field_modal: AddFieldModal = AddFieldModal(
3338
on_add_field=self._handle_modal_add_field,
@@ -56,6 +61,63 @@ def _show_code_preview(self) -> None:
5661
ui.code(code).classes("w-full")
5762
modal.open()
5863

64+
def _toggle_auth_model(self) -> None:
65+
if not state.selected_model or not state.use_builtin_auth:
66+
return
67+
68+
if not state.selected_model.metadata.is_auth_model and any(
69+
m.metadata.is_auth_model for m in state.models
70+
):
71+
ui.notify(
72+
"Cannot have more than one authentication model.", type="negative"
73+
)
74+
return
75+
76+
model = state.selected_model
77+
model.metadata.is_auth_model = not model.metadata.is_auth_model
78+
79+
if not model.metadata.is_auth_model:
80+
self._remove_auth_fields(model)
81+
if state.render_model_editor_fn:
82+
state.render_model_editor_fn()
83+
84+
if state.render_models_fn:
85+
state.render_models_fn()
86+
self._render_action_group.refresh()
87+
88+
if model.metadata.is_auth_model:
89+
self._setup_auth_model_fields(model)
90+
91+
def _remove_auth_fields(self, model: Model) -> None:
92+
for field_name in ("email", "password"):
93+
if field := next((f for f in model.fields if f.name == field_name), None):
94+
model.fields.remove(field)
95+
96+
def _setup_auth_model_fields(self, model: Model) -> None:
97+
self._remove_auth_fields(model)
98+
id_index = 0
99+
insert_position = id_index + 1 if id_index >= 0 else 0
100+
for field in reversed(DEFAULT_AUTH_USER_FIELDS):
101+
model.fields.insert(insert_position, field)
102+
103+
self._refresh_table(model.fields)
104+
105+
@ui.refreshable
106+
def _render_action_group(self) -> None:
107+
ui.button(
108+
icon="security",
109+
on_click=self._toggle_auth_model,
110+
color=(
111+
"green"
112+
if state.use_builtin_auth
113+
and state.selected_model
114+
and state.selected_model.metadata.is_auth_model
115+
else "grey"
116+
),
117+
).tooltip("Authentication model").bind_visibility_from(
118+
state, "use_builtin_auth"
119+
)
120+
59121
def _build(self) -> None:
60122
with self:
61123
with ui.row().classes("w-full justify-between items-center"):
@@ -67,6 +129,7 @@ def _build(self) -> None:
67129
).tooltip("Preview SQLAlchemy model code")
68130

69131
with ui.row().classes("gap-2 items-center"):
132+
self._render_action_group()
70133
with (
71134
ui.button(icon="menu").tooltip("Generate"),
72135
ui.menu(),
@@ -373,22 +436,30 @@ def _deselect_relation(self) -> None:
373436
self.relationship_table.selected = []
374437

375438
def _on_select_field(self, selection: list[dict[str, Any]]) -> None:
376-
if not state.selected_model:
439+
if not state.selected_model or not selection:
440+
self._deselect_field()
377441
return
378-
if not selection:
442+
443+
name = selection[0].get("name")
444+
445+
if (
446+
state.selected_model.metadata.is_auth_model
447+
and state.use_builtin_auth
448+
and name in ("password", "email")
449+
):
450+
ui.notify(
451+
f"Cannot edit {name} field in authentication model.", type="warning"
452+
)
379453
self._deselect_field()
380454
return
381-
if selection[0].get("name") == "id":
455+
456+
if name == "id":
382457
self._deselect_field()
383-
else:
384-
state.selected_field = next(
385-
(
386-
field
387-
for field in state.selected_model.fields
388-
if field.name == selection[0]["name"]
389-
),
390-
None,
391-
)
458+
return
459+
460+
state.selected_field = next(
461+
(field for field in state.selected_model.fields if field.name == name), None
462+
)
392463

393464
def _on_select_relation(self, selection: list[dict[str, Any]]) -> None:
394465
if not state.selected_model:
@@ -424,6 +495,17 @@ def _handle_update_field(
424495
):
425496
return
426497

498+
if state.selected_model.metadata.is_auth_model and name == "password":
499+
ui.notify(
500+
"Cannot rename password field in authentication model.", type="negative"
501+
)
502+
return
503+
if state.selected_model.metadata.is_auth_model and name == "email":
504+
ui.notify(
505+
"Cannot rename email field in authentication model.", type="negative"
506+
)
507+
return
508+
427509
if state.selected_field.name != name and self._field_name_exists(name):
428510
notify_field_exists(name, state.selected_model.name)
429511
return

fastapi_forge/frontend/panels/project_config_panel.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
)
1313
from fastapi_forge.enums import FieldDataType
1414
from fastapi_forge.forge import build_project
15+
from fastapi_forge.frontend.constants import DEFAULT_AUTH_USER_FIELDS
1516
from fastapi_forge.frontend.notifications import notify_validation_error
1617
from fastapi_forge.frontend.state import state
1718

@@ -166,16 +167,7 @@ def _handle_builtin_auth_change(self, event: ValueChangeEventArguments) -> None:
166167
unique=True,
167168
index=True,
168169
),
169-
ModelField(
170-
name="email",
171-
type=FieldDataType.STRING,
172-
unique=True,
173-
index=True,
174-
),
175-
ModelField(
176-
name="password",
177-
type=FieldDataType.STRING,
178-
),
170+
*DEFAULT_AUTH_USER_FIELDS,
179171
ModelField(
180172
name="created_at",
181173
type=FieldDataType.DATETIME,

fastapi_forge/frontend/state.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class ProjectState(BaseModel):
2727

2828
render_models_fn: Callable | None = None
2929
render_model_editor_fn: Callable | None = None
30+
render_actions_fn: Callable | None = None
3031
select_model_fn: Callable[[Model], None] | None = None
3132
deselect_model_fn: Callable | None = None
3233

@@ -177,6 +178,8 @@ def _trigger_ui_refresh(self) -> None:
177178
self.render_models_fn()
178179
if self.render_model_editor_fn:
179180
self.render_model_editor_fn()
181+
if self.render_actions_fn:
182+
self.render_actions_fn.refresh()
180183

181184

182185
state: ProjectState = ProjectState()

0 commit comments

Comments
 (0)