2012-04-01 4 views
13

デフォルトのDjango管理者で簡単なフォトギャラリーを作成しようとしています。ギャラリーごとにサンプル写真を保存したいのですが、ファイル名を保存したくありません。ファイル名の代わりに、モデルのID(N.jpg)を保存したいと思います。しかし、IDを存在させないオブジェクトを保存したいときは、初めてです。どのように私はモデルの次の自動インクリメントを知ることができますか何かsuper.saveとのアップロードの前にモデルデータを保存し、self.idが存在するときにファイルをアップロードした後に?クールな解決策はありますか?このようなDjangoのadminファイルが現在のモデルIDでアップロードされています

何か:

def upload_path_handler(instance, filename): 
    ext = filename extension 
    return "site_media/images/gallery/{id}.{ext}".format(id=instance.nextincrement, ext=ext) 

class Gallery(models.Model): 
    name = models.TextField() 
    image = models.FileField(upload_to=upload_path_handler) 

そしておそらく別のフィールドにファイル名を格納します。

+4

これはなぜdownvoteに値するでしょうか?それは確かにいくつかよりも質の高い問題です。 – hop

+0

前もって次のレコードのIDを知る信頼できる方法はありません。レコードが最初に作成された後にIDを取得することもできますが、これも競合条件の影響を受けます。私のアドバイス - あなたのファイルに名前を付けるために、ID以外の何かを選んでください。 – Brandon

+0

たとえば、現在のタイムスタンプ+マイクロ秒 – ilvar

答えて

9

ギャラリーインスタンスの前にイメージファイルが保存されます。だから、状態を運ぶギャラリーインスタンス自体/ W信号を使用して、2つのフェーズへの保存を分割する必要があります。

from django.db.models.signals import post_save, pre_save 
from django.dispatch import receiver 

_UNSAVED_FILEFIELD = 'unsaved_filefield' 

@receiver(pre_save, sender=Image) 
def skip_saving_file(sender, instance, **kwargs): 
    if not instance.pk and not hasattr(instance, _UNSAVED_FILEFIELD): 
     setattr(instance, _UNSAVED_FILEFIELD, instance.image) 
     instance.image = None 

@receiver(post_save, sender=Image) 
def save_file(sender, instance, created, **kwargs): 
    if created and hasattr(instance, _UNSAVED_FILEFIELD): 
     instance.image = getattr(instance, _UNSAVED_FILEFIELD) 
     instance.save()   
     # delete it if you feel uncomfortable... 
     # instance.__dict__.pop(_UNSAVED_FILEFIELD) 

upload_path_handler、私はタイプ - のImageField代わりにFileFieldにを使用することをお勧め

def upload_path_handler(instance, filename): 
    import os.path 
    fn, ext = os.path.splitext(filename) 
    return "site_media/images/gallery/{id}{ext}".format(id=instance.pk, ext=ext) 

のように見えますフィールドが画像のアップロード専用であるかどうかを確認する。また、私は同じ問題に遭遇した

def normalize_ext(image_field): 
    try: 
     from PIL import Image 
    except ImportError: 
     import Image 
    ext = Image.open(image_field).format 
    if hasattr(image_field, 'seek') and callable(image_field.seek): 
     image_field.seek(0) 
    ext = ext.lower() 
    if ext == 'jpeg': 
     ext = 'jpg' 
    return '.' + ext 
+0

ありがとうございます! :)私の唯一のコメントは:誰かがこのソリューションを使用しようとすると、sender = Imageはモデルオブジェクトのオブジェクトです。 –

+0

@KBalazs助けてくれるとうれしいです。コードを修正して、編集を確認してください。 – okm

31

のように(理由MIMEタイプの不要です)、ファイル名の拡張子を正常化することをお勧めします。 Okmの答えは私に正しい経路で送られましたが、私にはsave()メソッドをオーバーライドするだけで同じ機能を得ることが可能です。

def save(self, *args, **kwargs): 
    if self.pk is None: 
     saved_image = self.image 
     self.image = None 
     super(Material, self).save(*args, **kwargs) 
     self.image = saved_image 

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

この情報は間違いなく正しく保存されます。

+0

これはdjango 1.7で壊れていますか? – Kukosk

+1

@KukoskこれはDjango 1.7で動作します! – Ajoy

+3

