Search code examples
pythondjangodjango-modelsdjango-signals

Dynamic upload path - include originating field


I have a Django model with multiple ImageFields and use a callable to determine the upload path. I want to include the originating upload field's name in the upload path, in this case tiny, small, medium or press.

The only way I could think of was to create a pre_save receiver which replaces file.name with a uuid. Then the upload_to callable finds the match by comparing it to filename. Isn't there a less hacky way of doing this?

class SomeDjangoModel(models.Model):

    IMAGE_SIZES = ('tiny', 'small', 'medium', 'press')

    def image_path(self, filename):
        """ Example return: [some-django-model]/[medium]/[product1].[jpg] """
        size = None
        for field_name in self.IMAGE_SIZES:
            field_fn = getattr(getattr(self, field_name), 'name', '')
            if field_fn == filename.rpartition('/')[2]:
                size = field_name
                break

        return u'{}/{}/{}.{}'.format(
            slugify(self._meta.verbose_name),
            size or 'undetermined',
            self.slug,
            filename.rpartition('.')[2].lower(),
        )

    tiny = models.ImageField(upload_to=image_path, blank=True, null=True)
    small = models.ImageField(upload_to=image_path, blank=True, null=True)
    medium = models.ImageField(upload_to=image_path, blank=True, null=True)
    press = models.ImageField(upload_to=image_path, blank=True, null=True)

The pre_save receiver:

@receiver(pre_save, sender=SomeDjangoModel)
def set_unique_fn(sender, instance, **kwargs):
    """ Set a unique (but temporary) filename on all newly uploaded files. """

    for size in instance.IMAGE_SIZES:
        field = getattr(instance, '{}_img'.format(size), None)
        if not field:
            continue
        fieldfile = getattr(field, 'file', None)
        if isinstance(fieldfile, UploadedFile):
            fieldfile.name = u'{}.{}'.format(
                uuid.uuid4().hex,
                fieldfile.name.rpartition('.')[2],
            )

Solution

  • You can change image_path() so that it returns a callable which already knows the size:

    def image_path(size):
        def callback(self, filename)
            """ Example return: [some-django-model]/[medium]/[product1].[jpg] """
            return u'{}/{}/{}.{}'.format(
                slugify(self._meta.verbose_name),
                size,
                self.slug,
                filename.rpartition('.')[2].lower(),
            )
        return callback
    
    class SomeDjangoModel(models.Model):
        tiny = models.ImageField(upload_to=image_path('tiny'), blank=True, null=True)
        small = models.ImageField(upload_to=image_path('small'), blank=True, null=True)
        medium = models.ImageField(upload_to=image_path('medium'), blank=True, null=True)
        press = models.ImageField(upload_to=image_path('press'), blank=True, null=True)