Django – using instance.id while uploading image

Question:

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?

Asked By: Jeril

||

Answers:

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)
Answered By: Raja Simon

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)
Answered By: john.don83

you can create a model instance by passing cleaned data from your form as **kwargs to django model i did it that way & its much easier than anything else

iam naming the path according to owner field which refers to user model of django

in your views post method add this (this code is from my project not adapted to this question)

    pk = request.session['_auth_user_id']
    user_obj = User.objects.get(pk=pk)


    lab_form_instance = lab_form(request.POST,request.FILES)
    lab_form_instance.save(commit=False)
    # here you can put the form.is_valid() statement
    lab_form_instance.cleaned_data['owner'] =user_obj # here iam adding additional needed data for the model
    obj = lab(**lab_form_instance.cleaned_data)
    obj.save()

*django==4.1

Answered By: Peter Nasser Mosaad
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.