Skip to content

many_to_many

ManyToManyField

Bases: ForeignKeyField, QuerySetProtocol, RelationProtocol

Actual class returned from ManyToMany function call and stored in model_fields.

Source code in ormar\fields\many_to_many.py
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
class ManyToManyField(  # type: ignore
    ForeignKeyField,
    ormar.QuerySetProtocol,
    ormar.RelationProtocol,
):
    """
    Actual class returned from ManyToMany function call and stored in model_fields.
    """

    def __init__(self, **kwargs: Any) -> None:
        if TYPE_CHECKING:  # pragma: no cover
            self.__type__: type
            self.to: Type["Model"]
            self.through: Type["Model"]
        super().__init__(**kwargs)

    def get_source_related_name(self) -> str:
        """
        Returns name to use for source relation name.
        For FK it's the same, differs for m2m fields.
        It's either set as `related_name` or by default it's field name.
        :return: name of the related_name or default related name.
        :rtype: str
        """
        return (
            self.through.ormar_config.model_fields[
                self.default_source_field_name()
            ].related_name
            or self.name
        )

    def has_unresolved_forward_refs(self) -> bool:
        """
        Verifies if the filed has any ForwardRefs that require updating before the
        model can be used.

        :return: result of the check
        :rtype: bool
        """
        return self.to.__class__ == ForwardRef or self.through.__class__ == ForwardRef

    def evaluate_forward_ref(self, globalns: Any, localns: Any) -> None:
        """
        Evaluates the ForwardRef to actual Field based on global and local namespaces

        :param globalns: global namespace
        :type globalns: Any
        :param localns: local namespace
        :type localns: Any
        :return: None
        :rtype: None
        """
        if self.to.__class__ == ForwardRef:
            self._evaluate_forward_ref(globalns, localns)
            (
                self.__type__,
                self.column_type,
                pk_only_model,
            ) = populate_m2m_params_based_on_to_model(
                to=self.to, nullable=self.nullable
            )
            self.to_pk_only = pk_only_model

        if self.through.__class__ == ForwardRef:
            self._evaluate_forward_ref(globalns, localns, is_through=True)

            forbid_through_relations(self.through)

    def get_relation_name(self) -> str:
        """
        Returns name of the relation, which can be a own name or through model
        names for m2m models

        :return: result of the check
        :rtype: bool
        """
        if self.self_reference and self.name == self.self_reference_primary:
            return self.default_source_field_name()
        return self.default_target_field_name()

    def get_source_model(self) -> Type["Model"]:
        """
        Returns model from which the relation comes -> either owner or through model

        :return: source model
        :rtype: Type["Model"]
        """
        return self.through

    def get_filter_clause_target(self) -> Type["Model"]:
        return self.through

    def get_model_relation_fields(self, use_alias: bool = False) -> str:
        """
        Extract names of the database columns or model fields that are connected
        with given relation based on use_alias switch.

        :param use_alias: use db names aliases or model fields
        :type use_alias: bool
        :return: name or names of the related columns/ fields
        :rtype: Union[str, List[str]]
        """
        pk_field = self.owner.ormar_config.model_fields[self.owner.ormar_config.pkname]
        result = pk_field.get_alias() if use_alias else pk_field.name
        return result

    def get_related_field_alias(self) -> str:
        """
        Extract names of the related database columns or that are connected
        with given relation based to use as a target in filter clause.

        :return: name or names of the related columns/ fields
        :rtype: Union[str, Dict[str, str]]
        """
        if self.self_reference and self.self_reference_primary == self.name:
            field_name = self.default_target_field_name()
        else:
            field_name = self.default_source_field_name()
        sub_field = self.through.ormar_config.model_fields[field_name]
        return sub_field.get_alias()

    def get_related_field_name(self) -> Union[str, List[str]]:
        """
        Returns name of the relation field that should be used in prefetch query.
        This field is later used to register relation in prefetch query,
        populate relations dict, and populate nested model in prefetch query.

        :return: name(s) of the field
        :rtype: Union[str, List[str]]
        """
        if self.self_reference and self.self_reference_primary == self.name:
            return self.default_target_field_name()
        return self.default_source_field_name()

    def create_default_through_model(self) -> None:
        """
        Creates default empty through model if no additional fields are required.
        """
        owner_name = self.owner.get_name(lower=False)
        to_name = self.to.get_name(lower=False)
        class_name = f"{owner_name}{to_name}"
        table_name = f"{owner_name.lower()}s_{to_name.lower()}s"
        base_namespace = {
            "__module__": self.owner.__module__,
            "__qualname__": f"{self.owner.__qualname__}.{class_name}",
        }
        new_config = ormar.models.ormar_config.OrmarConfig(
            tablename=table_name,
            database=self.owner.ormar_config.database,
            metadata=self.owner.ormar_config.metadata,
        )
        through_model = type(
            class_name,
            (ormar.Model,),
            {
                **base_namespace,
                "ormar_config": new_config,
                "id": ormar.Integer(name="id", primary_key=True),
            },
        )
        self.through = cast(Type["Model"], through_model)

