Ustaw FileField Django na istniejący plik

89

Mam istniejący plik na dysku (powiedzmy /folder/file.txt) i pole modelu FileField w Django.

Kiedy robię

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

ponownie zapisuje plik jako file_1.txt(następnym razem _2, itp.).

Rozumiem dlaczego, ale nie chcę tego zachowania - wiem, że plik, z którym ma być skojarzone pole, naprawdę na mnie czeka i chcę tylko, aby Django na niego wskazał.

W jaki sposób?

Strzec
źródło
1
Nie jestem pewien, czy możesz uzyskać to, czego chcesz, bez modyfikowania Django lub tworzenia podklas FileField. Za każdym razem, gdy FileFieldzapisywany jest plik, tworzona jest nowa kopia pliku. Dodanie opcji unikania tego byłoby dość proste.
Michael Mior
no tak, wygląda na to, że muszę podklasę i dodać parametr. Nie chcę tworzyć dodatkowych tabel do tego prostego zadania
Guard
1
Umieść plik w innej lokalizacji, utwórz swoje pole z tą ścieżką, zapisz go, a następnie masz plik w miejscu docelowym upload_to.
benjaoming

Odpowiedzi:

22

Jeśli chcesz to zrobić na stałe, musisz utworzyć własną klasę 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

Teraz w swoim modelu używasz zmodyfikowanego MyFileStorage

from mystuff.customs import MyFileStorage

mfs = MyFileStorage()

class SomeModel(model.Model):
   my_file = model.FileField(storage=mfs)
Burhan Khalid
źródło
och, wygląda obiecująco. cuase kod FileField jest trochę nieintuicyjny
Guard
ale ... czy jest możliwa zmiana pamięci na podstawie żądania, na przykład: instance.field.storage = mfs; instance.field.save (nazwa, plik); ale nie robię tego w innej gałęzi mojego kodu
Guard
2
Nie, ponieważ silnik pamięci masowej jest powiązany z modelem. Możesz tego wszystkiego uniknąć, po prostu przechowując ścieżkę pliku w formacie FilePathFieldlub po prostu jako zwykły tekst.
Burhan Khalid
Nie możesz po prostu zwrócić imienia. Najpierw musisz usunąć istniejący plik.
Alexander Shpindler,
124

po prostu ustaw instance.field.nameścieżkę do swojego pliku

na przykład

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>
bara
źródło
15
To MEDIA_ROOTznaczy względna ścieżka od twojej .
mgalgs
7
W tym przykładzie myślę, że możesz po prostu zrobićdoc.file = 'path/to/file'
Andrew Swihart
13

spróbuj tego ( dokument ):

instance.field.name = <PATH RELATIVE TO MEDIA_ROOT> 
instance.save()
NIEMCY
źródło
5

Dobrze jest napisać własną klasę pamięci. Jednak get_available_namenie jest to właściwa metoda zastąpienia.

get_available_namejest wywoływana, gdy Django widzi plik o tej samej nazwie i próbuje uzyskać nową dostępną nazwę pliku. To nie metoda powoduje zmianę nazwy. metoda spowodowała to _save. Komentarze w programie _savesą całkiem dobre i łatwo można znaleźć, że otwiera on plik do zapisu z flagą, os.O_EXCLktóra wyrzuci błąd OSError, jeśli plik o tej samej nazwie już istnieje. Django wychwytuje ten błąd, a następnie wywołuje, get_available_nameaby uzyskać nową nazwę.

Więc myślę, że poprawnym sposobem jest przesłonięcie _savei wywołanie os.open () bez flagi os.O_EXCL. Modyfikacja jest dość prosta, jednak metoda jest trochę za długa, więc nie wklejam jej tutaj. Powiedz mi, czy potrzebujesz więcej pomocy :)

