update docs and readme

This commit is contained in:
collerek
2021-04-21 11:28:49 +02:00
parent ac4712f87c
commit c11e1a870f
10 changed files with 408 additions and 94 deletions

View File

@ -220,7 +220,10 @@ async def create():
async def read(): async def read():
# Fetch an instance, without loading a foreign key relationship on it. # Fetch an instance, without loading a foreign key relationship on it.
# Django style
book = await Book.objects.get(title="The Hobbit") book = await Book.objects.get(title="The Hobbit")
# or python style
book = await Book.objects.get(Book.title == "The Hobbit")
book2 = await Book.objects.first() book2 = await Book.objects.first()
# first() fetch the instance with lower primary key value # first() fetch the instance with lower primary key value
@ -334,20 +337,30 @@ async def filter_and_sort():
# get(), all() etc. # get(), all() etc.
# to use special methods or access related model fields use double # to use special methods or access related model fields use double
# underscore like to filter by the name of the author use author__name # underscore like to filter by the name of the author use author__name
# Django style
books = await Book.objects.all(author__name="J.R.R. Tolkien") books = await Book.objects.all(author__name="J.R.R. Tolkien")
# python style
books = await Book.objects.all(Book.author.name == "J.R.R. Tolkien")
assert len(books) == 3 assert len(books) == 3
# filter can accept special methods also separated with double underscore # filter can accept special methods also separated with double underscore
# to issue sql query ` where authors.name like "%tolkien%"` that is not # to issue sql query ` where authors.name like "%tolkien%"` that is not
# case sensitive (hence small t in Tolkien) # case sensitive (hence small t in Tolkien)
# Django style
books = await Book.objects.filter(author__name__icontains="tolkien").all() books = await Book.objects.filter(author__name__icontains="tolkien").all()
# python style
books = await Book.objects.filter(Book.author.name.icontains("tolkien")).all()
assert len(books) == 3 assert len(books) == 3
# to sort use order_by() function of queryset # to sort use order_by() function of queryset
# to sort decreasing use hyphen before the field name # to sort decreasing use hyphen before the field name
# same as with filter you can use double underscores to access related fields # same as with filter you can use double underscores to access related fields
# Django style
books = await Book.objects.filter(author__name__icontains="tolkien").order_by( books = await Book.objects.filter(author__name__icontains="tolkien").order_by(
"-year").all() "-year").all()
# python style
books = await Book.objects.filter(Book.author.name.icontains("tolkien")).order_by(
Book.year.desc()).all()
assert len(books) == 3 assert len(books) == 3
assert books[0].title == "The Silmarillion" assert books[0].title == "The Silmarillion"
assert books[2].title == "The Hobbit" assert books[2].title == "The Hobbit"
@ -417,12 +430,24 @@ async def pagination():
async def aggregations(): async def aggregations():
# ormar currently supports count: # count:
assert 2 == await Author.objects.count() assert 2 == await Author.objects.count()
# and exists # exists:
assert await Book.objects.filter(title="The Hobbit").exists() assert await Book.objects.filter(title="The Hobbit").exists()
# max:
assert 1990 == await Book.objects.max(columns=["year"])
# min:
assert 1937 == await Book.objects.min(columns=["year"])
# avg:
assert 1964.75 == await Book.objects.avg(columns=["year"])
# sum:
assert 7859 == await Book.objects.sum(columns=["year"])
# to read more about aggregated functions # to read more about aggregated functions
# visit: https://collerek.github.io/ormar/queries/aggregations/ # visit: https://collerek.github.io/ormar/queries/aggregations/
@ -448,16 +473,16 @@ metadata.drop_all(engine)
### QuerySet methods ### QuerySet methods
* `create(**kwargs): -> Model` * `create(**kwargs): -> Model`
* `get(**kwargs): -> Model` * `get(*args, **kwargs): -> Model`
* `get_or_none(**kwargs): -> Optional[Model]` * `get_or_none(*args, **kwargs): -> Optional[Model]`
* `get_or_create(**kwargs) -> Model` * `get_or_create(*args, **kwargs) -> Model`
* `first(): -> Model` * `first(*args, **kwargs): -> Model`
* `update(each: bool = False, **kwargs) -> int` * `update(each: bool = False, **kwargs) -> int`
* `update_or_create(**kwargs) -> Model` * `update_or_create(**kwargs) -> Model`
* `bulk_create(objects: List[Model]) -> None` * `bulk_create(objects: List[Model]) -> None`
* `bulk_update(objects: List[Model], columns: List[str] = None) -> None` * `bulk_update(objects: List[Model], columns: List[str] = None) -> None`
* `delete(each: bool = False, **kwargs) -> int` * `delete(*args, each: bool = False, **kwargs) -> int`
* `all(**kwargs) -> List[Optional[Model]]` * `all(*args, **kwargs) -> List[Optional[Model]]`
* `filter(*args, **kwargs) -> QuerySet` * `filter(*args, **kwargs) -> QuerySet`
* `exclude(*args, **kwargs) -> QuerySet` * `exclude(*args, **kwargs) -> QuerySet`
* `select_related(related: Union[List, str]) -> QuerySet` * `select_related(related: Union[List, str]) -> QuerySet`
@ -466,6 +491,10 @@ metadata.drop_all(engine)
* `offset(offset: int) -> QuerySet` * `offset(offset: int) -> QuerySet`
* `count() -> int` * `count() -> int`
* `exists() -> bool` * `exists() -> bool`
* `max(columns: List[str]) -> Any`
* `min(columns: List[str]) -> Any`
* `avg(columns: List[str]) -> Any`
* `sum(columns: List[str]) -> Any`
* `fields(columns: Union[List, str, set, dict]) -> QuerySet` * `fields(columns: Union[List, str, set, dict]) -> QuerySet`
* `exclude_fields(columns: Union[List, str, set, dict]) -> QuerySet` * `exclude_fields(columns: Union[List, str, set, dict]) -> QuerySet`
* `order_by(columns:Union[List, str]) -> QuerySet` * `order_by(columns:Union[List, str]) -> QuerySet`

