Skip to content

OneToOneField

OneToOneField is the unique variant of ForeignKey.

It behaves like a foreign key at declaration and query time, but the generated relation columns are unique and the reverse accessor is singular by default.

Practical example

class Profile(saffier.Model):
    id = saffier.IntegerField(primary_key=True, autoincrement=True)
    user = saffier.OneToOneField("User", on_delete=saffier.CASCADE)

    class Meta:
        registry = models

Reverse behavior

When related_name is omitted, Saffier generates a singular reverse accessor from the declaring model name. That means a Profile.user relation typically becomes user.profile, not user.profiles_set.

For reverse one-to-one relations, remove() can omit the child object because at most one related row can exist.

saffier.OneToOneField

OneToOneField(to, **kwargs)

Bases: ForeignKey

Foreign-key field that enforces a unique reverse relationship.

The field is implemented as a standard foreign key with unique=True, which gives it one-to-one semantics while reusing the full foreign-key machinery.

Source code in saffier/core/db/fields/base.py
1554
1555
1556
def __init__(self, to: type["Model"] | str, **kwargs: typing.Any):
    kwargs["unique"] = True
    super().__init__(to, **kwargs)

is_virtual class-attribute instance-attribute

is_virtual = False

server_default instance-attribute

server_default = pop('server_default', None)

null instance-attribute

null = get('null', False)

default_value instance-attribute

default_value = get('default')

primary_key instance-attribute

primary_key = primary_key

index instance-attribute

index = index

unique instance-attribute

unique = unique

validator instance-attribute

validator = get_validator(**kwargs)

comment instance-attribute

comment = get('comment')

column_name instance-attribute

column_name = pop('column_name', None)

owner instance-attribute

owner = pop('owner', None)

registry instance-attribute

registry = pop('registry', None)

name instance-attribute

name = pop('name', '')

inherit instance-attribute

inherit = pop('inherit', True)

no_copy instance-attribute

no_copy = pop('no_copy', False)

exclude instance-attribute

exclude = pop('exclude', False)

inject_default_on_partial_update instance-attribute

inject_default_on_partial_update = get(
    "inject_default_on_partial_update", False
)

server_onupdate instance-attribute

server_onupdate = pop('server_onupdate', None)

autoincrement instance-attribute

autoincrement = pop('autoincrement', False)

secret instance-attribute

secret = pop('secret', False)

related_fields instance-attribute

related_fields = tuple(pop('related_fields', ()))

_target_registry instance-attribute

_target_registry = pop('target_registry', None)

to instance-attribute

to = to

on_delete instance-attribute

on_delete = on_delete

on_update instance-attribute

on_update = on_update or CASCADE

related_name instance-attribute

related_name = related_name

embed_parent instance-attribute

embed_parent = embed_parent

no_constraint instance-attribute

no_constraint = no_constraint

remove_referenced instance-attribute

remove_referenced = remove_referenced

use_model_based_deletion instance-attribute

use_model_based_deletion = use_model_based_deletion

force_cascade_deletion_relation instance-attribute

force_cascade_deletion_relation = (
    force_cascade_deletion_relation
)

target_registry property writable

target_registry

Return the registry used to resolve string relation targets.

Explicit target_registry overrides take precedence, followed by the owning model registry and finally the field-level registry fallback.

target property

target

Resolve and cache the target model class for this relation.

String targets are looked up lazily so models can reference classes that are declared later in the import graph.

RETURNS DESCRIPTION
Any

Resolved target model class.

TYPE: Any

related_columns property

related_columns

Return target columns used by the relation mapping.

Composite foreign keys can point to multiple target columns. When explicit related_fields are not provided, the target model primary-key columns are used.

RETURNS DESCRIPTION
dict[str, Column | None]

dict[str, sqlalchemy.Column | None]: Mapping of target column names to

dict[str, Column | None]

SQLAlchemy columns where available.

ForeignKeyValidator

ForeignKeyValidator(
    *,
    title="",
    description="",
    help_text="",
    default=NO_DEFAULT,
    null=False,
    read_only=False,
    **kwargs,
)

Bases: SaffierField

