tortoise / tortoise-orm

Familiar asyncio ORM for python, built with relations in mind
https://tortoise.github.io
Apache License 2.0
4.38k stars 356 forks source link

Field 'DatetimeField' has error when I use '__year' or '__month'. report this:" 'int' object has no attribute 'utcoffset' " #1575

Closed h2magic-axious closed 1 month ago

h2magic-axious commented 3 months ago

Describe the bug This is my model:

class Transfer(AbstractModel):
    class Meta:
        table = "transfer"

    account = fields.ForeignKeyField("models.Account", related_name="transfers")
    category = fields.ForeignKeyField("models.Category")

    title = fields.CharField(max_length=30)
    amount = fields.DecimalField(max_digits=10, decimal_places=2)
    remark = fields.CharField(max_length=128, default="")
    created_at = fields.DatetimeField(auto_now_add=True)

To Reproduce This is filter:

@router.get("/test")
async def test_all_transfers():
    transfers = await Transfer.filter(created_at__year=2024)
    return response_success(transfers)

Expected behavior I get follow:

AttributeError: 'int' object has no attribute 'utcoffset'

Additional context this is full stack:

ERROR:    Exception in ASGI application
  + Exception Group Traceback (most recent call last):
  |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\_utils.py", line 87, in collapse_excgroups
  |     yield
  |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\middleware\base.py", line 190, in __call__
  |     async with anyio.create_task_group() as task_group:
  |   File "D:\coding\money-manage\venv\Lib\site-packages\anyio\_backends\_asyncio.py", line 678, in __aexit__
  |     raise BaseExceptionGroup(
  | ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File "D:\coding\money-manage\venv\Lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 407, in run_asgi
    |     result = await app(  # type: ignore[func-returns-value]
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "D:\coding\money-manage\venv\Lib\site-packages\uvicorn\middleware\proxy_headers.py", line 69, in __call__
    |     return await self.app(scope, receive, send)
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "D:\coding\money-manage\venv\Lib\site-packages\fastapi\applications.py", line 1054, in __call__
    |     await super().__call__(scope, receive, send)
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\applications.py", line 123, in __call__
    |     await self.middleware_stack(scope, receive, send)
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\middleware\errors.py", line 186, in __call__
    |     raise exc
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\middleware\errors.py", line 164, in __call__
    |     await self.app(scope, receive, _send)
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\middleware\base.py", line 189, in __call__
    |     with collapse_excgroups():
    |   File "C:\Python312\Lib\contextlib.py", line 158, in __exit__
    |     self.gen.throw(value)
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\_utils.py", line 93, in collapse_excgroups
    |     raise exc
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\middleware\base.py", line 191, in __call__
    |     response = await self.dispatch_func(request, call_next)
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "D:\coding\money-manage\main.py", line 68, in process_before_request
    |     return await call_next(request)
    |            ^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\middleware\base.py", line 165, in call_next
    |     raise app_exc
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\middleware\base.py", line 151, in coro
    |     await self.app(scope, receive_or_disconnect, send_no_error)
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\middleware\cors.py", line 83, in __call__
    |     await self.app(scope, receive, send)
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\middleware\exceptions.py", line 62, in __call__
    |     await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\_exception_handler.py", line 64, in wrapped_app
    |     raise exc
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
    |     await app(scope, receive, sender)
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\routing.py", line 758, in __call__
    |     await self.middleware_stack(scope, receive, send)
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\routing.py", line 778, in app
    |     await route.handle(scope, receive, send)
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\routing.py", line 299, in handle
    |     await self.app(scope, receive, send)
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\routing.py", line 79, in app
    |     await wrap_app_handling_exceptions(app, request)(scope, receive, send)
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\_exception_handler.py", line 64, in wrapped_app
    |     raise exc
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
    |     await app(scope, receive, sender)
    |   File "D:\coding\money-manage\venv\Lib\site-packages\starlette\routing.py", line 74, in app
    |     response = await func(request)
    |                ^^^^^^^^^^^^^^^^^^^
    |   File "D:\coding\money-manage\venv\Lib\site-packages\fastapi\routing.py", line 278, in app
    |     raw_response = await run_endpoint_function(
    |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "D:\coding\money-manage\venv\Lib\site-packages\fastapi\routing.py", line 191, in run_endpoint_function
    |     return await dependant.call(**values)
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "D:\coding\money-manage\routers\transfer.py", line 17, in test_all_transfers
    |     transfers = await Transfer.filter(created_at__year=2024)
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "D:\coding\money-manage\venv\Lib\site-packages\tortoise\queryset.py", line 1000, in __await__
    |     self._make_query()
    |   File "D:\coding\money-manage\venv\Lib\site-packages\tortoise\queryset.py", line 962, in _make_query
    |     self.resolve_filters(
    |   File "D:\coding\money-manage\venv\Lib\site-packages\tortoise\queryset.py", line 134, in resolve_filters
    |     modifier &= node.resolve(model, model._meta.basetable)
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "D:\coding\money-manage\venv\Lib\site-packages\tortoise\expressions.py", line 359, in resolve
    |     return self._resolve_kwargs(model, table)
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "D:\coding\money-manage\venv\Lib\site-packages\tortoise\expressions.py", line 321, in _resolve_kwargs
    |     filter_modifier = self._resolve_regular_kwarg(model, key, value, table)
    |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "D:\coding\money-manage\venv\Lib\site-packages\tortoise\expressions.py", line 280, in _resolve_regular_kwarg
    |     criterion, join = self._process_filter_kwarg(model, key, value, table)
    |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "D:\coding\money-manage\venv\Lib\site-packages\tortoise\expressions.py", line 265, in _process_filter_kwarg
    |     else model._meta.db.executor_class._field_to_db(field_object, value, model)
    |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "D:\coding\money-manage\venv\Lib\site-packages\tortoise\backends\base\executor.py", line 198, in _field_to_db
    |     return field_object.to_db_value(attr, instance)
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "D:\coding\money-manage\venv\Lib\site-packages\tortoise\fields\data.py", line 383, in to_db_value
    |     if timezone.is_naive(value):
    |        ^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "D:\coding\money-manage\venv\Lib\site-packages\tortoise\timezone.py", line 84, in is_naive
    |     return value.utcoffset() is None
    |            ^^^^^^^^^^^^^^^
    | AttributeError: 'int' object has no attribute 'utcoffset'
    +------------------------------------
waketzheng commented 2 months ago

Change line 381 in tortoise/fields/data.py from if value is not None to if isinstance(value, datetime.datetime)

image
abondar commented 1 month ago

Fixed in 0.21.0