finish inheritance docs, remove original through model from metadta, add high level overview in api docs
This commit is contained in:
@ -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).
|
||||
Reference in New Issue
Block a user