6

I was referring to this youtube video, to understand how to upload image using ImageField. He has explained how to use the instance.id while saving the image. I tried it, but instance.id is returning None. Whereas for him it worked perfectly. The following is the code:

#models.py
import os

def get_image_path(instance, filename):
    return os.path.join(str(instance.id), filename)

class AdProfile(models.Model):
    name = models.CharField(max_length=100)
    profile_image = models.ImageField(upload_to=get_image_path, blank=True, null=True)

Whenever the file is saved, its saving as None/filename.

Even this link informs the same. I am using Django 10.5 and MySQL database.

What might be the problem?

piet.t
  • 11,718
  • 21
  • 43
  • 52
Jeril
  • 7,858
  • 3
  • 52
  • 69

2 Answers2

14

Django admin somehow called the get_image_path function without saving the model to database so id is None. We can override django model using save method and make sure image is saved and get_image_path get the instance with id

class AdProfile(models.Model):
    name = models.CharField(max_length=100)
    profile_image = models.ImageField(upload_to=get_image_path, blank=True, null=True)

    # Model Save override 
    def save(self, *args, **kwargs):
        if self.id is None:
            saved_image = self.profile_image
            self.profile_image = None
            super(AdProfile, self).save(*args, **kwargs)
            self.profile_image = saved_image
            if 'force_insert' in kwargs:
                kwargs.pop('force_insert')

        super(AdProfile, self).save(*args, **kwargs)
RedPelle
  • 285
  • 4
  • 17
Raja Simon
  • 10,126
  • 5
  • 43
  • 74
  • You are awesome. It worked perfectly as expected. Thanks a lot. – Jeril Jun 01 '17 at 18:18
  • is there any way to do the same, while using `views.py`? – Jeril Jun 02 '17 at 04:58
  • I am getting the following error: `(1062, "Duplicate entry '8' for key 'PRIMARY'")`. I was using DRF to save the image. – Jeril Jun 02 '17 at 05:04
  • 4
    Perfect! I just had to check if 'force_insert' exists in kwargs: if 'force_insert' in kwargs: kwargs.pop('force_insert') to avoid an error, Django 2.1.2, Python 3.6 – RedPelle Oct 31 '18 at 05:35
  • @RedPelle Thanks for the suggestion. Can you edit the answer so that I can accept your edit. – Raja Simon Oct 31 '18 at 06:05
  • @RedPelle Thanks Adding below line to the code worked both for DRF and Django admin. ` if 'force_insert' in kwargs: kwargs.pop('force_insert') ` – Jatin Goyal Nov 14 '19 at 09:35
2

Using Raja Simon's answer, there is recipe to process all FileField in the model

class MyModel(models.Model):

    file_field = models.FileField(upload_to=upload_to, blank=True, null=True)

    def save(self, *args, **kwargs):
        if self.id is None:
            saved = []
            for f in self.__class__._meta.get_fields():
                if isinstance(f, models.FileField):
                    saved.append((f.name, getattr(self, f.name)))
                    setattr(self, f.name, None)

            super(self.__class__, self).save(*args, **kwargs)

            for name, val in saved:
                setattr(self, name, val)
        super(self.__class__, self).save(*args, **kwargs)

Moreover, we can make file location dynamic, i.e. based not only on self.id, but also on id of foreign key or whatever. Just iterate over fields and check if path changed.

def upload_to(o, fn):
    if o.parent and o.parent.id:
        return parent_upload_to(o.parent, fn)

    return "my_temp_dir/{}/{}".format(o.id, fn)


class MyModel(models.Model):

    parent = models.ForeignKey(Parent)

    def save(self, *args, **kwargs):

        # .... code from save() above here

        for f in [f for f in self.__class__._meta.get_fields() if isinstance(f, models.FileField)]:

            upload_to = f.upload_to

            f = getattr(self, f.name)  # f is FileField now

            if f and callable(upload_to):
                _, fn = os.path.split(f.name)
                old_name = os.path.normpath(f.name)
                new_name = os.path.normpath(upload_to(self, fn))

                if old_name != new_name:

                    old_path = os.path.join(settings.MEDIA_ROOT, old_name)
                    new_path = os.path.join(settings.MEDIA_ROOT, new_name)

                    new_dir, _ = os.path.split(new_path)
                    if not os.path.exists(new_dir):
                        print "Making  dir {}", new_dir
                        os.makedirs(new_dir)

                    print "Moving {} to {}".format(old_path, new_path)
                    try:
                        os.rename(old_path, new_path)
                        f.name = new_name

                    except WindowsError as e:
                        print "Can not move file, WindowsError: {}".format(e)

        super(self.__class__, self).save(*args, **kwargs)
john.don83
  • 103
  • 1
  • 10