diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index 5d9e14ab46..af497424fa 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -12,7 +12,6 @@ from django.db.models.expressions import ( # NOQA from django.db.models.fields import * # NOQA from django.db.models.fields.files import FileField, ImageField # NOQA from django.db.models.fields.proxy import OrderWrt # NOQA -from django.db.models.fields.subclassing import SubfieldBase # NOQA from django.db.models.lookups import Lookup, Transform # NOQA from django.db.models.manager import Manager # NOQA from django.db.models.query import Q, Prefetch, QuerySet # NOQA diff --git a/django/db/models/fields/subclassing.py b/django/db/models/fields/subclassing.py deleted file mode 100644 index 84d13ce85c..0000000000 --- a/django/db/models/fields/subclassing.py +++ /dev/null @@ -1,63 +0,0 @@ -""" -Convenience routines for creating non-trivial Field subclasses, as well as -backwards compatibility utilities. - -Add SubfieldBase as the metaclass for your Field subclass, implement -to_python() and the other necessary methods and everything will work -seamlessly. -""" - -import warnings - -from django.utils.deprecation import RemovedInDjango110Warning - - -class SubfieldBase(type): - """ - A metaclass for custom Field subclasses. This ensures the model's attribute - has the descriptor protocol attached to it. - """ - def __new__(cls, name, bases, attrs): - warnings.warn("SubfieldBase has been deprecated. Use Field.from_db_value instead.", - RemovedInDjango110Warning) - - new_class = super(SubfieldBase, cls).__new__(cls, name, bases, attrs) - new_class.contribute_to_class = make_contrib( - new_class, attrs.get('contribute_to_class') - ) - return new_class - - -class Creator(object): - """ - A placeholder class that provides a way to set the attribute on the model. - """ - def __init__(self, field): - self.field = field - - def __get__(self, obj, type=None): - if obj is None: - return self - return obj.__dict__[self.field.name] - - def __set__(self, obj, value): - obj.__dict__[self.field.name] = self.field.to_python(value) - - -def make_contrib(superclass, func=None): - """ - Returns a suitable contribute_to_class() method for the Field subclass. - - If 'func' is passed in, it is the existing contribute_to_class() method on - the subclass and it is called before anything else. It is assumed in this - case that the existing contribute_to_class() calls all the necessary - superclass methods. - """ - def contribute_to_class(self, cls, name, **kwargs): - if func: - func(self, cls, name, **kwargs) - else: - super(superclass, self).contribute_to_class(cls, name, **kwargs) - setattr(cls, self.name, Creator(self)) - - return contribute_to_class diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index 721154668f..ab36031fc0 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -428,13 +428,6 @@ get out of the way. Converting values to Python objects ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionchanged:: 1.8 - - Historically, Django provided a metaclass called ``SubfieldBase`` which - always called :meth:`~Field.to_python` on assignment. This did not play - nicely with custom database transformations, aggregation, or values - queries, so it has been replaced with :meth:`~Field.from_db_value`. - If your custom :class:`~Field` class deals with data structures that are more complex than strings, dates, integers, or floats, then you may need to override :meth:`~Field.from_db_value` and :meth:`~Field.to_python`. diff --git a/tests/field_subclassing/fields.py b/tests/field_subclassing/fields.py index 84ecf8d506..c2e4b50c76 100644 --- a/tests/field_subclassing/fields.py +++ b/tests/field_subclassing/fields.py @@ -1,94 +1,6 @@ from __future__ import unicode_literals -import json -import warnings - from django.db import models -from django.utils import six -from django.utils.deconstruct import deconstructible -from django.utils.deprecation import RemovedInDjango110Warning -from django.utils.encoding import force_text, python_2_unicode_compatible - -# Catch warning about subfieldbase -- remove in Django 1.10 -warnings.filterwarnings( - 'ignore', - 'SubfieldBase has been deprecated. Use Field.from_db_value instead.', - RemovedInDjango110Warning -) - - -@deconstructible -@python_2_unicode_compatible -class Small(object): - """ - A simple class to show that non-trivial Python objects can be used as - attributes. - """ - def __init__(self, first, second): - self.first, self.second = first, second - - def __str__(self): - return '%s%s' % (force_text(self.first), force_text(self.second)) - - def __eq__(self, other): - if isinstance(other, self.__class__): - return self.first == other.first and self.second == other.second - return False - - -class SmallField(six.with_metaclass(models.SubfieldBase, models.Field)): - """ - Turns the "Small" class into a Django field. Because of the similarities - with normal character fields and the fact that Small.__unicode__ does - something sensible, we don't need to implement a lot here. - """ - - def __init__(self, *args, **kwargs): - kwargs['max_length'] = 2 - super(SmallField, self).__init__(*args, **kwargs) - - def get_internal_type(self): - return 'CharField' - - def to_python(self, value): - if isinstance(value, Small): - return value - return Small(value[0], value[1]) - - def get_db_prep_save(self, value, connection): - return six.text_type(value) - - def get_prep_lookup(self, lookup_type, value): - if lookup_type == 'exact': - return force_text(value) - if lookup_type == 'in': - return [force_text(v) for v in value] - if lookup_type == 'isnull': - return [] - raise TypeError('Invalid lookup type: %r' % lookup_type) - - -class SmallerField(SmallField): - pass - - -class JSONField(six.with_metaclass(models.SubfieldBase, models.TextField)): - - description = ("JSONField automatically serializes and deserializes values to " - "and from JSON.") - - def to_python(self, value): - if not value: - return None - - if isinstance(value, six.string_types): - value = json.loads(value) - return value - - def get_db_prep_save(self, value, connection): - if value is None: - return None - return json.dumps(value) class CustomTypedField(models.TextField): diff --git a/tests/field_subclassing/models.py b/tests/field_subclassing/models.py deleted file mode 100644 index 7ff429c125..0000000000 --- a/tests/field_subclassing/models.py +++ /dev/null @@ -1,34 +0,0 @@ -""" -Tests for field subclassing. -""" -from django.db import models -from django.utils.encoding import force_text, python_2_unicode_compatible - -from .fields import JSONField, Small, SmallerField, SmallField - - -@python_2_unicode_compatible -class MyModel(models.Model): - name = models.CharField(max_length=10) - data = SmallField('small field') - - def __str__(self): - return force_text(self.name) - - -class OtherModel(models.Model): - data = SmallerField() - - -class ChoicesModel(models.Model): - SMALL_AB = Small('a', 'b') - SMALL_CD = Small('c', 'd') - SMALL_CHOICES = ( - (SMALL_AB, str(SMALL_AB)), - (SMALL_CD, str(SMALL_CD)), - ) - data = SmallField('small field', choices=SMALL_CHOICES) - - -class DataModel(models.Model): - data = JSONField() diff --git a/tests/field_subclassing/tests.py b/tests/field_subclassing/tests.py index af56fb294f..d291276c1f 100644 --- a/tests/field_subclassing/tests.py +++ b/tests/field_subclassing/tests.py @@ -1,126 +1,9 @@ from __future__ import unicode_literals -import inspect - -from django.core import exceptions, serializers from django.db import connection -from django.test import SimpleTestCase, TestCase +from django.test import SimpleTestCase -from .fields import CustomTypedField, Small -from .models import ChoicesModel, DataModel, MyModel, OtherModel - - -class CustomField(TestCase): - def test_refresh(self): - d = DataModel.objects.create(data=[1, 2, 3]) - d.refresh_from_db(fields=['data']) - self.assertIsInstance(d.data, list) - self.assertEqual(d.data, [1, 2, 3]) - - def test_defer(self): - d = DataModel.objects.create(data=[1, 2, 3]) - - self.assertIsInstance(d.data, list) - - d = DataModel.objects.get(pk=d.pk) - self.assertIsInstance(d.data, list) - self.assertEqual(d.data, [1, 2, 3]) - - d = DataModel.objects.defer("data").get(pk=d.pk) - self.assertIsInstance(d.data, list) - self.assertEqual(d.data, [1, 2, 3]) - # Refetch for save - d = DataModel.objects.defer("data").get(pk=d.pk) - d.save() - - d = DataModel.objects.get(pk=d.pk) - self.assertIsInstance(d.data, list) - self.assertEqual(d.data, [1, 2, 3]) - - def test_custom_field(self): - # Creating a model with custom fields is done as per normal. - s = Small(1, 2) - self.assertEqual(str(s), "12") - - m = MyModel.objects.create(name="m", data=s) - # Custom fields still have normal field's attributes. - self.assertEqual(m._meta.get_field("data").verbose_name, "small field") - - # The m.data attribute has been initialized correctly. It's a Small - # object. - self.assertEqual((m.data.first, m.data.second), (1, 2)) - - # The data loads back from the database correctly and 'data' has the - # right type. - m1 = MyModel.objects.get(pk=m.pk) - self.assertIsInstance(m1.data, Small) - self.assertEqual(str(m1.data), "12") - - # We can do normal filtering on the custom field (and will get an error - # when we use a lookup type that does not make sense). - s1 = Small(1, 3) - s2 = Small("a", "b") - self.assertQuerysetEqual( - MyModel.objects.filter(data__in=[s, s1, s2]), [ - "m", - ], - lambda m: m.name, - ) - self.assertRaises(TypeError, lambda: MyModel.objects.filter(data__lt=s)) - - # Serialization works, too. - stream = serializers.serialize("json", MyModel.objects.all()) - self.assertJSONEqual(stream, [{ - "pk": m1.pk, - "model": "field_subclassing.mymodel", - "fields": {"data": "12", "name": "m"} - }]) - - obj = list(serializers.deserialize("json", stream))[0] - self.assertEqual(obj.object, m) - - # Test retrieving custom field data - m.delete() - - m1 = MyModel.objects.create(name="1", data=Small(1, 2)) - MyModel.objects.create(name="2", data=Small(2, 3)) - - self.assertQuerysetEqual( - MyModel.objects.all(), [ - "12", - "23", - ], - lambda m: str(m.data), - ordered=False - ) - - def test_field_subclassing(self): - o = OtherModel.objects.create(data=Small("a", "b")) - o = OtherModel.objects.get() - self.assertEqual(o.data.first, "a") - self.assertEqual(o.data.second, "b") - - def test_subfieldbase_plays_nice_with_module_inspect(self): - """ - Custom fields should play nice with python standard module inspect. - - http://users.rcn.com/python/download/Descriptor.htm#properties - """ - # Even when looking for totally different properties, SubfieldBase's - # non property like behavior made inspect crash. Refs #12568. - data = dict(inspect.getmembers(MyModel)) - self.assertIn('__module__', data) - self.assertEqual(data['__module__'], 'field_subclassing.models') - - def test_validation_of_choices_for_custom_field(self): - # a valid choice - o = ChoicesModel.objects.create(data=Small('a', 'b')) - o.full_clean() - - # an invalid choice - o = ChoicesModel.objects.create(data=Small('d', 'e')) - with self.assertRaises(exceptions.ValidationError): - o.full_clean() +from .fields import CustomTypedField class TestDbType(SimpleTestCase):