create_default_through_model()

Creates default empty through model if no additional fields are required.

Source code in ormar\fields\many_to_many.py
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
def create_default_through_model(self) -> None:
    """
    Creates default empty through model if no additional fields are required.
    """
    owner_name = self.owner.get_name(lower=False)
    to_name = self.to.get_name(lower=False)
    class_name = f"{owner_name}{to_name}"
    table_name = f"{owner_name.lower()}s_{to_name.lower()}s"
    base_namespace = {
        "__module__": self.owner.__module__,
        "__qualname__": f"{self.owner.__qualname__}.{class_name}",
    }
    new_config = ormar.models.ormar_config.OrmarConfig(
        tablename=table_name,
        database=self.owner.ormar_config.database,
        metadata=self.owner.ormar_config.metadata,
    )
    through_model = type(
        class_name,
        (ormar.Model,),
        {
            **base_namespace,
            "ormar_config": new_config,
            "id": ormar.Integer(name="id", primary_key=True),
        },
    )
    self.through = cast(Type["Model"], through_model)

evaluate_forward_ref(globalns, localns)

Evaluates the ForwardRef to actual Field based on global and local namespaces

Parameters:

Name Type Description Default
globalns Any

global namespace

required
localns Any

local namespace

required

Returns:

Type Description
None

None

Source code in ormar\fields\many_to_many.py
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
def evaluate_forward_ref(self, globalns: Any, localns: Any) -> None:
    """
    Evaluates the ForwardRef to actual Field based on global and local namespaces

    :param globalns: global namespace
    :type globalns: Any
    :param localns: local namespace
    :type localns: Any
    :return: None
    :rtype: None
    """
    if self.to.__class__ == ForwardRef:
        self._evaluate_forward_ref(globalns, localns)
        (
            self.__type__,
            self.column_type,
            pk_only_model,
        ) = populate_m2m_params_based_on_to_model(
            to=self.to, nullable=self.nullable
        )
        self.to_pk_only = pk_only_model

    if self.through.__class__ == ForwardRef:
        self._evaluate_forward_ref(globalns, localns, is_through=True)

        forbid_through_relations(self.through)

get_model_relation_fields(use_alias=False)

Extract names of the database columns or model fields that are connected with given relation based on use_alias switch.

Parameters:

Name Type Description Default
use_alias bool

use db names aliases or model fields

False

Returns:

Type Description
Union[str, List[str]]

name or names of the related columns/ fields

Source code in ormar\fields\many_to_many.py
274
275
276
277
278
279
280
281
282
283
284
285
286
def get_model_relation_fields(self, use_alias: bool = False) -> str:
    """
    Extract names of the database columns or model fields that are connected
    with given relation based on use_alias switch.

    :param use_alias: use db names aliases or model fields
    :type use_alias: bool
    :return: name or names of the related columns/ fields
    :rtype: Union[str, List[str]]
    """
    pk_field = self.owner.ormar_config.model_fields[self.owner.ormar_config.pkname]
    result = pk_field.get_alias() if use_alias else pk_field.name
    return result

Extract names of the related database columns or that are connected with given relation based to use as a target in filter clause.

Returns:

Type Description
Union[str, Dict[str, str]]

name or names of the related columns/ fields

Source code in ormar\fields\many_to_many.py
288
289
290
291
292
293
294
295
296
297
298
299
300
301
def get_related_field_alias(self) -> str:
    """
    Extract names of the related database columns or that are connected
    with given relation based to use as a target in filter clause.

    :return: name or names of the related columns/ fields
    :rtype: Union[str, Dict[str, str]]
    """
    if self.self_reference and self.self_reference_primary == self.name:
        field_name = self.default_target_field_name()
    else:
        field_name = self.default_source_field_name()
    sub_field = self.through.ormar_config.model_fields[field_name]
    return sub_field.get_alias()

Returns name of the relation field that should be used in prefetch query. This field is later used to register relation in prefetch query, populate relations dict, and populate nested model in prefetch query.

