Search code examples
pythondjangodjango-migrations

Django - Cannot create migrations for ImageField with dynamic upload_to value


I just upgraded my app to 1.7 (actually still trying).

This is what i had in models.py:

def path_and_rename(path):
    def wrapper(instance, filename):
        ext = filename.split('.')[-1]
        # set filename as random string
        filename = '{}.{}'.format(uuid4().hex, ext)
        # return the whole path to the file
        return os.path.join(path, filename)
    return wrapper

class UserProfile(AbstractUser):
    #...
    avatar = models.ImageField(upload_to=path_and_rename("avatars/"),
                               null=True, blank=True,
                               default="avatars/none/default.png",
                               height_field="image_height",
                               width_field="image_width")

When i try to makemigrations, it throws:

ValueError: Could not find function wrapper in webapp.models.
Please note that due to Python 2 limitations, you cannot serialize unbound method functions (e.g. a method declared
and used in the same class body). Please move the function into the main module body to use migrations.

Solution

  • I am not sure if it is OK to answer my own question, but i just figured out (i think).

    According to this bug report, i edited my code:

    from django.utils.deconstruct import deconstructible
    
    @deconstructible
    class PathAndRename(object):
    
        def __init__(self, sub_path):
            self.path = sub_path
    
        def __call__(self, instance, filename):
            ext = filename.split('.')[-1]
            # set filename as random string
            filename = '{}.{}'.format(uuid4().hex, ext)
            # return the whole path to the file
            return os.path.join(self.path, filename)
    
    path_and_rename = PathAndRename("/avatars")
    

    And then, in field definition:

    avatar = models.ImageField(upload_to=path_and_rename,
                                   null=True, blank=True,
                                   default="avatars/none/default.png",
                                   height_field="image_height",
                                   width_field="image_width")
    

    This worked for me.