良い解決策!しばらくして、kwargsに 'force_insert = True'が含まれ、2番目のセーブ結果がIntegrityError:(1062、 "Duplicate entry")という単位テストで破損していることがわかりました。 ifブロックの最後にkwargs.pop( 'force_insert')を追加すると、問題が解決されます。 – jurer

0

django 1.7では、提案された解決策は私にとってはうまくいかなかったので、古いファイルを削除するストレージサブクラスとともにFileFieldサブクラスを書きました。

ストレージ:

class OverwriteFileSystemStorage(FileSystemStorage): 
    def _save(self, name, content): 
     self.delete(name) 
     return super()._save(name, content) 

    def get_available_name(self, name): 
     return name 

    def delete(self, name): 
     super().delete(name) 

     last_dir = os.path.dirname(self.path(name)) 

     while True: 
      try: 
       os.rmdir(last_dir) 
      except OSError as e: 
       if e.errno in {errno.ENOTEMPTY, errno.ENOENT}: 
        break 

       raise e 

      last_dir = os.path.dirname(last_dir) 

FileFieldに:

def tweak_field_save(cls, field): 
    field_defined_in_this_class = field.name in cls.__dict__ and field.name not in cls.__bases__[0].__dict__ 

    if field_defined_in_this_class: 
     orig_save = cls.save 

     if orig_save and callable(orig_save): 
      assert isinstance(field.storage, OverwriteFileSystemStorage), "Using other storage than '{0}' may cause unexpected behavior.".format(OverwriteFileSystemStorage.__name__) 

      def save(self, *args, **kwargs): 
       if self.pk is None: 
        orig_save(self, *args, **kwargs) 

        field_file = getattr(self, field.name) 

        if field_file: 
         old_path = field_file.path 
         new_filename = field.generate_filename(self, os.path.basename(old_path)) 
         new_path = field.storage.path(new_filename) 
         os.makedirs(os.path.dirname(new_path), exist_ok=True) 
         os.rename(old_path, new_path) 
         setattr(self, field.name, new_filename) 

        # for next save 
        if len(args) > 0: 
         args = tuple(v if k >= 2 else False for k, v in enumerate(args)) 

        kwargs['force_insert'] = False 
        kwargs['force_update'] = False 

       orig_save(self, *args, **kwargs) 

      cls.save = save 


def tweak_field_class(orig_cls): 
    orig_init = orig_cls.__init__ 

    def __init__(self, *args, **kwargs): 
     if 'storage' not in kwargs: 
      kwargs['storage'] = OverwriteFileSystemStorage() 

     if orig_init and callable(orig_init): 
      orig_init(self, *args, **kwargs) 

    orig_cls.__init__ = __init__ 

    orig_contribute_to_class = orig_cls.contribute_to_class 

    def contribute_to_class(self, cls, name): 
     if orig_contribute_to_class and callable(orig_contribute_to_class): 
      orig_contribute_to_class(self, cls, name) 

     tweak_field_save(cls, self) 

    orig_cls.contribute_to_class = contribute_to_class 

    return orig_cls 


def tweak_file_class(orig_cls): 
    """ 
    Overriding FieldFile.save method to remove the old associated file. 
    I'm doing the same thing in OverwriteFileSystemStorage, but it works just when the names match. 
    I probably want to preserve both methods if anyone calls Storage.save. 
    """ 

    orig_save = orig_cls.save 

    def new_save(self, name, content, save=True): 
     self.delete(save=False) 

     if orig_save and callable(orig_save): 
      orig_save(self, name, content, save=save) 

    new_save.__name__ = 'save' 
    orig_cls.save = new_save 

    return orig_cls 


@tweak_file_class 
class OverwriteFieldFile(models.FileField.attr_class): 
    pass 


@tweak_file_class 
class OverwriteImageFieldFile(models.ImageField.attr_class): 
    pass 


@tweak_field_class 
class RenamedFileField(models.FileField): 
    attr_class = OverwriteFieldFile 


@tweak_field_class 
class RenamedImageField(models.ImageField): 
    attr_class = OverwriteImageFieldFile 

と私のupload_toの呼び出し可能オブジェクトは、次のようになります。

def user_image_path(instance, filename): 
    name, ext = 'image', os.path.splitext(filename)[1] 

    if instance.pk is not None: 
     return os.path.join('users', os.path.join(str(instance.pk), name + ext)) 

    return os.path.join('users', '{0}_{1}{2}'.format(uuid1(), name, ext)) 
関連する問題