Source code in saffier/core/db/fields/_internal.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def __init__(
    self,
    *,
    title: str = "",
    description: str = "",
    help_text: str = "",
    default: typing.Any = NO_DEFAULT,
    null: bool = False,
    read_only: bool = False,
    **kwargs: typing.Any,
) -> None:
    super().__init__(**kwargs)
    self.explicit_none = default is None
    if null and default is NO_DEFAULT:
        default = None

    if default is not NO_DEFAULT:
        self.default = default

    self.null = null
    self.read_only = read_only
    self.title = title
    self.description = description
    self.help_text = help_text

error_messages class-attribute instance-attribute

error_messages = {}

explicit_none instance-attribute

explicit_none = default is None

default instance-attribute

default = default

null instance-attribute

null = null

read_only instance-attribute

read_only = read_only

title instance-attribute

title = title

description instance-attribute

description = description

help_text instance-attribute

help_text = help_text

__hash__

__hash__()
Source code in saffier/core/datastructures.py
19
20
21
22
23
24
25
26
def __hash__(self) -> int:
    values: dict[str, Any] = {}
    for key, value in self.__dict__.items():
        if isinstance(value, (list, set)):
            values[key] = tuple(value)
        else:
            values[key] = value
    return hash((type(self),) + tuple(values))

validate_or_error

validate_or_error(value)
Source code in saffier/core/db/fields/_internal.py
71
72
73
74
75
76
def validate_or_error(self, value: typing.Any) -> ValidationResult:
    try:
        value = self.check(value)
    except ValidationError as error:
        return ValidationResult(value=None, error=error)
    return ValidationResult(value=value, error=None)

has_default

has_default()
Source code in saffier/core/db/fields/_internal.py
78
79
80
81
82
def has_default(self) -> bool:
    if not hasattr(self, "default"):
        return False
    default = self.default
    return default is not None or self.explicit_none

validation_error

validation_error(code, value=None)
Source code in saffier/core/db/fields/_internal.py
84
85
86
def validation_error(self, code: str, value: typing.Any | None = None) -> ValidationError:
    text = self.get_error_message(code)
    return ValidationError(text=text, code=code)

get_error_message

get_error_message(code)
Source code in saffier/core/db/fields/_internal.py
88
89
def get_error_message(self, code: str) -> str:
    return self.error_messages[code].format(**self.__dict__)

get_default_value

get_default_value()
Source code in saffier/core/db/fields/_internal.py
91
92
93
94
95
def get_default_value(self) -> typing.Any:
    default = getattr(self, "default", None)
    if callable(default):
        return default()
    return default

check

check(value)
Source code in saffier/core/db/fields/base.py
681
682
683
684
685
686
687
688
def check(self, value: typing.Any) -> typing.Any:
    if value is None and self.null:
        return None
    if value is None:
        raise self.validation_error("null")
    if hasattr(value, "pk"):
        return value.pk
    return value

get_validator

get_validator(**kwargs)
Source code in saffier/core/db/fields/base.py
781
782
def get_validator(self, **kwargs: typing.Any) -> SaffierField:
    return self.ForeignKeyValidator(**kwargs)

get_column_type

get_column_type()
Source code in saffier/core/db/fields/base.py
130
131
def get_column_type(self) -> sqlalchemy.types.TypeEngine:
    raise NotImplementedError()  # pragma: no cover

get_constraints

get_constraints()
Source code in saffier/core/db/fields/base.py
133
134
def get_constraints(self) -> typing.Any:
    return []

get_columns

get_columns(name)
Source code in saffier/core/db/fields/base.py
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
def get_columns(self, name: str) -> typing.Sequence[sqlalchemy.Column]:
    target = self.target
    columns: list[sqlalchemy.Column] = []
    for related_key, related_column in self.related_columns.items():
        related_field = target.fields[related_key]
        related_type = (
            related_column.type
            if related_column is not None
            else related_field.get_column_type()
        )
        related_name = (
            getattr(related_column, "name", None) or related_field.column_name or related_key
        )
        related_nullable = (
            related_column.nullable if related_column is not None else related_field.null
        )
        columns.append(
            sqlalchemy.Column(
                key=self.get_fk_field_name(name, related_key),
                name=self.get_fk_column_name(name, related_name),
                type_=related_type,
                nullable=self.null or related_nullable,
                primary_key=self.primary_key,
                autoincrement=False,
            )
        )
    return columns

