update docs, add load_all(), tests for load_all, make through field optional
This commit is contained in:
@ -306,7 +306,7 @@ async def joins():
|
|||||||
# visit: https://collerek.github.io/ormar/relations/
|
# visit: https://collerek.github.io/ormar/relations/
|
||||||
|
|
||||||
# to read more about joins and subqueries
|
# to read more about joins and subqueries
|
||||||
# visit: https://collerek.github.io/ormar/queries/delete/
|
# visit: https://collerek.github.io/ormar/queries/joins-and-subqueries/
|
||||||
|
|
||||||
|
|
||||||
async def filter_and_sort():
|
async def filter_and_sort():
|
||||||
|
|||||||
@ -72,6 +72,27 @@ Excludes defaults and alias as they are populated separately
|
|||||||
|
|
||||||
`(bool)`: True if field is present on pydantic.FieldInfo
|
`(bool)`: True if field is present on pydantic.FieldInfo
|
||||||
|
|
||||||
|
<a name="fields.base.BaseField.get_base_pydantic_field_info"></a>
|
||||||
|
#### get\_base\_pydantic\_field\_info
|
||||||
|
|
||||||
|
```python
|
||||||
|
| @classmethod
|
||||||
|
| get_base_pydantic_field_info(cls, allow_null: bool) -> FieldInfo
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates base pydantic.FieldInfo with only default and optionally
|
||||||
|
required to fix pydantic Json field being set to required=False.
|
||||||
|
Used in an ormar Model Metaclass.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `allow_null (bool)`: flag if the default value can be None
|
||||||
|
or if it should be populated by pydantic Undefined
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(pydantic.FieldInfo)`: instance of base pydantic.FieldInfo
|
||||||
|
|
||||||
<a name="fields.base.BaseField.convert_to_pydantic_field_info"></a>
|
<a name="fields.base.BaseField.convert_to_pydantic_field_info"></a>
|
||||||
#### convert\_to\_pydantic\_field\_info
|
#### convert\_to\_pydantic\_field\_info
|
||||||
|
|
||||||
|
|||||||
@ -332,3 +332,32 @@ Selects the appropriate constructor based on a passed value.
|
|||||||
|
|
||||||
`(Optional[Union["Model", List["Model"]]])`: returns a Model or a list of Models
|
`(Optional[Union["Model", List["Model"]]])`: returns a Model or a list of Models
|
||||||
|
|
||||||
|
<a name="fields.foreign_key.ForeignKeyField.get_relation_name"></a>
|
||||||
|
#### get\_relation\_name
|
||||||
|
|
||||||
|
```python
|
||||||
|
| @classmethod
|
||||||
|
| get_relation_name(cls) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns name of the relation, which can be a own name or through model
|
||||||
|
names for m2m models
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(bool)`: result of the check
|
||||||
|
|
||||||
|
<a name="fields.foreign_key.ForeignKeyField.get_source_model"></a>
|
||||||
|
#### get\_source\_model
|
||||||
|
|
||||||
|
```python
|
||||||
|
| @classmethod
|
||||||
|
| get_source_model(cls) -> Type["Model"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns model from which the relation comes -> either owner or through model
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(Type["Model"])`: source model
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,7 @@ pydantic field to use and type of the target column field.
|
|||||||
#### ManyToMany
|
#### ManyToMany
|
||||||
|
|
||||||
```python
|
```python
|
||||||
ManyToMany(to: "ToType", through: "ToType", *, name: str = None, unique: bool = False, virtual: bool = False, **kwargs: Any, ,) -> Any
|
ManyToMany(to: "ToType", through: Optional["ToType"] = None, *, name: str = None, unique: bool = False, virtual: bool = False, **kwargs: Any, ,) -> Any
|
||||||
```
|
```
|
||||||
|
|
||||||
Despite a name it's a function that returns constructed ManyToManyField.
|
Despite a name it's a function that returns constructed ManyToManyField.
|
||||||
@ -134,3 +134,42 @@ Evaluates the ForwardRef to actual Field based on global and local namespaces
|
|||||||
|
|
||||||
`(None)`: None
|
`(None)`: None
|
||||||
|
|
||||||
|
<a name="fields.many_to_many.ManyToManyField.get_relation_name"></a>
|
||||||
|
#### get\_relation\_name
|
||||||
|
|
||||||
|
```python
|
||||||
|
| @classmethod
|
||||||
|
| get_relation_name(cls) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns name of the relation, which can be a own name or through model
|
||||||
|
names for m2m models
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(bool)`: result of the check
|
||||||
|
|
||||||
|
<a name="fields.many_to_many.ManyToManyField.get_source_model"></a>
|
||||||
|
#### get\_source\_model
|
||||||
|
|
||||||
|
```python
|
||||||
|
| @classmethod
|
||||||
|
| get_source_model(cls) -> Type["Model"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns model from which the relation comes -> either owner or through model
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(Type["Model"])`: source model
|
||||||
|
|
||||||
|
<a name="fields.many_to_many.ManyToManyField.create_default_through_model"></a>
|
||||||
|
#### create\_default\_through\_model
|
||||||
|
|
||||||
|
```python
|
||||||
|
| @classmethod
|
||||||
|
| create_default_through_model(cls) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
Creates default empty through model if no additional fields are required.
|
||||||
|
|
||||||
|
|||||||
188
docs/api/models/excludable-items.md
Normal file
188
docs/api/models/excludable-items.md
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
<a name="models.excludable"></a>
|
||||||
|
# models.excludable
|
||||||
|
|
||||||
|
<a name="models.excludable.Excludable"></a>
|
||||||
|
## Excludable Objects
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass
|
||||||
|
class Excludable()
|
||||||
|
```
|
||||||
|
|
||||||
|
Class that keeps sets of fields to exclude and include
|
||||||
|
|
||||||
|
<a name="models.excludable.Excludable.get_copy"></a>
|
||||||
|
#### get\_copy
|
||||||
|
|
||||||
|
```python
|
||||||
|
| get_copy() -> "Excludable"
|
||||||
|
```
|
||||||
|
|
||||||
|
Return copy of self to avoid in place modifications
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(ormar.models.excludable.Excludable)`: copy of self with copied sets
|
||||||
|
|
||||||
|
<a name="models.excludable.Excludable.set_values"></a>
|
||||||
|
#### set\_values
|
||||||
|
|
||||||
|
```python
|
||||||
|
| set_values(value: Set, is_exclude: bool) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
Appends the data to include/exclude sets.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `value (set)`: set of values to add
|
||||||
|
- `is_exclude (bool)`: flag if values are to be excluded or included
|
||||||
|
|
||||||
|
<a name="models.excludable.Excludable.is_included"></a>
|
||||||
|
#### is\_included
|
||||||
|
|
||||||
|
```python
|
||||||
|
| is_included(key: str) -> bool
|
||||||
|
```
|
||||||
|
|
||||||
|
Check if field in included (in set or set is {...})
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `key (str)`: key to check
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(bool)`: result of the check
|
||||||
|
|
||||||
|
<a name="models.excludable.Excludable.is_excluded"></a>
|
||||||
|
#### is\_excluded
|
||||||
|
|
||||||
|
```python
|
||||||
|
| is_excluded(key: str) -> bool
|
||||||
|
```
|
||||||
|
|
||||||
|
Check if field in excluded (in set or set is {...})
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `key (str)`: key to check
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(bool)`: result of the check
|
||||||
|
|
||||||
|
<a name="models.excludable.ExcludableItems"></a>
|
||||||
|
## ExcludableItems Objects
|
||||||
|
|
||||||
|
```python
|
||||||
|
class ExcludableItems()
|
||||||
|
```
|
||||||
|
|
||||||
|
Keeps a dictionary of Excludables by alias + model_name keys
|
||||||
|
to allow quick lookup by nested models without need to travers
|
||||||
|
deeply nested dictionaries and passing include/exclude around
|
||||||
|
|
||||||
|
<a name="models.excludable.ExcludableItems.from_excludable"></a>
|
||||||
|
#### from\_excludable
|
||||||
|
|
||||||
|
```python
|
||||||
|
| @classmethod
|
||||||
|
| from_excludable(cls, other: "ExcludableItems") -> "ExcludableItems"
|
||||||
|
```
|
||||||
|
|
||||||
|
Copy passed ExcludableItems to avoid inplace modifications.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `other (ormar.models.excludable.ExcludableItems)`: other excludable items to be copied
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(ormar.models.excludable.ExcludableItems)`: copy of other
|
||||||
|
|
||||||
|
<a name="models.excludable.ExcludableItems.get"></a>
|
||||||
|
#### get
|
||||||
|
|
||||||
|
```python
|
||||||
|
| get(model_cls: Type["Model"], alias: str = "") -> Excludable
|
||||||
|
```
|
||||||
|
|
||||||
|
Return Excludable for given model and alias.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `model_cls (ormar.models.metaclass.ModelMetaclass)`: target model to check
|
||||||
|
- `alias (str)`: table alias from relation manager
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(ormar.models.excludable.Excludable)`: Excludable for given model and alias
|
||||||
|
|
||||||
|
<a name="models.excludable.ExcludableItems.build"></a>
|
||||||
|
#### build
|
||||||
|
|
||||||
|
```python
|
||||||
|
| build(items: Union[List[str], str, Tuple[str], Set[str], Dict], model_cls: Type["Model"], is_exclude: bool = False) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
Receives the one of the types of items and parses them as to achieve
|
||||||
|
a end situation with one excludable per alias/model in relation.
|
||||||
|
|
||||||
|
Each excludable has two sets of values - one to include, one to exclude.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `items (Union[List[str], str, Tuple[str], Set[str], Dict])`: values to be included or excluded
|
||||||
|
- `model_cls (ormar.models.metaclass.ModelMetaclass)`: source model from which relations are constructed
|
||||||
|
- `is_exclude (bool)`: flag if items should be included or excluded
|
||||||
|
|
||||||
|
<a name="models.excludable.ExcludableItems._set_excludes"></a>
|
||||||
|
#### \_set\_excludes
|
||||||
|
|
||||||
|
```python
|
||||||
|
| _set_excludes(items: Set, model_name: str, is_exclude: bool, alias: str = "") -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
Sets set of values to be included or excluded for given key and model.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `items (set)`: items to include/exclude
|
||||||
|
- `model_name (str)`: name of model to construct key
|
||||||
|
- `is_exclude (bool)`: flag if values should be included or excluded
|
||||||
|
- `alias (str)`:
|
||||||
|
|
||||||
|
<a name="models.excludable.ExcludableItems._traverse_dict"></a>
|
||||||
|
#### \_traverse\_dict
|
||||||
|
|
||||||
|
```python
|
||||||
|
| _traverse_dict(values: Dict, source_model: Type["Model"], model_cls: Type["Model"], is_exclude: bool, related_items: List = None, alias: str = "") -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
Goes through dict of nested values and construct/update Excludables.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `values (Dict)`: items to include/exclude
|
||||||
|
- `source_model (ormar.models.metaclass.ModelMetaclass)`: source model from which relations are constructed
|
||||||
|
- `model_cls (ormar.models.metaclass.ModelMetaclass)`: model from which current relation is constructed
|
||||||
|
- `is_exclude (bool)`: flag if values should be included or excluded
|
||||||
|
- `related_items (List)`: list of names of related fields chain
|
||||||
|
- `alias (str)`: alias of relation
|
||||||
|
|
||||||
|
<a name="models.excludable.ExcludableItems._traverse_list"></a>
|
||||||
|
#### \_traverse\_list
|
||||||
|
|
||||||
|
```python
|
||||||
|
| _traverse_list(values: Set[str], model_cls: Type["Model"], is_exclude: bool) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
Goes through list of values and construct/update Excludables.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `values (set)`: items to include/exclude
|
||||||
|
- `model_cls (ormar.models.metaclass.ModelMetaclass)`: model from which current relation is constructed
|
||||||
|
- `is_exclude (bool)`: flag if values should be included or excluded
|
||||||
|
|
||||||
@ -87,28 +87,6 @@ extraction of ormar model_fields.
|
|||||||
|
|
||||||
`(Tuple[Dict, Dict])`: namespace of the class updated, dict of extracted model_fields
|
`(Tuple[Dict, Dict])`: namespace of the class updated, dict of extracted model_fields
|
||||||
|
|
||||||
<a name="models.helpers.models.validate_related_names_in_relations"></a>
|
|
||||||
#### validate\_related\_names\_in\_relations
|
|
||||||
|
|
||||||
```python
|
|
||||||
validate_related_names_in_relations(model_fields: Dict, new_model: Type["Model"]) -> None
|
|
||||||
```
|
|
||||||
|
|
||||||
Performs a validation of relation_names in relation fields.
|
|
||||||
If multiple fields are leading to the same related model
|
|
||||||
only one can have empty related_name param
|
|
||||||
(populated by default as model.name.lower()+'s').
|
|
||||||
Also related_names have to be unique for given related model.
|
|
||||||
|
|
||||||
**Raises**:
|
|
||||||
|
|
||||||
- `ModelDefinitionError`: if validation of related_names fail
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `model_fields (Dict[str, ormar.Field])`: dictionary of declared ormar model fields
|
|
||||||
- `new_model (Model class)`:
|
|
||||||
|
|
||||||
<a name="models.helpers.models.group_related_list"></a>
|
<a name="models.helpers.models.group_related_list"></a>
|
||||||
#### group\_related\_list
|
#### group\_related\_list
|
||||||
|
|
||||||
@ -134,3 +112,23 @@ Result dictionary is sorted by length of the values and by key
|
|||||||
|
|
||||||
`(Dict[str, List])`: list converted to dictionary to avoid repetition and group nested models
|
`(Dict[str, List])`: list converted to dictionary to avoid repetition and group nested models
|
||||||
|
|
||||||
|
<a name="models.helpers.models.meta_field_not_set"></a>
|
||||||
|
#### meta\_field\_not\_set
|
||||||
|
|
||||||
|
```python
|
||||||
|
meta_field_not_set(model: Type["Model"], field_name: str) -> bool
|
||||||
|
```
|
||||||
|
|
||||||
|
Checks if field with given name is already present in model.Meta.
|
||||||
|
Then check if it's set to something truthful
|
||||||
|
(in practice meaning not None, as it's non or ormar Field only).
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `model (Model class)`: newly constructed model
|
||||||
|
- `field_name (str)`: name of the ormar field
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(bool)`: result of the check
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
#### create\_pydantic\_field
|
#### create\_pydantic\_field
|
||||||
|
|
||||||
```python
|
```python
|
||||||
create_pydantic_field(field_name: str, model: Type["Model"], model_field: Type[ManyToManyField]) -> None
|
create_pydantic_field(field_name: str, model: Type["Model"], model_field: Type["ManyToManyField"]) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Registers pydantic field on through model that leads to passed model
|
Registers pydantic field on through model that leads to passed model
|
||||||
@ -42,7 +42,7 @@ field_name. Returns a pydantic field with type of field_name field type.
|
|||||||
#### populate\_default\_pydantic\_field\_value
|
#### populate\_default\_pydantic\_field\_value
|
||||||
|
|
||||||
```python
|
```python
|
||||||
populate_default_pydantic_field_value(ormar_field: Type[BaseField], field_name: str, attrs: dict) -> dict
|
populate_default_pydantic_field_value(ormar_field: Type["BaseField"], field_name: str, attrs: dict) -> dict
|
||||||
```
|
```
|
||||||
|
|
||||||
Grabs current value of the ormar Field in class namespace
|
Grabs current value of the ormar Field in class namespace
|
||||||
@ -94,7 +94,7 @@ Those annotations are later used by pydantic to construct it's own fields.
|
|||||||
#### get\_pydantic\_base\_orm\_config
|
#### get\_pydantic\_base\_orm\_config
|
||||||
|
|
||||||
```python
|
```python
|
||||||
get_pydantic_base_orm_config() -> Type[BaseConfig]
|
get_pydantic_base_orm_config() -> Type[pydantic.BaseConfig]
|
||||||
```
|
```
|
||||||
|
|
||||||
Returns empty pydantic Config with orm_mode set to True.
|
Returns empty pydantic Config with orm_mode set to True.
|
||||||
|
|||||||
25
docs/api/models/helpers/related-names-validation.md
Normal file
25
docs/api/models/helpers/related-names-validation.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<a name="models.helpers.related_names_validation"></a>
|
||||||
|
# models.helpers.related\_names\_validation
|
||||||
|
|
||||||
|
<a name="models.helpers.related_names_validation.validate_related_names_in_relations"></a>
|
||||||
|
#### validate\_related\_names\_in\_relations
|
||||||
|
|
||||||
|
```python
|
||||||
|
validate_related_names_in_relations(model_fields: Dict, new_model: Type["Model"]) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
Performs a validation of relation_names in relation fields.
|
||||||
|
If multiple fields are leading to the same related model
|
||||||
|
only one can have empty related_name param
|
||||||
|
(populated by default as model.name.lower()+'s').
|
||||||
|
Also related_names have to be unique for given related model.
|
||||||
|
|
||||||
|
**Raises**:
|
||||||
|
|
||||||
|
- `ModelDefinitionError`: if validation of related_names fail
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `model_fields (Dict[str, ormar.Field])`: dictionary of declared ormar model fields
|
||||||
|
- `new_model (Model class)`:
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ aliases for proper sql joins.
|
|||||||
#### register\_many\_to\_many\_relation\_on\_build
|
#### register\_many\_to\_many\_relation\_on\_build
|
||||||
|
|
||||||
```python
|
```python
|
||||||
register_many_to_many_relation_on_build(field: Type[ManyToManyField]) -> None
|
register_many_to_many_relation_on_build(field: Type["ManyToManyField"]) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Registers connection between through model and both sides of the m2m relation.
|
Registers connection between through model and both sides of the m2m relation.
|
||||||
@ -89,11 +89,24 @@ Autogenerated reverse fields also set related_name to the original field name.
|
|||||||
|
|
||||||
- `model_field (relation Field)`: original relation ForeignKey field
|
- `model_field (relation Field)`: original relation ForeignKey field
|
||||||
|
|
||||||
|
<a name="models.helpers.relations.register_through_shortcut_fields"></a>
|
||||||
|
#### register\_through\_shortcut\_fields
|
||||||
|
|
||||||
|
```python
|
||||||
|
register_through_shortcut_fields(model_field: Type["ManyToManyField"]) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
Registers m2m relation through shortcut on both ends of the relation.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `model_field (ManyToManyField)`: relation field defined in parent model
|
||||||
|
|
||||||
<a name="models.helpers.relations.register_relation_in_alias_manager"></a>
|
<a name="models.helpers.relations.register_relation_in_alias_manager"></a>
|
||||||
#### register\_relation\_in\_alias\_manager
|
#### register\_relation\_in\_alias\_manager
|
||||||
|
|
||||||
```python
|
```python
|
||||||
register_relation_in_alias_manager(field: Type[ForeignKeyField]) -> None
|
register_relation_in_alias_manager(field: Type["ForeignKeyField"]) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Registers the relation (and reverse relation) in alias manager.
|
Registers the relation (and reverse relation) in alias manager.
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
#### adjust\_through\_many\_to\_many\_model
|
#### adjust\_through\_many\_to\_many\_model
|
||||||
|
|
||||||
```python
|
```python
|
||||||
adjust_through_many_to_many_model(model_field: Type[ManyToManyField]) -> None
|
adjust_through_many_to_many_model(model_field: Type["ManyToManyField"]) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Registers m2m relation on through model.
|
Registers m2m relation on through model.
|
||||||
@ -21,7 +21,7 @@ Sets pydantic fields with child and parent model types.
|
|||||||
#### create\_and\_append\_m2m\_fk
|
#### create\_and\_append\_m2m\_fk
|
||||||
|
|
||||||
```python
|
```python
|
||||||
create_and_append_m2m_fk(model: Type["Model"], model_field: Type[ManyToManyField], field_name: str) -> None
|
create_and_append_m2m_fk(model: Type["Model"], model_field: Type["ManyToManyField"], field_name: str) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Registers sqlalchemy Column with sqlalchemy.ForeignKey leading to the model.
|
Registers sqlalchemy Column with sqlalchemy.ForeignKey leading to the model.
|
||||||
@ -38,7 +38,7 @@ Newly created field is added to m2m relation through model Meta columns and tabl
|
|||||||
#### check\_pk\_column\_validity
|
#### check\_pk\_column\_validity
|
||||||
|
|
||||||
```python
|
```python
|
||||||
check_pk_column_validity(field_name: str, field: BaseField, pkname: Optional[str]) -> Optional[str]
|
check_pk_column_validity(field_name: str, field: "BaseField", pkname: Optional[str]) -> Optional[str]
|
||||||
```
|
```
|
||||||
|
|
||||||
Receives the field marked as primary key and verifies if the pkname
|
Receives the field marked as primary key and verifies if the pkname
|
||||||
@ -165,7 +165,7 @@ It populates name, metadata, columns and constraints.
|
|||||||
#### update\_column\_definition
|
#### update\_column\_definition
|
||||||
|
|
||||||
```python
|
```python
|
||||||
update_column_definition(model: Union[Type["Model"], Type["NewBaseModel"]], field: Type[ForeignKeyField]) -> None
|
update_column_definition(model: Union[Type["Model"], Type["NewBaseModel"]], field: Type["ForeignKeyField"]) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Updates a column with a new type column based on updated parameters in FK fields.
|
Updates a column with a new type column based on updated parameters in FK fields.
|
||||||
|
|||||||
120
docs/api/models/helpers/validation.md
Normal file
120
docs/api/models/helpers/validation.md
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<a name="models.helpers.validation"></a>
|
||||||
|
# models.helpers.validation
|
||||||
|
|
||||||
|
<a name="models.helpers.validation.check_if_field_has_choices"></a>
|
||||||
|
#### check\_if\_field\_has\_choices
|
||||||
|
|
||||||
|
```python
|
||||||
|
check_if_field_has_choices(field: Type[BaseField]) -> bool
|
||||||
|
```
|
||||||
|
|
||||||
|
Checks if given field has choices populated.
|
||||||
|
A if it has one, a validator for this field needs to be attached.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `field (BaseField)`: ormar field to check
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(bool)`: result of the check
|
||||||
|
|
||||||
|
<a name="models.helpers.validation.convert_choices_if_needed"></a>
|
||||||
|
#### convert\_choices\_if\_needed
|
||||||
|
|
||||||
|
```python
|
||||||
|
convert_choices_if_needed(field: Type["BaseField"], value: Any) -> Tuple[Any, List]
|
||||||
|
```
|
||||||
|
|
||||||
|
Converts dates to isoformat as fastapi can check this condition in routes
|
||||||
|
and the fields are not yet parsed.
|
||||||
|
|
||||||
|
Converts enums to list of it's values.
|
||||||
|
|
||||||
|
Converts uuids to strings.
|
||||||
|
|
||||||
|
Converts decimal to float with given scale.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `field (Type[BaseField])`: ormar field to check with choices
|
||||||
|
- `values (Dict)`: current values of the model to verify
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(Tuple[Any, List])`: value, choices list
|
||||||
|
|
||||||
|
<a name="models.helpers.validation.validate_choices"></a>
|
||||||
|
#### validate\_choices
|
||||||
|
|
||||||
|
```python
|
||||||
|
validate_choices(field: Type["BaseField"], value: Any) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
Validates if given value is in provided choices.
|
||||||
|
|
||||||
|
**Raises**:
|
||||||
|
|
||||||
|
- `ValueError`: If value is not in choices.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `field (Type[BaseField])`: field to validate
|
||||||
|
- `value (Any)`: value of the field
|
||||||
|
|
||||||
|
<a name="models.helpers.validation.choices_validator"></a>
|
||||||
|
#### choices\_validator
|
||||||
|
|
||||||
|
```python
|
||||||
|
choices_validator(cls: Type["Model"], values: Dict[str, Any]) -> Dict[str, Any]
|
||||||
|
```
|
||||||
|
|
||||||
|
Validator that is attached to pydantic model pre root validators.
|
||||||
|
Validator checks if field value is in field.choices list.
|
||||||
|
|
||||||
|
**Raises**:
|
||||||
|
|
||||||
|
- `ValueError`: if field value is outside of allowed choices.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `cls (Model class)`: constructed class
|
||||||
|
- `values (Dict[str, Any])`: dictionary of field values (pydantic side)
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(Dict[str, Any])`: values if pass validation, otherwise exception is raised
|
||||||
|
|
||||||
|
<a name="models.helpers.validation.construct_modify_schema_function"></a>
|
||||||
|
#### construct\_modify\_schema\_function
|
||||||
|
|
||||||
|
```python
|
||||||
|
construct_modify_schema_function(fields_with_choices: List) -> SchemaExtraCallable
|
||||||
|
```
|
||||||
|
|
||||||
|
Modifies the schema to include fields with choices validator.
|
||||||
|
Those fields will be displayed in schema as Enum types with available choices
|
||||||
|
values listed next to them.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `fields_with_choices (List)`: list of fields with choices validation
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(Callable)`: callable that will be run by pydantic to modify the schema
|
||||||
|
|
||||||
|
<a name="models.helpers.validation.populate_choices_validators"></a>
|
||||||
|
#### populate\_choices\_validators
|
||||||
|
|
||||||
|
```python
|
||||||
|
populate_choices_validators(model: Type["Model"]) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
Checks if Model has any fields with choices set.
|
||||||
|
If yes it adds choices validation into pre root validators.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `model (Model class)`: newly constructed Model
|
||||||
|
|
||||||
@ -30,88 +30,12 @@ passed items.
|
|||||||
|
|
||||||
`(Union[Set, Dict, None])`: child extracted from items if exists
|
`(Union[Set, Dict, None])`: child extracted from items if exists
|
||||||
|
|
||||||
<a name="models.mixins.excludable_mixin.ExcludableMixin.get_excluded"></a>
|
|
||||||
#### get\_excluded
|
|
||||||
|
|
||||||
```python
|
|
||||||
| @staticmethod
|
|
||||||
| get_excluded(exclude: Union[Set, Dict, None], key: str = None) -> Union[Set, Dict, None]
|
|
||||||
```
|
|
||||||
|
|
||||||
Proxy to ExcludableMixin.get_child for exclusions.
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `exclude (Union[Set, Dict, None])`: bag of items to exclude
|
|
||||||
- `key (str)`: name of the child to extract
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
|
|
||||||
`(Union[Set, Dict, None])`: child extracted from items if exists
|
|
||||||
|
|
||||||
<a name="models.mixins.excludable_mixin.ExcludableMixin.get_included"></a>
|
|
||||||
#### get\_included
|
|
||||||
|
|
||||||
```python
|
|
||||||
| @staticmethod
|
|
||||||
| get_included(include: Union[Set, Dict, None], key: str = None) -> Union[Set, Dict, None]
|
|
||||||
```
|
|
||||||
|
|
||||||
Proxy to ExcludableMixin.get_child for inclusions.
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `include (Union[Set, Dict, None])`: bag of items to include
|
|
||||||
- `key (str)`: name of the child to extract
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
|
|
||||||
`(Union[Set, Dict, None])`: child extracted from items if exists
|
|
||||||
|
|
||||||
<a name="models.mixins.excludable_mixin.ExcludableMixin.is_excluded"></a>
|
|
||||||
#### is\_excluded
|
|
||||||
|
|
||||||
```python
|
|
||||||
| @staticmethod
|
|
||||||
| is_excluded(exclude: Union[Set, Dict, None], key: str = None) -> bool
|
|
||||||
```
|
|
||||||
|
|
||||||
Checks if given key should be excluded on model/ dict.
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `exclude (Union[Set, Dict, None])`: bag of items to exclude
|
|
||||||
- `key (str)`: name of the child to extract
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
|
|
||||||
`(Union[Set, Dict, None])`: child extracted from items if exists
|
|
||||||
|
|
||||||
<a name="models.mixins.excludable_mixin.ExcludableMixin.is_included"></a>
|
|
||||||
#### is\_included
|
|
||||||
|
|
||||||
```python
|
|
||||||
| @staticmethod
|
|
||||||
| is_included(include: Union[Set, Dict, None], key: str = None) -> bool
|
|
||||||
```
|
|
||||||
|
|
||||||
Checks if given key should be included on model/ dict.
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `include (Union[Set, Dict, None])`: bag of items to include
|
|
||||||
- `key (str)`: name of the child to extract
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
|
|
||||||
`(Union[Set, Dict, None])`: child extracted from items if exists
|
|
||||||
|
|
||||||
<a name="models.mixins.excludable_mixin.ExcludableMixin._populate_pk_column"></a>
|
<a name="models.mixins.excludable_mixin.ExcludableMixin._populate_pk_column"></a>
|
||||||
#### \_populate\_pk\_column
|
#### \_populate\_pk\_column
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| @staticmethod
|
| @staticmethod
|
||||||
| _populate_pk_column(model: Type["Model"], columns: List[str], use_alias: bool = False) -> List[str]
|
| _populate_pk_column(model: Union[Type["Model"], Type["ModelRow"]], columns: List[str], use_alias: bool = False) -> List[str]
|
||||||
```
|
```
|
||||||
|
|
||||||
Adds primary key column/alias (depends on use_alias flag) to list of
|
Adds primary key column/alias (depends on use_alias flag) to list of
|
||||||
@ -132,7 +56,7 @@ column names that are selected.
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
| @classmethod
|
| @classmethod
|
||||||
| own_table_columns(cls, model: Type["Model"], fields: Optional[Union[Set, Dict]], exclude_fields: Optional[Union[Set, Dict]], use_alias: bool = False) -> List[str]
|
| own_table_columns(cls, model: Union[Type["Model"], Type["ModelRow"]], excludable: ExcludableItems, alias: str = "", use_alias: bool = False) -> List[str]
|
||||||
```
|
```
|
||||||
|
|
||||||
Returns list of aliases or field names for given model.
|
Returns list of aliases or field names for given model.
|
||||||
@ -145,9 +69,9 @@ Primary key field is always added and cannot be excluded (will be added anyway).
|
|||||||
|
|
||||||
**Arguments**:
|
**Arguments**:
|
||||||
|
|
||||||
|
- `alias (str)`: relation prefix
|
||||||
|
- `excludable (ExcludableItems)`: structure of fields to include and exclude
|
||||||
- `model (Type["Model"])`: model on columns are selected
|
- `model (Type["Model"])`: model on columns are selected
|
||||||
- `fields (Optional[Union[Set, Dict]])`: set/dict of fields to include
|
|
||||||
- `exclude_fields (Optional[Union[Set, Dict]])`: set/dict of fields to exclude
|
|
||||||
- `use_alias (bool)`: flag if aliases or field names should be used
|
- `use_alias (bool)`: flag if aliases or field names should be used
|
||||||
|
|
||||||
**Returns**:
|
**Returns**:
|
||||||
@ -183,7 +107,7 @@ exclusion, for nested models all related models are excluded.
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
| @classmethod
|
| @classmethod
|
||||||
| get_names_to_exclude(cls, fields: Optional[Union[Dict, Set]] = None, exclude_fields: Optional[Union[Dict, Set]] = None) -> Set
|
| get_names_to_exclude(cls, excludable: ExcludableItems, alias: str) -> Set
|
||||||
```
|
```
|
||||||
|
|
||||||
Returns a set of models field names that should be explicitly excluded
|
Returns a set of models field names that should be explicitly excluded
|
||||||
@ -197,8 +121,8 @@ them with dicts constructed from those db rows.
|
|||||||
|
|
||||||
**Arguments**:
|
**Arguments**:
|
||||||
|
|
||||||
- `fields (Optional[Union[Set, Dict]])`: set/dict of fields to include
|
- `alias (str)`: alias of current relation
|
||||||
- `exclude_fields (Optional[Union[Set, Dict]])`: set/dict of fields to exclude
|
- `excludable (ExcludableItems)`: structure of fields to include and exclude
|
||||||
|
|
||||||
**Returns**:
|
**Returns**:
|
||||||
|
|
||||||
|
|||||||
@ -40,12 +40,26 @@ List is cached in cls._related_fields for quicker access.
|
|||||||
|
|
||||||
`(List)`: list of related fields
|
`(List)`: list of related fields
|
||||||
|
|
||||||
|
<a name="models.mixins.relation_mixin.RelationMixin.extract_through_names"></a>
|
||||||
|
#### extract\_through\_names
|
||||||
|
|
||||||
|
```python
|
||||||
|
| @classmethod
|
||||||
|
| extract_through_names(cls) -> Set
|
||||||
|
```
|
||||||
|
|
||||||
|
Extracts related fields through names which are shortcuts to through models.
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(Set)`: set of related through fields names
|
||||||
|
|
||||||
<a name="models.mixins.relation_mixin.RelationMixin.extract_related_names"></a>
|
<a name="models.mixins.relation_mixin.RelationMixin.extract_related_names"></a>
|
||||||
#### extract\_related\_names
|
#### extract\_related\_names
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| @classmethod
|
| @classmethod
|
||||||
| extract_related_names(cls) -> Set
|
| extract_related_names(cls) -> Set[str]
|
||||||
```
|
```
|
||||||
|
|
||||||
Returns List of fields names for all relations declared on a model.
|
Returns List of fields names for all relations declared on a model.
|
||||||
@ -53,7 +67,7 @@ List is cached in cls._related_names for quicker access.
|
|||||||
|
|
||||||
**Returns**:
|
**Returns**:
|
||||||
|
|
||||||
`(List)`: list of related fields names
|
`(Set)`: set of related fields names
|
||||||
|
|
||||||
<a name="models.mixins.relation_mixin.RelationMixin._extract_db_related_names"></a>
|
<a name="models.mixins.relation_mixin.RelationMixin._extract_db_related_names"></a>
|
||||||
#### \_extract\_db\_related\_names
|
#### \_extract\_db\_related\_names
|
||||||
@ -91,3 +105,24 @@ for nested models all related models are returned.
|
|||||||
|
|
||||||
`(Set)`: set of non mandatory related fields
|
`(Set)`: set of non mandatory related fields
|
||||||
|
|
||||||
|
<a name="models.mixins.relation_mixin.RelationMixin._iterate_related_models"></a>
|
||||||
|
#### \_iterate\_related\_models
|
||||||
|
|
||||||
|
```python
|
||||||
|
| @classmethod
|
||||||
|
| _iterate_related_models(cls, visited: Set[Union[Type["Model"], Type["RelationMixin"]]] = None, source_relation: str = None, source_model: Union[Type["Model"], Type["RelationMixin"]] = None) -> List[str]
|
||||||
|
```
|
||||||
|
|
||||||
|
Iterates related models recursively to extract relation strings of
|
||||||
|
nested not visited models.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `visited (Set[str])`: set of already visited models
|
||||||
|
- `source_relation (str)`: name of the current relation
|
||||||
|
- `source_model (Type["Model"])`: model from which relation comes in nested relations
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(List[str])`: list of relation strings to be passed to select_related
|
||||||
|
|
||||||
|
|||||||
@ -91,3 +91,22 @@ passed by the user.
|
|||||||
|
|
||||||
`(Dict)`: dictionary of model that is about to be saved
|
`(Dict)`: dictionary of model that is about to be saved
|
||||||
|
|
||||||
|
<a name="models.mixins.save_mixin.SavePrepareMixin.validate_choices"></a>
|
||||||
|
#### validate\_choices
|
||||||
|
|
||||||
|
```python
|
||||||
|
| @classmethod
|
||||||
|
| validate_choices(cls, new_kwargs: Dict) -> Dict
|
||||||
|
```
|
||||||
|
|
||||||
|
Receives dictionary of model that is about to be saved and validates the
|
||||||
|
fields with choices set to see if the value is allowed.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `new_kwargs (Dict)`: dictionary of model that is about to be saved
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(Dict)`: dictionary of model that is about to be saved
|
||||||
|
|
||||||
|
|||||||
@ -12,61 +12,6 @@ Class used for type hinting.
|
|||||||
Users can subclass this one for convenience but it's not required.
|
Users can subclass this one for convenience but it's not required.
|
||||||
The only requirement is that ormar.Model has to have inner class with name Meta.
|
The only requirement is that ormar.Model has to have inner class with name Meta.
|
||||||
|
|
||||||
<a name="models.metaclass.check_if_field_has_choices"></a>
|
|
||||||
#### check\_if\_field\_has\_choices
|
|
||||||
|
|
||||||
```python
|
|
||||||
check_if_field_has_choices(field: Type[BaseField]) -> bool
|
|
||||||
```
|
|
||||||
|
|
||||||
Checks if given field has choices populated.
|
|
||||||
A if it has one, a validator for this field needs to be attached.
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `field (BaseField)`: ormar field to check
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
|
|
||||||
`(bool)`: result of the check
|
|
||||||
|
|
||||||
<a name="models.metaclass.choices_validator"></a>
|
|
||||||
#### choices\_validator
|
|
||||||
|
|
||||||
```python
|
|
||||||
choices_validator(cls: Type["Model"], values: Dict[str, Any]) -> Dict[str, Any]
|
|
||||||
```
|
|
||||||
|
|
||||||
Validator that is attached to pydantic model pre root validators.
|
|
||||||
Validator checks if field value is in field.choices list.
|
|
||||||
|
|
||||||
**Raises**:
|
|
||||||
|
|
||||||
- `ValueError`: if field value is outside of allowed choices.
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `cls (Model class)`: constructed class
|
|
||||||
- `values (Dict[str, Any])`: dictionary of field values (pydantic side)
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
|
|
||||||
`(Dict[str, Any])`: values if pass validation, otherwise exception is raised
|
|
||||||
|
|
||||||
<a name="models.metaclass.populate_choices_validators"></a>
|
|
||||||
#### populate\_choices\_validators
|
|
||||||
|
|
||||||
```python
|
|
||||||
populate_choices_validators(model: Type["Model"]) -> None
|
|
||||||
```
|
|
||||||
|
|
||||||
Checks if Model has any fields with choices set.
|
|
||||||
If yes it adds choices validation into pre root validators.
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `model (Model class)`: newly constructed Model
|
|
||||||
|
|
||||||
<a name="models.metaclass.add_cached_properties"></a>
|
<a name="models.metaclass.add_cached_properties"></a>
|
||||||
#### add\_cached\_properties
|
#### add\_cached\_properties
|
||||||
|
|
||||||
@ -87,26 +32,6 @@ All properties here are used as "cache" to not recalculate them constantly.
|
|||||||
|
|
||||||
- `new_model (Model class)`: newly constructed Model
|
- `new_model (Model class)`: newly constructed Model
|
||||||
|
|
||||||
<a name="models.metaclass.meta_field_not_set"></a>
|
|
||||||
#### meta\_field\_not\_set
|
|
||||||
|
|
||||||
```python
|
|
||||||
meta_field_not_set(model: Type["Model"], field_name: str) -> bool
|
|
||||||
```
|
|
||||||
|
|
||||||
Checks if field with given name is already present in model.Meta.
|
|
||||||
Then check if it's set to something truthful
|
|
||||||
(in practice meaning not None, as it's non or ormar Field only).
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `model (Model class)`: newly constructed model
|
|
||||||
- `field_name (str)`: name of the ormar field
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
|
|
||||||
`(bool)`: result of the check
|
|
||||||
|
|
||||||
<a name="models.metaclass.add_property_fields"></a>
|
<a name="models.metaclass.add_property_fields"></a>
|
||||||
#### add\_property\_fields
|
#### add\_property\_fields
|
||||||
|
|
||||||
@ -141,24 +66,6 @@ Signals are emitted in both model own methods and in selected queryset ones.
|
|||||||
|
|
||||||
- `new_model (Model class)`: newly constructed model
|
- `new_model (Model class)`: newly constructed model
|
||||||
|
|
||||||
<a name="models.metaclass.update_attrs_and_fields"></a>
|
|
||||||
#### update\_attrs\_and\_fields
|
|
||||||
|
|
||||||
```python
|
|
||||||
update_attrs_and_fields(attrs: Dict, new_attrs: Dict, model_fields: Dict, new_model_fields: Dict, new_fields: Set) -> Dict
|
|
||||||
```
|
|
||||||
|
|
||||||
Updates __annotations__, values of model fields (so pydantic FieldInfos)
|
|
||||||
as well as model.Meta.model_fields definitions from parents.
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `attrs (Dict)`: new namespace for class being constructed
|
|
||||||
- `new_attrs (Dict)`: related of the namespace extracted from parent class
|
|
||||||
- `model_fields (Dict[str, BaseField])`: ormar fields in defined in current class
|
|
||||||
- `new_model_fields (Dict[str, BaseField])`: ormar fields defined in parent classes
|
|
||||||
- `new_fields (Set[str])`: set of new fields names
|
|
||||||
|
|
||||||
<a name="models.metaclass.verify_constraint_names"></a>
|
<a name="models.metaclass.verify_constraint_names"></a>
|
||||||
#### verify\_constraint\_names
|
#### verify\_constraint\_names
|
||||||
|
|
||||||
@ -195,7 +102,7 @@ Updates Meta parameters in child from parent if needed.
|
|||||||
#### copy\_and\_replace\_m2m\_through\_model
|
#### copy\_and\_replace\_m2m\_through\_model
|
||||||
|
|
||||||
```python
|
```python
|
||||||
copy_and_replace_m2m_through_model(field: Type[ManyToManyField], field_name: str, table_name: str, parent_fields: Dict, attrs: Dict, meta: ModelMeta) -> None
|
copy_and_replace_m2m_through_model(field: Type[ManyToManyField], field_name: str, table_name: str, parent_fields: Dict, attrs: Dict, meta: ModelMeta, base_class: Type["Model"]) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Clones class with Through model for m2m relations, appends child name to the name
|
Clones class with Through model for m2m relations, appends child name to the name
|
||||||
@ -211,6 +118,7 @@ Removes the original sqlalchemy table from metadata if it was not removed.
|
|||||||
|
|
||||||
**Arguments**:
|
**Arguments**:
|
||||||
|
|
||||||
|
- `base_class (Type["Model"])`: base class model
|
||||||
- `field (Type[ManyToManyField])`: field with relations definition
|
- `field (Type[ManyToManyField])`: field with relations definition
|
||||||
- `field_name (str)`: name of the relation field
|
- `field_name (str)`: name of the relation field
|
||||||
- `table_name (str)`: name of the table
|
- `table_name (str)`: name of the table
|
||||||
@ -281,6 +189,24 @@ If the class is a ormar.Model it is skipped.
|
|||||||
|
|
||||||
`(Tuple[Dict, Dict])`: updated attrs and model_fields
|
`(Tuple[Dict, Dict])`: updated attrs and model_fields
|
||||||
|
|
||||||
|
<a name="models.metaclass.update_attrs_and_fields"></a>
|
||||||
|
#### update\_attrs\_and\_fields
|
||||||
|
|
||||||
|
```python
|
||||||
|
update_attrs_and_fields(attrs: Dict, new_attrs: Dict, model_fields: Dict, new_model_fields: Dict, new_fields: Set) -> Dict
|
||||||
|
```
|
||||||
|
|
||||||
|
Updates __annotations__, values of model fields (so pydantic FieldInfos)
|
||||||
|
as well as model.Meta.model_fields definitions from parents.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `attrs (Dict)`: new namespace for class being constructed
|
||||||
|
- `new_attrs (Dict)`: related of the namespace extracted from parent class
|
||||||
|
- `model_fields (Dict[str, BaseField])`: ormar fields in defined in current class
|
||||||
|
- `new_model_fields (Dict[str, BaseField])`: ormar fields defined in parent classes
|
||||||
|
- `new_fields (Set[str])`: set of new fields names
|
||||||
|
|
||||||
<a name="models.metaclass.ModelMetaclass"></a>
|
<a name="models.metaclass.ModelMetaclass"></a>
|
||||||
## ModelMetaclass Objects
|
## ModelMetaclass Objects
|
||||||
|
|
||||||
|
|||||||
132
docs/api/models/model-row.md
Normal file
132
docs/api/models/model-row.md
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
<a name="models.model_row"></a>
|
||||||
|
# models.model\_row
|
||||||
|
|
||||||
|
<a name="models.model_row.ModelRow"></a>
|
||||||
|
## ModelRow Objects
|
||||||
|
|
||||||
|
```python
|
||||||
|
class ModelRow(NewBaseModel)
|
||||||
|
```
|
||||||
|
|
||||||
|
<a name="models.model_row.ModelRow.from_row"></a>
|
||||||
|
#### from\_row
|
||||||
|
|
||||||
|
```python
|
||||||
|
| @classmethod
|
||||||
|
| from_row(cls, row: sqlalchemy.engine.ResultProxy, source_model: Type["Model"], select_related: List = None, related_models: Any = None, related_field: Type["ForeignKeyField"] = None, excludable: ExcludableItems = None, current_relation_str: str = "", proxy_source_model: Optional[Type["Model"]] = None) -> Optional["Model"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Model method to convert raw sql row from database into ormar.Model instance.
|
||||||
|
Traverses nested models if they were specified in select_related for query.
|
||||||
|
|
||||||
|
Called recurrently and returns model instance if it's present in the row.
|
||||||
|
Note that it's processing one row at a time, so if there are duplicates of
|
||||||
|
parent row that needs to be joined/combined
|
||||||
|
(like parent row in sql join with 2+ child rows)
|
||||||
|
instances populated in this method are later combined in the QuerySet.
|
||||||
|
Other method working directly on raw database results is in prefetch_query,
|
||||||
|
where rows are populated in a different way as they do not have
|
||||||
|
nested models in result.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `proxy_source_model (Optional[Type["ModelRow"]])`: source model from which querysetproxy is constructed
|
||||||
|
- `excludable (ExcludableItems)`: structure of fields to include and exclude
|
||||||
|
- `current_relation_str (str)`: name of the relation field
|
||||||
|
- `source_model (Type[Model])`: model on which relation was defined
|
||||||
|
- `row (sqlalchemy.engine.result.ResultProxy)`: raw result row from the database
|
||||||
|
- `select_related (List)`: list of names of related models fetched from database
|
||||||
|
- `related_models (Union[List, Dict])`: list or dict of related models
|
||||||
|
- `related_field (Type[ForeignKeyField])`: field with relation declaration
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(Optional[Model])`: returns model if model is populated from database
|
||||||
|
|
||||||
|
<a name="models.model_row.ModelRow._populate_nested_models_from_row"></a>
|
||||||
|
#### \_populate\_nested\_models\_from\_row
|
||||||
|
|
||||||
|
```python
|
||||||
|
| @classmethod
|
||||||
|
| _populate_nested_models_from_row(cls, item: dict, row: sqlalchemy.engine.ResultProxy, source_model: Type["Model"], related_models: Any, excludable: ExcludableItems, table_prefix: str, current_relation_str: str = None, proxy_source_model: Type["Model"] = None) -> dict
|
||||||
|
```
|
||||||
|
|
||||||
|
Traverses structure of related models and populates the nested models
|
||||||
|
from the database row.
|
||||||
|
Related models can be a list if only directly related models are to be
|
||||||
|
populated, converted to dict if related models also have their own related
|
||||||
|
models to be populated.
|
||||||
|
|
||||||
|
Recurrently calls from_row method on nested instances and create nested
|
||||||
|
instances. In the end those instances are added to the final model dictionary.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `proxy_source_model (Optional[Type["ModelRow"]])`: source model from which querysetproxy is constructed
|
||||||
|
- `excludable (ExcludableItems)`: structure of fields to include and exclude
|
||||||
|
- `source_model (Type[Model])`: source model from which relation started
|
||||||
|
- `current_relation_str (str)`: joined related parts into one string
|
||||||
|
- `item (Dict)`: dictionary of already populated nested models, otherwise empty dict
|
||||||
|
- `row (sqlalchemy.engine.result.ResultProxy)`: raw result row from the database
|
||||||
|
- `related_models (Union[Dict, List])`: list or dict of related models
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(Dict)`: dictionary with keys corresponding to model fields names
|
||||||
|
and values are database values
|
||||||
|
|
||||||
|
<a name="models.model_row.ModelRow.populate_through_instance"></a>
|
||||||
|
#### populate\_through\_instance
|
||||||
|
|
||||||
|
```python
|
||||||
|
| @classmethod
|
||||||
|
| populate_through_instance(cls, row: sqlalchemy.engine.ResultProxy, through_name: str, related: str, excludable: ExcludableItems) -> "ModelRow"
|
||||||
|
```
|
||||||
|
|
||||||
|
Initialize the through model from db row.
|
||||||
|
Excluded all relation fields and other exclude/include set in excludable.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `row (sqlalchemy.engine.ResultProxy)`: loaded row from database
|
||||||
|
- `through_name (str)`: name of the through field
|
||||||
|
- `related (str)`: name of the relation
|
||||||
|
- `excludable (ExcludableItems)`: structure of fields to include and exclude
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`("ModelRow")`: initialized through model without relation
|
||||||
|
|
||||||
|
<a name="models.model_row.ModelRow.extract_prefixed_table_columns"></a>
|
||||||
|
#### extract\_prefixed\_table\_columns
|
||||||
|
|
||||||
|
```python
|
||||||
|
| @classmethod
|
||||||
|
| extract_prefixed_table_columns(cls, item: dict, row: sqlalchemy.engine.result.ResultProxy, table_prefix: str, excludable: ExcludableItems) -> Dict
|
||||||
|
```
|
||||||
|
|
||||||
|
Extracts own fields from raw sql result, using a given prefix.
|
||||||
|
Prefix changes depending on the table's position in a join.
|
||||||
|
|
||||||
|
If the table is a main table, there is no prefix.
|
||||||
|
All joined tables have prefixes to allow duplicate column names,
|
||||||
|
as well as duplicated joins to the same table from multiple different tables.
|
||||||
|
|
||||||
|
Extracted fields populates the related dict later used to construct a Model.
|
||||||
|
|
||||||
|
Used in Model.from_row and PrefetchQuery._populate_rows methods.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `excludable (ExcludableItems)`: structure of fields to include and exclude
|
||||||
|
- `item (Dict)`: dictionary of already populated nested models, otherwise empty dict
|
||||||
|
- `row (sqlalchemy.engine.result.ResultProxy)`: raw result row from the database
|
||||||
|
- `table_prefix (str)`: prefix of the table from AliasManager
|
||||||
|
each pair of tables have own prefix (two of them depending on direction) -
|
||||||
|
used in joins to allow multiple joins to the same table.
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(Dict)`: dictionary with keys corresponding to model fields names
|
||||||
|
and values are database values
|
||||||
|
|
||||||
@ -5,122 +5,14 @@
|
|||||||
## Model Objects
|
## Model Objects
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class Model(NewBaseModel)
|
class Model(ModelRow)
|
||||||
```
|
```
|
||||||
|
|
||||||
<a name="models.model.Model.from_row"></a>
|
|
||||||
#### from\_row
|
|
||||||
|
|
||||||
```python
|
|
||||||
| @classmethod
|
|
||||||
| from_row(cls: Type[T], row: sqlalchemy.engine.ResultProxy, select_related: List = None, related_models: Any = None, previous_model: Type[T] = None, source_model: Type[T] = None, related_name: str = None, fields: Optional[Union[Dict, Set]] = None, exclude_fields: Optional[Union[Dict, Set]] = None, current_relation_str: str = None) -> Optional[T]
|
|
||||||
```
|
|
||||||
|
|
||||||
Model method to convert raw sql row from database into ormar.Model instance.
|
|
||||||
Traverses nested models if they were specified in select_related for query.
|
|
||||||
|
|
||||||
Called recurrently and returns model instance if it's present in the row.
|
|
||||||
Note that it's processing one row at a time, so if there are duplicates of
|
|
||||||
parent row that needs to be joined/combined
|
|
||||||
(like parent row in sql join with 2+ child rows)
|
|
||||||
instances populated in this method are later combined in the QuerySet.
|
|
||||||
Other method working directly on raw database results is in prefetch_query,
|
|
||||||
where rows are populated in a different way as they do not have
|
|
||||||
nested models in result.
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `current_relation_str (str)`: name of the relation field
|
|
||||||
- `source_model (Type[Model])`: model on which relation was defined
|
|
||||||
- `row (sqlalchemy.engine.result.ResultProxy)`: raw result row from the database
|
|
||||||
- `select_related (List)`: list of names of related models fetched from database
|
|
||||||
- `related_models (Union[List, Dict])`: list or dict of related models
|
|
||||||
- `previous_model (Model class)`: internal param for nested models to specify table_prefix
|
|
||||||
- `related_name (str)`: internal parameter - name of current nested model
|
|
||||||
- `fields (Optional[Union[Dict, Set]])`: fields and related model fields to include
|
|
||||||
if provided only those are included
|
|
||||||
- `exclude_fields (Optional[Union[Dict, Set]])`: fields and related model fields to exclude
|
|
||||||
excludes the fields even if they are provided in fields
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
|
|
||||||
`(Optional[Model])`: returns model if model is populated from database
|
|
||||||
|
|
||||||
<a name="models.model.Model.populate_nested_models_from_row"></a>
|
|
||||||
#### populate\_nested\_models\_from\_row
|
|
||||||
|
|
||||||
```python
|
|
||||||
| @classmethod
|
|
||||||
| populate_nested_models_from_row(cls, item: dict, row: sqlalchemy.engine.ResultProxy, related_models: Any, fields: Optional[Union[Dict, Set]] = None, exclude_fields: Optional[Union[Dict, Set]] = None, current_relation_str: str = None, source_model: Type[T] = None) -> dict
|
|
||||||
```
|
|
||||||
|
|
||||||
Traverses structure of related models and populates the nested models
|
|
||||||
from the database row.
|
|
||||||
Related models can be a list if only directly related models are to be
|
|
||||||
populated, converted to dict if related models also have their own related
|
|
||||||
models to be populated.
|
|
||||||
|
|
||||||
Recurrently calls from_row method on nested instances and create nested
|
|
||||||
instances. In the end those instances are added to the final model dictionary.
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `source_model (Type[Model])`: source model from which relation started
|
|
||||||
- `current_relation_str (str)`: joined related parts into one string
|
|
||||||
- `item (Dict)`: dictionary of already populated nested models, otherwise empty dict
|
|
||||||
- `row (sqlalchemy.engine.result.ResultProxy)`: raw result row from the database
|
|
||||||
- `related_models (Union[Dict, List])`: list or dict of related models
|
|
||||||
- `fields (Optional[Union[Dict, Set]])`: fields and related model fields to include -
|
|
||||||
if provided only those are included
|
|
||||||
- `exclude_fields (Optional[Union[Dict, Set]])`: fields and related model fields to exclude
|
|
||||||
excludes the fields even if they are provided in fields
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
|
|
||||||
`(Dict)`: dictionary with keys corresponding to model fields names
|
|
||||||
and values are database values
|
|
||||||
|
|
||||||
<a name="models.model.Model.extract_prefixed_table_columns"></a>
|
|
||||||
#### extract\_prefixed\_table\_columns
|
|
||||||
|
|
||||||
```python
|
|
||||||
| @classmethod
|
|
||||||
| extract_prefixed_table_columns(cls, item: dict, row: sqlalchemy.engine.result.ResultProxy, table_prefix: str, fields: Optional[Union[Dict, Set]] = None, exclude_fields: Optional[Union[Dict, Set]] = None) -> dict
|
|
||||||
```
|
|
||||||
|
|
||||||
Extracts own fields from raw sql result, using a given prefix.
|
|
||||||
Prefix changes depending on the table's position in a join.
|
|
||||||
|
|
||||||
If the table is a main table, there is no prefix.
|
|
||||||
All joined tables have prefixes to allow duplicate column names,
|
|
||||||
as well as duplicated joins to the same table from multiple different tables.
|
|
||||||
|
|
||||||
Extracted fields populates the related dict later used to construct a Model.
|
|
||||||
|
|
||||||
Used in Model.from_row and PrefetchQuery._populate_rows methods.
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `item (Dict)`: dictionary of already populated nested models, otherwise empty dict
|
|
||||||
- `row (sqlalchemy.engine.result.ResultProxy)`: raw result row from the database
|
|
||||||
- `table_prefix (str)`: prefix of the table from AliasManager
|
|
||||||
each pair of tables have own prefix (two of them depending on direction) -
|
|
||||||
used in joins to allow multiple joins to the same table.
|
|
||||||
- `fields (Optional[Union[Dict, Set]])`: fields and related model fields to include -
|
|
||||||
if provided only those are included
|
|
||||||
- `exclude_fields (Optional[Union[Dict, Set]])`: fields and related model fields to exclude
|
|
||||||
excludes the fields even if they are provided in fields
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
|
|
||||||
`(Dict)`: dictionary with keys corresponding to model fields names
|
|
||||||
and values are database values
|
|
||||||
|
|
||||||
<a name="models.model.Model.upsert"></a>
|
<a name="models.model.Model.upsert"></a>
|
||||||
#### upsert
|
#### upsert
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| async upsert(**kwargs: Any) -> T
|
| async upsert(**kwargs: Any) -> "Model"
|
||||||
```
|
```
|
||||||
|
|
||||||
Performs either a save or an update depending on the presence of the pk.
|
Performs either a save or an update depending on the presence of the pk.
|
||||||
@ -139,7 +31,7 @@ For save kwargs are ignored, used only in update if provided.
|
|||||||
#### save
|
#### save
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| async save() -> T
|
| async save() -> "Model"
|
||||||
```
|
```
|
||||||
|
|
||||||
Performs a save of given Model instance.
|
Performs a save of given Model instance.
|
||||||
@ -203,7 +95,7 @@ number of updated instances
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
| @staticmethod
|
| @staticmethod
|
||||||
| async _update_and_follow(rel: T, follow: bool, visited: Set, update_count: int) -> Tuple[int, Set]
|
| async _update_and_follow(rel: "Model", follow: bool, visited: Set, update_count: int) -> Tuple[int, Set]
|
||||||
```
|
```
|
||||||
|
|
||||||
Internal method used in save_related to follow related models and update numbers
|
Internal method used in save_related to follow related models and update numbers
|
||||||
@ -227,7 +119,7 @@ number of updated instances
|
|||||||
#### update
|
#### update
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| async update(**kwargs: Any) -> T
|
| async update(**kwargs: Any) -> "Model"
|
||||||
```
|
```
|
||||||
|
|
||||||
Performs update of Model instance in the database.
|
Performs update of Model instance in the database.
|
||||||
@ -274,7 +166,7 @@ or update and the Model will be saved in database again.
|
|||||||
#### load
|
#### load
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| async load() -> T
|
| async load() -> "Model"
|
||||||
```
|
```
|
||||||
|
|
||||||
Allow to refresh existing Models fields from database.
|
Allow to refresh existing Models fields from database.
|
||||||
@ -289,3 +181,40 @@ Does NOT refresh the related models fields if they were loaded before.
|
|||||||
|
|
||||||
`(Model)`: reloaded Model
|
`(Model)`: reloaded Model
|
||||||
|
|
||||||
|
<a name="models.model.Model.load_all"></a>
|
||||||
|
#### load\_all
|
||||||
|
|
||||||
|
```python
|
||||||
|
| async load_all(follow: bool = False, exclude: Union[List, str, Set, Dict] = None) -> "Model"
|
||||||
|
```
|
||||||
|
|
||||||
|
Allow to refresh existing Models fields from database.
|
||||||
|
Performs refresh of the related models fields.
|
||||||
|
|
||||||
|
By default loads only self and the directly related ones.
|
||||||
|
|
||||||
|
If follow=True is set it loads also related models of related models.
|
||||||
|
|
||||||
|
To not get stuck in an infinite loop as related models also keep a relation
|
||||||
|
to parent model visited models set is kept.
|
||||||
|
|
||||||
|
That way already visited models that are nested are loaded, but the load do not
|
||||||
|
follow them inside. So Model A -> Model B -> Model C -> Model A -> Model X
|
||||||
|
will load second Model A but will never follow into Model X.
|
||||||
|
Nested relations of those kind need to be loaded manually.
|
||||||
|
|
||||||
|
**Raises**:
|
||||||
|
|
||||||
|
- `NoMatch`: If given pk is not found in database.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `exclude ()`:
|
||||||
|
- `follow (bool)`: flag to trigger deep save -
|
||||||
|
by default only directly related models are saved
|
||||||
|
with follow=True also related models of related models are saved
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(Model)`: reloaded Model
|
||||||
|
|
||||||
|
|||||||
@ -146,7 +146,7 @@ Raises exception if model is abstract or has ForwardRefs in relation fields.
|
|||||||
#### \_extract\_related\_model\_instead\_of\_field
|
#### \_extract\_related\_model\_instead\_of\_field
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| _extract_related_model_instead_of_field(item: str) -> Optional[Union["T", Sequence["T"]]]
|
| _extract_related_model_instead_of_field(item: str) -> Optional[Union["Model", Sequence["Model"]]]
|
||||||
```
|
```
|
||||||
|
|
||||||
Retrieves the related model/models from RelationshipManager.
|
Retrieves the related model/models from RelationshipManager.
|
||||||
@ -276,7 +276,7 @@ cause some dialect require different treatment
|
|||||||
#### remove
|
#### remove
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| remove(parent: "T", name: str) -> None
|
| remove(parent: "Model", name: str) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Removes child from relation with given name in RelationshipManager
|
Removes child from relation with given name in RelationshipManager
|
||||||
|
|||||||
@ -22,11 +22,25 @@ Shortcut for ormar's model AliasManager stored on Meta.
|
|||||||
|
|
||||||
`(AliasManager)`: alias manager from model's Meta
|
`(AliasManager)`: alias manager from model's Meta
|
||||||
|
|
||||||
<a name="queryset.join.SqlJoin.on_clause"></a>
|
<a name="queryset.join.SqlJoin.to_table"></a>
|
||||||
#### on\_clause
|
#### to\_table
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| on_clause(previous_alias: str, from_clause: str, to_clause: str) -> text
|
| @property
|
||||||
|
| to_table() -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
Shortcut to table name of the next model
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(str)`: name of the target table
|
||||||
|
|
||||||
|
<a name="queryset.join.SqlJoin._on_clause"></a>
|
||||||
|
#### \_on\_clause
|
||||||
|
|
||||||
|
```python
|
||||||
|
| _on_clause(previous_alias: str, from_clause: str, to_clause: str) -> text
|
||||||
```
|
```
|
||||||
|
|
||||||
Receives aliases and names of both ends of the join and combines them
|
Receives aliases and names of both ends of the join and combines them
|
||||||
@ -99,11 +113,11 @@ Updated are:
|
|||||||
- `related_name (str)`: name of the relation to follow
|
- `related_name (str)`: name of the relation to follow
|
||||||
- `remainder (Any)`: deeper tables if there are more nested joins
|
- `remainder (Any)`: deeper tables if there are more nested joins
|
||||||
|
|
||||||
<a name="queryset.join.SqlJoin.process_m2m_through_table"></a>
|
<a name="queryset.join.SqlJoin._process_m2m_through_table"></a>
|
||||||
#### process\_m2m\_through\_table
|
#### \_process\_m2m\_through\_table
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| process_m2m_through_table() -> None
|
| _process_m2m_through_table() -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Process Through table of the ManyToMany relation so that source table is
|
Process Through table of the ManyToMany relation so that source table is
|
||||||
@ -119,11 +133,11 @@ Replaces needed parameters like:
|
|||||||
|
|
||||||
To point to through model
|
To point to through model
|
||||||
|
|
||||||
<a name="queryset.join.SqlJoin.process_m2m_related_name_change"></a>
|
<a name="queryset.join.SqlJoin._process_m2m_related_name_change"></a>
|
||||||
#### process\_m2m\_related\_name\_change
|
#### \_process\_m2m\_related\_name\_change
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| process_m2m_related_name_change(reverse: bool = False) -> str
|
| _process_m2m_related_name_change(reverse: bool = False) -> str
|
||||||
```
|
```
|
||||||
|
|
||||||
Extracts relation name to link join through the Through model declared on
|
Extracts relation name to link join through the Through model declared on
|
||||||
@ -158,74 +172,21 @@ Updates the used aliases list directly.
|
|||||||
|
|
||||||
Process order_by causes for non m2m relations.
|
Process order_by causes for non m2m relations.
|
||||||
|
|
||||||
<a name="queryset.join.SqlJoin._replace_many_to_many_order_by_columns"></a>
|
<a name="queryset.join.SqlJoin._get_order_bys"></a>
|
||||||
#### \_replace\_many\_to\_many\_order\_by\_columns
|
#### \_get\_order\_bys
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| _replace_many_to_many_order_by_columns(part: str, new_part: str) -> None
|
| _get_order_bys() -> None
|
||||||
```
|
|
||||||
|
|
||||||
Substitutes the name of the relation with actual model name in m2m order bys.
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `part (str)`: name of the field with relation
|
|
||||||
- `new_part (str)`: name of the target model
|
|
||||||
|
|
||||||
<a name="queryset.join.SqlJoin._check_if_condition_apply"></a>
|
|
||||||
#### \_check\_if\_condition\_apply
|
|
||||||
|
|
||||||
```python
|
|
||||||
| @staticmethod
|
|
||||||
| _check_if_condition_apply(condition: List, part: str) -> bool
|
|
||||||
```
|
|
||||||
|
|
||||||
Checks filter conditions to find if they apply to current join.
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `condition (List[str])`: list of parts of condition split by '__'
|
|
||||||
- `part (str)`: name of the current relation join.
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
|
|
||||||
`(bool)`: result of the check
|
|
||||||
|
|
||||||
<a name="queryset.join.SqlJoin.set_aliased_order_by"></a>
|
|
||||||
#### set\_aliased\_order\_by
|
|
||||||
|
|
||||||
```python
|
|
||||||
| set_aliased_order_by(condition: List[str], to_table: str) -> None
|
|
||||||
```
|
|
||||||
|
|
||||||
Substitute hyphens ('-') with descending order.
|
|
||||||
Construct actual sqlalchemy text clause using aliased table and column name.
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `condition (List[str])`: list of parts of a current condition split by '__'
|
|
||||||
- `to_table (sqlalchemy.sql.elements.quoted_name)`: target table
|
|
||||||
|
|
||||||
<a name="queryset.join.SqlJoin.get_order_bys"></a>
|
|
||||||
#### get\_order\_bys
|
|
||||||
|
|
||||||
```python
|
|
||||||
| get_order_bys(to_table: str, pkname_alias: str) -> None
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Triggers construction of order bys if they are given.
|
Triggers construction of order bys if they are given.
|
||||||
Otherwise by default each table is sorted by a primary key column asc.
|
Otherwise by default each table is sorted by a primary key column asc.
|
||||||
|
|
||||||
**Arguments**:
|
<a name="queryset.join.SqlJoin._get_to_and_from_keys"></a>
|
||||||
|
#### \_get\_to\_and\_from\_keys
|
||||||
- `to_table (sqlalchemy.sql.elements.quoted_name)`: target table
|
|
||||||
- `pkname_alias (str)`: alias of the primary key column
|
|
||||||
|
|
||||||
<a name="queryset.join.SqlJoin.get_to_and_from_keys"></a>
|
|
||||||
#### get\_to\_and\_from\_keys
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| get_to_and_from_keys() -> Tuple[str, str]
|
| _get_to_and_from_keys() -> Tuple[str, str]
|
||||||
```
|
```
|
||||||
|
|
||||||
Based on the relation type, name of the relation and previous models and parts
|
Based on the relation type, name of the relation and previous models and parts
|
||||||
|
|||||||
@ -1,26 +1,6 @@
|
|||||||
<a name="queryset.prefetch_query"></a>
|
<a name="queryset.prefetch_query"></a>
|
||||||
# queryset.prefetch\_query
|
# queryset.prefetch\_query
|
||||||
|
|
||||||
<a name="queryset.prefetch_query.add_relation_field_to_fields"></a>
|
|
||||||
#### add\_relation\_field\_to\_fields
|
|
||||||
|
|
||||||
```python
|
|
||||||
add_relation_field_to_fields(fields: Union[Set[Any], Dict[Any, Any], None], related_field_name: str) -> Union[Set[Any], Dict[Any, Any], None]
|
|
||||||
```
|
|
||||||
|
|
||||||
Adds related field into fields to include as otherwise it would be skipped.
|
|
||||||
Related field is added only if fields are already populated.
|
|
||||||
Empty fields implies all fields.
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `fields (Dict)`: Union[Set[Any], Dict[Any, Any], None]
|
|
||||||
- `related_field_name (str)`: name of the field with relation
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
|
|
||||||
`(Union[Set[Any], Dict[Any, Any], None])`: updated fields dict
|
|
||||||
|
|
||||||
<a name="queryset.prefetch_query.sort_models"></a>
|
<a name="queryset.prefetch_query.sort_models"></a>
|
||||||
#### sort\_models
|
#### sort\_models
|
||||||
|
|
||||||
@ -232,7 +212,7 @@ on each of the parent models from list.
|
|||||||
#### \_extract\_related\_models
|
#### \_extract\_related\_models
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| async _extract_related_models(related: str, target_model: Type["Model"], prefetch_dict: Dict, select_dict: Dict, fields: Union[Set[Any], Dict[Any, Any], None], exclude_fields: Union[Set[Any], Dict[Any, Any], None], orders_by: Dict) -> None
|
| async _extract_related_models(related: str, target_model: Type["Model"], prefetch_dict: Dict, select_dict: Dict, excludable: "ExcludableItems", orders_by: Dict) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Constructs queries with required ids and extracts data with fields that should
|
Constructs queries with required ids and extracts data with fields that should
|
||||||
@ -261,7 +241,7 @@ Calls itself recurrently to extract deeper nested relations of related model.
|
|||||||
#### \_run\_prefetch\_query
|
#### \_run\_prefetch\_query
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| async _run_prefetch_query(target_field: Type["BaseField"], fields: Union[Set[Any], Dict[Any, Any], None], exclude_fields: Union[Set[Any], Dict[Any, Any], None], filter_clauses: List) -> Tuple[str, List]
|
| async _run_prefetch_query(target_field: Type["BaseField"], excludable: "ExcludableItems", filter_clauses: List, related_field_name: str) -> Tuple[str, str, List]
|
||||||
```
|
```
|
||||||
|
|
||||||
Actually runs the queries against the database and populates the raw response
|
Actually runs the queries against the database and populates the raw response
|
||||||
@ -273,8 +253,6 @@ models.
|
|||||||
**Arguments**:
|
**Arguments**:
|
||||||
|
|
||||||
- `target_field (Type["BaseField"])`: ormar field with relation definition
|
- `target_field (Type["BaseField"])`: ormar field with relation definition
|
||||||
- `fields (Union[Set[Any], Dict[Any, Any], None])`: fields to include
|
|
||||||
- `exclude_fields (Union[Set[Any], Dict[Any, Any], None])`: fields to exclude
|
|
||||||
- `filter_clauses (List[sqlalchemy.sql.elements.TextClause])`: list of clauses, actually one clause with ids of relation
|
- `filter_clauses (List[sqlalchemy.sql.elements.TextClause])`: list of clauses, actually one clause with ids of relation
|
||||||
|
|
||||||
**Returns**:
|
**Returns**:
|
||||||
@ -320,7 +298,7 @@ Updates models that are already loaded, usually children of children.
|
|||||||
#### \_populate\_rows
|
#### \_populate\_rows
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| _populate_rows(rows: List, target_field: Type["ForeignKeyField"], parent_model: Type["Model"], table_prefix: str, fields: Union[Set[Any], Dict[Any, Any], None], exclude_fields: Union[Set[Any], Dict[Any, Any], None], prefetch_dict: Dict, orders_by: Dict) -> None
|
| _populate_rows(rows: List, target_field: Type["ForeignKeyField"], parent_model: Type["Model"], table_prefix: str, exclude_prefix: str, excludable: "ExcludableItems", prefetch_dict: Dict, orders_by: Dict) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Instantiates children models extracted from given relation.
|
Instantiates children models extracted from given relation.
|
||||||
@ -334,12 +312,11 @@ and set on the parent model after sorting if needed.
|
|||||||
|
|
||||||
**Arguments**:
|
**Arguments**:
|
||||||
|
|
||||||
|
- `excludable (ExcludableItems)`: structure of fields to include and exclude
|
||||||
- `rows (List[sqlalchemy.engine.result.RowProxy])`: raw sql response from the prefetch query
|
- `rows (List[sqlalchemy.engine.result.RowProxy])`: raw sql response from the prefetch query
|
||||||
- `target_field (Type["BaseField"])`: field with relation definition from parent model
|
- `target_field (Type["BaseField"])`: field with relation definition from parent model
|
||||||
- `parent_model (Type[Model])`: model with relation definition
|
- `parent_model (Type[Model])`: model with relation definition
|
||||||
- `table_prefix (str)`: prefix of the target table from current relation
|
- `table_prefix (str)`: prefix of the target table from current relation
|
||||||
- `fields (Union[Set[Any], Dict[Any, Any], None])`: fields to include
|
|
||||||
- `exclude_fields (Union[Set[Any], Dict[Any, Any], None])`: fields to exclude
|
|
||||||
- `prefetch_dict (Dict)`: dictionaries of related models to prefetch
|
- `prefetch_dict (Dict)`: dictionaries of related models to prefetch
|
||||||
- `orders_by (Dict)`: dictionary of order by clauses by model
|
- `orders_by (Dict)`: dictionary of order by clauses by model
|
||||||
|
|
||||||
|
|||||||
@ -38,6 +38,16 @@ Shortcut to model class set on QuerySet.
|
|||||||
|
|
||||||
`(Type[Model])`: model class
|
`(Type[Model])`: model class
|
||||||
|
|
||||||
|
<a name="queryset.queryset.QuerySet.rebuild_self"></a>
|
||||||
|
#### rebuild\_self
|
||||||
|
|
||||||
|
```python
|
||||||
|
| rebuild_self(filter_clauses: List = None, exclude_clauses: List = None, select_related: List = None, limit_count: int = None, offset: int = None, excludable: "ExcludableItems" = None, order_bys: List = None, prefetch_related: List = None, limit_raw_sql: bool = None, proxy_source_model: Optional[Type["Model"]] = None) -> "QuerySet"
|
||||||
|
```
|
||||||
|
|
||||||
|
Method that returns new instance of queryset based on passed params,
|
||||||
|
all not passed params are taken from current values.
|
||||||
|
|
||||||
<a name="queryset.queryset.QuerySet._prefetch_related_models"></a>
|
<a name="queryset.queryset.QuerySet._prefetch_related_models"></a>
|
||||||
#### \_prefetch\_related\_models
|
#### \_prefetch\_related\_models
|
||||||
|
|
||||||
@ -252,7 +262,7 @@ To chain related `Models` relation use double underscores between names.
|
|||||||
#### fields
|
#### fields
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| fields(columns: Union[List, str, Set, Dict]) -> "QuerySet"
|
| fields(columns: Union[List, str, Set, Dict], _is_exclude: bool = False) -> "QuerySet"
|
||||||
```
|
```
|
||||||
|
|
||||||
With `fields()` you can select subset of model columns to limit the data load.
|
With `fields()` you can select subset of model columns to limit the data load.
|
||||||
@ -293,6 +303,7 @@ To include whole nested model specify model related field name and ellipsis.
|
|||||||
|
|
||||||
**Arguments**:
|
**Arguments**:
|
||||||
|
|
||||||
|
- `_is_exclude (bool)`: flag if it's exclude or include operation
|
||||||
- `columns (Union[List, str, Set, Dict])`: columns to include
|
- `columns (Union[List, str, Set, Dict])`: columns to include
|
||||||
|
|
||||||
**Returns**:
|
**Returns**:
|
||||||
|
|||||||
@ -17,38 +17,6 @@ class Query()
|
|||||||
|
|
||||||
Initialize empty order_by dict to be populated later during the query call
|
Initialize empty order_by dict to be populated later during the query call
|
||||||
|
|
||||||
<a name="queryset.query.Query.prefixed_pk_name"></a>
|
|
||||||
#### prefixed\_pk\_name
|
|
||||||
|
|
||||||
```python
|
|
||||||
| @property
|
|
||||||
| prefixed_pk_name() -> str
|
|
||||||
```
|
|
||||||
|
|
||||||
Shortcut for extracting prefixed with alias primary key column name from main
|
|
||||||
model
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
|
|
||||||
`(str)`: alias of pk column prefix with table name.
|
|
||||||
|
|
||||||
<a name="queryset.query.Query.alias"></a>
|
|
||||||
#### alias
|
|
||||||
|
|
||||||
```python
|
|
||||||
| alias(name: str) -> str
|
|
||||||
```
|
|
||||||
|
|
||||||
Shortcut to extracting column alias from given master model.
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `name (str)`: name of column
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
|
|
||||||
`(str)`: alias of given column name
|
|
||||||
|
|
||||||
<a name="queryset.query.Query.apply_order_bys_for_primary_model"></a>
|
<a name="queryset.query.Query.apply_order_bys_for_primary_model"></a>
|
||||||
#### apply\_order\_bys\_for\_primary\_model
|
#### apply\_order\_bys\_for\_primary\_model
|
||||||
|
|
||||||
|
|||||||
@ -154,7 +154,7 @@ with all children models under their relation keys.
|
|||||||
#### get\_relationship\_alias\_model\_and\_str
|
#### get\_relationship\_alias\_model\_and\_str
|
||||||
|
|
||||||
```python
|
```python
|
||||||
get_relationship_alias_model_and_str(source_model: Type["Model"], related_parts: List) -> Tuple[str, Type["Model"], str]
|
get_relationship_alias_model_and_str(source_model: Type["Model"], related_parts: List) -> Tuple[str, Type["Model"], str, bool]
|
||||||
```
|
```
|
||||||
|
|
||||||
Walks the relation to retrieve the actual model on which the clause should be
|
Walks the relation to retrieve the actual model on which the clause should be
|
||||||
|
|||||||
@ -120,7 +120,7 @@ Adds alias to the dictionary of aliases under given key.
|
|||||||
#### resolve\_relation\_alias
|
#### resolve\_relation\_alias
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| resolve_relation_alias(from_model: Type["Model"], relation_name: str) -> str
|
| resolve_relation_alias(from_model: Union[Type["Model"], Type["ModelRow"]], relation_name: str) -> str
|
||||||
```
|
```
|
||||||
|
|
||||||
Given model and relation name returns the alias for this relation.
|
Given model and relation name returns the alias for this relation.
|
||||||
@ -134,3 +134,24 @@ Given model and relation name returns the alias for this relation.
|
|||||||
|
|
||||||
`(str)`: alias of the relation
|
`(str)`: alias of the relation
|
||||||
|
|
||||||
|
<a name="relations.alias_manager.AliasManager.resolve_relation_alias_after_complex"></a>
|
||||||
|
#### resolve\_relation\_alias\_after\_complex
|
||||||
|
|
||||||
|
```python
|
||||||
|
| resolve_relation_alias_after_complex(source_model: Union[Type["Model"], Type["ModelRow"]], relation_str: str, relation_field: Type["ForeignKeyField"]) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
Given source model and relation string returns the alias for this complex
|
||||||
|
relation if it exists, otherwise fallback to normal relation from a relation
|
||||||
|
field definition.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `relation_field (Type["ForeignKeyField"])`: field with direct relation definition
|
||||||
|
- `source_model (source Model)`: model with query starts
|
||||||
|
- `relation_str (str)`: string with relation joins defined
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(str)`: alias of the relation
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
## QuerysetProxy Objects
|
## QuerysetProxy Objects
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class QuerysetProxy(ormar.QuerySetProtocol)
|
class QuerysetProxy()
|
||||||
```
|
```
|
||||||
|
|
||||||
Exposes QuerySet methods on relations, but also handles creating and removing
|
Exposes QuerySet methods on relations, but also handles creating and removing
|
||||||
@ -43,7 +43,7 @@ Set's the queryset. Initialized in RelationProxy.
|
|||||||
#### \_assign\_child\_to\_parent
|
#### \_assign\_child\_to\_parent
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| _assign_child_to_parent(child: Optional["T"]) -> None
|
| _assign_child_to_parent(child: Optional["Model"]) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Registers child in parents RelationManager.
|
Registers child in parents RelationManager.
|
||||||
@ -56,7 +56,7 @@ Registers child in parents RelationManager.
|
|||||||
#### \_register\_related
|
#### \_register\_related
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| _register_related(child: Union["T", Sequence[Optional["T"]]]) -> None
|
| _register_related(child: Union["Model", Sequence[Optional["Model"]]]) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Registers child/ children in parents RelationManager.
|
Registers child/ children in parents RelationManager.
|
||||||
@ -78,20 +78,35 @@ Cleans the current list of the related models.
|
|||||||
#### create\_through\_instance
|
#### create\_through\_instance
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| async create_through_instance(child: "T") -> None
|
| async create_through_instance(child: "Model", **kwargs: Any) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Crete a through model instance in the database for m2m relations.
|
Crete a through model instance in the database for m2m relations.
|
||||||
|
|
||||||
**Arguments**:
|
**Arguments**:
|
||||||
|
|
||||||
|
- `kwargs (Any)`: dict of additional keyword arguments for through instance
|
||||||
|
- `child (Model)`: child model instance
|
||||||
|
|
||||||
|
<a name="relations.querysetproxy.QuerysetProxy.update_through_instance"></a>
|
||||||
|
#### update\_through\_instance
|
||||||
|
|
||||||
|
```python
|
||||||
|
| async update_through_instance(child: "Model", **kwargs: Any) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
Updates a through model instance in the database for m2m relations.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `kwargs (Any)`: dict of additional keyword arguments for through instance
|
||||||
- `child (Model)`: child model instance
|
- `child (Model)`: child model instance
|
||||||
|
|
||||||
<a name="relations.querysetproxy.QuerysetProxy.delete_through_instance"></a>
|
<a name="relations.querysetproxy.QuerysetProxy.delete_through_instance"></a>
|
||||||
#### delete\_through\_instance
|
#### delete\_through\_instance
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| async delete_through_instance(child: "T") -> None
|
| async delete_through_instance(child: "Model") -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Removes through model instance from the database for m2m relations.
|
Removes through model instance from the database for m2m relations.
|
||||||
@ -256,6 +271,27 @@ Actual call delegated to QuerySet.
|
|||||||
|
|
||||||
`(Model)`: created model
|
`(Model)`: created model
|
||||||
|
|
||||||
|
<a name="relations.querysetproxy.QuerysetProxy.update"></a>
|
||||||
|
#### update
|
||||||
|
|
||||||
|
```python
|
||||||
|
| async update(each: bool = False, **kwargs: Any) -> int
|
||||||
|
```
|
||||||
|
|
||||||
|
Updates the model table after applying the filters from kwargs.
|
||||||
|
|
||||||
|
You have to either pass a filter to narrow down a query or explicitly pass
|
||||||
|
each=True flag to affect whole table.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `each (bool)`: flag if whole table should be affected if no filter is passed
|
||||||
|
- `kwargs (Any)`: fields names and proper value types
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(int)`: number of updated rows
|
||||||
|
|
||||||
<a name="relations.querysetproxy.QuerysetProxy.get_or_create"></a>
|
<a name="relations.querysetproxy.QuerysetProxy.get_or_create"></a>
|
||||||
#### get\_or\_create
|
#### get\_or\_create
|
||||||
|
|
||||||
|
|||||||
@ -10,37 +10,6 @@ class RelationsManager()
|
|||||||
|
|
||||||
Manages relations on a Model, each Model has it's own instance.
|
Manages relations on a Model, each Model has it's own instance.
|
||||||
|
|
||||||
<a name="relations.relation_manager.RelationsManager._get_relation_type"></a>
|
|
||||||
#### \_get\_relation\_type
|
|
||||||
|
|
||||||
```python
|
|
||||||
| _get_relation_type(field: Type[BaseField]) -> RelationType
|
|
||||||
```
|
|
||||||
|
|
||||||
Returns type of the relation declared on a field.
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `field (Type[BaseField])`: field with relation declaration
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
|
|
||||||
`(RelationType)`: type of the relation defined on field
|
|
||||||
|
|
||||||
<a name="relations.relation_manager.RelationsManager._add_relation"></a>
|
|
||||||
#### \_add\_relation
|
|
||||||
|
|
||||||
```python
|
|
||||||
| _add_relation(field: Type[BaseField]) -> None
|
|
||||||
```
|
|
||||||
|
|
||||||
Registers relation in the manager.
|
|
||||||
Adds Relation instance under field.name.
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `field (Type[BaseField])`: field with relation declaration
|
|
||||||
|
|
||||||
<a name="relations.relation_manager.RelationsManager.__contains__"></a>
|
<a name="relations.relation_manager.RelationsManager.__contains__"></a>
|
||||||
#### \_\_contains\_\_
|
#### \_\_contains\_\_
|
||||||
|
|
||||||
@ -62,7 +31,7 @@ Checks if relation with given name is already registered.
|
|||||||
#### get
|
#### get
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| get(name: str) -> Optional[Union["T", Sequence["T"]]]
|
| get(name: str) -> Optional[Union["Model", Sequence["Model"]]]
|
||||||
```
|
```
|
||||||
|
|
||||||
Returns the related model/models if relation is set.
|
Returns the related model/models if relation is set.
|
||||||
@ -76,23 +45,6 @@ Actual call is delegated to Relation instance registered under relation name.
|
|||||||
|
|
||||||
`(Optional[Union[Model, List[Model]])`: related model or list of related models if set
|
`(Optional[Union[Model, List[Model]])`: related model or list of related models if set
|
||||||
|
|
||||||
<a name="relations.relation_manager.RelationsManager._get"></a>
|
|
||||||
#### \_get
|
|
||||||
|
|
||||||
```python
|
|
||||||
| _get(name: str) -> Optional[Relation]
|
|
||||||
```
|
|
||||||
|
|
||||||
Returns the actual relation and not the related model(s).
|
|
||||||
|
|
||||||
**Arguments**:
|
|
||||||
|
|
||||||
- `name (str)`: name of the relation
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
|
|
||||||
`(ormar.relations.relation.Relation)`: Relation instance
|
|
||||||
|
|
||||||
<a name="relations.relation_manager.RelationsManager.add"></a>
|
<a name="relations.relation_manager.RelationsManager.add"></a>
|
||||||
#### add
|
#### add
|
||||||
|
|
||||||
@ -148,3 +100,51 @@ of relation from which you want to remove the parent.
|
|||||||
- `parent (Model)`: parent Model
|
- `parent (Model)`: parent Model
|
||||||
- `name (str)`: name of the relation
|
- `name (str)`: name of the relation
|
||||||
|
|
||||||
|
<a name="relations.relation_manager.RelationsManager._get"></a>
|
||||||
|
#### \_get
|
||||||
|
|
||||||
|
```python
|
||||||
|
| _get(name: str) -> Optional[Relation]
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns the actual relation and not the related model(s).
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `name (str)`: name of the relation
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(ormar.relations.relation.Relation)`: Relation instance
|
||||||
|
|
||||||
|
<a name="relations.relation_manager.RelationsManager._get_relation_type"></a>
|
||||||
|
#### \_get\_relation\_type
|
||||||
|
|
||||||
|
```python
|
||||||
|
| _get_relation_type(field: Type["BaseField"]) -> RelationType
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns type of the relation declared on a field.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `field (Type[BaseField])`: field with relation declaration
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
`(RelationType)`: type of the relation defined on field
|
||||||
|
|
||||||
|
<a name="relations.relation_manager.RelationsManager._add_relation"></a>
|
||||||
|
#### \_add\_relation
|
||||||
|
|
||||||
|
```python
|
||||||
|
| _add_relation(field: Type["BaseField"]) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
Registers relation in the manager.
|
||||||
|
Adds Relation instance under field.name.
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `field (Type[BaseField])`: field with relation declaration
|
||||||
|
|
||||||
|
|||||||
@ -131,7 +131,7 @@ will be deleted, and not only removed from relation).
|
|||||||
#### add
|
#### add
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| async add(item: "Model") -> None
|
| async add(item: "Model", **kwargs: Any) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Adds child model to relation.
|
Adds child model to relation.
|
||||||
@ -140,5 +140,6 @@ For ManyToMany relations through instance is automatically created.
|
|||||||
|
|
||||||
**Arguments**:
|
**Arguments**:
|
||||||
|
|
||||||
|
- `kwargs (Any)`: dict of additional keyword arguments for through instance
|
||||||
- `item (Model)`: child to add to relation
|
- `item (Model)`: child to add to relation
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,7 @@ Keeps related Models and handles adding/removing of the children.
|
|||||||
#### \_\_init\_\_
|
#### \_\_init\_\_
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| __init__(manager: "RelationsManager", type_: RelationType, field_name: str, to: Type["T"], through: Type["T"] = None) -> None
|
| __init__(manager: "RelationsManager", type_: RelationType, field_name: str, to: Type["Model"], through: Type["Model"] = None) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Initialize the Relation and keep the related models either as instances of
|
Initialize the Relation and keep the related models either as instances of
|
||||||
@ -73,7 +73,7 @@ Find child model in RelationProxy if exists.
|
|||||||
#### add
|
#### add
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| add(child: "T") -> None
|
| add(child: "Model") -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Adds child Model to relation, either sets child as related model or adds
|
Adds child Model to relation, either sets child as related model or adds
|
||||||
@ -101,7 +101,7 @@ it from the list in RelationProxy depending on relation type.
|
|||||||
#### get
|
#### get
|
||||||
|
|
||||||
```python
|
```python
|
||||||
| get() -> Optional[Union[List["T"], "T"]]
|
| get() -> Optional[Union[List["Model"], "Model"]]
|
||||||
```
|
```
|
||||||
|
|
||||||
Return the related model or models from RelationProxy.
|
Return the related model or models from RelationProxy.
|
||||||
|
|||||||
@ -306,7 +306,7 @@ async def joins():
|
|||||||
# visit: https://collerek.github.io/ormar/relations/
|
# visit: https://collerek.github.io/ormar/relations/
|
||||||
|
|
||||||
# to read more about joins and subqueries
|
# to read more about joins and subqueries
|
||||||
# visit: https://collerek.github.io/ormar/queries/delete/
|
# visit: https://collerek.github.io/ormar/queries/joins-and-subqueries/
|
||||||
|
|
||||||
|
|
||||||
async def filter_and_sort():
|
async def filter_and_sort():
|
||||||
|
|||||||
@ -27,6 +27,39 @@ await track.album.load()
|
|||||||
track.album.name # will return 'Malibu'
|
track.album.name # will return 'Malibu'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## load_all
|
||||||
|
|
||||||
|
`load_all(follow: bool = False, exclude: Union[List, str, Set, Dict] = None) -> Model`
|
||||||
|
|
||||||
|
Method works like `load()` but also goes through all relations of the `Model` on which the method is called,
|
||||||
|
and reloads them from database.
|
||||||
|
|
||||||
|
By default the `load_all` method loads only models that are directly related (one step away) to the model on which the method is called.
|
||||||
|
|
||||||
|
But you can specify the `follow=True` parameter to traverse through nested models and load all of them in the relation tree.
|
||||||
|
|
||||||
|
!!!warning
|
||||||
|
To avoid circular updates with `follow=True` set, `load_all` keeps a set of already visited Models,
|
||||||
|
and won't perform nested `loads` on Models that were already visited.
|
||||||
|
|
||||||
|
So if you have a diamond or circular relations types you need to perform the loads in a manual way.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# in example like this the second Street (coming from City) won't be load_all, so ZipCode won't be reloaded
|
||||||
|
Street -> District -> City -> Street -> ZipCode
|
||||||
|
```
|
||||||
|
|
||||||
|
Method accepts also optional exclude parameter that works exactly the same as exclude_fields method in `QuerySet`.
|
||||||
|
That way you can remove fields from related models being refreshed or skip whole related models.
|
||||||
|
|
||||||
|
Method performs one database query so it's more efficient than nested calls to `load()` and `all()` on related models.
|
||||||
|
|
||||||
|
!!!tip
|
||||||
|
To read more about `exclude` read [exclude_fields][exclude_fields]
|
||||||
|
|
||||||
|
!!!warning
|
||||||
|
All relations are cleared on `load_all()`, so if you exclude some nested models they will be empty after call.
|
||||||
|
|
||||||
## save
|
## save
|
||||||
|
|
||||||
`save() -> self`
|
`save() -> self`
|
||||||
@ -128,3 +161,4 @@ But you can specify the `follow=True` parameter to traverse through nested model
|
|||||||
[alembic]: https://alembic.sqlalchemy.org/en/latest/tutorial.html
|
[alembic]: https://alembic.sqlalchemy.org/en/latest/tutorial.html
|
||||||
[save status]: ../models/index/#model-save-status
|
[save status]: ../models/index/#model-save-status
|
||||||
[Internals]: #internals
|
[Internals]: #internals
|
||||||
|
[exclude_fields]: ../queries/select-columns.md#exclude_fields
|
||||||
|
|||||||
@ -52,7 +52,7 @@ class Department(ormar.Model):
|
|||||||
|
|
||||||
To define many-to-many relation use `ManyToMany` field.
|
To define many-to-many relation use `ManyToMany` field.
|
||||||
|
|
||||||
```python hl_lines="25-26"
|
```python hl_lines="18"
|
||||||
class Category(ormar.Model):
|
class Category(ormar.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
tablename = "categories"
|
tablename = "categories"
|
||||||
@ -62,13 +62,6 @@ class Category(ormar.Model):
|
|||||||
id: int = ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: str = ormar.String(max_length=40)
|
name: str = ormar.String(max_length=40)
|
||||||
|
|
||||||
# note: you need to specify through model
|
|
||||||
class PostCategory(ormar.Model):
|
|
||||||
class Meta:
|
|
||||||
tablename = "posts_categories"
|
|
||||||
database = database
|
|
||||||
metadata = metadata
|
|
||||||
|
|
||||||
class Post(ormar.Model):
|
class Post(ormar.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
tablename = "posts"
|
tablename = "posts"
|
||||||
@ -77,9 +70,7 @@ class Post(ormar.Model):
|
|||||||
|
|
||||||
id: int = ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
title: str = ormar.String(max_length=200)
|
title: str = ormar.String(max_length=200)
|
||||||
categories: Optional[Union[Category, List[Category]]] = ormar.ManyToMany(
|
categories: Optional[List[Category]] = ormar.ManyToMany(Category)
|
||||||
Category, through=PostCategory
|
|
||||||
)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -92,7 +83,52 @@ 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.
|
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
|
## Through fields
|
||||||
|
|
||||||
|
As part of the `ManyToMany` relation you can define a through model, that can contain additional
|
||||||
|
fields that you can use to filter, order etc. Fields defined like this are exposed on the reverse
|
||||||
|
side of the current query for m2m models.
|
||||||
|
|
||||||
|
So if you query from model `A` to model `B`, only model `B` has through field exposed.
|
||||||
|
Which kind of make sense, since it's a one through model/field for each of related models.
|
||||||
|
|
||||||
|
```python hl_lines="10-15"
|
||||||
|
class Category(ormar.Model):
|
||||||
|
class Meta(BaseMeta):
|
||||||
|
tablename = "categories"
|
||||||
|
|
||||||
|
id = ormar.Integer(primary_key=True)
|
||||||
|
name = ormar.String(max_length=40)
|
||||||
|
|
||||||
|
# you can specify additional fields on through model
|
||||||
|
class PostCategory(ormar.Model):
|
||||||
|
class Meta(BaseMeta):
|
||||||
|
tablename = "posts_x_categories"
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
sort_order: int = ormar.Integer(nullable=True)
|
||||||
|
param_name: str = ormar.String(default="Name", max_length=200)
|
||||||
|
|
||||||
|
|
||||||
|
class Post(ormar.Model):
|
||||||
|
class Meta(BaseMeta):
|
||||||
|
pass
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
title: str = ormar.String(max_length=200)
|
||||||
|
categories = ormar.ManyToMany(Category, through=PostCategory)
|
||||||
|
```
|
||||||
|
|
||||||
|
!!!tip
|
||||||
|
To read more about many-to-many relations and through fields visit [many-to-many][many-to-many] section
|
||||||
|
|
||||||
|
|
||||||
|
!!!tip
|
||||||
|
ManyToMany allows you to query the related models with [queryset-proxy][queryset-proxy].
|
||||||
|
|
||||||
|
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
|
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
|
different relations (remember the reverse side is auto-registered for you), you need to use
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# ManyToMany
|
# ManyToMany
|
||||||
|
|
||||||
`ManyToMany(to, through)` has required parameters `to` and `through` that takes target and relation `Model` classes.
|
`ManyToMany(to, through)` has required parameters `to` and optional `through` that takes target and relation `Model` classes.
|
||||||
|
|
||||||
Sqlalchemy column and Type are automatically taken from target `Model`.
|
Sqlalchemy column and Type are automatically taken from target `Model`.
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ Sqlalchemy column and Type are automatically taken from target `Model`.
|
|||||||
|
|
||||||
## Defining Models
|
## Defining Models
|
||||||
|
|
||||||
```Python hl_lines="32 49-50"
|
```Python hl_lines="40"
|
||||||
--8<-- "../docs_src/relations/docs002.py"
|
--8<-- "../docs_src/relations/docs002.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -20,8 +20,154 @@ post = await Post.objects.create(title="Hello, M2M", author=guido)
|
|||||||
news = await Category.objects.create(name="News")
|
news = await Category.objects.create(name="News")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Through Model
|
||||||
|
|
||||||
|
Optionally if you want to add additional fields you can explicitly create and pass
|
||||||
|
the through model class.
|
||||||
|
|
||||||
|
```Python hl_lines="14-20 29"
|
||||||
|
--8<-- "../docs_src/relations/docs004.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
!!!warning
|
||||||
|
Note that even of you do not provide through model it's going to be created for you automatically and
|
||||||
|
still has to be included in example in `alembic` migrations.
|
||||||
|
|
||||||
|
!!!tip
|
||||||
|
Note that you need to provide `through` model if you want to
|
||||||
|
customize the `Through` model name or the database table name of this model.
|
||||||
|
|
||||||
|
If you do not provide the Through field it will be generated for you.
|
||||||
|
|
||||||
|
The default naming convention is:
|
||||||
|
|
||||||
|
* for class name it's union of both classes name (parent+other) so in example above
|
||||||
|
it would be `PostCategory`
|
||||||
|
* for table name it similar but with underscore in between and s in the end of class
|
||||||
|
lowercase name, in example above would be `posts_categorys`
|
||||||
|
|
||||||
|
## Through Fields
|
||||||
|
|
||||||
|
The through field is auto added to the reverse side of the relation.
|
||||||
|
|
||||||
|
The exposed field is named as lowercase `Through` class name.
|
||||||
|
|
||||||
|
The exposed field **explicitly has no relations loaded** as the relation is already populated in `ManyToMany` field,
|
||||||
|
so it's useful only when additional fields are provided on `Through` model.
|
||||||
|
|
||||||
|
In a sample model setup as following:
|
||||||
|
|
||||||
|
```Python hl_lines="14-20 29"
|
||||||
|
--8<-- "../docs_src/relations/docs004.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
the through field can be used as a normal model field in most of the QuerySet operations.
|
||||||
|
|
||||||
|
Note that through field is attached only to related side of the query so:
|
||||||
|
|
||||||
|
```python
|
||||||
|
post = await Post.objects.select_related("categories").get()
|
||||||
|
# source model has no through field
|
||||||
|
assert post.postcategory is None
|
||||||
|
# related models have through field
|
||||||
|
assert post.categories[0].postcategory is not None
|
||||||
|
|
||||||
|
# same is applicable for reversed query
|
||||||
|
category = await Category.objects.select_related("posts").get()
|
||||||
|
assert category.postcategory is None
|
||||||
|
assert category.posts[0].postcategory is not None
|
||||||
|
```
|
||||||
|
|
||||||
|
Through field can be used for filtering the data.
|
||||||
|
```python
|
||||||
|
post = (
|
||||||
|
await Post.objects.select_related("categories")
|
||||||
|
.filter(postcategory__sort_order__gt=1)
|
||||||
|
.get()
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
!!!tip
|
||||||
|
Note that despite that the actual instance is not populated on source model,
|
||||||
|
in queries, order by statements etc you can access through model from both sides.
|
||||||
|
So below query has exactly the same effect (note access through `categories`)
|
||||||
|
|
||||||
|
```python
|
||||||
|
post = (
|
||||||
|
await Post.objects.select_related("categories")
|
||||||
|
.filter(categories__postcategory__sort_order__gt=1)
|
||||||
|
.get()
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Through model can be used in order by queries.
|
||||||
|
```python
|
||||||
|
post = (
|
||||||
|
await Post.objects.select_related("categories")
|
||||||
|
.order_by("-postcategory__sort_order")
|
||||||
|
.get()
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also select subset of the columns in a normal `QuerySet` way with `fields`
|
||||||
|
and `exclude_fields`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
post2 = (
|
||||||
|
await Post.objects.select_related("categories")
|
||||||
|
.exclude_fields("postcategory__param_name")
|
||||||
|
.get()
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
!!!warning
|
||||||
|
Note that because through fields explicitly nullifies all relation fields, as relation
|
||||||
|
is populated in ManyToMany field, you should not use the standard model methods like
|
||||||
|
`save()` and `update()` before re-loading the field from database.
|
||||||
|
|
||||||
|
If you want to modify the through field in place remember to reload it from database.
|
||||||
|
Otherwise you will set relations to None so effectively make the field useless!
|
||||||
|
|
||||||
|
```python
|
||||||
|
# always reload the field before modification
|
||||||
|
await post2.categories[0].postcategory.load()
|
||||||
|
# only then update the field
|
||||||
|
await post2.categories[0].postcategory.update(sort_order=3)
|
||||||
|
```
|
||||||
|
Note that reloading the model effectively reloads the relations as `pk_only` models
|
||||||
|
(only primary key is set) so they are not fully populated, but it's enough to preserve
|
||||||
|
the relation on update.
|
||||||
|
|
||||||
|
!!!warning
|
||||||
|
If you use i.e. `fastapi` the partially loaded related models on through field might cause
|
||||||
|
`pydantic` validation errors (that's the primary reason why they are not populated by default).
|
||||||
|
So either you need to exclude the related fields in your response, or fully load the related
|
||||||
|
models. In example above it would mean:
|
||||||
|
```python
|
||||||
|
await post2.categories[0].postcategory.post.load()
|
||||||
|
await post2.categories[0].postcategory.category.load()
|
||||||
|
```
|
||||||
|
Alternatively you can use `load_all()`:
|
||||||
|
```python
|
||||||
|
await post2.categories[0].postcategory.load_all()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Preferred way of update is through queryset proxy `update()` method**
|
||||||
|
|
||||||
|
```python
|
||||||
|
# filter the desired related model with through field and update only through field params
|
||||||
|
await post2.categories.filter(name='Test category').update(postcategory={"sort_order": 3})
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Relation methods
|
||||||
|
|
||||||
### add
|
### add
|
||||||
|
|
||||||
|
`add(item: Model, **kwargs)`
|
||||||
|
|
||||||
|
Allows you to add model to ManyToMany relation.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Add a category to a post.
|
# Add a category to a post.
|
||||||
await post.categories.add(news)
|
await post.categories.add(news)
|
||||||
@ -30,10 +176,24 @@ await news.posts.add(post)
|
|||||||
```
|
```
|
||||||
|
|
||||||
!!!warning
|
!!!warning
|
||||||
In all not None cases the primary key value for related model **has to exist in database**.
|
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.
|
Otherwise an IntegrityError will be raised by your database driver library.
|
||||||
|
|
||||||
|
If you declare your models with a Through model with additional fields, you can populate them
|
||||||
|
during adding child model to relation.
|
||||||
|
|
||||||
|
In order to do so, pass keyword arguments with field names and values to `add()` call.
|
||||||
|
|
||||||
|
Note that this works only for `ManyToMany` relations.
|
||||||
|
|
||||||
|
```python
|
||||||
|
post = await Post(title="Test post").save()
|
||||||
|
category = await Category(name="Test category").save()
|
||||||
|
# apart from model pass arguments referencing through model fields
|
||||||
|
await post.categories.add(category, sort_order=1, param_name='test')
|
||||||
|
```
|
||||||
|
|
||||||
### remove
|
### remove
|
||||||
|
|
||||||
Removal of the related model one by one.
|
Removal of the related model one by one.
|
||||||
|
|||||||
@ -104,6 +104,29 @@ assert len(await post.categories.all()) == 2
|
|||||||
!!!tip
|
!!!tip
|
||||||
Read more in queries documentation [create][create]
|
Read more in queries documentation [create][create]
|
||||||
|
|
||||||
|
For `ManyToMany` relations there is an additional functionality of passing parameters
|
||||||
|
that will be used to create a through model if you declared additional fields on explicitly
|
||||||
|
provided Through model.
|
||||||
|
|
||||||
|
Given sample like this:
|
||||||
|
|
||||||
|
```Python hl_lines="14-20, 29"
|
||||||
|
--8<-- "../docs_src/relations/docs004.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
You can populate fields on through model in the `create()` call in a following way:
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
post = await Post(title="Test post").save()
|
||||||
|
await post.categories.create(
|
||||||
|
name="Test category1",
|
||||||
|
# in arguments pass a dictionary with name of the through field and keys
|
||||||
|
# corresponding to through model fields
|
||||||
|
postcategory={"sort_order": 1, "param_name": "volume"},
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
### get_or_create
|
### get_or_create
|
||||||
|
|
||||||
`get_or_create(**kwargs) -> Model`
|
`get_or_create(**kwargs) -> Model`
|
||||||
@ -122,6 +145,29 @@ Updates the model, or in case there is no match in database creates a new one.
|
|||||||
!!!tip
|
!!!tip
|
||||||
Read more in queries documentation [update_or_create][update_or_create]
|
Read more in queries documentation [update_or_create][update_or_create]
|
||||||
|
|
||||||
|
### update
|
||||||
|
|
||||||
|
`update(**kwargs, each:bool = False) -> int`
|
||||||
|
|
||||||
|
Updates the related model with provided keyword arguments, return number of updated rows.
|
||||||
|
|
||||||
|
!!!tip
|
||||||
|
Read more in queries documentation [update][update]
|
||||||
|
|
||||||
|
Note that for `ManyToMany` relations update can also accept an argument with through field
|
||||||
|
name and a dictionary of fields.
|
||||||
|
|
||||||
|
```Python hl_lines="14-20 29"
|
||||||
|
--8<-- "../docs_src/relations/docs004.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
In example above you can update attributes of `postcategory` in a following call:
|
||||||
|
```python
|
||||||
|
await post.categories.filter(name="Test category3").update(
|
||||||
|
postcategory={"sort_order": 4}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
## Filtering and sorting
|
## Filtering and sorting
|
||||||
|
|
||||||
### filter
|
### filter
|
||||||
@ -251,6 +297,7 @@ Returns a bool value to confirm if there are rows matching the given criteria (a
|
|||||||
[create]: ../queries/create.md#create
|
[create]: ../queries/create.md#create
|
||||||
[get_or_create]: ../queries/read.md#get_or_create
|
[get_or_create]: ../queries/read.md#get_or_create
|
||||||
[update_or_create]: ../queries/update.md#update_or_create
|
[update_or_create]: ../queries/update.md#update_or_create
|
||||||
|
[update]: ../queries/update.md#update
|
||||||
[filter]: ../queries/filter-and-sort.md#filter
|
[filter]: ../queries/filter-and-sort.md#filter
|
||||||
[exclude]: ../queries/filter-and-sort.md#exclude
|
[exclude]: ../queries/filter-and-sort.md#exclude
|
||||||
[select_related]: ../queries/joins-and-subqueries.md#select_related
|
[select_related]: ../queries/joins-and-subqueries.md#select_related
|
||||||
|
|||||||
@ -1,9 +1,19 @@
|
|||||||
# 0.9.5
|
# 0.9.6
|
||||||
|
|
||||||
|
##Important
|
||||||
|
* `Through` model for `ManyToMany` relations now **becomes optional**. It's not a breaking change
|
||||||
|
since if you provide it everything works just fine as it used to. So if you don't want or need any additional
|
||||||
|
fields on `Through` model you can skip it. Note that it's going to be created for you automatically and
|
||||||
|
still has to be included in example in `alembic` migrations.
|
||||||
|
If you want to delete existing one check the default naming convention to adjust your existing database structure.
|
||||||
|
|
||||||
|
Note that you still need to provide it if you want to
|
||||||
|
customize the `Through` model name or the database table name.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
* Add `update` method to `QuerysetProxy` so now it's possible to update related models directly from parent model
|
* Add `update` method to `QuerysetProxy` so now it's possible to update related models directly from parent model
|
||||||
in `ManyToMany` relations and in reverse `ForeignKey` relations. Note that update like in `QuerySet` `update` returns number of
|
in `ManyToMany` relations and in reverse `ForeignKey` relations. Note that update like in `QuerySet` `update` returns number of
|
||||||
updated models and **does not update related models in place** on praent model. To get the refreshed data on parent model you need to refresh
|
updated models and **does not update related models in place** on parent model. To get the refreshed data on parent model you need to refresh
|
||||||
the related models (i.e. `await model_instance.related.all()`)
|
the related models (i.e. `await model_instance.related.all()`)
|
||||||
* Added possibility to add more fields on `Through` model for `ManyToMany` relationships:
|
* Added possibility to add more fields on `Through` model for `ManyToMany` relationships:
|
||||||
* name of the through model field is the lowercase name of the Through class
|
* name of the through model field is the lowercase name of the Through class
|
||||||
@ -14,13 +24,21 @@
|
|||||||
* you can filter on through model fields
|
* you can filter on through model fields
|
||||||
* you can include and exclude fields on through models
|
* you can include and exclude fields on through models
|
||||||
* through models are attached only to related models (i.e. if you query from A to B -> only on B)
|
* through models are attached only to related models (i.e. if you query from A to B -> only on B)
|
||||||
* check the updated docs for more information
|
* note that through models are explicitly loaded without relations -> relation is already populated in ManyToMany field.
|
||||||
|
* note that just like before you cannot declare the relation fields on through model, they will be populated for you by `ormar`
|
||||||
|
* check the updated ManyToMany relation docs for more information
|
||||||
|
|
||||||
# Other
|
# Other
|
||||||
* Updated docs and api docs
|
* Updated docs and api docs
|
||||||
* Refactors and optimisations mainly related to filters and order bys
|
* Refactors and optimisations mainly related to filters, exclusions and order bys
|
||||||
|
|
||||||
|
|
||||||
|
# 0.9.5
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
* Fix creation of `pydantic` FieldInfo after update of `pydantic` to version >=1.8
|
||||||
|
* Pin required dependency versions to avoid such situations in the future
|
||||||
|
|
||||||
|
|
||||||
# 0.9.4
|
# 0.9.4
|
||||||
|
|
||||||
|
|||||||
@ -29,15 +29,6 @@ class Category(ormar.Model):
|
|||||||
name: str = ormar.String(max_length=40)
|
name: str = ormar.String(max_length=40)
|
||||||
|
|
||||||
|
|
||||||
class PostCategory(ormar.Model):
|
|
||||||
class Meta:
|
|
||||||
tablename = "posts_categories"
|
|
||||||
database = database
|
|
||||||
metadata = metadata
|
|
||||||
|
|
||||||
# If there are no additional columns id will be created automatically as Integer
|
|
||||||
|
|
||||||
|
|
||||||
class Post(ormar.Model):
|
class Post(ormar.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
tablename = "posts"
|
tablename = "posts"
|
||||||
@ -46,7 +37,5 @@ class Post(ormar.Model):
|
|||||||
|
|
||||||
id: int = ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
title: str = ormar.String(max_length=200)
|
title: str = ormar.String(max_length=200)
|
||||||
categories: Optional[Union[Category, List[Category]]] = ormar.ManyToMany(
|
categories: Optional[List[Category]] = ormar.ManyToMany(Category)
|
||||||
Category, through=PostCategory
|
|
||||||
)
|
|
||||||
author: Optional[Author] = ormar.ForeignKey(Author)
|
author: Optional[Author] = ormar.ForeignKey(Author)
|
||||||
|
|||||||
29
docs_src/relations/docs004.py
Normal file
29
docs_src/relations/docs004.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
class BaseMeta(ormar.ModelMeta):
|
||||||
|
database = database
|
||||||
|
metadata = metadata
|
||||||
|
|
||||||
|
|
||||||
|
class Category(ormar.Model):
|
||||||
|
class Meta(BaseMeta):
|
||||||
|
tablename = "categories"
|
||||||
|
|
||||||
|
id = ormar.Integer(primary_key=True)
|
||||||
|
name = ormar.String(max_length=40)
|
||||||
|
|
||||||
|
|
||||||
|
class PostCategory(ormar.Model):
|
||||||
|
class Meta(BaseMeta):
|
||||||
|
tablename = "posts_x_categories"
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
sort_order: int = ormar.Integer(nullable=True)
|
||||||
|
param_name: str = ormar.String(default="Name", max_length=200)
|
||||||
|
|
||||||
|
|
||||||
|
class Post(ormar.Model):
|
||||||
|
class Meta(BaseMeta):
|
||||||
|
pass
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
title: str = ormar.String(max_length=200)
|
||||||
|
categories = ormar.ManyToMany(Category, through=PostCategory)
|
||||||
@ -53,9 +53,11 @@ nav:
|
|||||||
- Relation Mixin: api/models/mixins/relation-mixin.md
|
- Relation Mixin: api/models/mixins/relation-mixin.md
|
||||||
- Save Prepare Mixin: api/models/mixins/save-prepare-mixin.md
|
- Save Prepare Mixin: api/models/mixins/save-prepare-mixin.md
|
||||||
- api/models/model.md
|
- api/models/model.md
|
||||||
|
- Model Row: api/models/model-row.md
|
||||||
- New BaseModel: api/models/new-basemodel.md
|
- New BaseModel: api/models/new-basemodel.md
|
||||||
- Model Table Proxy: api/models/model-table-proxy.md
|
- Model Table Proxy: api/models/model-table-proxy.md
|
||||||
- Model Metaclass: api/models/model-metaclass.md
|
- Model Metaclass: api/models/model-metaclass.md
|
||||||
|
- Excludable Items: api/models/excludable-items.md
|
||||||
- Fields:
|
- Fields:
|
||||||
- Base Field: api/fields/base-field.md
|
- Base Field: api/fields/base-field.md
|
||||||
- Model Fields: api/fields/model-fields.md
|
- Model Fields: api/fields/model-fields.md
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import sys
|
import sys
|
||||||
from typing import Any, List, Optional, TYPE_CHECKING, Tuple, Type, Union
|
from typing import Any, List, Optional, TYPE_CHECKING, Tuple, Type, Union, cast
|
||||||
|
|
||||||
from pydantic.typing import ForwardRef, evaluate_forwardref
|
from pydantic.typing import ForwardRef, evaluate_forwardref
|
||||||
import ormar # noqa: I100
|
import ormar # noqa: I100
|
||||||
@ -43,7 +43,7 @@ def populate_m2m_params_based_on_to_model(
|
|||||||
|
|
||||||
def ManyToMany(
|
def ManyToMany(
|
||||||
to: "ToType",
|
to: "ToType",
|
||||||
through: "ToType",
|
through: Optional["ToType"] = None,
|
||||||
*,
|
*,
|
||||||
name: str = None,
|
name: str = None,
|
||||||
unique: bool = False,
|
unique: bool = False,
|
||||||
@ -212,3 +212,21 @@ class ManyToManyField(ForeignKeyField, ormar.QuerySetProtocol, ormar.RelationPro
|
|||||||
:rtype: Type["Model"]
|
:rtype: Type["Model"]
|
||||||
"""
|
"""
|
||||||
return cls.through
|
return cls.through
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_default_through_model(cls) -> None:
|
||||||
|
"""
|
||||||
|
Creates default empty through model if no additional fields are required.
|
||||||
|
"""
|
||||||
|
owner_name = cls.owner.get_name(lower=False)
|
||||||
|
to_name = cls.to.get_name(lower=False)
|
||||||
|
class_name = f"{owner_name}{to_name}"
|
||||||
|
table_name = f"{owner_name.lower()}s_{to_name.lower()}s"
|
||||||
|
new_meta_namespace = {
|
||||||
|
"tablename": table_name,
|
||||||
|
"database": cls.owner.Meta.database,
|
||||||
|
"metadata": cls.owner.Meta.metadata,
|
||||||
|
}
|
||||||
|
new_meta = type("Meta", (), new_meta_namespace)
|
||||||
|
through_model = type(class_name, (ormar.Model,), {"Meta": new_meta})
|
||||||
|
cls.through = cast(Type["Model"], through_model)
|
||||||
|
|||||||
@ -154,6 +154,8 @@ def sqlalchemy_columns_from_model_fields(
|
|||||||
pkname = None
|
pkname = None
|
||||||
for field_name, field in model_fields.items():
|
for field_name, field in model_fields.items():
|
||||||
field.owner = new_model
|
field.owner = new_model
|
||||||
|
if field.is_multi and not field.through:
|
||||||
|
field.create_default_through_model()
|
||||||
if field.primary_key:
|
if field.primary_key:
|
||||||
pkname = check_pk_column_validity(field_name, field, pkname)
|
pkname = check_pk_column_validity(field_name, field, pkname)
|
||||||
if not field.pydantic_only and not field.virtual and not field.is_multi:
|
if not field.pydantic_only and not field.virtual and not field.is_multi:
|
||||||
|
|||||||
@ -209,13 +209,14 @@ def update_attrs_from_base_meta( # noqa: CCR001
|
|||||||
setattr(attrs["Meta"], param, parent_value)
|
setattr(attrs["Meta"], param, parent_value)
|
||||||
|
|
||||||
|
|
||||||
def copy_and_replace_m2m_through_model(
|
def copy_and_replace_m2m_through_model( # noqa: CFQ002
|
||||||
field: Type[ManyToManyField],
|
field: Type[ManyToManyField],
|
||||||
field_name: str,
|
field_name: str,
|
||||||
table_name: str,
|
table_name: str,
|
||||||
parent_fields: Dict,
|
parent_fields: Dict,
|
||||||
attrs: Dict,
|
attrs: Dict,
|
||||||
meta: ModelMeta,
|
meta: ModelMeta,
|
||||||
|
base_class: Type["Model"],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Clones class with Through model for m2m relations, appends child name to the name
|
Clones class with Through model for m2m relations, appends child name to the name
|
||||||
@ -229,6 +230,8 @@ def copy_and_replace_m2m_through_model(
|
|||||||
|
|
||||||
Removes the original sqlalchemy table from metadata if it was not removed.
|
Removes the original sqlalchemy table from metadata if it was not removed.
|
||||||
|
|
||||||
|
:param base_class: base class model
|
||||||
|
:type base_class: Type["Model"]
|
||||||
:param field: field with relations definition
|
:param field: field with relations definition
|
||||||
:type field: Type[ManyToManyField]
|
:type field: Type[ManyToManyField]
|
||||||
:param field_name: name of the relation field
|
:param field_name: name of the relation field
|
||||||
@ -249,6 +252,10 @@ def copy_and_replace_m2m_through_model(
|
|||||||
copy_field.related_name = related_name # type: ignore
|
copy_field.related_name = related_name # type: ignore
|
||||||
|
|
||||||
through_class = field.through
|
through_class = field.through
|
||||||
|
if not through_class:
|
||||||
|
field.owner = base_class
|
||||||
|
field.create_default_through_model()
|
||||||
|
through_class = field.through
|
||||||
new_meta: ormar.ModelMeta = type( # type: ignore
|
new_meta: ormar.ModelMeta = type( # type: ignore
|
||||||
"Meta", (), dict(through_class.Meta.__dict__),
|
"Meta", (), dict(through_class.Meta.__dict__),
|
||||||
)
|
)
|
||||||
@ -338,6 +345,7 @@ def copy_data_from_parent_model( # noqa: CCR001
|
|||||||
parent_fields=parent_fields,
|
parent_fields=parent_fields,
|
||||||
attrs=attrs,
|
attrs=attrs,
|
||||||
meta=meta,
|
meta=meta,
|
||||||
|
base_class=base_class, # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
elif field.is_relation and field.related_name:
|
elif field.is_relation and field.related_name:
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import List, Sequence, TYPE_CHECKING
|
from typing import List, TYPE_CHECKING
|
||||||
|
|
||||||
import ormar
|
import ormar
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ class MergeModelMixin:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def merge_instances_list(cls, result_rows: Sequence["Model"]) -> Sequence["Model"]:
|
def merge_instances_list(cls, result_rows: List["Model"]) -> List["Model"]:
|
||||||
"""
|
"""
|
||||||
Merges a list of models into list of unique models.
|
Merges a list of models into list of unique models.
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import inspect
|
import inspect
|
||||||
from typing import List, Optional, Set, TYPE_CHECKING
|
from typing import List, Optional, Set, TYPE_CHECKING, Type, Union
|
||||||
|
|
||||||
|
|
||||||
class RelationMixin:
|
class RelationMixin:
|
||||||
@ -8,7 +8,7 @@ class RelationMixin:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma no cover
|
if TYPE_CHECKING: # pragma no cover
|
||||||
from ormar import ModelMeta
|
from ormar import ModelMeta, Model
|
||||||
|
|
||||||
Meta: ModelMeta
|
Meta: ModelMeta
|
||||||
_related_names: Optional[Set]
|
_related_names: Optional[Set]
|
||||||
@ -63,7 +63,7 @@ class RelationMixin:
|
|||||||
return related_fields
|
return related_fields
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def extract_related_names(cls) -> Set:
|
def extract_related_names(cls) -> Set[str]:
|
||||||
"""
|
"""
|
||||||
Returns List of fields names for all relations declared on a model.
|
Returns List of fields names for all relations declared on a model.
|
||||||
List is cached in cls._related_names for quicker access.
|
List is cached in cls._related_names for quicker access.
|
||||||
@ -118,3 +118,50 @@ class RelationMixin:
|
|||||||
name for name in related_names if cls.Meta.model_fields[name].nullable
|
name for name in related_names if cls.Meta.model_fields[name].nullable
|
||||||
}
|
}
|
||||||
return related_names
|
return related_names
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _iterate_related_models(
|
||||||
|
cls,
|
||||||
|
visited: Set[Union[Type["Model"], Type["RelationMixin"]]] = None,
|
||||||
|
source_relation: str = None,
|
||||||
|
source_model: Union[Type["Model"], Type["RelationMixin"]] = None,
|
||||||
|
) -> List[str]:
|
||||||
|
"""
|
||||||
|
Iterates related models recursively to extract relation strings of
|
||||||
|
nested not visited models.
|
||||||
|
|
||||||
|
:param visited: set of already visited models
|
||||||
|
:type visited: Set[str]
|
||||||
|
:param source_relation: name of the current relation
|
||||||
|
:type source_relation: str
|
||||||
|
:param source_model: model from which relation comes in nested relations
|
||||||
|
:type source_model: Type["Model"]
|
||||||
|
:return: list of relation strings to be passed to select_related
|
||||||
|
:rtype: List[str]
|
||||||
|
"""
|
||||||
|
visited = visited or set()
|
||||||
|
visited.add(cls)
|
||||||
|
relations = cls.extract_related_names()
|
||||||
|
processed_relations = []
|
||||||
|
for relation in relations:
|
||||||
|
target_model = cls.Meta.model_fields[relation].to
|
||||||
|
if source_model and target_model == source_model:
|
||||||
|
continue
|
||||||
|
if target_model not in visited:
|
||||||
|
visited.add(target_model)
|
||||||
|
deep_relations = target_model._iterate_related_models(
|
||||||
|
visited=visited, source_relation=relation, source_model=cls
|
||||||
|
)
|
||||||
|
processed_relations.extend(deep_relations)
|
||||||
|
# TODO add test for circular deps
|
||||||
|
else: # pragma: no cover
|
||||||
|
processed_relations.append(relation)
|
||||||
|
if processed_relations:
|
||||||
|
final_relations = [
|
||||||
|
f"{source_relation + '__' if source_relation else ''}{relation}"
|
||||||
|
for relation in processed_relations
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
final_relations = [source_relation] if source_relation else []
|
||||||
|
|
||||||
|
return final_relations
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
Set,
|
Set,
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Tuple,
|
Tuple,
|
||||||
|
Union,
|
||||||
)
|
)
|
||||||
|
|
||||||
import ormar.queryset # noqa I100
|
import ormar.queryset # noqa I100
|
||||||
@ -265,3 +268,45 @@ class Model(ModelRow):
|
|||||||
self.update_from_dict(kwargs)
|
self.update_from_dict(kwargs)
|
||||||
self.set_save_status(True)
|
self.set_save_status(True)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
async def load_all(
|
||||||
|
self, follow: bool = False, exclude: Union[List, str, Set, Dict] = None
|
||||||
|
) -> "Model":
|
||||||
|
"""
|
||||||
|
Allow to refresh existing Models fields from database.
|
||||||
|
Performs refresh of the related models fields.
|
||||||
|
|
||||||
|
By default loads only self and the directly related ones.
|
||||||
|
|
||||||
|
If follow=True is set it loads also related models of related models.
|
||||||
|
|
||||||
|
To not get stuck in an infinite loop as related models also keep a relation
|
||||||
|
to parent model visited models set is kept.
|
||||||
|
|
||||||
|
That way already visited models that are nested are loaded, but the load do not
|
||||||
|
follow them inside. So Model A -> Model B -> Model C -> Model A -> Model X
|
||||||
|
will load second Model A but will never follow into Model X.
|
||||||
|
Nested relations of those kind need to be loaded manually.
|
||||||
|
|
||||||
|
:raises NoMatch: If given pk is not found in database.
|
||||||
|
|
||||||
|
:param exclude: related models to exclude
|
||||||
|
:type exclude: Union[List, str, Set, Dict]
|
||||||
|
:param follow: flag to trigger deep save -
|
||||||
|
by default only directly related models are saved
|
||||||
|
with follow=True also related models of related models are saved
|
||||||
|
:type follow: bool
|
||||||
|
:return: reloaded Model
|
||||||
|
:rtype: Model
|
||||||
|
"""
|
||||||
|
relations = list(self.extract_related_names())
|
||||||
|
if follow:
|
||||||
|
relations = self._iterate_related_models()
|
||||||
|
queryset = self.__class__.objects
|
||||||
|
print(relations)
|
||||||
|
if exclude:
|
||||||
|
queryset = queryset.exclude_fields(exclude)
|
||||||
|
instance = await queryset.select_related(relations).get(pk=self.pk)
|
||||||
|
self._orm.clear()
|
||||||
|
self.update_from_dict(instance.dict())
|
||||||
|
return self
|
||||||
|
|||||||
@ -88,6 +88,7 @@ class ModelRow(NewBaseModel):
|
|||||||
current_relation_str=current_relation_str,
|
current_relation_str=current_relation_str,
|
||||||
source_model=source_model, # type: ignore
|
source_model=source_model, # type: ignore
|
||||||
proxy_source_model=proxy_source_model, # type: ignore
|
proxy_source_model=proxy_source_model, # type: ignore
|
||||||
|
table_prefix=table_prefix,
|
||||||
)
|
)
|
||||||
item = cls.extract_prefixed_table_columns(
|
item = cls.extract_prefixed_table_columns(
|
||||||
item=item, row=row, table_prefix=table_prefix, excludable=excludable
|
item=item, row=row, table_prefix=table_prefix, excludable=excludable
|
||||||
@ -110,6 +111,7 @@ class ModelRow(NewBaseModel):
|
|||||||
source_model: Type["Model"],
|
source_model: Type["Model"],
|
||||||
related_models: Any,
|
related_models: Any,
|
||||||
excludable: ExcludableItems,
|
excludable: ExcludableItems,
|
||||||
|
table_prefix: str,
|
||||||
current_relation_str: str = None,
|
current_relation_str: str = None,
|
||||||
proxy_source_model: Type["Model"] = None,
|
proxy_source_model: Type["Model"] = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
@ -143,15 +145,20 @@ class ModelRow(NewBaseModel):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
for related in related_models:
|
for related in related_models:
|
||||||
|
field = cls.Meta.model_fields[related]
|
||||||
|
field = cast(Type["ForeignKeyField"], field)
|
||||||
|
model_cls = field.to
|
||||||
|
model_excludable = excludable.get(
|
||||||
|
model_cls=cast(Type["Model"], cls), alias=table_prefix
|
||||||
|
)
|
||||||
|
if model_excludable.is_excluded(related):
|
||||||
|
return item
|
||||||
|
|
||||||
relation_str = (
|
relation_str = (
|
||||||
"__".join([current_relation_str, related])
|
"__".join([current_relation_str, related])
|
||||||
if current_relation_str
|
if current_relation_str
|
||||||
else related
|
else related
|
||||||
)
|
)
|
||||||
field = cls.Meta.model_fields[related]
|
|
||||||
field = cast(Type["ForeignKeyField"], field)
|
|
||||||
model_cls = field.to
|
|
||||||
|
|
||||||
remainder = None
|
remainder = None
|
||||||
if isinstance(related_models, dict) and related_models[related]:
|
if isinstance(related_models, dict) and related_models[related]:
|
||||||
remainder = related_models[related]
|
remainder = related_models[related]
|
||||||
|
|||||||
@ -148,8 +148,8 @@ class QuerySet:
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def _prefetch_related_models(
|
async def _prefetch_related_models(
|
||||||
self, models: Sequence[Optional["Model"]], rows: List
|
self, models: List[Optional["Model"]], rows: List
|
||||||
) -> Sequence[Optional["Model"]]:
|
) -> List[Optional["Model"]]:
|
||||||
"""
|
"""
|
||||||
Performs prefetch query for selected models names.
|
Performs prefetch query for selected models names.
|
||||||
|
|
||||||
@ -169,7 +169,7 @@ class QuerySet:
|
|||||||
)
|
)
|
||||||
return await query.prefetch_related(models=models, rows=rows) # type: ignore
|
return await query.prefetch_related(models=models, rows=rows) # type: ignore
|
||||||
|
|
||||||
def _process_query_result_rows(self, rows: List) -> Sequence[Optional["Model"]]:
|
def _process_query_result_rows(self, rows: List) -> List[Optional["Model"]]:
|
||||||
"""
|
"""
|
||||||
Process database rows and initialize ormar Model from each of the rows.
|
Process database rows and initialize ormar Model from each of the rows.
|
||||||
|
|
||||||
|
|||||||
@ -68,6 +68,14 @@ class Relation:
|
|||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
if self._type in (RelationType.PRIMARY, RelationType.THROUGH):
|
||||||
|
self.related_models = None
|
||||||
|
self._owner.__dict__[self.field_name] = None
|
||||||
|
elif self.related_models is not None:
|
||||||
|
self.related_models._clear()
|
||||||
|
self._owner.__dict__[self.field_name] = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def through(self) -> Type["Model"]:
|
def through(self) -> Type["Model"]:
|
||||||
if not self._through: # pragma: no cover
|
if not self._through: # pragma: no cover
|
||||||
|
|||||||
@ -26,37 +26,6 @@ class RelationsManager:
|
|||||||
for field in self._related_fields:
|
for field in self._related_fields:
|
||||||
self._add_relation(field)
|
self._add_relation(field)
|
||||||
|
|
||||||
def _get_relation_type(self, field: Type["BaseField"]) -> RelationType:
|
|
||||||
"""
|
|
||||||
Returns type of the relation declared on a field.
|
|
||||||
|
|
||||||
:param field: field with relation declaration
|
|
||||||
:type field: Type[BaseField]
|
|
||||||
:return: type of the relation defined on field
|
|
||||||
:rtype: RelationType
|
|
||||||
"""
|
|
||||||
if field.is_multi:
|
|
||||||
return RelationType.MULTIPLE
|
|
||||||
if field.is_through:
|
|
||||||
return RelationType.THROUGH
|
|
||||||
return RelationType.PRIMARY if not field.virtual else RelationType.REVERSE
|
|
||||||
|
|
||||||
def _add_relation(self, field: Type["BaseField"]) -> None:
|
|
||||||
"""
|
|
||||||
Registers relation in the manager.
|
|
||||||
Adds Relation instance under field.name.
|
|
||||||
|
|
||||||
:param field: field with relation declaration
|
|
||||||
:type field: Type[BaseField]
|
|
||||||
"""
|
|
||||||
self._relations[field.name] = Relation(
|
|
||||||
manager=self,
|
|
||||||
type_=self._get_relation_type(field),
|
|
||||||
field_name=field.name,
|
|
||||||
to=field.to,
|
|
||||||
through=getattr(field, "through", None),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __contains__(self, item: str) -> bool:
|
def __contains__(self, item: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Checks if relation with given name is already registered.
|
Checks if relation with given name is already registered.
|
||||||
@ -68,6 +37,10 @@ class RelationsManager:
|
|||||||
"""
|
"""
|
||||||
return item in self._related_names
|
return item in self._related_names
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
for relation in self._relations.values():
|
||||||
|
relation.clear()
|
||||||
|
|
||||||
def get(self, name: str) -> Optional[Union["Model", Sequence["Model"]]]:
|
def get(self, name: str) -> Optional[Union["Model", Sequence["Model"]]]:
|
||||||
"""
|
"""
|
||||||
Returns the related model/models if relation is set.
|
Returns the related model/models if relation is set.
|
||||||
@ -83,20 +56,6 @@ class RelationsManager:
|
|||||||
return relation.get()
|
return relation.get()
|
||||||
return None # pragma nocover
|
return None # pragma nocover
|
||||||
|
|
||||||
def _get(self, name: str) -> Optional[Relation]:
|
|
||||||
"""
|
|
||||||
Returns the actual relation and not the related model(s).
|
|
||||||
|
|
||||||
:param name: name of the relation
|
|
||||||
:type name: str
|
|
||||||
:return: Relation instance
|
|
||||||
:rtype: ormar.relations.relation.Relation
|
|
||||||
"""
|
|
||||||
relation = self._relations.get(name, None)
|
|
||||||
if relation is not None:
|
|
||||||
return relation
|
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add(parent: "Model", child: "Model", field: Type["ForeignKeyField"],) -> None:
|
def add(parent: "Model", child: "Model", field: Type["ForeignKeyField"],) -> None:
|
||||||
"""
|
"""
|
||||||
@ -164,3 +123,48 @@ class RelationsManager:
|
|||||||
relation_name = item.Meta.model_fields[name].get_related_name()
|
relation_name = item.Meta.model_fields[name].get_related_name()
|
||||||
item._orm.remove(name, parent)
|
item._orm.remove(name, parent)
|
||||||
parent._orm.remove(relation_name, item)
|
parent._orm.remove(relation_name, item)
|
||||||
|
|
||||||
|
def _get(self, name: str) -> Optional[Relation]:
|
||||||
|
"""
|
||||||
|
Returns the actual relation and not the related model(s).
|
||||||
|
|
||||||
|
:param name: name of the relation
|
||||||
|
:type name: str
|
||||||
|
:return: Relation instance
|
||||||
|
:rtype: ormar.relations.relation.Relation
|
||||||
|
"""
|
||||||
|
relation = self._relations.get(name, None)
|
||||||
|
if relation is not None:
|
||||||
|
return relation
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_relation_type(self, field: Type["BaseField"]) -> RelationType:
|
||||||
|
"""
|
||||||
|
Returns type of the relation declared on a field.
|
||||||
|
|
||||||
|
:param field: field with relation declaration
|
||||||
|
:type field: Type[BaseField]
|
||||||
|
:return: type of the relation defined on field
|
||||||
|
:rtype: RelationType
|
||||||
|
"""
|
||||||
|
if field.is_multi:
|
||||||
|
return RelationType.MULTIPLE
|
||||||
|
if field.is_through:
|
||||||
|
return RelationType.THROUGH
|
||||||
|
return RelationType.PRIMARY if not field.virtual else RelationType.REVERSE
|
||||||
|
|
||||||
|
def _add_relation(self, field: Type["BaseField"]) -> None:
|
||||||
|
"""
|
||||||
|
Registers relation in the manager.
|
||||||
|
Adds Relation instance under field.name.
|
||||||
|
|
||||||
|
:param field: field with relation declaration
|
||||||
|
:type field: Type[BaseField]
|
||||||
|
"""
|
||||||
|
self._relations[field.name] = Relation(
|
||||||
|
manager=self,
|
||||||
|
type_=self._get_relation_type(field),
|
||||||
|
field_name=field.name,
|
||||||
|
to=field.to,
|
||||||
|
through=getattr(field, "through", None),
|
||||||
|
)
|
||||||
|
|||||||
@ -75,6 +75,9 @@ class RelationProxy(list):
|
|||||||
self._initialize_queryset()
|
self._initialize_queryset()
|
||||||
return getattr(self.queryset_proxy, item)
|
return getattr(self.queryset_proxy, item)
|
||||||
|
|
||||||
|
def _clear(self) -> None:
|
||||||
|
super().clear()
|
||||||
|
|
||||||
def _initialize_queryset(self) -> None:
|
def _initialize_queryset(self) -> None:
|
||||||
"""
|
"""
|
||||||
Initializes the QuerySetProxy if not yet initialized.
|
Initializes the QuerySetProxy if not yet initialized.
|
||||||
|
|||||||
@ -21,9 +21,15 @@ renderer:
|
|||||||
- title: Model
|
- title: Model
|
||||||
contents:
|
contents:
|
||||||
- models.model.*
|
- models.model.*
|
||||||
|
- title: Model Row
|
||||||
|
contents:
|
||||||
|
- models.model_row.*
|
||||||
- title: New BaseModel
|
- title: New BaseModel
|
||||||
contents:
|
contents:
|
||||||
- models.newbasemodel.*
|
- models.newbasemodel.*
|
||||||
|
- title: Excludable Items
|
||||||
|
contents:
|
||||||
|
- models.excludable.*
|
||||||
- title: Model Table Proxy
|
- title: Model Table Proxy
|
||||||
contents:
|
contents:
|
||||||
- models.modelproxy.*
|
- models.modelproxy.*
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Optional, Union, List
|
from typing import List, Optional
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import pytest
|
import pytest
|
||||||
@ -23,13 +23,6 @@ class Child(ormar.Model):
|
|||||||
born_year: int = ormar.Integer(name="year_born", nullable=True)
|
born_year: int = ormar.Integer(name="year_born", nullable=True)
|
||||||
|
|
||||||
|
|
||||||
class ArtistChildren(ormar.Model):
|
|
||||||
class Meta:
|
|
||||||
tablename = "children_x_artists"
|
|
||||||
metadata = metadata
|
|
||||||
database = database
|
|
||||||
|
|
||||||
|
|
||||||
class Artist(ormar.Model):
|
class Artist(ormar.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
tablename = "artists"
|
tablename = "artists"
|
||||||
@ -40,9 +33,7 @@ class Artist(ormar.Model):
|
|||||||
first_name: str = ormar.String(name="fname", max_length=100)
|
first_name: str = ormar.String(name="fname", max_length=100)
|
||||||
last_name: str = ormar.String(name="lname", max_length=100)
|
last_name: str = ormar.String(name="lname", max_length=100)
|
||||||
born_year: int = ormar.Integer(name="year")
|
born_year: int = ormar.Integer(name="year")
|
||||||
children: Optional[Union[Child, List[Child]]] = ormar.ManyToMany(
|
children: Optional[List[Child]] = ormar.ManyToMany(Child)
|
||||||
Child, through=ArtistChildren
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Album(ormar.Model):
|
class Album(ormar.Model):
|
||||||
|
|||||||
@ -42,18 +42,13 @@ class Category(ormar.Model):
|
|||||||
name: str = ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class ItemsXCategories(ormar.Model):
|
|
||||||
class Meta(LocalMeta):
|
|
||||||
tablename = "items_x_categories"
|
|
||||||
|
|
||||||
|
|
||||||
class Item(ormar.Model):
|
class Item(ormar.Model):
|
||||||
class Meta(LocalMeta):
|
class Meta(LocalMeta):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
id: int = ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: str = ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
categories = ormar.ManyToMany(Category, through=ItemsXCategories)
|
categories = ormar.ManyToMany(Category)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True, scope="module")
|
@pytest.fixture(autouse=True, scope="module")
|
||||||
|
|||||||
@ -121,11 +121,11 @@ class Bus(Car):
|
|||||||
max_persons: int = ormar.Integer()
|
max_persons: int = ormar.Integer()
|
||||||
|
|
||||||
|
|
||||||
class PersonsCar(ormar.Model):
|
# class PersonsCar(ormar.Model):
|
||||||
class Meta:
|
# class Meta:
|
||||||
tablename = "cars_x_persons"
|
# tablename = "cars_x_persons"
|
||||||
metadata = metadata
|
# metadata = metadata
|
||||||
database = db
|
# database = db
|
||||||
|
|
||||||
|
|
||||||
class Car2(ormar.Model):
|
class Car2(ormar.Model):
|
||||||
@ -138,7 +138,9 @@ class Car2(ormar.Model):
|
|||||||
name: str = ormar.String(max_length=50)
|
name: str = ormar.String(max_length=50)
|
||||||
owner: Person = ormar.ForeignKey(Person, related_name="owned")
|
owner: Person = ormar.ForeignKey(Person, related_name="owned")
|
||||||
co_owners: List[Person] = ormar.ManyToMany(
|
co_owners: List[Person] = ormar.ManyToMany(
|
||||||
Person, through=PersonsCar, related_name="coowned"
|
Person,
|
||||||
|
# through=PersonsCar,
|
||||||
|
related_name="coowned",
|
||||||
)
|
)
|
||||||
created_date: datetime.datetime = ormar.DateTime(default=datetime.datetime.now)
|
created_date: datetime.datetime = ormar.DateTime(default=datetime.datetime.now)
|
||||||
|
|
||||||
|
|||||||
171
tests/test_load_all.py
Normal file
171
tests/test_load_all.py
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
import databases
|
||||||
|
import pytest
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
import ormar
|
||||||
|
from tests.settings import DATABASE_URL
|
||||||
|
|
||||||
|
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
class BaseMeta(ormar.ModelMeta):
|
||||||
|
database = database
|
||||||
|
metadata = metadata
|
||||||
|
|
||||||
|
|
||||||
|
class Language(ormar.Model):
|
||||||
|
class Meta(BaseMeta):
|
||||||
|
tablename = "languages"
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
name: str = ormar.String(max_length=100)
|
||||||
|
level: str = ormar.String(max_length=150, default="Beginner")
|
||||||
|
|
||||||
|
|
||||||
|
class CringeLevel(ormar.Model):
|
||||||
|
class Meta(BaseMeta):
|
||||||
|
tablename = "levels"
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
name: str = ormar.String(max_length=100)
|
||||||
|
language = ormar.ForeignKey(Language)
|
||||||
|
|
||||||
|
|
||||||
|
class NickName(ormar.Model):
|
||||||
|
class Meta(BaseMeta):
|
||||||
|
tablename = "nicks"
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
name: str = ormar.String(max_length=100, nullable=False, name="hq_name")
|
||||||
|
is_lame: bool = ormar.Boolean(nullable=True)
|
||||||
|
level: CringeLevel = ormar.ForeignKey(CringeLevel)
|
||||||
|
|
||||||
|
|
||||||
|
class HQ(ormar.Model):
|
||||||
|
class Meta(BaseMeta):
|
||||||
|
tablename = "hqs"
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
name: str = ormar.String(max_length=100, nullable=False, name="hq_name")
|
||||||
|
nicks: List[NickName] = ormar.ManyToMany(NickName)
|
||||||
|
|
||||||
|
|
||||||
|
class Company(ormar.Model):
|
||||||
|
class Meta(BaseMeta):
|
||||||
|
tablename = "companies"
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
name: str = ormar.String(max_length=100, nullable=False, name="company_name")
|
||||||
|
founded: int = ormar.Integer(nullable=True)
|
||||||
|
hq: HQ = ormar.ForeignKey(HQ, related_name="companies")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True, scope="module")
|
||||||
|
def create_test_database():
|
||||||
|
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||||
|
metadata.drop_all(engine)
|
||||||
|
metadata.create_all(engine)
|
||||||
|
yield
|
||||||
|
metadata.drop_all(engine)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_load_all_fk_rel():
|
||||||
|
async with database:
|
||||||
|
async with database.transaction(force_rollback=True):
|
||||||
|
hq = await HQ.objects.create(name="Main")
|
||||||
|
company = await Company.objects.create(name="Banzai", founded=1988, hq=hq)
|
||||||
|
|
||||||
|
hq = await HQ.objects.get(name="Main")
|
||||||
|
await hq.load_all()
|
||||||
|
|
||||||
|
assert hq.companies[0] == company
|
||||||
|
assert hq.companies[0].name == "Banzai"
|
||||||
|
assert hq.companies[0].founded == 1988
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_load_all_many_to_many():
|
||||||
|
async with database:
|
||||||
|
async with database.transaction(force_rollback=True):
|
||||||
|
nick1 = await NickName.objects.create(name="BazingaO", is_lame=False)
|
||||||
|
nick2 = await NickName.objects.create(name="Bazinga20", is_lame=True)
|
||||||
|
hq = await HQ.objects.create(name="Main")
|
||||||
|
await hq.nicks.add(nick1)
|
||||||
|
await hq.nicks.add(nick2)
|
||||||
|
|
||||||
|
hq = await HQ.objects.get(name="Main")
|
||||||
|
await hq.load_all()
|
||||||
|
|
||||||
|
assert hq.nicks[0] == nick1
|
||||||
|
assert hq.nicks[0].name == "BazingaO"
|
||||||
|
|
||||||
|
assert hq.nicks[1] == nick2
|
||||||
|
assert hq.nicks[1].name == "Bazinga20"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_loading_reversed_relation():
|
||||||
|
async with database:
|
||||||
|
async with database.transaction(force_rollback=True):
|
||||||
|
hq = await HQ.objects.create(name="Main")
|
||||||
|
await Company.objects.create(name="Banzai", founded=1988, hq=hq)
|
||||||
|
|
||||||
|
company = await Company.objects.get(name="Banzai")
|
||||||
|
await company.load_all()
|
||||||
|
|
||||||
|
assert company.hq == hq
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_loading_nested():
|
||||||
|
async with database:
|
||||||
|
async with database.transaction(force_rollback=True):
|
||||||
|
language = await Language.objects.create(name="English")
|
||||||
|
level = await CringeLevel.objects.create(name="High", language=language)
|
||||||
|
level2 = await CringeLevel.objects.create(name="Low", language=language)
|
||||||
|
nick1 = await NickName.objects.create(
|
||||||
|
name="BazingaO", is_lame=False, level=level
|
||||||
|
)
|
||||||
|
nick2 = await NickName.objects.create(
|
||||||
|
name="Bazinga20", is_lame=True, level=level2
|
||||||
|
)
|
||||||
|
hq = await HQ.objects.create(name="Main")
|
||||||
|
await hq.nicks.add(nick1)
|
||||||
|
await hq.nicks.add(nick2)
|
||||||
|
|
||||||
|
hq = await HQ.objects.get(name="Main")
|
||||||
|
await hq.load_all(follow=True)
|
||||||
|
|
||||||
|
assert hq.nicks[0] == nick1
|
||||||
|
assert hq.nicks[0].name == "BazingaO"
|
||||||
|
assert hq.nicks[0].level.name == "High"
|
||||||
|
assert hq.nicks[0].level.language.name == "English"
|
||||||
|
|
||||||
|
assert hq.nicks[1] == nick2
|
||||||
|
assert hq.nicks[1].name == "Bazinga20"
|
||||||
|
assert hq.nicks[1].level.name == "Low"
|
||||||
|
assert hq.nicks[1].level.language.name == "English"
|
||||||
|
|
||||||
|
await hq.load_all(follow=True, exclude="nicks__level__language")
|
||||||
|
assert len(hq.nicks) == 2
|
||||||
|
assert hq.nicks[0].level.language is None
|
||||||
|
assert hq.nicks[1].level.language is None
|
||||||
|
|
||||||
|
await hq.load_all(follow=True, exclude="nicks__level__language__level")
|
||||||
|
assert len(hq.nicks) == 2
|
||||||
|
assert hq.nicks[0].level.language is not None
|
||||||
|
assert hq.nicks[0].level.language.level is None
|
||||||
|
assert hq.nicks[1].level.language is not None
|
||||||
|
assert hq.nicks[1].level.language.level is None
|
||||||
|
|
||||||
|
await hq.load_all(follow=True, exclude="nicks__level")
|
||||||
|
assert len(hq.nicks) == 2
|
||||||
|
assert hq.nicks[0].level is None
|
||||||
|
assert hq.nicks[1].level is None
|
||||||
|
|
||||||
|
await hq.load_all(follow=True, exclude="nicks")
|
||||||
|
assert len(hq.nicks) == 0
|
||||||
@ -288,6 +288,25 @@ async def test_update_through_models_from_queryset_on_through() -> Any:
|
|||||||
assert post2.categories[2].postcategory.param_name == "area"
|
assert post2.categories[2].postcategory.param_name == "area"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_update_through_model_after_load() -> Any:
|
||||||
|
async with database:
|
||||||
|
post = await Post(title="Test post").save()
|
||||||
|
await post.categories.create(
|
||||||
|
name="Test category1",
|
||||||
|
postcategory={"sort_order": 2, "param_name": "volume"},
|
||||||
|
)
|
||||||
|
post2 = await Post.objects.select_related("categories").get()
|
||||||
|
assert len(post2.categories) == 1
|
||||||
|
|
||||||
|
await post2.categories[0].postcategory.load()
|
||||||
|
await post2.categories[0].postcategory.update(sort_order=3)
|
||||||
|
|
||||||
|
post3 = await Post.objects.select_related("categories").get()
|
||||||
|
assert len(post3.categories) == 1
|
||||||
|
assert post3.categories[0].postcategory.sort_order == 3
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_update_through_from_related() -> Any:
|
async def test_update_through_from_related() -> Any:
|
||||||
async with database:
|
async with database:
|
||||||
@ -371,9 +390,10 @@ async def test_excluding_fields_on_through_model() -> Any:
|
|||||||
# ordering by in order_by (V)
|
# ordering by in order_by (V)
|
||||||
# updating in query (V)
|
# updating in query (V)
|
||||||
# updating from querysetproxy (V)
|
# updating from querysetproxy (V)
|
||||||
# including/excluding in fields?
|
# including/excluding in fields? (V)
|
||||||
|
# make through optional? auto-generated for cases other fields are missing? (V)
|
||||||
|
|
||||||
# modifying from instance (both sides?) (X) <= no, the loaded one doesn't have relations
|
# modifying from instance (both sides?) (X) <= no, the loaded one doesn't have relations
|
||||||
|
# allowing to change fk fields names in through model? (X) <= separate issue
|
||||||
|
|
||||||
# allowing to change fk fields names in through model?
|
# prevent adding relation on through field definition
|
||||||
# make through optional? auto-generated for cases other fields are missing?
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing import List, Union, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import pytest
|
import pytest
|
||||||
@ -34,13 +34,6 @@ class Category(ormar.Model):
|
|||||||
name: str = ormar.String(max_length=40)
|
name: str = ormar.String(max_length=40)
|
||||||
|
|
||||||
|
|
||||||
class PostCategory(ormar.Model):
|
|
||||||
class Meta:
|
|
||||||
tablename = "posts_categories"
|
|
||||||
database = database
|
|
||||||
metadata = metadata
|
|
||||||
|
|
||||||
|
|
||||||
class Post(ormar.Model):
|
class Post(ormar.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
tablename = "posts"
|
tablename = "posts"
|
||||||
@ -49,9 +42,7 @@ class Post(ormar.Model):
|
|||||||
|
|
||||||
id: int = ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
title: str = ormar.String(max_length=200)
|
title: str = ormar.String(max_length=200)
|
||||||
categories: Optional[Union[Category, List[Category]]] = ormar.ManyToMany(
|
categories: Optional[List[Category]] = ormar.ManyToMany(Category)
|
||||||
Category, through=PostCategory
|
|
||||||
)
|
|
||||||
author: Optional[Author] = ormar.ForeignKey(Author)
|
author: Optional[Author] = ormar.ForeignKey(Author)
|
||||||
|
|
||||||
|
|
||||||
@ -74,6 +65,7 @@ async def create_test_database():
|
|||||||
async def cleanup():
|
async def cleanup():
|
||||||
yield
|
yield
|
||||||
async with database:
|
async with database:
|
||||||
|
PostCategory = Post.Meta.model_fields["categories"].through
|
||||||
await PostCategory.objects.delete(each=True)
|
await PostCategory.objects.delete(each=True)
|
||||||
await Post.objects.delete(each=True)
|
await Post.objects.delete(each=True)
|
||||||
await Category.objects.delete(each=True)
|
await Category.objects.delete(each=True)
|
||||||
|
|||||||
@ -85,13 +85,6 @@ class Car(ormar.Model):
|
|||||||
factory: Optional[Factory] = ormar.ForeignKey(Factory)
|
factory: Optional[Factory] = ormar.ForeignKey(Factory)
|
||||||
|
|
||||||
|
|
||||||
class UsersCar(ormar.Model):
|
|
||||||
class Meta:
|
|
||||||
tablename = "cars_x_users"
|
|
||||||
metadata = metadata
|
|
||||||
database = database
|
|
||||||
|
|
||||||
|
|
||||||
class User(ormar.Model):
|
class User(ormar.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
tablename = "users"
|
tablename = "users"
|
||||||
@ -100,7 +93,7 @@ class User(ormar.Model):
|
|||||||
|
|
||||||
id: int = ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: str = ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
cars: List[Car] = ormar.ManyToMany(Car, through=UsersCar)
|
cars: List[Car] = ormar.ManyToMany(Car)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True, scope="module")
|
@pytest.fixture(autouse=True, scope="module")
|
||||||
|
|||||||
Reference in New Issue
Block a user