add tests for cross model forward references, add docs for processing forwardrefs, wip on refactoring queries into separate pages based on functionality
This commit is contained in:
@ -92,7 +92,34 @@ class Post(ormar.Model):
|
||||
|
||||
It allows you to use `await post.categories.all()` but also `await category.posts.all()` to fetch data related only to specific post, category etc.
|
||||
|
||||
##Self-reference and postponed references
|
||||
|
||||
In order to create auto-relation or create two models that reference each other in at least two
|
||||
different relations (remember the reverse side is auto-registered for you), you need to use
|
||||
`ForwardRef` from `typing` module.
|
||||
|
||||
```python hl_lines="1 11 14"
|
||||
PersonRef = ForwardRef("Person")
|
||||
|
||||
|
||||
class Person(ormar.Model):
|
||||
class Meta(ModelMeta):
|
||||
metadata = metadata
|
||||
database = db
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
supervisor: PersonRef = ormar.ForeignKey(PersonRef, related_name="employees")
|
||||
|
||||
|
||||
Person.update_forward_refs()
|
||||
```
|
||||
|
||||
!!!tip
|
||||
To read more about self-reference and postponed relations visit [postponed-annotations][postponed-annotations] section
|
||||
|
||||
|
||||
[foreign-keys]: ./foreign-key.md
|
||||
[many-to-many]: ./many-to-many.md
|
||||
[queryset-proxy]: ./queryset-proxy.md
|
||||
[queryset-proxy]: ./queryset-proxy.md
|
||||
[postponed-annotations]: ./postponed-annotations.md
|
||||
171
docs/relations/postponed-annotations.md
Normal file
171
docs/relations/postponed-annotations.md
Normal file
@ -0,0 +1,171 @@
|
||||
# Postponed annotations
|
||||
|
||||
## Self-referencing Models
|
||||
|
||||
When you want to reference the same model during declaration to create a
|
||||
relation you need to declare the referenced model as a `ForwardRef`, as during the declaration
|
||||
the class is not yet ready and python by default won't let you reference it.
|
||||
|
||||
Although you might be tempted to use __future__ annotations or simply quote the name with `""` it won't work
|
||||
as `ormar` is designed to work with explicitly declared `ForwardRef`.
|
||||
|
||||
First, you need to import the required ref from typing.
|
||||
```python
|
||||
from typing import ForwardRef
|
||||
```
|
||||
|
||||
But note that before python 3.7 it used to be internal, so for python <= 3.6 you need
|
||||
|
||||
```python
|
||||
from typing import _ForwardRef as ForwardRef
|
||||
```
|
||||
|
||||
or since `pydantic` is required by `ormar` it can handle this switch for you.
|
||||
In that case you can simply import ForwardRef from pydantic regardless of your python version.
|
||||
|
||||
```python
|
||||
from pydantic.typing import ForwardRef
|
||||
```
|
||||
|
||||
Now we need a sample model and a reference to the same model,
|
||||
which will be used to creat a self referencing relation.
|
||||
|
||||
```python
|
||||
# create the forwardref to model Person
|
||||
PersonRef = ForwardRef("Person")
|
||||
|
||||
|
||||
class Person(ormar.Model):
|
||||
class Meta(ModelMeta):
|
||||
metadata = metadata
|
||||
database = db
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
# use the forwardref as to parameter
|
||||
supervisor: PersonRef = ormar.ForeignKey(PersonRef, related_name="employees")
|
||||
|
||||
```
|
||||
|
||||
That's so simple. But before you can use the model you need to manually update the references
|
||||
so that they lead to the actual models.
|
||||
|
||||
!!!warning
|
||||
If you try to use the model without updated references, `ModelError` exception will be raised.
|
||||
So in our example above any call like following will cause exception
|
||||
```python
|
||||
# creation of model - exception
|
||||
await Person.objects.create(name="Test")
|
||||
# initialization of model - exception
|
||||
Person2(name="Test")
|
||||
# usage of model's QuerySet - exception
|
||||
await Person2.objects.get()
|
||||
```
|
||||
|
||||
To update the references call the `update_forward_refs` method on **each model**
|
||||
with forward references, only **after all related models were declared.**
|
||||
|
||||
So in order to make our previous example work we need just one extra line.
|
||||
|
||||
```python hl_lines="14"
|
||||
PersonRef = ForwardRef("Person")
|
||||
|
||||
|
||||
class Person(ormar.Model):
|
||||
class Meta(ModelMeta):
|
||||
metadata = metadata
|
||||
database = db
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
supervisor: PersonRef = ormar.ForeignKey(PersonRef, related_name="employees")
|
||||
|
||||
|
||||
Person.update_forward_refs()
|
||||
|
||||
```
|
||||
|
||||
Of course the same can be done with ManyToMany relations in exactly same way, both for to
|
||||
and through parameters.
|
||||
|
||||
```python
|
||||
# declare the reference
|
||||
ChildRef = ForwardRef("Child")
|
||||
|
||||
class ChildFriend(ormar.Model):
|
||||
class Meta(ModelMeta):
|
||||
metadata = metadata
|
||||
database = db
|
||||
|
||||
class Child(ormar.Model):
|
||||
class Meta(ModelMeta):
|
||||
metadata = metadata
|
||||
database = db
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
# use it in relation
|
||||
friends = ormar.ManyToMany(ChildRef, through=ChildFriend,
|
||||
related_name="also_friends")
|
||||
|
||||
|
||||
Child.update_forward_refs()
|
||||
```
|
||||
|
||||
## Cross model relations
|
||||
|
||||
The same mechanism and logic as for self-reference model can be used to link multiple different
|
||||
models between each other.
|
||||
|
||||
Of course `ormar` links both sides of relation for you,
|
||||
creating a reverse relation with specified (or default) `related_name`.
|
||||
|
||||
But if you need two (or more) relations between any two models, that for whatever reason
|
||||
should be stored on both sides (so one relation is declared on one model,
|
||||
and other on the second model), you need to use `ForwardRef` to achieve that.
|
||||
|
||||
Look at the following simple example.
|
||||
|
||||
```python
|
||||
# teacher is not yet defined
|
||||
TeacherRef = ForwardRef("Teacher")
|
||||
|
||||
|
||||
class Student(ormar.Model):
|
||||
class Meta(ModelMeta):
|
||||
metadata = metadata
|
||||
database = db
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
# so we use reference instead of actual model
|
||||
primary_teacher: TeacherRef = ormar.ForeignKey(TeacherRef,
|
||||
related_name="own_students")
|
||||
|
||||
|
||||
class StudentTeacher(ormar.Model):
|
||||
class Meta(ModelMeta):
|
||||
tablename = 'students_x_teachers'
|
||||
metadata = metadata
|
||||
database = db
|
||||
|
||||
|
||||
class Teacher(ormar.Model):
|
||||
class Meta(ModelMeta):
|
||||
metadata = metadata
|
||||
database = db
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
# we need students for other relation hence the order
|
||||
students = ormar.ManyToMany(Student, through=StudentTeacher,
|
||||
related_name="teachers")
|
||||
|
||||
# now the Teacher model is already defined we can update references
|
||||
Student.update_forward_refs()
|
||||
|
||||
```
|
||||
|
||||
!!!warning
|
||||
Remember that `related_name` needs to be unique across related models regardless
|
||||
of how many relations are defined.
|
||||
Reference in New Issue
Block a user