* WIP * WIP - make test_model_definition tests pass * WIP - make test_model_methods pass * WIP - make whole test suit at least run - failing 49/443 tests * WIP fix part of the getting pydantic tests as types of fields are now kept in core schema and not on fieldsinfo * WIP fix validation in update by creating individual fields validators, failing 36/443 * WIP fix __pydantic_extra__ in intializing model, fix test related to pydantic config checks, failing 32/442 * WIP - fix enum schema in model_json_schema, failing 31/442 * WIP - fix copying through model, fix setting pydantic fields on through, fix default config and inheriting from it, failing 26/442 * WIP fix tests checking pydantic schema, fix excluding parent fields, failing 21/442 * WIP some missed files * WIP - fix validators inheritance and fix validators in generated pydantic, failing 17/442 * WIP - fix through models setting - only on reverse side of relation, but always on reverse side, failing 15/442 * WIP - fix through models setting - only on reverse side of relation, but always on reverse side, failing 15/442 * WIP - working on proper populating __dict__ for relations for new schema dumping, some work on openapi docs, failing 13/442 * WIP - remove property fields as pydantic has now computed_field on its own, failing 9/442 * WIP - fixes in docs, failing 8/442 * WIP - fix tests for largebinary schema, wrapped bytes fields fail in pydantic, will be fixed in pydantic-core, remaining is circural schema for related models, failing 6/442 * WIP - fix to pk only models in schemas * Getting test suites to pass (#1249) * wip, fixing tests * iteration, fixing some more tests * iteration, fixing some more tests * adhere to comments * adhere to comments * remove unnecessary dict call, re-add getattribute for testing * todo for reverse relationship * adhere to comments, remove prints * solve circular refs * all tests pass 🎉 * remove 3.7 from tests * add lint and type check jobs * reforat with ruff, fix jobs * rename jobs * fix imports * fix evaluate in py3.8 * partially fix coverage * fix coverage, add more tests * fix test ids * fix test ids * fix lint, fix docs, make docs fully working scripts, add test docs job * fix pyproject * pin py ver in test docs * change dir in test docs * fix pydantic warning hack * rm poetry call in test_docs * switch to pathlib in test docs * remove coverage req test docs * fix type check tests, fix part of types * fix/skip next part of types * fix next part of types * fix next part of types * fix coverage * fix coverage * fix type (bit dirty 🤷) * fix some code smells * change pre-commit * tweak workflows * remove no root from tests * switch to full python path by passing sys.executable * some small refactor in new base model, one sample test, change makefile * small refactors to reduce complexity of methods * temp add tests for prs against pydantic_v2 * remove all references to __fields__ * remove all references to construct, deprecate the method and update model_construct to be in line with pydantic * deprecate dict and add model_dump, todo switch to model_dict in calls * fix tests * change to union * change to union * change to model_dump and model_dump_json from dict and json deprecated methods, deprecate them in ormar too * finish switching dict() -> model_dump() * finish switching json() -> model_dump_json() * remove fully pydantic_only * switch to extra for payment card, change missed json calls * fix coverage - no more warnings internal * fix coverage - no more warnings internal - part 2 * split model_construct into own and pydantic parts * split determine pydantic field type * change to new field validators * fix benchmarks, add codspeed instead of pytest-benchmark, add action and gh workflow * restore pytest-benchmark * remove codspeed * pin pydantic version, restore codspeed * change on push to pydantic_v2 to trigger first one * Use lifespan function instead of event (#1259) * check return types * fix imports order, set warnings=False on json that passes the dict, fix unnecessary loop in one of the test * remove references to model's meta as it's now ormar config, rename related methods too * filter out pydantic serializer warnings * remove choices leftovers * remove leftovers after property_fields, keep only enough to exclude them in initialization * add migration guide * fix meta references * downgrade databases for now * Change line numbers in documentation (#1265) * proofread and fix the docs, part 1 * proofread and fix the docs for models * proofread and fix the docs for fields * proofread and fix the docs for relations * proofread and fix rest of the docs, add release notes for 0.20 * create tables in new docs src * cleanup old deps, uncomment docs publish on tag * fix import reorder --------- Co-authored-by: TouwaStar <30479449+TouwaStar@users.noreply.github.com> Co-authored-by: Goran Mekić <meka@tilda.center>
331 lines
9.8 KiB
Markdown
331 lines
9.8 KiB
Markdown
# QuerySetProxy
|
|
|
|
When access directly the related `ManyToMany` field as well as `ReverseForeignKey` returns the list of related models.
|
|
|
|
But at the same time it exposes subset of QuerySet API, so you can filter, create, select related etc related models directly from parent model.
|
|
|
|
!!!note
|
|
By default exposed QuerySet is already filtered to return only `Models` related to parent `Model`.
|
|
|
|
So if you issue `post.categories.all()` you will get all categories related to that post, not all in table.
|
|
|
|
!!!note
|
|
Note that when accessing QuerySet API methods through QuerysetProxy you don't
|
|
need to use `objects` attribute like in normal queries.
|
|
|
|
So note that it's `post.categories.all()` and **not** `post.categories.objects.all()`.
|
|
|
|
To learn more about available QuerySet methods visit [queries][queries]
|
|
|
|
!!!warning
|
|
Querying related models from ManyToMany cleans list of related models loaded on parent model:
|
|
|
|
Example: `post.categories.first()` will set post.categories to list of 1 related model -> the one returned by first()
|
|
|
|
Example 2: if post has 4 categories so `len(post.categories) == 4` calling `post.categories.limit(2).all()`
|
|
-> will load only 2 children and now `assert len(post.categories) == 2`
|
|
|
|
This happens for all QuerysetProxy methods returning data: `get`, `all` and `first` and in `get_or_create` if model already exists.
|
|
|
|
Note that value returned by `create` or created in `get_or_create` and `update_or_create`
|
|
if model does not exist will be added to relation list (not clearing it).
|
|
|
|
## Read data from database
|
|
|
|
### get
|
|
|
|
`get(**kwargs): -> Model`
|
|
|
|
To grab just one of related models filtered by name you can use `get(**kwargs)` method.
|
|
|
|
```python
|
|
# grab one category
|
|
assert news == await post.categories.get(name="News")
|
|
|
|
# note that method returns the category so you can grab this value
|
|
# but it also modifies list of related models in place
|
|
# so regardless of what was previously loaded on parent model
|
|
# now it has only one value -> just loaded with get() call
|
|
assert len(post.categories) == 1
|
|
assert post.categories[0] == news
|
|
|
|
```
|
|
|
|
!!!tip
|
|
Read more in queries documentation [get][get]
|
|
|
|
### get_or_create
|
|
|
|
`get_or_create(_defaults: Optional[Dict[str, Any]] = None, **kwargs) -> Tuple[Model, bool]`
|
|
|
|
Tries to get a row meeting the criteria and if `NoMatch` exception is raised it creates a new one with given kwargs and _defaults.
|
|
|
|
!!!tip
|
|
Read more in queries documentation [get_or_create][get_or_create]
|
|
|
|
### all
|
|
|
|
`all(**kwargs) -> List[Optional["Model"]]`
|
|
|
|
To get a list of related models use `all()` method.
|
|
|
|
Note that you can filter the queryset, select related, exclude fields etc. like in normal query.
|
|
|
|
```python
|
|
# with all Queryset methods - filtering, selecting columns, counting etc.
|
|
await news.posts.filter(title__contains="M2M").all()
|
|
await Category.objects.filter(posts__author=guido).get()
|
|
|
|
# columns models of many to many relation can be prefetched
|
|
news_posts = await news.posts.select_related("author").all()
|
|
assert news_posts[0].author == guido
|
|
```
|
|
|
|
!!!tip
|
|
Read more in queries documentation [all][all]
|
|
|
|
### iterate
|
|
|
|
`iterate(**kwargs) -> AsyncGenerator["Model"]`
|
|
|
|
To iterate on related models use `iterate()` method.
|
|
|
|
Note that you can filter the queryset, select related, exclude fields etc. like in normal query.
|
|
|
|
```python
|
|
# iterate on categories of this post with an async generator
|
|
async for category in post.categories.iterate():
|
|
print(category.name)
|
|
```
|
|
|
|
!!!tip
|
|
Read more in queries documentation [iterate][iterate]
|
|
|
|
## Insert/ update data into database
|
|
|
|
### create
|
|
|
|
`create(**kwargs): -> Model`
|
|
|
|
Create related `Model` directly from parent `Model`.
|
|
|
|
The link table is automatically populated, as well as relation ids in the database.
|
|
|
|
```python
|
|
# Creating columns object from instance:
|
|
await post.categories.create(name="Tips")
|
|
assert len(await post.categories.all()) == 2
|
|
# newly created instance already have relation persisted in the database
|
|
```
|
|
|
|
!!!tip
|
|
Read more in queries documentation [create][create]
|
|
|
|
For `ManyToMany` relations there is an additional functionality of passing parameters
|
|
that will be used to create a through model if you declared additional fields on explicitly
|
|
provided Through model.
|
|
|
|
Given sample like this:
|
|
|
|
```Python hl_lines="19-24 32"
|
|
--8<-- "../docs_src/relations/docs004.py"
|
|
```
|
|
|
|
You can populate fields on through model in the `create()` call in a following way:
|
|
|
|
```python
|
|
|
|
post = await Post(title="Test post").save()
|
|
await post.categories.create(
|
|
name="Test category1",
|
|
# in arguments pass a dictionary with name of the through field and keys
|
|
# corresponding to through model fields
|
|
postcategory={"sort_order": 1, "param_name": "volume"},
|
|
)
|
|
```
|
|
|
|
### get_or_create
|
|
|
|
`get_or_create(_defaults: Optional[Dict[str, Any]] = None, **kwargs) -> Tuple[Model, bool]`
|
|
|
|
Tries to get a row meeting the criteria and if NoMatch exception is raised it creates a new one with given kwargs.
|
|
|
|
!!!tip
|
|
Read more in queries documentation [get_or_create][get_or_create]
|
|
|
|
### update_or_create
|
|
|
|
`update_or_create(**kwargs) -> Model`
|
|
|
|
Updates the model, or in case there is no match in database creates a new one.
|
|
|
|
!!!tip
|
|
Read more in queries documentation [update_or_create][update_or_create]
|
|
|
|
### update
|
|
|
|
`update(**kwargs, each:bool = False) -> int`
|
|
|
|
Updates the related model with provided keyword arguments, return number of updated rows.
|
|
|
|
!!!tip
|
|
Read more in queries documentation [update][update]
|
|
|
|
Note that for `ManyToMany` relations update can also accept an argument with through field
|
|
name and a dictionary of fields.
|
|
|
|
```Python hl_lines="19-24 32"
|
|
--8<-- "../docs_src/relations/docs004.py"
|
|
```
|
|
|
|
In example above you can update attributes of `postcategory` in a following call:
|
|
```python
|
|
await post.categories.filter(name="Test category3").update(
|
|
postcategory={"sort_order": 4}
|
|
)
|
|
```
|
|
|
|
## Filtering and sorting
|
|
|
|
### filter
|
|
|
|
`filter(*args, **kwargs) -> QuerySet`
|
|
|
|
Allows you to filter by any Model attribute/field as well as to fetch instances, with a filter across an FK relationship.
|
|
|
|
!!!tip
|
|
Read more in queries documentation [filter][filter]
|
|
|
|
### exclude
|
|
|
|
`exclude(*args, **kwargs) -> QuerySet`
|
|
|
|
Works exactly the same as filter and all modifiers (suffixes) are the same, but returns a not condition.
|
|
|
|
!!!tip
|
|
Read more in queries documentation [exclude][exclude]
|
|
|
|
### order_by
|
|
|
|
`order_by(columns:Union[List, str]) -> QuerySet`
|
|
|
|
With order_by() you can order the results from database based on your choice of fields.
|
|
|
|
!!!tip
|
|
Read more in queries documentation [order_by][order_by]
|
|
|
|
## Joins and subqueries
|
|
|
|
### select_related
|
|
|
|
`select_related(related: Union[List, str]) -> QuerySet`
|
|
|
|
Allows to prefetch related models during the same query.
|
|
|
|
With select_related always only one query is run against the database, meaning that one (sometimes complicated) join is generated and later nested models are processed in python.
|
|
|
|
!!!tip
|
|
Read more in queries documentation [select_related][select_related]
|
|
|
|
### prefetch_related
|
|
|
|
`prefetch_related(related: Union[List, str]) -> QuerySet`
|
|
|
|
Allows to prefetch related models during query - but opposite to select_related each subsequent model is fetched in a separate database query.
|
|
|
|
With prefetch_related always one query per Model is run against the database, meaning that you will have multiple queries executed one after another.
|
|
|
|
!!!tip
|
|
Read more in queries documentation [prefetch_related][prefetch_related]
|
|
|
|
## Pagination and rows number
|
|
|
|
### paginate
|
|
|
|
`paginate(page: int, page_size: int = 20) -> QuerySet`
|
|
|
|
Combines the offset and limit methods based on page number and size.
|
|
|
|
!!!tip
|
|
Read more in queries documentation [paginate][paginate]
|
|
|
|
### limit
|
|
|
|
`limit(limit_count: int) -> QuerySet`
|
|
|
|
You can limit the results to desired number of parent models.
|
|
|
|
!!!tip
|
|
Read more in queries documentation [limit][limit]
|
|
|
|
### offset
|
|
|
|
`offset(offset: int) -> QuerySet`
|
|
|
|
You can offset the results by desired number of main models.
|
|
|
|
!!!tip
|
|
Read more in queries documentation [offset][offset]
|
|
|
|
## Selecting subset of columns
|
|
|
|
### fields
|
|
|
|
`fields(columns: Union[List, str, set, dict]) -> QuerySet`
|
|
|
|
With fields() you can select subset of model columns to limit the data load.
|
|
|
|
!!!tip
|
|
Read more in queries documentation [fields][fields]
|
|
|
|
### exclude_fields
|
|
|
|
`exclude_fields(columns: Union[List, str, set, dict]) -> QuerySet`
|
|
|
|
With exclude_fields() you can select subset of model columns that will be excluded to limit the data load.
|
|
|
|
!!!tip
|
|
Read more in queries documentation [exclude_fields][exclude_fields]
|
|
|
|
## Aggregated functions
|
|
|
|
### count
|
|
|
|
`count(distinct: bool = True) -> int`
|
|
|
|
Returns number of rows matching the given criteria (i.e. applied with filter and exclude)
|
|
|
|
!!!tip
|
|
Read more in queries documentation [count][count]
|
|
|
|
### exists
|
|
|
|
`exists() -> bool`
|
|
|
|
Returns a bool value to confirm if there are rows matching the given criteria (applied with filter and exclude)
|
|
|
|
!!!tip
|
|
Read more in queries documentation [exists][exists]
|
|
|
|
|
|
[queries]: ../queries/index.md
|
|
[get]: ../queries/read.md#get
|
|
[all]: ../queries/read.md#all
|
|
[iterate]: ../queries/read.md#iterate
|
|
[create]: ../queries/create.md#create
|
|
[get_or_create]: ../queries/read.md#get_or_create
|
|
[update_or_create]: ../queries/update.md#update_or_create
|
|
[update]: ../queries/update.md#update
|
|
[filter]: ../queries/filter-and-sort.md#filter
|
|
[exclude]: ../queries/filter-and-sort.md#exclude
|
|
[select_related]: ../queries/joins-and-subqueries.md#select_related
|
|
[prefetch_related]: ../queries/joins-and-subqueries.md#prefetch_related
|
|
[limit]: ../queries/pagination-and-rows-number.md#limit
|
|
[offset]: ../queries/pagination-and-rows-number.md#offset
|
|
[paginate]: ../queries/pagination-and-rows-number.md#paginate
|
|
[count]: ../queries/aggregations.md#count
|
|
[exists]: ../queries/aggregations.md#exists
|
|
[fields]: ../queries/select-columns.md#fields
|
|
[exclude_fields]: ../queries/select-columns.md#exclude_fields
|
|
[order_by]: ../queries/filter-and-sort.md#order_by
|