55
66from fastapi_forge .dtos import Model , ModelField , ModelFieldMetadata , ModelRelationship
77from 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+ )
913from 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
0 commit comments