get_global_constraints

get_global_constraints(name, columns, *, schema=None)

Build cross-table constraints and indexes for the foreign key columns.

PARAMETER DESCRIPTION
name

Logical field name on the owning model.

TYPE: str

columns

Physical columns generated for the relation.

TYPE: Sequence[Column]

schema

Optional schema override for the target table reference.

TYPE: str | None DEFAULT: None

RETURNS DESCRIPTION
Sequence[Constraint | Index]

typing.Sequence[sqlalchemy.Constraint | sqlalchemy.Index]: SQLAlchemy constraints and indexes that should be attached to the owning table.

Source code in saffier/core/db/fields/base.py
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
def get_global_constraints(
    self,
    name: str,
    columns: typing.Sequence[sqlalchemy.Column],
    *,
    schema: str | None = None,
) -> typing.Sequence[sqlalchemy.Constraint | sqlalchemy.Index]:
    """Build cross-table constraints and indexes for the foreign key columns.

    Args:
        name: Logical field name on the owning model.
        columns: Physical columns generated for the relation.
        schema: Optional schema override for the target table reference.

    Returns:
        typing.Sequence[sqlalchemy.Constraint | sqlalchemy.Index]:
            SQLAlchemy constraints and indexes that should be attached to
            the owning table.
    """
    owner_registry = getattr(self.owner.meta, "registry", None)
    target = self.target
    target_registry = getattr(target.meta, "registry", None)
    owner_database = getattr(self.owner, "database", None)
    target_database = getattr(target, "database", None)
    use_constraint = not (
        self.no_constraint
        or (
            owner_registry not in (None, False)
            and target_registry not in (None, False)
            and owner_registry is not target_registry
        )
        or (
            owner_database is not None
            and target_database is not None
            and owner_database is not target_database
        )
    )
    constraints: list[sqlalchemy.Constraint | sqlalchemy.Index] = []
    if use_constraint:
        table_name = target.meta.tablename
        if schema is not None:
            table_name = f"{schema}.{table_name}"
        constraints.append(
            sqlalchemy.schema.ForeignKeyConstraint(
                columns,
                [
                    f"{table_name}.{self.from_fk_field_name(name, column.key)}"
                    for column in columns
                ],
                ondelete=self.on_delete,
                onupdate=self.on_update,
                name=self.get_fk_name(name),
            )
        )
    if self.unique or self.index:
        constraints.append(
            sqlalchemy.Index(
                self.get_fkindex_name(name),
                *columns,
                unique=self.unique,
            )
        )
    return constraints

has_column

has_column()
Source code in saffier/core/db/fields/base.py
149
150
def has_column(self) -> bool:
    return not self.is_virtual

get_embedded_fields

get_embedded_fields(field_name, existing_fields)
Source code in saffier/core/db/fields/base.py
152
153
154
155
156
157
158
def get_embedded_fields(
    self,
    field_name: str,
    existing_fields: dict[str, "Field"],
) -> dict[str, "Field"]:
    del field_name, existing_fields
    return {}

expand_relationship

expand_relationship(value)

Normalize relation values into lightweight target model instances.

Raw primary-key values, dictionaries, proxy models, and already-loaded target instances are all accepted so callers can assign foreign keys in a natural way while the ORM preserves a consistent in-memory shape.

PARAMETER DESCRIPTION
value

Incoming relation value.

TYPE: Any

RETURNS DESCRIPTION
Any

typing.Any: Target model instance or None.

