Fixed #22436: More careful checking on method ref'ce serialization
This commit is contained in:
parent
250e2b422b
commit
6fd455adfc
|
@ -12,7 +12,7 @@ import types
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.migrations.loader import MigrationLoader
|
from django.db.migrations.loader import MigrationLoader
|
||||||
from django.utils import datetime_safe, six
|
from django.utils import datetime_safe, six, importlib
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.functional import Promise
|
from django.utils.functional import Promise
|
||||||
|
|
||||||
|
@ -284,13 +284,29 @@ class MigrationWriter(object):
|
||||||
klass = value.__self__
|
klass = value.__self__
|
||||||
module = klass.__module__
|
module = klass.__module__
|
||||||
return "%s.%s.%s" % (module, klass.__name__, value.__name__), set(["import %s" % module])
|
return "%s.%s.%s" % (module, klass.__name__, value.__name__), set(["import %s" % module])
|
||||||
elif value.__name__ == '<lambda>':
|
# Further error checking
|
||||||
|
if value.__name__ == '<lambda>':
|
||||||
raise ValueError("Cannot serialize function: lambda")
|
raise ValueError("Cannot serialize function: lambda")
|
||||||
elif value.__module__ is None:
|
if value.__module__ is None:
|
||||||
raise ValueError("Cannot serialize function %r: No module" % value)
|
raise ValueError("Cannot serialize function %r: No module" % value)
|
||||||
else:
|
# Python 3 is a lot easier, and only uses this branch if it's not local.
|
||||||
module = value.__module__
|
if getattr(value, "__qualname__", None) and getattr(value, "__module__", None):
|
||||||
return "%s.%s" % (module, value.__name__), set(["import %s" % module])
|
if "<" not in value.__qualname__: # Qualname can include <locals>
|
||||||
|
return "%s.%s" % (value.__module__, value.__qualname__), set(["import %s" % value.__module__])
|
||||||
|
# Python 2/fallback version
|
||||||
|
module_name = value.__module__
|
||||||
|
# Make sure it's actually there and not an unbound method
|
||||||
|
module = importlib.import_module(module_name)
|
||||||
|
if not hasattr(module, value.__name__):
|
||||||
|
raise ValueError(
|
||||||
|
"Could not find function %s in %s.\nPlease note that "
|
||||||
|
"due to Python 2 limitations, you cannot serialize "
|
||||||
|
"unbound method functions (e.g. a method declared\n"
|
||||||
|
"and used in the same class body). Please move the "
|
||||||
|
"function into the main module body to use migrations.\n"
|
||||||
|
"For more information, see https://docs.djangoproject.com/en/1.7/topics/migrations/#serializing-values"
|
||||||
|
)
|
||||||
|
return "%s.%s" % (module_name, value.__name__), set(["import %s" % module_name])
|
||||||
# Classes
|
# Classes
|
||||||
elif isinstance(value, type):
|
elif isinstance(value, type):
|
||||||
special_cases = [
|
special_cases = [
|
||||||
|
|
|
@ -491,11 +491,30 @@ Django can serialize the following:
|
||||||
- Any class reference
|
- Any class reference
|
||||||
- Anything with a custom ``deconstruct()`` method (:ref:`see below <custom-deconstruct-method>`)
|
- Anything with a custom ``deconstruct()`` method (:ref:`see below <custom-deconstruct-method>`)
|
||||||
|
|
||||||
|
Django can serialize the following on Python 3 only:
|
||||||
|
|
||||||
|
- Unbound methods used from within the class body (see below)
|
||||||
|
|
||||||
Django cannot serialize:
|
Django cannot serialize:
|
||||||
|
|
||||||
- Arbitrary class instances (e.g. ``MyClass(4.3, 5.7)``)
|
- Arbitrary class instances (e.g. ``MyClass(4.3, 5.7)``)
|
||||||
- Lambdas
|
- Lambdas
|
||||||
|
|
||||||
|
Due to the fact ``__qualname__`` was only introduced in Python 3, Django can only
|
||||||
|
serialize the following pattern (an unbound method used within the class body)
|
||||||
|
on Python 3, and will fail to serialize a reference to it on Python 2::
|
||||||
|
|
||||||
|
class MyModel(models.Model):
|
||||||
|
|
||||||
|
def upload_to(self):
|
||||||
|
return "something dynamic"
|
||||||
|
|
||||||
|
my_file = models.FileField(upload_to=upload_to)
|
||||||
|
|
||||||
|
If you are using Python 2, we recommend you move your methods for upload_to
|
||||||
|
and similar arguments that accept callables (e.g. ``default``) to live in
|
||||||
|
the main module body, rather than the class body.
|
||||||
|
|
||||||
.. _custom-deconstruct-method:
|
.. _custom-deconstruct-method:
|
||||||
|
|
||||||
Adding a deconstruct() method
|
Adding a deconstruct() method
|
||||||
|
|
|
@ -4,6 +4,7 @@ from __future__ import unicode_literals
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
import tokenize
|
import tokenize
|
||||||
|
import unittest
|
||||||
|
|
||||||
from django.core.validators import RegexValidator, EmailValidator
|
from django.core.validators import RegexValidator, EmailValidator
|
||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
|
@ -16,6 +17,12 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.timezone import get_default_timezone
|
from django.utils.timezone import get_default_timezone
|
||||||
|
|
||||||
|
|
||||||
|
class TestModel1(object):
|
||||||
|
def upload_to(self):
|
||||||
|
return "somewhere dynamic"
|
||||||
|
thing = models.FileField(upload_to=upload_to)
|
||||||
|
|
||||||
|
|
||||||
class WriterTests(TestCase):
|
class WriterTests(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests the migration writer (makes migration files from Migration instances)
|
Tests the migration writer (makes migration files from Migration instances)
|
||||||
|
@ -137,6 +144,26 @@ class WriterTests(TestCase):
|
||||||
self.assertSerializedEqual(one_item_tuple)
|
self.assertSerializedEqual(one_item_tuple)
|
||||||
self.assertSerializedEqual(many_items_tuple)
|
self.assertSerializedEqual(many_items_tuple)
|
||||||
|
|
||||||
|
@unittest.skipUnless(six.PY2, "Only applies on Python 2")
|
||||||
|
def test_serialize_direct_function_reference(self):
|
||||||
|
"""
|
||||||
|
Ticket #22436: You cannot use a function straight from its body
|
||||||
|
(e.g. define the method and use it in the same body)
|
||||||
|
"""
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.serialize_round_trip(TestModel1.thing)
|
||||||
|
|
||||||
|
def test_serialize_local_function_reference(self):
|
||||||
|
"""
|
||||||
|
Neither py2 or py3 can serialize a reference in a local scope.
|
||||||
|
"""
|
||||||
|
class TestModel2(object):
|
||||||
|
def upload_to(self):
|
||||||
|
return "somewhere dynamic"
|
||||||
|
thing = models.FileField(upload_to=upload_to)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.serialize_round_trip(TestModel2.thing)
|
||||||
|
|
||||||
def test_simple_migration(self):
|
def test_simple_migration(self):
|
||||||
"""
|
"""
|
||||||
Tests serializing a simple migration.
|
Tests serializing a simple migration.
|
||||||
|
|
Loading…
Reference in New Issue