diff --git a/.gitignore b/.gitignore
index 2b9985d..26114bb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
p38venv
alembic
alembic.ini
+build
.idea
.pytest_cache
.mypy_cache
diff --git a/docs/api/fields/base-field.md b/docs/api/fields/base-field.md
index 82b99d3..b30d498 100644
--- a/docs/api/fields/base-field.md
+++ b/docs/api/fields/base-field.md
@@ -20,8 +20,7 @@ to pydantic field types like ConstrainedStr
#### is\_valid\_uni\_relation
```python
- | @classmethod
- | is_valid_uni_relation(cls) -> bool
+ | is_valid_uni_relation() -> bool
```
Checks if field is a relation definition but only for ForeignKey relation,
@@ -40,8 +39,7 @@ Model columns only.
#### get\_alias
```python
- | @classmethod
- | get_alias(cls) -> str
+ | get_alias() -> str
```
Used to translate Model column names to database column names during db queries.
@@ -51,75 +49,26 @@ Used to translate Model column names to database column names during db queries.
`(str)`: returns custom database column name if defined by user,
otherwise field name in ormar/pydantic
-
-#### is\_valid\_field\_info\_field
+
+#### get\_pydantic\_default
```python
- | @classmethod
- | is_valid_field_info_field(cls, field_name: str) -> bool
-```
-
-Checks if field belongs to pydantic FieldInfo
-- used during setting default pydantic values.
-Excludes defaults and alias as they are populated separately
-(defaults) or not at all (alias)
-
-**Arguments**:
-
-- `field_name (str)`: field name of BaseFIeld
-
-**Returns**:
-
-`(bool)`: True if field is present on pydantic.FieldInfo
-
-
-#### get\_base\_pydantic\_field\_info
-
-```python
- | @classmethod
- | get_base_pydantic_field_info(cls, allow_null: bool) -> FieldInfo
+ | get_pydantic_default() -> Dict
```
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
-
-#### convert\_to\_pydantic\_field\_info
-
-```python
- | @classmethod
- | convert_to_pydantic_field_info(cls, allow_null: bool = False) -> FieldInfo
-```
-
-Converts a BaseField into pydantic.FieldInfo
-that is later easily processed by pydantic.
-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)`: actual instance of pydantic.FieldInfo with all needed fields populated
-
#### default\_value
```python
- | @classmethod
- | default_value(cls, use_server: bool = False) -> Optional[FieldInfo]
+ | default_value(use_server: bool = False) -> Optional[Dict]
```
Returns a FieldInfo instance with populated default
@@ -145,8 +94,7 @@ which is returning a FieldInfo instance
#### get\_default
```python
- | @classmethod
- | get_default(cls, use_server: bool = False) -> Any
+ | get_default(use_server: bool = False) -> Any
```
Return default value for a field.
@@ -166,8 +114,7 @@ treated as default value, default False
#### has\_default
```python
- | @classmethod
- | has_default(cls, use_server: bool = True) -> bool
+ | has_default(use_server: bool = True) -> bool
```
Checks if the field has default value set.
@@ -185,8 +132,7 @@ treated as default value, default False
#### is\_auto\_primary\_key
```python
- | @classmethod
- | is_auto_primary_key(cls) -> bool
+ | is_auto_primary_key() -> bool
```
Checks if field is first a primary key and if it,
@@ -201,8 +147,7 @@ Autoincrement primary_key is nullable/optional.
#### construct\_constraints
```python
- | @classmethod
- | construct_constraints(cls) -> List
+ | construct_constraints() -> List
```
Converts list of ormar constraints into sqlalchemy ForeignKeys.
@@ -217,8 +162,7 @@ And we need a new ForeignKey for subclasses of current model
#### get\_column
```python
- | @classmethod
- | get_column(cls, name: str) -> sqlalchemy.Column
+ | get_column(name: str) -> sqlalchemy.Column
```
Returns definition of sqlalchemy.Column used in creation of sqlalchemy.Table.
@@ -233,12 +177,28 @@ primary_key, index, unique, nullable, default and server_default.
`(sqlalchemy.Column)`: actual definition of the database column as sqlalchemy requires.
+
+#### \_get\_encrypted\_column
+
+```python
+ | _get_encrypted_column(name: str) -> sqlalchemy.Column
+```
+
+Returns EncryptedString column type instead of actual column.
+
+**Arguments**:
+
+- `name (str)`: column name
+
+**Returns**:
+
+`(sqlalchemy.Column)`: newly defined column
+
#### expand\_relationship
```python
- | @classmethod
- | expand_relationship(cls, value: Any, child: Union["Model", "NewBaseModel"], to_register: bool = True) -> Any
+ | expand_relationship(value: Any, child: Union["Model", "NewBaseModel"], to_register: bool = True) -> Any
```
Function overwritten for relations, in basic field the value is returned as is.
@@ -261,8 +221,7 @@ dict (from Model) or actual instance/list of a "Model".
#### set\_self\_reference\_flag
```python
- | @classmethod
- | set_self_reference_flag(cls) -> None
+ | set_self_reference_flag() -> None
```
Sets `self_reference` to True if field to and owner are same model.
@@ -275,8 +234,7 @@ Sets `self_reference` to True if field to and owner are same model.
#### has\_unresolved\_forward\_refs
```python
- | @classmethod
- | has_unresolved_forward_refs(cls) -> bool
+ | has_unresolved_forward_refs() -> bool
```
Verifies if the filed has any ForwardRefs that require updating before the
@@ -290,8 +248,7 @@ model can be used.
#### evaluate\_forward\_ref
```python
- | @classmethod
- | evaluate_forward_ref(cls, globalns: Any, localns: Any) -> None
+ | evaluate_forward_ref(globalns: Any, localns: Any) -> None
```
Evaluates the ForwardRef to actual Field based on global and local namespaces
@@ -309,8 +266,7 @@ Evaluates the ForwardRef to actual Field based on global and local namespaces
#### get\_related\_name
```python
- | @classmethod
- | get_related_name(cls) -> str
+ | get_related_name() -> str
```
Returns name to use for reverse relation.
diff --git a/docs/api/fields/foreign-key.md b/docs/api/fields/foreign-key.md
index 055b661..c8ae997 100644
--- a/docs/api/fields/foreign-key.md
+++ b/docs/api/fields/foreign-key.md
@@ -5,7 +5,7 @@
#### create\_dummy\_instance
```python
-create_dummy_instance(fk: Type["Model"], pk: Any = None) -> "Model"
+create_dummy_instance(fk: Type["T"], pk: Any = None) -> "T"
```
Ormar never returns you a raw data.
@@ -31,7 +31,7 @@ If the nested related Models are required they are set with -1 as pk value.
#### create\_dummy\_model
```python
-create_dummy_model(base_model: Type["Model"], pk_field: Type[Union[BaseField, "ForeignKeyField", "ManyToManyField"]]) -> Type["BaseModel"]
+create_dummy_model(base_model: Type["T"], pk_field: Union[BaseField, "ForeignKeyField", "ManyToManyField"]) -> Type["BaseModel"]
```
Used to construct a dummy pydantic model for type hints and pydantic validation.
@@ -40,7 +40,7 @@ Populates only pk field and set it to desired type.
**Arguments**:
- `base_model (Model class)`: class of target dummy model
-- `pk_field (Type[Union[BaseField, "ForeignKeyField", "ManyToManyField"]])`: ormar Field to be set on pydantic Model
+- `pk_field (Union[BaseField, "ForeignKeyField", "ManyToManyField"])`: ormar Field to be set on pydantic Model
**Returns**:
@@ -50,7 +50,7 @@ Populates only pk field and set it to desired type.
#### populate\_fk\_params\_based\_on\_to\_model
```python
-populate_fk_params_based_on_to_model(to: Type["Model"], nullable: bool, onupdate: str = None, ondelete: str = None) -> Tuple[Any, List, Any]
+populate_fk_params_based_on_to_model(to: Type["T"], nullable: bool, onupdate: str = None, ondelete: str = None) -> Tuple[Any, List, Any]
```
Based on target to model to which relation leads to populates the type of the
@@ -69,6 +69,25 @@ How to treat child rows on delete of parent (the one where FK is defined) model.
`(Tuple[Any, List, Any])`: tuple with target pydantic type, list of fk constraints and target col type
+
+#### validate\_not\_allowed\_fields
+
+```python
+validate_not_allowed_fields(kwargs: Dict) -> None
+```
+
+Verifies if not allowed parameters are set on relation models.
+Usually they are omitted later anyway but this way it's explicitly
+notify the user that it's not allowed/ supported.
+
+**Raises**:
+
+- `ModelDefinitionError`: if any forbidden field is set
+
+**Arguments**:
+
+- `kwargs (Dict)`: dict of kwargs to verify passed to relation field
+
## UniqueColumns Objects
@@ -94,7 +113,7 @@ to produce sqlalchemy.ForeignKeys
#### ForeignKey
```python
-ForeignKey(to: "ToType", *, name: str = None, unique: bool = False, nullable: bool = True, related_name: str = None, virtual: bool = False, onupdate: str = None, ondelete: str = None, **kwargs: Any, ,) -> Any
+ForeignKey(to: "ToType", *, name: str = None, unique: bool = False, nullable: bool = True, related_name: str = None, virtual: bool = False, onupdate: str = None, ondelete: str = None, **kwargs: Any, ,) -> "T"
```
Despite a name it's a function that returns constructed ForeignKeyField.
@@ -134,8 +153,7 @@ Actual class returned from ForeignKey function call and stored in model_fields.
#### get\_source\_related\_name
```python
- | @classmethod
- | get_source_related_name(cls) -> str
+ | get_source_related_name() -> str
```
Returns name to use for source relation name.
@@ -150,8 +168,7 @@ It's either set as `related_name` or by default it's owner model. get_name + 's'
#### get\_related\_name
```python
- | @classmethod
- | get_related_name(cls) -> str
+ | get_related_name() -> str
```
Returns name to use for reverse relation.
@@ -161,12 +178,37 @@ It's either set as `related_name` or by default it's owner model. get_name + 's'
`(str)`: name of the related_name or default related name.
+
+#### default\_target\_field\_name
+
+```python
+ | default_target_field_name() -> str
+```
+
+Returns default target model name on through model.
+
+**Returns**:
+
+`(str)`: name of the field
+
+
+#### default\_source\_field\_name
+
+```python
+ | default_source_field_name() -> str
+```
+
+Returns default target model name on through model.
+
+**Returns**:
+
+`(str)`: name of the field
+
#### evaluate\_forward\_ref
```python
- | @classmethod
- | evaluate_forward_ref(cls, globalns: Any, localns: Any) -> None
+ | evaluate_forward_ref(globalns: Any, localns: Any) -> None
```
Evaluates the ForwardRef to actual Field based on global and local namespaces
@@ -184,8 +226,7 @@ Evaluates the ForwardRef to actual Field based on global and local namespaces
#### \_extract\_model\_from\_sequence
```python
- | @classmethod
- | _extract_model_from_sequence(cls, value: List, child: "Model", to_register: bool) -> List["Model"]
+ | _extract_model_from_sequence(value: List, child: "Model", to_register: bool) -> List["Model"]
```
Takes a list of Models and registers them on parent.
@@ -207,8 +248,7 @@ Used in reverse FK relations.
#### \_register\_existing\_model
```python
- | @classmethod
- | _register_existing_model(cls, value: "Model", child: "Model", to_register: bool) -> "Model"
+ | _register_existing_model(value: "Model", child: "Model", to_register: bool) -> "Model"
```
Takes already created instance and registers it for parent.
@@ -230,8 +270,7 @@ Used in reverse FK relations and normal FK for single models.
#### \_construct\_model\_from\_dict
```python
- | @classmethod
- | _construct_model_from_dict(cls, value: dict, child: "Model", to_register: bool) -> "Model"
+ | _construct_model_from_dict(value: dict, child: "Model", to_register: bool) -> "Model"
```
Takes a dictionary, creates a instance and registers it for parent.
@@ -254,8 +293,7 @@ Used in normal FK for dictionaries.
#### \_construct\_model\_from\_pk
```python
- | @classmethod
- | _construct_model_from_pk(cls, value: Any, child: "Model", to_register: bool) -> "Model"
+ | _construct_model_from_pk(value: Any, child: "Model", to_register: bool) -> "Model"
```
Takes a pk value, creates a dummy instance and registers it for parent.
@@ -277,8 +315,7 @@ Used in normal FK for dictionaries.
#### register\_relation
```python
- | @classmethod
- | register_relation(cls, model: "Model", child: "Model") -> None
+ | register_relation(model: "Model", child: "Model") -> None
```
Registers relation between parent and child in relation manager.
@@ -296,8 +333,7 @@ Used in Metaclass and sometimes some relations are missing
#### has\_unresolved\_forward\_refs
```python
- | @classmethod
- | has_unresolved_forward_refs(cls) -> bool
+ | has_unresolved_forward_refs() -> bool
```
Verifies if the filed has any ForwardRefs that require updating before the
@@ -311,8 +347,7 @@ model can be used.
#### expand\_relationship
```python
- | @classmethod
- | expand_relationship(cls, value: Any, child: Union["Model", "NewBaseModel"], to_register: bool = True) -> Optional[Union["Model", List["Model"]]]
+ | expand_relationship(value: Any, child: Union["Model", "NewBaseModel"], to_register: bool = True) -> Optional[Union["Model", List["Model"]]]
```
For relations the child model is first constructed (if needed),
@@ -336,8 +371,7 @@ Selects the appropriate constructor based on a passed value.
#### get\_relation\_name
```python
- | @classmethod
- | get_relation_name(cls) -> str
+ | get_relation_name() -> str
```
Returns name of the relation, which can be a own name or through model
@@ -351,8 +385,7 @@ names for m2m models
#### get\_source\_model
```python
- | @classmethod
- | get_source_model(cls) -> Type["Model"]
+ | get_source_model() -> Type["Model"]
```
Returns model from which the relation comes -> either owner or through model
diff --git a/docs/api/fields/many-to-many.md b/docs/api/fields/many-to-many.md
index 89570aa..de80987 100644
--- a/docs/api/fields/many-to-many.md
+++ b/docs/api/fields/many-to-many.md
@@ -1,6 +1,19 @@
# fields.many\_to\_many
+
+#### forbid\_through\_relations
+
+```python
+forbid_through_relations(through: Type["Model"]) -> None
+```
+
+Verifies if the through model does not have relations.
+
+**Arguments**:
+
+- `through (Type['Model])`: through Model to be checked
+
#### populate\_m2m\_params\_based\_on\_to\_model
@@ -24,7 +37,7 @@ pydantic field to use and type of the target column field.
#### ManyToMany
```python
-ManyToMany(to: "ToType", through: Optional["ToType"] = None, *, 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, ,) -> "RelationProxy[T]"
```
Despite a name it's a function that returns constructed ManyToManyField.
@@ -60,8 +73,7 @@ Actual class returned from ManyToMany function call and stored in model_fields.
#### get\_source\_related\_name
```python
- | @classmethod
- | get_source_related_name(cls) -> str
+ | get_source_related_name() -> str
```
Returns name to use for source relation name.
@@ -72,40 +84,11 @@ It's either set as `related_name` or by default it's field name.
`(str)`: name of the related_name or default related name.
-
-#### default\_target\_field\_name
-
-```python
- | @classmethod
- | default_target_field_name(cls) -> str
-```
-
-Returns default target model name on through model.
-
-**Returns**:
-
-`(str)`: name of the field
-
-
-#### default\_source\_field\_name
-
-```python
- | @classmethod
- | default_source_field_name(cls) -> str
-```
-
-Returns default target model name on through model.
-
-**Returns**:
-
-`(str)`: name of the field
-
#### has\_unresolved\_forward\_refs
```python
- | @classmethod
- | has_unresolved_forward_refs(cls) -> bool
+ | has_unresolved_forward_refs() -> bool
```
Verifies if the filed has any ForwardRefs that require updating before the
@@ -119,8 +102,7 @@ model can be used.
#### evaluate\_forward\_ref
```python
- | @classmethod
- | evaluate_forward_ref(cls, globalns: Any, localns: Any) -> None
+ | evaluate_forward_ref(globalns: Any, localns: Any) -> None
```
Evaluates the ForwardRef to actual Field based on global and local namespaces
@@ -138,8 +120,7 @@ Evaluates the ForwardRef to actual Field based on global and local namespaces
#### get\_relation\_name
```python
- | @classmethod
- | get_relation_name(cls) -> str
+ | get_relation_name() -> str
```
Returns name of the relation, which can be a own name or through model
@@ -153,8 +134,7 @@ names for m2m models
#### get\_source\_model
```python
- | @classmethod
- | get_source_model(cls) -> Type["Model"]
+ | get_source_model() -> Type["Model"]
```
Returns model from which the relation comes -> either owner or through model
@@ -167,8 +147,7 @@ Returns model from which the relation comes -> either owner or through model
#### create\_default\_through\_model
```python
- | @classmethod
- | create_default_through_model(cls) -> None
+ | create_default_through_model() -> None
```
Creates default empty through model if no additional fields are required.
diff --git a/docs/api/models/helpers/models.md b/docs/api/models/helpers/models.md
index 2537a70..328a95c 100644
--- a/docs/api/models/helpers/models.md
+++ b/docs/api/models/helpers/models.md
@@ -5,7 +5,7 @@
#### is\_field\_an\_forward\_ref
```python
-is_field_an_forward_ref(field: Type["BaseField"]) -> bool
+is_field_an_forward_ref(field: "BaseField") -> bool
```
Checks if field is a relation field and whether any of the referenced models
@@ -91,7 +91,7 @@ extraction of ormar model_fields.
#### group\_related\_list
```python
-group_related_list(list_: List) -> Dict
+group_related_list(list_: List) -> collections.OrderedDict
```
Translates the list of related strings into a dictionary.
diff --git a/docs/api/models/helpers/pydantic.md b/docs/api/models/helpers/pydantic.md
index 49e44a5..2788e90 100644
--- a/docs/api/models/helpers/pydantic.md
+++ b/docs/api/models/helpers/pydantic.md
@@ -5,7 +5,7 @@
#### create\_pydantic\_field
```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: "ManyToManyField") -> None
```
Registers pydantic field on through model that leads to passed model
@@ -38,32 +38,6 @@ field_name. Returns a pydantic field with type of field_name field type.
`(pydantic.ModelField)`: newly created pydantic field
-
-#### populate\_default\_pydantic\_field\_value
-
-```python
-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
-(so the default_value declared on ormar model if set)
-and converts it to pydantic.FieldInfo
-that pydantic is able to extract later.
-
-On FieldInfo there are saved all needed params like max_length of the string
-and other constraints that pydantic can use to build
-it's own field validation used by ormar.
-
-**Arguments**:
-
-- `ormar_field (ormar Field)`: field to convert
-- `field_name (str)`: field to convert name
-- `attrs (Dict)`: current class namespace
-
-**Returns**:
-
-`(Dict)`: updated namespace dict
-
#### populate\_pydantic\_default\_values
@@ -76,7 +50,7 @@ dictionary of the class. Fields declared on model are all subclasses of the
BaseField class.
Trigger conversion of ormar field into pydantic FieldInfo, which has all needed
-paramaters saved.
+parameters saved.
Overwrites the annotations of ormar fields to corresponding types declared on
ormar fields (constructed dynamically for relations).
diff --git a/docs/api/models/helpers/relations.md b/docs/api/models/helpers/relations.md
index 8da7561..025356b 100644
--- a/docs/api/models/helpers/relations.md
+++ b/docs/api/models/helpers/relations.md
@@ -5,7 +5,7 @@
#### register\_relation\_on\_build
```python
-register_relation_on_build(field: Type["ForeignKeyField"]) -> None
+register_relation_on_build(field: "ForeignKeyField") -> None
```
Registers ForeignKey relation in alias_manager to set a table_prefix.
@@ -23,7 +23,7 @@ aliases for proper sql joins.
#### register\_many\_to\_many\_relation\_on\_build
```python
-register_many_to_many_relation_on_build(field: Type["ManyToManyField"]) -> None
+register_many_to_many_relation_on_build(field: "ManyToManyField") -> None
```
Registers connection between through model and both sides of the m2m relation.
@@ -43,7 +43,7 @@ By default relation name is a model.name.lower().
#### expand\_reverse\_relationship
```python
-expand_reverse_relationship(model_field: Type["ForeignKeyField"]) -> None
+expand_reverse_relationship(model_field: "ForeignKeyField") -> None
```
If the reverse relation has not been set before it's set here.
@@ -76,7 +76,7 @@ If the reverse relation has not been set before it's set here.
#### register\_reverse\_model\_fields
```python
-register_reverse_model_fields(model_field: Type["ForeignKeyField"]) -> None
+register_reverse_model_fields(model_field: "ForeignKeyField") -> None
```
Registers reverse ForeignKey field on related model.
@@ -93,7 +93,7 @@ Autogenerated reverse fields also set related_name to the original field name.
#### register\_through\_shortcut\_fields
```python
-register_through_shortcut_fields(model_field: Type["ManyToManyField"]) -> None
+register_through_shortcut_fields(model_field: "ManyToManyField") -> None
```
Registers m2m relation through shortcut on both ends of the relation.
@@ -106,7 +106,7 @@ Registers m2m relation through shortcut on both ends of the relation.
#### register\_relation\_in\_alias\_manager
```python
-register_relation_in_alias_manager(field: Type["ForeignKeyField"]) -> None
+register_relation_in_alias_manager(field: "ForeignKeyField") -> None
```
Registers the relation (and reverse relation) in alias manager.
@@ -125,7 +125,7 @@ fk - register_relation_on_build
#### verify\_related\_name\_dont\_duplicate
```python
-verify_related_name_dont_duplicate(related_name: str, model_field: Type["ForeignKeyField"]) -> None
+verify_related_name_dont_duplicate(related_name: str, model_field: "ForeignKeyField") -> None
```
Verifies whether the used related_name (regardless of the fact if user defined or
@@ -150,7 +150,7 @@ model
#### reverse\_field\_not\_already\_registered
```python
-reverse_field_not_already_registered(model_field: Type["ForeignKeyField"]) -> bool
+reverse_field_not_already_registered(model_field: "ForeignKeyField") -> bool
```
Checks if child is already registered in parents pydantic fields.
diff --git a/docs/api/models/helpers/sqlalchemy.md b/docs/api/models/helpers/sqlalchemy.md
index 473b599..8738404 100644
--- a/docs/api/models/helpers/sqlalchemy.md
+++ b/docs/api/models/helpers/sqlalchemy.md
@@ -5,7 +5,7 @@
#### adjust\_through\_many\_to\_many\_model
```python
-adjust_through_many_to_many_model(model_field: Type["ManyToManyField"]) -> None
+adjust_through_many_to_many_model(model_field: "ManyToManyField") -> None
```
Registers m2m relation on through model.
@@ -21,7 +21,7 @@ Sets pydantic fields with child and parent model types.
#### create\_and\_append\_m2m\_fk
```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: "ManyToManyField", field_name: str) -> None
```
Registers sqlalchemy Column with sqlalchemy.ForeignKey leading to the model.
@@ -98,6 +98,72 @@ or pkname validation fails.
`(Tuple[Optional[str], List[sqlalchemy.Column]])`: pkname, list of sqlalchemy columns
+
+#### \_process\_fields
+
+```python
+_process_fields(model_fields: Dict, new_model: Type["Model"]) -> Tuple[Optional[str], List[sqlalchemy.Column]]
+```
+
+Helper method.
+
+Populates pkname and columns.
+Trigger validation of primary_key - only one and required pk can be set,
+cannot be pydantic_only.
+
+Append fields to columns if it's not pydantic_only,
+virtual ForeignKey or ManyToMany field.
+
+Sets `owner` on each model_field as reference to newly created Model.
+
+**Raises**:
+
+- `ModelDefinitionError`: if validation of related_names fail,
+or pkname validation fails.
+
+**Arguments**:
+
+- `model_fields (Dict[str, ormar.Field])`: dictionary of declared ormar model fields
+- `new_model (Model class)`:
+
+**Returns**:
+
+`(Tuple[Optional[str], List[sqlalchemy.Column]])`: pkname, list of sqlalchemy columns
+
+
+#### \_is\_through\_model\_not\_set
+
+```python
+_is_through_model_not_set(field: "BaseField") -> bool
+```
+
+Alias to if check that verifies if through model was created.
+
+**Arguments**:
+
+- `field ("BaseField")`: field to check
+
+**Returns**:
+
+`(bool)`: result of the check
+
+
+#### \_is\_db\_field
+
+```python
+_is_db_field(field: "BaseField") -> bool
+```
+
+Alias to if check that verifies if field should be included in database.
+
+**Arguments**:
+
+- `field ("BaseField")`: field to check
+
+**Returns**:
+
+`(bool)`: result of the check
+
#### populate\_meta\_tablename\_columns\_and\_pk
@@ -165,7 +231,7 @@ It populates name, metadata, columns and constraints.
#### update\_column\_definition
```python
-update_column_definition(model: Union[Type["Model"], Type["NewBaseModel"]], field: Type["ForeignKeyField"]) -> None
+update_column_definition(model: Union[Type["Model"], Type["NewBaseModel"]], field: "ForeignKeyField") -> None
```
Updates a column with a new type column based on updated parameters in FK fields.
@@ -173,7 +239,7 @@ Updates a column with a new type column based on updated parameters in FK fields
**Arguments**:
- `model (Type["Model"])`: model on which columns needs to be updated
-- `field (Type[ForeignKeyField])`: field with column definition that requires update
+- `field (ForeignKeyField)`: field with column definition that requires update
**Returns**:
diff --git a/docs/api/models/helpers/validation.md b/docs/api/models/helpers/validation.md
index 9c2717b..5c38665 100644
--- a/docs/api/models/helpers/validation.md
+++ b/docs/api/models/helpers/validation.md
@@ -5,7 +5,7 @@
#### check\_if\_field\_has\_choices
```python
-check_if_field_has_choices(field: Type[BaseField]) -> bool
+check_if_field_has_choices(field: BaseField) -> bool
```
Checks if given field has choices populated.
@@ -23,7 +23,7 @@ A if it has one, a validator for this field needs to be attached.
#### convert\_choices\_if\_needed
```python
-convert_choices_if_needed(field: Type["BaseField"], value: Any) -> Tuple[Any, List]
+convert_choices_if_needed(field: "BaseField", value: Any) -> Tuple[Any, List]
```
Converts dates to isoformat as fastapi can check this condition in routes
@@ -37,7 +37,7 @@ Converts decimal to float with given scale.
**Arguments**:
-- `field (Type[BaseField])`: ormar field to check with choices
+- `field (BaseField)`: ormar field to check with choices
- `values (Dict)`: current values of the model to verify
**Returns**:
@@ -48,7 +48,7 @@ Converts decimal to float with given scale.
#### validate\_choices
```python
-validate_choices(field: Type["BaseField"], value: Any) -> None
+validate_choices(field: "BaseField", value: Any) -> None
```
Validates if given value is in provided choices.
@@ -59,7 +59,7 @@ Validates if given value is in provided choices.
**Arguments**:
-- `field (Type[BaseField])`: field to validate
+- `field (BaseField)`: field to validate
- `value (Any)`: value of the field
diff --git a/docs/api/models/mixins/excludable-mixin.md b/docs/api/models/mixins/excludable-mixin.md
index b2ad2f6..7731a62 100644
--- a/docs/api/models/mixins/excludable-mixin.md
+++ b/docs/api/models/mixins/excludable-mixin.md
@@ -78,12 +78,12 @@ Primary key field is always added and cannot be excluded (will be added anyway).
`(List[str])`: list of column field names or aliases
-
-#### \_update\_excluded\_with\_related\_not\_required
+
+#### \_update\_excluded\_with\_related
```python
| @classmethod
- | _update_excluded_with_related_not_required(cls, exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None], nested: bool = False) -> Union[Set, Dict]
+ | _update_excluded_with_related(cls, exclude: Union[Set, Dict, None]) -> Set
```
Used during generation of the dict().
@@ -96,7 +96,6 @@ exclusion, for nested models all related models are excluded.
**Arguments**:
- `exclude (Union[Set, Dict, None])`: set/dict with fields to exclude
-- `nested (bool)`: flag setting nested models (child of previous one, not main one)
**Returns**:
diff --git a/docs/api/models/mixins/merge-model-mixin.md b/docs/api/models/mixins/merge-model-mixin.md
index a7ccbd9..d6e28f9 100644
--- a/docs/api/models/mixins/merge-model-mixin.md
+++ b/docs/api/models/mixins/merge-model-mixin.md
@@ -19,7 +19,7 @@ in the end all parent (main) models should be unique.
```python
| @classmethod
- | merge_instances_list(cls, result_rows: Sequence["Model"]) -> Sequence["Model"]
+ | merge_instances_list(cls, result_rows: List["Model"]) -> List["Model"]
```
Merges a list of models into list of unique models.
@@ -41,7 +41,7 @@ populated, each instance is one row in db and some models can duplicate
```python
| @classmethod
- | merge_two_instances(cls, one: "Model", other: "Model") -> "Model"
+ | merge_two_instances(cls, one: "Model", other: "Model", relation_map: Dict = None) -> "Model"
```
Merges current (other) Model and previous one (one) and returns the current
@@ -51,6 +51,7 @@ If needed it's calling itself recurrently and merges also children models.
**Arguments**:
+- `relation_map (Dict)`: map of models relations to follow
- `one (Model)`: previous model instance
- `other (Model)`: current model instance
@@ -58,3 +59,30 @@ If needed it's calling itself recurrently and merges also children models.
`(Model)`: current Model instance with data merged from previous one.
+
+#### \_merge\_items\_lists
+
+```python
+ | @classmethod
+ | _merge_items_lists(cls, field_name: str, current_field: List, other_value: List, relation_map: Optional[Dict]) -> List
+```
+
+Takes two list of nested models and process them going deeper
+according with the map.
+
+If model from one's list is in other -> they are merged with relations
+to follow passed from map.
+
+If one's model is not in other it's simply appended to the list.
+
+**Arguments**:
+
+- `field_name (str)`: name of the current relation field
+- `current_field (List[Model])`: list of nested models from one model
+- `other_value (List[Model])`: list of nested models from other model
+- `relation_map (Dict)`: map of relations to follow
+
+**Returns**:
+
+`(List[Model])`: merged list of models
+
diff --git a/docs/api/models/mixins/prefetch-query-mixin.md b/docs/api/models/mixins/prefetch-query-mixin.md
index b5eb0f7..d4eadc2 100644
--- a/docs/api/models/mixins/prefetch-query-mixin.md
+++ b/docs/api/models/mixins/prefetch-query-mixin.md
@@ -59,7 +59,7 @@ or field name specified by related parameter.
```python
| @classmethod
- | get_related_field_name(cls, target_field: Type["ForeignKeyField"]) -> str
+ | get_related_field_name(cls, target_field: "ForeignKeyField") -> str
```
Returns name of the relation field that should be used in prefetch query.
diff --git a/docs/api/models/mixins/relation-mixin.md b/docs/api/models/mixins/relation-mixin.md
index 50ccb79..9859f90 100644
--- a/docs/api/models/mixins/relation-mixin.md
+++ b/docs/api/models/mixins/relation-mixin.md
@@ -30,7 +30,7 @@ related fields.
```python
| @classmethod
- | extract_related_fields(cls) -> List
+ | extract_related_fields(cls) -> List["ForeignKeyField"]
```
Returns List of ormar Fields for all relations declared on a model.
@@ -45,7 +45,7 @@ List is cached in cls._related_fields for quicker access.
```python
| @classmethod
- | extract_through_names(cls) -> Set
+ | extract_through_names(cls) -> Set[str]
```
Extracts related fields through names which are shortcuts to through models.
@@ -84,43 +84,35 @@ related fields that are not stored as foreign keys on given model.
`(Set)`: set of model fields with non fk relation fields excluded
-
-#### \_exclude\_related\_names\_not\_required
-
-```python
- | @classmethod
- | _exclude_related_names_not_required(cls, nested: bool = False) -> Set
-```
-
-Returns a set of non mandatory related models field names.
-
-For a main model (not nested) only nullable related field names are returned,
-for nested models all related models are returned.
-
-**Arguments**:
-
-- `nested (bool)`: flag setting nested models (child of previous one, not main one)
-
-**Returns**:
-
-`(Set)`: set of non mandatory related fields
-
#### \_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]
+ | _iterate_related_models(cls, node_list: NodeList = None, source_relation: str = None) -> List[str]
```
Iterates related models recursively to extract relation strings of
nested not visited models.
+**Returns**:
+
+`(List[str])`: list of relation strings to be passed to select_related
+
+
+#### \_get\_final\_relations
+
+```python
+ | @staticmethod
+ | _get_final_relations(processed_relations: List, source_relation: Optional[str]) -> List[str]
+```
+
+Helper method to prefix nested relation strings with current source relation
+
**Arguments**:
-- `visited (Set[str])`: set of already visited models
+- `processed_relations (List[str])`: list of already processed relation str
- `source_relation (str)`: name of the current relation
-- `source_model (Type["Model"])`: model from which relation comes in nested relations
**Returns**:
diff --git a/docs/api/models/mixins/save-prepare-mixin.md b/docs/api/models/mixins/save-prepare-mixin.md
index a3f14cb..c03daf3 100644
--- a/docs/api/models/mixins/save-prepare-mixin.md
+++ b/docs/api/models/mixins/save-prepare-mixin.md
@@ -33,6 +33,25 @@ Translate columns into aliases (db names).
`(Dict[str, str])`: dictionary of model that is about to be saved
+
+#### \_remove\_not\_ormar\_fields
+
+```python
+ | @classmethod
+ | _remove_not_ormar_fields(cls, new_kwargs: dict) -> dict
+```
+
+Removes primary key for if it's nullable or autoincrement pk field,
+and it's set to None.
+
+**Arguments**:
+
+- `new_kwargs (Dict[str, str])`: dictionary of model that is about to be saved
+
+**Returns**:
+
+`(Dict[str, str])`: dictionary of model that is about to be saved
+
#### \_remove\_pk\_from\_kwargs
@@ -52,6 +71,25 @@ and it's set to None.
`(Dict[str, str])`: dictionary of model that is about to be saved
+
+#### parse\_non\_db\_fields
+
+```python
+ | @classmethod
+ | parse_non_db_fields(cls, model_dict: Dict) -> Dict
+```
+
+Receives dictionary of model that is about to be saved and changes uuid fields
+to strings in bulk_update.
+
+**Arguments**:
+
+- `model_dict (Dict)`: dictionary of model that is about to be saved
+
+**Returns**:
+
+`(Dict)`: dictionary of model that is about to be saved
+
#### substitute\_models\_with\_pks
@@ -110,3 +148,73 @@ fields with choices set to see if the value is allowed.
`(Dict)`: dictionary of model that is about to be saved
+
+#### \_upsert\_model
+
+```python
+ | @staticmethod
+ | async _upsert_model(instance: "Model", save_all: bool, previous_model: Optional["Model"], relation_field: Optional["ForeignKeyField"], update_count: int) -> int
+```
+
+Method updates given instance if:
+
+* instance is not saved or
+* instance have no pk or
+* save_all=True flag is set
+
+and instance is not __pk_only__.
+
+If relation leading to instance is a ManyToMany also the through model is saved
+
+**Arguments**:
+
+- `instance (Model)`: current model to upsert
+- `save_all (bool)`: flag if all models should be saved or only not saved ones
+- `relation_field (Optional[ForeignKeyField])`: field with relation
+- `previous_model (Model)`: previous model from which method came
+- `update_count (int)`: no of updated models
+
+**Returns**:
+
+`(int)`: no of updated models
+
+
+#### \_upsert\_through\_model
+
+```python
+ | @staticmethod
+ | async _upsert_through_model(instance: "Model", previous_model: "Model", relation_field: "ForeignKeyField") -> None
+```
+
+Upsert through model for m2m relation.
+
+**Arguments**:
+
+- `instance (Model)`: current model to upsert
+- `relation_field (Optional[ForeignKeyField])`: field with relation
+- `previous_model (Model)`: previous model from which method came
+
+
+#### \_update\_relation\_list
+
+```python
+ | async _update_relation_list(fields_list: Collection["ForeignKeyField"], follow: bool, save_all: bool, relation_map: Dict, update_count: int) -> int
+```
+
+Internal method used in save_related to follow deeper from
+related models and update numbers of updated related instances.
+
+**Arguments**:
+
+- `fields_list (Collection["ForeignKeyField"])`: list of ormar fields to follow and save
+- `relation_map (Dict)`: map of relations to follow
+- `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
+- `update_count (int)`: internal parameter for recursive calls -
+number of updated instances
+
+**Returns**:
+
+`(int)`: tuple of update count and visited
+
diff --git a/docs/api/models/model-metaclass.md b/docs/api/models/model-metaclass.md
index 957a9f7..ab65c2a 100644
--- a/docs/api/models/model-metaclass.md
+++ b/docs/api/models/model-metaclass.md
@@ -102,7 +102,7 @@ Updates Meta parameters in child from parent if needed.
#### copy\_and\_replace\_m2m\_through\_model
```python
-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
+copy_and_replace_m2m_through_model(field: 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
@@ -119,7 +119,7 @@ Removes the original sqlalchemy table from metadata if it was not removed.
**Arguments**:
- `base_class (Type["Model"])`: base class model
-- `field (Type[ManyToManyField])`: field with relations definition
+- `field (ManyToManyField)`: field with relations definition
- `field_name (str)`: name of the relation field
- `table_name (str)`: name of the table
- `parent_fields (Dict)`: dictionary of fields to copy to new models from parent
@@ -130,9 +130,7 @@ Removes the original sqlalchemy table from metadata if it was not removed.
#### copy\_data\_from\_parent\_model
```python
-copy_data_from_parent_model(base_class: Type["Model"], curr_class: type, attrs: Dict, model_fields: Dict[
- str, Union[Type[BaseField], Type[ForeignKeyField], Type[ManyToManyField]]
- ]) -> Tuple[Dict, Dict]
+copy_data_from_parent_model(base_class: Type["Model"], curr_class: type, attrs: Dict, model_fields: Dict[str, Union[BaseField, ForeignKeyField, ManyToManyField]]) -> Tuple[Dict, Dict]
```
Copy the key parameters [databse, metadata, property_fields and constraints]
@@ -162,9 +160,7 @@ Since relation fields requires different related_name for different children
#### extract\_from\_parents\_definition
```python
-extract_from_parents_definition(base_class: type, curr_class: type, attrs: Dict, model_fields: Dict[
- str, Union[Type[BaseField], Type[ForeignKeyField], Type[ManyToManyField]]
- ]) -> Tuple[Dict, Dict]
+extract_from_parents_definition(base_class: type, curr_class: type, attrs: Dict, model_fields: Dict[str, Union[BaseField, ForeignKeyField, ManyToManyField]]) -> Tuple[Dict, Dict]
```
Extracts fields from base classes if they have valid oramr fields.
diff --git a/docs/api/models/model-row.md b/docs/api/models/model-row.md
index 60f0b3a..3483d50 100644
--- a/docs/api/models/model-row.md
+++ b/docs/api/models/model-row.md
@@ -13,7 +13,7 @@ class ModelRow(NewBaseModel)
```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"]
+ | from_row(cls, row: sqlalchemy.engine.ResultProxy, source_model: Type["Model"], select_related: List = None, related_models: Any = None, related_field: "ForeignKeyField" = None, excludable: ExcludableItems = None, current_relation_str: str = "", proxy_source_model: Optional[Type["Model"]] = None, used_prefixes: List[str] = None) -> Optional["Model"]
```
Model method to convert raw sql row from database into ormar.Model instance.
@@ -30,6 +30,7 @@ nested models in result.
**Arguments**:
+- `used_prefixes (List[str])`: list of already extracted prefixes
- `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
@@ -37,18 +38,37 @@ nested models in result.
- `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
+- `related_field (ForeignKeyField)`: field with relation declaration
**Returns**:
`(Optional[Model])`: returns model if model is populated from database
+
+#### \_process\_table\_prefix
+
+```python
+ | @classmethod
+ | _process_table_prefix(cls, source_model: Type["Model"], current_relation_str: str, related_field: "ForeignKeyField", used_prefixes: List[str]) -> str
+```
+
+**Arguments**:
+
+- `source_model (Type[Model])`: model on which relation was defined
+- `current_relation_str (str)`: current relation string
+- `related_field ("ForeignKeyField")`: field with relation declaration
+- `used_prefixes (List[str])`: list of already extracted prefixes
+
+**Returns**:
+
+`(str)`: table_prefix to use
+
#### \_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
+ | _populate_nested_models_from_row(cls, item: dict, row: sqlalchemy.engine.ResultProxy, source_model: Type["Model"], related_models: Any, excludable: ExcludableItems, table_prefix: str, used_prefixes: List[str], current_relation_str: str = None, proxy_source_model: Type["Model"] = None) -> dict
```
Traverses structure of related models and populates the nested models
@@ -75,12 +95,48 @@ instances. In the end those instances are added to the final model dictionary.
`(Dict)`: dictionary with keys corresponding to model fields names
and values are database values
-
-#### populate\_through\_instance
+
+#### \_process\_remainder\_and\_relation\_string
+
+```python
+ | @staticmethod
+ | _process_remainder_and_relation_string(related_models: Union[Dict, List], current_relation_str: Optional[str], related: str) -> Tuple[str, Optional[Union[Dict, List]]]
+```
+
+Process remainder models and relation string
+
+**Arguments**:
+
+- `related_models (Union[Dict, List])`: list or dict of related models
+- `current_relation_str (Optional[str])`: current relation string
+- `related (str)`: name of the relation
+
+
+#### \_populate\_through\_instance
```python
| @classmethod
- | populate_through_instance(cls, row: sqlalchemy.engine.ResultProxy, through_name: str, related: str, excludable: ExcludableItems) -> "ModelRow"
+ | _populate_through_instance(cls, row: sqlalchemy.engine.ResultProxy, item: Dict, related: str, excludable: ExcludableItems, child: "Model", proxy_source_model: Optional[Type["Model"]]) -> None
+```
+
+Populates the through model on reverse side of current query.
+Normally it's child class, unless the query is from queryset.
+
+**Arguments**:
+
+- `row (sqlalchemy.engine.ResultProxy)`: row from db result
+- `item (Dict)`: parent item dict
+- `related (str)`: current relation name
+- `excludable (ExcludableItems)`: structure of fields to include and exclude
+- `child ("Model")`: child item of parent
+- `proxy_source_model (Type["Model"])`: source model from which querysetproxy is constructed
+
+
+#### \_create\_through\_instance
+
+```python
+ | @classmethod
+ | _create_through_instance(cls, row: sqlalchemy.engine.ResultProxy, through_name: str, related: str, excludable: ExcludableItems) -> "ModelRow"
```
Initialize the through model from db row.
diff --git a/docs/api/models/model.md b/docs/api/models/model.md
index facb8f4..a086dc4 100644
--- a/docs/api/models/model.md
+++ b/docs/api/models/model.md
@@ -12,7 +12,7 @@ class Model(ModelRow)
#### upsert
```python
- | async upsert(**kwargs: Any) -> "Model"
+ | async upsert(**kwargs: Any) -> T
```
Performs either a save or an update depending on the presence of the pk.
@@ -31,7 +31,7 @@ For save kwargs are ignored, used only in update if provided.
#### save
```python
- | async save() -> "Model"
+ | async save() -> T
```
Performs a save of given Model instance.
@@ -60,7 +60,7 @@ Sets model save status to True.
#### save\_related
```python
- | async save_related(follow: bool = False, visited: Set = None, update_count: int = 0) -> int
+ | async save_related(follow: bool = False, save_all: bool = False, relation_map: Dict = None, exclude: Union[Set, Dict] = None, update_count: int = 0, previous_model: "Model" = None, relation_field: Optional["ForeignKeyField"] = None) -> int
```
Triggers a upsert method on all related models
@@ -79,10 +79,14 @@ Nested relations of those kind need to be persisted manually.
**Arguments**:
+- `relation_field (Optional[ForeignKeyField])`: field with relation leading to this model
+- `previous_model (Model)`: previous model from which method came
+- `exclude (Union[Set, Dict])`: items to exclude during saving of relations
+- `relation_map (Dict)`: map of relations to follow
+- `save_all (bool)`: flag if all models should be saved or only not saved ones
- `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
-- `visited (Set)`: internal parameter for recursive calls - already visited models
- `update_count (int)`: internal parameter for recursive calls -
number of updated instances
@@ -90,36 +94,11 @@ number of updated instances
`(int)`: number of updated/saved models
-
-#### \_update\_and\_follow
-
-```python
- | @staticmethod
- | 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
-of updated related instances.
-
-**Arguments**:
-
-- `rel (Model)`: Model to follow
-- `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
-- `visited (Set)`: internal parameter for recursive calls - already visited models
-- `update_count (int)`: internal parameter for recursive calls -
-number of updated instances
-
-**Returns**:
-
-`(Tuple[int, Set])`: tuple of update count and visited
-
#### update
```python
- | async update(**kwargs: Any) -> "Model"
+ | async update(_columns: List[str] = None, **kwargs: Any) -> T
```
Performs update of Model instance in the database.
@@ -129,14 +108,15 @@ Sends pre_update and post_update signals.
Sets model save status to True.
+**Arguments**:
+
+- `_columns (List)`: list of columns to update, if None all are updated
+- `kwargs (Any)`: list of fields to update as field=value pairs
+
**Raises**:
- `ModelPersistenceError`: If the pk column is not set
-**Arguments**:
-
-- `kwargs (Any)`: list of fields to update as field=value pairs
-
**Returns**:
`(Model)`: updated Model
@@ -166,7 +146,7 @@ or update and the Model will be saved in database again.
#### load
```python
- | async load() -> "Model"
+ | async load() -> T
```
Allow to refresh existing Models fields from database.
@@ -185,7 +165,7 @@ Does NOT refresh the related models fields if they were loaded before.
#### load\_all
```python
- | async load_all(follow: bool = False, exclude: Union[List, str, Set, Dict] = None) -> "Model"
+ | async load_all(follow: bool = False, exclude: Union[List, str, Set, Dict] = None, order_by: Union[List, str] = None) -> T
```
Allow to refresh existing Models fields from database.
@@ -203,17 +183,18 @@ 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 ()`:
+- `order_by (Union[List, str])`: columns by which models should be sorted
+- `exclude (Union[List, str, Set, Dict])`: related models to 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
+**Raises**:
+
+- `NoMatch`: If given pk is not found in database.
+
**Returns**:
`(Model)`: reloaded Model
diff --git a/docs/api/models/new-basemodel.md b/docs/api/models/new-basemodel.md
index 88b4ed7..3c5cdb1 100644
--- a/docs/api/models/new-basemodel.md
+++ b/docs/api/models/new-basemodel.md
@@ -364,7 +364,7 @@ Returns related field names applying on them include and exclude set.
```python
| @staticmethod
- | _extract_nested_models_from_list(models: MutableSequence, include: Union[Set, Dict, None], exclude: Union[Set, Dict, None]) -> List
+ | _extract_nested_models_from_list(relation_map: Dict, models: MutableSequence, include: Union[Set, Dict, None], exclude: Union[Set, Dict, None]) -> List
```
Converts list of models into list of dictionaries.
@@ -383,7 +383,7 @@ Converts list of models into list of dictionaries.
#### \_skip\_ellipsis
```python
- | _skip_ellipsis(items: Union[Set, Dict, None], key: str) -> Union[Set, Dict, None]
+ | _skip_ellipsis(items: Union[Set, Dict, None], key: str, default_return: Any = None) -> Union[Set, Dict, None]
```
Helper to traverse the include/exclude dictionaries.
@@ -399,11 +399,25 @@ and not the actual set/dict with fields names.
`(Union[Set, Dict, None])`: nested value of the items
+
+#### \_convert\_all
+
+```python
+ | _convert_all(items: Union[Set, Dict, None]) -> Union[Set, Dict, None]
+```
+
+Helper to convert __all__ pydantic special index to ormar which does not
+support index based exclusions.
+
+**Arguments**:
+
+- `items (Union[Set, Dict, None])`: current include/exclude value
+
#### \_extract\_nested\_models
```python
- | _extract_nested_models(nested: bool, dict_instance: Dict, include: Optional[Dict], exclude: Optional[Dict]) -> Dict
+ | _extract_nested_models(relation_map: Dict, dict_instance: Dict, include: Optional[Dict], exclude: Optional[Dict]) -> Dict
```
Traverse nested models and converts them into dictionaries.
@@ -424,7 +438,7 @@ Calls itself recursively if needed.
#### dict
```python
- | dict(*, include: Union[Set, Dict] = None, exclude: Union[Set, Dict] = None, by_alias: bool = False, skip_defaults: bool = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, nested: bool = False) -> "DictStrAny"
+ | dict(*, include: Union[Set, Dict] = None, exclude: Union[Set, Dict] = None, by_alias: bool = False, skip_defaults: bool = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, relation_map: Dict = None) -> "DictStrAny"
```
Generate a dictionary representation of the model,
@@ -443,7 +457,7 @@ Additionally fields decorated with @property_field are also added.
- `exclude_unset (bool)`: flag to exclude not set values - passed to pydantic
- `exclude_defaults (bool)`: flag to exclude default values - passed to pydantic
- `exclude_none (bool)`: flag to exclude None values - passed to pydantic
-- `nested (bool)`: flag if the current model is nested
+- `relation_map (Dict)`: map of the relations to follow to avoid circural deps
**Returns**:
@@ -536,14 +550,14 @@ That includes own non-relational fields ang foreign key fields.
#### get\_relation\_model\_id
```python
- | get_relation_model_id(target_field: Type["BaseField"]) -> Optional[int]
+ | get_relation_model_id(target_field: "BaseField") -> Optional[int]
```
Returns an id of the relation side model to use in prefetch query.
**Arguments**:
-- `target_field (Type["BaseField"])`: field with relation definition
+- `target_field ("BaseField")`: field with relation definition
**Returns**:
diff --git a/docs/api/models/traversible.md b/docs/api/models/traversible.md
new file mode 100644
index 0000000..f83c91f
--- /dev/null
+++ b/docs/api/models/traversible.md
@@ -0,0 +1,78 @@
+
+# models.traversible
+
+
+## NodeList Objects
+
+```python
+class NodeList()
+```
+
+Helper class that helps with iterating nested models
+
+
+#### add
+
+```python
+ | add(node_class: Type["RelationMixin"], relation_name: str = None, parent_node: "Node" = None) -> "Node"
+```
+
+Adds new Node or returns the existing one
+
+**Arguments**:
+
+- `node_class (ormar.models.metaclass.ModelMetaclass)`: Model in current node
+- `relation_name (str)`: name of the current relation
+- `parent_node (Optional[Node])`: parent node
+
+**Returns**:
+
+`(Node)`: returns new or already existing node
+
+
+#### find
+
+```python
+ | find(node_class: Type["RelationMixin"], relation_name: Optional[str] = None, parent_node: "Node" = None) -> Optional["Node"]
+```
+
+Searches for existing node with given parameters
+
+**Arguments**:
+
+- `node_class (ormar.models.metaclass.ModelMetaclass)`: Model in current node
+- `relation_name (str)`: name of the current relation
+- `parent_node (Optional[Node])`: parent node
+
+**Returns**:
+
+`(Optional[Node])`: returns already existing node or None
+
+
+## Node Objects
+
+```python
+class Node()
+```
+
+
+#### visited
+
+```python
+ | visited(relation_name: str) -> bool
+```
+
+Checks if given relation was already visited.
+
+Relation was visited if it's name is in current node children.
+
+Relation was visited if one of the parent node had the same Model class
+
+**Arguments**:
+
+- `relation_name (str)`: name of relation
+
+**Returns**:
+
+`(bool)`: result of the check
+
diff --git a/docs/api/query-set/clause.md b/docs/api/query-set/clause.md
index 53bd55f..0efe18b 100644
--- a/docs/api/query-set/clause.md
+++ b/docs/api/query-set/clause.md
@@ -1,6 +1,115 @@
# queryset.clause
+
+## FilterGroup Objects
+
+```python
+class FilterGroup()
+```
+
+Filter groups are used in complex queries condition to group and and or
+clauses in where condition
+
+
+#### resolve
+
+```python
+ | resolve(model_cls: Type["Model"], select_related: List = None, filter_clauses: List = None) -> Tuple[List[FilterAction], List[str]]
+```
+
+Resolves the FilterGroups actions to use proper target model, replace
+complex relation prefixes if needed and nested groups also resolved.
+
+**Arguments**:
+
+- `model_cls (Type["Model"])`: model from which the query is run
+- `select_related (List[str])`: list of models to join
+- `filter_clauses (List[FilterAction])`: list of filter conditions
+
+**Returns**:
+
+`(Tuple[List[FilterAction], List[str]])`: list of filter conditions and select_related list
+
+
+#### \_iter
+
+```python
+ | _iter() -> Generator
+```
+
+Iterates all actions in a tree
+
+**Returns**:
+
+`(Generator)`: generator yielding from own actions and nested groups
+
+
+#### \_get\_text\_clauses
+
+```python
+ | _get_text_clauses() -> List[sqlalchemy.sql.expression.TextClause]
+```
+
+Helper to return list of text queries from actions and nested groups
+
+**Returns**:
+
+`(List[sqlalchemy.sql.elements.TextClause])`: list of text queries from actions and nested groups
+
+
+#### get\_text\_clause
+
+```python
+ | get_text_clause() -> sqlalchemy.sql.expression.TextClause
+```
+
+Returns all own actions and nested groups conditions compiled and joined
+inside parentheses.
+Escapes characters if it's required.
+Substitutes values of the models if value is a ormar Model with its pk value.
+Compiles the clause.
+
+**Returns**:
+
+`(sqlalchemy.sql.elements.TextClause)`: complied and escaped clause
+
+
+#### or\_
+
+```python
+or_(*args: FilterGroup, **kwargs: Any) -> FilterGroup
+```
+
+Construct or filter from nested groups and keyword arguments
+
+**Arguments**:
+
+- `args (Tuple[FilterGroup])`: nested filter groups
+- `kwargs (Any)`: fields names and proper value types
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup ready to be resolved
+
+
+#### and\_
+
+```python
+and_(*args: FilterGroup, **kwargs: Any) -> FilterGroup
+```
+
+Construct and filter from nested groups and keyword arguments
+
+**Arguments**:
+
+- `args (Tuple[FilterGroup])`: nested filter groups
+- `kwargs (Any)`: fields names and proper value types
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup ready to be resolved
+
## QueryClause Objects
@@ -14,7 +123,7 @@ Constructs FilterActions from strings passed as arguments
#### prepare\_filter
```python
- | prepare_filter(**kwargs: Any) -> Tuple[List[FilterAction], List[str]]
+ | prepare_filter(_own_only: bool = False, **kwargs: Any) -> Tuple[List[FilterAction], List[str]]
```
Main external access point that processes the clauses into sqlalchemy text
@@ -23,6 +132,7 @@ mentioned in select_related strings but not included in select_related.
**Arguments**:
+- `_own_only ()`:
- `kwargs (Any)`: key, value pair with column names and values
**Returns**:
@@ -33,7 +143,7 @@ mentioned in select_related strings but not included in select_related.
#### \_populate\_filter\_clauses
```python
- | _populate_filter_clauses(**kwargs: Any) -> Tuple[List[FilterAction], List[str]]
+ | _populate_filter_clauses(_own_only: bool, **kwargs: Any) -> Tuple[List[FilterAction], List[str]]
```
Iterates all clauses and extracts used operator and field from related
@@ -104,3 +214,16 @@ present in alias_manager.
`(List[FilterAction])`: list of actions with aliases changed if needed
+
+#### \_verify\_prefix\_and\_switch
+
+```python
+ | _verify_prefix_and_switch(action: "FilterAction") -> None
+```
+
+Helper to switch prefix to complex relation one if required
+
+**Arguments**:
+
+- `action (ormar.queryset.actions.filter_action.FilterAction)`: action to switch prefix in
+
diff --git a/docs/api/query-set/field-accessor.md b/docs/api/query-set/field-accessor.md
new file mode 100644
index 0000000..8ef5e14
--- /dev/null
+++ b/docs/api/query-set/field-accessor.md
@@ -0,0 +1,359 @@
+
+# queryset.field\_accessor
+
+
+## FieldAccessor Objects
+
+```python
+class FieldAccessor()
+```
+
+Helper to access ormar fields directly from Model class also for nested
+models attributes.
+
+
+#### \_\_bool\_\_
+
+```python
+ | __bool__() -> bool
+```
+
+Hack to avoid pydantic name check from parent model, returns false
+
+**Returns**:
+
+`(bool)`: False
+
+
+#### \_\_getattr\_\_
+
+```python
+ | __getattr__(item: str) -> Any
+```
+
+Accessor return new accessor for each field and nested models.
+Thanks to that operator overload is possible to use in filter.
+
+**Arguments**:
+
+- `item (str)`: attribute name
+
+**Returns**:
+
+`(ormar.queryset.field_accessor.FieldAccessor)`: FieldAccessor for field or nested model
+
+
+#### \_\_eq\_\_
+
+```python
+ | __eq__(other: Any) -> FilterGroup
+```
+
+overloaded to work as sql `column = `
+
+**Arguments**:
+
+- `other (str)`: value to check agains operator
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup for operator
+
+
+#### \_\_ge\_\_
+
+```python
+ | __ge__(other: Any) -> FilterGroup
+```
+
+overloaded to work as sql `column >= `
+
+**Arguments**:
+
+- `other (str)`: value to check agains operator
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup for operator
+
+
+#### \_\_gt\_\_
+
+```python
+ | __gt__(other: Any) -> FilterGroup
+```
+
+overloaded to work as sql `column > `
+
+**Arguments**:
+
+- `other (str)`: value to check agains operator
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup for operator
+
+
+#### \_\_le\_\_
+
+```python
+ | __le__(other: Any) -> FilterGroup
+```
+
+overloaded to work as sql `column <= `
+
+**Arguments**:
+
+- `other (str)`: value to check agains operator
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup for operator
+
+
+#### \_\_lt\_\_
+
+```python
+ | __lt__(other: Any) -> FilterGroup
+```
+
+overloaded to work as sql `column < `
+
+**Arguments**:
+
+- `other (str)`: value to check agains operator
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup for operator
+
+
+#### \_\_mod\_\_
+
+```python
+ | __mod__(other: Any) -> FilterGroup
+```
+
+overloaded to work as sql `column LIKE '%%'`
+
+**Arguments**:
+
+- `other (str)`: value to check agains operator
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup for operator
+
+
+#### \_\_lshift\_\_
+
+```python
+ | __lshift__(other: Any) -> FilterGroup
+```
+
+overloaded to work as sql `column IN (, ,...)`
+
+**Arguments**:
+
+- `other (str)`: value to check agains operator
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup for operator
+
+
+#### \_\_rshift\_\_
+
+```python
+ | __rshift__(other: Any) -> FilterGroup
+```
+
+overloaded to work as sql `column IS NULL`
+
+**Arguments**:
+
+- `other (str)`: value to check agains operator
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup for operator
+
+
+#### in\_
+
+```python
+ | in_(other: Any) -> FilterGroup
+```
+
+works as sql `column IN (, ,...)`
+
+**Arguments**:
+
+- `other (str)`: value to check agains operator
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup for operator
+
+
+#### iexact
+
+```python
+ | iexact(other: Any) -> FilterGroup
+```
+
+works as sql `column = ` case-insensitive
+
+**Arguments**:
+
+- `other (str)`: value to check agains operator
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup for operator
+
+
+#### contains
+
+```python
+ | contains(other: Any) -> FilterGroup
+```
+
+works as sql `column LIKE '%%'`
+
+**Arguments**:
+
+- `other (str)`: value to check agains operator
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup for operator
+
+
+#### icontains
+
+```python
+ | icontains(other: Any) -> FilterGroup
+```
+
+works as sql `column LIKE '%%'` case-insensitive
+
+**Arguments**:
+
+- `other (str)`: value to check agains operator
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup for operator
+
+
+#### startswith
+
+```python
+ | startswith(other: Any) -> FilterGroup
+```
+
+works as sql `column LIKE '%'`
+
+**Arguments**:
+
+- `other (str)`: value to check agains operator
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup for operator
+
+
+#### istartswith
+
+```python
+ | istartswith(other: Any) -> FilterGroup
+```
+
+works as sql `column LIKE '%'` case-insensitive
+
+**Arguments**:
+
+- `other (str)`: value to check agains operator
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup for operator
+
+
+#### endswith
+
+```python
+ | endswith(other: Any) -> FilterGroup
+```
+
+works as sql `column LIKE '%'`
+
+**Arguments**:
+
+- `other (str)`: value to check agains operator
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup for operator
+
+
+#### iendswith
+
+```python
+ | iendswith(other: Any) -> FilterGroup
+```
+
+works as sql `column LIKE '%'` case-insensitive
+
+**Arguments**:
+
+- `other (str)`: value to check agains operator
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup for operator
+
+
+#### isnull
+
+```python
+ | isnull(other: Any) -> FilterGroup
+```
+
+works as sql `column IS NULL` or `IS NOT NULL`
+
+**Arguments**:
+
+- `other (str)`: value to check agains operator
+
+**Returns**:
+
+`(ormar.queryset.clause.FilterGroup)`: FilterGroup for operator
+
+
+#### asc
+
+```python
+ | asc() -> OrderAction
+```
+
+works as sql `column asc`
+
+**Returns**:
+
+`(ormar.queryset.actions.OrderGroup)`: OrderGroup for operator
+
+
+#### desc
+
+```python
+ | desc() -> OrderAction
+```
+
+works as sql `column desc`
+
+**Returns**:
+
+`(ormar.queryset.actions.OrderGroup)`: OrderGroup for operator
+
diff --git a/docs/api/query-set/join.md b/docs/api/query-set/join.md
index 9a8f898..d6e7ce4 100644
--- a/docs/api/query-set/join.md
+++ b/docs/api/query-set/join.md
@@ -27,7 +27,7 @@ Shortcut for ormar's model AliasManager stored on Meta.
```python
| @property
- | to_table() -> str
+ | to_table() -> sqlalchemy.Table
```
Shortcut to table name of the next model
@@ -172,6 +172,36 @@ Updates the used aliases list directly.
Process order_by causes for non m2m relations.
+
+#### \_verify\_allowed\_order\_field
+
+```python
+ | _verify_allowed_order_field(order_by: str) -> None
+```
+
+Verifies if proper field string is used.
+
+**Arguments**:
+
+- `order_by (str)`: string with order by definition
+
+
+#### \_get\_alias\_and\_model
+
+```python
+ | _get_alias_and_model(order_by: str) -> Tuple[str, Type["Model"]]
+```
+
+Returns proper model and alias to be applied in the clause.
+
+**Arguments**:
+
+- `order_by (str)`: string with order by definition
+
+**Returns**:
+
+`(Tuple[str, Type["Model"]])`: alias and model to be used in clause
+
#### \_get\_order\_bys
diff --git a/docs/api/query-set/prefetch-query.md b/docs/api/query-set/prefetch-query.md
index ff0a64c..0ab7ce2 100644
--- a/docs/api/query-set/prefetch-query.md
+++ b/docs/api/query-set/prefetch-query.md
@@ -241,7 +241,7 @@ Calls itself recurrently to extract deeper nested relations of related model.
#### \_run\_prefetch\_query
```python
- | async _run_prefetch_query(target_field: Type["BaseField"], excludable: "ExcludableItems", filter_clauses: List, related_field_name: str) -> Tuple[str, str, List]
+ | async _run_prefetch_query(target_field: "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
@@ -252,7 +252,7 @@ models.
**Arguments**:
-- `target_field (Type["BaseField"])`: ormar field with relation definition
+- `target_field ("BaseField")`: ormar field with relation definition
- `filter_clauses (List[sqlalchemy.sql.elements.TextClause])`: list of clauses, actually one clause with ids of relation
**Returns**:
@@ -283,14 +283,14 @@ deeper on related model and already loaded in select related query.
#### \_update\_already\_loaded\_rows
```python
- | _update_already_loaded_rows(target_field: Type["BaseField"], prefetch_dict: Dict, orders_by: Dict) -> None
+ | _update_already_loaded_rows(target_field: "BaseField", prefetch_dict: Dict, orders_by: Dict) -> None
```
Updates models that are already loaded, usually children of children.
**Arguments**:
-- `target_field (Type["BaseField"])`: ormar field with relation definition
+- `target_field ("BaseField")`: ormar field with relation definition
- `prefetch_dict (Dict)`: dictionaries of related models to prefetch
- `orders_by (Dict)`: dictionary of order by clauses by model
@@ -298,7 +298,7 @@ Updates models that are already loaded, usually children of children.
#### \_populate\_rows
```python
- | _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
+ | _populate_rows(rows: List, target_field: "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.
@@ -314,7 +314,7 @@ and set on the parent model after sorting if needed.
- `excludable (ExcludableItems)`: structure of fields to include and exclude
- `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 ("BaseField")`: field with relation definition from parent model
- `parent_model (Type[Model])`: model with relation definition
- `table_prefix (str)`: prefix of the target table from current relation
- `prefetch_dict (Dict)`: dictionaries of related models to prefetch
diff --git a/docs/api/query-set/query-set.md b/docs/api/query-set/query-set.md
index 34f0945..d259202 100644
--- a/docs/api/query-set/query-set.md
+++ b/docs/api/query-set/query-set.md
@@ -5,7 +5,7 @@
## QuerySet Objects
```python
-class QuerySet()
+class QuerySet(Generic[T])
```
Main class to perform database queries, exposed on each model as objects attribute.
@@ -29,7 +29,7 @@ Shortcut to model class Meta set on QuerySet model.
```python
| @property
- | model() -> Type["Model"]
+ | model() -> Type["T"]
```
Shortcut to model class set on QuerySet.
@@ -52,7 +52,7 @@ all not passed params are taken from current values.
#### \_prefetch\_related\_models
```python
- | async _prefetch_related_models(models: Sequence[Optional["Model"]], rows: List) -> Sequence[Optional["Model"]]
+ | async _prefetch_related_models(models: List[Optional["T"]], rows: List) -> List[Optional["T"]]
```
Performs prefetch query for selected models names.
@@ -70,7 +70,7 @@ Performs prefetch query for selected models names.
#### \_process\_query\_result\_rows
```python
- | _process_query_result_rows(rows: List) -> Sequence[Optional["Model"]]
+ | _process_query_result_rows(rows: List) -> List[Optional["T"]]
```
Process database rows and initialize ormar Model from each of the rows.
@@ -83,12 +83,29 @@ Process database rows and initialize ormar Model from each of the rows.
`(List[Model])`: list of models
+
+#### \_resolve\_filter\_groups
+
+```python
+ | _resolve_filter_groups(groups: Any) -> Tuple[List[FilterGroup], List[str]]
+```
+
+Resolves filter groups to populate FilterAction params in group tree.
+
+**Arguments**:
+
+- `groups (Any)`: tuple of FilterGroups
+
+**Returns**:
+
+`(Tuple[List[FilterGroup], List[str]])`: list of resolver groups
+
#### check\_single\_result\_rows\_count
```python
| @staticmethod
- | check_single_result_rows_count(rows: Sequence[Optional["Model"]]) -> None
+ | check_single_result_rows_count(rows: Sequence[Optional["T"]]) -> None
```
Verifies if the result has one and only one row.
@@ -149,7 +166,7 @@ If any of the params is not passed the QuerySet own value is used.
#### filter
```python
- | filter(_exclude: bool = False, **kwargs: Any) -> "QuerySet"
+ | filter(*args: Any, *, _exclude: bool = False, **kwargs: Any) -> "QuerySet[T]"
```
Allows you to filter by any `Model` attribute/field
@@ -162,6 +179,8 @@ You can use special filter suffix to change the filter operands:
* contains - like `album__name__contains='Mal'` (sql like)
* icontains - like `album__name__icontains='mal'` (sql like case insensitive)
* in - like `album__name__in=['Malibu', 'Barclay']` (sql in)
+* isnull - like `album__name__isnull=True` (sql is null)
+(isnotnull `album__name__isnull=False` (sql is not null))
* gt - like `position__gt=3` (sql >)
* gte - like `position__gte=3` (sql >=)
* lt - like `position__lt=3` (sql <)
@@ -184,7 +203,7 @@ You can use special filter suffix to change the filter operands:
#### exclude
```python
- | exclude(**kwargs: Any) -> "QuerySet"
+ | exclude(*args: Any, **kwargs: Any) -> "QuerySet[T]"
```
Works exactly the same as filter and all modifiers (suffixes) are the same,
@@ -211,7 +230,7 @@ becomes a union of conditions.
#### select\_related
```python
- | select_related(related: Union[List, str]) -> "QuerySet"
+ | select_related(related: Union[List, str]) -> "QuerySet[T]"
```
Allows to prefetch related models during the same query.
@@ -232,11 +251,40 @@ To chain related `Models` relation use double underscores between names.
`(QuerySet)`: QuerySet
+
+#### select\_all
+
+```python
+ | select_all(follow: bool = False) -> "QuerySet[T]"
+```
+
+By default adds only directly related models.
+
+If follow=True is set it adds 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.
+
+**Arguments**:
+
+- `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
+
#### prefetch\_related
```python
- | prefetch_related(related: Union[List, str]) -> "QuerySet"
+ | prefetch_related(related: Union[List, str]) -> "QuerySet[T]"
```
Allows to prefetch related models during query - but opposite to
@@ -262,7 +310,7 @@ To chain related `Models` relation use double underscores between names.
#### fields
```python
- | fields(columns: Union[List, str, Set, Dict], _is_exclude: bool = False) -> "QuerySet"
+ | fields(columns: Union[List, str, Set, Dict], _is_exclude: bool = False) -> "QuerySet[T]"
```
With `fields()` you can select subset of model columns to limit the data load.
@@ -314,7 +362,7 @@ To include whole nested model specify model related field name and ellipsis.
#### exclude\_fields
```python
- | exclude_fields(columns: Union[List, str, Set, Dict]) -> "QuerySet"
+ | exclude_fields(columns: Union[List, str, Set, Dict]) -> "QuerySet[T]"
```
With `exclude_fields()` you can select subset of model columns that will
@@ -349,7 +397,7 @@ if explicitly excluded.
#### order\_by
```python
- | order_by(columns: Union[List, str]) -> "QuerySet"
+ | order_by(columns: Union[List, str, OrderAction]) -> "QuerySet[T]"
```
With `order_by()` you can order the results from database based on your
@@ -413,6 +461,62 @@ Returns number of rows matching the given criteria
`(int)`: number of rows
+
+#### max
+
+```python
+ | async max(columns: Union[str, List[str]]) -> Any
+```
+
+Returns max value of columns for rows matching the given criteria
+(applied with `filter` and `exclude` if set before).
+
+**Returns**:
+
+`(Any)`: max value of column(s)
+
+
+#### min
+
+```python
+ | async min(columns: Union[str, List[str]]) -> Any
+```
+
+Returns min value of columns for rows matching the given criteria
+(applied with `filter` and `exclude` if set before).
+
+**Returns**:
+
+`(Any)`: min value of column(s)
+
+
+#### sum
+
+```python
+ | async sum(columns: Union[str, List[str]]) -> Any
+```
+
+Returns sum value of columns for rows matching the given criteria
+(applied with `filter` and `exclude` if set before).
+
+**Returns**:
+
+`(int)`: sum value of columns
+
+
+#### avg
+
+```python
+ | async avg(columns: Union[str, List[str]]) -> Any
+```
+
+Returns avg value of columns for rows matching the given criteria
+(applied with `filter` and `exclude` if set before).
+
+**Returns**:
+
+`(Union[int, float, List])`: avg value of columns
+
#### update
@@ -438,7 +542,7 @@ each=True flag to affect whole table.
#### delete
```python
- | async delete(each: bool = False, **kwargs: Any) -> int
+ | async delete(*args: Any, *, each: bool = False, **kwargs: Any) -> int
```
Deletes from the model table after applying the filters from kwargs.
@@ -459,7 +563,7 @@ each=True flag to affect whole table.
#### paginate
```python
- | paginate(page: int, page_size: int = 20) -> "QuerySet"
+ | paginate(page: int, page_size: int = 20) -> "QuerySet[T]"
```
You can paginate the result which is a combination of offset and limit clauses.
@@ -478,7 +582,7 @@ Limit is set to page size and offset is set to (page-1) * page_size.
#### limit
```python
- | limit(limit_count: int, limit_raw_sql: bool = None) -> "QuerySet"
+ | limit(limit_count: int, limit_raw_sql: bool = None) -> "QuerySet[T]"
```
You can limit the results to desired number of parent models.
@@ -499,7 +603,7 @@ models use the `limit_raw_sql` parameter flag, and set it to `True`.
#### offset
```python
- | offset(offset: int, limit_raw_sql: bool = None) -> "QuerySet"
+ | offset(offset: int, limit_raw_sql: bool = None) -> "QuerySet[T]"
```
You can also offset the results by desired number of main models.
@@ -520,7 +624,7 @@ models use the `limit_raw_sql` parameter flag, and set it to `True`.
#### first
```python
- | async first(**kwargs: Any) -> "Model"
+ | async first(*args: Any, **kwargs: Any) -> "T"
```
Gets the first row from the db ordered by primary key column ascending.
@@ -538,11 +642,34 @@ Gets the first row from the db ordered by primary key column ascending.
`(Model)`: returned model
+
+#### get\_or\_none
+
+```python
+ | async get_or_none(*args: Any, **kwargs: Any) -> Optional["T"]
+```
+
+Get's the first row from the db meeting the criteria set by kwargs.
+
+If no criteria set it will return the last row in db sorted by pk.
+
+Passing a criteria is actually calling filter(**kwargs) method described below.
+
+If not match is found None will be returned.
+
+**Arguments**:
+
+- `kwargs (Any)`: fields names and proper value types
+
+**Returns**:
+
+`(Model)`: returned model
+
#### get
```python
- | async get(**kwargs: Any) -> "Model"
+ | async get(*args: Any, **kwargs: Any) -> "T"
```
Get's the first row from the db meeting the criteria set by kwargs.
@@ -568,7 +695,7 @@ Passing a criteria is actually calling filter(**kwargs) method described below.
#### get\_or\_create
```python
- | async get_or_create(**kwargs: Any) -> "Model"
+ | async get_or_create(*args: Any, **kwargs: Any) -> "T"
```
Combination of create and get methods.
@@ -589,7 +716,7 @@ it creates a new one with given kwargs.
#### update\_or\_create
```python
- | async update_or_create(**kwargs: Any) -> "Model"
+ | async update_or_create(**kwargs: Any) -> "T"
```
Updates the model, or in case there is no match in database creates a new one.
@@ -606,7 +733,7 @@ Updates the model, or in case there is no match in database creates a new one.
#### all
```python
- | async all(**kwargs: Any) -> Sequence[Optional["Model"]]
+ | async all(*args: Any, **kwargs: Any) -> List[Optional["T"]]
```
Returns all rows from a database for given model for set filter options.
@@ -627,7 +754,7 @@ If there are no rows meeting the criteria an empty list is returned.
#### create
```python
- | async create(**kwargs: Any) -> "Model"
+ | async create(**kwargs: Any) -> "T"
```
Creates the model instance, saves it in a database and returns the updates model
@@ -647,7 +774,7 @@ The allowed kwargs are `Model` fields names and proper value types.
#### bulk\_create
```python
- | async bulk_create(objects: List["Model"]) -> None
+ | async bulk_create(objects: List["T"]) -> None
```
Performs a bulk update in one database session to speed up the process.
@@ -666,7 +793,7 @@ Bulk operations do not send signals.
#### bulk\_update
```python
- | async bulk_update(objects: List["Model"], columns: List[str] = None) -> None
+ | async bulk_update(objects: List["T"], columns: List[str] = None) -> None
```
Performs bulk update in one database session to speed up the process.
diff --git a/docs/api/query-set/query.md b/docs/api/query-set/query.md
index dc3314d..fc086c9 100644
--- a/docs/api/query-set/query.md
+++ b/docs/api/query-set/query.md
@@ -28,6 +28,16 @@ Applies order_by queries on main model when it's used as a subquery.
That way the subquery with limit and offset only on main model has proper
sorting applied and correct models are fetched.
+
+#### \_apply\_default\_model\_sorting
+
+```python
+ | _apply_default_model_sorting() -> None
+```
+
+Applies orders_by from model Meta class (if provided), if it was not provided
+it was filled by metaclass so it's always there and falls back to pk column
+
#### \_pagination\_query\_required
@@ -62,11 +72,13 @@ Returns ready to run query with all joins and clauses.
`(sqlalchemy.sql.selectable.Select)`: ready to run query with all joins and clauses.
-
-#### \_build\_pagination\_subquery
+
+#### \_build\_pagination\_condition
```python
- | _build_pagination_subquery() -> sqlalchemy.sql.select
+ | _build_pagination_condition() -> Tuple[
+ | sqlalchemy.sql.expression.TextClause, sqlalchemy.sql.expression.TextClause
+ | ]
```
In order to apply limit and offset on main table in join only
@@ -78,9 +90,8 @@ Needed only if limit or offset are set, the flag limit_sql_raw is not set
and query has select_related applied. Otherwise we can limit/offset normally
at the end of whole query.
-**Returns**:
-
-`(sqlalchemy.sql.select)`: constructed subquery on main table with limit, offset and order applied
+The condition is added to filters to filter out desired number of main model
+primary key values. Whole query is used to determine the values.
#### \_apply\_expression\_modifiers
diff --git a/docs/api/query-set/utils.md b/docs/api/query-set/utils.md
index f42e340..2268086 100644
--- a/docs/api/query-set/utils.md
+++ b/docs/api/query-set/utils.md
@@ -5,7 +5,7 @@
#### check\_node\_not\_dict\_or\_not\_last\_node
```python
-check_node_not_dict_or_not_last_node(part: str, parts: List, current_level: Any) -> bool
+check_node_not_dict_or_not_last_node(part: str, is_last: bool, current_level: Any) -> bool
```
Checks if given name is not present in the current level of the structure.
@@ -86,6 +86,27 @@ only other values are overwritten.
`(Dict)`: combination of both dicts
+
+#### subtract\_dict
+
+```python
+subtract_dict(current_dict: Any, updating_dict: Any) -> Dict
+```
+
+Update one dict with another but with regard for nested keys.
+
+That way nested sets are unionised, dicts updated and
+only other values are overwritten.
+
+**Arguments**:
+
+- `current_dict (Dict[str, ellipsis])`: dict to update
+- `updating_dict (Dict)`: dict with values to update
+
+**Returns**:
+
+`(Dict)`: combination of both dicts
+
#### update\_dict\_from\_list
@@ -169,3 +190,24 @@ constructed, extracts alias based on last relation leading to target model.
`(Tuple[str, Type["Model"], str])`: table prefix, target model and relation string
+
+#### \_process\_through\_field
+
+```python
+_process_through_field(related_parts: List, relation: Optional[str], related_field: "BaseField", previous_model: Type["Model"], previous_models: List[Type["Model"]]) -> Tuple[Type["Model"], Optional[str], bool]
+```
+
+Helper processing through models as they need to be treated differently.
+
+**Arguments**:
+
+- `related_parts (List[str])`: split relation string
+- `relation (str)`: relation name
+- `related_field ("ForeignKeyField")`: field with relation declaration
+- `previous_model (Type["Model"])`: model from which relation is coming
+- `previous_models (List[Type["Model"]])`: list of already visited models in relation chain
+
+**Returns**:
+
+`(Tuple[Type["Model"], str, bool])`: previous_model, relation, is_through
+
diff --git a/docs/api/relations/alias-manager.md b/docs/api/relations/alias-manager.md
index d563cf8..57c4885 100644
--- a/docs/api/relations/alias-manager.md
+++ b/docs/api/relations/alias-manager.md
@@ -56,7 +56,7 @@ List has to have sqlalchemy names of columns (ormar aliases) not the ormar ones.
```python
| @staticmethod
- | prefixed_table_name(alias: str, name: str) -> text
+ | prefixed_table_name(alias: str, table: sqlalchemy.Table) -> text
```
Creates text clause with table name with aliased name.
@@ -64,7 +64,7 @@ Creates text clause with table name with aliased name.
**Arguments**:
- `alias (str)`: alias of given table
-- `name (str)`: table name
+- `table (sqlalchemy.Table)`: table
**Returns**:
@@ -138,7 +138,7 @@ Given model and relation name returns the alias for this relation.
#### 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
+ | resolve_relation_alias_after_complex(source_model: Union[Type["Model"], Type["ModelRow"]], relation_str: str, relation_field: "ForeignKeyField") -> str
```
Given source model and relation string returns the alias for this complex
@@ -147,7 +147,7 @@ field definition.
**Arguments**:
-- `relation_field (Type["ForeignKeyField"])`: field with direct relation definition
+- `relation_field ("ForeignKeyField")`: field with direct relation definition
- `source_model (source Model)`: model with query starts
- `relation_str (str)`: string with relation joins defined
diff --git a/docs/api/relations/queryset-proxy.md b/docs/api/relations/queryset-proxy.md
index 1eb9637..3b46759 100644
--- a/docs/api/relations/queryset-proxy.md
+++ b/docs/api/relations/queryset-proxy.md
@@ -5,7 +5,7 @@
## QuerysetProxy Objects
```python
-class QuerysetProxy()
+class QuerysetProxy(Generic[T])
```
Exposes QuerySet methods on relations, but also handles creating and removing
@@ -16,7 +16,7 @@ of through Models for m2m relations.
```python
| @property
- | queryset() -> "QuerySet"
+ | queryset() -> "QuerySet[T]"
```
Returns queryset if it's set, AttributeError otherwise.
@@ -43,7 +43,7 @@ Set's the queryset. Initialized in RelationProxy.
#### \_assign\_child\_to\_parent
```python
- | _assign_child_to_parent(child: Optional["Model"]) -> None
+ | _assign_child_to_parent(child: Optional["T"]) -> None
```
Registers child in parents RelationManager.
@@ -56,7 +56,7 @@ Registers child in parents RelationManager.
#### \_register\_related
```python
- | _register_related(child: Union["Model", Sequence[Optional["Model"]]]) -> None
+ | _register_related(child: Union["T", Sequence[Optional["T"]]]) -> None
```
Registers child/ children in parents RelationManager.
@@ -78,7 +78,7 @@ Cleans the current list of the related models.
#### create\_through\_instance
```python
- | async create_through_instance(child: "Model", **kwargs: Any) -> None
+ | async create_through_instance(child: "T", **kwargs: Any) -> None
```
Crete a through model instance in the database for m2m relations.
@@ -92,7 +92,7 @@ Crete a through model instance in the database for m2m relations.
#### update\_through\_instance
```python
- | async update_through_instance(child: "Model", **kwargs: Any) -> None
+ | async update_through_instance(child: "T", **kwargs: Any) -> None
```
Updates a through model instance in the database for m2m relations.
@@ -102,11 +102,26 @@ Updates a through model instance in the database for m2m relations.
- `kwargs (Any)`: dict of additional keyword arguments for through instance
- `child (Model)`: child model instance
+
+#### upsert\_through\_instance
+
+```python
+ | async upsert_through_instance(child: "T", **kwargs: Any) -> None
+```
+
+Updates a through model instance in the database for m2m relations if
+it already exists, else creates one.
+
+**Arguments**:
+
+- `kwargs (Any)`: dict of additional keyword arguments for through instance
+- `child (Model)`: child model instance
+
#### delete\_through\_instance
```python
- | async delete_through_instance(child: "Model") -> None
+ | async delete_through_instance(child: "T") -> None
```
Removes through model instance from the database for m2m relations.
@@ -147,6 +162,62 @@ Actual call delegated to QuerySet.
`(int)`: number of rows
+
+#### max
+
+```python
+ | async max(columns: Union[str, List[str]]) -> Any
+```
+
+Returns max value of columns for rows matching the given criteria
+(applied with `filter` and `exclude` if set before).
+
+**Returns**:
+
+`(Any)`: max value of column(s)
+
+
+#### min
+
+```python
+ | async min(columns: Union[str, List[str]]) -> Any
+```
+
+Returns min value of columns for rows matching the given criteria
+(applied with `filter` and `exclude` if set before).
+
+**Returns**:
+
+`(Any)`: min value of column(s)
+
+
+#### sum
+
+```python
+ | async sum(columns: Union[str, List[str]]) -> Any
+```
+
+Returns sum value of columns for rows matching the given criteria
+(applied with `filter` and `exclude` if set before).
+
+**Returns**:
+
+`(int)`: sum value of columns
+
+
+#### avg
+
+```python
+ | async avg(columns: Union[str, List[str]]) -> Any
+```
+
+Returns avg value of columns for rows matching the given criteria
+(applied with `filter` and `exclude` if set before).
+
+**Returns**:
+
+`(Union[int, float, List])`: avg value of columns
+
#### clear
@@ -175,7 +246,7 @@ or not, keep_reversed=False deletes them from database.
#### first
```python
- | async first(**kwargs: Any) -> "Model"
+ | async first(*args: Any, **kwargs: Any) -> "T"
```
Gets the first row from the db ordered by primary key column ascending.
@@ -192,11 +263,34 @@ List of related models is cleared before the call.
`(_asyncio.Future)`:
+
+#### get\_or\_none
+
+```python
+ | async get_or_none(*args: Any, **kwargs: Any) -> Optional["T"]
+```
+
+Get's the first row from the db meeting the criteria set by kwargs.
+
+If no criteria set it will return the last row in db sorted by pk.
+
+Passing a criteria is actually calling filter(**kwargs) method described below.
+
+If not match is found None will be returned.
+
+**Arguments**:
+
+- `kwargs (Any)`: fields names and proper value types
+
+**Returns**:
+
+`(Model)`: returned model
+
#### get
```python
- | async get(**kwargs: Any) -> "Model"
+ | async get(*args: Any, **kwargs: Any) -> "T"
```
Get's the first row from the db meeting the criteria set by kwargs.
@@ -226,7 +320,7 @@ List of related models is cleared before the call.
#### all
```python
- | async all(**kwargs: Any) -> Sequence[Optional["Model"]]
+ | async all(*args: Any, **kwargs: Any) -> List[Optional["T"]]
```
Returns all rows from a database for given model for set filter options.
@@ -251,7 +345,7 @@ List of related models is cleared before the call.
#### create
```python
- | async create(**kwargs: Any) -> "Model"
+ | async create(**kwargs: Any) -> "T"
```
Creates the model instance, saves it in a database and returns the updates model
@@ -296,7 +390,7 @@ each=True flag to affect whole table.
#### get\_or\_create
```python
- | async get_or_create(**kwargs: Any) -> "Model"
+ | async get_or_create(*args: Any, **kwargs: Any) -> "T"
```
Combination of create and get methods.
@@ -317,7 +411,7 @@ it creates a new one with given kwargs.
#### update\_or\_create
```python
- | async update_or_create(**kwargs: Any) -> "Model"
+ | async update_or_create(**kwargs: Any) -> "T"
```
Updates the model, or in case there is no match in database creates a new one.
@@ -336,7 +430,7 @@ Actual call delegated to QuerySet.
#### filter
```python
- | filter(**kwargs: Any) -> "QuerysetProxy"
+ | filter(*args: Any, **kwargs: Any) -> "QuerysetProxy[T]"
```
Allows you to filter by any `Model` attribute/field
@@ -349,6 +443,8 @@ You can use special filter suffix to change the filter operands:
* contains - like `album__name__contains='Mal'` (sql like)
* icontains - like `album__name__icontains='mal'` (sql like case insensitive)
* in - like `album__name__in=['Malibu', 'Barclay']` (sql in)
+* isnull - like `album__name__isnull=True` (sql is null)
+(isnotnull `album__name__isnull=False` (sql is not null))
* gt - like `position__gt=3` (sql >)
* gte - like `position__gte=3` (sql >=)
* lt - like `position__lt=3` (sql <)
@@ -372,7 +468,7 @@ Actual call delegated to QuerySet.
#### exclude
```python
- | exclude(**kwargs: Any) -> "QuerysetProxy"
+ | exclude(*args: Any, **kwargs: Any) -> "QuerysetProxy[T]"
```
Works exactly the same as filter and all modifiers (suffixes) are the same,
@@ -397,11 +493,40 @@ Actual call delegated to QuerySet.
`(QuerysetProxy)`: filtered QuerysetProxy
+
+#### select\_all
+
+```python
+ | select_all(follow: bool = False) -> "QuerysetProxy[T]"
+```
+
+By default adds only directly related models.
+
+If follow=True is set it adds 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.
+
+**Arguments**:
+
+- `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
+
#### select\_related
```python
- | select_related(related: Union[List, str]) -> "QuerysetProxy"
+ | select_related(related: Union[List, str]) -> "QuerysetProxy[T]"
```
Allows to prefetch related models during the same query.
@@ -428,7 +553,7 @@ Actual call delegated to QuerySet.
#### prefetch\_related
```python
- | prefetch_related(related: Union[List, str]) -> "QuerysetProxy"
+ | prefetch_related(related: Union[List, str]) -> "QuerysetProxy[T]"
```
Allows to prefetch related models during query - but opposite to
@@ -456,7 +581,7 @@ Actual call delegated to QuerySet.
#### paginate
```python
- | paginate(page: int, page_size: int = 20) -> "QuerysetProxy"
+ | paginate(page: int, page_size: int = 20) -> "QuerysetProxy[T]"
```
You can paginate the result which is a combination of offset and limit clauses.
@@ -477,7 +602,7 @@ Actual call delegated to QuerySet.
#### limit
```python
- | limit(limit_count: int) -> "QuerysetProxy"
+ | limit(limit_count: int) -> "QuerysetProxy[T]"
```
You can limit the results to desired number of parent models.
@@ -496,7 +621,7 @@ Actual call delegated to QuerySet.
#### offset
```python
- | offset(offset: int) -> "QuerysetProxy"
+ | offset(offset: int) -> "QuerysetProxy[T]"
```
You can also offset the results by desired number of main models.
@@ -515,7 +640,7 @@ Actual call delegated to QuerySet.
#### fields
```python
- | fields(columns: Union[List, str, Set, Dict]) -> "QuerysetProxy"
+ | fields(columns: Union[List, str, Set, Dict]) -> "QuerysetProxy[T]"
```
With `fields()` you can select subset of model columns to limit the data load.
@@ -568,7 +693,7 @@ Actual call delegated to QuerySet.
#### exclude\_fields
```python
- | exclude_fields(columns: Union[List, str, Set, Dict]) -> "QuerysetProxy"
+ | exclude_fields(columns: Union[List, str, Set, Dict]) -> "QuerysetProxy[T]"
```
With `exclude_fields()` you can select subset of model columns that will
@@ -605,7 +730,7 @@ Actual call delegated to QuerySet.
#### order\_by
```python
- | order_by(columns: Union[List, str]) -> "QuerysetProxy"
+ | order_by(columns: Union[List, str, "OrderAction"]) -> "QuerysetProxy[T]"
```
With `order_by()` you can order the results from database based on your
diff --git a/docs/api/relations/relation-manager.md b/docs/api/relations/relation-manager.md
index d83febe..3b6c0e8 100644
--- a/docs/api/relations/relation-manager.md
+++ b/docs/api/relations/relation-manager.md
@@ -50,7 +50,7 @@ Actual call is delegated to Relation instance registered under relation name.
```python
| @staticmethod
- | add(parent: "Model", child: "Model", field: Type["ForeignKeyField"]) -> None
+ | add(parent: "Model", child: "Model", field: "ForeignKeyField") -> None
```
Adds relation on both sides -> meaning on both child and parent models.
@@ -121,14 +121,14 @@ Returns the actual relation and not the related model(s).
#### \_get\_relation\_type
```python
- | _get_relation_type(field: Type["BaseField"]) -> RelationType
+ | _get_relation_type(field: "BaseField") -> RelationType
```
Returns type of the relation declared on a field.
**Arguments**:
-- `field (Type[BaseField])`: field with relation declaration
+- `field (BaseField)`: field with relation declaration
**Returns**:
@@ -138,7 +138,7 @@ Returns type of the relation declared on a field.
#### \_add\_relation
```python
- | _add_relation(field: Type["BaseField"]) -> None
+ | _add_relation(field: "BaseField") -> None
```
Registers relation in the manager.
@@ -146,5 +146,5 @@ Adds Relation instance under field.name.
**Arguments**:
-- `field (Type[BaseField])`: field with relation declaration
+- `field (BaseField)`: field with relation declaration
diff --git a/docs/api/relations/relation-proxy.md b/docs/api/relations/relation-proxy.md
index 1d122a7..a896df6 100644
--- a/docs/api/relations/relation-proxy.md
+++ b/docs/api/relations/relation-proxy.md
@@ -5,7 +5,7 @@
## RelationProxy Objects
```python
-class RelationProxy(list)
+class RelationProxy(Generic[T], list)
```
Proxy of the Relation that is a list with special methods.
@@ -96,7 +96,7 @@ Otherwise QuerySetProxy cannot filter by parent primary key.
#### \_set\_queryset
```python
- | _set_queryset() -> "QuerySet"
+ | _set_queryset() -> "QuerySet[T]"
```
Creates new QuerySet with relation model and pre filters it with currents
@@ -111,7 +111,7 @@ to the parent model only, without need for user to filter them.
#### remove
```python
- | async remove(item: "Model", keep_reversed: bool = True) -> None
+ | async remove(item: "T", keep_reversed: bool = True) -> None
```
Removes the related from relation with parent.
@@ -131,7 +131,7 @@ will be deleted, and not only removed from relation).
#### add
```python
- | async add(item: "Model", **kwargs: Any) -> None
+ | async add(item: "T", **kwargs: Any) -> None
```
Adds child model to relation.
diff --git a/docs/api/relations/relation.md b/docs/api/relations/relation.md
index 29e8ab7..a20016b 100644
--- a/docs/api/relations/relation.md
+++ b/docs/api/relations/relation.md
@@ -18,7 +18,7 @@ Different types of relations supported by ormar:
## Relation Objects
```python
-class Relation()
+class Relation(Generic[T])
```
Keeps related Models and handles adding/removing of the children.
@@ -27,7 +27,7 @@ Keeps related Models and handles adding/removing of the children.
#### \_\_init\_\_
```python
- | __init__(manager: "RelationsManager", type_: RelationType, field_name: str, to: Type["Model"], through: Type["Model"] = None) -> None
+ | __init__(manager: "RelationsManager", type_: RelationType, field_name: str, to: Type["T"], through: Type["Model"] = None) -> None
```
Initialize the Relation and keep the related models either as instances of
diff --git a/docs/api/relations/utils.md b/docs/api/relations/utils.md
index a771d31..8711b3d 100644
--- a/docs/api/relations/utils.md
+++ b/docs/api/relations/utils.md
@@ -5,7 +5,7 @@
#### get\_relations\_sides\_and\_names
```python
-get_relations_sides_and_names(to_field: Type[ForeignKeyField], parent: "Model", child: "Model") -> Tuple["Model", "Model", str, str]
+get_relations_sides_and_names(to_field: ForeignKeyField, parent: "Model", child: "Model") -> Tuple["Model", "Model", str, str]
```
Determines the names of child and parent relations names, as well as
diff --git a/docs/api/signals/decorators.md b/docs/api/signals/decorators.md
index de7fe8b..7fcbc0a 100644
--- a/docs/api/signals/decorators.md
+++ b/docs/api/signals/decorators.md
@@ -128,3 +128,75 @@ that should have the signal receiver registered
`(Callable)`: returns the original function untouched
+
+#### pre\_relation\_add
+
+```python
+pre_relation_add(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable
+```
+
+Connect given function to all senders for pre_relation_add signal.
+
+**Arguments**:
+
+- `senders (Union[Type["Model"], List[Type["Model"]]])`: one or a list of "Model" classes
+that should have the signal receiver registered
+
+**Returns**:
+
+`(Callable)`: returns the original function untouched
+
+
+#### post\_relation\_add
+
+```python
+post_relation_add(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable
+```
+
+Connect given function to all senders for post_relation_add signal.
+
+**Arguments**:
+
+- `senders (Union[Type["Model"], List[Type["Model"]]])`: one or a list of "Model" classes
+that should have the signal receiver registered
+
+**Returns**:
+
+`(Callable)`: returns the original function untouched
+
+
+#### pre\_relation\_remove
+
+```python
+pre_relation_remove(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable
+```
+
+Connect given function to all senders for pre_relation_remove signal.
+
+**Arguments**:
+
+- `senders (Union[Type["Model"], List[Type["Model"]]])`: one or a list of "Model" classes
+that should have the signal receiver registered
+
+**Returns**:
+
+`(Callable)`: returns the original function untouched
+
+
+#### post\_relation\_remove
+
+```python
+post_relation_remove(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable
+```
+
+Connect given function to all senders for post_relation_remove signal.
+
+**Arguments**:
+
+- `senders (Union[Type["Model"], List[Type["Model"]]])`: one or a list of "Model" classes
+that should have the signal receiver registered
+
+**Returns**:
+
+`(Callable)`: returns the original function untouched
+
diff --git a/docs/queries/filter-and-sort.md b/docs/queries/filter-and-sort.md
index 4045f10..7f4f7d3 100644
--- a/docs/queries/filter-and-sort.md
+++ b/docs/queries/filter-and-sort.md
@@ -69,34 +69,34 @@ tracks = Track.objects.filter(album__name="Fantasies").all()
You can use special filter suffix to change the filter operands:
-* exact - exact match to value, sql `column = `
- * can be written as`album__name__exact='Malibu'`
-* iexact - exact match sql `column = ` (case insensitive)
- * can be written as`album__name__iexact='malibu'`
-* contains - sql `column LIKE '%%'`
- * can be written as`album__name__contains='Mal'`
-* icontains - sql `column LIKE '%%'` (case insensitive)
- * can be written as`album__name__icontains='mal'`
-* in - sql ` column IN (, , ...)`
- * can be written as`album__name__in=['Malibu', 'Barclay']`
-* isnull - sql `column IS NULL` (and sql `column IS NOT NULL`)
- * can be written as`album__name__isnull=True` (isnotnull `album__name__isnull=False`)
-* gt - sql `column > ` (greater than)
- * can be written as`position__gt=3`
-* gte - sql `column >= ` (greater or equal than)
- * can be written as`position__gte=3`
-* lt - sql `column < ` (lower than)
- * can be written as`position__lt=3`
-* lte - sql `column <= ` (lower equal than)
- * can be written as`position__lte=3`
-* startswith - sql `column LIKE '%'` (exact start match)
- * can be written as`album__name__startswith='Mal'`
-* istartswith - sql `column LIKE '%'` (case insensitive)
- * can be written as`album__name__istartswith='mal'`
-* endswith - sql `column LIKE '%'` (exact end match)
- * can be written as`album__name__endswith='ibu'`
-* iendswith - sql `column LIKE '%'` (case insensitive)
- * can be written as`album__name__iendswith='IBU'`
+* **exact** - exact match to value, sql `column = `
+ * can be written as`album__name__exact='Malibu'`
+* **iexact** - exact match sql `column = ` (case insensitive)
+ * can be written as`album__name__iexact='malibu'`
+* **contains** - sql `column LIKE '%%'`
+ * can be written as`album__name__contains='Mal'`
+* **icontains** - sql `column LIKE '%%'` (case insensitive)
+ * can be written as`album__name__icontains='mal'`
+* **in** - sql ` column IN (, , ...)`
+ * can be written as`album__name__in=['Malibu', 'Barclay']`
+* **isnull** - sql `column IS NULL` (and sql `column IS NOT NULL`)
+ * can be written as`album__name__isnull=True` (isnotnull `album__name__isnull=False`)
+* **gt** - sql `column > ` (greater than)
+ * can be written as`position__gt=3`
+* **gte** - sql `column >= ` (greater or equal than)
+ * can be written as`position__gte=3`
+* **lt** - sql `column < ` (lower than)
+ * can be written as`position__lt=3`
+* **lte** - sql `column <= ` (lower equal than)
+ * can be written as`position__lte=3`
+* **startswith** - sql `column LIKE '%'` (exact start match)
+ * can be written as`album__name__startswith='Mal'`
+* **istartswith** - sql `column LIKE '%'` (case insensitive)
+ * can be written as`album__name__istartswith='mal'`
+* **endswith** - sql `column LIKE '%'` (exact end match)
+ * can be written as`album__name__endswith='ibu'`
+* **iendswith** - sql `column LIKE '%'` (case insensitive)
+ * can be written as`album__name__iendswith='IBU'`
Some samples:
@@ -116,40 +116,40 @@ Product.objects.filter(
### Python style filters
-* exact - exact match to value, sql `column = `
- * can be written as `Track.album.name == 'Malibu`
-* iexact - exact match sql `column = ` (case insensitive)
- * can be written as `Track.album.name.iexact('malibu')`
-* contains - sql `column LIKE '%%'`
- * can be written as `Track.album.name % 'Mal')`
- * can be written as `Track.album.name.contains('Mal')`
-* icontains - sql `column LIKE '%%'` (case insensitive)
- * can be written as `Track.album.name.icontains('mal')`
-* in - sql ` column IN (, , ...)`
- * can be written as `Track.album.name << ['Malibu', 'Barclay']`
- * can be written as `Track.album.name.in_(['Malibu', 'Barclay'])`
-* isnull - sql `column IS NULL` (and sql `column IS NOT NULL`)
- * can be written as `Track.album.name >> None`
- * can be written as `Track.album.name.is_null(True)`
- * not null can be written as `Track.album.name.is_null(False)`
- * not null can be written as `~(Track.album.name >> None)`
- * not null can be written as `~(Track.album.name.is_null(True))`
-* gt - sql `column > ` (greater than)
- * can be written as `Track.album.name > 3`
-* gte - sql `column >= ` (greater or equal than)
- * can be written as `Track.album.name >= 3`
-* lt - sql `column < ` (lower than)
- * can be written as `Track.album.name < 3`
-* lte - sql `column <= ` (lower equal than)
- * can be written as `Track.album.name <= 3`
-* startswith - sql `column LIKE '%'` (exact start match)
- * can be written as `Track.album.name.startswith('Mal')`
-* istartswith - sql `column LIKE '%'` (case insensitive)
- * can be written as `Track.album.name.istartswith('mal')`
-* endswith - sql `column LIKE '%'` (exact end match)
- * can be written as `Track.album.name.endswith('ibu')`
-* iendswith - sql `column LIKE '%'` (case insensitive)
- * can be written as `Track.album.name.iendswith('IBU')`
+* **exact** - exact match to value, sql `column = `
+ * can be written as `Track.album.name == 'Malibu`
+* **iexact** - exact match sql `column = ` (case insensitive)
+ * can be written as `Track.album.name.iexact('malibu')`
+* **contains** - sql `column LIKE '%%'`
+ * can be written as `Track.album.name % 'Mal')`
+ * can be written as `Track.album.name.contains('Mal')`
+* **icontains** - sql `column LIKE '%%'` (case insensitive)
+ * can be written as `Track.album.name.icontains('mal')`
+* **in** - sql ` column IN (, , ...)`
+ * can be written as `Track.album.name << ['Malibu', 'Barclay']`
+ * can be written as `Track.album.name.in_(['Malibu', 'Barclay'])`
+* **isnull** - sql `column IS NULL` (and sql `column IS NOT NULL`)
+ * can be written as `Track.album.name >> None`
+ * can be written as `Track.album.name.isnull(True)`
+ * not null can be written as `Track.album.name.isnull(False)`
+ * not null can be written as `~(Track.album.name >> None)`
+ * not null can be written as `~(Track.album.name.isnull(True))`
+* **gt** - sql `column > ` (greater than)
+ * can be written as `Track.album.name > 3`
+* **gte** - sql `column >= ` (greater or equal than)
+ * can be written as `Track.album.name >= 3`
+* **lt** - sql `column < ` (lower than)
+ * can be written as `Track.album.name < 3`
+* **lte** - sql `column <= ` (lower equal than)
+ * can be written as `Track.album.name <= 3`
+* **startswith** - sql `column LIKE '%'` (exact start match)
+ * can be written as `Track.album.name.startswith('Mal')`
+* **istartswith** - sql `column LIKE '%'` (case insensitive)
+ * can be written as `Track.album.name.istartswith('mal')`
+* **endswith** - sql `column LIKE '%'` (exact end match)
+ * can be written as `Track.album.name.endswith('ibu')`
+* **iendswith** - sql `column LIKE '%'` (case insensitive)
+ * can be written as `Track.album.name.iendswith('IBU')`
Some samples:
@@ -291,7 +291,7 @@ Let's select books of Tolkien **OR** books written after 1970
sql:
`WHERE ( authors.name = 'J.R.R. Tolkien' OR books.year > 1970 )`
-### Django style
+#### Django style
```python
books = (
await Book.objects.select_related("author")
@@ -301,7 +301,7 @@ books = (
assert len(books) == 5
```
-### Python style
+#### Python style
```python
books = (
await Book.objects.select_related("author")
@@ -316,7 +316,7 @@ Now let's select books written after 1960 or before 1940 which were written by T
sql:
`WHERE ( books.year > 1960 OR books.year < 1940 ) AND authors.name = 'J.R.R. Tolkien'`
-### Django style
+#### Django style
```python
# OPTION 1 - split and into separate call
books = (
@@ -344,7 +344,7 @@ assert books[0].title == "The Hobbit"
assert books[1].title == "The Silmarillion"
```
-### Python style
+#### Python style
```python
books = (
await Book.objects.select_related("author")
@@ -375,7 +375,7 @@ Books of Sapkowski from before 2000 or books of Tolkien written after 1960
sql:
`WHERE ( ( books.year > 1960 AND authors.name = 'J.R.R. Tolkien' ) OR ( books.year < 2000 AND authors.name = 'Andrzej Sapkowski' ) ) `
-### Django style
+#### Django style
```python
books = (
await Book.objects.select_related("author")
@@ -390,7 +390,7 @@ books = (
assert len(books) == 2
```
-### Python style
+#### Python style
```python
books = (
await Book.objects.select_related("author")
@@ -411,7 +411,7 @@ sql:
( books.year < 2000 AND os0cec_authors.name = 'Andrzej Sapkowski' ) OR
books.title LIKE '%hobbit%' )`
-### Django style
+#### Django style
```python
books = (
await Book.objects.select_related("author")
@@ -426,7 +426,7 @@ books = (
)
```
-### Python style
+#### Python style
```python
books = (
await Book.objects.select_related("author")
@@ -451,7 +451,7 @@ AND authors.name = 'J.R.R. Tolkien' ) OR
You can construct a query as follows:
-### Django style
+#### Django style
```python
books = (
await Book.objects.select_related("author")
@@ -472,16 +472,21 @@ assert books[1].title == "The Silmarillion"
assert books[2].title == "The Witcher"
```
+#### Python style
```python
books = (
await Book.objects.select_related("author")
- .filter(
- ormar.or_(
- ormar.and_(
- ormar.or_(year__gt=1960, year__lt=1940),
- author__name="J.R.R. Tolkien",
- ),
- ormar.and_(year__lt=2000, author__name="Andrzej Sapkowski"),
+ .filter(
+ (
+ (
+ (Book.year > 1960) |
+ (Book.year < 1940)
+ ) &
+ (Book.author.name == "J.R.R. Tolkien")
+ ) |
+ (
+ (Book.year < 2000) &
+ (Book.author.name == "Andrzej Sapkowski")
)
)
.all()
@@ -512,7 +517,7 @@ assert books[0].title == "The Witcher"
Same applies to python style chaining and nesting.
-### Django style
+#### Django style
Note that with django style you cannot provide the same keyword argument several times so queries like `filter(ormar.or_(name='Jack', name='John'))` are not allowed. If you want to check the same
column for several values simply use `in` operator: `filter(name__in=['Jack','John'])`.
@@ -559,7 +564,7 @@ books = (
assert len(books) == 5
```
-### Python style
+#### Python style
Note that with python style you can perfectly use the same fields as many times as you want.
@@ -721,7 +726,7 @@ Given sample Models like following:
To order by main model field just provide a field name
-### Django style
+#### Django style
```python
toys = await Toy.objects.select_related("owner").order_by("name").all()
assert [x.name.replace("Toy ", "") for x in toys] == [
@@ -731,7 +736,7 @@ assert toys[0].owner == zeus
assert toys[1].owner == aphrodite
```
-### Python style
+#### Python style
```python
toys = await Toy.objects.select_related("owner").order_by(Toy.name.asc()).all()
assert [x.name.replace("Toy ", "") for x in toys] == [
@@ -747,7 +752,7 @@ To sort on nested models separate field names with dunder '__'.
You can sort this way across all relation types -> `ForeignKey`, reverse virtual FK
and `ManyToMany` fields.
-### Django style
+#### Django style
```python
toys = await Toy.objects.select_related("owner").order_by("owner__name").all()
assert toys[0].owner.name == toys[1].owner.name == "Aphrodite"
@@ -755,7 +760,7 @@ assert toys[2].owner.name == toys[3].owner.name == "Hermes"
assert toys[4].owner.name == toys[5].owner.name == "Zeus"
```
-### Python style
+#### Python style
```python
toys = await Toy.objects.select_related("owner").order_by(Toy.owner.name.asc()).all()
assert toys[0].owner.name == toys[1].owner.name == "Aphrodite"
@@ -765,7 +770,7 @@ assert toys[4].owner.name == toys[5].owner.name == "Zeus"
To sort in descending order provide a hyphen in front of the field name
-### Django style
+#### Django style
```python
owner = (
await Owner.objects.select_related("toys")
@@ -777,7 +782,7 @@ assert owner.toys[0].name == "Toy 4"
assert owner.toys[1].name == "Toy 1"
```
-### Python style
+#### Python style
```python
owner = (
await Owner.objects.select_related("toys")
diff --git a/docs/releases.md b/docs/releases.md
index 6b9a4a5..ae63e5e 100644
--- a/docs/releases.md
+++ b/docs/releases.md
@@ -25,10 +25,10 @@
* isnull - sql `column IS NULL` (and sql `column IS NOT NULL`)
* OLD: `album__name__isnull=True` (isnotnull `album__name__isnull=False`)
* NEW: can be also written as `Track.album.name >> None`
- * NEW: can be also written as `Track.album.name.is_null(True)`
- * NEW: not null can be also written as `Track.album.name.is_null(False)`
+ * NEW: can be also written as `Track.album.name.isnull(True)`
+ * NEW: not null can be also written as `Track.album.name.isnull(False)`
* NEW: not null can be also written as `~(Track.album.name >> None)`
- * NEW: not null can be also written as `~(Track.album.name.is_null(True))`
+ * NEW: not null can be also written as `~(Track.album.name.isnull(True))`
* gt - sql `column > ` (greater than)
* OLD: `position__gt=3`
* NEW: can be also written as `Track.album.name > 3`
diff --git a/mkdocs.yml b/mkdocs.yml
index 65d5215..c32dbcc 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -77,6 +77,7 @@ nav:
- Order Query: api/query-set/order-query.md
- Limit Query: api/query-set/limit-query.md
- Offset Query: api/query-set/offset-query.md
+ - Field Accessor: api/query-set/field-accessor.md
- api/query-set/utils.md
- Relations:
- Relation Manager: api/relations/relation-manager.md
diff --git a/ormar/queryset/field_accessor.py b/ormar/queryset/field_accessor.py
index 2701454..f0a7e2d 100644
--- a/ormar/queryset/field_accessor.py
+++ b/ormar/queryset/field_accessor.py
@@ -9,6 +9,11 @@ if TYPE_CHECKING: # pragma: no cover
class FieldAccessor:
+ """
+ Helper to access ormar fields directly from Model class also for nested
+ models attributes.
+ """
+
def __init__(
self,
source_model: Type["Model"],
@@ -22,10 +27,24 @@ class FieldAccessor:
self._access_chain = access_chain
def __bool__(self) -> bool:
- # hack to avoid pydantic name check from parent model
+ """
+ Hack to avoid pydantic name check from parent model, returns false
+
+ :return: False
+ :rtype: bool
+ """
return False
def __getattr__(self, item: str) -> Any:
+ """
+ Accessor return new accessor for each field and nested models.
+ Thanks to that operator overload is possible to use in filter.
+
+ :param item: attribute name
+ :type item: str
+ :return: FieldAccessor for field or nested model
+ :rtype: ormar.queryset.field_accessor.FieldAccessor
+ """
if self._field and item == self._field.name:
return self._field
@@ -57,60 +76,208 @@ class FieldAccessor:
return FilterGroup(**filter_kwg)
def __eq__(self, other: Any) -> FilterGroup: # type: ignore
+ """
+ overloaded to work as sql `column = `
+
+ :param other: value to check agains operator
+ :type other: str
+ :return: FilterGroup for operator
+ :rtype: ormar.queryset.clause.FilterGroup
+ """
return self._select_operator(op="__eq__", other=other)
def __ge__(self, other: Any) -> FilterGroup:
+ """
+ overloaded to work as sql `column >= `
+
+ :param other: value to check agains operator
+ :type other: str
+ :return: FilterGroup for operator
+ :rtype: ormar.queryset.clause.FilterGroup
+ """
return self._select_operator(op="__ge__", other=other)
def __gt__(self, other: Any) -> FilterGroup:
+ """
+ overloaded to work as sql `column > `
+
+ :param other: value to check agains operator
+ :type other: str
+ :return: FilterGroup for operator
+ :rtype: ormar.queryset.clause.FilterGroup
+ """
return self._select_operator(op="__gt__", other=other)
def __le__(self, other: Any) -> FilterGroup:
+ """
+ overloaded to work as sql `column <= `
+
+ :param other: value to check agains operator
+ :type other: str
+ :return: FilterGroup for operator
+ :rtype: ormar.queryset.clause.FilterGroup
+ """
return self._select_operator(op="__le__", other=other)
def __lt__(self, other: Any) -> FilterGroup:
+ """
+ overloaded to work as sql `column < `
+
+ :param other: value to check agains operator
+ :type other: str
+ :return: FilterGroup for operator
+ :rtype: ormar.queryset.clause.FilterGroup
+ """
return self._select_operator(op="__lt__", other=other)
def __mod__(self, other: Any) -> FilterGroup:
+ """
+ overloaded to work as sql `column LIKE '%%'`
+
+ :param other: value to check agains operator
+ :type other: str
+ :return: FilterGroup for operator
+ :rtype: ormar.queryset.clause.FilterGroup
+ """
return self._select_operator(op="__mod__", other=other)
def __lshift__(self, other: Any) -> FilterGroup:
+ """
+ overloaded to work as sql `column IN (, ,...)`
+
+ :param other: value to check agains operator
+ :type other: str
+ :return: FilterGroup for operator
+ :rtype: ormar.queryset.clause.FilterGroup
+ """
return self._select_operator(op="in", other=other)
def __rshift__(self, other: Any) -> FilterGroup:
+ """
+ overloaded to work as sql `column IS NULL`
+
+ :param other: value to check agains operator
+ :type other: str
+ :return: FilterGroup for operator
+ :rtype: ormar.queryset.clause.FilterGroup
+ """
return self._select_operator(op="isnull", other=True)
def in_(self, other: Any) -> FilterGroup:
+ """
+ works as sql `column IN (, ,...)`
+
+ :param other: value to check agains operator
+ :type other: str
+ :return: FilterGroup for operator
+ :rtype: ormar.queryset.clause.FilterGroup
+ """
return self._select_operator(op="in", other=other)
def iexact(self, other: Any) -> FilterGroup:
+ """
+ works as sql `column = ` case-insensitive
+
+ :param other: value to check agains operator
+ :type other: str
+ :return: FilterGroup for operator
+ :rtype: ormar.queryset.clause.FilterGroup
+ """
return self._select_operator(op="iexact", other=other)
def contains(self, other: Any) -> FilterGroup:
+ """
+ works as sql `column LIKE '%%'`
+
+ :param other: value to check agains operator
+ :type other: str
+ :return: FilterGroup for operator
+ :rtype: ormar.queryset.clause.FilterGroup
+ """
return self._select_operator(op="contains", other=other)
def icontains(self, other: Any) -> FilterGroup:
+ """
+ works as sql `column LIKE '%%'` case-insensitive
+
+ :param other: value to check agains operator
+ :type other: str
+ :return: FilterGroup for operator
+ :rtype: ormar.queryset.clause.FilterGroup
+ """
return self._select_operator(op="icontains", other=other)
def startswith(self, other: Any) -> FilterGroup:
+ """
+ works as sql `column LIKE '%'`
+
+ :param other: value to check agains operator
+ :type other: str
+ :return: FilterGroup for operator
+ :rtype: ormar.queryset.clause.FilterGroup
+ """
return self._select_operator(op="startswith", other=other)
def istartswith(self, other: Any) -> FilterGroup:
+ """
+ works as sql `column LIKE '%'` case-insensitive
+
+ :param other: value to check agains operator
+ :type other: str
+ :return: FilterGroup for operator
+ :rtype: ormar.queryset.clause.FilterGroup
+ """
return self._select_operator(op="istartswith", other=other)
def endswith(self, other: Any) -> FilterGroup:
+ """
+ works as sql `column LIKE '%'`
+
+ :param other: value to check agains operator
+ :type other: str
+ :return: FilterGroup for operator
+ :rtype: ormar.queryset.clause.FilterGroup
+ """
return self._select_operator(op="endswith", other=other)
def iendswith(self, other: Any) -> FilterGroup:
+ """
+ works as sql `column LIKE '%'` case-insensitive
+
+ :param other: value to check agains operator
+ :type other: str
+ :return: FilterGroup for operator
+ :rtype: ormar.queryset.clause.FilterGroup
+ """
return self._select_operator(op="iendswith", other=other)
def isnull(self, other: Any) -> FilterGroup:
+ """
+ works as sql `column IS NULL` or `IS NOT NULL`
+
+ :param other: value to check agains operator
+ :type other: str
+ :return: FilterGroup for operator
+ :rtype: ormar.queryset.clause.FilterGroup
+ """
return self._select_operator(op="isnull", other=other)
def asc(self) -> OrderAction:
+ """
+ works as sql `column asc`
+
+ :return: OrderGroup for operator
+ :rtype: ormar.queryset.actions.OrderGroup
+ """
return OrderAction(order_str=self._access_chain, model_cls=self._source_model)
def desc(self) -> OrderAction:
+ """
+ works as sql `column desc`
+
+ :return: OrderGroup for operator
+ :rtype: ormar.queryset.actions.OrderGroup
+ """
return OrderAction(
order_str="-" + self._access_chain, model_cls=self._source_model
)
diff --git a/pydoc-markdown.yml b/pydoc-markdown.yml
index 7243a2a..372334c 100644
--- a/pydoc-markdown.yml
+++ b/pydoc-markdown.yml
@@ -122,6 +122,9 @@ renderer:
- title: Offset Query
contents:
- queryset.offset_query.*
+ - title: Field accessor
+ contents:
+ - queryset.field_accessor.*
- title: Utils
contents:
- queryset.utils.*