Source code in saffier/core/db/fields/base.py
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
def expand_relationship(self, value: typing.Any) -> typing.Any:
    """Normalize relation values into lightweight target model instances.

    Raw primary-key values, dictionaries, proxy models, and already-loaded
    target instances are all accepted so callers can assign foreign keys in a
    natural way while the ORM preserves a consistent in-memory shape.

    Args:
        value: Incoming relation value.

    Returns:
        typing.Any: Target model instance or `None`.
    """
    if value is None:
        return None
    target = self.target
    related_columns = tuple(self.related_columns.keys())
    if isinstance(value, target):
        if (
            self.null
            and related_columns
            and all(
                key in value.__dict__ and getattr(value, key) is None
                for key in related_columns
            )
        ):
            return None
        return value
    if hasattr(value, "__db_model__"):
        value_cls = value.__class__
        if (
            getattr(value_cls, "is_proxy_model", False)
            and getattr(value_cls, "parent", None) is target
        ):
            if (
                self.null
                and related_columns
                and all(
                    key in value.__dict__ and getattr(value, key) is None
                    for key in related_columns
                )
            ):
                return None
            return value
    if isinstance(value, dict):
        return target(**value)
    if hasattr(value, "pk"):
        pk_value = value.pk
        if isinstance(pk_value, dict):
            return target(pk=pk_value)
        return target(pk=pk_value)
    return target(pk=value)

clean

clean(name, value, *, for_query=False)
Source code in saffier/core/db/fields/base.py
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
def clean(
    self, name: str, value: typing.Any, *, for_query: bool = False
) -> dict[str, typing.Any]:
    del for_query
    cleaned: dict[str, typing.Any] = {}
    related_keys = tuple(self.related_columns.keys())
    column_names = self.get_column_names(name)

    if value is None:
        for column_name in column_names:
            cleaned[column_name] = None
        return cleaned

    target = self.target
    if isinstance(value, dict):
        for related_key, column_name in zip(related_keys, column_names, strict=False):
            if related_key in value:
                cleaned[column_name] = value[related_key]
            elif column_name in value:
                cleaned[column_name] = value[column_name]
        return cleaned

    if isinstance(value, target):
        for related_key, column_name in zip(related_keys, column_names, strict=False):
            cleaned[column_name] = getattr(value, related_key, None)
        return cleaned

    if hasattr(value, "__db_model__"):
        value_cls = value.__class__
        if (
            getattr(value_cls, "is_proxy_model", False)
            and getattr(value_cls, "parent", None) is target
        ):
            for related_key, column_name in zip(related_keys, column_names, strict=False):
                cleaned[column_name] = getattr(value, related_key, None)
            return cleaned

    pk_value = getattr(value, "pk", None) if hasattr(value, "pk") else None
    if isinstance(pk_value, dict):
        return self.clean(name, pk_value)
    if pk_value is not None and len(column_names) == 1:
        cleaned[column_names[0]] = pk_value
        return cleaned

    if len(column_names) == 1:
        cleaned[column_names[0]] = value
        return cleaned

    raise ValueError(f"Cannot handle composite foreign key value {value!r}.")

modify_input

modify_input(name, kwargs)
Source code in saffier/core/db/fields/base.py
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
def modify_input(self, name: str, kwargs: dict[str, typing.Any]) -> None:
    if name in kwargs:
        return

    column_names = self.get_column_names(name)
    if len(column_names) <= 1:
        return

    payload: dict[str, typing.Any] = {}
    for column_name in column_names:
        if column_name in kwargs:
            payload[self.from_fk_field_name(name, column_name)] = kwargs.pop(column_name)

    if not payload:
        return
    if len(payload) != len(column_names):
        raise ValueError("Cannot update the foreign key partially")
    kwargs[name] = payload

raise_for_non_default

raise_for_non_default(default, server_default)
Source code in saffier/core/db/fields/base.py
185
186
def raise_for_non_default(self, default: typing.Any, server_default: typing.Any) -> typing.Any:
    del default, server_default

get_default_value

get_default_value()
Source code in saffier/core/db/fields/base.py
188
189
def get_default_value(self) -> typing.Any:
    return self.validator.get_default_value()

has_default

has_default()
Source code in saffier/core/db/fields/base.py
191
192
def has_default(self) -> bool:
    return self.validator.has_default()

get_default_values

