Django DatabaseError timedelta的无效连接器

Posted

技术标签:

【中文标题】Django DatabaseError timedelta的无效连接器【英文标题】:Django DatabaseError Invalid connector for timedelta 【发布时间】:2021-12-26 08:33:18 【问题描述】:

以前,我确实实现了ExpressionWrapper 来创建自定义过滤器,它在postgresql 中运行良好,但是当我使用sqlite3 运行测试时,错误提示为django.db.utils.DatabaseError: Invalid connector for timedelta: *.

class AccessDurationQuerySet(models.QuerySet):

    def filter_expiration(self, is_expired: bool = False):
        """
        To filter whether AccessDuration already expired or yet.
        don't use the same `expiration_date` as expression key,
        because it will *** with `AccessDuration.expiration_date` property.
        Issue: https://***.com/q/69012110/6396981
        """
        kwargs = '_expiration_date__gte': Now()
        if is_expired:
            del kwargs['_expiration_date__gte']
            kwargs['_expiration_date__lt'] = Now()

        return self.all().annotate(
            _expiration_date=models.ExpressionWrapper(
                models.F('lab_start_date') + (timezone.timedelta(days=1) * models.F('duration')),
                output_field=models.DateTimeField()
            )
        ).filter(**kwargs)


class AccessDurationManager(
    models.Manager.from_queryset(AccessDurationQuerySet),
    DefaultManager
):

    def published(self):
        return super().published().filter(
            status=AccessDurationStatusChoices.active
        )


class AccessDuration(TimeStampedModel):
    course = models.CharField(max_length=100)
    duration = models.FloatField(default=settings.DEFAULT_DURATION,
                                 help_text=_('In days'))
    container_duration = models.CharField(max_length=100,
                                          default=_('Up 2 hours'),
                                          help_text=_('How long a container has to be running, '
                                                      'value based on Docker API Status'))
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    lab_start_date = models.DateTimeField(verbose_name=('Start Date'),
                                          null=True)
    status = models.CharField(max_length=20,
                              choices=AccessDurationStatusChoices.choices,
                              default=AccessDurationStatusChoices.inactive)
    objects = AccessDurationManager()

    ...

无论如何,我正在为我的项目使用 docker:

docker-compose -f local.yml run django pytest myproject/tests/flows/2_test_invite_student.py::test_invite_student_by_superuser

然后报错说:

self = <django.db.backends.sqlite3.operations.DatabaseOperations object at 0x7f203c6313a0>, connector = '*', sub_expressions = ['86400000000', '"webssh_accessduration"."duration"']

    def combine_duration_expression(self, connector, sub_expressions):
        if connector not in ['+', '-']:
>           raise DatabaseError('Invalid connector for timedelta: %s.' % connector)
E           django.db.utils.DatabaseError: Invalid connector for timedelta: *.