x1a0
źródło
to 50 linii kodu, które musisz skopiować, co jest dość złe. Zastępowanie get_available_name wydaje się być bardziej odizolowane, krótsze i znacznie bezpieczniejsze w przypadku, powiedzmy, aktualizacji do nowszych wersji Django w przyszłości
Michael Gendin
2
Problem z nadpisywaniem polega tylko na tym,get_available_name że gdy przesyłasz plik o tej samej nazwie, serwer zapętla się w nieskończoność. Ponieważ _savesprawdza nazwę pliku i decyduje się na nową, jednak get_available_namenadal zwraca zduplikowaną. Musisz więc zastąpić oba.
x1a0
1
Ups, prowadzimy tę dyskusję w dwóch pytaniach, ale dopiero teraz zauważyłem, że są one nieco inne) Więc mam rację w tym pytaniu, a ty jesteś w tym)
Michael Gendin
1

Miałem dokładnie ten sam problem! potem zdaję sobie sprawę, że powodowały to moje Modele. przykład Mam moje modele w ten sposób:

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

Następnie chciałem mieć więcej jednego kafelka odwołującego się do tego samego pliku na dysku! Sposób, w jaki znalazłem rozwiązanie, polegał na zmianie struktury mojego modelu na następującą:

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

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

Co po tym, jak zdaję sobie sprawę, że ma to więcej sensu, ponieważ jeśli chcę, aby ten sam plik był zapisywany więcej niż jeden w mojej bazie danych, muszę utworzyć dla niego kolejną tabelę!

Myślę, że możesz rozwiązać swój problem w ten sposób, mając tylko nadzieję, że możesz zmienić modele!

EDYTOWAĆ

Wydaje mi się, że możesz użyć innego magazynu, na przykład tego: SymlinkOrCopyStorage

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

Arthur Neves
źródło
ma sens w twoim przypadku, nie w moim. Nie chcę, aby przywoływano go wielokrotnie. Tworzę obiekt odwołujący się do pliku, a następnie zdaję sobie sprawę, że w innych atrybutach są błędy i ponownie otwieram formularz tworzenia. Po ponownym przesłaniu nie chcę stracić pliku, który jest już zapisany na dysku
Guard
więc myślę, że możesz użyć mojego podejścia! ponieważ będziesz mieć tabelę FormFile, która będzie przechowywać plik tylko wtedy, gdy masz! wtedy w tabeli formularza będziesz mieć FK dla tego pliku! więc możesz zmieniać / tworzyć nowe formularze dla tego samego pliku! (przy okazji zmieniam kolejność FK w moim głównym przykładzie)
Arthur Neves
Jeśli chcesz zamieścić swoją domenę (modele) w swoim poście! ja też mogę mieć lepsze pomysły!
Arthur Neves
domena właściwie nie ma znaczenia - mam modelkę z powiązanym z nią zdjęciem i mam niestandardowy ekran edycji. po przesłaniu chcę, aby zdjęcie pozostało na serwerze, ale tak naprawdę nie lubię tworzyć oddzielnych modeli, tabel i wyszukiwań FK tylko dlatego, że wyglądają na ograniczenie frameworka
Guard
Wydaje mi się, że ograniczenie tutaj jest spowodowane tym, że kiedy zapisujesz FileField w django, zawsze przechodzi on przez Django Storages! więc nie ma sensu po prostu wymusić ścieżkę do pliku! także skąd Django powinno wiedzieć, że plik już istnieje w ścieżce? innym podejściem, którego możesz użyć, jest użycie zamiast tego FilePathField! więc możesz po prostu ustawić ścieżkę w swojej bazie danych i przeprowadzić wyszukiwanie w taki sposób, w jaki uważasz, że jest najlepszy!
Arthur Neves
1

Należy zdefiniować własny magazyn, odziedziczyć go z FileSystemStorage i nadpisać OS_OPEN_FLAGSatrybut i get_available_name()metodę klasy :

Wersja Django: 3.1.0

Projekt / 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

W swoim modelu użyj niestandardowego 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())
Sułtan
źródło