mirror of https://github.com/django/django.git
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."),
|
||||
}
|
||||
class_lookups = default_lookups.copy()
|
||||
system_check_deprecated_details = None
|
||||
system_check_removed_details = None
|
||||
|
||||
# Generic field type description, usually overridden by subclasses
|
||||
def _description(self):
|
||||
|
@ -191,6 +193,7 @@ class Field(RegisterLookupMixin):
|
|||
errors.extend(self._check_db_index())
|
||||
errors.extend(self._check_null_allowed_for_primary_keys())
|
||||
errors.extend(self._check_backend_specific_checks(**kwargs))
|
||||
errors.extend(self._check_deprecation_details())
|
||||
return errors
|
||||
|
||||
def _check_field_name(self):
|
||||
|
@ -290,6 +293,34 @@ class Field(RegisterLookupMixin):
|
|||
def _check_backend_specific_checks(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):
|
||||
"""
|
||||
Returns enough information to recreate the field as a 4-tuple:
|
||||
|
@ -1832,6 +1863,14 @@ class BigIntegerField(IntegerField):
|
|||
class IPAddressField(Field):
|
||||
empty_strings_allowed = False
|
||||
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):
|
||||
kwargs['max_length'] = 15
|
||||
|
@ -1856,19 +1895,6 @@ class IPAddressField(Field):
|
|||
defaults.update(kwargs)
|
||||
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):
|
||||
empty_strings_allowed = True
|
||||
|
|
|
@ -438,6 +438,9 @@ Migrations
|
|||
* Migrations can now :ref:`serialize model managers
|
||||
<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
|
||||
^^^^^^
|
||||
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
|
|
@ -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