mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	[1.7.x] Fixed #22436: More careful checking on method ref'ce serialization
This commit is contained in:
		| @@ -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. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user