Merge pull request #134 from collerek/fix_pg_query
fix quoting in order_by, add get_or_none
This commit is contained in:
@ -429,6 +429,7 @@ metadata.drop_all(engine)
|
||||
|
||||
* `create(**kwargs): -> Model`
|
||||
* `get(**kwargs): -> Model`
|
||||
* `get_or_none(**kwargs): -> Optional[Model]`
|
||||
* `get_or_create(**kwargs) -> Model`
|
||||
* `first(): -> Model`
|
||||
* `update(each: bool = False, **kwargs) -> int`
|
||||
|
||||
@ -429,6 +429,7 @@ metadata.drop_all(engine)
|
||||
|
||||
* `create(**kwargs): -> Model`
|
||||
* `get(**kwargs): -> Model`
|
||||
* `get_or_none(**kwargs): -> Optional[Model]`
|
||||
* `get_or_create(**kwargs) -> Model`
|
||||
* `first(): -> Model`
|
||||
* `update(each: bool = False, **kwargs) -> int`
|
||||
|
||||
@ -5,6 +5,7 @@ You can use following methods to filter the data (sql where clause).
|
||||
* `filter(**kwargs) -> QuerySet`
|
||||
* `exclude(**kwargs) -> QuerySet`
|
||||
* `get(**kwargs) -> Model`
|
||||
* `get_or_none(**kwargs) -> Optional[Model]`
|
||||
* `get_or_create(**kwargs) -> Model`
|
||||
* `all(**kwargs) -> List[Optional[Model]]`
|
||||
|
||||
@ -13,6 +14,7 @@ You can use following methods to filter the data (sql where clause).
|
||||
* `QuerysetProxy.filter(**kwargs)` method
|
||||
* `QuerysetProxy.exclude(**kwargs)` method
|
||||
* `QuerysetProxy.get(**kwargs)` method
|
||||
* `QuerysetProxy.get_or_none(**kwargs)` method
|
||||
* `QuerysetProxy.get_or_create(**kwargs)` method
|
||||
* `QuerysetProxy.all(**kwargs)` method
|
||||
|
||||
@ -397,6 +399,11 @@ When any kwargs are passed it's a shortcut equivalent to calling `filter(**kwarg
|
||||
|
||||
To read more about `get` go to [read/get](../read/#get)
|
||||
|
||||
## get_or_none
|
||||
|
||||
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(**kwargs) -> Model`
|
||||
@ -461,6 +468,11 @@ objects from other side of the relation.
|
||||
!!!tip
|
||||
To read more about `QuerysetProxy` visit [querysetproxy][querysetproxy] section
|
||||
|
||||
#### get_or_none
|
||||
|
||||
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
|
||||
|
||||
Works exactly the same as [get_or_create](./#get_or_create) function above but allows
|
||||
|
||||
@ -46,6 +46,7 @@ To read more about any specific section or function please refer to the details
|
||||
### [Read data from database](./read.md)
|
||||
|
||||
* `get(**kwargs) -> Model`
|
||||
* `get_or_none(**kwargs) -> Optional[Model]`
|
||||
* `get_or_create(**kwargs) -> Model`
|
||||
* `first() -> Model`
|
||||
* `all(**kwargs) -> List[Optional[Model]]`
|
||||
@ -57,6 +58,7 @@ To read more about any specific section or function please refer to the details
|
||||
|
||||
* `QuerysetProxy`
|
||||
* `QuerysetProxy.get(**kwargs)` method
|
||||
* `QuerysetProxy.get_or_none(**kwargs)` method
|
||||
* `QuerysetProxy.get_or_create(**kwargs)` method
|
||||
* `QuerysetProxy.first()` method
|
||||
* `QuerysetProxy.all(**kwargs)` method
|
||||
@ -122,6 +124,7 @@ To read more about any specific section or function please refer to the details
|
||||
* `exclude(**kwargs) -> QuerySet`
|
||||
* `order_by(columns:Union[List, str]) -> QuerySet`
|
||||
* `get(**kwargs) -> Model`
|
||||
* `get_or_none(**kwargs) -> Optional[Model]`
|
||||
* `get_or_create(**kwargs) -> Model`
|
||||
* `all(**kwargs) -> List[Optional[Model]]`
|
||||
|
||||
@ -131,6 +134,7 @@ To read more about any specific section or function please refer to the details
|
||||
* `QuerysetProxy.exclude(**kwargs)` method
|
||||
* `QuerysetProxy.order_by(columns:Union[List, str])` method
|
||||
* `QuerysetProxy.get(**kwargs)` method
|
||||
* `QuerysetProxy.get_or_none(**kwargs)` method
|
||||
* `QuerysetProxy.get_or_create(**kwargs)` method
|
||||
* `QuerysetProxy.all(**kwargs)` method
|
||||
|
||||
|
||||
@ -55,6 +55,13 @@ track == track2
|
||||
|
||||
If there are multiple rows meeting the criteria the `MultipleMatches` exception is raised.
|
||||
|
||||
## get_or_none
|
||||
|
||||
`get_or_none(**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.
|
||||
|
||||
|
||||
## get_or_create
|
||||
|
||||
`get_or_create(**kwargs) -> Model`
|
||||
@ -190,6 +197,14 @@ objects from other side of the relation.
|
||||
!!!tip
|
||||
To read more about `QuerysetProxy` visit [querysetproxy][querysetproxy] section
|
||||
|
||||
### get_or_none
|
||||
|
||||
Exact equivalent of get described above but instead of raising the exception returns `None` if no db record matching the criteria is found.
|
||||
|
||||
!!!tip
|
||||
To read more about `QuerysetProxy` visit [querysetproxy][querysetproxy] section
|
||||
|
||||
|
||||
### get_or_create
|
||||
|
||||
Works exactly the same as [get_or_create](./#get_or_create) function above but allows
|
||||
|
||||
@ -1,3 +1,14 @@
|
||||
# 0.10.1
|
||||
|
||||
## Features
|
||||
|
||||
* add `get_or_none(**kwargs)` method to `QuerySet` and `QuerysetProxy`. It is exact equivalent of `get(**kwargs)` but instead of raising `ormar.NoMatch` exception if there is no db record matching the criteria, `get_or_none` simply returns `None`.
|
||||
|
||||
## Fixes
|
||||
|
||||
* Fix dialect dependent quoting of column and table names in order_by clauses not working
|
||||
properly in postgres.
|
||||
|
||||
# 0.10.0
|
||||
|
||||
## Breaking
|
||||
|
||||
@ -75,7 +75,7 @@ class UndefinedType: # pragma no cover
|
||||
|
||||
Undefined = UndefinedType()
|
||||
|
||||
__version__ = "0.10.0"
|
||||
__version__ = "0.10.1"
|
||||
__all__ = [
|
||||
"Integer",
|
||||
"BigInteger",
|
||||
|
||||
@ -69,7 +69,13 @@ class OrderAction(QueryAction):
|
||||
:rtype: sqlalchemy.sql.elements.TextClause
|
||||
"""
|
||||
prefix = f"{self.table_prefix}_" if self.table_prefix else ""
|
||||
return text(f"{prefix}{self.table}" f".{self.field_alias} {self.direction}")
|
||||
table_name = self.table.name
|
||||
field_name = self.field_alias
|
||||
if not prefix:
|
||||
dialect = self.target_model.Meta.database._backend._dialect
|
||||
table_name = dialect.identifier_preparer.quote(table_name)
|
||||
field_name = dialect.identifier_preparer.quote(field_name)
|
||||
return text(f"{prefix}{table_name}" f".{field_name} {self.direction}")
|
||||
|
||||
def _split_value_into_parts(self, order_str: str) -> None:
|
||||
if order_str.startswith("-"):
|
||||
|
||||
@ -778,6 +778,26 @@ class QuerySet(Generic[T]):
|
||||
self.check_single_result_rows_count(processed_rows)
|
||||
return processed_rows[0] # type: ignore
|
||||
|
||||
async def get_or_none(self, **kwargs: Any) -> Optional["T"]:
|
||||
"""
|
||||
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.
|
||||
|
||||
Passing a criteria is actually calling filter(**kwargs) method described below.
|
||||
|
||||
If not match is found None will be returned.
|
||||
|
||||
:param kwargs: fields names and proper value types
|
||||
:type kwargs: Any
|
||||
:return: returned model
|
||||
:rtype: Model
|
||||
"""
|
||||
try:
|
||||
return await self.get(**kwargs)
|
||||
except ormar.NoMatch:
|
||||
return None
|
||||
|
||||
async def get(self, **kwargs: Any) -> "T":
|
||||
"""
|
||||
Get's the first row from the db meeting the criteria set by kwargs.
|
||||
|
||||
@ -279,6 +279,30 @@ class QuerysetProxy(Generic[T]):
|
||||
self._register_related(first)
|
||||
return first
|
||||
|
||||
async def get_or_none(self, **kwargs: Any) -> Optional["T"]:
|
||||
"""
|
||||
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.
|
||||
|
||||
Passing a criteria is actually calling filter(**kwargs) method described below.
|
||||
|
||||
If not match is found None will be returned.
|
||||
|
||||
:param kwargs: fields names and proper value types
|
||||
:type kwargs: Any
|
||||
:return: returned model
|
||||
:rtype: Model
|
||||
"""
|
||||
try:
|
||||
get = await self.queryset.get(**kwargs)
|
||||
except ormar.NoMatch:
|
||||
return None
|
||||
|
||||
self._clean_items_on_load()
|
||||
self._register_related(get)
|
||||
return get
|
||||
|
||||
async def get(self, **kwargs: Any) -> "T":
|
||||
"""
|
||||
Get's the first row from the db meeting the criteria set by kwargs.
|
||||
|
||||
@ -230,6 +230,8 @@ async def test_fernet_filters_nomatch():
|
||||
with pytest.raises(NoMatch):
|
||||
await Filter.objects.get(name="test1")
|
||||
|
||||
assert await Filter.objects.get_or_none(name="test1") is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_hash_filters_works():
|
||||
|
||||
@ -83,6 +83,18 @@ async def test_not_saved_raises_error(cleanup):
|
||||
await post.categories.add(news)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_not_existing_raises_error(cleanup):
|
||||
async with database:
|
||||
guido = await Author(first_name="Guido", last_name="Van Rossum").save()
|
||||
post = await Post.objects.create(title="Hello, M2M", author=guido)
|
||||
|
||||
with pytest.raises(NoMatch):
|
||||
await post.categories.get()
|
||||
|
||||
assert await post.categories.get_or_none() is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_assigning_related_objects(cleanup):
|
||||
async with database:
|
||||
@ -95,6 +107,9 @@ async def test_assigning_related_objects(cleanup):
|
||||
# or from the other end:
|
||||
await news.posts.add(post)
|
||||
|
||||
assert await post.categories.get_or_none(name="no exist") is None
|
||||
assert await post.categories.get_or_none(name="News") == news
|
||||
|
||||
# Creating columns object from instance:
|
||||
await post.categories.create(name="Tips")
|
||||
assert len(post.categories) == 2
|
||||
|
||||
@ -220,6 +220,8 @@ async def test_model_get():
|
||||
with pytest.raises(ormar.NoMatch):
|
||||
await User.objects.get()
|
||||
|
||||
assert await User.objects.get_or_none() is None
|
||||
|
||||
user = await User.objects.create(name="Tom")
|
||||
lookup = await User.objects.get()
|
||||
assert lookup == user
|
||||
|
||||
99
tests/test_reserved_sql_keywords_escaped.py
Normal file
99
tests/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"
|
||||
Reference in New Issue
Block a user