Установите для Django FileField существующий файл

89

У меня есть существующий файл на диске (например, /folder/file.txt) и поле модели FileField в Django.

Когда я делаю

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

он повторно сохраняет файл как file_1.txt(в следующий раз и _2т. д.).

Я понимаю, почему, но я не хочу такого поведения - я знаю, что файл, с которым я хочу связать поле, действительно ждет меня, и я просто хочу, чтобы Django указывал на него.

Как?

Охранник
источник
1
Не уверен, что вы можете получить то, что хотите, без изменения Django или создания подклассов FileField. Каждый раз, когда FileFieldсохраняется a, создается новая копия файла. Было бы довольно просто добавить опцию, чтобы этого избежать.
Michael Mior
ну да, похоже, мне нужно создать подкласс и добавить параметр. Я не хочу создавать дополнительные столы для этой простой задачи
Guard
1
Поместите файл в другое место, создайте поле с этим путем, сохраните его, и тогда у вас будет файл в месте назначения upload_to.
benjaoming

Ответы:

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 (имя, файл); но не делаю этого в другой ветке моего кода
Guard
2
Нет, поскольку механизм хранения привязан к модели. Вы можете избежать всего этого, просто сохранив путь к файлу либо в a, FilePathFieldлибо в виде обычного текста.
Бурхан Халид
Вы не можете просто вернуть имя. Сначала вам нужно удалить существующий файл.
Александр Шпиндлер
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
7
В этом примере, я думаю, вы также можете просто сделатьdoc.file = 'path/to/file'
Эндрю Свихарт
13

попробуйте это ( док ):

instance.field.name = <PATH RELATIVE TO MEDIA_ROOT> 
instance.save()
ООН
источник
5

Правильно написать собственный класс хранилища. Однако get_available_nameэто неправильный метод переопределения.

get_available_nameвызывается, когда Django видит файл с таким же именем и пытается получить новое доступное имя файла. Это не метод, который вызывает переименование. метод вызвал то есть _save. Комментарии _saveдовольно хороши, и вы можете легко найти, что он открывает файл для записи с флагом, os.O_EXCLкоторый выдает ошибку OSError, если такое имя файла уже существует. Django перехватывает эту ошибку, а затем вызывает get_available_nameдля получения нового имени.

Поэтому я думаю, что правильный способ - переопределить _saveи вызвать os.open () без флага os.O_EXCL. Модификация довольно проста, однако метод немного длинный, поэтому я не вставляю его здесь. Скажите, если вам нужна дополнительная помощь :)

x1a0
источник
это 50 строк кода, которые вам нужно скопировать, что очень плохо. Переопределение get_available_name кажется более изолированным, короче и намного безопаснее, скажем, для обновления до более новых версий Django в будущем
Майкл Гендин,
2
Проблема только переопределения get_available_nameзаключается в том, что когда вы загружаете файл с тем же именем, сервер попадет в бесконечный цикл. Поскольку _saveпроверяет имя файла и решает получить новое, но get_available_nameвсе равно возвращает дубликат. Так что вам нужно переопределить оба.
x1a0
1
Ой, мы обсуждаем это в двух вопросах, но только сейчас я заметил, что они немного отличаются) Так что я прав в этом вопросе, а вы в этом)
Майкл Гендин
1

У меня была точно такая же проблема! затем я понимаю, что причиной этого были мои Модели. Например, у меня были такие модели:

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

Затем я хотел иметь больше одной плитки, ссылающейся на один и тот же файл на диске! Я нашел способ решить эту проблему, изменив структуру моей модели на это:

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

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

Что после того, как я понял, что это имеет больше смысла, потому что, если я хочу, чтобы один и тот же файл сохранялся более одного в моей БД, я должен создать для него другую таблицу!

Думаю, вы тоже можете решить свою проблему, просто надеясь, что вы сможете изменить модели!

РЕДАКТИРОВАТЬ

Также я думаю, вы можете использовать другое хранилище, например, это: SymlinkOrCopyStorage

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

Артур Невес
источник
имеет смысл в вашем случае, а не в моем. Я не хочу, чтобы на него ссылались несколько раз. Я создаю объект, ссылающийся на файл, затем понимаю, что есть ошибки в других attrs, и снова открываю форму создания. При повторной отправке я не хочу потерять файл, который уже сохранен на диске
Guard
так что я думаю, вы можете использовать мой подход! потому что у вас будет таблица FormFile, которая будет содержать файл только тогда, когда у вас есть! тогда в таблице формы у вас будет FK для этого файла! так что вы можете изменять / создавать новые формы для того же файла! (кстати, я меняю порядок FK в моем основном примере)
Артур Невес
Если вы хотите разместить свой домен (модели) в своем посте! я тоже могу иметь лучшую идею!
Артур Невес
домен на самом деле не имеет значения - у меня есть модель с связанной с ней фотографией, и у меня есть собственный экран редактирования. после загрузки я хочу, чтобы фотография оставалась на сервере, но на самом деле мне не нравится создавать отдельную модель, таблицу и поиск FK только потому, что это выглядит как ограничение фреймворка
Guard
Ограничение здесь, я думаю, связано с тем, что когда вы сохраняете FileField в django, он всегда проходит через хранилища Django! так что не имеет смысла просто указать путь к файлу! также как Django должен знать, что файл уже существует в пути? другой подход, который вы можете использовать, - это использовать вместо него FilePathField! так что вы можете просто установить путь в своей БД и выполнить поиск так, как вы считаете лучшим!
Артур Невес
1

Вы должны определить собственное хранилище, унаследовать его от FileSystemStorage и переопределить OS_OPEN_FLAGSатрибут и get_available_name()метод класса :

Версия Django: 3.1

Проект / ядро ​​/ файлы / хранилища / бэкэнды / 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())
Султан
источник