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 | |