2011-02-27 69 views
34

我試圖在django中生成動態文件路徑。我想打一個文件系統是這樣的:Django中的動態文件路徑

-- user_12 
    --- photo_1 
    --- photo_2 
--- user_ 13 
    ---- photo_1 

我發現了一個相關的問題:Django Custom image upload field with dynamic path

在這裏,他們說,我們可以改變upload_to路徑並導致https://docs.djangoproject.com/en/stable/topics/files/文檔。在文檔中,有一個例子:

from django.db import models 
from django.core.files.storage import FileSystemStorage 

fs = FileSystemStorage(location='/media/photos') 

class Car(models.Model): 
    ... 
    photo = models.ImageField(storage=fs) 

但是,這仍然不是動態的,我想給車輛ID的圖像名稱,汽車的定義完成之前,我不能指定ID。那麼我如何創建一個帶有車輛ID的路徑?

+0

我以爲在保存之前我無法獲得'instance'屬性。我錯了,那些汽車屬性已被限制爲'實例',但尚未保存在數據庫中 – laike9m 2014-01-09 08:38:17

回答

54

您可以使用在upload_to參數可贖回,而不是使用自定義存儲。請參閱docs,並注意其中的警告,即在調用該函數時可能尚未設置主鍵(因爲在將對象保存到數據庫之前可能需要處理上載),所以可能無法使用ID。您可能會考慮在模型上使用另一個字段,如slug。 E.g:

import os 
def get_upload_path(instance, filename): 
    return os.path.join(
     "user_%d" % instance.owner.id, "car_%s" % instance.slug, filename) 

則:

photo = models.ImageField(upload_to=get_upload_path) 
+0

我是python的新手,我可能會錯過一些東西。但是,我沒有看到你在下面的語句中傳遞實例,文件名 - photo = models.ImageField(upload_to = get_upload_path)。所以,我的問題是該方法如何知道什麼是價值? – 2013-05-23 15:27:15

+2

請參閱答案中的「文檔」鏈接。它賣給你 - 「將通過的兩個論據是......「 – DrMeers 2013-05-23 21:16:47

+1

它在Django 1.5中不起作用 – 2013-07-08 15:53:34

5

https://docs.djangoproject.com/en/stable/ref/models/fields/#django.db.models.FileField.upload_to

def upload_path_handler(instance, filename): 
    return "user_{id}/{file}".format(id=instance.user.id, file=filename) 

class Car(models.Model): 
    ... 
    photo = models.ImageField(upload_to=upload_path_handler, storage=fs) 

有一個在文檔的警告,但它不應該影響你,因爲我們是User ID,而不是Car ID後。

In most cases, this object will not have been saved to the database yet, so if it uses the default AutoField, it might not yet have a value for its primary key field.

+2

有一件事情可能並非完全明顯,除非您閱讀了關於'FileField'的所有文檔,否則'upload_to'可調用實際上會返回一個文件名'/ path/to/base.ext',並將其附加到存儲的位置。由於存儲抽象了絕對路徑和文件操作,因此不太明顯的好處是可以將文件移動到完全不同的存儲/位置,並且模型將繼續按預期工作。 – 2011-02-27 20:38:58

+0

嗨,我試過但得到錯誤:AttributeError:'汽車'對象沒有屬性'用戶'。我如何將request.user.id(登錄用戶的ID)傳遞給instance.user.id?謝謝。 – learnJQueryUI 2015-02-07 12:17:20

0

This guy有辦法做到動態路徑。這個想法是設置你最喜歡的存儲,並用一個函數定製「upload_to()」參數。

希望這會有所幫助。

0

我發現了一個不同的解決方案,它很髒,但它的工作原理。您應該創建一個新的虛擬模型,它與原始模型自動同步。我對此並不感到自豪,但沒有找到其他解決方案。在我的情況下,我想上傳文件,並將其存儲在以模型ID命名的目錄中(因爲我將生成更多文件)。

的model.py

class dummyexperiment(models.Model): 
    def __unicode__(self): 
    return str(self.id) 

class experiment(models.Model): 
    def get_exfile_path(instance, filename): 
    if instance.id == None: 
     iid = instance.dummye.id 
    else: 
     iid = instance.id 
    return os.path.join('experiments', str(iid), filename) 

    exfile = models.FileField(upload_to=get_exfile_path) 

    def save(self, *args, **kwargs): 
    if self.id == None: 
     self.dummye = dummyexperiment() 
     self.dummye.save() 
    super(experiment, self).save(*args, **kwargs) 

我在Python和Django中很新,但它似乎是確定我。

另一種解決方案:

def get_theme_path(instance, filename): 
    id = instance.id 
    if id == None: 
    id = max(map(lambda a:a.id,Theme.objects.all())) + 1 
    return os.path.join('experiments', str(id), filename) 
+0

好吧,所以你得到一個隨機的id,那麼你如何將文件鏈接到保存的對象? – Mikhail 2013-10-14 04:44:01

+1

呵呵,很久以前,我停下來用django工作,所以我不記得了,我查了一下這些代碼,並且顯示我正在使用別的東西,在這裏(編輯我的答案): – balazs 2013-10-14 11:04:17

+0

這是一個有趣的方法,希望主鍵在同步總項目數。我想這是真的,只要你不刪除任何東西。 – Mikhail 2013-10-15 18:01:04

4

可以使用lambda函數如下,請注意,如果實例新的,那麼它不會有實例ID,所以用別的東西:

logo = models.ImageField(upload_to=lambda instance, filename: 'directory/images/{0}/{1}'.format(instance.owner.id, filename)) 
2

好非常遲到了,但是這一次對我的作品。這樣才

class Album(models.Model): 
    albumname = models.CharField(max_length=100) 
    audiofile = models.FileField(upload_to=content_file_name) 
+0

這對我有效,但我有一個問題,我需要爲一個模型ID生成兩個子樹,我這樣做:upload_dir = os.path.join('equipos',instance.equipo_id,'anexos','img')&upload_dir = os.path.join ('equipos',instance.equipo_id,'anexos','files')在不​​同的模型中調用不同的函數,有沒有辦法做到這一點,但沒有打破DRY範式? – 2016-12-22 17:30:26

0

def content_file_name(instance, filename): 
    upload_dir = os.path.join('uploads',instance.albumname) 
    if not os.path.exists(upload_dir): 
     os.makedirs(upload_dir) 
    return os.path.join(upload_dir, filename) 

型號彷彿模型實例沒有被保存到數據庫中還沒有主鍵(id)可能無法使用,我寫了移動的文件我的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)) 
1

我的解決方案是不優雅,但它的工作原理:

在t他的模型,使用標準的功能,將需要的ID /包

def directory_path(instance, filename): 
    return 'files/instance_id_{0}/{1}'.format(instance.pk, filename) 

在views.py保存這樣的形式:

f=form.save(commit=False) 
ftemp1=f.filefield 
f.filefield=None 
f.save() 
#And now that we have crated the record we can add it 
f.filefield=ftemp1 
f.save() 

它爲我工作。 注意:我的文件區位於模型中,並且允許使用空值。 Null = True