DjangoのFileFieldを既存のファイルに設定します


89

ディスク上に既存のファイル(たとえば/folder/file.txt)とDjangoのFileFieldモデルフィールドがあります。

私がする時

instance.field = File(file('/folder/file.txt'))
instance.save()

ファイルをfile_1.txt次のように再保存します(次回_2など)。

理由は理解できますが、この動作は望ましくありません。フィールドに関連付けるファイルが実際にそこにあり、Djangoがそれを指すようにしたいだけです。

どうやって?


1
Djangoを変更したり、サブクラス化したりせずに、必要なものを取得できるかどうかはわかりませんFileField。aFileFieldが保存されるたびに、ファイルの新しいコピーが作成されます。これを回避するオプションを追加するのはかなり簡単です。
Michael Mior 2011年

はい、サブクラス化してパラメータを追加する必要があるようです。この単純なタスクのために追加のテーブルを作成したくありません
Guard

1
ファイルを別の場所に置き、このパスを使用してフィールドを作成し、保存すると、upload_toの宛先にファイルが作成されます。
2011年

回答:


22

これを永続的に実行する場合は、独自のFileStorageクラスを作成する必要があります

import os
from django.conf import settings
from django.core.files.storage import FileSystemStorage

class MyFileStorage(FileSystemStorage):

    # This method is actually defined in Storage
    def get_available_name(self, name):
        if self.exists(name):
            os.remove(os.path.join(settings.MEDIA_ROOT, name))
        return name # simply returns the name passed

これで、モデルで、変更したMyFileStorageを使用します

from mystuff.customs import MyFileStorage

mfs = MyFileStorage()

class SomeModel(model.Model):
   my_file = model.FileField(storage=mfs)

ああ、有望に見えます。FileFieldのコードが直感的ではないことを確認してください
Guard

しかし...次のように、リクエストごとにストレージを変更することは可能ですか?instance.field.storage = mfs; instance.field.save(name、file); しかし、私のコードの別のブランチでは実行していません
Guard

2
いいえ、ストレージエンジンはモデルに関連付けられているためです。ファイルパスをaFilePathFieldまたはプレーンテキストとして保存するだけで、これらすべてを回避できます。
Burhan Khalid 2011

名前だけを返すことはできません。最初に既存のファイルを削除する必要があります。
アレクサンダーシュピンドラー2018年

124

設定するだけ instance.field.nameファイルのパスにするです

例えば

class Document(models.Model):
    file = FileField(upload_to=get_document_path)
    description = CharField(max_length=100)


doc = Document()
doc.file.name = 'path/to/file'  # must be relative to MEDIA_ROOT
doc.file
<FieldFile: path/to/file>

15
あなたからの相対パスMEDIA_ROOT、つまり。
mgalgs 2012年

7
この例では、あなたもできると思いますdoc.file = 'path/to/file'
Andrew Swihart

13

これを試してください(doc):

instance.field.name = <PATH RELATIVE TO MEDIA_ROOT> 
instance.save()

5

独自のストレージクラスを作成するのは正しいことです。しかしながらget_available_name、オーバーライドする適切な方法ではありません。

get_available_nameDjangoが同じ名前のファイルを見つけて、新しい使用可能なファイル名を取得しようとしたときに呼び出されます。名前の変更を引き起こすのはメソッドではありません。原因となったメソッドは_saveです。のコメント_saveは非常に優れておりos.O_EXCL、同じファイル名がすでに存在する場合にOSErrorをスローするフラグを使用して書き込み用のファイルを開くことが簡単にわかります。Djangoはこのエラーをキャッチしてから呼び出しますget_available_name、新しい名前を取得するために。

したがって、正しい方法は、_saveフラグなしでos.open()をオーバーライドして呼び出すことだと思いますos.O_EXCL。変更は非常に簡単ですが、メソッドが少し長いので、ここに貼り付けません。さらにヘルプが必要な場合は教えてください:)


コピーする必要があるのは50行のコードですが、これはかなり悪いことです。get_available_nameのオーバーライドは、たとえば、将来的に新しいバージョンのDjangoにアップグレードする場合に、より分離され、短く、はるかに安全になります
Michael

