首页 > 解决方案 > 延迟保存 Django ImageField 直到对象有 pk

问题描述

我有一个 Book 的 Django 模型,它有一个slug基于其pk. 它还有一个thumbnailwhich 被保存到包含 that 的路径中slug

在管理员中,如果我创建并保存没有缩略图的图书,然后添加缩略图并再次保存图书,则此方法有效:缩略图保存到/media/books/<slug>/foo.jpg.

但是,如果我使用缩略图创建图书并保存它,则缩略图slug会在生成之前保存,因此它会保存到/media/books/foo.jpg. 这是因为文件是在模型之前保存的。

我想始终包含slug在缩略图的路径中,但我不知道如何延迟保存缩略图直到slug生成之后。有任何想法吗?

from django.db import models
from hashes import Hashids


def upload_path(instance, filename):
    return "/".join([books, instance.slug, filename])


class Book(models.Model):
    title = models.CharField(null=False, blank=False, max_length=255)
    slug = models.SlugField(max_length=10, null=False, blank=True)

    thumbnail = models.ImageField(
        upload_to=upload_path, null=False, blank=True, default=""
    )

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

        if not self.slug:
            # Now we have a pk, generate a slug if it doesn't have one.
            hashids = Hashids(salt="my salt", min_length=5)
            self.slug = hashids.encode(self.pk)
            kwargs["force_insert"] = False
            self.save(*args, **kwargs)

(我知道 Hashids 不安全;有人可以pkslug.

标签: djangodjango-models

解决方案


我认为答案是在生成 pk 后将文件移动到正确的位置。该模型:

import os
from django.conf import settings
from django.db import models
from hashes import Hashids

def upload_path(instance, filename):
    return "/".join([books, instance.slug, filename])

class Book(models.Model):
    # field definitions here

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

        if not self.slug:
            # Now we have a pk, generate a slug if it doesn't have one.
            hashids = Hashids(salt="my salt", min_length=5)
            self.slug = hashids.encode(self.pk)

        if self.thumbnail and f"/{self.slug}/" not in self.thumbnail.path:
            # Move the thumbnail to correct location.
            initial_path = self.thumbnail.path
            filename = os.path.basename(initial_path)
            new_name = upload_path(self, filename)
            new_path = os.path.join(settings.MEDIA_ROOT, new_name)

            if not os.path.exists(os.path.dirname(new_path)):
                # Make the slug directory if it doesn't exist.
                os.makedirs(os.path.dirname(new_path))

            os.rename(initial_path, new_path)

            self.thumbnail.name = new_name
            kwargs["force_insert"] = False
            super().save(*args, **kwargs)

推荐阅读