View File

@ -220,7 +220,10 @@ async def create():
async def read(): async def read():
# Fetch an instance, without loading a foreign key relationship on it. # Fetch an instance, without loading a foreign key relationship on it.
# Django style
book = await Book.objects.get(title="The Hobbit") book = await Book.objects.get(title="The Hobbit")
# or python style
book = await Book.objects.get(Book.title == "The Hobbit")
book2 = await Book.objects.first() book2 = await Book.objects.first()
# first() fetch the instance with lower primary key value # first() fetch the instance with lower primary key value
@ -334,20 +337,30 @@ async def filter_and_sort():
# get(), all() etc. # get(), all() etc.
# to use special methods or access related model fields use double # to use special methods or access related model fields use double
# underscore like to filter by the name of the author use author__name # underscore like to filter by the name of the author use author__name
# Django style
books = await Book.objects.all(author__name="J.R.R. Tolkien") books = await Book.objects.all(author__name="J.R.R. Tolkien")
# python style
books = await Book.objects.all(Book.author.name == "J.R.R. Tolkien")
assert len(books) == 3 assert len(books) == 3
# filter can accept special methods also separated with double underscore # filter can accept special methods also separated with double underscore
# to issue sql query ` where authors.name like "%tolkien%"` that is not # to issue sql query ` where authors.name like "%tolkien%"` that is not
# case sensitive (hence small t in Tolkien) # case sensitive (hence small t in Tolkien)
# Django style
books = await Book.objects.filter(author__name__icontains="tolkien").all() books = await Book.objects.filter(author__name__icontains="tolkien").all()
# python style
books = await Book.objects.filter(Book.author.name.icontains("tolkien")).all()
assert len(books) == 3 assert len(books) == 3
# to sort use order_by() function of queryset # to sort use order_by() function of queryset
# to sort decreasing use hyphen before the field name # to sort decreasing use hyphen before the field name
# same as with filter you can use double underscores to access related fields # same as with filter you can use double underscores to access related fields
# Django style
books = await Book.objects.filter(author__name__icontains="tolkien").order_by( books = await Book.objects.filter(author__name__icontains="tolkien").order_by(
"-year").all() "-year").all()
# python style
books = await Book.objects.filter(Book.author.name.icontains("tolkien")).order_by(
Book.year.desc()).all()
assert len(books) == 3 assert len(books) == 3
assert books[0].title == "The Silmarillion" assert books[0].title == "The Silmarillion"
assert books[2].title == "The Hobbit" assert books[2].title == "The Hobbit"
@ -417,12 +430,24 @@ async def pagination():
async def aggregations(): async def aggregations():
# ormar currently supports count: # count:
assert 2 == await Author.objects.count() assert 2 == await Author.objects.count()
# and exists # exists:
assert await Book.objects.filter(title="The Hobbit").exists() assert await Book.objects.filter(title="The Hobbit").exists()
# max:
assert 1990 == await Book.objects.max(columns=["year"])
# min:
assert 1937 == await Book.objects.min(columns=["year"])
# avg:
assert 1964.75 == await Book.objects.avg(columns=["year"])
# sum:
assert 7859 == await Book.objects.sum(columns=["year"])
# to read more about aggregated functions # to read more about aggregated functions
# visit: https://collerek.github.io/ormar/queries/aggregations/ # visit: https://collerek.github.io/ormar/queries/aggregations/
@ -448,16 +473,16 @@ metadata.drop_all(engine)
### QuerySet methods ### QuerySet methods
* `create(**kwargs): -> Model` * `create(**kwargs): -> Model`
* `get(**kwargs): -> Model` * `get(*args, **kwargs): -> Model`
* `get_or_none(**kwargs): -> Optional[Model]` * `get_or_none(*args, **kwargs): -> Optional[Model]`
* `get_or_create(**kwargs) -> Model` * `get_or_create(*args, **kwargs) -> Model`
* `first(): -> Model` * `first(*args, **kwargs): -> Model`
* `update(each: bool = False, **kwargs) -> int` * `update(each: bool = False, **kwargs) -> int`
* `update_or_create(**kwargs) -> Model` * `update_or_create(**kwargs) -> Model`
* `bulk_create(objects: List[Model]) -> None` * `bulk_create(objects: List[Model]) -> None`
* `bulk_update(objects: List[Model], columns: List[str] = None) -> None` * `bulk_update(objects: List[Model], columns: List[str] = None) -> None`
* `delete(each: bool = False, **kwargs) -> int` * `delete(*args, each: bool = False, **kwargs) -> int`
* `all(**kwargs) -> List[Optional[Model]]` * `all(*args, **kwargs) -> List[Optional[Model]]`
* `filter(*args, **kwargs) -> QuerySet` * `filter(*args, **kwargs) -> QuerySet`
* `exclude(*args, **kwargs) -> QuerySet` * `exclude(*args, **kwargs) -> QuerySet`
* `select_related(related: Union[List, str]) -> QuerySet` * `select_related(related: Union[List, str]) -> QuerySet`
@ -466,6 +491,10 @@ metadata.drop_all(engine)
* `offset(offset: int) -> QuerySet` * `offset(offset: int) -> QuerySet`
* `count() -> int` * `count() -> int`
* `exists() -> bool` * `exists() -> bool`
* `max(columns: List[str]) -> Any`
* `min(columns: List[str]) -> Any`
* `avg(columns: List[str]) -> Any`
* `sum(columns: List[str]) -> Any`
* `fields(columns: Union[List, str, set, dict]) -> QuerySet` * `fields(columns: Union[List, str, set, dict]) -> QuerySet`
* `exclude_fields(columns: Union[List, str, set, dict]) -> QuerySet` * `exclude_fields(columns: Union[List, str, set, dict]) -> QuerySet`
* `order_by(columns:Union[List, str]) -> QuerySet` * `order_by(columns:Union[List, str]) -> QuerySet`

