diff --git a/docs/api/exceptions.md b/docs/api/exceptions.md index e9bb2b6..39422d6 100644 --- a/docs/api/exceptions.md +++ b/docs/api/exceptions.md @@ -68,13 +68,6 @@ Raised for errors in query definition: * using Queryset.update() without filter and setting each flag to True * using Queryset.delete() without filter and setting each flag to True - -## RelationshipInstanceError Objects - -```python -class RelationshipInstanceError(AsyncOrmException) -``` - ## ModelPersistenceError Objects diff --git a/docs/api/fields/base-field.md b/docs/api/fields/base-field.md index 64fa4fb..7bd2d25 100644 --- a/docs/api/fields/base-field.md +++ b/docs/api/fields/base-field.md @@ -16,60 +16,6 @@ All values are kept as class variables, ormar Fields are never instantiated. Subclasses pydantic.FieldInfo to keep the fields related to pydantic field types like ConstrainedStr - -#### \_\_type\_\_ - - -#### related\_name - - -#### column\_type - - -#### constraints - - -#### name - - -#### alias - - -#### primary\_key - - -#### autoincrement - - -#### nullable - - -#### index - - -#### unique - - -#### pydantic\_only - - -#### virtual - - -#### choices - - -#### to - - -#### through - - -#### default - - -#### server\_default - #### is\_valid\_uni\_relation diff --git a/docs/api/fields/foreign-key.md b/docs/api/fields/foreign-key.md index f274e37..e6875dc 100644 --- a/docs/api/fields/foreign-key.md +++ b/docs/api/fields/foreign-key.md @@ -67,15 +67,6 @@ class ForeignKeyConstraint() Internal container to store ForeignKey definitions used later to produce sqlalchemy.ForeignKeys - -#### name - - -#### ondelete - - -#### onupdate - #### ForeignKey @@ -116,18 +107,6 @@ class ForeignKeyField(BaseField) Actual class returned from ForeignKey function call and stored in model_fields. - -#### to - - -#### name - - -#### related\_name - - -#### virtual - #### \_extract\_model\_from\_sequence diff --git a/docs/api/fields/many-to-many.md b/docs/api/fields/many-to-many.md index d950ee6..259b66f 100644 --- a/docs/api/fields/many-to-many.md +++ b/docs/api/fields/many-to-many.md @@ -1,9 +1,6 @@ # fields.many\_to\_many - -#### REF\_PREFIX - #### ManyToMany @@ -40,9 +37,6 @@ class ManyToManyField(ForeignKeyField, ormar.QuerySetProtocol, ormar.RelationP Actual class returned from ManyToMany function call and stored in model_fields. - -#### through - #### default\_target\_field\_name diff --git a/docs/api/fields/model-fields.md b/docs/api/fields/model-fields.md index 0744438..cf2e341 100644 --- a/docs/api/fields/model-fields.md +++ b/docs/api/fields/model-fields.md @@ -48,19 +48,6 @@ class ModelFieldFactory() Default field factory that construct Field classes and populated their values. - -#### \_bases - - -#### \_type - - -#### \_\_new\_\_ - -```python - | __new__(cls, *args: Any, **kwargs: Any) -> Type[BaseField] -``` - #### get\_column\_type @@ -103,16 +90,6 @@ class String(ModelFieldFactory, str) String field factory that construct Field classes and populated their values. - -#### \_type - - -#### \_\_new\_\_ - -```python - | __new__(cls, *, allow_blank: bool = True, strip_whitespace: bool = False, min_length: int = None, max_length: int = None, curtail_length: int = None, regex: str = None, **kwargs: Any) -> Type[BaseField] -``` - #### get\_column\_type @@ -155,16 +132,6 @@ class Integer(ModelFieldFactory, int) Integer field factory that construct Field classes and populated their values. - -#### \_type - - -#### \_\_new\_\_ - -```python - | __new__(cls, *, minimum: int = None, maximum: int = None, multiple_of: int = None, **kwargs: Any) -> Type[BaseField] -``` - #### get\_column\_type @@ -193,16 +160,6 @@ class Text(ModelFieldFactory, str) Text field factory that construct Field classes and populated their values. - -#### \_type - - -#### \_\_new\_\_ - -```python - | __new__(cls, *, allow_blank: bool = True, strip_whitespace: bool = False, **kwargs: Any) -> Type[BaseField] -``` - #### get\_column\_type @@ -231,16 +188,6 @@ class Float(ModelFieldFactory, float) Float field factory that construct Field classes and populated their values. - -#### \_type - - -#### \_\_new\_\_ - -```python - | __new__(cls, *, minimum: float = None, maximum: float = None, multiple_of: int = None, **kwargs: Any) -> Type[BaseField] -``` - #### get\_column\_type @@ -269,9 +216,6 @@ class DateTime(ModelFieldFactory, datetime.datetime) DateTime field factory that construct Field classes and populated their values. - -#### \_type - #### get\_column\_type @@ -300,9 +244,6 @@ class Date(ModelFieldFactory, datetime.date) Date field factory that construct Field classes and populated their values. - -#### \_type - #### get\_column\_type @@ -331,9 +272,6 @@ class Time(ModelFieldFactory, datetime.time) Time field factory that construct Field classes and populated their values. - -#### \_type - #### get\_column\_type @@ -362,9 +300,6 @@ class JSON(ModelFieldFactory, pydantic.Json) JSON field factory that construct Field classes and populated their values. - -#### \_type - #### get\_column\_type @@ -393,16 +328,6 @@ class BigInteger(Integer, int) BigInteger field factory that construct Field classes and populated their values. - -#### \_type - - -#### \_\_new\_\_ - -```python - | __new__(cls, *, minimum: int = None, maximum: int = None, multiple_of: int = None, **kwargs: Any) -> Type[BaseField] -``` - #### get\_column\_type @@ -431,16 +356,6 @@ class Decimal(ModelFieldFactory, decimal.Decimal) Decimal field factory that construct Field classes and populated their values. - -#### \_type - - -#### \_\_new\_\_ - -```python - | __new__(cls, *, minimum: float = None, maximum: float = None, multiple_of: int = None, precision: int = None, scale: int = None, max_digits: int = None, decimal_places: int = None, **kwargs: Any) -> Type[BaseField] -``` - #### get\_column\_type @@ -483,16 +398,6 @@ class UUID(ModelFieldFactory, uuid.UUID) UUID field factory that construct Field classes and populated their values. - -#### \_type - - -#### \_\_new\_\_ - -```python - | __new__(cls, *, uuid_format: str = "hex", **kwargs: Any) -> Type[BaseField] -``` - #### get\_column\_type diff --git a/docs/api/models/helpers/relations.md b/docs/api/models/helpers/relations.md index 83b30a3..5e51ec4 100644 --- a/docs/api/models/helpers/relations.md +++ b/docs/api/models/helpers/relations.md @@ -1,9 +1,6 @@ # models.helpers.relations - -#### alias\_manager - #### register\_relation\_on\_build diff --git a/docs/api/models/model-metaclass.md b/docs/api/models/model-metaclass.md index b9e372a..949828e 100644 --- a/docs/api/models/model-metaclass.md +++ b/docs/api/models/model-metaclass.md @@ -1,59 +1,6 @@ # models.metaclass - -#### PARSED\_FIELDS\_KEY - - -#### CONFIG\_KEY - - -## ModelMeta Objects - -```python -class ModelMeta() -``` - -Class used for type hinting. -Users can subclass this one for convenience but it's not required. -The only requirement is that ormar.Model has to have inner class with name Meta. - - -#### tablename - - -#### table - - -#### metadata - - -#### database - - -#### columns - - -#### constraints - - -#### pkname - - -#### model\_fields - - -#### alias\_manager - - -#### property\_fields - - -#### signals - - -#### abstract - #### check\_if\_field\_has\_choices @@ -233,6 +180,33 @@ Updates Meta parameters in child from parent if needed. - `attrs (Dict)`: new namespace for class being constructed - `model_fields (Dict[str, BaseField])`: ormar fields in defined in current class + +#### 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) -> None +``` + +Clones class with Through model for m2m relations, appends child name to the name +of the cloned class. + +Clones non foreign keys fields from parent model, the same with database columns. + +Modifies related_name with appending child table name after '_' + +For table name, the table name of child is appended after '_'. + +Removes the original sqlalchemy table from metadata if it was not removed. + +**Arguments**: + +- `field (Type[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 +- `attrs (Dict)`: new namespace for class being constructed +- `meta (ModelMeta)`: metaclass of currently created model + #### copy\_data\_from\_parent\_model @@ -296,6 +270,18 @@ If the class is a ormar.Model it is skipped. `(Tuple[Dict, Dict])`: updated attrs and model_fields + +## ModelMeta Objects + +```python +class ModelMeta() +``` + +Class used for type hinting. +Users can subclass this one for convenience but it's not required. +The only requirement is that ormar.Model has to have inner class with name Meta. + + ## ModelMetaclass Objects diff --git a/docs/api/models/model.md b/docs/api/models/model.md index b22d3d9..c770017 100644 --- a/docs/api/models/model.md +++ b/docs/api/models/model.md @@ -24,9 +24,6 @@ will become: `(Dict[str, List])`: list converted to dictionary to avoid repetition and group nested models - -#### T - ## Model Objects @@ -34,16 +31,6 @@ will become: class Model(NewBaseModel) ``` - -#### \_\_abstract\_\_ - - -#### \_\_repr\_\_ - -```python - | __repr__() -> str -``` - #### from\_row @@ -271,7 +258,7 @@ Sets model save status to True. **Raises**: -- `ModelPersistenceError`: If the pk column is not set will throw ModelPersistenceError +- `ModelPersistenceError`: If the pk column is not set **Arguments**: @@ -315,7 +302,7 @@ Does NOT refresh the related models fields if they were loaded before. **Raises**: -- `NoMatch`: If given pk is not found in database the NoMatch exception is raised. +- `NoMatch`: If given pk is not found in database. **Returns**: diff --git a/docs/api/models/new-basemodel.md b/docs/api/models/new-basemodel.md index 0190131..5cec0ad 100644 --- a/docs/api/models/new-basemodel.md +++ b/docs/api/models/new-basemodel.md @@ -15,9 +15,6 @@ Constructed with ModelMetaclass which in turn also inherits pydantic metaclass. Abstracts away all internals and helper functions, so final Model class has only the logic concerned with database connection and data persistance. - -#### \_\_slots\_\_ - #### \_\_init\_\_ diff --git a/docs/api/query-set/clause.md b/docs/api/query-set/clause.md index dcef64d..f43a6c2 100644 --- a/docs/api/query-set/clause.md +++ b/docs/api/query-set/clause.md @@ -1,12 +1,6 @@ # queryset.clause - -#### FILTER\_OPERATORS - - -#### ESCAPE\_CHARACTERS - ## QueryClause Objects @@ -16,13 +10,6 @@ class QueryClause() Constructs where clauses from strings passed as arguments - -#### \_\_init\_\_ - -```python - | __init__(model_cls: Type["Model"], filter_clauses: List, select_related: List) -> None -``` - #### filter diff --git a/docs/api/query-set/filter-query.md b/docs/api/query-set/filter-query.md index 5a3b68e..fc81f6c 100644 --- a/docs/api/query-set/filter-query.md +++ b/docs/api/query-set/filter-query.md @@ -10,13 +10,6 @@ class FilterQuery() Modifies the select query with given list of where/filter clauses. - -#### \_\_init\_\_ - -```python - | __init__(filter_clauses: List, exclude: bool = False) -> None -``` - #### apply diff --git a/docs/api/query-set/join.md b/docs/api/query-set/join.md index 8519c54..fcf5b88 100644 --- a/docs/api/query-set/join.md +++ b/docs/api/query-set/join.md @@ -10,18 +10,6 @@ class JoinParameters(NamedTuple) Named tuple that holds set of parameters passed during join construction. - -#### prev\_model - - -#### previous\_alias - - -#### from\_table - - -#### model\_cls - ## SqlJoin Objects @@ -29,13 +17,6 @@ Named tuple that holds set of parameters passed during join construction. class SqlJoin() ``` - -#### \_\_init\_\_ - -```python - | __init__(used_aliases: List, select_from: sqlalchemy.sql.select, columns: List[sqlalchemy.Column], fields: Optional[Union[Set, Dict]], exclude_fields: Optional[Union[Set, Dict]], order_columns: Optional[List], sorted_orders: OrderedDict) -> None -``` - #### alias\_manager diff --git a/docs/api/query-set/limit-query.md b/docs/api/query-set/limit-query.md index 5e9e973..f505194 100644 --- a/docs/api/query-set/limit-query.md +++ b/docs/api/query-set/limit-query.md @@ -10,13 +10,6 @@ class LimitQuery() Modifies the select query with limit clause. - -#### \_\_init\_\_ - -```python - | __init__(limit_count: Optional[int]) -> None -``` - #### apply diff --git a/docs/api/query-set/offset-query.md b/docs/api/query-set/offset-query.md index be6cf64..90a0db2 100644 --- a/docs/api/query-set/offset-query.md +++ b/docs/api/query-set/offset-query.md @@ -10,13 +10,6 @@ class OffsetQuery() Modifies the select query with offset if set - -#### \_\_init\_\_ - -```python - | __init__(query_offset: Optional[int]) -> None -``` - #### apply diff --git a/docs/api/query-set/order-query.md b/docs/api/query-set/order-query.md index 6927644..abe79e4 100644 --- a/docs/api/query-set/order-query.md +++ b/docs/api/query-set/order-query.md @@ -10,13 +10,6 @@ class OrderQuery() Modifies the select query with given list of order_by clauses. - -#### \_\_init\_\_ - -```python - | __init__(sorted_orders: Dict) -> None -``` - #### apply diff --git a/docs/api/query-set/prefetch-query.md b/docs/api/query-set/prefetch-query.md index 102fdf5..d6ceea0 100644 --- a/docs/api/query-set/prefetch-query.md +++ b/docs/api/query-set/prefetch-query.md @@ -77,13 +77,6 @@ Query used to fetch related models in subsequent queries. Each model is fetched only ones by the name of the relation. That means that for each prefetch_related entry next query is issued to database. - -#### \_\_init\_\_ - -```python - | __init__(model_cls: Type["Model"], fields: Optional[Union[Dict, Set]], exclude_fields: Optional[Union[Dict, Set]], prefetch_related: List, select_related: List, orders_by: List) -> None -``` - #### prefetch\_related diff --git a/docs/api/query-set/query-set.md b/docs/api/query-set/query-set.md index e572a09..0821a66 100644 --- a/docs/api/query-set/query-set.md +++ b/docs/api/query-set/query-set.md @@ -10,20 +10,6 @@ class QuerySet() Main class to perform database queries, exposed on each model as objects attribute. - -#### \_\_init\_\_ - -```python - | __init__(model_cls: Type["Model"] = None, filter_clauses: List = None, exclude_clauses: List = None, select_related: List = None, limit_count: int = None, offset: int = None, columns: Dict = None, exclude_columns: Dict = None, order_bys: List = None, prefetch_related: List = None, limit_raw_sql: bool = False) -> None -``` - - -#### \_\_get\_\_ - -```python - | __get__(instance: Optional[Union["QuerySet", "QuerysetProxy"]], owner: Union[Type["Model"], Type["QuerysetProxy"]]) -> "QuerySet" -``` - #### model\_meta diff --git a/docs/api/query-set/query.md b/docs/api/query-set/query.md index 4166562..4715c36 100644 --- a/docs/api/query-set/query.md +++ b/docs/api/query-set/query.md @@ -8,13 +8,6 @@ class Query() ``` - -#### \_\_init\_\_ - -```python - | __init__(model_cls: Type["Model"], filter_clauses: List, exclude_clauses: List, select_related: List, limit_count: Optional[int], offset: Optional[int], fields: Optional[Union[Dict, Set]], exclude_fields: Optional[Union[Dict, Set]], order_bys: Optional[List], limit_raw_sql: bool) -> None -``` - #### \_init\_sorted\_orders diff --git a/docs/api/relations/alias-manager.md b/docs/api/relations/alias-manager.md index 4ffffe9..4190288 100644 --- a/docs/api/relations/alias-manager.md +++ b/docs/api/relations/alias-manager.md @@ -27,13 +27,6 @@ class AliasManager() Keep all aliases of relations between different tables. One global instance is shared between all models. - -#### \_\_init\_\_ - -```python - | __init__() -> None -``` - #### prefixed\_columns diff --git a/docs/api/relations/queryset-proxy.md b/docs/api/relations/queryset-proxy.md index 38cabb2..6df774d 100644 --- a/docs/api/relations/queryset-proxy.md +++ b/docs/api/relations/queryset-proxy.md @@ -11,13 +11,6 @@ class QuerysetProxy(ormar.QuerySetProtocol) Exposes QuerySet methods on relations, but also handles creating and removing of through Models for m2m relations. - -#### \_\_init\_\_ - -```python - | __init__(relation: "Relation", type_: "RelationType", qryset: "QuerySet" = None) -> None -``` - #### queryset diff --git a/docs/api/relations/relation-manager.md b/docs/api/relations/relation-manager.md index 818bdd9..21f5947 100644 --- a/docs/api/relations/relation-manager.md +++ b/docs/api/relations/relation-manager.md @@ -10,13 +10,6 @@ class RelationsManager() Manages relations on a Model, each Model has it's own instance. - -#### \_\_init\_\_ - -```python - | __init__(related_fields: List[Type[ForeignKeyField]] = None, owner: "NewBaseModel" = None) -> None -``` - #### \_get\_relation\_type diff --git a/docs/api/relations/relation-proxy.md b/docs/api/relations/relation-proxy.md index a25122a..b2716f7 100644 --- a/docs/api/relations/relation-proxy.md +++ b/docs/api/relations/relation-proxy.md @@ -10,13 +10,6 @@ class RelationProxy(list) Proxy of the Relation that is a list with special methods. - -#### \_\_init\_\_ - -```python - | __init__(relation: "Relation", type_: "RelationType", field_name: str, data_: Any = None) -> None -``` - #### related\_field\_name diff --git a/docs/api/relations/relation.md b/docs/api/relations/relation.md index 141d1b3..1c50b36 100644 --- a/docs/api/relations/relation.md +++ b/docs/api/relations/relation.md @@ -14,15 +14,6 @@ Different types of relations supported by ormar: * reverse ForeignKey = REVERSE * ManyToMany = MULTIPLE - -#### PRIMARY - - -#### REVERSE - - -#### MULTIPLE - ## Relation Objects @@ -119,10 +110,3 @@ Return the related model or models from RelationProxy. `(Optional[Union[List[Model], Model]])`: related model/models if set - -#### \_\_repr\_\_ - -```python - | __repr__() -> str -``` - diff --git a/docs/api/signals/signal.md b/docs/api/signals/signal.md index 547ecf8..cc09308 100644 --- a/docs/api/signals/signal.md +++ b/docs/api/signals/signal.md @@ -1,12 +1,3 @@ - -# signals - -Signals and SignalEmitter that gathers the signals on models Meta. -Used to signal receivers functions about events, i.e. post_save, pre_delete etc. - - -#### \_\_all\_\_ - # signals.signal @@ -54,13 +45,6 @@ class Signal() Signal that notifies all receiver functions. In ormar used by models to send pre_save, post_save etc. signals. - -#### \_\_init\_\_ - -```python - | __init__() -> None -``` - #### connect @@ -120,24 +104,3 @@ class SignalEmitter() Emitter that registers the signals in internal dictionary. If signal with given name does not exist it's auto added on access. - -#### \_\_init\_\_ - -```python - | __init__() -> None -``` - - -#### \_\_getattr\_\_ - -```python - | __getattr__(item: str) -> Signal -``` - - -#### \_\_setattr\_\_ - -```python - | __setattr__(key: str, value: Any) -> None -``` - diff --git a/ormar/queryset/query.py b/ormar/queryset/query.py index 761e08b..64b9ede 100644 --- a/ormar/queryset/query.py +++ b/ormar/queryset/query.py @@ -88,9 +88,9 @@ class Query: for clause in self.order_columns: if "__" not in clause: text_clause = ( - text(f"{self.alias(clause[1:])} desc") + text(f"{self.table.name}.{self.alias(clause[1:])} desc") if clause.startswith("-") - else text(self.alias(clause)) + else text(f"{self.table.name}.{self.alias(clause)}") ) self.sorted_orders[clause] = text_clause else: @@ -202,11 +202,7 @@ class Query: for filter_clause in self.exclude_clauses if filter_clause.text.startswith(f"{self.table.name}.") ] - sorts_to_use = { - k: v - for k, v in self.sorted_orders.items() - if k.startswith(f"{self.table.name}.") - } + sorts_to_use = {k: v for k, v in self.sorted_orders.items() if "__" not in k} expr = FilterQuery(filter_clauses=filters_to_use).apply(expr) expr = FilterQuery(filter_clauses=excludes_to_use, exclude=True).apply(expr) expr = OrderQuery(sorted_orders=sorts_to_use).apply(expr) diff --git a/ormar/queryset/queryset.py b/ormar/queryset/queryset.py index 599f4f0..3ab3489 100644 --- a/ormar/queryset/queryset.py +++ b/ormar/queryset/queryset.py @@ -658,7 +658,7 @@ class QuerySet: return await self.filter(**kwargs).first() expr = self.build_select_expression( - limit=1, order_bys=[f"{self.model.Meta.pkname}"] + limit=1, order_bys=[f"{self.model.Meta.pkname}"] + self.order_bys ) rows = await self.database.fetch_all(expr) processed_rows = self._process_query_result_rows(rows) @@ -687,7 +687,7 @@ class QuerySet: if not self.filter_clauses: expr = self.build_select_expression( - limit=1, order_bys=[f"-{self.model.Meta.pkname}"] + limit=1, order_bys=[f"-{self.model.Meta.pkname}"] + self.order_bys ) else: expr = self.build_select_expression() diff --git a/pydoc-markdown.yml b/pydoc-markdown.yml index 36dea6e..01ea1e8 100644 --- a/pydoc-markdown.yml +++ b/pydoc-markdown.yml @@ -4,7 +4,7 @@ loaders: search_path: [ormar/] processors: - type: filter - documented_only: false + documented_only: true skip_empty_modules: false exclude_private: false exclude_special: false diff --git a/tests/test_inheritance_concrete.py b/tests/test_inheritance_concrete.py index 90e4c35..2ac22ff 100644 --- a/tests/test_inheritance_concrete.py +++ b/tests/test_inheritance_concrete.py @@ -369,6 +369,10 @@ async def test_inheritance_with_multi_relation(): ).save() await truck.co_owners.add(joe) await truck.co_owners.add(alex) + + bus3 = await Bus2(name="Unicorn 3", max_persons=30, owner=joe).save() + await bus3.co_owners.add(sam) + bus = await Bus2(name="Unicorn 2", max_persons=50, owner=sam).save() await bus.co_owners.add(joe) await bus.co_owners.add(alex) @@ -380,13 +384,35 @@ async def test_inheritance_with_multi_relation(): assert len(shelby.co_owners) == 2 assert shelby.max_capacity == 1400 - unicorn = await Bus2.objects.select_related(["owner", "co_owners"]).get() + unicorn = await Bus2.objects.select_related(["owner", "co_owners"]).get( + name="Unicorn 2" + ) assert unicorn.name == "Unicorn 2" assert unicorn.owner.name == "Sam" assert unicorn.co_owners[0].name == "Joe" assert len(unicorn.co_owners) == 2 assert unicorn.max_persons == 50 + unicorn = ( + await Bus2.objects.select_related(["owner", "co_owners"]) + .order_by("-co_owners__name") + .get() + ) + assert unicorn.name == "Unicorn 2" + assert unicorn.owner.name == "Sam" + assert len(unicorn.co_owners) == 2 + assert unicorn.co_owners[0].name == "Joe" + + unicorn = ( + await Bus2.objects.select_related(["owner", "co_owners"]) + .order_by("co_owners__name") + .get() + ) + assert unicorn.name == "Unicorn 2" + assert unicorn.owner.name == "Sam" + assert len(unicorn.co_owners) == 2 + assert unicorn.co_owners[0].name == "Alex" + joe_check = await Person.objects.select_related( ["coowned_trucks2", "coowned_buses2"] ).get(name="Joe") @@ -411,3 +437,68 @@ async def test_inheritance_with_multi_relation(): assert joe_check.coowned_trucks2[0].created_date is None assert joe_check.coowned_buses2[0] == unicorn assert joe_check.coowned_buses2[0].created_date is None + + await shelby.co_owners.remove(joe) + await shelby.co_owners.remove(alex) + await Truck2.objects.delete(name="Shelby wanna be 2") + + unicorn = ( + await Bus2.objects.select_related(["owner", "co_owners"]) + .filter(co_owners__name="Joe") + .get() + ) + assert unicorn.name == "Unicorn 2" + assert unicorn.owner.name == "Sam" + assert unicorn.co_owners[0].name == "Joe" + assert len(unicorn.co_owners) == 1 + assert unicorn.max_persons == 50 + + unicorn = ( + await Bus2.objects.select_related(["owner", "co_owners"]) + .exclude(co_owners__name="Joe") + .get() + ) + assert unicorn.name == "Unicorn 2" + assert unicorn.owner.name == "Sam" + assert unicorn.co_owners[0].name == "Alex" + assert len(unicorn.co_owners) == 1 + assert unicorn.max_persons == 50 + + unicorn = await Bus2.objects.get() + assert unicorn.name == "Unicorn 2" + assert unicorn.owner.name is None + assert len(unicorn.co_owners) == 0 + await unicorn.co_owners.all() + + assert len(unicorn.co_owners) == 2 + assert unicorn.co_owners[0].name == "Joe" + + await unicorn.owner.load() + assert unicorn.owner.name == "Sam" + + unicorns = ( + await Bus2.objects.select_related(["owner", "co_owners"]) + .filter(name__contains="Unicorn") + .order_by("-name") + .all() + ) + assert unicorns[0].name == "Unicorn 3" + assert unicorns[0].owner.name == "Joe" + assert len(unicorns[0].co_owners) == 1 + assert unicorns[0].co_owners[0].name == "Sam" + + assert unicorns[1].name == "Unicorn 2" + assert unicorns[1].owner.name == "Sam" + assert len(unicorns[1].co_owners) == 2 + assert unicorns[1].co_owners[0].name == "Joe" + + unicorns = ( + await Bus2.objects.select_related(["owner", "co_owners"]) + .filter(name__contains="Unicorn") + .order_by("-name") + .limit(2, limit_raw_sql=True) + .all() + ) + assert len(unicorns) == 2 + assert unicorns[1].name == "Unicorn 2" + assert len(unicorns[1].co_owners) == 1