Setel FileField Django ke file yang ada

89

Saya memiliki berkas yang ada di disk (katakanlah /folder/file.txt) dan bidang model FileField di Django.

Ketika saya melakukannya

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

itu menyimpan ulang file sebagai file_1.txt(saat berikutnya _2, dll.).

Saya mengerti mengapa, tetapi saya tidak ingin perilaku ini - saya tahu berkas yang saya inginkan terkait dengan bidang tersebut benar-benar ada di sana menunggu saya, dan saya hanya ingin Django menunjukkannya.

Bagaimana?

Menjaga
sumber
1
Tidak yakin anda bisa mendapatkan apa yang anda inginkan tanpa memodifikasi Django atau subkelas FileField. Setiap kali a FileFielddisimpan, salinan baru dari file tersebut dibuat. Akan sangat mudah untuk menambahkan opsi untuk menghindari hal ini.
Michael Mior
ya, sepertinya saya harus subclass dan menambahkan param. Saya tidak ingin membuat tabel tambahan untuk tugas sederhana ini
Jagalah
1
Letakkan file di lokasi berbeda, buat bidang Anda dengan jalur ini, simpan, lalu Anda memiliki file di tujuan upload_to.
benjaoming

Jawaban:

22

Jika Anda ingin melakukan ini secara permanen, Anda perlu membuat kelas FileStorage Anda sendiri

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

Sekarang dalam model Anda, Anda menggunakan MyFileStorage yang telah dimodifikasi

from mystuff.customs import MyFileStorage

mfs = MyFileStorage()

class SomeModel(model.Model):
   my_file = model.FileField(storage=mfs)
Burhan Khalid
sumber
oh, terlihat menjanjikan. karena kode FileField agak non-intuitif
Penjaga
tetapi ... apakah mungkin untuk mengubah penyimpanan berdasarkan permintaan, seperti: instance.field.storage = mfs; instance.field.save (nama, file); tetapi tidak melakukannya di cabang lain dari kode saya
Penjaga
2
Tidak, karena mesin penyimpanan terkait dengan model. Anda dapat menghindari semua ini hanya dengan menyimpan jalur file Anda baik dalam bentuk FilePathFieldteks biasa atau teks biasa.
Burhan Khalid
Anda tidak bisa begitu saja mengembalikan nama. Anda perlu menghapus file yang ada terlebih dahulu.
Alexander Shpindler
124

cukup setel instance.field.nameke jalur file Anda

misalnya

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
sumber
15
Jalur relatif dari Anda MEDIA_ROOT, yaitu.
mgalgs
7
Dalam contoh ini, saya pikir Anda juga bisa melakukannyadoc.file = 'path/to/file'
Andrew Swihart
13

coba ini ( dok ):

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

Menulis kelas penyimpanan sendiri adalah hak. Namun get_available_nameini bukanlah metode yang tepat untuk diganti.

get_available_namedipanggil ketika Django melihat sebuah berkas dengan nama yang sama dan mencoba mendapatkan nama berkas baru yang tersedia. Bukan metode yang menyebabkan penggantian nama. metode yang menyebabkan itu _save. Komentar di _savecukup bagus dan Anda dapat dengan mudah menemukannya membuka file untuk menulis dengan flag os.O_EXCLyang akan memunculkan OSError jika nama file yang sama sudah ada. Django menangkap Kesalahan ini kemudian memanggil get_available_nameuntuk mendapatkan nama baru.

Jadi menurut saya cara yang benar adalah menimpa _savedan memanggil os.open () tanpa flag os.O_EXCL. Modifikasinya cukup sederhana namun metodenya agak lama jadi saya tidak paste disini. Beri tahu saya jika Anda membutuhkan bantuan lebih lanjut :)

