1010 ProjectSpec ,
1111)
1212from fastapi_forge .enums import FieldDataType
13- from fastapi_forge .frontend import notifications as n
13+ from fastapi_forge .frontend .notifications import (
14+ notify_model_exists ,
15+ notify_something_went_wrong ,
16+ notify_validation_error ,
17+ )
1418
1519
1620class ProjectState (BaseModel ):
21+ """Central state management for the project configuration."""
22+
1723 models : list [Model ] = []
1824 selected_model : Model | None = None
1925 selected_field : ModelField | None = None
2026 selected_relation : ModelRelationship | None = None
2127
2228 render_models_fn : Callable | None = None
2329 render_model_editor_fn : Callable | None = None
24- select_model_fn : Callable | None = None
30+ select_model_fn : Callable [[ Model ], None ] | None = None
2531 deselect_model_fn : Callable | None = None
2632
2733 project_name : str = ""
@@ -32,7 +38,7 @@ class ProjectState(BaseModel):
3238 use_rabbitmq : bool = False
3339
3440 def initialize_from_project (self , project : ProjectSpec ) -> None :
35- """Initialize the state from an existing project specification"""
41+ """Initialize state from an existing project specification. """
3642 self .project_name = project .project_name
3743 self .use_postgres = project .use_postgres
3844 self .use_alembic = project .use_alembic
@@ -41,90 +47,62 @@ def initialize_from_project(self, project: ProjectSpec) -> None:
4147 self .use_rabbitmq = project .use_rabbitmq
4248 self .models = project .models .copy ()
4349
44- if self .render_models_fn :
45- self .render_models_fn ()
50+ self ._trigger_ui_refresh ()
4651
4752 def add_model (self , model_name : str ) -> None :
48- if self . render_models_fn is None :
49- n . notify_something_went_wrong ()
53+ """Add a new model to the project."""
54+ if not self . _validate_ui_callbacks ():
5055 return
5156
52- if any ( model . name == model_name for model in self .models ):
53- n . notify_model_exists (model_name )
57+ if self ._model_exists ( model_name ):
58+ notify_model_exists (model_name )
5459 return
5560
5661 try :
57- default_id_field = ModelField (
58- name = "id" ,
59- type = FieldDataType .UUID ,
60- primary_key = True ,
61- nullable = False ,
62- unique = True ,
63- index = True ,
64- )
65-
66- new_model = Model (name = model_name , fields = [default_id_field ])
67- self .models .append (new_model )
68-
69- self .render_models_fn ()
70-
62+ self .models .append (self ._create_default_model (model_name ))
63+ self ._trigger_ui_refresh ()
7164 except ValidationError as exc :
72- n . notify_validation_error (exc )
65+ notify_validation_error (exc )
7366
7467 def delete_model (self , model : Model ) -> None :
75- if (
76- model not in self .models
77- or self .deselect_model_fn is None
78- or self .render_models_fn is None
79- ):
80- ui .notify ("Something went wrong..." , type = "warning" )
68+ """Remove a model from the project."""
69+ if not self ._validate_model_operation (model ):
8170 return
8271
8372 self .models .remove (model )
84- self .deselect_model_fn ()
85- self .render_models_fn ()
73+ self ._cleanup_relationships_for_deleted_model (model .name )
74+ self ._deselect_current_model ()
75+ self ._trigger_ui_refresh ()
8676
8777 def update_model_name (self , model : Model , new_name : str ) -> None :
78+ """Rename an existing model."""
8879 if model .name == new_name :
8980 return
9081
91- if any ( m . name == new_name for m in self . models if m != model ):
92- n . notify_model_exists (new_name )
82+ if self . _model_exists ( new_name , exclude = model ):
83+ notify_model_exists (new_name )
9384 return
9485
9586 old_name = model .name
9687 model .name = new_name
9788 self ._update_relationships_for_rename (old_name , new_name )
9889
99- if self . select_model_fn and model == self .selected_model :
90+ if model == self .selected_model and self . select_model_fn :
10091 self .select_model_fn (model )
10192
102- if self .render_models_fn :
103- self .render_models_fn ()
104-
105- self .render_model_editor_fn ()
93+ self ._trigger_ui_refresh ()
10694
10795 def select_model (self , model : Model ) -> None :
108- if self .selected_model == model :
96+ """Set the currently selected model."""
97+ if self .selected_model == model or not self ._validate_ui_callbacks ():
10998 return
110- # print("selecting")
11199
112- if (
113- self .select_model_fn is None
114- or self .render_models_fn is None
115- or self .deselect_model_fn is None
116- ):
117- n .notify_something_went_wrong ()
118- return
119100 self .selected_model = model
120- self .select_model_fn (model )
121-
122- if model not in self .models :
123- self .deselect_model_fn ()
124-
125- self .render_models_fn ()
101+ self .select_model_fn (model ) # type: ignore
102+ self ._trigger_ui_refresh ()
126103
127104 def get_project_spec (self ) -> ProjectSpec :
105+ """Generate a ProjectSpec from the current state."""
128106 return ProjectSpec (
129107 project_name = self .project_name ,
130108 use_postgres = self .use_postgres ,
@@ -135,26 +113,70 @@ def get_project_spec(self) -> ProjectSpec:
135113 models = self .models ,
136114 )
137115
138- def _cleanup_relationships_for_deleted_model (
139- self ,
140- deleted_model_name : str ,
141- ) -> None :
116+ def _create_default_model (self , name : str ) -> Model :
117+ """Create a new model with default fields."""
118+ return Model (
119+ name = name ,
120+ fields = [
121+ ModelField (
122+ name = "id" ,
123+ type = FieldDataType .UUID ,
124+ primary_key = True ,
125+ nullable = False ,
126+ unique = True ,
127+ index = True ,
128+ )
129+ ],
130+ )
131+
132+ def _cleanup_relationships_for_deleted_model (self , deleted_model_name : str ) -> None :
133+ """Remove relationships pointing to deleted models."""
142134 for model in self .models :
143135 model .relationships = [
144136 rel
145137 for rel in model .relationships
146138 if rel .target_model != deleted_model_name
147139 ]
148140
149- def _update_relationships_for_rename (
150- self ,
151- old_name : str ,
152- new_name : str ,
153- ) -> None :
141+ def _update_relationships_for_rename (self , old_name : str , new_name : str ) -> None :
142+ """Update relationships when a model is renamed."""
154143 for model in self .models :
155144 for relationship in model .relationships :
156145 if relationship .target_model == old_name :
157146 relationship .target_model = new_name
158147
148+ def _model_exists (self , name : str , exclude : Model | None = None ) -> bool :
149+ """Check if a model with the given name already exists."""
150+ return any (model .name == name for model in self .models if model != exclude )
151+
152+ def _validate_ui_callbacks (self ) -> bool :
153+ """Verify required UI callbacks are set."""
154+ if not all ([self .render_models_fn , self .select_model_fn ]):
155+ notify_something_went_wrong ()
156+ return False
157+ return True
158+
159+ def _validate_model_operation (self , model : Model ) -> bool :
160+ """Validate conditions for model operations."""
161+ if model not in self .models or not all (
162+ [self .deselect_model_fn , self .render_models_fn ]
163+ ):
164+ ui .notify ("Something went wrong..." , type = "warning" )
165+ return False
166+ return True
167+
168+ def _deselect_current_model (self ) -> None :
169+ """Clear current model selection."""
170+ if self .deselect_model_fn :
171+ self .deselect_model_fn ()
172+ self .selected_model = None
173+
174+ def _trigger_ui_refresh (self ) -> None :
175+ """Refresh all relevant UI components."""
176+ if self .render_models_fn :
177+ self .render_models_fn ()
178+ if self .render_model_editor_fn :
179+ self .render_model_editor_fn ()
180+
159181
160182state : ProjectState = ProjectState ()
0 commit comments