Loading django/db/migrations/writer.py +22 −6 Original line number Diff line number Diff line Loading @@ -12,7 +12,7 @@ import types from django.apps import apps from django.db import models 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.functional import Promise Loading Loading @@ -284,13 +284,29 @@ class MigrationWriter(object): klass = value.__self__ module = klass.__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") elif value.__module__ is None: if value.__module__ is None: raise ValueError("Cannot serialize function %r: No module" % value) else: module = value.__module__ return "%s.%s" % (module, value.__name__), set(["import %s" % module]) # Python 3 is a lot easier, and only uses this branch if it's not local. if getattr(value, "__qualname__", None) and getattr(value, "__module__", None): 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 elif isinstance(value, type): special_cases = [ Loading docs/topics/migrations.txt +19 −0 Original line number Diff line number Diff line Loading @@ -491,11 +491,30 @@ Django can serialize the following: - Any class reference - 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: - Arbitrary class instances (e.g. ``MyClass(4.3, 5.7)``) - 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: Adding a deconstruct() method Loading tests/migrations/test_writer.py +27 −0 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ from __future__ import unicode_literals import datetime import os import tokenize import unittest from django.core.validators import RegexValidator, EmailValidator from django.db import models, migrations Loading @@ -16,6 +17,12 @@ from django.utils.translation import ugettext_lazy as _ 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): """ Tests the migration writer (makes migration files from Migration instances) Loading Loading @@ -137,6 +144,26 @@ class WriterTests(TestCase): self.assertSerializedEqual(one_item_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): """ Tests serializing a simple migration. Loading Loading
django/db/migrations/writer.py +22 −6 Original line number Diff line number Diff line Loading @@ -12,7 +12,7 @@ import types from django.apps import apps from django.db import models 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.functional import Promise Loading Loading @@ -284,13 +284,29 @@ class MigrationWriter(object): klass = value.__self__ module = klass.__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") elif value.__module__ is None: if value.__module__ is None: raise ValueError("Cannot serialize function %r: No module" % value) else: module = value.__module__ return "%s.%s" % (module, value.__name__), set(["import %s" % module]) # Python 3 is a lot easier, and only uses this branch if it's not local. if getattr(value, "__qualname__", None) and getattr(value, "__module__", None): 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 elif isinstance(value, type): special_cases = [ Loading
docs/topics/migrations.txt +19 −0 Original line number Diff line number Diff line Loading @@ -491,11 +491,30 @@ Django can serialize the following: - Any class reference - 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: - Arbitrary class instances (e.g. ``MyClass(4.3, 5.7)``) - 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: Adding a deconstruct() method Loading
tests/migrations/test_writer.py +27 −0 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ from __future__ import unicode_literals import datetime import os import tokenize import unittest from django.core.validators import RegexValidator, EmailValidator from django.db import models, migrations Loading @@ -16,6 +17,12 @@ from django.utils.translation import ugettext_lazy as _ 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): """ Tests the migration writer (makes migration files from Migration instances) Loading Loading @@ -137,6 +144,26 @@ class WriterTests(TestCase): self.assertSerializedEqual(one_item_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): """ Tests serializing a simple migration. Loading