x1a0
sumber
itu 50 baris kode yang harus Anda salin, yang sangat buruk. Mengganti get_available_name tampaknya lebih terisolasi, lebih pendek, dan jauh lebih aman untuk, katakanlah, meningkatkan ke versi terbaru Django di masa mendatang
Michael Gendin
2
Masalah hanya menimpa get_available_nameadalah ketika Anda mengunggah file dengan nama yang sama, server akan mengalami loop tanpa akhir. Sejak _savememeriksa nama file dan memutuskan untuk mendapatkan yang baru namun get_available_nametetap mengembalikan yang duplikat. Jadi, Anda perlu mengganti keduanya.
x1a0
1
Ups, kami mengadakan diskusi ini dalam dua pertanyaan, tetapi baru sekarang saya perhatikan bahwa mereka sedikit berbeda) Jadi saya benar dalam pertanyaan itu, dan Anda ada di sini)
Michael Gendin
1

Saya punya masalah yang persis sama! kemudian saya menyadari bahwa Model saya menyebabkan itu. contoh saya membuat model saya seperti ini:

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

Kemudian, saya ingin memiliki lebih dari satu ubin yang mereferensikan file yang sama di disk! Cara yang saya temukan untuk menyelesaikannya adalah dengan mengubah struktur Model saya menjadi ini:

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

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

Yang setelah saya sadari itu lebih masuk akal, karena jika saya ingin file yang sama disimpan lebih dari satu di DB saya, saya harus membuat tabel lain untuk itu!

Saya rasa Anda juga bisa menyelesaikan masalah Anda seperti itu, hanya berharap Anda bisa mengubah modelnya!

EDIT

Saya rasa Anda juga dapat menggunakan penyimpanan yang berbeda, seperti ini misalnya: SymlinkOrCopyStorage

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

Arthur Neves
sumber
masuk akal dalam kasus Anda, bukan dalam kasus saya. Saya tidak ingin ini dirujuk berkali-kali. Saya membuat objek yang mereferensikan file, lalu saya menyadari ada kesalahan di attrs lain, dan saya membuka kembali formulir pembuatan. Saat pengiriman ulang, saya tidak ingin kehilangan file yang sudah disimpan di disk
Jaga
jadi saya kira Anda bisa menggunakan pendekatan saya! karena Anda akan memiliki tabel FormFile yang hanya akan menyimpan file yang Anda miliki! maka dalam tabel Formulir Anda, Anda akan memiliki FK untuk file itu! sehingga Anda dapat mengubah / membuat formulir baru untuk file yang sama! (btw saya mengubah urutan FK dalam contoh utama saya)
Arthur Neves
Jika Anda ingin memposting domain Anda (model) di posting Anda! saya bisa memiliki ideia yang lebih baik juga!
Arthur Neves
domain sebenarnya tidak masalah - Saya memiliki model dengan foto yang terkait dengannya, dan saya memiliki layar pengeditan khusus. setelah diunggah saya ingin fotonya tetap ada di server, tetapi sebenarnya saya tidak suka menelurkan model terpisah, tabel, dan pencarian FK hanya karena tampaknya batasan kerangka kerja
Guard
Batasan di sini saya kira adalah karena ketika Anda menyimpan FileField di django, selalu melewati Penyimpanan Django! jadi tidak akan masuk akal Anda hanya memaksa jalur file! juga bagaimana Django harus mengetahui bahwa berkas sudah ada di jalur? Pendekatan lain yang dapat Anda gunakan adalah menggunakan FilePathField sebagai gantinya! sehingga Anda dapat menetapkan jalur di DB Anda dan melakukan pencarian dengan cara yang menurut Anda terbaik!
Arthur Neves
1

Anda harus menentukan penyimpanan Anda sendiri, mewarisinya dari FileSystemStorage, dan mengganti OS_OPEN_FLAGSatribut dan get_available_name()metode kelas :

Versi Django: 3.1.0

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

Di model Anda, gunakan OverwriteStorage kustom Anda

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())
Sultan
sumber