/usr/local/lib/python3.8/site-packages/django/db/backends/sqlite3/operations.py:341: DatabaseError
-------------------------------------------------------------------------------------------------------------------- Captured log call ---------------------------------------------------------------------------------------------------------------------
ERROR    django.request:log.py:224 Internal Server Error: /dashboard/manager/courses/manage/
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.8/site-packages/django/core/handlers/base.py", line 204, in _get_response
    response = response.render()
  File "/usr/local/lib/python3.8/site-packages/django/template/response.py", line 105, in render
    self.content = self.rendered_content
  File "/usr/local/lib/python3.8/site-packages/django/template/response.py", line 83, in rendered_content
    return template.render(context, self._request)
  File "/usr/local/lib/python3.8/site-packages/django/template/backends/django.py", line 61, in render
    return self.template.render(context)
  File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 170, in render
    return self._render(context)
  File "/usr/local/lib/python3.8/site-packages/django/test/utils.py", line 96, in instrumented_test_render
    return self.nodelist.render(context)
  File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 938, in render
    bit = node.render_annotated(context)
  File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.8/site-packages/django/template/loader_tags.py", line 150, in render
    return compiled_parent._render(context)
  File "/usr/local/lib/python3.8/site-packages/django/test/utils.py", line 96, in instrumented_test_render
    return self.nodelist.render(context)
  File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 938, in render
    bit = node.render_annotated(context)
  File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.8/site-packages/django/template/loader_tags.py", line 62, in render
    result = block.nodelist.render(context)
  File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 938, in render
    bit = node.render_annotated(context)
  File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.8/site-packages/django/template/defaulttags.py", line 168, in render
    len_values = len(values)
  File "/usr/local/lib/python3.8/site-packages/django/db/models/query.py", line 269, in __len__
    self._fetch_all()
  File "/usr/local/lib/python3.8/site-packages/django/db/models/query.py", line 1308, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "/usr/local/lib/python3.8/site-packages/django/db/models/query.py", line 53, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1143, in execute_sql
    sql, params = self.as_sql()
  File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 498, in as_sql
    extra_select, order_by, group_by = self.pre_sql_setup()
  File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 55, in pre_sql_setup
    self.setup_query()
  File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 46, in setup_query
    self.select, self.klass_info, self.annotation_col_map = self.get_select()
  File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 262, in get_select
    sql, params = self.compile(col)
  File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 430, in compile
    sql, params = node.as_sql(self, self.connection)
  File "/usr/local/lib/python3.8/site-packages/django/db/models/expressions.py", line 878, in as_sql
    return compiler.compile(self.expression)
  File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 428, in compile
    sql, params = vendor_impl(self, self.connection)
  File "/usr/local/lib/python3.8/site-packages/django/db/models/expressions.py", line 22, in as_sqlite
    sql, params = self.as_sql(compiler, connection, **extra_context)
  File "/usr/local/lib/python3.8/site-packages/django/db/models/expressions.py", line 465, in as_sql
    sql, params = compiler.compile(self.rhs)
  File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 428, in compile
    sql, params = vendor_impl(self, self.connection)
  File "/usr/local/lib/python3.8/site-packages/django/db/models/expressions.py", line 22, in as_sqlite
    sql, params = self.as_sql(compiler, connection, **extra_context)
  File "/usr/local/lib/python3.8/site-packages/django/db/models/expressions.py", line 455, in as_sql
    return DurationExpression(self.lhs, self.connector, self.rhs).as_sql(compiler, connection)
  File "/usr/local/lib/python3.8/site-packages/django/db/models/expressions.py", line 506, in as_sql
    sql = connection.ops.combine_duration_expression(self.connector, expressions)
  File "/usr/local/lib/python3.8/site-packages/django/db/backends/sqlite3/operations.py", line 341, in combine_duration_expression
    raise DatabaseError('Invalid connector for timedelta: %s.' % connector)
django.db.utils.DatabaseError: Invalid connector for timedelta: *.

【问题讨论】:

【参考方案1】:

SQLite 和 mysql 不支持持续时间表达式的乘法和除法连接器。

您可以通过以下链接了解更多信息https://code.djangoproject.com/ticket/25287

【讨论】:

那我该如何实现呢?基本上,我想过滤访问持续时间是否过期(基于AccessDuration中的现有字段) 检查我的新答案,我对此有一些看法。【参考方案2】:

根据https://github.com/django/django/pull/14237,其中一些现在至少在 SQLite 中得到支持。你应该尝试更新版本的 django 来使用它。根据文档,在 django 4.0 上应该是可能的。所以给 django==4.0rc1 尝试让它工作。

增加了对标量乘除 DurationField 的支持 SQLite 上的值。

【讨论】:

似乎只支持'DecimalField', 'DurationField', 'FloatField', 'IntegerField',github.com/django/django/pull/14237/…这些字段,同时我的情况是DateTimeField

以上是关于Django DatabaseError timedelta的无效连接器的主要内容,如果未能解决你的问题,请参考以下文章

Django如何在DatabaseError后重新连接:查询超时

(DatabaseError: no such table: django_session) Django 1.3 selenium 测试期间的错误

Django DatabaseError timedelta的无效连接器

Django DatabaseError - 没有这样的保存点

java.sql.SQLException: Io 异常: Socket read timed out

尝试重置密码时出现 Djongo + Django + MongoDB Atlas DatabaseError