View File

@ -2,27 +2,27 @@
You can use following methods to filter the data (sql where clause). You can use following methods to filter the data (sql where clause).
* `filter(**kwargs) -> QuerySet` * `filter(*args, **kwargs) -> QuerySet`
* `exclude(**kwargs) -> QuerySet` * `exclude(*args, **kwargs) -> QuerySet`
* `get(**kwargs) -> Model` * `get(*args, **kwargs) -> Model`
* `get_or_none(**kwargs) -> Optional[Model]` * `get_or_none(*args, **kwargs) -> Optional[Model]`
* `get_or_create(**kwargs) -> Model` * `get_or_create(*args, **kwargs) -> Model`
* `all(**kwargs) -> List[Optional[Model]]` * `all(*args, **kwargs) -> List[Optional[Model]]`
* `QuerysetProxy` * `QuerysetProxy`
* `QuerysetProxy.filter(**kwargs)` method * `QuerysetProxy.filter(*args, **kwargs)` method
* `QuerysetProxy.exclude(**kwargs)` method * `QuerysetProxy.exclude(*args, **kwargs)` method
* `QuerysetProxy.get(**kwargs)` method * `QuerysetProxy.get(*args, **kwargs)` method
* `QuerysetProxy.get_or_none(**kwargs)` method * `QuerysetProxy.get_or_none(*args, **kwargs)` method
* `QuerysetProxy.get_or_create(**kwargs)` method * `QuerysetProxy.get_or_create(*args, **kwargs)` method
* `QuerysetProxy.all(**kwargs)` method * `QuerysetProxy.all(*args, **kwargs)` method
And following methods to sort the data (sql order by clause). And following methods to sort the data (sql order by clause).
* `order_by(columns:Union[List, str]) -> QuerySet` * `order_by(columns:Union[List, str, OrderAction]) -> QuerySet`
* `QuerysetProxy` * `QuerysetProxy`
* `QuerysetProxy.order_by(columns:Union[List, str])` method * `QuerysetProxy.order_by(columns:Union[List, str, OrderAction])` method
## Filtering ## Filtering
@ -65,24 +65,107 @@ tracks = Track.objects.filter(album__name="Fantasies").all()
# will return all tracks where the columns album name = 'Fantasies' # will return all tracks where the columns album name = 'Fantasies'
``` ```
### Django style filters
You can use special filter suffix to change the filter operands: You can use special filter suffix to change the filter operands:
* exact - like `album__name__exact='Malibu'` (exact match) * exact - exact match to value, sql `column = <VALUE>`
* iexact - like `album__name__iexact='malibu'` (exact match case insensitive) * can be written as`album__name__exact='Malibu'`
* contains - like `album__name__contains='Mal'` (sql like) * iexact - exact match sql `column = <VALUE>` (case insensitive)
* icontains - like `album__name__icontains='mal'` (sql like case insensitive) * can be written as`album__name__iexact='malibu'`
* in - like `album__name__in=['Malibu', 'Barclay']` (sql in) * contains - sql `column LIKE '%<VALUE>%'`
* isnull - like `album__name__isnull=True` (sql is null) * can be written as`album__name__contains='Mal'`
(isnotnull `album__name__isnull=False` (sql is not null)) * icontains - sql `column LIKE '%<VALUE>%'` (case insensitive)
* gt - like `position__gt=3` (sql >) * can be written as`album__name__icontains='mal'`
* gte - like `position__gte=3` (sql >=) * in - sql ` column IN (<VALUE1>, <VALUE2>, ...)`
* lt - like `position__lt=3` (sql <) * can be written as`album__name__in=['Malibu', 'Barclay']`
* lte - like `position__lte=3` (sql <=) * isnull - sql `column IS NULL` (and sql `column IS NOT NULL`)
* startswith - like `album__name__startswith='Mal'` (exact start match) * can be written as`album__name__isnull=True` (isnotnull `album__name__isnull=False`)
* istartswith - like `album__name__istartswith='mal'` (exact start match case * gt - sql `column > <VALUE>` (greater than)
insensitive) * can be written as`position__gt=3`
* endswith - like `album__name__endswith='ibu'` (exact end match) * gte - sql `column >= <VALUE>` (greater or equal than)
* iendswith - like `album__name__iendswith='IBU'` (exact end match case insensitive) * can be written as`position__gte=3`
* lt - sql `column < <VALUE>` (lower than)
* can be written as`position__lt=3`
* lte - sql `column <= <VALUE>` (lower equal than)
* can be written as`position__lte=3`
* startswith - sql `column LIKE '<VALUE>%'` (exact start match)
* can be written as`album__name__startswith='Mal'`
* istartswith - sql `column LIKE '<VALUE>%'` (case insensitive)
* can be written as`album__name__istartswith='mal'`
* endswith - sql `column LIKE '%<VALUE>'` (exact end match)
* can be written as`album__name__endswith='ibu'`
* iendswith - sql `column LIKE '%<VALUE>'` (case insensitive)
* can be written as`album__name__iendswith='IBU'`
Some samples:
```python
# sql: ( product.name = 'Test' AND product.rating >= 3.0 )
Product.objects.filter(name='Test', rating__gte=3.0).get()
# sql: ( product.name = 'Test' AND product.rating >= 3.0 )
# OR (categories.name IN ('Toys', 'Books'))
Product.objects.filter(
ormar.or_(
ormar.and_(name='Test', rating__gte=3.0),
categories__name__in=['Toys', 'Books'])
).get()
# note: to read more about and_ and or_ read complex filters section below
```
### Python style filters
* exact - exact match to value, sql `column = <VALUE>`
* can be written as `Track.album.name == 'Malibu`
* iexact - exact match sql `column = <VALUE>` (case insensitive)
* can be written as `Track.album.name.iexact('malibu')`
* contains - sql `column LIKE '%<VALUE>%'`
* can be written as `Track.album.name % 'Mal')`
* can be written as `Track.album.name.contains('Mal')`
* icontains - sql `column LIKE '%<VALUE>%'` (case insensitive)
* can be written as `Track.album.name.icontains('mal')`
* in - sql ` column IN (<VALUE1>, <VALUE2>, ...)`
* 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 > <VALUE>` (greater than)
* can be written as `Track.album.name > 3`
* gte - sql `column >= <VALUE>` (greater or equal than)
* can be written as `Track.album.name >= 3`
* lt - sql `column < <VALUE>` (lower than)
* can be written as `Track.album.name < 3`
* lte - sql `column <= <VALUE>` (lower equal than)
* can be written as `Track.album.name <= 3`
* startswith - sql `column LIKE '<VALUE>%'` (exact start match)
* can be written as `Track.album.name.startswith('Mal')`
* istartswith - sql `column LIKE '<VALUE>%'` (case insensitive)
* can be written as `Track.album.name.istartswith('mal')`
* endswith - sql `column LIKE '%<VALUE>'` (exact end match)
* can be written as `Track.album.name.endswith('ibu')`
* iendswith - sql `column LIKE '%<VALUE>'` (case insensitive)
* can be written as `Track.album.name.iendswith('IBU')`
Some samples:
```python
# sql: ( product.name = 'Test' AND product.rating >= 3.0 )
Product.objects.filter(
(Product.name == 'Test') & (Product.rating >=3.0)
).get()
# sql: ( product.name = 'Test' AND product.rating >= 3.0 )
# OR (categories.name IN ('Toys', 'Books'))
Product.objects.filter(
((Product.name='Test') & (Product.rating >= 3.0)) |
(Product.categories.name << ['Toys', 'Books'])
).get()
```
!!!note !!!note
All methods that do not return the rows explicitly returns a QueySet instance so All methods that do not return the rows explicitly returns a QueySet instance so
@ -155,7 +238,7 @@ In order to build `OR` and nested conditions ormar provides two functions that c
`filter()` and `exclude()` in `QuerySet` and `QuerysetProxy`. `filter()` and `exclude()` in `QuerySet` and `QuerysetProxy`.
!!!note !!!note
Note that you cannot provide those methods in any other method like `get()` or `all()` which accepts only keyword arguments. Note that you can provide those methods in any other method like `get()` or `all()` that accepts `*args`.
Call to `or_` and `and_` can be nested in each other, as well as combined with keyword arguments. Call to `or_` and `and_` can be nested in each other, as well as combined with keyword arguments.
Since it sounds more complicated than it is, let's look at some examples. Since it sounds more complicated than it is, let's look at some examples.
@ -208,6 +291,7 @@ Let's select books of Tolkien **OR** books written after 1970
sql: sql:
`WHERE ( authors.name = 'J.R.R. Tolkien' OR books.year > 1970 )` `WHERE ( authors.name = 'J.R.R. Tolkien' OR books.year > 1970 )`
### Django style
```python ```python
books = ( books = (
await Book.objects.select_related("author") await Book.objects.select_related("author")
@ -217,11 +301,22 @@ books = (
assert len(books) == 5 assert len(books) == 5
``` ```
### Python style
```python
books = (
await Book.objects.select_related("author")
.filter((Book.author.name=="J.R.R. Tolkien") | (Book.year > 1970))
.all()
)
assert len(books) == 5
```
Now let's select books written after 1960 or before 1940 which were written by Tolkien. Now let's select books written after 1960 or before 1940 which were written by Tolkien.
sql: sql:
`WHERE ( books.year > 1960 OR books.year < 1940 ) AND authors.name = 'J.R.R. Tolkien'` `WHERE ( books.year > 1960 OR books.year < 1940 ) AND authors.name = 'J.R.R. Tolkien'`
### Django style
```python ```python
# OPTION 1 - split and into separate call # OPTION 1 - split and into separate call
books = ( books = (
@ -249,11 +344,38 @@ assert books[0].title == "The Hobbit"
assert books[1].title == "The Silmarillion" assert books[1].title == "The Silmarillion"
``` ```
### Python style
```python
books = (
await Book.objects.select_related("author")
.filter((Book.year > 1960) | (Book.year < 1940))
.filter(Book.author.name == "J.R.R. Tolkien")
.all()
)
assert len(books) == 2
# OPTION 2 - all in one
books = (
await Book.objects.select_related("author")
.filter(
(
(Book.year > 1960) | (Book.year < 1940)
) & (Book.author.name == "J.R.R. Tolkien")
)
.all()
)
assert len(books) == 2
assert books[0].title == "The Hobbit"
assert books[1].title == "The Silmarillion"
```
Books of Sapkowski from before 2000 or books of Tolkien written after 1960 Books of Sapkowski from before 2000 or books of Tolkien written after 1960
sql: sql:
`WHERE ( ( books.year > 1960 AND authors.name = 'J.R.R. Tolkien' ) OR ( books.year < 2000 AND authors.name = 'Andrzej Sapkowski' ) ) ` `WHERE ( ( books.year > 1960 AND authors.name = 'J.R.R. Tolkien' ) OR ( books.year < 2000 AND authors.name = 'Andrzej Sapkowski' ) ) `
### Django style
```python ```python
books = ( books = (
await Book.objects.select_related("author") await Book.objects.select_related("author")
@ -268,7 +390,20 @@ books = (
assert len(books) == 2 assert len(books) == 2
``` ```
Of course those functions can have more than 2 conditions, so if we for example want also ### Python style
```python
books = (
await Book.objects.select_related("author")
.filter(
((Book.year > 1960) & (Book.author.name == "J.R.R. Tolkien")) |
((Book.year < 2000) & (Book.author.name == "Andrzej Sapkowski"))
)
.all()
)
assert len(books) == 2
```
Of course those functions can have more than 2 conditions, so if we for example want
books that contains 'hobbit': books that contains 'hobbit':
sql: sql:
@ -276,6 +411,7 @@ sql:
( books.year < 2000 AND os0cec_authors.name = 'Andrzej Sapkowski' ) OR ( books.year < 2000 AND os0cec_authors.name = 'Andrzej Sapkowski' ) OR
books.title LIKE '%hobbit%' )` books.title LIKE '%hobbit%' )`
### Django style
```python ```python
books = ( books = (
await Book.objects.select_related("author") await Book.objects.select_related("author")
@ -290,6 +426,19 @@ books = (
) )
``` ```
### Python style
```python
books = (
await Book.objects.select_related("author")
.filter(
((Book.year > 1960) & (Book.author.name == "J.R.R. Tolkien")) |
((Book.year < 2000) & (Book.author.name == "Andrzej Sapkowski")) |
(Book.title.icontains("hobbit"))
)
.all()
)
```
If you want or need to you can nest deeper conditions as deep as you want, in example to If you want or need to you can nest deeper conditions as deep as you want, in example to
achieve a query like this: achieve a query like this:
@ -301,6 +450,28 @@ AND authors.name = 'J.R.R. Tolkien' ) OR
``` ```
You can construct a query as follows: You can construct a query as follows:
### Django 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"),
)
)
.all()
)
assert len(books) == 3
assert books[0].title == "The Hobbit"
assert books[1].title == "The Silmarillion"
assert books[2].title == "The Witcher"
```
```python ```python
books = ( books = (
await Book.objects.select_related("author") await Book.objects.select_related("author")
@ -339,9 +510,11 @@ assert len(books) == 1
assert books[0].title == "The Witcher" assert books[0].title == "The Witcher"
``` ```
Same applies to python style chaining and nesting.
!!!note ### Django style
Note that 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
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'])`. column for several values simply use `in` operator: `filter(name__in=['Jack','John'])`.
If you pass only one parameter to `or_` or `and_` functions it's simply wrapped in parenthesis and If you pass only one parameter to `or_` or `and_` functions it's simply wrapped in parenthesis and
@ -386,13 +559,28 @@ books = (
assert len(books) == 5 assert len(books) == 5
``` ```
### Python style
Note that with python style you can perfectly use the same fields as many times as you want.
```python
books = (
await Book.objects.select_related("author")
.filter(
(Book.author.name.icontains("tolkien")) |
(Book.author.name.icontains("sapkowski"))
))
.all()
)
```
## get ## get
`get(**kwargs) -> Model` `get(*args, **kwargs) -> Model`
Get's the first row from the db meeting the criteria set by kwargs. Get's the first row from the db meeting the criteria set by kwargs.
When any kwargs are passed it's a shortcut equivalent to calling `filter(**kwargs).get()` When any args and/or kwargs are passed it's a shortcut equivalent to calling `filter(*args, **kwargs).get()`
!!!tip !!!tip
To read more about `filter` go to [filter](./#filter). To read more about `filter` go to [filter](./#filter).
@ -403,14 +591,13 @@ When any kwargs are passed it's a shortcut equivalent to calling `filter(**kwarg
Exact equivalent of get described above but instead of raising the exception returns `None` if no db record matching the criteria is found. Exact equivalent of get described above but instead of raising the exception returns `None` if no db record matching the criteria is found.
## get_or_create ## get_or_create
`get_or_create(**kwargs) -> Model` `get_or_create(*args, **kwargs) -> Model`
Combination of create and get methods. Combination of create and get methods.
When any kwargs are passed it's a shortcut equivalent to calling `filter(**kwargs).get_or_create()` When any args and/or kwargs are passed it's a shortcut equivalent to calling `filter(*args, **kwargs).get_or_create()`
!!!tip !!!tip
To read more about `filter` go to [filter](./#filter). To read more about `filter` go to [filter](./#filter).
@ -423,11 +610,11 @@ When any kwargs are passed it's a shortcut equivalent to calling `filter(**kwarg
## all ## all
`all(**kwargs) -> List[Optional["Model"]]` `all(*args, **kwargs) -> List[Optional["Model"]]`
Returns all rows from a database for given model for set filter options. Returns all rows from a database for given model for set filter options.
When any kwargs are passed it's a shortcut equivalent to calling `filter(**kwargs).all()` When any kwargs are passed it's a shortcut equivalent to calling `filter(*args, **kwargs).all()`
!!!tip !!!tip
To read more about `filter` go to [filter](./#filter). To read more about `filter` go to [filter](./#filter).
@ -493,7 +680,7 @@ objects from other side of the relation.
### order_by ### order_by
`order_by(columns: Union[List, str]) -> QuerySet` `order_by(columns: Union[List, str, OrderAction]) -> QuerySet`
With `order_by()` you can order the results from database based on your choice of With `order_by()` you can order the results from database based on your choice of
fields. fields.
@ -534,6 +721,7 @@ Given sample Models like following:
To order by main model field just provide a field name To order by main model field just provide a field name
### Django style
```python ```python
toys = await Toy.objects.select_related("owner").order_by("name").all() toys = await Toy.objects.select_related("owner").order_by("name").all()
assert [x.name.replace("Toy ", "") for x in toys] == [ assert [x.name.replace("Toy ", "") for x in toys] == [
@ -543,11 +731,23 @@ assert toys[0].owner == zeus
assert toys[1].owner == aphrodite assert toys[1].owner == aphrodite
``` ```
### 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] == [
str(x + 1) for x in range(6)
]
assert toys[0].owner == zeus
assert toys[1].owner == aphrodite
```
To sort on nested models separate field names with dunder '__'. To sort on nested models separate field names with dunder '__'.
You can sort this way across all relation types -> `ForeignKey`, reverse virtual FK You can sort this way across all relation types -> `ForeignKey`, reverse virtual FK
and `ManyToMany` fields. and `ManyToMany` fields.
### Django style
```python ```python
toys = await Toy.objects.select_related("owner").order_by("owner__name").all() toys = await Toy.objects.select_related("owner").order_by("owner__name").all()
assert toys[0].owner.name == toys[1].owner.name == "Aphrodite" assert toys[0].owner.name == toys[1].owner.name == "Aphrodite"
@ -555,8 +755,17 @@ assert toys[2].owner.name == toys[3].owner.name == "Hermes"
assert toys[4].owner.name == toys[5].owner.name == "Zeus" assert toys[4].owner.name == toys[5].owner.name == "Zeus"
``` ```
### 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"
assert toys[2].owner.name == toys[3].owner.name == "Hermes"
assert toys[4].owner.name == toys[5].owner.name == "Zeus"
```
To sort in descending order provide a hyphen in front of the field name To sort in descending order provide a hyphen in front of the field name
### Django style
```python ```python
owner = ( owner = (
await Owner.objects.select_related("toys") await Owner.objects.select_related("toys")
@ -568,6 +777,18 @@ assert owner.toys[0].name == "Toy 4"
assert owner.toys[1].name == "Toy 1" assert owner.toys[1].name == "Toy 1"
``` ```
### Python style
```python
owner = (
await Owner.objects.select_related("toys")
.order_by(Owner.toys.name.desc())
.filter(Owner.name == "Zeus")
.get()
)
assert owner.toys[0].name == "Toy 4"
assert owner.toys[1].name == "Toy 1"
```
!!!note !!!note
All methods that do not return the rows explicitly returns a QueySet instance so All methods that do not return the rows explicitly returns a QueySet instance so
you can chain them together you can chain them together

View File

@ -2,10 +2,10 @@
Following methods allow you to load data from the database. Following methods allow you to load data from the database.
* `get(**kwargs) -> Model` * `get(*args, **kwargs) -> Model`
* `get_or_create(**kwargs) -> Model` * `get_or_create(*args, **kwargs) -> Model`
* `first() -> Model` * `first(*args, **kwargs) -> Model`
* `all(**kwargs) -> List[Optional[Model]]` * `all(*args, **kwargs) -> List[Optional[Model]]`
* `Model` * `Model`
@ -13,20 +13,20 @@ Following methods allow you to load data from the database.
* `QuerysetProxy` * `QuerysetProxy`
* `QuerysetProxy.get(**kwargs)` method * `QuerysetProxy.get(*args, **kwargs)` method
* `QuerysetProxy.get_or_create(**kwargs)` method * `QuerysetProxy.get_or_create(*args, **kwargs)` method
* `QuerysetProxy.first()` method * `QuerysetProxy.first(*args, **kwargs)` method
* `QuerysetProxy.all(**kwargs)` method * `QuerysetProxy.all(*args, **kwargs)` method
## get ## get
`get(**kwargs) -> Model` `get(*args, **kwargs) -> Model`
Get's the first row from the db meeting the criteria set by kwargs. 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 column. If no criteria set it will return the last row in db sorted by pk column.
Passing a criteria is actually calling filter(**kwargs) method described below. Passing a criteria is actually calling filter(*args, **kwargs) method described below.
```python ```python
class Track(ormar.Model): class Track(ormar.Model):
@ -57,14 +57,14 @@ track == track2
## get_or_none ## get_or_none
`get_or_none(**kwargs) -> Model` `get_or_none(*args, **kwargs) -> Model`
Exact equivalent of get described above but instead of raising the exception returns `None` if no db record matching the criteria is found. Exact equivalent of get described above but instead of raising the exception returns `None` if no db record matching the criteria is found.
## get_or_create ## get_or_create
`get_or_create(**kwargs) -> Model` `get_or_create(*args, **kwargs) -> Model`
Combination of create and get methods. Combination of create and get methods.
@ -102,7 +102,7 @@ assert album == album2
## first ## first
`first() -> Model` `first(*args, **kwargs) -> Model`
Gets the first row from the db ordered by primary key column ascending. Gets the first row from the db ordered by primary key column ascending.
@ -127,11 +127,11 @@ assert album.name == 'The Cat'
## all ## all
`all(**kwargs) -> List[Optional["Model"]]` `all(*args, **kwargs) -> List[Optional["Model"]]`
Returns all rows from a database for given model for set filter options. Returns all rows from a database for given model for set filter options.
Passing kwargs is a shortcut and equals to calling `filter(**kwrags).all()`. Passing kwargs is a shortcut and equals to calling `filter(*args, **kwargs).all()`.
If there are no rows meeting the criteria an empty list is returned. If there are no rows meeting the criteria an empty list is returned.

View File

@ -2,7 +2,7 @@
## ✨ Features ## ✨ Features
* Add possibility to `filter` and `order_by` with field access instead of dunder separated strings. [#51](https://github.com/collerek/ormar/issues/51) * Add **Python style** to `filter` and `order_by` with field access instead of dunder separated strings. [#51](https://github.com/collerek/ormar/issues/51)
* Accessing a field with attribute access (chain of dot notation) can be used to construct `FilterGroups` (`ormar.and_` and `ormar.or_`) * Accessing a field with attribute access (chain of dot notation) can be used to construct `FilterGroups` (`ormar.and_` and `ormar.or_`)
* Field access overloads set of python operators and provide a set of functions to allow same functionality as with dunder separated param names in `**kwargs`, that means that querying from sample model `Track` related to model `Album` now you have more options: * Field access overloads set of python operators and provide a set of functions to allow same functionality as with dunder separated param names in `**kwargs`, that means that querying from sample model `Track` related to model `Album` now you have more options:
* exact - exact match to value, sql `column = <VALUE>` * exact - exact match to value, sql `column = <VALUE>`
@ -102,7 +102,7 @@
(Product.categories.name << ['Toys', 'Books']) (Product.categories.name << ['Toys', 'Books'])
).get() ).get()
``` ```
* Now you can alos use field access to provide OrderActions to `order_by()` * Now you can also use field access to provide OrderActions to `order_by()`
* Order ascending: * Order ascending:
* OLD: `Product.objects.order_by("name").all()` * OLD: `Product.objects.order_by("name").all()`
* NEW: `Product.objects.order_by(Product.name.asc()).all()` * NEW: `Product.objects.order_by(Product.name.asc()).all()`
@ -112,6 +112,12 @@
* You can of course also combine different models and many order_bys: * You can of course also combine different models and many order_bys:
`Product.objects.order_by([Product.category.name.asc(), Product.name.desc()]).all()` `Product.objects.order_by([Product.category.name.asc(), Product.name.desc()]).all()`
## 🐛 Fixes
* Not really a bug but rather inconsistency. Providing a filter with nested model i.e. `album__category__name = 'AA'`
is checking if album and category models are included in `select_related()` and if not it's auto-adding them there.
The same functionality was not working for `FilterGroups` (`and_` and `or_`), now it works (also for python style filters which return `FilterGroups`).
# 0.10.3 # 0.10.3
## ✨ Features ## ✨ Features

BIN
examples/db.sqlite Normal file

Binary file not shown.

View File

@ -87,7 +87,10 @@ async def create():
async def read(): async def read():
# Fetch an instance, without loading a foreign key relationship on it. # Fetch an instance, without loading a foreign key relationship on it.
# Django style
book = await Book.objects.get(title="The Hobbit") book = await Book.objects.get(title="The Hobbit")
# or python style
book = await Book.objects.get(Book.title == "The Hobbit")
book2 = await Book.objects.first() book2 = await Book.objects.first()
# first() fetch the instance with lower primary key value # first() fetch the instance with lower primary key value
@ -193,7 +196,7 @@ async def joins():
# visit: https://collerek.github.io/ormar/relations/ # visit: https://collerek.github.io/ormar/relations/
# to read more about joins and subqueries # to read more about joins and subqueries
# visit: https://collerek.github.io/ormar/queries/delete/ # visit: https://collerek.github.io/ormar/queries/joins-and-subqueries/
async def filter_and_sort(): async def filter_and_sort():
@ -201,20 +204,30 @@ async def filter_and_sort():
# get(), all() etc. # get(), all() etc.
# to use special methods or access related model fields use double # to use special methods or access related model fields use double
# underscore like to filter by the name of the author use author__name # underscore like to filter by the name of the author use author__name
# Django style
books = await Book.objects.all(author__name="J.R.R. Tolkien") books = await Book.objects.all(author__name="J.R.R. Tolkien")
# python style
books = await Book.objects.all(Book.author.name == "J.R.R. Tolkien")
assert len(books) == 3 assert len(books) == 3
# filter can accept special methods also separated with double underscore # filter can accept special methods also separated with double underscore
# to issue sql query ` where authors.name like "%tolkien%"` that is not # to issue sql query ` where authors.name like "%tolkien%"` that is not
# case sensitive (hence small t in Tolkien) # case sensitive (hence small t in Tolkien)
# Django style
books = await Book.objects.filter(author__name__icontains="tolkien").all() books = await Book.objects.filter(author__name__icontains="tolkien").all()
# python style
books = await Book.objects.filter(Book.author.name.icontains("tolkien")).all()
assert len(books) == 3 assert len(books) == 3
# to sort use order_by() function of queryset # to sort use order_by() function of queryset
# to sort decreasing use hyphen before the field name # to sort decreasing use hyphen before the field name
# same as with filter you can use double underscores to access related fields # same as with filter you can use double underscores to access related fields
# Django style
books = await Book.objects.filter(author__name__icontains="tolkien").order_by( books = await Book.objects.filter(author__name__icontains="tolkien").order_by(
"-year").all() "-year").all()
# python style
books = await Book.objects.filter(Book.author.name.icontains("tolkien")).order_by(
Book.year.desc()).all()
assert len(books) == 3 assert len(books) == 3
assert books[0].title == "The Silmarillion" assert books[0].title == "The Silmarillion"
assert books[2].title == "The Hobbit" assert books[2].title == "The Hobbit"
@ -284,12 +297,24 @@ async def pagination():
async def aggregations(): async def aggregations():
# ormar currently supports count: # count:
assert 2 == await Author.objects.count() assert 2 == await Author.objects.count()
# and exists # exists
assert await Book.objects.filter(title="The Hobbit").exists() assert await Book.objects.filter(title="The Hobbit").exists()
# max
assert 1990 == await Book.objects.max(columns=["year"])
# min
assert 1937 == await Book.objects.min(columns=["year"])
# avg
assert 1964.75 == await Book.objects.avg(columns=["year"])
# sum
assert 7859 == await Book.objects.sum(columns=["year"])
# to read more about aggregated functions # to read more about aggregated functions
# visit: https://collerek.github.io/ormar/queries/aggregations/ # visit: https://collerek.github.io/ormar/queries/aggregations/

View File

@ -177,7 +177,7 @@ class FilterAction(QueryAction):
f"{self.table.name}.{self.column.name}", aliased_name f"{self.table.name}.{self.column.name}", aliased_name
) )
dialect_name = self.target_model.Meta.database._backend._dialect.name dialect_name = self.target_model.Meta.database._backend._dialect.name
if dialect_name != 'sqlite': # pragma: no cover if dialect_name != "sqlite": # pragma: no cover
clause_text = clause_text.replace("%%", "%") # remove %% in some dialects clause_text = clause_text.replace("%%", "%") # remove %% in some dialects
clause = text(clause_text) clause = text(clause_text)
return clause return clause

View File

@ -7,6 +7,7 @@ from typing import (
Sequence, Sequence,
Set, Set,
TYPE_CHECKING, TYPE_CHECKING,
Tuple,
Type, Type,
TypeVar, TypeVar,
Union, Union,
@ -180,16 +181,19 @@ class QuerySet(Generic[T]):
return self.model.merge_instances_list(result_rows) # type: ignore return self.model.merge_instances_list(result_rows) # type: ignore
return cast(List[Optional["T"]], result_rows) return cast(List[Optional["T"]], result_rows)
def _resolve_filter_groups(self, groups: Any) -> List[FilterGroup]: def _resolve_filter_groups(
self, groups: Any
) -> Tuple[List[FilterGroup], List[str]]:
""" """
Resolves filter groups to populate FilterAction params in group tree. Resolves filter groups to populate FilterAction params in group tree.
:param groups: tuple of FilterGroups :param groups: tuple of FilterGroups
:type groups: Any :type groups: Any
:return: list of resolver groups :return: list of resolver groups
:rtype: List[FilterGroup] :rtype: Tuple[List[FilterGroup], List[str]]
""" """
filter_groups = [] filter_groups = []
select_related = self._select_related
if groups: if groups:
for group in groups: for group in groups:
if not isinstance(group, FilterGroup): if not isinstance(group, FilterGroup):
@ -200,13 +204,13 @@ class QuerySet(Generic[T]):
"other values need to be passed by" "other values need to be passed by"
"keyword arguments" "keyword arguments"
) )
group.resolve( _, select_related = group.resolve(
model_cls=self.model, model_cls=self.model,
select_related=self._select_related, select_related=self._select_related,
filter_clauses=self.filter_clauses, filter_clauses=self.filter_clauses,
) )
filter_groups.append(group) filter_groups.append(group)
return filter_groups return filter_groups, select_related
@staticmethod @staticmethod
def check_single_result_rows_count(rows: Sequence[Optional["T"]]) -> None: def check_single_result_rows_count(rows: Sequence[Optional["T"]]) -> None:
@ -304,10 +308,10 @@ class QuerySet(Generic[T]):
:return: filtered QuerySet :return: filtered QuerySet
:rtype: QuerySet :rtype: QuerySet
""" """
filter_groups = self._resolve_filter_groups(groups=args) filter_groups, select_related = self._resolve_filter_groups(groups=args)
qryclause = QueryClause( qryclause = QueryClause(
model_cls=self.model, model_cls=self.model,
select_related=self._select_related, select_related=select_related,
filter_clauses=self.filter_clauses, filter_clauses=self.filter_clauses,
) )
filter_clauses, select_related = qryclause.prepare_filter(**kwargs) filter_clauses, select_related = qryclause.prepare_filter(**kwargs)