Returns:

Type Description
Union[str, List[str]]

name(s) of the field

Source code in ormar\fields\many_to_many.py
303
304
305
306
307
308
309
310
311
312
313
314
def get_related_field_name(self) -> Union[str, List[str]]:
    """
    Returns name of the relation field that should be used in prefetch query.
    This field is later used to register relation in prefetch query,
    populate relations dict, and populate nested model in prefetch query.

    :return: name(s) of the field
    :rtype: Union[str, List[str]]
    """
    if self.self_reference and self.self_reference_primary == self.name:
        return self.default_target_field_name()
    return self.default_source_field_name()

get_relation_name()

Returns name of the relation, which can be a own name or through model names for m2m models

Returns:

Type Description
bool

result of the check

Source code in ormar\fields\many_to_many.py
250
251
252
253
254
255
256
257
258
259
260
def get_relation_name(self) -> str:
    """
    Returns name of the relation, which can be a own name or through model
    names for m2m models

    :return: result of the check
    :rtype: bool
    """
    if self.self_reference and self.name == self.self_reference_primary:
        return self.default_source_field_name()
    return self.default_target_field_name()

get_source_model()

Returns model from which the relation comes -> either owner or through model

Returns:

Type Description
Type["Model"]

source model

Source code in ormar\fields\many_to_many.py
262
263
264
265
266
267
268
269
def get_source_model(self) -> Type["Model"]:
    """
    Returns model from which the relation comes -> either owner or through model

    :return: source model
    :rtype: Type["Model"]
    """
    return self.through

Returns name to use for source relation name. For FK it's the same, differs for m2m fields. It's either set as related_name or by default it's field name.

Returns:

Type Description
str

name of the related_name or default related name.

Source code in ormar\fields\many_to_many.py
198
199
200
201
202
203
204
205
206
207
208
209
210
211
def get_source_related_name(self) -> str:
    """
    Returns name to use for source relation name.
    For FK it's the same, differs for m2m fields.
    It's either set as `related_name` or by default it's field name.
    :return: name of the related_name or default related name.
    :rtype: str
    """
    return (
        self.through.ormar_config.model_fields[
            self.default_source_field_name()
        ].related_name
        or self.name
    )

has_unresolved_forward_refs()

Verifies if the filed has any ForwardRefs that require updating before the model can be used.

Returns:

Type Description
bool

result of the check

Source code in ormar\fields\many_to_many.py
213
214
215
216
217
218
219
220
221
def has_unresolved_forward_refs(self) -> bool:
    """
    Verifies if the filed has any ForwardRefs that require updating before the
    model can be used.

    :return: result of the check
    :rtype: bool
    """
    return self.to.__class__ == ForwardRef or self.through.__class__ == ForwardRef

ManyToMany(to, through=None, *, name=None, unique=False, virtual=False, **kwargs)

Despite a name it's a function that returns constructed ManyToManyField. This function is actually used in model declaration (as ormar.ManyToMany(ToModel, through=ThroughModel)).

Accepts number of relation setting parameters as well as all BaseField ones.

Parameters:

Name Type Description Default
to Union[Type[T], ForwardRef]

target related ormar Model

required
through Optional[Union[Type[T], ForwardRef]]

through model for m2m relation

None
name Optional[str]

name of the database field - later called alias

None
unique bool

parameter passed to sqlalchemy.ForeignKey, unique flag

False
virtual bool

marks if relation is virtual. It is for reversed FK and auto generated FK on through model in Many2Many relations.

False
kwargs Any

all other args to be populated by BaseField

{}

Returns:

Type Description
ManyToManyField

ormar ManyToManyField with m2m relation to selected model

