197 lines
4.9 KiB
Markdown
197 lines
4.9 KiB
Markdown
# Relations
|
|
|
|
## Defining a relationship
|
|
|
|
### ForeignKey
|
|
|
|
`ForeignKey(to, related_name=None)` has required parameters `to` that takes target `Model` class.
|
|
|
|
Sqlalchemy column and Type are automatically taken from target `Model`.
|
|
|
|
* Sqlalchemy column: class of a target `Model` primary key column
|
|
* Type (used for pydantic): type of a target `Model`
|
|
|
|
#### Defining Models
|
|
|
|
To define a relation add `ForeignKey` field that points to related `Model`.
|
|
|
|
```Python hl_lines="29"
|
|
--8<-- "../docs_src/fields/docs003.py"
|
|
```
|
|
|
|
#### Reverse Relation
|
|
|
|
`ForeignKey` fields are automatically registering reverse side of the relation.
|
|
|
|
By default it's child (source) `Model` name + s, like courses in snippet below:
|
|
|
|
```Python hl_lines="29 35"
|
|
--8<-- "../docs_src/fields/docs001.py"
|
|
```
|
|
|
|
#### related_name
|
|
|
|
But you can overwrite this name by providing `related_name` parameter like below:
|
|
|
|
```Python hl_lines="29 35"
|
|
--8<-- "../docs_src/fields/docs002.py"
|
|
```
|
|
|
|
!!!tip
|
|
The reverse relation on access returns list of `wekref.proxy` to avoid circular references.
|
|
|
|
|
|
### Relation Setup
|
|
|
|
You have several ways to set-up a relationship connection.
|
|
|
|
#### `Model` instance
|
|
|
|
The most obvious one is to pass a related `Model` instance to the constructor.
|
|
|
|
```Python hl_lines="34-35"
|
|
--8<-- "../docs_src/relations/docs001.py"
|
|
```
|
|
|
|
#### Primary key value
|
|
|
|
You can setup the relation also with just the pk column value of the related model.
|
|
|
|
```Python hl_lines="37-38"
|
|
--8<-- "../docs_src/relations/docs001.py"
|
|
```
|
|
|
|
#### Dictionary
|
|
|
|
Next option is with a dictionary of key-values of the related model.
|
|
|
|
You can build the dictionary yourself or get it from existing model with `dict()` method.
|
|
|
|
```Python hl_lines="40-41"
|
|
--8<-- "../docs_src/relations/docs001.py"
|
|
```
|
|
|
|
#### None
|
|
|
|
Finally you can explicitly set it to None (default behavior if no value passed).
|
|
|
|
```Python hl_lines="43-44"
|
|
--8<-- "../docs_src/relations/docs001.py"
|
|
```
|
|
|
|
!!!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.
|
|
|
|
|
|
### ManyToMany
|
|
|
|
`ManyToMany(to, through)` has required parameters `to` and `through` that takes target and relation `Model` classes.
|
|
|
|
Sqlalchemy column and Type are automatically taken from target `Model`.
|
|
|
|
* Sqlalchemy column: class of a target `Model` primary key column
|
|
* Type (used for pydantic): type of a target `Model`
|
|
|
|
####Defining `Models`:
|
|
|
|
```Python
|
|
--8<-- "../docs_src/relations/docs002.py"
|
|
```
|
|
|
|
Create sample data:
|
|
```Python
|
|
guido = await Author.objects.create(first_name="Guido", last_name="Van Rossum")
|
|
post = await Post.objects.create(title="Hello, M2M", author=guido)
|
|
news = await Category.objects.create(name="News")
|
|
```
|
|
|
|
#### Adding related models
|
|
|
|
```python
|
|
# Add a category to a post.
|
|
await post.categories.add(news)
|
|
# or from the other end:
|
|
await news.posts.add(post)
|
|
```
|
|
|
|
!!!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.
|
|
|
|
#### create()
|
|
|
|
Create related `Model` directly from parent `Model`.
|
|
|
|
The link table is automatically populated, as well as relation ids in the database.
|
|
|
|
```python
|
|
# Creating columns object from instance:
|
|
await post.categories.create(name="Tips")
|
|
assert len(await post.categories.all()) == 2
|
|
# newly created instance already have relation persisted in the database
|
|
```
|
|
|
|
!!!note
|
|
Note that when accessing QuerySet API methods through ManyToMany relation you don't
|
|
need to use objects attribute like in normal queries.
|
|
|
|
To learn more about available QuerySet methods visit [queries][queries]
|
|
|
|
#### remove()
|
|
|
|
Removal of the related model one by one.
|
|
|
|
Removes also the relation in the database.
|
|
|
|
```python
|
|
await news.posts.remove(post)
|
|
```
|
|
|
|
#### clear()
|
|
|
|
Removal all related models in one call.
|
|
|
|
Removes also the relation in the database.
|
|
|
|
```python
|
|
await news.posts.clear()
|
|
```
|
|
|
|
#### Other queryset methods
|
|
|
|
When access directly the related `ManyToMany` field returns the list of related models.
|
|
|
|
But at the same time it exposes full QuerySet API, so you can filter, create, select related etc.
|
|
|
|
```python
|
|
# Many to many relation exposes a list of columns models
|
|
# and an API of the Queryset:
|
|
assert news == await post.categories.get(name="News")
|
|
|
|
# with all Queryset methods - filtering, selecting columns, counting etc.
|
|
await news.posts.filter(title__contains="M2M").all()
|
|
await Category.objects.filter(posts__author=guido).get()
|
|
|
|
# columns models of many to many relation can be prefetched
|
|
news_posts = await news.posts.select_related("author").all()
|
|
assert news_posts[0].author == guido
|
|
```
|
|
|
|
Currently supported methods are:
|
|
|
|
!!!tip
|
|
To learn more about available QuerySet methods visit [queries][queries]
|
|
|
|
##### get()
|
|
##### all()
|
|
##### filter()
|
|
##### select_related()
|
|
##### limit()
|
|
##### offset()
|
|
##### count()
|
|
##### exists()
|
|
|
|
[queries]: ./queries.md |