work on relations docs
This commit is contained in:
@ -0,0 +1,96 @@
|
||||
# Relations
|
||||
|
||||
## Defining a relationship
|
||||
|
||||
### Foreign Key
|
||||
|
||||
To define a relationship you simply need to create a ForeignKey field on one `Model` and point it to another `Model`.
|
||||
|
||||
```Python hl_lines="24"
|
||||
--8<-- "../docs_src/relations/docs001.py"
|
||||
```
|
||||
|
||||
It automatically creates an sql foreign key constraint on a underlying table as well as nested pydantic model in the definition.
|
||||
|
||||
|
||||
```Python hl_lines="29 33"
|
||||
--8<-- "../docs_src/relations/docs002.py"
|
||||
```
|
||||
|
||||
Of course it's handled for you so you don't have to delve deep into this but you can.
|
||||
|
||||
### Reverse Relation
|
||||
|
||||
At the same time the reverse relationship is registered automatically on parent model (target of `ForeignKey`).
|
||||
|
||||
By default it's child (source) `Model` name + 's', like courses in snippet below:
|
||||
|
||||
```Python hl_lines="25 31"
|
||||
--8<-- "../docs_src/fields/docs001.py"
|
||||
```
|
||||
|
||||
But you can overwrite this name by providing `related_name` parameter like below:
|
||||
|
||||
```Python hl_lines="25 30"
|
||||
--8<-- "../docs_src/fields/docs002.py"
|
||||
```
|
||||
|
||||
!!!tip
|
||||
Since related models are coming from Relationship Manager the reverse relation on access returns list of `wekref.proxy` to avoid circular references.
|
||||
|
||||
## Relationship Manager
|
||||
|
||||
Since orm uses Sqlalchemy core under the hood to prepare the queries,
|
||||
the orm needs a way to uniquely identify each relationship between to tables to construct working queries.
|
||||
|
||||
Imagine that you have models as following:
|
||||
|
||||
```Python
|
||||
--8<-- "../docs_src/relations/docs003.py"
|
||||
```
|
||||
|
||||
Now imagine that you want to go from school class to student and his category and to teacher and his category.
|
||||
|
||||
```Python
|
||||
classes = await SchoolClass.objects.select_related(
|
||||
["teachers__category", "students__category"]).all()
|
||||
```
|
||||
|
||||
!!!note
|
||||
To select related models use `select_related` method from `Model` `QuerySet`.
|
||||
|
||||
Note that you use relation (`ForeignKey`) names and not the table names.
|
||||
|
||||
Since you join two times to the same table it won't work by default -> you would need to use aliases for category tables and columns.
|
||||
|
||||
But don't worry - orm can handle situations like this, as it uses the Relationship Manager which has it's aliases defined for all relationships.
|
||||
|
||||
Each class is registered with the same instance of the RelationshipManager that you can access like this:
|
||||
|
||||
```python
|
||||
SchoolClass._orm_relationship_manager
|
||||
```
|
||||
|
||||
It's the same object for all `Models`
|
||||
|
||||
```python
|
||||
print(Teacher._orm_relationship_manager == Student._orm_relationship_manager)
|
||||
# will produce: True
|
||||
```
|
||||
|
||||
You can even preview the alias used for any relation by passing two tables names.
|
||||
|
||||
```python
|
||||
print(Teacher._orm_relationship_manager.resolve_relation_join(
|
||||
'students', 'categories'))
|
||||
# will produce: KId1c6 (sample value)
|
||||
|
||||
print(Teacher._orm_relationship_manager.resolve_relation_join(
|
||||
'categories', 'students'))
|
||||
# will produce: EFccd5 (sample value)
|
||||
```
|
||||
|
||||
!!!note
|
||||
The order that you pass the names matters -> as those are 2 different relationships depending on join order.
|
||||
|
||||
As aliases are produced randomly you can be presented with different results.
|
||||
|
||||
26
docs_src/relations/docs001.py
Normal file
26
docs_src/relations/docs001.py
Normal file
@ -0,0 +1,26 @@
|
||||
import orm
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Album(orm.Model):
|
||||
__tablename__ = "album"
|
||||
__metadata__ = metadata
|
||||
__database__ = database
|
||||
|
||||
id = orm.Integer(primary_key=True)
|
||||
name = orm.String(length=100)
|
||||
|
||||
|
||||
class Track(orm.Model):
|
||||
__tablename__ = "track"
|
||||
__metadata__ = metadata
|
||||
__database__ = database
|
||||
|
||||
id = orm.Integer(primary_key=True)
|
||||
album = orm.ForeignKey(Album)
|
||||
title = orm.String(length=100)
|
||||
position = orm.Integer()
|
||||
39
docs_src/relations/docs002.py
Normal file
39
docs_src/relations/docs002.py
Normal file
@ -0,0 +1,39 @@
|
||||
import orm
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Album(orm.Model):
|
||||
__tablename__ = "album"
|
||||
__metadata__ = metadata
|
||||
__database__ = database
|
||||
|
||||
id = orm.Integer(primary_key=True)
|
||||
name = orm.String(length=100)
|
||||
|
||||
|
||||
class Track(orm.Model):
|
||||
__tablename__ = "track"
|
||||
__metadata__ = metadata
|
||||
__database__ = database
|
||||
|
||||
id = orm.Integer(primary_key=True)
|
||||
album = orm.ForeignKey(Album)
|
||||
title = orm.String(length=100)
|
||||
position = orm.Integer()
|
||||
|
||||
|
||||
print(Track.__table__.columns['album'].__repr__())
|
||||
# Will produce:
|
||||
# Column('album', Integer(), ForeignKey('album.id'), table=<track>)
|
||||
|
||||
print(Track.__pydantic_model__.__fields__['album'])
|
||||
# Will produce:
|
||||
# ModelField(
|
||||
# name='album'
|
||||
# type=Optional[Album]
|
||||
# required=False
|
||||
# default=None)
|
||||
44
docs_src/relations/docs003.py
Normal file
44
docs_src/relations/docs003.py
Normal file
@ -0,0 +1,44 @@
|
||||
import databases
|
||||
import sqlalchemy
|
||||
import orm
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class SchoolClass(orm.Model):
|
||||
__tablename__ = "schoolclasses"
|
||||
__metadata__ = metadata
|
||||
__database__ = database
|
||||
|
||||
id = orm.Integer(primary_key=True)
|
||||
name = orm.String(length=100)
|
||||
|
||||
|
||||
class Category(orm.Model):
|
||||
__tablename__ = "categories"
|
||||
__metadata__ = metadata
|
||||
__database__ = database
|
||||
|
||||
id = orm.Integer(primary_key=True)
|
||||
name = orm.String(length=100)
|
||||
|
||||
|
||||
class Student(orm.Model):
|
||||
__metadata__ = metadata
|
||||
__database__ = database
|
||||
|
||||
id = orm.Integer(primary_key=True)
|
||||
name = orm.String(length=100)
|
||||
schoolclass = orm.ForeignKey(SchoolClass)
|
||||
category = orm.ForeignKey(Category)
|
||||
|
||||
|
||||
class Teacher(orm.Model):
|
||||
__metadata__ = metadata
|
||||
__database__ = database
|
||||
|
||||
id = orm.Integer(primary_key=True)
|
||||
name = orm.String(length=100)
|
||||
schoolclass = orm.ForeignKey(SchoolClass)
|
||||
category = orm.ForeignKey(Category)
|
||||
Reference in New Issue
Block a user