Source code in ormar\fields\many_to_many.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
def ManyToMany(  # type: ignore
    to: Union[Type["T"], "ForwardRef"],
    through: Optional[Union[Type["T"], "ForwardRef"]] = None,
    *,
    name: Optional[str] = None,
    unique: bool = False,
    virtual: bool = False,
    **kwargs: Any,
) -> "RelationProxy[T]":
    """
    Despite a name it's a function that returns constructed ManyToManyField.
    This function is actually used in model declaration
    (as ormar.ManyToMany(ToModel, through=ThroughModel)).

    Accepts number of relation setting parameters as well as all BaseField ones.

    :param to: target related ormar Model
    :type to: Model class
    :param through: through model for m2m relation
    :type through: Model class
    :param name: name of the database field - later called alias
    :type name: str
    :param unique: parameter passed to sqlalchemy.ForeignKey, unique flag
    :type unique: bool
    :param virtual: marks if relation is virtual.
    It is for reversed FK and auto generated FK on through model in Many2Many relations.
    :type virtual: bool
    :param kwargs: all other args to be populated by BaseField
    :type kwargs: Any
    :return: ormar ManyToManyField with m2m relation to selected model
    :rtype: ManyToManyField
    """
    related_name = kwargs.pop("related_name", None)
    nullable = kwargs.pop("nullable", True)

    owner = kwargs.pop("owner", None)
    self_reference = kwargs.pop("self_reference", False)

    orders_by = kwargs.pop("orders_by", None)
    related_orders_by = kwargs.pop("related_orders_by", None)

    skip_reverse = kwargs.pop("skip_reverse", False)
    skip_field = kwargs.pop("skip_field", False)

    through_relation_name = kwargs.pop("through_relation_name", None)
    through_reverse_relation_name = kwargs.pop("through_reverse_relation_name", None)

    if through is not None and through.__class__ != ForwardRef:
        forbid_through_relations(cast(Type["Model"], through))

    validate_not_allowed_fields(kwargs)
    pk_only_model = None
    if to.__class__ == ForwardRef:
        __type__ = (
            Union[to, List[to]]  # type: ignore
            if not nullable
            else Optional[Union[to, List[to]]]  # type: ignore
        )
        column_type = None
    else:
        __type__, column_type, pk_only_model = populate_m2m_params_based_on_to_model(
            to=to, nullable=nullable  # type: ignore
        )
    namespace = dict(
        __type__=__type__,
        to=to,
        to_pk_only=pk_only_model,
        through=through,
        alias=name,
        name=name,
        nullable=nullable,
        unique=unique,
        column_type=column_type,
        related_name=related_name,
        virtual=virtual,
        primary_key=False,
        index=False,
        default=None,
        server_default=None,
        owner=owner,
        self_reference=self_reference,
        is_relation=True,
        is_multi=True,
        orders_by=orders_by,
        related_orders_by=related_orders_by,
        skip_reverse=skip_reverse,
        skip_field=skip_field,
        through_relation_name=through_relation_name,
        through_reverse_relation_name=through_reverse_relation_name,
    )

    Field = type("ManyToMany", (ManyToManyField, BaseField), {})
    return Field(**namespace)

forbid_through_relations(through)

Verifies if the through model does not have relations.

Parameters:

Name Type Description Default
through Type[Model]

through Model to be checked

required
Source code in ormar\fields\many_to_many.py
30
31
32
33
34
35
36
37
38
39
40
41
42
def forbid_through_relations(through: Type["Model"]) -> None:
    """
    Verifies if the through model does not have relations.

    :param through: through Model to be checked
    :type through: Type['Model]
    """
    if any(field.is_relation for field in through.ormar_config.model_fields.values()):
        raise ModelDefinitionError(
            f"Through Models cannot have explicit relations "
            f"defined. Remove the relations from Model "
            f"{through.get_name(lower=False)}"
        )

populate_m2m_params_based_on_to_model(to, nullable)

Based on target to model to which relation leads to populates the type of the pydantic field to use and type of the target column field.

Parameters:

Name Type Description Default
to Type[Model]

target related ormar Model

required
nullable bool

marks field as optional/ required

required

Returns:

Type Description
tuple with target pydantic type and target col type

Tuple[List, Any]

Source code in ormar\fields\many_to_many.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def populate_m2m_params_based_on_to_model(
    to: Type["Model"], nullable: bool
) -> Tuple[Any, Any, Any]:
    """
    Based on target to model to which relation leads to populates the type of the
    pydantic field to use and type of the target column field.

    :param to: target related ormar Model
    :type to: Model class
    :param nullable: marks field as optional/ required
    :type nullable: bool
    :return: Tuple[List, Any]
    :rtype: tuple with target pydantic type and target col type
    """
    to_field = to.ormar_config.model_fields[to.ormar_config.pkname]
    pk_only_model = create_dummy_model(to, to_field)
    base_type = Union[  # type: ignore
        to_field.__type__,  # type: ignore
        to,  # type: ignore
        pk_only_model,  # type: ignore
        List[to],  # type: ignore
        List[pk_only_model],  # type: ignore
    ]
    __type__ = (
        base_type  # type: ignore
        if not nullable
        else Optional[base_type]  # type: ignore
    )
    column_type = to_field.column_type
    return __type__, column_type, pk_only_model