split tests into packages
This commit is contained in:
0
tests/test_queries/__init__.py
Normal file
0
tests/test_queries/__init__.py
Normal file
177
tests/test_queries/test_aggr_functions.py
Normal file
177
tests/test_queries/test_aggr_functions.py
Normal file
@ -0,0 +1,177 @@
|
||||
from typing import Optional
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from ormar.exceptions import QueryDefinitionError
|
||||
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)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="function")
|
||||
async def cleanup():
|
||||
yield
|
||||
async with database:
|
||||
await Book.objects.delete(each=True)
|
||||
await Author.objects.delete(each=True)
|
||||
|
||||
|
||||
async def sample_data():
|
||||
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()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_min_method():
|
||||
async with database:
|
||||
await sample_data()
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_max_method():
|
||||
async with database:
|
||||
await sample_data()
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sum_method():
|
||||
async with database:
|
||||
await sample_data()
|
||||
assert await Book.objects.sum("year") == 5773
|
||||
result = await Book.objects.sum(["year", "ranking"])
|
||||
assert result == dict(year=5773, ranking=9)
|
||||
|
||||
with pytest.raises(QueryDefinitionError):
|
||||
await Book.objects.sum("title")
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_avg_method():
|
||||
async with database:
|
||||
await sample_data()
|
||||
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
|
||||
|
||||
with pytest.raises(QueryDefinitionError):
|
||||
await Book.objects.avg("title")
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_queryset_method():
|
||||
async with database:
|
||||
await sample_data()
|
||||
author = await Author.objects.select_related("books").get()
|
||||
assert await author.books.min("year") == 1920
|
||||
assert await author.books.max("year") == 1930
|
||||
assert await author.books.sum("ranking") == 9
|
||||
assert await author.books.avg("ranking") == 3.0
|
||||
assert await author.books.max(["year", "title"]) == dict(
|
||||
year=1930, title="Book 3"
|
||||
)
|
||||
158
tests/test_queries/test_deep_relations_select_all.py
Normal file
158
tests/test_queries/test_deep_relations_select_all.py
Normal file
@ -0,0 +1,158 @@
|
||||
import databases
|
||||
import pytest
|
||||
from sqlalchemy import func
|
||||
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Chart(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "charts"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
chart_id = ormar.Integer(primary_key=True, autoincrement=True)
|
||||
name = ormar.String(max_length=200, unique=True, index=True)
|
||||
query_text = ormar.Text()
|
||||
datasets = ormar.JSON()
|
||||
layout = ormar.JSON()
|
||||
data_config = ormar.JSON()
|
||||
created_date = ormar.DateTime(server_default=func.now())
|
||||
library = ormar.String(max_length=200, default="plotly")
|
||||
used_filters = ormar.JSON()
|
||||
|
||||
|
||||
class Report(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "reports"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
report_id = ormar.Integer(primary_key=True, autoincrement=True)
|
||||
name = ormar.String(max_length=200, unique=True, index=True)
|
||||
filters_position = ormar.String(max_length=200)
|
||||
created_date = ormar.DateTime(server_default=func.now())
|
||||
|
||||
|
||||
class Language(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "languages"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
language_id = ormar.Integer(primary_key=True, autoincrement=True)
|
||||
code = ormar.String(max_length=5)
|
||||
name = ormar.String(max_length=200)
|
||||
|
||||
|
||||
class TranslationNode(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "translation_nodes"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
node_id = ormar.Integer(primary_key=True, autoincrement=True)
|
||||
node_type = ormar.String(max_length=200)
|
||||
|
||||
|
||||
class Translation(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "translations"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
translation_id = ormar.Integer(primary_key=True, autoincrement=True)
|
||||
node_id = ormar.ForeignKey(TranslationNode, related_name="translations")
|
||||
language = ormar.ForeignKey(Language, name="language_id")
|
||||
value = ormar.String(max_length=500)
|
||||
|
||||
|
||||
class Filter(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "filters"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
filter_id = ormar.Integer(primary_key=True, autoincrement=True)
|
||||
name = ormar.String(max_length=200, unique=True, index=True)
|
||||
label = ormar.String(max_length=200)
|
||||
query_text = ormar.Text()
|
||||
allow_multiselect = ormar.Boolean(default=True)
|
||||
created_date = ormar.DateTime(server_default=func.now())
|
||||
is_dynamic = ormar.Boolean(default=True)
|
||||
is_date = ormar.Boolean(default=False)
|
||||
translation = ormar.ForeignKey(TranslationNode, name="translation_node_id")
|
||||
|
||||
|
||||
class FilterValue(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "filter_values"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
value_id = ormar.Integer(primary_key=True, autoincrement=True)
|
||||
value = ormar.String(max_length=300)
|
||||
label = ormar.String(max_length=300)
|
||||
filter = ormar.ForeignKey(Filter, name="filter_id", related_name="values")
|
||||
translation = ormar.ForeignKey(TranslationNode, name="translation_node_id")
|
||||
|
||||
|
||||
class FilterXReport(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "filters_x_reports"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
filter_x_report_id = ormar.Integer(primary_key=True)
|
||||
filter = ormar.ForeignKey(Filter, name="filter_id", related_name="reports")
|
||||
report = ormar.ForeignKey(Report, name="report_id", related_name="filters")
|
||||
sort_order = ormar.Integer()
|
||||
default_value = ormar.Text()
|
||||
is_visible = ormar.Boolean()
|
||||
|
||||
|
||||
class ChartXReport(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "charts_x_reports"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
chart_x_report_id = ormar.Integer(primary_key=True)
|
||||
chart = ormar.ForeignKey(Chart, name="chart_id", related_name="reports")
|
||||
report = ormar.ForeignKey(Report, name="report_id", related_name="charts")
|
||||
sort_order = ormar.Integer()
|
||||
width = ormar.Integer()
|
||||
|
||||
|
||||
class ChartColumn(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "charts_columns"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
column_id = ormar.Integer(primary_key=True, autoincrement=True)
|
||||
chart = ormar.ForeignKey(Chart, name="chart_id", related_name="columns")
|
||||
column_name = ormar.String(max_length=200)
|
||||
column_type = ormar.String(max_length=200)
|
||||
translation = ormar.ForeignKey(TranslationNode, name="translation_node_id")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_saving_related_fk_rel():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
await Report.objects.select_all(follow=True).all()
|
||||
141
tests/test_queries/test_filter_groups.py
Normal file
141
tests/test_queries/test_filter_groups.py
Normal file
@ -0,0 +1,141 @@
|
||||
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"
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def test_or_group():
|
||||
result = ormar.or_(name="aa", books__title="bb")
|
||||
result.resolve(model_cls=Author)
|
||||
assert len(result.actions) == 2
|
||||
assert result.actions[0].target_model == Author
|
||||
assert result.actions[1].target_model == Book
|
||||
assert (
|
||||
str(result.get_text_clause()) == f"( authors.name = 'aa' OR "
|
||||
f"{result.actions[1].table_prefix}"
|
||||
f"_books.title = 'bb' )"
|
||||
)
|
||||
|
||||
|
||||
def test_and_group():
|
||||
result = ormar.and_(name="aa", books__title="bb")
|
||||
result.resolve(model_cls=Author)
|
||||
assert len(result.actions) == 2
|
||||
assert result.actions[0].target_model == Author
|
||||
assert result.actions[1].target_model == Book
|
||||
assert (
|
||||
str(result.get_text_clause()) == f"( authors.name = 'aa' AND "
|
||||
f"{result.actions[1].table_prefix}"
|
||||
f"_books.title = 'bb' )"
|
||||
)
|
||||
|
||||
|
||||
def test_nested_and():
|
||||
result = ormar.and_(
|
||||
ormar.or_(name="aa", books__title="bb"), ormar.or_(name="cc", books__title="dd")
|
||||
)
|
||||
result.resolve(model_cls=Author)
|
||||
assert len(result.actions) == 0
|
||||
assert len(result._nested_groups) == 2
|
||||
book_prefix = result._nested_groups[0].actions[1].table_prefix
|
||||
assert (
|
||||
str(result.get_text_clause()) == f"( ( authors.name = 'aa' OR "
|
||||
f"{book_prefix}"
|
||||
f"_books.title = 'bb' ) AND "
|
||||
f"( authors.name = 'cc' OR "
|
||||
f"{book_prefix}"
|
||||
f"_books.title = 'dd' ) )"
|
||||
)
|
||||
|
||||
|
||||
def test_nested_group_and_action():
|
||||
result = ormar.and_(ormar.or_(name="aa", books__title="bb"), books__title="dd")
|
||||
result.resolve(model_cls=Author)
|
||||
assert len(result.actions) == 1
|
||||
assert len(result._nested_groups) == 1
|
||||
book_prefix = result._nested_groups[0].actions[1].table_prefix
|
||||
assert (
|
||||
str(result.get_text_clause()) == f"( ( authors.name = 'aa' OR "
|
||||
f"{book_prefix}"
|
||||
f"_books.title = 'bb' ) AND "
|
||||
f"{book_prefix}"
|
||||
f"_books.title = 'dd' )"
|
||||
)
|
||||
|
||||
|
||||
def test_deeply_nested_or():
|
||||
result = ormar.or_(
|
||||
ormar.and_(
|
||||
ormar.or_(name="aa", books__title="bb"),
|
||||
ormar.or_(name="cc", books__title="dd"),
|
||||
),
|
||||
ormar.and_(
|
||||
ormar.or_(books__year__lt=1900, books__title="11"),
|
||||
ormar.or_(books__year__gt="xx", books__title="22"),
|
||||
),
|
||||
)
|
||||
result.resolve(model_cls=Author)
|
||||
assert len(result.actions) == 0
|
||||
assert len(result._nested_groups) == 2
|
||||
assert len(result._nested_groups[0]._nested_groups) == 2
|
||||
book_prefix = result._nested_groups[0]._nested_groups[0].actions[1].table_prefix
|
||||
result_qry = str(result.get_text_clause())
|
||||
expected_qry = (
|
||||
f"( ( ( authors.name = 'aa' OR {book_prefix}_books.title = 'bb' ) AND "
|
||||
f"( authors.name = 'cc' OR {book_prefix}_books.title = 'dd' ) ) "
|
||||
f"OR ( ( {book_prefix}_books.year < 1900 OR {book_prefix}_books.title = '11' ) AND "
|
||||
f"( {book_prefix}_books.year > 'xx' OR {book_prefix}_books.title = '22' ) ) )"
|
||||
)
|
||||
assert result_qry.replace("\n", "") == expected_qry.replace("\n", "")
|
||||
|
||||
|
||||
def test_one_model_group():
|
||||
result = ormar.and_(year__gt=1900, title="bb")
|
||||
result.resolve(model_cls=Book)
|
||||
assert len(result.actions) == 2
|
||||
assert len(result._nested_groups) == 0
|
||||
|
||||
|
||||
def test_one_model_nested_group():
|
||||
result = ormar.and_(
|
||||
ormar.or_(year__gt=1900, title="bb"), ormar.or_(year__lt=1800, title="aa")
|
||||
)
|
||||
result.resolve(model_cls=Book)
|
||||
assert len(result.actions) == 0
|
||||
assert len(result._nested_groups) == 2
|
||||
|
||||
|
||||
def test_one_model_with_group():
|
||||
result = ormar.or_(ormar.and_(year__gt=1900, title="bb"), title="uu")
|
||||
result.resolve(model_cls=Book)
|
||||
assert len(result.actions) == 1
|
||||
assert len(result._nested_groups) == 1
|
||||
76
tests/test_queries/test_isnull_filter.py
Normal file
76
tests/test_queries/test_isnull_filter.py
Normal file
@ -0,0 +1,76 @@
|
||||
from typing import Optional
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
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"
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_is_null():
|
||||
async with database:
|
||||
tolkien = await Author.objects.create(name="J.R.R. Tolkien")
|
||||
await Book.objects.create(author=tolkien, title="The Hobbit")
|
||||
await Book.objects.create(
|
||||
author=tolkien, title="The Lord of the Rings", year=1955
|
||||
)
|
||||
await Book.objects.create(author=tolkien, title="The Silmarillion", year=1977)
|
||||
|
||||
books = await Book.objects.all(year__isnull=True)
|
||||
assert len(books) == 1
|
||||
assert books[0].year is None
|
||||
assert books[0].title == "The Hobbit"
|
||||
|
||||
books = await Book.objects.all(year__isnull=False)
|
||||
assert len(books) == 2
|
||||
|
||||
tolkien = await Author.objects.select_related("books").get(
|
||||
books__year__isnull=True
|
||||
)
|
||||
assert len(tolkien.books) == 1
|
||||
assert tolkien.books[0].year is None
|
||||
assert tolkien.books[0].title == "The Hobbit"
|
||||
|
||||
tolkien = await Author.objects.select_related("books").get(
|
||||
books__year__isnull=False
|
||||
)
|
||||
assert len(tolkien.books) == 2
|
||||
assert tolkien.books[0].year == 1955
|
||||
assert tolkien.books[0].title == "The Lord of the Rings"
|
||||
238
tests/test_queries/test_or_filters.py
Normal file
238
tests/test_queries/test_or_filters.py
Normal file
@ -0,0 +1,238 @@
|
||||
from typing import Optional
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from ormar.exceptions import QueryDefinitionError
|
||||
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"
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_or_filters():
|
||||
async with database:
|
||||
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()
|
||||
|
||||
books = (
|
||||
await Book.objects.select_related("author")
|
||||
.filter(ormar.or_(author__name="J.R.R. Tolkien", year__gt=1970))
|
||||
.all()
|
||||
)
|
||||
assert len(books) == 5
|
||||
|
||||
books = (
|
||||
await Book.objects.select_related("author")
|
||||
.filter(ormar.or_(author__name="J.R.R. Tolkien", year__lt=1995))
|
||||
.all()
|
||||
)
|
||||
assert len(books) == 4
|
||||
assert not any([x.title == "The Tower of Fools" for x in books])
|
||||
|
||||
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
|
||||
assert books[0].title == "The Hobbit"
|
||||
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 = (
|
||||
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"),
|
||||
)
|
||||
)
|
||||
.filter(title__startswith="The")
|
||||
.all()
|
||||
)
|
||||
assert len(books) == 2
|
||||
assert books[0].title == "The Silmarillion"
|
||||
assert books[1].title == "The Witcher"
|
||||
|
||||
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"
|
||||
|
||||
books = (
|
||||
await Book.objects.select_related("author")
|
||||
.exclude(
|
||||
ormar.or_(
|
||||
ormar.and_(year__gt=1960, author__name="J.R.R. Tolkien"),
|
||||
ormar.and_(year__lt=2000, author__name="Andrzej Sapkowski"),
|
||||
)
|
||||
)
|
||||
.filter(title__startswith="The")
|
||||
.all()
|
||||
)
|
||||
assert len(books) == 3
|
||||
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 = (
|
||||
await Book.objects.select_related("author")
|
||||
.filter(ormar.or_(year__gt=1980, year__lt=1910))
|
||||
.filter(title__startswith="The")
|
||||
.limit(1)
|
||||
.all()
|
||||
)
|
||||
assert len(books) == 1
|
||||
assert books[0].title == "The Witcher"
|
||||
|
||||
books = (
|
||||
await Book.objects.select_related("author")
|
||||
.filter(ormar.or_(year__gt=1980, author__name="Andrzej Sapkowski"))
|
||||
.filter(title__startswith="The")
|
||||
.limit(1)
|
||||
.all()
|
||||
)
|
||||
assert len(books) == 1
|
||||
assert books[0].title == "The Witcher"
|
||||
|
||||
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)
|
||||
.all()
|
||||
)
|
||||
assert len(books) == 1
|
||||
assert books[0].title == "The Tower of Fools"
|
||||
|
||||
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"
|
||||
|
||||
with pytest.raises(QueryDefinitionError):
|
||||
await Book.objects.select_related("author").filter("wrong").all()
|
||||
|
||||
books = await tolkien.books.filter(
|
||||
ormar.or_(year__lt=1940, year__gt=1960)
|
||||
).all()
|
||||
assert len(books) == 2
|
||||
|
||||
books = await tolkien.books.filter(
|
||||
ormar.and_(
|
||||
ormar.or_(year__lt=1940, year__gt=1960), title__icontains="hobbit"
|
||||
)
|
||||
).all()
|
||||
assert len(books) == 1
|
||||
assert tolkien.books[0].title == "The Hobbit"
|
||||
|
||||
books = (
|
||||
await Book.objects.select_related("author")
|
||||
.filter(ormar.or_(author__name="J.R.R. Tolkien"))
|
||||
.all()
|
||||
)
|
||||
assert len(books) == 3
|
||||
|
||||
books = (
|
||||
await Book.objects.select_related("author")
|
||||
.filter(
|
||||
ormar.or_(
|
||||
ormar.and_(author__name__icontains="tolkien"),
|
||||
ormar.and_(author__name__icontains="sapkowski"),
|
||||
)
|
||||
)
|
||||
.all()
|
||||
)
|
||||
assert len(books) == 5
|
||||
345
tests/test_queries/test_order_by.py
Normal file
345
tests/test_queries/test_order_by.py
Normal file
@ -0,0 +1,345 @@
|
||||
from typing import List, Optional
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Song(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "songs"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
sort_order: int = ormar.Integer()
|
||||
|
||||
|
||||
class Owner(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "owners"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class AliasNested(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "aliases_nested"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(name="alias_id", primary_key=True)
|
||||
name: str = ormar.String(name="alias_name", max_length=100)
|
||||
|
||||
|
||||
class AliasTest(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "aliases"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(name="alias_id", primary_key=True)
|
||||
name: str = ormar.String(name="alias_name", max_length=100)
|
||||
nested = ormar.ForeignKey(AliasNested, name="nested_alias")
|
||||
|
||||
|
||||
class Toy(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "toys"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
owner: Owner = ormar.ForeignKey(Owner)
|
||||
|
||||
|
||||
class Factory(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "factories"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Car(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "cars"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
factory: Optional[Factory] = ormar.ForeignKey(Factory)
|
||||
|
||||
|
||||
class User(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "users"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
cars: List[Car] = ormar.ManyToMany(Car)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sort_order_on_main_model():
|
||||
async with database:
|
||||
await Song.objects.create(name="Song 3", sort_order=3)
|
||||
await Song.objects.create(name="Song 1", sort_order=1)
|
||||
await Song.objects.create(name="Song 2", sort_order=2)
|
||||
|
||||
songs = await Song.objects.all()
|
||||
assert songs[0].name == "Song 3"
|
||||
assert songs[1].name == "Song 1"
|
||||
assert songs[2].name == "Song 2"
|
||||
|
||||
songs = await Song.objects.order_by("-sort_order").all()
|
||||
assert songs[0].name == "Song 3"
|
||||
assert songs[1].name == "Song 2"
|
||||
assert songs[2].name == "Song 1"
|
||||
|
||||
songs = await Song.objects.order_by("sort_order").all()
|
||||
assert songs[0].name == "Song 1"
|
||||
assert songs[1].name == "Song 2"
|
||||
assert songs[2].name == "Song 3"
|
||||
|
||||
songs = await Song.objects.order_by("name").all()
|
||||
assert songs[0].name == "Song 1"
|
||||
assert songs[1].name == "Song 2"
|
||||
assert songs[2].name == "Song 3"
|
||||
|
||||
songs = await Song.objects.order_by("name").limit(2).all()
|
||||
assert len(songs) == 2
|
||||
assert songs[0].name == "Song 1"
|
||||
assert songs[1].name == "Song 2"
|
||||
|
||||
await Song.objects.create(name="Song 4", sort_order=1)
|
||||
|
||||
songs = await Song.objects.order_by(["sort_order", "name"]).all()
|
||||
assert songs[0].name == "Song 1"
|
||||
assert songs[1].name == "Song 4"
|
||||
assert songs[2].name == "Song 2"
|
||||
assert songs[3].name == "Song 3"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sort_order_on_related_model():
|
||||
async with database:
|
||||
aphrodite = await Owner.objects.create(name="Aphrodite")
|
||||
hermes = await Owner.objects.create(name="Hermes")
|
||||
zeus = await Owner.objects.create(name="Zeus")
|
||||
|
||||
await Toy.objects.create(name="Toy 4", owner=zeus)
|
||||
await Toy.objects.create(name="Toy 5", owner=hermes)
|
||||
await Toy.objects.create(name="Toy 2", owner=aphrodite)
|
||||
await Toy.objects.create(name="Toy 1", owner=zeus)
|
||||
await Toy.objects.create(name="Toy 3", owner=aphrodite)
|
||||
await Toy.objects.create(name="Toy 6", owner=hermes)
|
||||
|
||||
toys = await Toy.objects.select_related("owner").order_by("name").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
|
||||
|
||||
toys = await Toy.objects.select_related("owner").order_by("owner__name").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"
|
||||
|
||||
owner = (
|
||||
await Owner.objects.select_related("toys")
|
||||
.order_by("toys__name")
|
||||
.filter(name="Zeus")
|
||||
.get()
|
||||
)
|
||||
assert owner.toys[0].name == "Toy 1"
|
||||
assert owner.toys[1].name == "Toy 4"
|
||||
|
||||
owner = (
|
||||
await Owner.objects.select_related("toys")
|
||||
.order_by("-toys__name")
|
||||
.filter(name="Zeus")
|
||||
.get()
|
||||
)
|
||||
assert owner.toys[0].name == "Toy 4"
|
||||
assert owner.toys[1].name == "Toy 1"
|
||||
|
||||
owners = (
|
||||
await Owner.objects.select_related("toys")
|
||||
.order_by("-toys__name")
|
||||
.filter(name__in=["Zeus", "Hermes"])
|
||||
.all()
|
||||
)
|
||||
assert owners[0].toys[0].name == "Toy 6"
|
||||
assert owners[0].toys[1].name == "Toy 5"
|
||||
assert owners[0].name == "Hermes"
|
||||
|
||||
assert owners[1].toys[0].name == "Toy 4"
|
||||
assert owners[1].toys[1].name == "Toy 1"
|
||||
assert owners[1].name == "Zeus"
|
||||
|
||||
await Toy.objects.create(name="Toy 7", owner=zeus)
|
||||
|
||||
owners = (
|
||||
await Owner.objects.select_related("toys")
|
||||
.order_by("-toys__name")
|
||||
.filter(name__in=["Zeus", "Hermes"])
|
||||
.all()
|
||||
)
|
||||
assert owners[0].toys[0].name == "Toy 7"
|
||||
assert owners[0].toys[1].name == "Toy 4"
|
||||
assert owners[0].toys[2].name == "Toy 1"
|
||||
assert owners[0].name == "Zeus"
|
||||
|
||||
assert owners[1].toys[0].name == "Toy 6"
|
||||
assert owners[1].toys[1].name == "Toy 5"
|
||||
assert owners[1].name == "Hermes"
|
||||
|
||||
toys = (
|
||||
await Toy.objects.select_related("owner")
|
||||
.order_by(["owner__name", "name"])
|
||||
.limit(2)
|
||||
.all()
|
||||
)
|
||||
assert len(toys) == 2
|
||||
assert toys[0].name == "Toy 2"
|
||||
assert toys[1].name == "Toy 3"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sort_order_on_many_to_many():
|
||||
async with database:
|
||||
factory1 = await Factory.objects.create(name="Factory 1")
|
||||
factory2 = await Factory.objects.create(name="Factory 2")
|
||||
|
||||
car1 = await Car.objects.create(name="Buggy", factory=factory1)
|
||||
car2 = await Car.objects.create(name="Volkswagen", factory=factory2)
|
||||
car3 = await Car.objects.create(name="Ferrari", factory=factory1)
|
||||
car4 = await Car.objects.create(name="Volvo", factory=factory2)
|
||||
car5 = await Car.objects.create(name="Skoda", factory=factory1)
|
||||
car6 = await Car.objects.create(name="Seat", factory=factory2)
|
||||
|
||||
user1 = await User.objects.create(name="Mark")
|
||||
user2 = await User.objects.create(name="Julie")
|
||||
|
||||
await user1.cars.add(car1)
|
||||
await user1.cars.add(car3)
|
||||
await user1.cars.add(car4)
|
||||
await user1.cars.add(car5)
|
||||
|
||||
await user2.cars.add(car1)
|
||||
await user2.cars.add(car2)
|
||||
await user2.cars.add(car5)
|
||||
await user2.cars.add(car6)
|
||||
|
||||
user = (
|
||||
await User.objects.select_related("cars")
|
||||
.filter(name="Mark")
|
||||
.order_by("cars__name")
|
||||
.get()
|
||||
)
|
||||
assert user.cars[0].name == "Buggy"
|
||||
assert user.cars[1].name == "Ferrari"
|
||||
assert user.cars[2].name == "Skoda"
|
||||
assert user.cars[3].name == "Volvo"
|
||||
|
||||
user = (
|
||||
await User.objects.select_related("cars")
|
||||
.filter(name="Mark")
|
||||
.order_by("-cars__name")
|
||||
.get()
|
||||
)
|
||||
assert user.cars[3].name == "Buggy"
|
||||
assert user.cars[2].name == "Ferrari"
|
||||
assert user.cars[1].name == "Skoda"
|
||||
assert user.cars[0].name == "Volvo"
|
||||
|
||||
users = await User.objects.select_related("cars").order_by("-cars__name").all()
|
||||
assert users[0].name == "Mark"
|
||||
assert users[1].cars[0].name == "Volkswagen"
|
||||
assert users[1].cars[1].name == "Skoda"
|
||||
assert users[1].cars[2].name == "Seat"
|
||||
assert users[1].cars[3].name == "Buggy"
|
||||
|
||||
users = (
|
||||
await User.objects.select_related(["cars__factory"])
|
||||
.order_by(["-cars__factory__name", "cars__name"])
|
||||
.all()
|
||||
)
|
||||
|
||||
assert users[0].name == "Julie"
|
||||
assert users[0].cars[0].name == "Seat"
|
||||
assert users[0].cars[1].name == "Volkswagen"
|
||||
assert users[0].cars[2].name == "Buggy"
|
||||
assert users[0].cars[3].name == "Skoda"
|
||||
|
||||
assert users[1].name == "Mark"
|
||||
assert users[1].cars[0].name == "Volvo"
|
||||
assert users[1].cars[1].name == "Buggy"
|
||||
assert users[1].cars[2].name == "Ferrari"
|
||||
assert users[1].cars[3].name == "Skoda"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sort_order_with_aliases():
|
||||
async with database:
|
||||
al1 = await AliasTest.objects.create(name="Test4")
|
||||
al2 = await AliasTest.objects.create(name="Test2")
|
||||
al3 = await AliasTest.objects.create(name="Test1")
|
||||
al4 = await AliasTest.objects.create(name="Test3")
|
||||
|
||||
aliases = await AliasTest.objects.order_by("-name").all()
|
||||
assert [alias.name[-1] for alias in aliases] == ["4", "3", "2", "1"]
|
||||
|
||||
nest1 = await AliasNested.objects.create(name="Try1")
|
||||
nest2 = await AliasNested.objects.create(name="Try2")
|
||||
nest3 = await AliasNested.objects.create(name="Try3")
|
||||
nest4 = await AliasNested.objects.create(name="Try4")
|
||||
|
||||
al1.nested = nest1
|
||||
await al1.update()
|
||||
|
||||
al2.nested = nest2
|
||||
await al2.update()
|
||||
|
||||
al3.nested = nest3
|
||||
await al3.update()
|
||||
|
||||
al4.nested = nest4
|
||||
await al4.update()
|
||||
|
||||
aliases = (
|
||||
await AliasTest.objects.select_related("nested")
|
||||
.order_by("-nested__name")
|
||||
.all()
|
||||
)
|
||||
assert aliases[0].nested.name == "Try4"
|
||||
assert aliases[1].nested.name == "Try3"
|
||||
assert aliases[2].nested.name == "Try2"
|
||||
assert aliases[3].nested.name == "Try1"
|
||||
110
tests/test_queries/test_pagination.py
Normal file
110
tests/test_queries/test_pagination.py
Normal file
@ -0,0 +1,110 @@
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from ormar import ModelMeta
|
||||
from ormar.exceptions import QueryDefinitionError
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class BaseMeta(ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
|
||||
class Car(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class UsersCar(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "cars_x_users"
|
||||
|
||||
|
||||
class User(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
cars = ormar.ManyToMany(Car, through=UsersCar)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pagination_errors():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
with pytest.raises(QueryDefinitionError):
|
||||
await Car.objects.paginate(0).all()
|
||||
|
||||
with pytest.raises(QueryDefinitionError):
|
||||
await Car.objects.paginate(1, page_size=0).all()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pagination_on_single_model():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
for i in range(20):
|
||||
await Car(name=f"{i}").save()
|
||||
|
||||
cars_page1 = await Car.objects.paginate(1, page_size=5).all()
|
||||
assert len(cars_page1) == 5
|
||||
assert cars_page1[0].name == "0"
|
||||
assert cars_page1[4].name == "4"
|
||||
cars_page2 = await Car.objects.paginate(2, page_size=5).all()
|
||||
assert len(cars_page2) == 5
|
||||
assert cars_page2[0].name == "5"
|
||||
assert cars_page2[4].name == "9"
|
||||
|
||||
all_cars = await Car.objects.paginate(1).all()
|
||||
assert len(all_cars) == 20
|
||||
|
||||
half_cars = await Car.objects.paginate(2, page_size=10).all()
|
||||
assert len(half_cars) == 10
|
||||
assert half_cars[0].name == "10"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_proxy_pagination():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
user = await User(name="Jon").save()
|
||||
|
||||
for i in range(20):
|
||||
c = await Car(name=f"{i}").save()
|
||||
await user.cars.add(c)
|
||||
|
||||
await user.cars.paginate(1, page_size=5).all()
|
||||
assert len(user.cars) == 5
|
||||
assert user.cars[0].name == "0"
|
||||
assert user.cars[4].name == "4"
|
||||
|
||||
await user.cars.paginate(2, page_size=5).all()
|
||||
assert len(user.cars) == 5
|
||||
assert user.cars[0].name == "5"
|
||||
assert user.cars[4].name == "9"
|
||||
|
||||
await user.cars.paginate(1).all()
|
||||
assert len(user.cars) == 20
|
||||
|
||||
await user.cars.paginate(2, page_size=10).all()
|
||||
assert len(user.cars) == 10
|
||||
assert user.cars[0].name == "10"
|
||||
222
tests/test_queries/test_queryproxy_on_m2m_models.py
Normal file
222
tests/test_queries/test_queryproxy_on_m2m_models.py
Normal file
@ -0,0 +1,222 @@
|
||||
import asyncio
|
||||
from typing import List, Optional, Union
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from ormar.exceptions import QueryDefinitionError
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Subject(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "subjects"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=80)
|
||||
|
||||
|
||||
class Author(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "authors"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
first_name: str = ormar.String(max_length=80)
|
||||
last_name: str = ormar.String(max_length=80)
|
||||
|
||||
|
||||
class Category(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "categories"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=40)
|
||||
sort_order: int = ormar.Integer(nullable=True)
|
||||
subject: Optional[Subject] = ormar.ForeignKey(Subject)
|
||||
|
||||
|
||||
class PostCategory(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "posts_categories"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
|
||||
class Post(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "posts"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
title: str = ormar.String(max_length=200)
|
||||
categories: Optional[Union[Category, List[Category]]] = ormar.ManyToMany(
|
||||
Category, through=PostCategory
|
||||
)
|
||||
author: Optional[Author] = ormar.ForeignKey(Author)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def event_loop():
|
||||
loop = asyncio.get_event_loop()
|
||||
yield loop
|
||||
loop.close()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
async def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_queryset_methods():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
guido = await Author.objects.create(
|
||||
first_name="Guido", last_name="Van Rossum"
|
||||
)
|
||||
subject = await Subject(name="Random").save()
|
||||
post = await Post.objects.create(title="Hello, M2M", author=guido)
|
||||
news = await Category.objects.create(
|
||||
name="News", sort_order=1, subject=subject
|
||||
)
|
||||
breaking = await Category.objects.create(
|
||||
name="Breaking", sort_order=3, subject=subject
|
||||
)
|
||||
|
||||
# Add a category to a post.
|
||||
await post.categories.add(news)
|
||||
await post.categories.add(breaking)
|
||||
|
||||
category = await post.categories.get_or_create(name="News")
|
||||
assert category == news
|
||||
assert len(post.categories) == 1
|
||||
|
||||
category = await post.categories.get_or_create(name="Breaking News")
|
||||
assert category != breaking
|
||||
assert category.pk is not None
|
||||
assert len(post.categories) == 2
|
||||
|
||||
await post.categories.update_or_create(pk=category.pk, name="Urgent News")
|
||||
assert len(post.categories) == 2
|
||||
cat = await post.categories.get_or_create(name="Urgent News")
|
||||
assert cat.pk == category.pk
|
||||
assert len(post.categories) == 1
|
||||
|
||||
await post.categories.remove(cat)
|
||||
await cat.delete()
|
||||
|
||||
assert len(post.categories) == 0
|
||||
|
||||
category = await post.categories.update_or_create(
|
||||
name="Weather News", sort_order=2, subject=subject
|
||||
)
|
||||
assert category.pk is not None
|
||||
assert category.posts[0] == post
|
||||
|
||||
assert len(post.categories) == 1
|
||||
|
||||
categories = await post.categories.all()
|
||||
assert len(categories) == 3 == len(post.categories)
|
||||
|
||||
assert await post.categories.exists()
|
||||
assert 3 == await post.categories.count()
|
||||
|
||||
categories = await post.categories.limit(2).all()
|
||||
assert len(categories) == 2 == len(post.categories)
|
||||
|
||||
categories2 = await post.categories.limit(2).offset(1).all()
|
||||
assert len(categories2) == 2 == len(post.categories)
|
||||
assert categories != categories2
|
||||
|
||||
categories = await post.categories.order_by("-sort_order").all()
|
||||
assert len(categories) == 3 == len(post.categories)
|
||||
assert post.categories[2].name == "News"
|
||||
assert post.categories[0].name == "Breaking"
|
||||
|
||||
categories = await post.categories.exclude(name__icontains="news").all()
|
||||
assert len(categories) == 1 == len(post.categories)
|
||||
assert post.categories[0].name == "Breaking"
|
||||
|
||||
categories = (
|
||||
await post.categories.filter(name__icontains="news")
|
||||
.order_by("-name")
|
||||
.all()
|
||||
)
|
||||
assert len(categories) == 2 == len(post.categories)
|
||||
assert post.categories[0].name == "Weather News"
|
||||
assert post.categories[1].name == "News"
|
||||
|
||||
categories = await post.categories.fields("name").all()
|
||||
assert len(categories) == 3 == len(post.categories)
|
||||
for cat in post.categories:
|
||||
assert cat.sort_order is None
|
||||
|
||||
categories = await post.categories.exclude_fields("sort_order").all()
|
||||
assert len(categories) == 3 == len(post.categories)
|
||||
for cat in post.categories:
|
||||
assert cat.sort_order is None
|
||||
assert cat.subject.name is None
|
||||
|
||||
categories = await post.categories.select_related("subject").all()
|
||||
assert len(categories) == 3 == len(post.categories)
|
||||
for cat in post.categories:
|
||||
assert cat.subject.name is not None
|
||||
|
||||
categories = await post.categories.prefetch_related("subject").all()
|
||||
assert len(categories) == 3 == len(post.categories)
|
||||
for cat in post.categories:
|
||||
assert cat.subject.name is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_queryset_update():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
guido = await Author.objects.create(
|
||||
first_name="Guido", last_name="Van Rossum"
|
||||
)
|
||||
subject = await Subject(name="Random").save()
|
||||
post = await Post.objects.create(title="Hello, M2M", author=guido)
|
||||
await post.categories.create(name="News", sort_order=1, subject=subject)
|
||||
await post.categories.create(name="Breaking", sort_order=3, subject=subject)
|
||||
|
||||
await post.categories.order_by("sort_order").all()
|
||||
assert len(post.categories) == 2
|
||||
assert post.categories[0].sort_order == 1
|
||||
assert post.categories[0].name == "News"
|
||||
assert post.categories[1].sort_order == 3
|
||||
assert post.categories[1].name == "Breaking"
|
||||
|
||||
updated = await post.categories.update(each=True, name="Test")
|
||||
assert updated == 2
|
||||
|
||||
await post.categories.order_by("sort_order").all()
|
||||
assert len(post.categories) == 2
|
||||
assert post.categories[0].name == "Test"
|
||||
assert post.categories[1].name == "Test"
|
||||
|
||||
updated = await post.categories.filter(sort_order=3).update(name="Test 2")
|
||||
assert updated == 1
|
||||
|
||||
await post.categories.order_by("sort_order").all()
|
||||
assert len(post.categories) == 2
|
||||
assert post.categories[0].name == "Test"
|
||||
assert post.categories[1].name == "Test 2"
|
||||
|
||||
with pytest.raises(QueryDefinitionError):
|
||||
await post.categories.update(name="Test WRONG")
|
||||
311
tests/test_queries/test_queryset_level_methods.py
Normal file
311
tests/test_queries/test_queryset_level_methods.py
Normal file
@ -0,0 +1,311 @@
|
||||
from typing import Optional
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from ormar.exceptions import ModelPersistenceError, QueryDefinitionError
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Book(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "books"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
title: str = ormar.String(max_length=200)
|
||||
author: str = ormar.String(max_length=100)
|
||||
genre: str = ormar.String(
|
||||
max_length=100,
|
||||
default="Fiction",
|
||||
choices=["Fiction", "Adventure", "Historic", "Fantasy"],
|
||||
)
|
||||
|
||||
|
||||
class ToDo(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "todos"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
text: str = ormar.String(max_length=500)
|
||||
completed: bool = ormar.Boolean(default=False)
|
||||
|
||||
|
||||
class Category(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "categories"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=500)
|
||||
|
||||
|
||||
class Note(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "notes"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
text: str = ormar.String(max_length=500)
|
||||
category: Optional[Category] = ormar.ForeignKey(Category)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_and_update():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
await Book.objects.create(
|
||||
title="Tom Sawyer", author="Twain, Mark", genre="Adventure"
|
||||
)
|
||||
await Book.objects.create(
|
||||
title="War and Peace", author="Tolstoy, Leo", genre="Fiction"
|
||||
)
|
||||
await Book.objects.create(
|
||||
title="Anna Karenina", author="Tolstoy, Leo", genre="Fiction"
|
||||
)
|
||||
await Book.objects.create(
|
||||
title="Harry Potter", author="Rowling, J.K.", genre="Fantasy"
|
||||
)
|
||||
await Book.objects.create(
|
||||
title="Lord of the Rings", author="Tolkien, J.R.", genre="Fantasy"
|
||||
)
|
||||
|
||||
all_books = await Book.objects.all()
|
||||
assert len(all_books) == 5
|
||||
|
||||
await Book.objects.filter(author="Tolstoy, Leo").update(
|
||||
author="Lenin, Vladimir"
|
||||
)
|
||||
all_books = await Book.objects.filter(author="Lenin, Vladimir").all()
|
||||
assert len(all_books) == 2
|
||||
|
||||
historic_books = await Book.objects.filter(genre="Historic").all()
|
||||
assert len(historic_books) == 0
|
||||
|
||||
with pytest.raises(QueryDefinitionError):
|
||||
await Book.objects.update(genre="Historic")
|
||||
|
||||
await Book.objects.filter(author="Lenin, Vladimir").update(genre="Historic")
|
||||
|
||||
historic_books = await Book.objects.filter(genre="Historic").all()
|
||||
assert len(historic_books) == 2
|
||||
|
||||
await Book.objects.delete(genre="Fantasy")
|
||||
all_books = await Book.objects.all()
|
||||
assert len(all_books) == 3
|
||||
|
||||
await Book.objects.update(each=True, genre="Fiction")
|
||||
all_books = await Book.objects.filter(genre="Fiction").all()
|
||||
assert len(all_books) == 3
|
||||
|
||||
with pytest.raises(QueryDefinitionError):
|
||||
await Book.objects.delete()
|
||||
|
||||
await Book.objects.delete(each=True)
|
||||
all_books = await Book.objects.all()
|
||||
assert len(all_books) == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_or_create():
|
||||
async with database:
|
||||
tom = await Book.objects.get_or_create(
|
||||
title="Volume I", author="Anonymous", genre="Fiction"
|
||||
)
|
||||
assert await Book.objects.count() == 1
|
||||
|
||||
assert (
|
||||
await Book.objects.get_or_create(
|
||||
title="Volume I", author="Anonymous", genre="Fiction"
|
||||
)
|
||||
== tom
|
||||
)
|
||||
assert await Book.objects.count() == 1
|
||||
|
||||
assert await Book.objects.create(
|
||||
title="Volume I", author="Anonymous", genre="Fiction"
|
||||
)
|
||||
with pytest.raises(ormar.exceptions.MultipleMatches):
|
||||
await Book.objects.get_or_create(
|
||||
title="Volume I", author="Anonymous", genre="Fiction"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_or_create():
|
||||
async with database:
|
||||
tom = await Book.objects.update_or_create(
|
||||
title="Volume I", author="Anonymous", genre="Fiction"
|
||||
)
|
||||
assert await Book.objects.count() == 1
|
||||
|
||||
assert await Book.objects.update_or_create(id=tom.id, genre="Historic")
|
||||
assert await Book.objects.count() == 1
|
||||
|
||||
assert await Book.objects.update_or_create(pk=tom.id, genre="Fantasy")
|
||||
assert await Book.objects.count() == 1
|
||||
|
||||
assert await Book.objects.create(
|
||||
title="Volume I", author="Anonymous", genre="Fantasy"
|
||||
)
|
||||
with pytest.raises(ormar.exceptions.MultipleMatches):
|
||||
await Book.objects.get(
|
||||
title="Volume I", author="Anonymous", genre="Fantasy"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bulk_create():
|
||||
async with database:
|
||||
await ToDo.objects.bulk_create(
|
||||
[
|
||||
ToDo(text="Buy the groceries."),
|
||||
ToDo(text="Call Mum.", completed=True),
|
||||
ToDo(text="Send invoices.", completed=True),
|
||||
]
|
||||
)
|
||||
|
||||
todoes = await ToDo.objects.all()
|
||||
assert len(todoes) == 3
|
||||
for todo in todoes:
|
||||
assert todo.pk is not None
|
||||
|
||||
completed = await ToDo.objects.filter(completed=True).all()
|
||||
assert len(completed) == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bulk_create_with_relation():
|
||||
async with database:
|
||||
category = await Category.objects.create(name="Sample Category")
|
||||
|
||||
await Note.objects.bulk_create(
|
||||
[
|
||||
Note(text="Buy the groceries.", category=category),
|
||||
Note(text="Call Mum.", category=category),
|
||||
]
|
||||
)
|
||||
|
||||
todoes = await Note.objects.all()
|
||||
assert len(todoes) == 2
|
||||
for todo in todoes:
|
||||
assert todo.category.pk == category.pk
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bulk_update():
|
||||
async with database:
|
||||
await ToDo.objects.bulk_create(
|
||||
[
|
||||
ToDo(text="Buy the groceries."),
|
||||
ToDo(text="Call Mum.", completed=True),
|
||||
ToDo(text="Send invoices.", completed=True),
|
||||
]
|
||||
)
|
||||
todoes = await ToDo.objects.all()
|
||||
assert len(todoes) == 3
|
||||
|
||||
for todo in todoes:
|
||||
todo.text = todo.text + "_1"
|
||||
todo.completed = False
|
||||
|
||||
await ToDo.objects.bulk_update(todoes)
|
||||
|
||||
completed = await ToDo.objects.filter(completed=False).all()
|
||||
assert len(completed) == 3
|
||||
|
||||
todoes = await ToDo.objects.all()
|
||||
assert len(todoes) == 3
|
||||
|
||||
for todo in todoes:
|
||||
assert todo.text[-2:] == "_1"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bulk_update_with_only_selected_columns():
|
||||
async with database:
|
||||
await ToDo.objects.bulk_create(
|
||||
[
|
||||
ToDo(text="Reset the world simulation.", completed=False),
|
||||
ToDo(text="Watch kittens.", completed=True),
|
||||
]
|
||||
)
|
||||
|
||||
todoes = await ToDo.objects.all()
|
||||
assert len(todoes) == 2
|
||||
|
||||
for todo in todoes:
|
||||
todo.text = todo.text + "_1"
|
||||
todo.completed = False
|
||||
|
||||
await ToDo.objects.bulk_update(todoes, columns=["completed"])
|
||||
|
||||
completed = await ToDo.objects.filter(completed=False).all()
|
||||
assert len(completed) == 2
|
||||
|
||||
todoes = await ToDo.objects.all()
|
||||
assert len(todoes) == 2
|
||||
|
||||
for todo in todoes:
|
||||
assert todo.text[-2:] != "_1"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bulk_update_with_relation():
|
||||
async with database:
|
||||
category = await Category.objects.create(name="Sample Category")
|
||||
category2 = await Category.objects.create(name="Sample II Category")
|
||||
|
||||
await Note.objects.bulk_create(
|
||||
[
|
||||
Note(text="Buy the groceries.", category=category),
|
||||
Note(text="Call Mum.", category=category),
|
||||
Note(text="Text skynet.", category=category),
|
||||
]
|
||||
)
|
||||
|
||||
notes = await Note.objects.all()
|
||||
assert len(notes) == 3
|
||||
|
||||
for note in notes:
|
||||
note.category = category2
|
||||
|
||||
await Note.objects.bulk_update(notes)
|
||||
|
||||
notes_upd = await Note.objects.all()
|
||||
assert len(notes_upd) == 3
|
||||
|
||||
for note in notes_upd:
|
||||
assert note.category.pk == category2.pk
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bulk_update_not_saved_objts():
|
||||
async with database:
|
||||
category = await Category.objects.create(name="Sample Category")
|
||||
with pytest.raises(ModelPersistenceError):
|
||||
await Note.objects.bulk_update(
|
||||
[
|
||||
Note(text="Buy the groceries.", category=category),
|
||||
Note(text="Call Mum.", category=category),
|
||||
]
|
||||
)
|
||||
99
tests/test_queries/test_reserved_sql_keywords_escaped.py
Normal file
99
tests/test_queries/test_reserved_sql_keywords_escaped.py
Normal file
@ -0,0 +1,99 @@
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
|
||||
class User(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "user"
|
||||
|
||||
id: int = ormar.Integer(primary_key=True, autoincrement=True, nullable=False)
|
||||
user: str = ormar.String(
|
||||
unique=True, index=True, nullable=False, max_length=255
|
||||
) # ID of the user on auth0
|
||||
first: str = ormar.String(nullable=False, max_length=255)
|
||||
last: str = ormar.String(nullable=False, max_length=255)
|
||||
email: str = ormar.String(unique=True, index=True, nullable=False, max_length=255)
|
||||
display_name: str = ormar.String(
|
||||
unique=True, index=True, nullable=False, max_length=255
|
||||
)
|
||||
pic_url: str = ormar.Text(nullable=True)
|
||||
|
||||
|
||||
class Task(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "task"
|
||||
|
||||
id: int = ormar.Integer(primary_key=True, autoincrement=True, nullable=False)
|
||||
from_: str = ormar.String(name="from", nullable=True, max_length=200)
|
||||
user = ormar.ForeignKey(User)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_single_model_quotes():
|
||||
async with database:
|
||||
await User.objects.create(
|
||||
user="test",
|
||||
first="first",
|
||||
last="last",
|
||||
email="email@com.com",
|
||||
display_name="first last",
|
||||
)
|
||||
|
||||
user = await User.objects.order_by("user").get(first="first")
|
||||
assert user.last == "last"
|
||||
assert user.email == "email@com.com"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_two_model_quotes():
|
||||
async with database:
|
||||
user = await User.objects.create(
|
||||
user="test",
|
||||
first="first",
|
||||
last="last",
|
||||
email="email@com.com",
|
||||
display_name="first last",
|
||||
)
|
||||
|
||||
await Task(user=user, from_="aa").save()
|
||||
await Task(user=user, from_="bb").save()
|
||||
|
||||
task = (
|
||||
await Task.objects.select_related("user")
|
||||
.order_by("user__user")
|
||||
.get(from_="aa")
|
||||
)
|
||||
assert task.user.last == "last"
|
||||
assert task.user.email == "email@com.com"
|
||||
|
||||
tasks = await Task.objects.select_related("user").order_by("-from").all()
|
||||
assert len(tasks) == 2
|
||||
assert tasks[0].user.last == "last"
|
||||
assert tasks[0].user.email == "email@com.com"
|
||||
assert tasks[0].from_ == "bb"
|
||||
|
||||
assert tasks[1].user.last == "last"
|
||||
assert tasks[1].user.email == "email@com.com"
|
||||
assert tasks[1].from_ == "aa"
|
||||
258
tests/test_queries/test_reverse_fk_queryset.py
Normal file
258
tests/test_queries/test_reverse_fk_queryset.py
Normal file
@ -0,0 +1,258 @@
|
||||
from typing import Optional
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from ormar import NoMatch
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "albums"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
is_best_seller: bool = ormar.Boolean(default=False)
|
||||
|
||||
|
||||
class Writer(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "writers"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Track(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "tracks"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
album: Optional[Album] = ormar.ForeignKey(Album)
|
||||
title: str = ormar.String(max_length=100)
|
||||
position: int = ormar.Integer()
|
||||
play_count: int = ormar.Integer(nullable=True)
|
||||
written_by: Optional[Writer] = ormar.ForeignKey(Writer)
|
||||
|
||||
|
||||
async def get_sample_data():
|
||||
album = await Album(name="Malibu").save()
|
||||
writer1 = await Writer.objects.create(name="John")
|
||||
writer2 = await Writer.objects.create(name="Sue")
|
||||
track1 = await Track(
|
||||
album=album, title="The Bird", position=1, play_count=30, written_by=writer1
|
||||
).save()
|
||||
track2 = await Track(
|
||||
album=album,
|
||||
title="Heart don't stand a chance",
|
||||
position=2,
|
||||
play_count=20,
|
||||
written_by=writer2,
|
||||
).save()
|
||||
tracks3 = await Track(
|
||||
album=album, title="The Waters", position=3, play_count=10, written_by=writer1
|
||||
).save()
|
||||
return album, [track1, track2, tracks3]
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_quering_by_reverse_fk():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
sample_data = await get_sample_data()
|
||||
track1 = sample_data[1][0]
|
||||
album = await Album.objects.first()
|
||||
|
||||
assert await album.tracks.exists()
|
||||
assert await album.tracks.count() == 3
|
||||
|
||||
track = await album.tracks.get_or_create(
|
||||
title="The Bird", position=1, play_count=30
|
||||
)
|
||||
assert track == track1
|
||||
assert len(album.tracks) == 1
|
||||
|
||||
track = await album.tracks.get_or_create(
|
||||
title="The Bird2", position=4, play_count=5
|
||||
)
|
||||
assert track != track1
|
||||
assert track.pk is not None
|
||||
assert len(album.tracks) == 2
|
||||
|
||||
await album.tracks.update_or_create(pk=track.pk, play_count=50)
|
||||
assert len(album.tracks) == 2
|
||||
track = await album.tracks.get_or_create(title="The Bird2")
|
||||
assert track.play_count == 50
|
||||
assert len(album.tracks) == 1
|
||||
|
||||
await album.tracks.remove(track)
|
||||
assert track.album is None
|
||||
await track.delete()
|
||||
|
||||
assert len(album.tracks) == 0
|
||||
|
||||
track6 = await album.tracks.update_or_create(
|
||||
title="The Bird3", position=4, play_count=5
|
||||
)
|
||||
assert track6.pk is not None
|
||||
assert track6.play_count == 5
|
||||
|
||||
assert len(album.tracks) == 1
|
||||
|
||||
await album.tracks.remove(track6)
|
||||
assert track6.album is None
|
||||
await track6.delete()
|
||||
|
||||
assert len(album.tracks) == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_getting():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
sample_data = await get_sample_data()
|
||||
album = sample_data[0]
|
||||
track1 = await album.tracks.fields(["album", "title", "position"]).get(
|
||||
title="The Bird"
|
||||
)
|
||||
track2 = await album.tracks.exclude_fields("play_count").get(
|
||||
title="The Bird"
|
||||
)
|
||||
for track in [track1, track2]:
|
||||
assert track.title == "The Bird"
|
||||
assert track.album == album
|
||||
assert track.play_count is None
|
||||
|
||||
assert len(album.tracks) == 1
|
||||
|
||||
tracks = await album.tracks.all()
|
||||
assert len(tracks) == 3
|
||||
|
||||
assert len(album.tracks) == 3
|
||||
|
||||
tracks = await album.tracks.order_by("play_count").all()
|
||||
assert len(tracks) == 3
|
||||
assert tracks[0].title == "The Waters"
|
||||
assert tracks[2].title == "The Bird"
|
||||
|
||||
assert len(album.tracks) == 3
|
||||
|
||||
track = await album.tracks.create(
|
||||
title="The Bird Fly Away", position=4, play_count=10
|
||||
)
|
||||
assert track.title == "The Bird Fly Away"
|
||||
assert track.position == 4
|
||||
assert track.album == album
|
||||
|
||||
assert len(album.tracks) == 4
|
||||
|
||||
tracks = await album.tracks.all()
|
||||
assert len(tracks) == 4
|
||||
|
||||
tracks = await album.tracks.limit(2).all()
|
||||
assert len(tracks) == 2
|
||||
|
||||
tracks2 = await album.tracks.limit(2).offset(2).all()
|
||||
assert len(tracks2) == 2
|
||||
assert tracks != tracks2
|
||||
|
||||
tracks3 = await album.tracks.filter(play_count__lt=15).all()
|
||||
assert len(tracks3) == 2
|
||||
|
||||
tracks4 = await album.tracks.exclude(play_count__lt=15).all()
|
||||
assert len(tracks4) == 2
|
||||
assert tracks3 != tracks4
|
||||
|
||||
assert len(album.tracks) == 2
|
||||
|
||||
await album.tracks.clear()
|
||||
tracks = await album.tracks.all()
|
||||
assert len(tracks) == 0
|
||||
assert len(album.tracks) == 0
|
||||
|
||||
still_tracks = await Track.objects.all()
|
||||
assert len(still_tracks) == 4
|
||||
for track in still_tracks:
|
||||
assert track.album is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cleaning_related():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
sample_data = await get_sample_data()
|
||||
album = sample_data[0]
|
||||
await album.tracks.clear(keep_reversed=False)
|
||||
tracks = await album.tracks.all()
|
||||
assert len(tracks) == 0
|
||||
assert len(album.tracks) == 0
|
||||
|
||||
no_tracks = await Track.objects.all()
|
||||
assert len(no_tracks) == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_loading_related():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
sample_data = await get_sample_data()
|
||||
album = sample_data[0]
|
||||
tracks = await album.tracks.select_related("written_by").all()
|
||||
assert len(tracks) == 3
|
||||
assert len(album.tracks) == 3
|
||||
for track in tracks:
|
||||
assert track.written_by is not None
|
||||
|
||||
tracks = await album.tracks.prefetch_related("written_by").all()
|
||||
assert len(tracks) == 3
|
||||
assert len(album.tracks) == 3
|
||||
for track in tracks:
|
||||
assert track.written_by is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_adding_removing():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
sample_data = await get_sample_data()
|
||||
album = sample_data[0]
|
||||
track_new = await Track(title="Rainbow", position=5, play_count=300).save()
|
||||
await album.tracks.add(track_new)
|
||||
assert track_new.album == album
|
||||
assert len(album.tracks) == 4
|
||||
|
||||
track_check = await Track.objects.get(title="Rainbow")
|
||||
assert track_check.album == album
|
||||
|
||||
await album.tracks.remove(track_new)
|
||||
assert track_new.album is None
|
||||
assert len(album.tracks) == 3
|
||||
|
||||
track1 = album.tracks[0]
|
||||
await album.tracks.remove(track1, keep_reversed=False)
|
||||
with pytest.raises(NoMatch):
|
||||
await track1.load()
|
||||
|
||||
track_test = await Track.objects.get(title="Rainbow")
|
||||
assert track_test.album is None
|
||||
220
tests/test_queries/test_selecting_subset_of_columns.py
Normal file
220
tests/test_queries/test_selecting_subset_of_columns.py
Normal file
@ -0,0 +1,220 @@
|
||||
import itertools
|
||||
from typing import Optional, List
|
||||
|
||||
import databases
|
||||
import pydantic
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class NickNames(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "nicks"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100, nullable=False, name="hq_name")
|
||||
is_lame: bool = ormar.Boolean(nullable=True)
|
||||
|
||||
|
||||
class NicksHq(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "nicks_x_hq"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
|
||||
class HQ(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "hqs"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100, nullable=False, name="hq_name")
|
||||
nicks: List[NickNames] = ormar.ManyToMany(NickNames, through=NicksHq)
|
||||
|
||||
|
||||
class Company(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "companies"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100, nullable=False, name="company_name")
|
||||
founded: int = ormar.Integer(nullable=True)
|
||||
hq: HQ = ormar.ForeignKey(HQ)
|
||||
|
||||
|
||||
class Car(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "cars"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
manufacturer: Optional[Company] = ormar.ForeignKey(Company)
|
||||
name: str = ormar.String(max_length=100)
|
||||
year: int = ormar.Integer(nullable=True)
|
||||
gearbox_type: str = ormar.String(max_length=20, nullable=True)
|
||||
gears: int = ormar.Integer(nullable=True)
|
||||
aircon_type: str = ormar.String(max_length=20, nullable=True)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_selecting_subset():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
nick1 = await NickNames.objects.create(name="Nippon", is_lame=False)
|
||||
nick2 = await NickNames.objects.create(name="EroCherry", is_lame=True)
|
||||
hq = await HQ.objects.create(name="Japan")
|
||||
await hq.nicks.add(nick1)
|
||||
await hq.nicks.add(nick2)
|
||||
|
||||
toyota = await Company.objects.create(name="Toyota", founded=1937, hq=hq)
|
||||
|
||||
await Car.objects.create(
|
||||
manufacturer=toyota,
|
||||
name="Corolla",
|
||||
year=2020,
|
||||
gearbox_type="Manual",
|
||||
gears=5,
|
||||
aircon_type="Manual",
|
||||
)
|
||||
await Car.objects.create(
|
||||
manufacturer=toyota,
|
||||
name="Yaris",
|
||||
year=2019,
|
||||
gearbox_type="Manual",
|
||||
gears=5,
|
||||
aircon_type="Manual",
|
||||
)
|
||||
await Car.objects.create(
|
||||
manufacturer=toyota,
|
||||
name="Supreme",
|
||||
year=2020,
|
||||
gearbox_type="Auto",
|
||||
gears=6,
|
||||
aircon_type="Auto",
|
||||
)
|
||||
|
||||
all_cars = (
|
||||
await Car.objects.select_related(["manufacturer__hq__nicks"])
|
||||
.fields(
|
||||
[
|
||||
"id",
|
||||
"name",
|
||||
"manufacturer__name",
|
||||
"manufacturer__hq__name",
|
||||
"manufacturer__hq__nicks__name",
|
||||
]
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
all_cars2 = (
|
||||
await Car.objects.select_related(["manufacturer__hq__nicks"])
|
||||
.fields(
|
||||
{
|
||||
"id": ...,
|
||||
"name": ...,
|
||||
"manufacturer": {
|
||||
"name": ...,
|
||||
"hq": {"name": ..., "nicks": {"name": ...}},
|
||||
},
|
||||
}
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
all_cars3 = (
|
||||
await Car.objects.select_related(["manufacturer__hq__nicks"])
|
||||
.fields(
|
||||
{
|
||||
"id": ...,
|
||||
"name": ...,
|
||||
"manufacturer": {
|
||||
"name": ...,
|
||||
"hq": {"name": ..., "nicks": {"name"}},
|
||||
},
|
||||
}
|
||||
)
|
||||
.all()
|
||||
)
|
||||
assert all_cars3 == all_cars
|
||||
|
||||
for car in itertools.chain(all_cars, all_cars2):
|
||||
assert all(
|
||||
getattr(car, x) is None
|
||||
for x in ["year", "gearbox_type", "gears", "aircon_type"]
|
||||
)
|
||||
assert car.manufacturer.name == "Toyota"
|
||||
assert car.manufacturer.founded is None
|
||||
assert car.manufacturer.hq.name == "Japan"
|
||||
assert len(car.manufacturer.hq.nicks) == 2
|
||||
assert car.manufacturer.hq.nicks[0].is_lame is None
|
||||
|
||||
all_cars = (
|
||||
await Car.objects.select_related("manufacturer")
|
||||
.fields("id")
|
||||
.fields(["name"])
|
||||
.all()
|
||||
)
|
||||
for car in all_cars:
|
||||
assert all(
|
||||
getattr(car, x) is None
|
||||
for x in ["year", "gearbox_type", "gears", "aircon_type"]
|
||||
)
|
||||
assert car.manufacturer.name == "Toyota"
|
||||
assert car.manufacturer.founded == 1937
|
||||
assert car.manufacturer.hq.name is None
|
||||
|
||||
all_cars_check = await Car.objects.select_related("manufacturer").all()
|
||||
all_cars_with_whole_nested = (
|
||||
await Car.objects.select_related("manufacturer")
|
||||
.fields(["id", "name", "year", "gearbox_type", "gears", "aircon_type"])
|
||||
.fields({"manufacturer": ...})
|
||||
.all()
|
||||
)
|
||||
for car in itertools.chain(all_cars_check, all_cars_with_whole_nested):
|
||||
assert all(
|
||||
getattr(car, x) is not None
|
||||
for x in ["year", "gearbox_type", "gears", "aircon_type"]
|
||||
)
|
||||
assert car.manufacturer.name == "Toyota"
|
||||
assert car.manufacturer.founded == 1937
|
||||
|
||||
all_cars_dummy = (
|
||||
await Car.objects.select_related("manufacturer")
|
||||
.fields(["id", "name", "year", "gearbox_type", "gears", "aircon_type"])
|
||||
# .fields({"manufacturer": ...})
|
||||
# .exclude_fields({"manufacturer": ...})
|
||||
.fields({"manufacturer": {"name"}})
|
||||
.exclude_fields({"manufacturer__founded"})
|
||||
.all()
|
||||
)
|
||||
|
||||
assert all_cars_dummy[0].manufacturer.founded is None
|
||||
|
||||
with pytest.raises(pydantic.error_wrappers.ValidationError):
|
||||
# cannot exclude mandatory model columns - company__name in this example
|
||||
await Car.objects.select_related("manufacturer").fields(
|
||||
["id", "name", "manufacturer__founded"]
|
||||
).all()
|
||||
Reference in New Issue
Block a user