Skip to content

Commit a94f5d2

Browse files
authored
feat: on_delete select option for relations (#29)
1 parent ec1f525 commit a94f5d2

14 files changed

Lines changed: 103 additions & 81 deletions

fastapi_forge/data_type_registry.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from pydantic import BaseModel
44

5-
from fastapi_forge.enums import FieldDataType
5+
from fastapi_forge.enums import FieldDataTypeEnum
66

77

88
class DataTypeInfo(BaseModel):
@@ -17,14 +17,14 @@ class DataTypeInfo(BaseModel):
1717

1818
class DataTypeInfoRegistry:
1919
def __init__(self):
20-
self._registry: dict[FieldDataType, DataTypeInfo] = {}
20+
self._registry: dict[FieldDataTypeEnum, DataTypeInfo] = {}
2121

22-
def register(self, field_data_type: FieldDataType, data_type: DataTypeInfo):
22+
def register(self, field_data_type: FieldDataTypeEnum, data_type: DataTypeInfo):
2323
if field_data_type in self._registry:
2424
raise ValueError(f"Data type '{field_data_type}' is already registered.")
2525
self._registry[field_data_type] = data_type
2626

27-
def get(self, field_data_type: FieldDataType) -> DataTypeInfo:
27+
def get(self, field_data_type: FieldDataTypeEnum) -> DataTypeInfo:
2828
if field_data_type not in self._registry:
2929
raise ValueError(f"Data type '{field_data_type}' not found.")
3030
return self._registry[field_data_type]
@@ -38,7 +38,7 @@ def all(self) -> list[DataTypeInfo]:
3838

3939

4040
registry.register(
41-
FieldDataType.STRING,
41+
FieldDataTypeEnum.STRING,
4242
DataTypeInfo(
4343
sqlalchemy_type="String",
4444
sqlalchemy_prefix=True,
@@ -51,7 +51,7 @@ def all(self) -> list[DataTypeInfo]:
5151

5252

5353
registry.register(
54-
FieldDataType.FLOAT,
54+
FieldDataTypeEnum.FLOAT,
5555
DataTypeInfo(
5656
sqlalchemy_type="Float",
5757
sqlalchemy_prefix=True,
@@ -65,7 +65,7 @@ def all(self) -> list[DataTypeInfo]:
6565
)
6666

6767
registry.register(
68-
FieldDataType.BOOLEAN,
68+
FieldDataTypeEnum.BOOLEAN,
6969
DataTypeInfo(
7070
sqlalchemy_type="Boolean",
7171
sqlalchemy_prefix=True,
@@ -77,7 +77,7 @@ def all(self) -> list[DataTypeInfo]:
7777
)
7878

7979
registry.register(
80-
FieldDataType.DATETIME,
80+
FieldDataTypeEnum.DATETIME,
8181
DataTypeInfo(
8282
sqlalchemy_type="DateTime(timezone=True)",
8383
sqlalchemy_prefix=True,
@@ -90,7 +90,7 @@ def all(self) -> list[DataTypeInfo]:
9090
)
9191

9292
registry.register(
93-
FieldDataType.UUID,
93+
FieldDataTypeEnum.UUID,
9494
DataTypeInfo(
9595
sqlalchemy_type="UUID(as_uuid=True)",
9696
sqlalchemy_prefix=True,
@@ -102,7 +102,7 @@ def all(self) -> list[DataTypeInfo]:
102102
)
103103

104104
registry.register(
105-
FieldDataType.JSONB,
105+
FieldDataTypeEnum.JSONB,
106106
DataTypeInfo(
107107
sqlalchemy_type="JSONB",
108108
sqlalchemy_prefix=False,
@@ -114,7 +114,7 @@ def all(self) -> list[DataTypeInfo]:
114114
)
115115

116116
registry.register(
117-
FieldDataType.INTEGER,
117+
FieldDataTypeEnum.INTEGER,
118118
DataTypeInfo(
119119
sqlalchemy_type="Integer",
120120
sqlalchemy_prefix=True,

fastapi_forge/dtos.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
)
1111

1212
from fastapi_forge.data_type_registry import DataTypeInfo, registry
13-
from fastapi_forge.enums import FieldDataType
13+
from fastapi_forge.enums import FieldDataTypeEnum, OnDeleteEnum
1414
from fastapi_forge.string_utils import camel_to_snake_hyphen, snake_to_camel
1515

1616
BoundedStr = Annotated[str, Field(..., min_length=1, max_length=100)]
@@ -40,11 +40,12 @@ class ModelField(_Base):
4040
"""Represents a field in a model with validation and computed properties."""
4141

4242
name: FieldName
43-
type: FieldDataType
43+
type: FieldDataTypeEnum
4444
primary_key: bool = False
4545
nullable: bool = False
4646
unique: bool = False
4747
index: bool = False
48+
on_delete: OnDeleteEnum = OnDeleteEnum.CASCADE
4849
default_value: str | None = None
4950
extra_kwargs: dict[str, Any] | None = None
5051
metadata: ModelFieldMetadata = ModelFieldMetadata()
@@ -73,13 +74,13 @@ def _validate(self) -> Self:
7374
metadata = self.metadata
7475
if (
7576
metadata.is_created_at_timestamp or metadata.is_updated_at_timestamp
76-
) and self.type != FieldDataType.DATETIME:
77+
) and self.type != FieldDataTypeEnum.DATETIME:
7778
msg = "Create/update timestamp fields must be of type DateTime."
7879
raise ValueError(
7980
msg,
8081
)
8182

82-
if metadata.is_foreign_key and self.type != FieldDataType.UUID:
83+
if metadata.is_foreign_key and self.type != FieldDataTypeEnum.UUID:
8384
msg = "Foreign Keys must be of type UUID."
8485
raise ValueError(msg)
8586

@@ -99,6 +100,7 @@ class ModelRelationship(_Base):
99100
field_name: FieldName
100101
target_model: ModelName
101102
back_populates: BackPopulates | None = None
103+
on_delete: OnDeleteEnum = OnDeleteEnum.CASCADE
102104

103105
nullable: bool = False
104106
unique: bool = False
@@ -252,7 +254,7 @@ def get_preview(self) -> "Model":
252254
preview_model.fields.append(
253255
ModelField(
254256
name=relation.field_name,
255-
type=FieldDataType.UUID,
257+
type=FieldDataTypeEnum.UUID,
256258
primary_key=False,
257259
nullable=relation.nullable,
258260
unique=relation.unique,

fastapi_forge/enums.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from functools import lru_cache
33

44

5-
class FieldDataType(StrEnum):
5+
class FieldDataTypeEnum(StrEnum):
66
STRING = "String"
77
INTEGER = "Integer"
88
FLOAT = "Float"
@@ -44,11 +44,11 @@ def get_type_mappings(cls) -> dict[str, list[str]]:
4444
}
4545

4646
@classmethod
47-
def get_custom_types(cls) -> dict[str, "FieldDataType"]:
47+
def get_custom_types(cls) -> dict[str, "FieldDataTypeEnum"]:
4848
return {}
4949

5050
@classmethod
51-
def from_db_type(cls, db_type: str) -> "FieldDataType":
51+
def from_db_type(cls, db_type: str) -> "FieldDataTypeEnum":
5252
db_type = db_type.lower()
5353

5454
custom_types = cls.get_custom_types()
@@ -65,7 +65,12 @@ def from_db_type(cls, db_type: str) -> "FieldDataType":
6565
)
6666

6767

68-
class HTTPMethod(StrEnum):
68+
class OnDeleteEnum(StrEnum):
69+
CASCADE = "CASCADE"
70+
SET_NULL = "SET NULL"
71+
72+
73+
class HTTPMethodEnum(StrEnum):
6974
GET = "get"
7075
GET_ID = "get_id"
7176
POST = "post"

fastapi_forge/frontend/constants.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from typing import Any
22

33
from fastapi_forge.dtos import ModelField
4-
from fastapi_forge.enums import FieldDataType
4+
from fastapi_forge.enums import FieldDataTypeEnum
55

66
SELECTED_MODEL_TEXT_COLOR = "text-black-500 dark:text-amber-300"
77

@@ -40,9 +40,9 @@
4040
"align": "left",
4141
},
4242
{
43-
"name": "back_populates",
44-
"label": "Back Populates",
45-
"field": "back_populates",
43+
"name": "on_delete",
44+
"label": "On Delete",
45+
"field": "on_delete",
4646
"align": "left",
4747
},
4848
{"name": "nullable", "label": "Nullable", "field": "nullable", "align": "center"},
@@ -54,12 +54,12 @@
5454
DEFAULT_AUTH_USER_FIELDS: list[ModelField] = [
5555
ModelField(
5656
name="email",
57-
type=FieldDataType.STRING,
57+
type=FieldDataTypeEnum.STRING,
5858
unique=True,
5959
index=True,
6060
),
6161
ModelField(
6262
name="password",
63-
type=FieldDataType.STRING,
63+
type=FieldDataTypeEnum.STRING,
6464
),
6565
]

fastapi_forge/frontend/modals/field_modal.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pydantic import ValidationError
66

77
from fastapi_forge.dtos import ModelField, ModelFieldMetadata
8-
from fastapi_forge.enums import FieldDataType
8+
from fastapi_forge.enums import FieldDataTypeEnum
99
from fastapi_forge.frontend.notifications import notify_validation_error
1010
from fastapi_forge.frontend.state import state
1111
from fastapi_forge.jinja_utils import generate_field
@@ -38,7 +38,7 @@ def _build(self) -> None:
3838
"outlined dense"
3939
)
4040
self.field_type = ui.select(
41-
list(FieldDataType),
41+
list(FieldDataTypeEnum),
4242
label="Field Type",
4343
on_change=self._toggle_metadata_visibility,
4444
).props("outlined dense")
@@ -84,7 +84,7 @@ def _build(self) -> None:
8484
self._build_action_buttons()
8585

8686
def _toggle_metadata_visibility(self):
87-
self.show_metadata = self.field_type.value == FieldDataType.DATETIME
87+
self.show_metadata = self.field_type.value == FieldDataTypeEnum.DATETIME
8888

8989
def _add_kwarg_row(self, key: str = "", value: str = "") -> None:
9090
with (
@@ -254,7 +254,7 @@ def _set_field(self, field: ModelField) -> None:
254254
self.default_value.value = field.default_value or ""
255255
self.created_at.value = field.metadata.is_created_at_timestamp
256256
self.updated_at.value = field.metadata.is_updated_at_timestamp
257-
self.show_metadata = field.type == FieldDataType.DATETIME
257+
self.show_metadata = field.type == FieldDataTypeEnum.DATETIME
258258
self.extra_kwargs = field.extra_kwargs.copy() if field.extra_kwargs else {}
259259
self.kwargs_container.clear()
260260

fastapi_forge/frontend/modals/relation_modal.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from nicegui import ui
55

66
from fastapi_forge.dtos import Model, ModelRelationship
7+
from fastapi_forge.enums import OnDeleteEnum
78

89

910
class BaseRelationModal(ui.dialog, ABC):
@@ -27,6 +28,11 @@ def _build_common_ui(self) -> None:
2728
label="Target Model",
2829
options=[],
2930
).props("outlined dense")
31+
self.on_delete = ui.select(
32+
label="On Delete",
33+
options=list(OnDeleteEnum),
34+
value=OnDeleteEnum.CASCADE,
35+
).props("outlined dense")
3036
self.back_populates = ui.input(label="Back Populates").props(
3137
"outlined dense"
3238
)
@@ -47,6 +53,7 @@ def _reset(self) -> None:
4753
self.field_name.value = ""
4854
self.target_model.value = None
4955
self.back_populates.value = ""
56+
self.on_delete.value = None
5057
self.nullable.value = False
5158
self.index.value = False
5259
self.unique.value = False
@@ -74,6 +81,7 @@ def _add_relation(self) -> None:
7481
nullable=self.nullable.value,
7582
index=self.index.value,
7683
unique=self.unique.value,
84+
on_delete=self.on_delete.value,
7785
)
7886
self.close()
7987

@@ -109,6 +117,7 @@ def _update_relation(self) -> None:
109117
nullable=self.nullable.value,
110118
index=self.index.value,
111119
unique=self.unique.value,
120+
on_delete=self.on_delete.value,
112121
)
113122
self.close()
114123

@@ -121,6 +130,7 @@ def _set_relation(self, relation: ModelRelationship) -> None:
121130
self.index.value = relation.index
122131
self.unique.value = relation.unique
123132
self.back_populates.value = relation.back_populates
133+
self.on_delete.value = relation.on_delete
124134

125135
def open(
126136
self,

fastapi_forge/frontend/panels/model_editor_panel.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pydantic import ValidationError
55

66
from fastapi_forge.dtos import Model, ModelField, ModelFieldMetadata, ModelRelationship
7-
from fastapi_forge.enums import FieldDataType
7+
from fastapi_forge.enums import FieldDataTypeEnum, OnDeleteEnum
88
from fastapi_forge.frontend.constants import (
99
DEFAULT_AUTH_USER_FIELDS,
1010
FIELD_COLUMNS,
@@ -312,6 +312,7 @@ def _handle_modal_add_relation(
312312
nullable: bool,
313313
index: bool,
314314
unique: bool,
315+
on_delete: OnDeleteEnum,
315316
back_populates: str | None = None,
316317
) -> None:
317318
if not state.selected_model:
@@ -337,6 +338,7 @@ def _handle_modal_add_relation(
337338
nullable=nullable,
338339
index=index,
339340
unique=unique,
341+
on_delete=on_delete,
340342
)
341343
except ValidationError as exc:
342344
notify_validation_error(exc)
@@ -406,7 +408,7 @@ def _add_field(
406408
try:
407409
field_input = ModelField(
408410
name=name,
409-
type=FieldDataType(type),
411+
type=FieldDataTypeEnum(type),
410412
primary_key=primary_key,
411413
nullable=nullable,
412414
unique=unique,
@@ -514,7 +516,7 @@ def _handle_update_field(
514516
try:
515517
field_input = ModelField(
516518
name=name,
517-
type=FieldDataType(type),
519+
type=FieldDataTypeEnum(type),
518520
primary_key=primary_key,
519521
nullable=nullable,
520522
unique=unique,
@@ -538,6 +540,7 @@ def _handle_update_relation(
538540
nullable: bool = False,
539541
index: bool = False,
540542
unique: bool = False,
543+
on_delete: OnDeleteEnum = OnDeleteEnum.CASCADE,
541544
back_populates: str | None = None,
542545
) -> None:
543546
if not state.selected_model or not state.selected_relation:
@@ -565,6 +568,7 @@ def _handle_update_relation(
565568
nullable=nullable,
566569
index=index,
567570
unique=unique,
571+
on_delete=on_delete,
568572
)
569573
except ValidationError as exc:
570574
notify_validation_error(exc)

0 commit comments

Comments
 (0)