2
オーバーライドのみの問題はget_available_name、同じ名前のファイルをアップロードすると、サーバーが無限ループに陥ることです。以来_saveとチェックし、ファイル名が新しいものを取得することを決定get_available_name、まだ重複1を返します。したがって、両方をオーバーライドする必要があります。
x1a0 2012年

1
おっと、私たちは2つの質問でこの議論をしていますが、今だけ私はそれらがわずかに異なることに気づきました)それで私はその質問に正しいです、そしてあなたはこれにいます)
Michael Gendin 2012年

1

私はまったく同じ問題を抱えていました!それから私は私のモデルがそれを引き起こしていたことに気づきます。例私はこのようなモデルを持っていました:

class Tile(models.Model):
  image = models.ImageField()

次に、ディスク内の同じファイルを参照する1つのタイルを複数作成したかったのです。それを解決するために私が見つけた方法は、モデル構造を次のように変更することでした。

class Tile(models.Model):
  image = models.ForeignKey(TileImage)

class TileImage(models.Model):
  image = models.ImageField()

同じファイルをDBに複数保存したい場合は、別のテーブルを作成する必要があるため、これはより理にかなっていることに気付いた後です。

モデルを変えられることを願って、あなたもそのような問題を解決できると思います!

編集

また、たとえばSymlinkOrCopyStorageのように、別のストレージを使用できると思います。

http://code.welldev.org/django-storages/src/11bef0c2a410/storages/backends/symlinkorcopy.py


私の場合ではなく、あなたの場合には意味があります。何度も参照されたくありません。ファイルを参照するオブジェクトを作成した後、他の属性にエラーがあることに気付き、作成フォームを再度開きます。再送信時に、ディスクにすでに保存されているファイルを失いたくありません
Guard

だから私のアプローチを使用できると思います!ファイルを保持するテーブルFormFileがあるので、次のようになります。次に、フォームテーブルにそのファイルのFKがあります。そのため、同じファイルに対して新しいフォームを変更/作成できます。(ところで、私のメインの例ではFKの順序を変更しています)
Arthur Neves 2011年

ドメイン(モデル)を投稿したい場合!私もより良いアイデアを持つことができます!
Arthur Neves 2011年

ドメインは実際には関係ありません。写真が関連付けられたモデルがあり、カスタム編集画面があります。アップロードしたら、写真をサーバーに残したいのですが、フレームワークの制限のように見えるという理由だけで、モデル、テーブル、FKルックアップを個別に生成するのは好きではありません
Guard

ここでの制限は、FileFieldをdjangoに保存すると、常にDjangoストレージを通過するためだと思います。したがって、ファイルパスを強制するだけでは意味がありません。また、Djangoはファイルがパスにすでに存在することをどのように知る必要がありますか?使用できる別のアプローチは、代わりにFilePathFieldを使用することです。そのため、DBにパスを設定して、自分が最適だと思う方法でルックアップを行うことができます。
Arthur Neves 2011年

1

独自のストレージを定義し、それをFileSystemStorageから継承し、OS_OPEN_FLAGSクラスの属性とget_available_name()メソッドをオーバーライドする必要があります。

Djangoバージョン: 3.1

Project / core / files / storages / backends / local.py

import os

from django.core.files.storage import FileSystemStorage


class OverwriteStorage(FileSystemStorage):
    """
    FileSystemStorage subclass that allows overwrite the already existing
    files.
    
    Be careful using this class, as user-uploaded files will overwrite
    already existing files.
    """

    # The combination that don't makes os.open() raise OSError if the
    # file already exists before it's opened.
    OS_OPEN_FLAGS = os.O_WRONLY | os.O_TRUNC | os.O_CREAT | getattr(os, 'O_BINARY', 0)

    def get_available_name(self, name, max_length=None):
        """
        This method will be called before starting the save process.
        """
        return name

モデルでは、カスタムOverwriteStorageを使用します

myapp / models.py

from django.db import models

from core.files.storages.backends.local import OverwriteStorage


class MyModel(models.Model):
   my_file = models.FileField(storage=OverwriteStorage())
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.