finish inheritance docs, remove original through model from metadta, add high level overview in api docs

This commit is contained in:
collerek
2021-01-05 15:18:13 +01:00
parent 9f8e8e87e8
commit 3279ef7a85
5 changed files with 330 additions and 34 deletions

View File

@ -220,6 +220,8 @@ Therefore, you have two options:
That might sound complicated but let's look at the following example:
### ForeignKey relations
```python
# normal model used in relation
class Person(ormar.Model):
@ -320,4 +322,142 @@ Person.Meta.model_fields
`owner: Person = ormar.ForeignKey(Person, related_name="owned")`
and model fields for Person owned cars would become `owned_trucks` and `owned_buses`.
### ManyToMany relations
Similarly, you can inherit from Models that have ManyToMany relations declared but
there is one, but substantial difference - the Through model.
Since in the future the Through model will be able to hold additional fields and now it links only two Tables
(`from` and `to` ones), each child that inherits the m2m relation field has to have separate
Through model.
Of course, you can overwrite the relation in each Child model, but that requires additional
code and undermines the point of the whole inheritance. `Ormar` will handle this for you if
you agree with default naming convention, which you can always manually overwrite in
children if needed.
Again, let's look at the example to easier grasp the concepts.
We will modify the previous example described above to use m2m relation for co_owners.
```python
# person remain the same as above
class Person(ormar.Model):
class Meta:
metadata = metadata
database = db
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
# new through model between Person and Car2
class PersonsCar(ormar.Model):
class Meta:
tablename = "cars_x_persons"
metadata = metadata
database = db
# note how co_owners is now ManyToMany relation
class Car2(ormar.Model):
class Meta:
# parent class needs to be marked abstract
abstract = True
metadata = metadata
database = db
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=50)
# note the related_name - needs to be unique across Person
# model, regardless of how many different models leads to Person
owner: Person = ormar.ForeignKey(Person, related_name="owned")
co_owners: List[Person] = ormar.ManyToMany(
Person, through=PersonsCar, related_name="coowned"
)
created_date: datetime.datetime = ormar.DateTime(default=datetime.datetime.now)
# child models define only additional Fields
class Truck2(Car2):
class Meta:
# note how you don't have to provide inherited Meta params
tablename = "trucks2"
max_capacity: int = ormar.Integer()
class Bus2(Car2):
class Meta:
tablename = "buses2"
max_persons: int = ormar.Integer()
```
`Ormar` automatically modifies related_name of the fields to include the **table** name
of the children models. The dafault name is original related_name + '_' + child table name.
That way for class Truck2 the relation defined in
`owner: Person = ormar.ForeignKey(Person, related_name="owned")` becomes `owned_trucks2`
You can verify the names by inspecting the list of fields present on `Person` model.
```python
Person.Meta.model_fields
{
# note how all relation fields need to be unique on Person
# regardless if autogenerated or manually overwritten
'id': <class 'ormar.fields.model_fields.Integer'>,
'name': <class 'ormar.fields.model_fields.String'>,
# note that we expanded on previous example so all 'old' fields are here
'trucks': <class 'ormar.fields.foreign_key.ForeignKey'>,
'coowned_trucks': <class 'ormar.fields.foreign_key.ForeignKey'>,
'buses': <class 'ormar.fields.foreign_key.ForeignKey'>,
'coowned_buses': <class 'ormar.fields.foreign_key.ForeignKey'>,
# newly defined related fields
'owned_trucks2': <class 'ormar.fields.foreign_key.ForeignKey'>,
'coowned_trucks2': <class 'abc.ManyToMany'>,
'owned_buses2': <class 'ormar.fields.foreign_key.ForeignKey'>,
'coowned_buses2': <class 'abc.ManyToMany'>
}
```
But that's not all. It's kind of internal to `ormar` but affects the data structure in the database,
so let's examine the through models for both `Bus2` and `Truck2` models.
```python
Bus2.Meta.model_fields['co_owners'].through
<class 'abc.PersonsCarBus2'>
Bus2.Meta.model_fields['co_owners'].through.Meta.tablename
'cars_x_persons_buses2'
Truck2.Meta.model_fields['co_owners'].through
<class 'abc.PersonsCarTruck2'>
Truck2.Meta.model_fields['co_owners'].through.Meta.tablename
'cars_x_persons_trucks2'
```
As you can see above `ormar` cloned the Through model for each of the Child classes and added
Child **class** name at the end, while changing the table names of the cloned fields
the name of the **table** from the child is used.
Note that original model is not only not used, the table for this model is removed from metadata:
```python
Bus2.Meta.metadata.tables.keys()
dict_keys(['test_date_models', 'categories', 'subjects', 'persons', 'trucks', 'buses',
'cars_x_persons_trucks2', 'trucks2', 'cars_x_persons_buses2', 'buses2'])
```
So be aware that if you introduce inheritance along the way and convert a model into
abstract parent model you may lose your data on through table if not careful.
!!!note
Note that original table name and model name of the Through model is never used.
Only the cloned models tables are created and used.
!!!warning
Note that each subclass of the Model that has `ManyToMany` relation defined generates
a new `Through` model, meaning also **new database table**.
That means that each time you define a Child model you need to either manually create
the table in the database, or run a migration (with alembic).