Opened 7 years ago
Last modified 4 years ago
#29738 closed Bug
Django can't serialize DateTimeTZRange(lower=None, upper=None, bounds='[)') — at Version 4
| Reported by: | Graham Mayer | Owned by: | nobody |
|---|---|---|---|
| Component: | Migrations | Version: | 2.0 |
| Severity: | Normal | Keywords: | rangefield postgresql psycopg2 migrations removed |
| Cc: | jon.dufresne@… | Triage Stage: | Accepted |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description (last modified by )
Tried to use DateTimeTZRange(lower=None, upper=None, bounds='[)') as a default for a model field and get the following error when running python manage.py makemigrations:
Traceback (most recent call last):
File "manage.py", line 12, in <module>
execute_from_command_line(sys.argv)
File "/home/grahammayer/logimeter/logimeter/lib/python3.6/site-packages/django/core/management/__init__.py", line 371, in execute_from_command_line
utility.execute()
File "/home/grahammayer/logimeter/logimeter/lib/python3.6/site-packages/django/core/management/__init__.py", line 365, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/home/grahammayer/logimeter/logimeter/lib/python3.6/site-packages/django/core/management/base.py", line 288, in run_from_argv
self.execute(*args, **cmd_options)
File "/home/grahammayer/logimeter/logimeter/lib/python3.6/site-packages/django/core/management/base.py", line 335, in execute
output = self.handle(*args, **options)
File "/home/grahammayer/logimeter/logimeter/lib/python3.6/site-packages/django/core/management/commands/makemigrations.py", line 172, in handle
self.write_migration_files(changes)
File "/home/grahammayer/logimeter/logimeter/lib/python3.6/site-packages/django/core/management/commands/makemigrations.py", line 210, in write_migration_files
migration_string = writer.as_string()
File "/home/grahammayer/logimeter/logimeter/lib/python3.6/site-packages/django/db/migrations/writer.py", line 151, in as_string
operation_string, operation_imports = OperationWriter(operation).serialize()
File "/home/grahammayer/logimeter/logimeter/lib/python3.6/site-packages/django/db/migrations/writer.py", line 110, in serialize
_write(arg_name, arg_value)
File "/home/grahammayer/logimeter/logimeter/lib/python3.6/site-packages/django/db/migrations/writer.py", line 74, in _write
arg_string, arg_imports = MigrationWriter.serialize(_arg_value)
File "/home/grahammayer/logimeter/logimeter/lib/python3.6/site-packages/django/db/migrations/writer.py", line 279, in serialize
return serializer_factory(value).serialize()
File "/home/grahammayer/logimeter/logimeter/lib/python3.6/site-packages/django/db/migrations/serializer.py", line 203, in serialize
return self.serialize_deconstructed(path, args, kwargs)
File "/home/grahammayer/logimeter/logimeter/lib/python3.6/site-packages/django/db/migrations/serializer.py", line 90, in serialize_deconstructed
arg_string, arg_imports = serializer_factory(arg).serialize()
File "/home/grahammayer/logimeter/logimeter/lib/python3.6/site-packages/django/db/migrations/serializer.py", line 370, in serializer_factory
"topics/migrations/#migration-serializing" % (value, get_docs_version())
ValueError: Cannot serialize: DateTimeTZRange(None, None, '[)')
Change History (4)
comment:1 by , 7 years ago
| Component: | Migrations → contrib.postgres |
|---|---|
| Description: | modified (diff) |
| Easy pickings: | set |
| Keywords: | rangefield postgresql psycopg2 migrations added |
| Needs documentation: | set |
| Needs tests: | set |
| Triage Stage: | Unreviewed → Accepted |
comment:2 by , 7 years ago
| Description: | modified (diff) |
|---|---|
| Keywords: | rangefield postgresql psycopg2 migrations removed |
A solution could to make django.db.migration.serializer offer a way to register serializers for types and have django.contrib.postgres.App.ready() be in charge of registering the ones to handle psycopg2 range types.
If the registry uses an OrderedDict we should be able to replace that long series of if by iterating over the registry items.
registry = OrderedDict([ (models.Field, ModelFieldSerializer), ... ]) def serializer_factory(value): if isinstance(value, Promise): value = str(value) elif isinstance(value, LazyObject): # The unwrapped value is returned as the first item of the arguments # tuple. value = value.__reduce__()[1][0] for type_, serializer_cls in registry.items(): if isinstance(value, type_): return serializer_cls(value) raise ValueError(...)
The hasattr and checks could be performed using abcs.
from abc import ABC class Deconstructable(ABC): @classmethod def __subclasshook__(cls, C): return hasattr(C, 'deconstruct')
comment:3 by , 7 years ago
| Keywords: | rangefield postgresql psycopg2 migrations removed added |
|---|
comment:4 by , 7 years ago
| Description: | modified (diff) |
|---|
Presumably you are using
default=DateTimeTZRange(lower=None, upper=None, bounds='[)')? Could you provide a simple example project?The problem is that
psycopg2.extras.DateTimeTZRangecannot be serialized into a migrations, see documentation.So there are a few options:
default=None, null=True?default=(None, None)work instead? (I believe this will be limited to the default bounds:[))DateTimeTZRangedeconstructible which will allow for other bounds to be defined.We should be able to get away with making these range objects deconstructible as we only expect them to take simple arguments that are already supported by the serializer.
Here is an example:
I managed to produce the following migration:
(Disclaimer: I have only attempted to produce migrations and not actually called
RangeTestModel.objects.create()...)So in summary, I think that we only need to add some documentation to explain how to handle defaults for range fields and tests to ensure that it continues to work.