get_default_values(field_name, cleaned_data)
Source code in saffier/core/db/fields/base.py
194
195
196
197
198
199
200
201
def get_default_values(
    self,
    field_name: str,
    cleaned_data: dict[str, typing.Any],
) -> dict[str, typing.Any]:
    if field_name in cleaned_data or not self.has_column():
        return {}
    return {field_name: self.get_default_value()}

is_required

is_required()
Source code in saffier/core/db/fields/base.py
203
204
205
206
def is_required(self) -> bool:
    if self.primary_key and self.autoincrement:
        return False
    return not (self.null or self.server_default is not None or self.has_default())

get_is_null_clause

get_is_null_clause(column)
Source code in saffier/core/db/fields/base.py
208
209
def get_is_null_clause(self, column: typing.Any) -> typing.Any:
    return column == None  # noqa: E711

get_is_empty_clause

get_is_empty_clause(column)
Source code in saffier/core/db/fields/base.py
211
212
def get_is_empty_clause(self, column: typing.Any) -> typing.Any:
    return self.get_is_null_clause(column)

operator_to_clause

operator_to_clause(field_name, operator, table, value)

Translate one lookup operator into a SQLAlchemy clause.

PARAMETER DESCRIPTION
field_name

Logical field name being filtered.

TYPE: str

operator

Saffier lookup suffix such as exact or isnull.

TYPE: str

table

SQLAlchemy table containing the target column.

TYPE: Table

value

Lookup value supplied by the caller.

TYPE: Any

RETURNS DESCRIPTION
Any

SQLAlchemy boolean clause implementing the lookup.

TYPE: Any

Source code in saffier/core/db/fields/base.py
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
def operator_to_clause(
    self,
    field_name: str,
    operator: str,
    table: sqlalchemy.Table,
    value: typing.Any,
) -> typing.Any:
    """Translate one lookup operator into a SQLAlchemy clause.

    Args:
        field_name: Logical field name being filtered.
        operator: Saffier lookup suffix such as `exact` or `isnull`.
        table: SQLAlchemy table containing the target column.
        value: Lookup value supplied by the caller.

    Returns:
        Any: SQLAlchemy boolean clause implementing the lookup.
    """
    column = table.columns[field_name]
    mapped_operator = settings.filter_operators.get(operator, operator)

    if mapped_operator == "isnull":
        is_null = self.get_is_null_clause(column)
        return is_null if value else sqlalchemy.not_(is_null)

    if mapped_operator == "isempty":
        is_empty = self.get_is_empty_clause(column)
        return is_empty if value else sqlalchemy.not_(is_empty)

    return getattr(column, mapped_operator)(value)

get_fk_name

get_fk_name(name)
Source code in saffier/core/db/fields/base.py
817
818
819
def get_fk_name(self, name: str) -> str:
    fk_name = f"fk_{self.owner.meta.tablename}_{self.target.meta.tablename}_{name}"
    return fk_name[:CHAR_LIMIT]

get_fkindex_name

get_fkindex_name(name)
Source code in saffier/core/db/fields/base.py
821
822
823
def get_fkindex_name(self, name: str) -> str:
    fk_name = f"fkindex_{self.owner.meta.tablename}_{self.target.meta.tablename}_{name}"
    return fk_name[:CHAR_LIMIT]

get_fk_field_name

get_fk_field_name(name, fieldname)
Source code in saffier/core/db/fields/base.py
825
826
827
828
def get_fk_field_name(self, name: str, fieldname: str) -> str:
    if len(self.related_columns) == 1:
        return name
    return f"{name}_{fieldname}"

get_fk_column_name

get_fk_column_name(name, fieldname)
Source code in saffier/core/db/fields/base.py
830
831
832
833
834
def get_fk_column_name(self, name: str, fieldname: str) -> str:
    name = self.column_name or name
    if len(self.related_columns) == 1:
        return name
    return f"{name}_{fieldname}"

get_column_names

get_column_names(name)
Source code in saffier/core/db/fields/base.py
836
837
838
839
def get_column_names(self, name: str) -> tuple[str, ...]:
    return tuple(
        self.get_fk_field_name(name, field_name) for field_name in self.related_columns
    )

