ManyToMany
ManyToMany(to, through) has required parameters to and optional through that takes target and relation Model classes.
Sqlalchemy column and Type are automatically taken from target Model.
- Sqlalchemy column: class of a target
Modelprimary key column - Type (used for pydantic): type of a target
Model
Defining Models
| Python | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | |
Create sample data:
| Python | |
|---|---|
1 2 3 | |
Reverse relation
ForeignKey fields are automatically registering reverse side of the relation.
By default it's child (source) Model name + s, like posts in snippet below:
| Python | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
Reverse relation exposes API to manage related objects also from parent side.
related_name
By default, the related_name is generated in the same way as for the ForeignKey relation (class.name.lower()+'s'),
but in the same way you can overwrite this name by providing related_name parameter like below:
| Python | |
|---|---|
1 2 3 | |
Warning
When you provide multiple relations to the same model ormar can no longer auto generate
the related_name for you. Therefore, in that situation you have to provide related_name
for all but one (one can be default and generated) or all related fields.
Skipping reverse relation
If you are sure you don't want the reverse relation you can use skip_reverse=True
flag of the ManyToMany.
If you set skip_reverse flag internally the field is still registered on the other
side of the relationship so you can:
filterby related models fields from reverse modelorder_byby related models fields from reverse model
But you cannot:
- access the related field from reverse model with
related_name - even if you
select_relatedfrom reverse side of the model the returned models won't be populated in reversed instance (the join is not prevented so you still canfilterandorder_byover the relation) - the relation won't be populated in
model_dump()andjson() - you cannot pass the nested related objects when populating from dictionary or json (also through
fastapi). It will be either ignored or error will be raised depending onextrasetting in pydanticConfig.
Example:
| Python | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | |
Through Model
Optionally if you want to add additional fields you can explicitly create and pass the through model class.
| Python | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | |
Warning
Note that even of you do not provide through model it's going to be created for you automatically and
still has to be included in example in alembic migrations.
Tip
Note that you need to provide through model if you want to
customize the Through model name or the database table name of this model.
If you do not provide the Through field it will be generated for you.
The default naming convention is:
- for class name it's union of both classes name (parent+other) so in example above
it would be
PostCategory - for table name it similar but with underscore in between and s in the end of class
lowercase name, in example above would be
posts_categorys
Customizing Through relation names
By default Through model relation names default to related model name in lowercase.
So in example like this:
| Python | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
To customize the names of fields/relation in Through model now you can use new parameters to ManyToMany:
through_relation_name- name of the field leading to the model in whichManyToManyis declaredthrough_reverse_relation_name- name of the field leading to the model to whichManyToManyleads to
Example:
| Python | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Note
Note that explicitly declaring relations in Through model is forbidden, so even if you
provide your own custom Through model you cannot change the names there and you need to use
same through_relation_name and through_reverse_relation_name parameters.
Overriding foreign key constraint names on the through model
Auto-generated foreign key constraint names on the through model can be overridden with:
through_foreign_key_name- name of the FK constraint on the column that references the model whereManyToManyis declared (the owner side).through_reverse_foreign_key_name- name of the FK constraint on the column that references the target model.
This is primarily useful for databases with short identifier limits (for example MySQL's 64 character limit) where the auto-generated name would be truncated.
| Python | |
|---|---|
1 2 3 4 5 6 7 8 9 10 | |
Making through relation columns non-nullable
By default the auto-generated foreign key columns on the through table are nullable (matching SQLAlchemy's default for non primary-key columns). This can be overridden per column with:
through_relation_nullable- controls nullability of the column pointing to the model whereManyToManyis declared (the owner side). Defaults toTrue.through_reverse_relation_nullable- controls nullability of the column pointing to the target model. Defaults toTrue.
Set either (or both) to False when you want the database to enforce that a
through row always references both sides.
| Python | |
|---|---|
1 2 3 4 5 6 7 8 9 10 | |
Through Fields
The through field is auto added to the reverse side of the relation.
The exposed field is named as lowercase Through class name.
The exposed field explicitly has no relations loaded as the relation is already populated in ManyToMany field,
so it's useful only when additional fields are provided on Through model.
In a sample model setup as following:
| Python | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | |
the through field can be used as a normal model field in most of the QuerySet operations.
Note that through field is attached only to related side of the query so:
| Python | |
|---|---|
1 2 3 4 5 6 7 8 9 10 | |
Through field can be used for filtering the data.
| Python | |
|---|---|
1 2 3 4 5 | |
Tip
Note that despite that the actual instance is not populated on source model,
in queries, order by statements etc you can access through model from both sides.
So below query has exactly the same effect (note access through categories)
| Python | |
|---|---|
1 2 3 4 5 | |
Through model can be used in order by queries.
| Python | |
|---|---|
1 2 3 4 5 | |
You can also select subset of the columns in a normal QuerySet way with fields
and exclude_fields.
| Python | |
|---|---|
1 2 3 4 5 | |
Warning
Note that because through fields explicitly nullifies all relation fields, as relation
is populated in ManyToMany field, you should not use the standard model methods like
save() and update() before re-loading the field from database.
If you want to modify the through field in place remember to reload it from database. Otherwise you will set relations to None so effectively make the field useless!
| Python | |
|---|---|
1 2 3 4 | |
pk_only models
(only primary key is set) so they are not fully populated, but it's enough to preserve
the relation on update.
Warning
If you use i.e. fastapi the partially loaded related models on through field might cause
pydantic validation errors (that's the primary reason why they are not populated by default).
So either you need to exclude the related fields in your response, or fully load the related
models. In example above it would mean:
| Python | |
|---|---|
1 2 | |
load_all():
| Python | |
|---|---|
1 | |
Preferred way of update is through queryset proxy update() method
| Python | |
|---|---|
1 2 | |
Relation methods
add
add(item: Model, **kwargs)
Allows you to add model to ManyToMany relation.
| Python | |
|---|---|
1 2 3 4 | |
Warning
In all not None cases the primary key value for related model has to exist in database.
Otherwise an IntegrityError will be raised by your database driver library.
If you declare your models with a Through model with additional fields, you can populate them during adding child model to relation.
In order to do so, pass keyword arguments with field names and values to add() call.
Note that this works only for ManyToMany relations.
| Python | |
|---|---|
1 2 3 4 | |
remove
Removal of the related model one by one.
Removes also the relation in the database.
| Python | |
|---|---|
1 | |
clear
Removal of all related models in one call.
Removes also the relation in the database.
| Python | |
|---|---|
1 | |
QuerysetProxy
Reverse relation exposes QuerysetProxy API that allows you to query related model like you would issue a normal Query.
To read which methods of QuerySet are available read below querysetproxy