Django DatabaseError Invalid connector for timedelta

Viewed 212

Previously, I did implement the ExpressionWrapper to create a custom filter, It's working fine in the postgresql, but when I did run test with sqlite3, then the error said 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 clash with `AccessDuration.expiration_date` property.
        Issue: https://stackoverflow.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()

    ...

Anyway, I'm using docker for my project:

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

Then the error said:

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: *.
2 Answers

As per https://github.com/django/django/pull/14237 some of this is now supported atleast in SQLite. you should try more recent version of django to use it. And as per docs it should be possible on django 4.0. so give django==4.0rc1 try to make this working.

Added support for multiplying and dividing DurationField by scalar values on SQLite.

Related