Skip to content

Commit

Permalink
Ported over Field.deconstruct() from my schema alteration branch.
Browse files Browse the repository at this point in the history
This is to help other ongoing branches which would benefit from
this functionality.
  • Loading branch information
andrewgodwin committed Jun 28, 2013
1 parent 8809da6 commit 48dd1e6
Show file tree
Hide file tree
Showing 6 changed files with 501 additions and 1 deletion.
188 changes: 187 additions & 1 deletion django/db/models/fields/__init__.py
Expand Up @@ -99,7 +99,8 @@ def __init__(self, verbose_name=None, name=None, primary_key=False,
db_tablespace=None, auto_created=False, validators=[], db_tablespace=None, auto_created=False, validators=[],
error_messages=None): error_messages=None):
self.name = name self.name = name
self.verbose_name = verbose_name self.verbose_name = verbose_name # May be set by set_attributes_from_name
self._verbose_name = verbose_name # Store original for deconstruction
self.primary_key = primary_key self.primary_key = primary_key
self.max_length, self._unique = max_length, unique self.max_length, self._unique = max_length, unique
self.blank, self.null = blank, null self.blank, self.null = blank, null
Expand Down Expand Up @@ -128,14 +129,99 @@ def __init__(self, verbose_name=None, name=None, primary_key=False,
self.creation_counter = Field.creation_counter self.creation_counter = Field.creation_counter
Field.creation_counter += 1 Field.creation_counter += 1


self._validators = validators # Store for deconstruction later
self.validators = self.default_validators + validators self.validators = self.default_validators + validators


messages = {} messages = {}
for c in reversed(self.__class__.__mro__): for c in reversed(self.__class__.__mro__):
messages.update(getattr(c, 'default_error_messages', {})) messages.update(getattr(c, 'default_error_messages', {}))
messages.update(error_messages or {}) messages.update(error_messages or {})
self._error_messages = error_messages # Store for deconstruction later
self.error_messages = messages self.error_messages = messages


def deconstruct(self):
"""
Returns enough information to recreate the field as a 4-tuple:
* The name of the field on the model, if contribute_to_class has been run
* The import path of the field, including the class: django.db.models.IntegerField
This should be the most portable version, so less specific may be better.
* A list of positional arguments
* A dict of keyword arguments
Note that the positional or keyword arguments must contain values of the
following types (including inner values of collection types):
* None, bool, str, unicode, int, long, float, complex, set, frozenset, list, tuple, dict
* UUID
* datetime.datetime (naive), datetime.date
* top-level classes, top-level functions - will be referenced by their full import path
* Storage instances - these have their own deconstruct() method
This is because the values here must be serialised into a text format
(possibly new Python code, possibly JSON) and these are the only types
with encoding handlers defined.
There's no need to return the exact way the field was instantiated this time,
just ensure that the resulting field is the same - prefer keyword arguments
over positional ones, and omit parameters with their default values.
"""
# Short-form way of fetching all the default parameters
keywords = {}
possibles = {
"verbose_name": None,
"primary_key": False,
"max_length": None,
"unique": False,
"blank": False,
"null": False,
"db_index": False,
"default": NOT_PROVIDED,
"editable": True,
"serialize": True,
"unique_for_date": None,
"unique_for_month": None,
"unique_for_year": None,
"choices": [],
"help_text": '',
"db_column": None,
"db_tablespace": settings.DEFAULT_INDEX_TABLESPACE,
"auto_created": False,
"validators": [],
"error_messages": None,
}
attr_overrides = {
"unique": "_unique",
"choices": "_choices",
"error_messages": "_error_messages",
"validators": "_validators",
"verbose_name": "_verbose_name",
}
equals_comparison = set(["choices", "validators", "db_tablespace"])
for name, default in possibles.items():
value = getattr(self, attr_overrides.get(name, name))
if name in equals_comparison:
if value != default:
keywords[name] = value
else:
if value is not default:
keywords[name] = value
# Work out path - we shorten it for known Django core fields
path = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
if path.startswith("django.db.models.fields.related"):
path = path.replace("django.db.models.fields.related", "django.db.models")
if path.startswith("django.db.models.fields.files"):
path = path.replace("django.db.models.fields.files", "django.db.models")
if path.startswith("django.db.models.fields"):
path = path.replace("django.db.models.fields", "django.db.models")
# Return basic info - other fields should override this.
return (
self.name,
path,
[],
keywords,
)

def __eq__(self, other): def __eq__(self, other):
# Needed for @total_ordering # Needed for @total_ordering
if isinstance(other, Field): if isinstance(other, Field):
Expand Down Expand Up @@ -566,6 +652,7 @@ def __repr__(self):
return '<%s: %s>' % (path, name) return '<%s: %s>' % (path, name)
return '<%s>' % path return '<%s>' % path



class AutoField(Field): class AutoField(Field):
description = _("Integer") description = _("Integer")


Expand All @@ -580,6 +667,12 @@ def __init__(self, *args, **kwargs):
kwargs['blank'] = True kwargs['blank'] = True
Field.__init__(self, *args, **kwargs) Field.__init__(self, *args, **kwargs)


def deconstruct(self):
name, path, args, kwargs = super(AutoField, self).deconstruct()
del kwargs['blank']
kwargs['primary_key'] = True
return name, path, args, kwargs

def get_internal_type(self): def get_internal_type(self):
return "AutoField" return "AutoField"


Expand Down Expand Up @@ -630,6 +723,11 @@ def __init__(self, *args, **kwargs):
kwargs['blank'] = True kwargs['blank'] = True
Field.__init__(self, *args, **kwargs) Field.__init__(self, *args, **kwargs)


def deconstruct(self):
name, path, args, kwargs = super(BooleanField, self).deconstruct()
del kwargs['blank']
return name, path, args, kwargs

def get_internal_type(self): def get_internal_type(self):
return "BooleanField" return "BooleanField"


Expand Down Expand Up @@ -733,6 +831,18 @@ def __init__(self, verbose_name=None, name=None, auto_now=False,
kwargs['blank'] = True kwargs['blank'] = True
Field.__init__(self, verbose_name, name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)


def deconstruct(self):
name, path, args, kwargs = super(DateField, self).deconstruct()
if self.auto_now:
kwargs['auto_now'] = True
del kwargs['editable']
del kwargs['blank']
if self.auto_now_add:
kwargs['auto_now_add'] = True
del kwargs['editable']
del kwargs['blank']
return name, path, args, kwargs

def get_internal_type(self): def get_internal_type(self):
return "DateField" return "DateField"


Expand Down Expand Up @@ -927,6 +1037,14 @@ def __init__(self, verbose_name=None, name=None, max_digits=None,
self.max_digits, self.decimal_places = max_digits, decimal_places self.max_digits, self.decimal_places = max_digits, decimal_places
Field.__init__(self, verbose_name, name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)


def deconstruct(self):
name, path, args, kwargs = super(DecimalField, self).deconstruct()
if self.max_digits:
kwargs['max_digits'] = self.max_digits
if self.decimal_places:
kwargs['decimal_places'] = self.decimal_places
return name, path, args, kwargs

def get_internal_type(self): def get_internal_type(self):
return "DecimalField" return "DecimalField"


Expand Down Expand Up @@ -989,6 +1107,12 @@ def __init__(self, *args, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 75) kwargs['max_length'] = kwargs.get('max_length', 75)
CharField.__init__(self, *args, **kwargs) CharField.__init__(self, *args, **kwargs)


def deconstruct(self):
name, path, args, kwargs = super(EmailField, self).deconstruct()
# We do not exclude max_length if it matches default as we want to change
# the default in future.
return name, path, args, kwargs

def formfield(self, **kwargs): def formfield(self, **kwargs):
# As with CharField, this will cause email validation to be performed # As with CharField, this will cause email validation to be performed
# twice. # twice.
Expand All @@ -1008,6 +1132,22 @@ def __init__(self, verbose_name=None, name=None, path='', match=None,
kwargs['max_length'] = kwargs.get('max_length', 100) kwargs['max_length'] = kwargs.get('max_length', 100)
Field.__init__(self, verbose_name, name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)


def deconstruct(self):
name, path, args, kwargs = super(FilePathField, self).deconstruct()
if self.path != '':
kwargs['path'] = self.path
if self.match is not None:
kwargs['match'] = self.match
if self.recursive is not False:
kwargs['recursive'] = self.recursive
if self.allow_files is not True:
kwargs['allow_files'] = self.allow_files
if self.allow_folders is not False:
kwargs['allow_folders'] = self.allow_folders
if kwargs.get("max_length", None) == 100:
del kwargs["max_length"]
return name, path, args, kwargs

def formfield(self, **kwargs): def formfield(self, **kwargs):
defaults = { defaults = {
'path': self.path, 'path': self.path,
Expand Down Expand Up @@ -1115,6 +1255,11 @@ def __init__(self, *args, **kwargs):
kwargs['max_length'] = 15 kwargs['max_length'] = 15
Field.__init__(self, *args, **kwargs) Field.__init__(self, *args, **kwargs)


def deconstruct(self):
name, path, args, kwargs = super(IPAddressField, self).deconstruct()
del kwargs['max_length']
return name, path, args, kwargs

def get_internal_type(self): def get_internal_type(self):
return "IPAddressField" return "IPAddressField"


Expand All @@ -1131,12 +1276,23 @@ class GenericIPAddressField(Field):
def __init__(self, verbose_name=None, name=None, protocol='both', def __init__(self, verbose_name=None, name=None, protocol='both',
unpack_ipv4=False, *args, **kwargs): unpack_ipv4=False, *args, **kwargs):
self.unpack_ipv4 = unpack_ipv4 self.unpack_ipv4 = unpack_ipv4
self.protocol = protocol
self.default_validators, invalid_error_message = \ self.default_validators, invalid_error_message = \
validators.ip_address_validators(protocol, unpack_ipv4) validators.ip_address_validators(protocol, unpack_ipv4)
self.default_error_messages['invalid'] = invalid_error_message self.default_error_messages['invalid'] = invalid_error_message
kwargs['max_length'] = 39 kwargs['max_length'] = 39
Field.__init__(self, verbose_name, name, *args, **kwargs) Field.__init__(self, verbose_name, name, *args, **kwargs)


def deconstruct(self):
name, path, args, kwargs = super(GenericIPAddressField, self).deconstruct()
if self.unpack_ipv4 is not False:
kwargs['unpack_ipv4'] = self.unpack_ipv4
if self.protocol != "both":
kwargs['protocol'] = self.protocol
if kwargs.get("max_length", None) == 39:
del kwargs['max_length']
return name, path, args, kwargs

def get_internal_type(self): def get_internal_type(self):
return "GenericIPAddressField" return "GenericIPAddressField"


Expand Down Expand Up @@ -1177,6 +1333,12 @@ def __init__(self, *args, **kwargs):
kwargs['blank'] = True kwargs['blank'] = True
Field.__init__(self, *args, **kwargs) Field.__init__(self, *args, **kwargs)


def deconstruct(self):
name, path, args, kwargs = super(NullBooleanField, self).deconstruct()
del kwargs['null']
del kwargs['blank']
return name, path, args, kwargs

def get_internal_type(self): def get_internal_type(self):
return "NullBooleanField" return "NullBooleanField"


Expand Down Expand Up @@ -1254,6 +1416,16 @@ def __init__(self, *args, **kwargs):
kwargs['db_index'] = True kwargs['db_index'] = True
super(SlugField, self).__init__(*args, **kwargs) super(SlugField, self).__init__(*args, **kwargs)


def deconstruct(self):
name, path, args, kwargs = super(SlugField, self).deconstruct()
if kwargs.get("max_length", None) == 50:
del kwargs['max_length']
if self.db_index is False:
kwargs['db_index'] = False
else:
del kwargs['db_index']
return name, path, args, kwargs

def get_internal_type(self): def get_internal_type(self):
return "SlugField" return "SlugField"


Expand Down Expand Up @@ -1302,6 +1474,14 @@ def __init__(self, verbose_name=None, name=None, auto_now=False,
kwargs['blank'] = True kwargs['blank'] = True
Field.__init__(self, verbose_name, name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)


def deconstruct(self):
name, path, args, kwargs = super(TimeField, self).deconstruct()
if self.auto_now is not False:
kwargs["auto_now"] = self.auto_now
if self.auto_now_add is not False:
kwargs["auto_now_add"] = self.auto_now_add
return name, path, args, kwargs

def get_internal_type(self): def get_internal_type(self):
return "TimeField" return "TimeField"


Expand Down Expand Up @@ -1367,6 +1547,12 @@ def __init__(self, verbose_name=None, name=None, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 200) kwargs['max_length'] = kwargs.get('max_length', 200)
CharField.__init__(self, verbose_name, name, **kwargs) CharField.__init__(self, verbose_name, name, **kwargs)


def deconstruct(self):
name, path, args, kwargs = super(URLField, self).deconstruct()
if kwargs.get("max_length", None) == 200:
del kwargs['max_length']
return name, path, args, kwargs

def formfield(self, **kwargs): def formfield(self, **kwargs):
# As with CharField, this will cause URL validation to be performed # As with CharField, this will cause URL validation to be performed
# twice. # twice.
Expand Down
19 changes: 19 additions & 0 deletions django/db/models/fields/files.py
Expand Up @@ -227,6 +227,17 @@ def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, **k
kwargs['max_length'] = kwargs.get('max_length', 100) kwargs['max_length'] = kwargs.get('max_length', 100)
super(FileField, self).__init__(verbose_name, name, **kwargs) super(FileField, self).__init__(verbose_name, name, **kwargs)


def deconstruct(self):
name, path, args, kwargs = super(FileField, self).deconstruct()
if kwargs.get("max_length", None) != 100:
kwargs["max_length"] = 100
else:
del kwargs["max_length"]
kwargs['upload_to'] = self.upload_to
if self.storage is not default_storage:
kwargs['storage'] = self.storage
return name, path, args, kwargs

def get_internal_type(self): def get_internal_type(self):
return "FileField" return "FileField"


Expand Down Expand Up @@ -326,6 +337,14 @@ def __init__(self, verbose_name=None, name=None, width_field=None,
self.width_field, self.height_field = width_field, height_field self.width_field, self.height_field = width_field, height_field
super(ImageField, self).__init__(verbose_name, name, **kwargs) super(ImageField, self).__init__(verbose_name, name, **kwargs)


def deconstruct(self):
name, path, args, kwargs = super(ImageField, self).deconstruct()
if self.width_field:
kwargs['width_field'] = self.width_field
if self.height_field:
kwargs['height_field'] = self.height_field
return name, path, args, kwargs

def contribute_to_class(self, cls, name): def contribute_to_class(self, cls, name):
super(ImageField, self).contribute_to_class(cls, name) super(ImageField, self).contribute_to_class(cls, name)
# Attach update_dimension_fields so that dimension fields declared # Attach update_dimension_fields so that dimension fields declared
Expand Down

0 comments on commit 48dd1e6

Please sign in to comment.