add docs, release docs

This commit is contained in:
collerek
2021-03-07 18:50:35 +01:00
parent d388b9f745
commit 472c8368e4
3 changed files with 227 additions and 4 deletions

View File

@ -26,7 +26,7 @@ And following methods to sort the data (sql order by clause).
### filter ### filter
`filter(**kwargs) -> QuerySet` `filter(*args, **kwargs) -> QuerySet`
Allows you to filter by any `Model` attribute/field as well as to fetch instances, with Allows you to filter by any `Model` attribute/field as well as to fetch instances, with
a filter across an FK relationship. a filter across an FK relationship.
@ -97,7 +97,7 @@ You can use special filter suffix to change the filter operands:
### exclude ### exclude
`exclude(**kwargs) -> QuerySet` `exclude(*args, **kwargs) -> QuerySet`
Works exactly the same as filter and all modifiers (suffixes) are the same, but returns Works exactly the same as filter and all modifiers (suffixes) are the same, but returns
a not condition. a not condition.
@ -139,6 +139,181 @@ notes = await Track.objects.exclude(position_gt=3).all()
# returns all tracks with position < 3 # returns all tracks with position < 3
``` ```
## Complex filters (including OR)
By default both `filter()` and `exclude()` methods combine provided filter options with
`AND` condition so `filter(name="John", age__gt=30)` translates into `WHERE name = 'John' AND age > 30`.
Sometimes it's useful to query the database with conditions that should not be applied
jointly like `WHERE name = 'John' OR age > 30`, or build a complex where query that you would
like to have bigger control over. After all `WHERE (name = 'John' OR age > 30) and city='New York'` is
completely different than `WHERE name = 'John' OR (age > 30 and city='New York')`.
In order to build `OR` and nested conditions ormar provides two functions that can be used in
`filter()` and `exclude()` in `QuerySet` and `QuerysetProxy`.
!!!note
Note that you cannot provide those methods in any other method like `get()` or `all()` which accepts only 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.
Given a sample models like this:
```python
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)
title: str = ormar.String(max_length=100)
year: int = ormar.Integer(nullable=True)
```
Let's create some sample data:
```python
tolkien = await Author(name="J.R.R. Tolkien").save()
await Book(author=tolkien, title="The Hobbit", year=1933).save()
await Book(author=tolkien, title="The Lord of the Rings", year=1955).save()
await Book(author=tolkien, title="The Silmarillion", year=1977).save()
sapkowski = await Author(name="Andrzej Sapkowski").save()
await Book(author=sapkowski, title="The Witcher", year=1990).save()
await Book(author=sapkowski, title="The Tower of Fools", year=2002).save()
```
We can construct some sample complex queries:
Let's select books of Tolkien **OR** books written after 1970
sql:
`WHERE ( authors.name = 'J.R.R. Tolkien' OR books.year > 1970 )`
```python
books = (
await Book.objects.select_related("author")
.filter(ormar.or_(author__name="J.R.R. Tolkien", year__gt=1970))
.all()
)
assert len(books) == 5
```
Now let's select books written after 1960 or before 1940 which were written by Tolkien.
sql:
`WHERE ( books.year > 1960 OR books.year < 1940 ) AND authors.name = 'J.R.R. Tolkien'`
```python
# OPTION 1 - split and into separate call
books = (
await Book.objects.select_related("author")
.filter(ormar.or_(year__gt=1960, year__lt=1940))
.filter(author__name="J.R.R. Tolkien")
.all()
)
assert len(books) == 2
# OPTION 2 - all in one
books = (
await Book.objects.select_related("author")
.filter(
ormar.and_(
ormar.or_(year__gt=1960, year__lt=1940),
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
sql:
`WHERE ( ( books.year > 1960 AND authors.name = 'J.R.R. Tolkien' ) OR ( books.year < 2000 AND authors.name = 'Andrzej Sapkowski' ) ) `
```python
books = (
await Book.objects.select_related("author")
.filter(
ormar.or_(
ormar.and_(year__gt=1960, author__name="J.R.R. Tolkien"),
ormar.and_(year__lt=2000, 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 also
books that contains 'hobbit':
sql:
`WHERE ( ( books.year > 1960 AND authors.name = 'J.R.R. Tolkien' ) OR
( books.year < 2000 AND os0cec_authors.name = 'Andrzej Sapkowski' ) OR
books.title LIKE '%hobbit%' )`
```python
books = (
await Book.objects.select_related("author")
.filter(
ormar.or_(
ormar.and_(year__gt=1960, author__name="J.R.R. Tolkien"),
ormar.and_(year__lt=2000, author__name="Andrzej Sapkowski"),
title__icontains="hobbit",
)
)
.all()
)
```
By now you should already have an idea how `ormar.or_` and `ormar.and_` works.
Of course, you could chain them in any other methods of queryset, so in example a perfectly
valid query can look like follows:
```python
books = (
await Book.objects.select_related("author")
.filter(ormar.or_(year__gt=1980, author__name="Andrzej Sapkowski"))
.filter(title__startswith="The")
.limit(1)
.offset(1)
.order_by("-id")
.all()
)
assert len(books) == 1
assert books[0].title == "The Witcher"
```
!!!note
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
column for several values simply use `in` operator: `filter(name__in=['Jack','John'])`.
Note that also that technically you can still do `filter(ormar.or_(name='Jack', name__exact='John'))`
but it's not recommended. The different operators can be used as long as they do not
repeat so `filter(ormar.or_(year__lt=1560, year__gt=2000))` is fine.
## get ## get
`get(**kwargs) -> Model` `get(**kwargs) -> Model`

View File

@ -6,7 +6,22 @@
album__name__isnull=True #(sql: album.name is null) album__name__isnull=True #(sql: album.name is null)
album__name__isnull=False #(sql: album.name is not null)) album__name__isnull=False #(sql: album.name is not null))
``` ```
* Add `ormar.or_` and `ormar.and_` functions that can be used to compose
complex queries with nested conditions.
Sample query:
```python
books = (
await Book.objects.select_related("author")
.filter(
ormar.and_(
ormar.or_(year__gt=1960, year__lt=1940),
author__name="J.R.R. Tolkien",
)
)
.all()
)
```
Check the updated docs in Queries -> Filtering and sorting -> Complex filters
# 0.9.6 # 0.9.6

View File

@ -80,6 +80,21 @@ async def test_or_filters():
assert books[0].title == "The Hobbit" assert books[0].title == "The Hobbit"
assert books[1].title == "The Silmarillion" assert books[1].title == "The Silmarillion"
books = (
await Book.objects.select_related("author")
.filter(
ormar.and_(
ormar.or_(year__gt=1960, year__lt=1940),
author__name="J.R.R. Tolkien",
)
)
.all()
)
assert len(books) == 2
assert books[0].title == "The Hobbit"
assert books[1].title == "The Silmarillion"
books = ( books = (
await Book.objects.select_related("author") await Book.objects.select_related("author")
.filter( .filter(
@ -109,6 +124,23 @@ async def test_or_filters():
assert len(books) == 3 assert len(books) == 3
assert not any([x.title in ["The Silmarillion", "The Witcher"] for x in books]) assert not any([x.title in ["The Silmarillion", "The Witcher"] for x in books])
books = (
await Book.objects.select_related("author")
.filter(
ormar.or_(
ormar.and_(year__gt=1960, author__name="J.R.R. Tolkien"),
ormar.and_(year__lt=2000, author__name="Andrzej Sapkowski"),
title__icontains="hobbit",
)
)
.filter(title__startswith="The")
.all()
)
assert len(books) == 3
assert not any(
[x.title in ["The Tower of Fools", "The Lord of the Rings"] for x in books]
)
books = ( books = (
await Book.objects.select_related("author") await Book.objects.select_related("author")
.filter(ormar.or_(year__gt=1980, year__lt=1910)) .filter(ormar.or_(year__gt=1980, year__lt=1910))
@ -163,5 +195,6 @@ async def test_or_filters():
# fix limit -> change to where subquery to extract number of distinct pk values (V) # fix limit -> change to where subquery to extract number of distinct pk values (V)
# finish docstrings (V) # finish docstrings (V)
# fix types for FilterAction and FilterGroup (X) # fix types for FilterAction and FilterGroup (X)
# add docs (V)
# add docs # fix querysetproxy