2012-04-01 42 views
13

我想創建一個簡單的照片庫與默認的Django管理員。我想爲每個畫廊保存一張樣本照片,但我不想保留這個文件名。而不是文件名,我想保存模型的編號(N.jpg)。但第一次我想保存對象的ID不存在。我怎麼能知道模型中的下一個自動增量,或者在super.save上傳之前以某種方式保存模型數據,當self.id存在時上傳文件之後?有一個很酷的解決方案嗎?Django管理文件上傳與當前模型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 2012-04-01 21:43:27

+0

沒有可靠的方法提前知道下一個記錄的ID。記錄最初創建後,您可以獲得id,但這也受競爭條件的影響。我的建議 - 選擇除id之外的其他名稱來命名文件。 – Brandon 2012-04-02 01:21:34

+0

例如,當前時間戳+微秒 – ilvar 2012-04-02 02:21:52

回答

9

圖片文件在圖庫實例前被保存。所以,你必須使用信號W /畫廊實例本身攜帶狀態,以保存拆分兩個階段:

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看起來像

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) 

我建議使用ImageField的,而不是針對的FileField固定 - 檢查該字段是否僅用於圖片上傳。此外,你可能想正常化文件擴展名(這是不必要的,因爲mimetype)像

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 =圖像是模型對象的對象,如果其他人會嘗試使用此解決方案。 – 2012-04-02 21:57:43

+0

@KBalazs很高興它有幫助,只需修復代碼,請檢查編輯請 – okm 2012-04-03 09:50:42

31

我遇到了同樣的問題。 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 2014-12-16 05:38:56

+1

@Kukosk這在Django 1.7中起作用! – Ajoy 2015-05-27 14:45:25

+3

不錯的解決方案!一段時間後,我注意到它在單元測試中斷了,因爲kwargs包含'force_insert = True',第二個保存結果在IntegrityError中:(1062,「重複條目」)。在if塊的末尾添加kwargs.pop('force_insert')可以解決問題。 – jurer 2015-07-25 12:33:47

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