diff --git a/ormar/fields/base.py b/ormar/fields/base.py index f7eaff5c5..1347a28e8 100644 --- a/ormar/fields/base.py +++ b/ormar/fields/base.py @@ -244,7 +244,7 @@ def construct_constraints(self) -> List: con.reference, ondelete=con.ondelete, onupdate=con.onupdate, - name=f"fk_{self.owner.Meta.tablename}_{self.to.Meta.tablename}" + name=con.name or f"fk_{self.owner.Meta.tablename}_{self.to.Meta.tablename}" f"_{self.to.get_column_alias(self.to.Meta.pkname)}_{self.name}", ) for con in self.constraints diff --git a/ormar/fields/foreign_key.py b/ormar/fields/foreign_key.py index dfd13d173..4938051a2 100644 --- a/ormar/fields/foreign_key.py +++ b/ormar/fields/foreign_key.py @@ -1,6 +1,4 @@ -import string -import sys -import uuid +import string, sys, uuid, sqlalchemy from dataclasses import dataclass from random import choices from typing import ( @@ -15,7 +13,6 @@ overload, ) -import sqlalchemy from pydantic import BaseModel, create_model from pydantic.typing import ForwardRef, evaluate_forwardref @@ -93,7 +90,11 @@ def create_dummy_model( def populate_fk_params_based_on_to_model( - to: Type["T"], nullable: bool, onupdate: str = None, ondelete: str = None + to: Type["T"], + nullable: bool, + onupdate: str = None, + ondelete: str = None, + fk_name: str = None, ) -> Tuple[Any, List, Any]: """ Based on target to model to which relation leads to populates the type of the @@ -112,7 +113,7 @@ def populate_fk_params_based_on_to_model( :return: tuple with target pydantic type, list of fk constraints and target col type :rtype: Tuple[Any, List, Any] """ - fk_string = to.Meta.tablename + "." + to.get_column_alias(to.Meta.pkname) + fk_string = f"{to.Meta.tablename}.{to.get_column_alias(to.Meta.pkname)}" to_field = to.Meta.model_fields[to.Meta.pkname] pk_only_model = create_dummy_model(to, to_field) __type__ = ( @@ -122,7 +123,7 @@ def populate_fk_params_based_on_to_model( ) constraints = [ ForeignKeyConstraint( - reference=fk_string, ondelete=ondelete, onupdate=onupdate, name=None + reference=fk_string, ondelete=ondelete, onupdate=onupdate, name=fk_name ) ] column_type = to_field.column_type @@ -214,6 +215,7 @@ def ForeignKey( # type: ignore # noqa CFQ002 virtual: bool = False, onupdate: Union[ReferentialAction, str] = None, ondelete: Union[ReferentialAction, str] = None, + fk_name: str = None, **kwargs: Any, ) -> "T": """ @@ -241,6 +243,8 @@ def ForeignKey( # type: ignore # noqa CFQ002 :param ondelete: parameter passed to sqlalchemy.ForeignKey. How to treat child rows on delete of parent (the one where FK is defined) model. :type ondelete: Union[ReferentialAction, str] + :param fk_name: if fk_name is specified, it overrides foreign key constraint name which is generated automatically in migrations + :type fk_name: str :param kwargs: all other args to be populated by BaseField :type kwargs: Any :return: ormar ForeignKeyField with relation to selected model @@ -265,7 +269,7 @@ def ForeignKey( # type: ignore # noqa CFQ002 validate_not_allowed_fields(kwargs) if to.__class__ == ForwardRef: - __type__ = to if not nullable else Optional[to] + __type__ = Optional[to] if nullable else to constraints: List = [] column_type = None else: @@ -273,6 +277,7 @@ def ForeignKey( # type: ignore # noqa CFQ002 to=to, # type: ignore nullable=nullable, ondelete=ondelete, + fk_name=fk_name, onupdate=onupdate, ) @@ -282,6 +287,7 @@ def ForeignKey( # type: ignore # noqa CFQ002 through=None, alias=name, name=kwargs.pop("real_name", None), + fk_name=fk_name, nullable=nullable, sql_nullable=sql_nullable, constraints=constraints, @@ -320,6 +326,7 @@ def __init__(self, **kwargs: Any) -> None: self.to: Type["Model"] self.ondelete: str = kwargs.pop("ondelete", None) self.onupdate: str = kwargs.pop("onupdate", None) + self.fk_name: str = kwargs.pop("fk_name", None) super().__init__(**kwargs) def get_source_related_name(self) -> str: @@ -383,6 +390,7 @@ def evaluate_forward_ref(self, globalns: Any, localns: Any) -> None: nullable=self.nullable, ondelete=self.ondelete, onupdate=self.onupdate, + fk_name=self.fk_name, ) def _extract_model_from_sequence( diff --git a/ormar/fields/many_to_many.py b/ormar/fields/many_to_many.py index 350fa3c38..dbe8a1533 100644 --- a/ormar/fields/many_to_many.py +++ b/ormar/fields/many_to_many.py @@ -122,6 +122,8 @@ def ManyToMany( # type: ignore skip_reverse = kwargs.pop("skip_reverse", False) skip_field = kwargs.pop("skip_field", False) + through_relation_fk_name = kwargs.pop("through_relation_fk_name", None) + through_reverse_relation_fk_name = kwargs.pop("through_reverse_relation_fk_name", None) through_relation_name = kwargs.pop("through_relation_name", None) through_reverse_relation_name = kwargs.pop("through_reverse_relation_name", None) @@ -165,6 +167,8 @@ def ManyToMany( # type: ignore related_orders_by=related_orders_by, skip_reverse=skip_reverse, skip_field=skip_field, + through_reverse_relation_fk_name=through_reverse_relation_fk_name, + through_relation_fk_name=through_relation_fk_name, through_relation_name=through_relation_name, through_reverse_relation_name=through_reverse_relation_name, ) diff --git a/ormar/models/helpers/sqlalchemy.py b/ormar/models/helpers/sqlalchemy.py index b0ade1d02..f5ca62e16 100644 --- a/ormar/models/helpers/sqlalchemy.py +++ b/ormar/models/helpers/sqlalchemy.py @@ -44,10 +44,10 @@ def adjust_through_many_to_many_model(model_field: "ManyToManyField") -> None: ) create_and_append_m2m_fk( - model=model_field.to, model_field=model_field, field_name=parent_name + model=model_field.to, model_field=model_field, field_name=parent_name, fk_name=model_field.through_reverse_relation_fk_name, ) create_and_append_m2m_fk( - model=model_field.owner, model_field=model_field, field_name=child_name + model=model_field.owner, model_field=model_field, field_name=child_name, fk_name=model_field.through_relation_fk_name ) create_pydantic_field(parent_name, model_field.to, model_field) @@ -58,7 +58,7 @@ def adjust_through_many_to_many_model(model_field: "ManyToManyField") -> None: def create_and_append_m2m_fk( - model: Type["Model"], model_field: "ManyToManyField", field_name: str + model: Type["Model"], model_field: "ManyToManyField", field_name: str, fk_name: str = None ) -> None: """ Registers sqlalchemy Column with sqlalchemy.ForeignKey leading to the model. @@ -85,7 +85,7 @@ def create_and_append_m2m_fk( model.Meta.tablename + "." + pk_alias, ondelete="CASCADE", onupdate="CASCADE", - name=f"fk_{model_field.through.Meta.tablename}_{model.Meta.tablename}" + name=fk_name or f"fk_{model_field.through.Meta.tablename}_{model.Meta.tablename}" f"_{field_name}_{pk_alias}", ), )