Skip to content

Commit

Permalink
fix: DatetimeField use __year report `int object has no attribute u…
Browse files Browse the repository at this point in the history
…tcoffset` (1575) (#1580)
  • Loading branch information
waketzheng authored Apr 27, 2024
1 parent 0031d7e commit 677fe21
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Fixed
- Fix pydantic v2.5 unittest error. (#1535)
- Fix pydantic_model_creator `exclude_readonly` parameter not working.
- Fix annotation propagation for non-filter queries. (#1590)
- Fix `DatetimeField` use '__year' report `'int' object has no attribute 'utcoffset'`. (#1575)

0.20.0
------
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -180,5 +180,5 @@ source = ["tortoise"]
[tool.coverage.report]
show_missing = true

[tool.ruff]
[tool.ruff.lint]
ignore = ["E501"]
13 changes: 13 additions & 0 deletions tests/fields/test_time.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import os
from datetime import date, datetime, time, timedelta
from time import sleep
from unittest.mock import patch

import pytz
from iso8601 import ParseError

from tests import testmodels
from tortoise import fields, timezone
from tortoise.contrib import test
from tortoise.contrib.test.condition import NotIn
from tortoise.exceptions import ConfigurationError, IntegrityError
from tortoise.timezone import get_default_timezone

Expand Down Expand Up @@ -147,6 +149,17 @@ async def test_timezone(self):
os.environ["TIMEZONE"] = old_tz
os.environ["USE_TZ"] = old_use_tz

@test.requireCapability(dialect=NotIn("sqlite", "mssql"))
async def test_filter_by_year_month_day(self):
with patch.dict(os.environ, {"USE_TZ": "True"}):
obj = await testmodels.DatetimeFields.create(datetime=datetime(2024, 1, 2))
same_year_objs = await testmodels.DatetimeFields.filter(datetime__year=2024)
filtered_obj = await testmodels.DatetimeFields.filter(
datetime__year=2024, datetime__month=1, datetime__day=2
).first()
assert obj == filtered_obj
assert obj.id in [i.id for i in same_year_objs]


@test.requireCapability(dialect="sqlite")
@test.requireCapability(dialect="postgres")
Expand Down
29 changes: 17 additions & 12 deletions tortoise/fields/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,9 +298,7 @@ def __init__(self, max_digits: int, decimal_places: int, **kwargs: Any) -> None:
self.quant = Decimal("1" if decimal_places == 0 else f"1.{('0' * decimal_places)}")

def to_python_value(self, value: Any) -> Optional[Decimal]:
if value is None:
value = None
else:
if value is not None:
value = Decimal(value).quantize(self.quant).normalize()
self.validate(value)
return value
Expand All @@ -316,6 +314,15 @@ def function_cast(self, term: Term) -> Term:
return functions.Cast(term, SqlTypes.NUMERIC)


# In case of queryset with filter `__year`/`__month`/`__day` ..., value can be int, float or str. Example:
# `await MyModel.filter(created_at__year=2024)`
# `await MyModel.filter(created_at__year=2024.0)`
# `await MyModel.filter(created_at__year='2024')`
DatetimeFieldQueryValueType = TypeVar(
"DatetimeFieldQueryValueType", datetime.datetime, int, float, str
)


class DatetimeField(Field[datetime.datetime], datetime.datetime):
"""
Datetime field.
Expand Down Expand Up @@ -351,9 +358,7 @@ def __init__(self, auto_now: bool = False, auto_now_add: bool = False, **kwargs:
self.auto_now_add = auto_now | auto_now_add

def to_python_value(self, value: Any) -> Optional[datetime.datetime]:
if value is None:
value = None
else:
if value is not None:
if isinstance(value, datetime.datetime):
value = value
elif isinstance(value, int):
Expand All @@ -368,18 +373,18 @@ def to_python_value(self, value: Any) -> Optional[datetime.datetime]:
return value

def to_db_value(
self, value: Optional[datetime.datetime], instance: "Union[Type[Model], Model]"
) -> Optional[datetime.datetime]:
self, value: Optional[DatetimeFieldQueryValueType], instance: "Union[Type[Model], Model]"
) -> Optional[DatetimeFieldQueryValueType]:
# Only do this if it is a Model instance, not class. Test for guaranteed instance var
if hasattr(instance, "_saved_in_db") and (
self.auto_now
or (self.auto_now_add and getattr(instance, self.model_field_name) is None)
):
value = timezone.now()
setattr(instance, self.model_field_name, value)
return value
now = timezone.now()
setattr(instance, self.model_field_name, now)
return now # type:ignore[return-value]
if value is not None:
if get_use_tz():
if isinstance(value, datetime.datetime) and get_use_tz():
if timezone.is_naive(value):
warnings.warn(
"DateTimeField %s received a naive datetime (%s)"
Expand Down

0 comments on commit 677fe21

Please sign in to comment.