diff --git a/django/db/models/fields/subclassing.py b/django/db/models/fields/subclassing.py
|
a
|
b
|
|
| 76 | 76 | A metaclass for custom Field subclasses. This ensures the model's attribute |
| 77 | 77 | has the descriptor protocol attached to it. |
| 78 | 78 | """ |
| 79 | | def __new__(cls, base, name, attrs): |
| 80 | | new_class = super(SubfieldBase, cls).__new__(cls, base, name, attrs) |
| 81 | | new_class.contribute_to_class = make_contrib( |
| 82 | | attrs.get('contribute_to_class')) |
| 83 | | return new_class |
| | 79 | def __init__(cls, name, base, attrs): |
| | 80 | def contribute_to_class(self, model_cls, name): |
| | 81 | super(cls, self).contribute_to_class(model_cls, name) |
| | 82 | setattr(model_cls, name, Creator(self)) |
| | 83 | cls.contribute_to_class = contribute_to_class |
| 84 | 84 | |
| 85 | 85 | class Creator(object): |
| 86 | 86 | """ |
| … |
… |
|
| 96 | 96 | |
| 97 | 97 | def __set__(self, obj, value): |
| 98 | 98 | obj.__dict__[self.field.name] = self.field.to_python(value) |
| 99 | | |
| 100 | | def make_contrib(func=None): |
| 101 | | """ |
| 102 | | Returns a suitable contribute_to_class() method for the Field subclass. |
| 103 | | |
| 104 | | If 'func' is passed in, it is the existing contribute_to_class() method on |
| 105 | | the subclass and it is called before anything else. It is assumed in this |
| 106 | | case that the existing contribute_to_class() calls all the necessary |
| 107 | | superclass methods. |
| 108 | | """ |
| 109 | | def contribute_to_class(self, cls, name): |
| 110 | | if func: |
| 111 | | func(self, cls, name) |
| 112 | | else: |
| 113 | | super(self.__class__, self).contribute_to_class(cls, name) |
| 114 | | setattr(cls, self.name, Creator(self)) |
| 115 | | |
| 116 | | return contribute_to_class |
diff --git a/tests/modeltests/field_subclassing/fields.py b/tests/modeltests/field_subclassing/fields.py
|
a
|
b
|
|
| 40 | 40 | return value |
| 41 | 41 | return Small(value[0], value[1]) |
| 42 | 42 | |
| 43 | | def get_db_prep_save(self, value): |
| | 43 | def get_db_prep_save(self, value, connection): |
| 44 | 44 | return unicode(value) |
| 45 | 45 | |
| 46 | 46 | def get_prep_lookup(self, lookup_type, value): |
| … |
… |
|
| 53 | 53 | raise TypeError('Invalid lookup type: %r' % lookup_type) |
| 54 | 54 | |
| 55 | 55 | |
| | 56 | class SmallerField(SmallField): |
| | 57 | """ |
| | 58 | Checks that the SubfieldBase metaclass works with inheritance. |
| | 59 | """ |
| | 60 | |
| | 61 | |
| 56 | 62 | class JSONField(models.TextField): |
| 57 | 63 | __metaclass__ = models.SubfieldBase |
| 58 | 64 | |
| … |
… |
|
| 62 | 68 | def to_python(self, value): |
| 63 | 69 | if not value: |
| 64 | 70 | return None |
| 65 | | |
| 66 | 71 | if isinstance(value, basestring): |
| 67 | 72 | value = json.loads(value) |
| 68 | 73 | return value |
| 69 | 74 | |
| 70 | | def get_db_prep_save(self, value): |
| | 75 | def get_db_prep_save(self, value, connection): |
| 71 | 76 | if value is None: |
| 72 | 77 | return None |
| 73 | 78 | return json.dumps(value) |
diff --git a/tests/modeltests/field_subclassing/models.py b/tests/modeltests/field_subclassing/models.py
|
a
|
b
|
|
| 5 | 5 | from django.db import models |
| 6 | 6 | from django.utils.encoding import force_unicode |
| 7 | 7 | |
| 8 | | from fields import Small, SmallField, JSONField |
| | 8 | from fields import Small, SmallField, SmallerField, JSONField |
| 9 | 9 | |
| 10 | 10 | |
| 11 | | class MyModel(models.Model): |
| | 11 | class AbstractSmallModel(models.Model): |
| 12 | 12 | name = models.CharField(max_length=10) |
| 13 | | data = SmallField('small field') |
| | 13 | |
| | 14 | class Meta: |
| | 15 | abstract = True |
| 14 | 16 | |
| 15 | 17 | def __unicode__(self): |
| 16 | 18 | return force_unicode(self.name) |
| 17 | 19 | |
| | 20 | |
| | 21 | class SmallModel(AbstractSmallModel): |
| | 22 | data = SmallField('small field') |
| | 23 | |
| | 24 | |
| | 25 | try: |
| | 26 | class SmallerModel(AbstractSmallModel): |
| | 27 | data = SmallerField('small field') |
| | 28 | except RuntimeError: # Ticket #10728 |
| | 29 | SmallerModel = None |
| | 30 | |
| | 31 | |
| 18 | 32 | class DataModel(models.Model): |
| 19 | 33 | data = JSONField() |
diff --git a/tests/modeltests/field_subclassing/tests.py b/tests/modeltests/field_subclassing/tests.py
|
a
|
b
|
|
| 2 | 2 | from django.test import TestCase |
| 3 | 3 | |
| 4 | 4 | from fields import Small |
| 5 | | from models import DataModel, MyModel |
| | 5 | from models import DataModel, SmallModel, SmallerModel |
| 6 | 6 | |
| 7 | 7 | |
| 8 | 8 | class CustomField(TestCase): |
| … |
… |
|
| 22 | 22 | self.assertTrue(isinstance(d.data, list)) |
| 23 | 23 | self.assertEqual(d.data, [1, 2, 3]) |
| 24 | 24 | |
| 25 | | def test_custom_field(self): |
| | 25 | def test_custom_field(self, MyModel=SmallModel): |
| 26 | 26 | # Creating a model with custom fields is done as per normal. |
| 27 | 27 | s = Small(1, 2) |
| 28 | 28 | self.assertEqual(str(s), "12") |
| … |
… |
|
| 55 | 55 | |
| 56 | 56 | # Serialization works, too. |
| 57 | 57 | stream = serializers.serialize("json", MyModel.objects.all()) |
| 58 | | self.assertEqual(stream, '[{"pk": 1, "model": "field_subclassing.mymodel", "fields": {"data": "12", "name": "m"}}]') |
| | 58 | self.assertEqual(stream, '[{"pk": 1, "model": "field_subclassing.%s", ' |
| | 59 | '"fields": {"data": "12", "name": "m"}}]' |
| | 60 | % MyModel.__name__.lower()) |
| 59 | 61 | |
| 60 | 62 | obj = list(serializers.deserialize("json", stream))[0] |
| 61 | 63 | self.assertEqual(obj.object, m) |
| … |
… |
|
| 73 | 75 | ], |
| 74 | 76 | lambda m: str(m.data) |
| 75 | 77 | ) |
| | 78 | |
| | 79 | def test_custom_field_subclass(self): |
| | 80 | if not SmallerModel: |
| | 81 | self.fail("Couldn't subclass SubfieldBase field") |
| | 82 | else: |
| | 83 | self.test_custom_field(SmallerModel) |
| | 84 | |