ตั้งค่า FileField ของ Django เป็นไฟล์ที่มีอยู่


90

ฉันมีไฟล์ที่มีอยู่บนดิสก์ (พูดว่า /folder/file.txt) และฟิลด์โมเดล FileField ใน Django

เมื่อฉันทำ

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

มันจะบันทึกไฟล์อีกครั้งเป็นfile_1.txt(ในครั้งถัดไป_2เป็นต้น)

ฉันเข้าใจว่าทำไม แต่ฉันไม่ต้องการพฤติกรรมนี้ - ฉันรู้ว่าไฟล์ที่ฉันต้องการให้ฟิลด์เชื่อมโยงนั้นรอฉันอยู่จริงๆและฉันแค่อยากให้ Django ชี้ไปที่มัน

อย่างไร?


1
ไม่แน่ใจว่าคุณจะได้รับสิ่งที่คุณต้องการโดยไม่ต้องแก้ไขหรือ Django FileFieldsubclassing เมื่อใดก็ตามที่FileFieldบันทึกไฟล์สำเนาใหม่จะถูกสร้างขึ้น การเพิ่มตัวเลือกเพื่อหลีกเลี่ยงปัญหานี้ค่อนข้างตรงไปตรงมา
Michael Mior

ใช่ดูเหมือนว่าฉันต้องคลาสย่อยและเพิ่มพารามิเตอร์ ฉันไม่อยากสร้างตารางพิเศษสำหรับงานง่ายๆนี้
ยาม

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
ไม่ได้เนื่องจากเครื่องมือจัดเก็บข้อมูลเชื่อมโยงกับโมเดล คุณสามารถหลีกเลี่ยงสิ่งเหล่านี้ได้เพียงแค่จัดเก็บเส้นทางไฟล์ของคุณในFilePathFieldรูปแบบข้อความธรรมดาหรือแบบธรรมดา
Burhan Khalid

คุณไม่สามารถคืนชื่อได้ คุณต้องลบไฟล์ที่มีอยู่ก่อน
Alexander Shpindler

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'
Andrew Swihart


5

มันถูกต้องที่จะเขียนคลาสพื้นที่เก็บข้อมูลของตัวเอง อย่างไรก็ตามget_available_nameไม่ใช่วิธีการที่เหมาะสมในการลบล้าง

get_available_nameถูกเรียกเมื่อ Django เห็นไฟล์ที่มีชื่อเดียวกันและพยายามรับชื่อไฟล์ใหม่ที่มี ไม่ใช่วิธีการที่ทำให้เกิดการเปลี่ยนชื่อ _saveวิธีการที่เกิดขึ้นว่าเป็น ความคิดเห็นใน_saveนั้นค่อนข้างดีและคุณสามารถค้นหาได้อย่างง่ายดายเปิดไฟล์สำหรับการเขียนด้วยแฟos.O_EXCLล็กซึ่งจะทำให้ OSError มีชื่อไฟล์เดียวกันอยู่แล้ว Django จับข้อผิดพลาดนี้จากนั้นเรียกget_available_nameเพื่อรับชื่อใหม่

ดังนั้นฉันคิดว่าวิธีที่ถูกต้องคือการแทนที่_saveและเรียกใช้ os.open () โดยไม่มีแฟล็os.O_EXCLก การปรับเปลี่ยนนั้นค่อนข้างง่าย แต่วิธีนี้ยาวไปหน่อยดังนั้นฉันจึงไม่ได้วางไว้ที่นี่ บอกฉันว่าคุณต้องการความช่วยเหลือเพิ่มเติม :)


เป็นโค้ด 50 บรรทัดที่คุณต้องคัดลอกซึ่งค่อนข้างแย่ การลบล้าง get_available_name ดูเหมือนจะแยกได้มากกว่าสั้นกว่าและปลอดภัยกว่ามากสำหรับการอัปเกรดเป็น Django เวอร์ชันใหม่ในอนาคต
Michael Gendin

2
ปัญหาของการลบล้างเพียงอย่างเดียวget_available_nameคือเมื่อคุณอัปโหลดไฟล์ที่มีชื่อเดียวกันเซิร์ฟเวอร์จะวนซ้ำไม่รู้จบ เนื่องจาก_saveตรวจสอบชื่อไฟล์และตัดสินใจรับไฟล์ใหม่ แต่get_available_nameก็ยังส่งคืนไฟล์ที่ซ้ำกัน ดังนั้นคุณต้องลบล้างทั้งสองอย่าง
x1a0

1
อ๊ะเรามีการสนทนานี้ในสองคำถาม แต่ตอนนี้ฉันสังเกตเห็นว่าพวกเขาแตกต่างกันเล็กน้อย) ดังนั้นฉันถูกต้องสำหรับคำถามนั้นและคุณอยู่ในคำถามนี้)
Michael Gendin

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 อื่น ๆ และฉันเปิดแบบฟอร์มการสร้างขึ้นมาใหม่ ในการส่งใหม่ฉันไม่ต้องการหลวมไฟล์ที่บันทึกไว้แล้วในดิสก์
ยาม

ดังนั้นฉันเดาว่าคุณสามารถใช้แนวทางของฉันได้! เพราะคุณจะมี FormFile ตารางซึ่งจะเก็บไฟล์ไว้เท่านั้น! จากนั้นในตารางฟอร์มของคุณคุณจะมี FK สำหรับไฟล์นั้น! เพื่อให้คุณสามารถเปลี่ยน / สร้างรูปแบบใหม่สำหรับไฟล์เดียวกันได้! (btw ฉันกำลังเปลี่ยนลำดับของ FK ในตัวอย่างหลักของฉัน)
Arthur Neves

หากคุณต้องการโพสต์โดเมน (โมเดล) ในโพสต์ของคุณ! ฉันสามารถมีไอเดียที่ดีขึ้นได้เช่นกัน!
Arthur Neves

จริงๆแล้วโดเมนไม่สำคัญ - ฉันมีโมเดลที่มีรูปถ่ายเชื่อมโยงอยู่และฉันมีหน้าจอแก้ไขแบบกำหนดเอง เมื่ออัปโหลดแล้วฉันต้องการให้รูปภาพยังคงอยู่บนเซิร์ฟเวอร์ แต่จริงๆแล้วฉันไม่ชอบวางไข่แบบจำลองตารางและการค้นหา FK ที่แยกจากกันเพียงเพราะดูเหมือนว่าจะเป็นข้อ จำกัด ของเฟรมเวิร์ก
ยาม

ข้อ จำกัด ที่นี่ฉันเดาว่าเป็นเพราะเมื่อคุณบันทึก FileField ใน django มันจะผ่าน Django Storages เสมอ! ดังนั้นจึงไม่สมเหตุสมผลที่คุณจะบังคับเส้นทางไฟล์! Django ควรรู้ได้อย่างไรว่าไฟล์มีอยู่แล้วในพา ธ ? อีกวิธีหนึ่งที่คุณสามารถใช้ได้คือใช้ FilePathField แทน! ดังนั้นคุณสามารถกำหนดเส้นทางในฐานข้อมูลของคุณและทำการค้นหาในแบบที่คุณคิดว่าดีที่สุด!
Arthur Neves

1

คุณควรกำหนดที่เก็บข้อมูลของคุณเองสืบทอดจาก FileSystemStorage และแทนที่OS_OPEN_FLAGSคลาสแอตทริบิวต์และget_available_name()วิธีการ:

Django เวอร์ชัน: 3.1.2

โครงการ / 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.