From 16c232913fac6e675c115efb54a48b7f94e2d6dc Mon Sep 17 00:00:00 2001 From: Szymon Jasinski Date: Wed, 16 Sep 2020 15:50:10 +0200 Subject: [PATCH 1/2] Get django-revision to work with django-money --- src/ralph/__init__.py | 3 ++ src/ralph/lib/serializers/__init__.py | 73 +++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 src/ralph/lib/serializers/__init__.py diff --git a/src/ralph/__init__.py b/src/ralph/__init__.py index df6f6264bf..2c0b92dce8 100644 --- a/src/ralph/__init__.py +++ b/src/ralph/__init__.py @@ -1,3 +1,4 @@ +from django.core import serializers from django.db.models.options import Options __version__ = '3.0.0' @@ -12,3 +13,5 @@ def monkey_options_init(self, meta, app_label): Options.__init__ = lambda self, meta, app_label=None: monkey_options_init( self, meta, app_label ) + +serializers.register_serializer("json", "ralph.lib.serializers") diff --git a/src/ralph/lib/serializers/__init__.py b/src/ralph/lib/serializers/__init__.py new file mode 100644 index 0000000000..358952483d --- /dev/null +++ b/src/ralph/lib/serializers/__init__.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +import json +import sys + +from django.core.serializers.base import DeserializationError +from django.core.serializers.json import Serializer as JSONSerializer +from django.utils import six +from djmoney.models.fields import MoneyField + +from djmoney.money import Money +from djmoney.utils import get_currency_field_name + +from ralph.settings import DEFAULT_CURRENCY_CODE + +Serializer = JSONSerializer + + +def Deserializer(stream_or_string, **options): # noqa + """ + Deserialize a stream or string of JSON data. + """ + # Copied almost without changes from djmoney.serializers (django-money). + # Adding support for situation where old models to be deserialized have price field, + # but not price_currency field. + # In Ralph, price field existed before in various models as a Decimal field. + # All price fields were migrated to MoneyField without changing the original field name. + # This can cause problems in original django-money's implementation of Deserializer. + # This updated Deserializer is needed to get revision (django-revision) + # to work in circumstances described above. + + from django.core.serializers.python import Deserializer as PythonDeserializer, _get_model + + ignore = options.pop("ignorenonexistent", False) + + if not isinstance(stream_or_string, (bytes, six.string_types)): + stream_or_string = stream_or_string.read() + if isinstance(stream_or_string, bytes): + stream_or_string = stream_or_string.decode("utf-8") + try: + for obj in json.loads(stream_or_string): + try: + Model = _get_model(obj["model"]) + except DeserializationError: + if ignore: + continue + else: + raise + money_fields = {} + fields = {} + field_names = {field.name for field in Model._meta.get_fields()} + for (field_name, field_value) in six.iteritems(obj["fields"]): + if ignore and field_name not in field_names: + # skip fields no longer on model + continue + field = Model._meta.get_field(field_name) + if isinstance(field, MoneyField) and field_value is not None: + try: + currency = obj["fields"][get_currency_field_name(field_name)] + except KeyError: + currency = DEFAULT_CURRENCY_CODE + money_fields[field_name] = Money(field_value, currency) + else: + fields[field_name] = field_value + obj["fields"] = fields + + for inner_obj in PythonDeserializer([obj], **options): + for field, value in money_fields.items(): + setattr(inner_obj.object, field, value) + yield inner_obj + except (GeneratorExit, DeserializationError): + raise + except Exception as exc: + six.reraise(DeserializationError, DeserializationError(exc), sys.exc_info()[2]) From ca0320efd514578cedb957fa6a39ad42da4904e1 Mon Sep 17 00:00:00 2001 From: Szymon Jasinski Date: Wed, 16 Sep 2020 16:09:43 +0200 Subject: [PATCH 2/2] Get django-reversion to work with django-money --- src/ralph/__init__.py | 3 --- src/ralph/apps.py | 3 +++ src/ralph/lib/serializers/__init__.py | 23 ++++++++++++++--------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/ralph/__init__.py b/src/ralph/__init__.py index 2c0b92dce8..df6f6264bf 100644 --- a/src/ralph/__init__.py +++ b/src/ralph/__init__.py @@ -1,4 +1,3 @@ -from django.core import serializers from django.db.models.options import Options __version__ = '3.0.0' @@ -13,5 +12,3 @@ def monkey_options_init(self, meta, app_label): Options.__init__ = lambda self, meta, app_label=None: monkey_options_init( self, meta, app_label ) - -serializers.register_serializer("json", "ralph.lib.serializers") diff --git a/src/ralph/apps.py b/src/ralph/apps.py index 57ac781a9c..c314152fde 100644 --- a/src/ralph/apps.py +++ b/src/ralph/apps.py @@ -2,6 +2,7 @@ from importlib import import_module from django.apps import AppConfig +from django.core import serializers class RalphAppConfig(AppConfig): @@ -15,6 +16,8 @@ def ready(self): """ super().ready() + serializers.register_serializer("json", "ralph.lib.serializers") + package = self.module.__name__ for module in self.get_load_modules_when_ready(): try: diff --git a/src/ralph/lib/serializers/__init__.py b/src/ralph/lib/serializers/__init__.py index 358952483d..e6cb11b579 100644 --- a/src/ralph/lib/serializers/__init__.py +++ b/src/ralph/lib/serializers/__init__.py @@ -20,15 +20,17 @@ def Deserializer(stream_or_string, **options): # noqa Deserialize a stream or string of JSON data. """ # Copied almost without changes from djmoney.serializers (django-money). - # Adding support for situation where old models to be deserialized have price field, - # but not price_currency field. - # In Ralph, price field existed before in various models as a Decimal field. - # All price fields were migrated to MoneyField without changing the original field name. - # This can cause problems in original django-money's implementation of Deserializer. - # This updated Deserializer is needed to get revision (django-revision) + # Adding support for situation where old models to be deserialized have + # price field, but not price_currency field. + # In Ralph, price field existed before in various models as + # a Decimal field. All price fields were migrated to MoneyField + # without changing the original field name. This can cause problems + # in original django-money's implementation of Deserializer. + # This updated Deserializer is needed to get reversion (django-reversion) # to work in circumstances described above. - from django.core.serializers.python import Deserializer as PythonDeserializer, _get_model + from django.core.serializers.python import \ + Deserializer as PythonDeserializer, _get_model ignore = options.pop("ignorenonexistent", False) @@ -55,7 +57,8 @@ def Deserializer(stream_or_string, **options): # noqa field = Model._meta.get_field(field_name) if isinstance(field, MoneyField) and field_value is not None: try: - currency = obj["fields"][get_currency_field_name(field_name)] + currency = \ + obj["fields"][get_currency_field_name(field_name)] except KeyError: currency = DEFAULT_CURRENCY_CODE money_fields[field_name] = Money(field_value, currency) @@ -70,4 +73,6 @@ def Deserializer(stream_or_string, **options): # noqa except (GeneratorExit, DeserializationError): raise except Exception as exc: - six.reraise(DeserializationError, DeserializationError(exc), sys.exc_info()[2]) + six.reraise( + DeserializationError, DeserializationError(exc), sys.exc_info()[2] + )