Django - 在上传图像时使用instance.id

6

我在参考这个youtube视频,了解如何使用ImageField上传图像。他讲解了如何在保存图像时使用instance.id。我尝试了一下,但是instance.id返回的是None。而对于他来说,它完美地工作了。以下是代码:

#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)

每次保存文件时,它都会保存为None/filename
即使这个链接也是这样说明的。我正在使用Django 10.5和MySQL数据库。
可能出了什么问题?

请说明您如何上传文件以及何时调用 save() 函数? - Raja Simon
我使用管理员面板上传了文件,它没有编写任何额外的 save() 方法。 - Jeril
是的,这就是问题所在,你需要告诉管理员save()使用get_image_path。我现在正在写答案... - Raja Simon
好的 @RajaSimon - Jeril
2个回答

14

Django管理界面在未将模型保存到数据库,即id为None的情况下调用了get_image_path函数。我们可以通过重写Django模型的save方法,并确保图像被保存,然后使用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)

你太棒了。它完美地按预期工作。非常感谢。 - Jeril
有没有办法在使用 views.py 的同时做到同样的事情? - Jeril
我遇到了以下错误:(1062,“主键为'PRIMARY'的重复条目'8'”)。我正在使用DRF保存图像。 - Jeril
4
完美! 我只需要检查kwargs中是否存在'force_insert': 如果 'force_insert' 存在于 kwargs 中,那么就需要删除它以避免出错。这适用于 Django 2.1.2 和 Python 3.6。 - RedPelle
@RedPelle 感谢您的建议。您能否编辑答案,以便我可以接受您的编辑。 - Raja Simon
@RedPelle 谢谢 将以下行添加到代码中,适用于DRF和Django管理界面。 if 'force_insert' in kwargs: kwargs.pop('force_insert') - Jatin Goyal

2

使用Raja Simon的答案,这里有一份处理模型中所有FileField的步骤:

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)

此外,我们可以使文件位置动态化,即不仅基于self.id,还基于外键或其他id。只需遍历字段并检查路径是否更改即可。
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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接