add docs
This commit is contained in:
@ -368,6 +368,51 @@ You can set this parameter by providing `Meta` class `constraints` argument.
|
||||
--8<-- "../docs_src/models/docs006.py"
|
||||
```
|
||||
|
||||
## Model sort order
|
||||
|
||||
When querying the database with given model by default the Model is ordered by the `primary_key`
|
||||
column ascending. If you wish to change the default behaviour you can do it by providing `orders_by`
|
||||
parameter to model `Meta` class.
|
||||
|
||||
Sample default ordering:
|
||||
```python
|
||||
database = databases.Database(DATABASE_URL)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
# default sort by column id ascending
|
||||
class Author(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "authors"
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
```
|
||||
Modified
|
||||
```python
|
||||
|
||||
database = databases.Database(DATABASE_URL)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
# now default sort by name descending
|
||||
class Author(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "authors"
|
||||
orders_by = ["-name"]
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
```
|
||||
|
||||
## Model Initialization
|
||||
|
||||
There are two ways to create and persist the `Model` instance in the database.
|
||||
|
||||
@ -1,15 +1,23 @@
|
||||
# Aggregation functions
|
||||
|
||||
Currently 2 aggregation functions are supported.
|
||||
Currently 6 aggregation functions are supported.
|
||||
|
||||
|
||||
* `count() -> int`
|
||||
* `exists() -> bool`
|
||||
* `sum(columns) -> Any`
|
||||
* `avg(columns) -> Any`
|
||||
* `min(columns) -> Any`
|
||||
* `max(columns) -> Any`
|
||||
|
||||
|
||||
* `QuerysetProxy`
|
||||
* `QuerysetProxy.count()` method
|
||||
* `QuerysetProxy.exists()` method
|
||||
* `QuerysetProxy.sum(columns)` method
|
||||
* `QuerysetProxy.avg(columns)` method
|
||||
* `QuerysetProxy.min(column)` method
|
||||
* `QuerysetProxy.max(columns)` method
|
||||
|
||||
|
||||
## count
|
||||
@ -68,6 +76,209 @@ class Book(ormar.Model):
|
||||
has_sample = await Book.objects.filter(title='Sample').exists()
|
||||
```
|
||||
|
||||
## sum
|
||||
|
||||
`sum(columns) -> Any`
|
||||
|
||||
Returns sum value of columns for rows matching the given criteria (applied with `filter` and `exclude` if set before).
|
||||
|
||||
You can pass one or many column names including related columns.
|
||||
|
||||
As of now each column passed is aggregated separately (so `sum(col1+col2)` is not possible,
|
||||
you can have `sum(col1, col2)` and later add 2 returned sums in python)
|
||||
|
||||
You cannot `sum` non numeric columns.
|
||||
|
||||
If you aggregate on one column, the single value is directly returned as a result
|
||||
If you aggregate on multiple columns a dictionary with column: result pairs is returned
|
||||
|
||||
Given models like follows
|
||||
|
||||
```Python
|
||||
--8<-- "../docs_src/aggregations/docs001.py"
|
||||
```
|
||||
|
||||
A sample usage might look like following
|
||||
|
||||
```python
|
||||
author = await Author(name="Author 1").save()
|
||||
await Book(title="Book 1", year=1920, ranking=3, author=author).save()
|
||||
await Book(title="Book 2", year=1930, ranking=1, author=author).save()
|
||||
await Book(title="Book 3", year=1923, ranking=5, author=author).save()
|
||||
|
||||
assert await Book.objects.sum("year") == 5773
|
||||
result = await Book.objects.sum(["year", "ranking"])
|
||||
assert result == dict(year=5773, ranking=9)
|
||||
|
||||
try:
|
||||
# cannot sum string column
|
||||
await Book.objects.sum("title")
|
||||
except ormar.QueryDefinitionError:
|
||||
pass
|
||||
|
||||
assert await Author.objects.select_related("books").sum("books__year") == 5773
|
||||
result = await Author.objects.select_related("books").sum(
|
||||
["books__year", "books__ranking"]
|
||||
)
|
||||
assert result == dict(books__year=5773, books__ranking=9)
|
||||
|
||||
assert (
|
||||
await Author.objects.select_related("books")
|
||||
.filter(books__year__lt=1925)
|
||||
.sum("books__year")
|
||||
== 3843
|
||||
)
|
||||
```
|
||||
|
||||
## avg
|
||||
|
||||
`avg(columns) -> Any`
|
||||
|
||||
Returns avg value of columns for rows matching the given criteria (applied with `filter` and `exclude` if set before).
|
||||
|
||||
You can pass one or many column names including related columns.
|
||||
|
||||
As of now each column passed is aggregated separately (so `sum(col1+col2)` is not possible,
|
||||
you can have `sum(col1, col2)` and later add 2 returned sums in python)
|
||||
|
||||
You cannot `avg` non numeric columns.
|
||||
|
||||
If you aggregate on one column, the single value is directly returned as a result
|
||||
If you aggregate on multiple columns a dictionary with column: result pairs is returned
|
||||
|
||||
```Python
|
||||
--8<-- "../docs_src/aggregations/docs001.py"
|
||||
```
|
||||
|
||||
A sample usage might look like following
|
||||
|
||||
```python
|
||||
author = await Author(name="Author 1").save()
|
||||
await Book(title="Book 1", year=1920, ranking=3, author=author).save()
|
||||
await Book(title="Book 2", year=1930, ranking=1, author=author).save()
|
||||
await Book(title="Book 3", year=1923, ranking=5, author=author).save()
|
||||
|
||||
assert round(float(await Book.objects.avg("year")), 2) == 1924.33
|
||||
result = await Book.objects.avg(["year", "ranking"])
|
||||
assert round(float(result.get("year")), 2) == 1924.33
|
||||
assert result.get("ranking") == 3.0
|
||||
|
||||
try:
|
||||
# cannot avg string column
|
||||
await Book.objects.avg("title")
|
||||
except ormar.QueryDefinitionError:
|
||||
pass
|
||||
|
||||
result = await Author.objects.select_related("books").avg("books__year")
|
||||
assert round(float(result), 2) == 1924.33
|
||||
result = await Author.objects.select_related("books").avg(
|
||||
["books__year", "books__ranking"]
|
||||
)
|
||||
assert round(float(result.get("books__year")), 2) == 1924.33
|
||||
assert result.get("books__ranking") == 3.0
|
||||
|
||||
assert (
|
||||
await Author.objects.select_related("books")
|
||||
.filter(books__year__lt=1925)
|
||||
.avg("books__year")
|
||||
== 1921.5
|
||||
)
|
||||
```
|
||||
|
||||
## min
|
||||
|
||||
`min(columns) -> Any`
|
||||
|
||||
Returns min value of columns for rows matching the given criteria (applied with `filter` and `exclude` if set before).
|
||||
|
||||
You can pass one or many column names including related columns.
|
||||
|
||||
As of now each column passed is aggregated separately (so `sum(col1+col2)` is not possible,
|
||||
you can have `sum(col1, col2)` and later add 2 returned sums in python)
|
||||
|
||||
If you aggregate on one column, the single value is directly returned as a result
|
||||
If you aggregate on multiple columns a dictionary with column: result pairs is returned
|
||||
|
||||
```Python
|
||||
--8<-- "../docs_src/aggregations/docs001.py"
|
||||
```
|
||||
|
||||
A sample usage might look like following
|
||||
|
||||
```python
|
||||
author = await Author(name="Author 1").save()
|
||||
await Book(title="Book 1", year=1920, ranking=3, author=author).save()
|
||||
await Book(title="Book 2", year=1930, ranking=1, author=author).save()
|
||||
await Book(title="Book 3", year=1923, ranking=5, author=author).save()
|
||||
|
||||
assert await Book.objects.min("year") == 1920
|
||||
result = await Book.objects.min(["year", "ranking"])
|
||||
assert result == dict(year=1920, ranking=1)
|
||||
|
||||
assert await Book.objects.min("title") == "Book 1"
|
||||
|
||||
assert await Author.objects.select_related("books").min("books__year") == 1920
|
||||
result = await Author.objects.select_related("books").min(
|
||||
["books__year", "books__ranking"]
|
||||
)
|
||||
assert result == dict(books__year=1920, books__ranking=1)
|
||||
|
||||
assert (
|
||||
await Author.objects.select_related("books")
|
||||
.filter(books__year__gt=1925)
|
||||
.min("books__year")
|
||||
== 1930
|
||||
)
|
||||
```
|
||||
|
||||
## max
|
||||
|
||||
`max(columns) -> Any`
|
||||
|
||||
Returns max value of columns for rows matching the given criteria (applied with `filter` and `exclude` if set before).
|
||||
|
||||
Returns min value of columns for rows matching the given criteria (applied with `filter` and `exclude` if set before).
|
||||
|
||||
You can pass one or many column names including related columns.
|
||||
|
||||
As of now each column passed is aggregated separately (so `sum(col1+col2)` is not possible,
|
||||
you can have `sum(col1, col2)` and later add 2 returned sums in python)
|
||||
|
||||
If you aggregate on one column, the single value is directly returned as a result
|
||||
If you aggregate on multiple columns a dictionary with column: result pairs is returned
|
||||
|
||||
```Python
|
||||
--8<-- "../docs_src/aggregations/docs001.py"
|
||||
```
|
||||
|
||||
A sample usage might look like following
|
||||
|
||||
```python
|
||||
author = await Author(name="Author 1").save()
|
||||
await Book(title="Book 1", year=1920, ranking=3, author=author).save()
|
||||
await Book(title="Book 2", year=1930, ranking=1, author=author).save()
|
||||
await Book(title="Book 3", year=1923, ranking=5, author=author).save()
|
||||
|
||||
assert await Book.objects.max("year") == 1930
|
||||
result = await Book.objects.max(["year", "ranking"])
|
||||
assert result == dict(year=1930, ranking=5)
|
||||
|
||||
assert await Book.objects.max("title") == "Book 3"
|
||||
|
||||
assert await Author.objects.select_related("books").max("books__year") == 1930
|
||||
result = await Author.objects.select_related("books").max(
|
||||
["books__year", "books__ranking"]
|
||||
)
|
||||
assert result == dict(books__year=1930, books__ranking=5)
|
||||
|
||||
assert (
|
||||
await Author.objects.select_related("books")
|
||||
.filter(books__year__lt=1925)
|
||||
.max("books__year")
|
||||
== 1923
|
||||
)
|
||||
```
|
||||
|
||||
## QuerysetProxy methods
|
||||
|
||||
When access directly the related `ManyToMany` field as well as `ReverseForeignKey`
|
||||
@ -89,6 +300,26 @@ objects from other side of the relation.
|
||||
Works exactly the same as [exists](./#exists) function above but allows you to select columns from related
|
||||
objects from other side of the relation.
|
||||
|
||||
### sum
|
||||
|
||||
Works exactly the same as [sum](./#sum) function above but allows you to sum columns from related
|
||||
objects from other side of the relation.
|
||||
|
||||
### avg
|
||||
|
||||
Works exactly the same as [avg](./#avg) function above but allows you to average columns from related
|
||||
objects from other side of the relation.
|
||||
|
||||
### min
|
||||
|
||||
Works exactly the same as [min](./#min) function above but allows you to select minimum of columns from related
|
||||
objects from other side of the relation.
|
||||
|
||||
### max
|
||||
|
||||
Works exactly the same as [max](./#max) function above but allows you to select maximum of columns from related
|
||||
objects from other side of the relation.
|
||||
|
||||
!!!tip
|
||||
To read more about `QuerysetProxy` visit [querysetproxy][querysetproxy] section
|
||||
|
||||
|
||||
@ -289,7 +289,7 @@ books = (
|
||||
```
|
||||
|
||||
If you want or need to you can nest deeper conditions as deep as you want, in example to
|
||||
acheive a query like this:
|
||||
achieve a query like this:
|
||||
|
||||
sql:
|
||||
```
|
||||
@ -564,6 +564,38 @@ assert owner.toys[1].name == "Toy 1"
|
||||
|
||||
Something like `Track.object.select_related("album").filter(album__name="Malibu").offset(1).limit(1).all()`
|
||||
|
||||
### Default sorting in ormar
|
||||
|
||||
Since order of rows in a database is not guaranteed, `ormar` **always** issues an `order by` sql clause to each (part of) query even if you do not provide order yourself.
|
||||
|
||||
When querying the database with given model by default the `Model` is ordered by the `primary_key`
|
||||
column ascending. If you wish to change the default behaviour you can do it by providing `orders_by`
|
||||
parameter to model `Meta` class.
|
||||
|
||||
!!!tip
|
||||
To read more about models sort order visit [models](../models/index.md#model-sort-order) section of documentation
|
||||
|
||||
By default the relations follow the same ordering, but you can modify the order in which related models are loaded during query by providing `orders_by` and `related_orders_by`
|
||||
parameters to relations.
|
||||
|
||||
!!!tip
|
||||
To read more about models sort order visit [relations](../relations/index.md#relationship-default-sort-order) section of documentation
|
||||
|
||||
Order in which order_by clauses are applied is as follows:
|
||||
|
||||
* Explicitly passed `order_by()` calls in query
|
||||
* Relation passed `orders_by` and `related_orders_by` if exists
|
||||
* Model `Meta` class `orders_by`
|
||||
* Model `primary_key` column ascending (fallback, used if none of above provided)
|
||||
|
||||
**Order from only one source is applied to each `Model` (so that you can always overwrite it in a single query).**
|
||||
|
||||
That means that if you provide explicit `order_by` for a model in a query, the `Relation` and `Model` sort orders are skipped.
|
||||
|
||||
If you provide a `Relation` one, the `Model` sort is skipped.
|
||||
|
||||
Finally, if you provide one for `Model` the default one by `primary_key` is skipped.
|
||||
|
||||
### QuerysetProxy methods
|
||||
|
||||
When access directly the related `ManyToMany` field as well as `ReverseForeignKey`
|
||||
|
||||
@ -128,6 +128,58 @@ class Post(ormar.Model):
|
||||
|
||||
It allows you to use `await post.categories.all()` but also `await category.posts.all()` to fetch data related only to specific post, category etc.
|
||||
|
||||
## Relationship default sort order
|
||||
|
||||
By default relations follow model default sort order so `primary_key` column ascending, or any sort order se in `Meta` class.
|
||||
|
||||
!!!tip
|
||||
To read more about models sort order visit [models](../models/index.md#model-sort-order) section of documentation
|
||||
|
||||
But you can modify the order in which related models are loaded during query by providing `orders_by` and `related_orders_by`
|
||||
parameters to relations.
|
||||
|
||||
In relations you can sort only by directly related model columns or for `ManyToMany`
|
||||
columns also `Through` model columns `{through_field_name}__{column_name}`
|
||||
|
||||
Sample configuration might look like this:
|
||||
|
||||
```python hl_lines="24"
|
||||
database = databases.Database(DATABASE_URL)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
|
||||
class Author(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "authors"
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Book(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "books"
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
author: Optional[Author] = ormar.ForeignKey(
|
||||
Author, orders_by=["name"], related_orders_by=["-year"]
|
||||
)
|
||||
title: str = ormar.String(max_length=100)
|
||||
year: int = ormar.Integer(nullable=True)
|
||||
ranking: int = ormar.Integer(nullable=True)
|
||||
```
|
||||
|
||||
Now calls:
|
||||
|
||||
`await Author.objects.select_related("books").get()` - the books will be sorted by the book year descending
|
||||
|
||||
`await Book.objects.select_related("author").all()` - the authors will be sorted by author name ascending
|
||||
|
||||
## Self-reference and postponed references
|
||||
|
||||
In order to create auto-relation or create two models that reference each other in at least two
|
||||
|
||||
@ -192,6 +192,47 @@ Send for `Model.update()` method.
|
||||
|
||||
`sender` is a `ormar.Model` class and `instance` is the model that was deleted.
|
||||
|
||||
### pre_relation_add
|
||||
|
||||
`pre_relation_add(sender: Type["Model"], instance: "Model", child: "Model",
|
||||
relation_name: str, passed_args: Dict)`
|
||||
|
||||
Send for `Model.relation_name.add()` method for `ManyToMany` relations and reverse side of `ForeignKey` relation.
|
||||
|
||||
`sender` - sender class, `instance` - instance to which related model is added, `child` - model being added,
|
||||
`relation_name` - name of the relation to which child is added, for add signals also `passed_kwargs` - dict of kwargs passed to `add()`
|
||||
|
||||
### post_relation_add
|
||||
|
||||
`post_relation_add(sender: Type["Model"], instance: "Model", child: "Model",
|
||||
relation_name: str, passed_args: Dict)`
|
||||
|
||||
Send for `Model.relation_name.add()` method for `ManyToMany` relations and reverse side of `ForeignKey` relation.
|
||||
|
||||
`sender` - sender class, `instance` - instance to which related model is added, `child` - model being added,
|
||||
`relation_name` - name of the relation to which child is added, for add signals also `passed_kwargs` - dict of kwargs passed to `add()`
|
||||
|
||||
### pre_relation_remove
|
||||
|
||||
`pre_relation_remove(sender: Type["Model"], instance: "Model", child: "Model",
|
||||
relation_name: str)`
|
||||
|
||||
Send for `Model.relation_name.remove()` method for `ManyToMany` relations and reverse side of `ForeignKey` relation.
|
||||
|
||||
`sender` - sender class, `instance` - instance to which related model is added, `child` - model being added,
|
||||
`relation_name` - name of the relation to which child is added.
|
||||
|
||||
### post_relation_remove
|
||||
|
||||
`post_relation_remove(sender: Type["Model"], instance: "Model", child: "Model",
|
||||
relation_name: str, passed_args: Dict)`
|
||||
|
||||
Send for `Model.relation_name.remove()` method for `ManyToMany` relations and reverse side of `ForeignKey` relation.
|
||||
|
||||
`sender` - sender class, `instance` - instance to which related model is added, `child` - model being added,
|
||||
`relation_name` - name of the relation to which child is added.
|
||||
|
||||
|
||||
## Defining your own signals
|
||||
|
||||
Note that you can create your own signals although you will have to send them manually in your code or subclass `ormar.Model`
|
||||
|
||||
0
docs_src/aggregations/__init__.py
Normal file
0
docs_src/aggregations/__init__.py
Normal file
36
docs_src/aggregations/docs001.py
Normal file
36
docs_src/aggregations/docs001.py
Normal file
@ -0,0 +1,36 @@
|
||||
from typing import Optional
|
||||
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
|
||||
class Author(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "authors"
|
||||
order_by = ["-name"]
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Book(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "books"
|
||||
order_by = ["year", "-ranking"]
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
author: Optional[Author] = ormar.ForeignKey(Author)
|
||||
title: str = ormar.String(max_length=100)
|
||||
year: int = ormar.Integer(nullable=True)
|
||||
ranking: int = ormar.Integer(nullable=True)
|
||||
@ -71,9 +71,16 @@ class Query:
|
||||
self.sorted_orders[clause] = clause.get_text_clause()
|
||||
|
||||
if not current_table_sorted:
|
||||
for order_by in self.model_cls.Meta.orders_by:
|
||||
clause = ormar.OrderAction(order_str=order_by, model_cls=self.model_cls)
|
||||
self.sorted_orders[clause] = clause.get_text_clause()
|
||||
self._apply_default_model_sorting()
|
||||
|
||||
def _apply_default_model_sorting(self) -> 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
|
||||
"""
|
||||
for order_by in self.model_cls.Meta.orders_by:
|
||||
clause = ormar.OrderAction(order_str=order_by, model_cls=self.model_cls)
|
||||
self.sorted_orders[clause] = clause.get_text_clause()
|
||||
|
||||
def _pagination_query_required(self) -> bool:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user