from_fk_field_name

from_fk_field_name(name, fieldname)
Source code in saffier/core/db/fields/base.py
841
842
843
844
def from_fk_field_name(self, name: str, fieldname: str) -> str:
    if len(self.related_columns) == 1:
        return next(iter(self.related_columns.keys()))
    return fieldname.removeprefix(f"{name}_")

pre_save_callback async

pre_save_callback(value, original_value, is_update)

Save nested related objects before persisting the owning row.

The callback allows callers to pass an unsaved related model instance or a dictionary payload. When necessary, the related object is saved first and the method returns the concrete foreign-key column values to persist on the owning model.

PARAMETER DESCRIPTION
value

Current relation value being persisted.

TYPE: Any

original_value

Previous relation value used during updates.

TYPE: Any

is_update

Whether the owning model is performing an update.

TYPE: bool

RETURNS DESCRIPTION
dict[str, Any]

dict[str, typing.Any]: Database-ready foreign-key column payload.

Source code in saffier/core/db/fields/base.py
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
async def pre_save_callback(
    self,
    value: typing.Any,
    original_value: typing.Any,
    is_update: bool,
) -> dict[str, typing.Any]:
    """Save nested related objects before persisting the owning row.

    The callback allows callers to pass an unsaved related model instance or
    a dictionary payload. When necessary, the related object is saved first
    and the method returns the concrete foreign-key column values to persist
    on the owning model.

    Args:
        value: Current relation value being persisted.
        original_value: Previous relation value used during updates.
        is_update: Whether the owning model is performing an update.

    Returns:
        dict[str, typing.Any]: Database-ready foreign-key column payload.
    """
    target = self.target
    if value is None or (isinstance(value, dict) and not value):
        value = original_value

    if isinstance(value, target):
        if (
            getattr(value, "_saffier_save_in_progress", False)
            or getattr(value, "pk", None) is not None
        ):
            return self.clean(self.name, value, for_query=False)
        await value.save()
        return self.clean(self.name, value, for_query=False)

    if hasattr(value, "__db_model__"):
        value_cls = value.__class__
        if (
            getattr(value_cls, "is_proxy_model", False)
            and getattr(value_cls, "parent", None) is target
        ):
            if (
                getattr(value, "_saffier_save_in_progress", False)
                or getattr(value, "pk", None) is not None
            ):
                return self.clean(self.name, value, for_query=False)
            await value.save()
            return self.clean(self.name, value, for_query=False)

    if isinstance(value, dict):
        return await self.pre_save_callback(
            target(**value),
            original_value=None,
            is_update=is_update,
        )

    if hasattr(value, "pk") and getattr(value, "pk", None) is not None:
        return self.clean(self.name, value, for_query=False)

    if value is None:
        return {}
    return {self.name: value}

is_cross_db

is_cross_db(owner_database=None)
Source code in saffier/core/db/fields/base.py
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
def is_cross_db(self, owner_database: typing.Any | None = None) -> bool:
    if owner_database is None:
        owner_database = getattr(self.owner, "database", None)
        if owner_database is None:
            owner_registry = getattr(getattr(self.owner, "meta", None), "registry", None)
            owner_database = getattr(owner_registry, "database", None)

    target = self.target
    target_database = getattr(target, "database", None)
    if target_database is None:
        target_registry = getattr(getattr(target, "meta", None), "registry", None)
        target_database = getattr(target_registry, "database", None)

    if owner_database is None or target_database is None:
        return False

    return str(owner_database.url) != str(target_database.url)
get_related_model_for_admin()
Source code in saffier/core/db/fields/base.py
1148
1149
1150
1151
1152
1153
1154
def get_related_model_for_admin(self) -> typing.Any | None:
    target = self.target
    registry = getattr(getattr(target, "meta", None), "registry", None)
    admin_models = getattr(registry, "admin_models", ())
    if registry and target.__name__ in admin_models:
        return target
    return None

get_column

get_column(name)
Source code in saffier/core/db/fields/base.py
1558
1559
def get_column(self, name: str) -> sqlalchemy.Column:
    return super().get_column(name)