diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 0eeb485797..b672e4efc3 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -7,15 +7,15 @@ other serializers. from django.conf import settings from django.core.serializers import base from django.db import models -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_unicode, is_protected_type class Serializer(base.Serializer): """ Serializes a QuerySet to basic Python objects. """ - + internal_use_only = True - + def start_serialization(self): self._current = None self.objects = [] @@ -35,7 +35,14 @@ class Serializer(base.Serializer): self._current = None def handle_field(self, obj, field): - self._current[field.name] = smart_unicode(getattr(obj, field.name), strings_only=True) + value = field._get_val_from_obj(obj) + # Protected types (i.e., primitives like None, numbers, dates, + # and Decimals) are passed through as is. All other values are + # converted to string first. + if is_protected_type(value): + self._current[field.name] = value + else: + self._current[field.name] = field.value_to_string(obj) def handle_fk_field(self, obj, field): related = getattr(obj, field.name) diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index 04498db00c..2d74fe28f3 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -65,11 +65,9 @@ class Serializer(base.Serializer): "type" : field.get_internal_type() }) - # Get a "string version" of the object's data (this is handled by the - # serializer base class). + # Get a "string version" of the object's data. if getattr(obj, field.name) is not None: - value = self.get_string_value(obj, field) - self.xml.characters(smart_unicode(value)) + self.xml.characters(field.value_to_string(obj)) else: self.xml.addQuickElement("None") diff --git a/django/utils/encoding.py b/django/utils/encoding.py index 7e87b05c79..9388d67f4c 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -41,6 +41,19 @@ def smart_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): return s return force_unicode(s, encoding, strings_only, errors) +def is_protected_type(obj): + """Determine if the object instance is of a protected type. + + Objects of protected types are preserved as-is when passed to + force_unicode(strings_only=True). + """ + return isinstance(obj, ( + types.NoneType, + int, long, + datetime.datetime, datetime.date, datetime.time, + float, Decimal) + ) + def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): """ Similar to smart_unicode, except that lazy instances are resolved to @@ -48,7 +61,7 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): If strings_only is True, don't convert (some) non-string-like objects. """ - if strings_only and isinstance(s, (types.NoneType, int, long, datetime.datetime, datetime.date, datetime.time, float, Decimal)): + if strings_only and is_protected_type(s): return s try: if not isinstance(s, basestring,): diff --git a/tests/modeltests/serializers/models.py b/tests/modeltests/serializers/models.py index eda19ecc4f..cf1bf50c24 100644 --- a/tests/modeltests/serializers/models.py +++ b/tests/modeltests/serializers/models.py @@ -73,6 +73,45 @@ class Movie(models.Model): class Score(models.Model): score = models.FloatField() + +class Team(object): + def __init__(self, title): + self.title = title + + def __unicode__(self): + raise NotImplementedError("Not so simple") + + def __str__(self): + raise NotImplementedError("Not so simple") + + def to_string(self): + return "%s" % self.title + +class TeamField(models.CharField): + __metaclass__ = models.SubfieldBase + + def __init__(self): + super(TeamField, self).__init__(max_length=100) + + def get_db_prep_save(self, value): + return unicode(value.title) + + def to_python(self, value): + if isinstance(value, Team): + return value + return Team(value) + + def value_to_string(self, obj): + return self._get_val_from_obj(obj).to_string() + +class Player(models.Model): + name = models.CharField(max_length=50) + rank = models.IntegerField() + team = TeamField() + + def __unicode__(self): + return u'%s (%d) playing for %s' % (self.name, self.rank, self.team.to_string()) + __test__ = {'API_TESTS':""" # Create some data: >>> from datetime import datetime @@ -223,6 +262,21 @@ None >>> print list(serializers.deserialize('json', serializers.serialize('json', [sc])))[0].object.score 3.4 +# Custom field with non trivial to string convertion value +>>> player = Player() +>>> player.name = "Soslan Djanaev" +>>> player.rank = 1 +>>> player.team = Team("Spartak Moskva") +>>> player.save() + +>>> serialized = serializers.serialize("json", Player.objects.all()) +>>> print serialized +[{"pk": 1, "model": "serializers.player", "fields": {"name": "Soslan Djanaev", "rank": 1, "team": "Spartak Moskva"}}] + +>>> obj = list(serializers.deserialize("json", serialized))[0] +>>> print obj + + """} try: @@ -259,6 +313,20 @@ try: +# Custom field with non trivial to string convertion value with YAML serializer + +>>> print serializers.serialize("yaml", Player.objects.all()) +- fields: {name: Soslan Djanaev, rank: 1, team: Spartak Moskva} + model: serializers.player + pk: 1 + + +>>> serialized = serializers.serialize("yaml", Player.objects.all()) +>>> obj = list(serializers.deserialize("yaml", serialized))[0] +>>> print obj + + + """ except ImportError: pass