Fixed #23861 -- Added an API to deprecate model fields.
Thanks Markus Holterman and Berker Peksag for review.
This commit is contained in:
parent
6e1c9c6568
commit
c87ee41954
|
@ -113,6 +113,8 @@ class Field(RegisterLookupMixin):
|
||||||
"%(date_field_label)s %(lookup_type)s."),
|
"%(date_field_label)s %(lookup_type)s."),
|
||||||
}
|
}
|
||||||
class_lookups = default_lookups.copy()
|
class_lookups = default_lookups.copy()
|
||||||
|
system_check_deprecated_details = None
|
||||||
|
system_check_removed_details = None
|
||||||
|
|
||||||
# Generic field type description, usually overridden by subclasses
|
# Generic field type description, usually overridden by subclasses
|
||||||
def _description(self):
|
def _description(self):
|
||||||
|
@ -191,6 +193,7 @@ class Field(RegisterLookupMixin):
|
||||||
errors.extend(self._check_db_index())
|
errors.extend(self._check_db_index())
|
||||||
errors.extend(self._check_null_allowed_for_primary_keys())
|
errors.extend(self._check_null_allowed_for_primary_keys())
|
||||||
errors.extend(self._check_backend_specific_checks(**kwargs))
|
errors.extend(self._check_backend_specific_checks(**kwargs))
|
||||||
|
errors.extend(self._check_deprecation_details())
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
def _check_field_name(self):
|
def _check_field_name(self):
|
||||||
|
@ -290,6 +293,34 @@ class Field(RegisterLookupMixin):
|
||||||
def _check_backend_specific_checks(self, **kwargs):
|
def _check_backend_specific_checks(self, **kwargs):
|
||||||
return connection.validation.check_field(self, **kwargs)
|
return connection.validation.check_field(self, **kwargs)
|
||||||
|
|
||||||
|
def _check_deprecation_details(self):
|
||||||
|
if self.system_check_removed_details is not None:
|
||||||
|
return [
|
||||||
|
checks.Error(
|
||||||
|
self.system_check_removed_details.get(
|
||||||
|
'msg',
|
||||||
|
'%s has been removed except for support in historical '
|
||||||
|
'migrations.' % self.__class__.__name__
|
||||||
|
),
|
||||||
|
hint=self.system_check_removed_details.get('hint'),
|
||||||
|
obj=self,
|
||||||
|
id=self.system_check_removed_details.get('id', 'fields.EXXX'),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
elif self.system_check_deprecated_details is not None:
|
||||||
|
return [
|
||||||
|
checks.Warning(
|
||||||
|
self.system_check_deprecated_details.get(
|
||||||
|
'msg',
|
||||||
|
'%s has been deprecated.' % self.__class__.__name__
|
||||||
|
),
|
||||||
|
hint=self.system_check_deprecated_details.get('hint'),
|
||||||
|
obj=self,
|
||||||
|
id=self.system_check_deprecated_details.get('id', 'fields.WXXX'),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
return []
|
||||||
|
|
||||||
def deconstruct(self):
|
def deconstruct(self):
|
||||||
"""
|
"""
|
||||||
Returns enough information to recreate the field as a 4-tuple:
|
Returns enough information to recreate the field as a 4-tuple:
|
||||||
|
@ -1832,6 +1863,14 @@ class BigIntegerField(IntegerField):
|
||||||
class IPAddressField(Field):
|
class IPAddressField(Field):
|
||||||
empty_strings_allowed = False
|
empty_strings_allowed = False
|
||||||
description = _("IPv4 address")
|
description = _("IPv4 address")
|
||||||
|
system_check_deprecated_details = {
|
||||||
|
'msg': (
|
||||||
|
'IPAddressField has been deprecated. Support for it (except in '
|
||||||
|
'historical migrations) will be removed in Django 1.9.'
|
||||||
|
),
|
||||||
|
'hint': 'Use GenericIPAddressField instead.',
|
||||||
|
'id': 'fields.W900',
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
kwargs['max_length'] = 15
|
kwargs['max_length'] = 15
|
||||||
|
@ -1856,19 +1895,6 @@ class IPAddressField(Field):
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
return super(IPAddressField, self).formfield(**defaults)
|
return super(IPAddressField, self).formfield(**defaults)
|
||||||
|
|
||||||
def check(self, **kwargs):
|
|
||||||
errors = super(IPAddressField, self).check(**kwargs)
|
|
||||||
errors.append(
|
|
||||||
checks.Warning(
|
|
||||||
'IPAddressField has been deprecated. Support for it '
|
|
||||||
'(except in historical migrations) will be removed in Django 1.9.',
|
|
||||||
hint='Use GenericIPAddressField instead.',
|
|
||||||
obj=self,
|
|
||||||
id='fields.W900',
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return errors
|
|
||||||
|
|
||||||
|
|
||||||
class GenericIPAddressField(Field):
|
class GenericIPAddressField(Field):
|
||||||
empty_strings_allowed = True
|
empty_strings_allowed = True
|
||||||
|
|
|
@ -438,6 +438,9 @@ Migrations
|
||||||
* Migrations can now :ref:`serialize model managers
|
* Migrations can now :ref:`serialize model managers
|
||||||
<using-managers-in-migrations>` as part of the model state.
|
<using-managers-in-migrations>` as part of the model state.
|
||||||
|
|
||||||
|
* A :ref:`generic mechanism to handle the deprecation of model fields
|
||||||
|
<migrations-removing-model-fields>` was added.
|
||||||
|
|
||||||
Models
|
Models
|
||||||
^^^^^^
|
^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -387,6 +387,54 @@ contains a reference to them. On the plus side, methods and managers from these
|
||||||
base classes inherit normally, so if you absolutely need access to these you
|
base classes inherit normally, so if you absolutely need access to these you
|
||||||
can opt to move them into a superclass.
|
can opt to move them into a superclass.
|
||||||
|
|
||||||
|
.. _migrations-removing-model-fields:
|
||||||
|
|
||||||
|
Considerations when removing model fields
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
.. versionadded:: 1.8
|
||||||
|
|
||||||
|
Similar to the "references to historical functions" considerations described in
|
||||||
|
the previous section, removing custom model fields from your project or
|
||||||
|
third-party app will cause a problem if they are referenced in old migrations.
|
||||||
|
|
||||||
|
To help with this situation, Django provides some model field attributes to
|
||||||
|
assist with model field deprecation using the :doc:`system checks framework
|
||||||
|
</topics/checks>`.
|
||||||
|
|
||||||
|
Add the ``system_check_deprecated_details`` attribute to your model field
|
||||||
|
similar to the following::
|
||||||
|
|
||||||
|
class IPAddressField(Field):
|
||||||
|
system_check_deprecated_details = {
|
||||||
|
'msg': (
|
||||||
|
'IPAddressField has been deprecated. Support for it (except '
|
||||||
|
'in historical migrations) will be removed in Django 1.9.'
|
||||||
|
),
|
||||||
|
'hint': 'Use GenericIPAddressField instead.', # optional
|
||||||
|
'id': 'fields.W900', # pick a unique ID for your field.
|
||||||
|
}
|
||||||
|
|
||||||
|
After a deprecation period of your choosing (two major releases for fields in
|
||||||
|
Django itself), change the ``system_check_deprecated_details`` attribute to
|
||||||
|
``system_check_removed_details`` and update the dictionary similar to::
|
||||||
|
|
||||||
|
class IPAddressField(Field):
|
||||||
|
system_check_removed_details = {
|
||||||
|
'msg': (
|
||||||
|
'IPAddressField has been removed except for support in '
|
||||||
|
'historical migrations.'
|
||||||
|
),
|
||||||
|
'hint': 'Use GenericIPAddressField instead.',
|
||||||
|
'id': 'fields.E900', # pick a unique ID for your field.
|
||||||
|
}
|
||||||
|
|
||||||
|
You should keep the field's methods that are required for it to operate in
|
||||||
|
database migrations such as ``__init__()``, ``deconstruct()``, and
|
||||||
|
``get_internal_type()``. Keep this stub field for as long as any migrations
|
||||||
|
which reference the field exist. For example, after squashing migrations and
|
||||||
|
removing the old ones, you should be able to remove the field completely.
|
||||||
|
|
||||||
.. _data-migrations:
|
.. _data-migrations:
|
||||||
|
|
||||||
Data Migrations
|
Data Migrations
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.core import checks
|
||||||
|
from django.test import SimpleTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestDeprecatedField(SimpleTestCase):
|
||||||
|
def test_default_details(self):
|
||||||
|
class MyField(models.Field):
|
||||||
|
system_check_deprecated_details = {}
|
||||||
|
|
||||||
|
class Model(models.Model):
|
||||||
|
name = MyField()
|
||||||
|
|
||||||
|
model = Model()
|
||||||
|
self.assertEqual(model.check(), [
|
||||||
|
checks.Warning(
|
||||||
|
msg='MyField has been deprecated.',
|
||||||
|
hint=None,
|
||||||
|
obj=Model._meta.get_field('name'),
|
||||||
|
id='fields.WXXX',
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_user_specified_details(self):
|
||||||
|
class MyField(models.Field):
|
||||||
|
system_check_deprecated_details = {
|
||||||
|
'msg': 'This field is deprecated and will be removed soon.',
|
||||||
|
'hint': 'Use something else.',
|
||||||
|
'id': 'fields.W999',
|
||||||
|
}
|
||||||
|
|
||||||
|
class Model(models.Model):
|
||||||
|
name = MyField()
|
||||||
|
|
||||||
|
model = Model()
|
||||||
|
self.assertEqual(model.check(), [
|
||||||
|
checks.Warning(
|
||||||
|
msg='This field is deprecated and will be removed soon.',
|
||||||
|
hint='Use something else.',
|
||||||
|
obj=Model._meta.get_field('name'),
|
||||||
|
id='fields.W999',
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class TestRemovedField(SimpleTestCase):
|
||||||
|
def test_default_details(self):
|
||||||
|
class MyField(models.Field):
|
||||||
|
system_check_removed_details = {}
|
||||||
|
|
||||||
|
class Model(models.Model):
|
||||||
|
name = MyField()
|
||||||
|
|
||||||
|
model = Model()
|
||||||
|
self.assertEqual(model.check(), [
|
||||||
|
checks.Error(
|
||||||
|
msg='MyField has been removed except for support in historical migrations.',
|
||||||
|
hint=None,
|
||||||
|
obj=Model._meta.get_field('name'),
|
||||||
|
id='fields.EXXX',
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_user_specified_details(self):
|
||||||
|
class MyField(models.Field):
|
||||||
|
system_check_removed_details = {
|
||||||
|
'msg': 'Support for this field is gone.',
|
||||||
|
'hint': 'Use something else.',
|
||||||
|
'id': 'fields.E999',
|
||||||
|
}
|
||||||
|
|
||||||
|
class Model(models.Model):
|
||||||
|
name = MyField()
|
||||||
|
|
||||||
|
model = Model()
|
||||||
|
self.assertEqual(model.check(), [
|
||||||
|
checks.Error(
|
||||||
|
msg='Support for this field is gone.',
|
||||||
|
hint='Use something else.',
|
||||||
|
obj=Model._meta.get_field('name'),
|
||||||
|
id='fields.E999',
|
||||||
|
)
|
||||||
|
])
|
Loading…
Reference in New Issue