Đặt FileField của Django thành một tệp hiện có


89

Tôi có một tệp hiện có trên đĩa (giả sử /folder/file.txt) và trường mô hình FileField trong Django.

Khi tôi làm

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

nó sẽ lưu lại tệp dưới dạng file_1.txt(lần sau _2, v.v.).

Tôi hiểu tại sao, nhưng tôi không muốn hành vi này - tôi biết tệp tôi muốn trường liên kết thực sự ở đó đang chờ tôi và tôi chỉ muốn Django trỏ đến nó.

Làm sao?


1
Không chắc bạn có thể nhận được những gì bạn muốn mà không cần sửa đổi Django hoặc phân lớp FileField. Bất cứ khi nào FileFieldtệp được lưu, một bản sao mới của tệp sẽ được tạo. Sẽ khá đơn giản nếu thêm một tùy chọn để tránh điều này.
Michael Mior

vâng, có vẻ như tôi phải phân lớp và thêm một tham số. Tôi không muốn tạo thêm các bảng cho nhiệm vụ đơn giản này
Bảo vệ

1
Đặt tệp ở một vị trí khác, tạo trường của bạn bằng đường dẫn này, lưu nó và sau đó bạn có tệp trong đích upload_to.
benjaoming

Câu trả lời:


22

Nếu bạn muốn làm điều này vĩnh viễn, bạn cần tạo lớp FileStorage của riêng mình

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

Bây giờ trong mô hình của bạn, bạn sử dụng MyFileStorage đã sửa đổi của mình

from mystuff.customs import MyFileStorage

mfs = MyFileStorage()

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

ồ, có vẻ hứa hẹn. vì mã của FileField hơi không trực quan
Guard

nhưng ... liệu có thể thay đổi bộ nhớ trên cơ sở mỗi yêu cầu, như: instance.field.storage = mfs; instance.field.save (tên, tệp); nhưng không làm điều đó trong một nhánh khác của mã của tôi
Guard

2
Không, vì công cụ lưu trữ được gắn với mô hình. Bạn có thể tránh tất cả những điều này bằng cách chỉ cần lưu trữ đường dẫn tệp của mình ở dạng a FilePathFieldhoặc đơn giản là văn bản thuần túy.
Burhan Khalid

Bạn không thể chỉ trả lại một cái tên. Trước tiên, bạn cần xóa tệp hiện có.
Alexander Shpindler

124

chỉ cần đặt thành instance.field.nameđường dẫn của tệp của bạn

ví dụ

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
Đường dẫn tương đối từ của bạn MEDIA_ROOT, đó là.
mgalgs

7
Trong ví dụ này, tôi nghĩ bạn cũng có thể làm đượcdoc.file = 'path/to/file'
Andrew Swihart

13

hãy thử cái này ( doc ):

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

5

Viết lớp lưu trữ riêng là đúng. Tuy nhiên get_available_namekhông phải là phương pháp phù hợp để ghi đè.

get_available_nameđược gọi khi Django nhìn thấy một tệp có cùng tên và cố gắng lấy một tên tệp mới có sẵn. Nó không phải là phương pháp gây ra việc đổi tên. phương pháp gây ra đó là _save. Nhận xét trong _savelà khá tốt và bạn có thể dễ dàng tìm thấy nó mở tệp để viết với cờ os.O_EXCLsẽ tạo ra lỗi OSError nếu cùng tên tệp đã tồn tại. Django bắt lỗi này sau đó gọi get_available_nameđể lấy tên mới.

Vì vậy, tôi nghĩ cách chính xác là ghi đè _savevà gọi os.open () mà không có cờ os.O_EXCL. Việc sửa đổi khá đơn giản tuy nhiên cách làm hơi dài nên tôi không dán ở đây. Hãy cho tôi biết nếu bạn cần thêm trợ giúp :)


đó là 50 dòng mã mà bạn phải sao chép, điều này khá tệ. Trọng get_available_name dường như được nhiều cô lập, ngắn hơn, và nhiều hơn nữa an toàn hơn cho, nói, nâng cấp lên phiên bản mới hơn của Django trong tương lai
Michael Gendin

2
Vấn đề chỉ ghi đè get_available_namelà khi bạn tải lên một tệp có cùng tên, máy chủ sẽ gặp phải một vòng lặp vô tận. Kể từ khi _savekiểm tra tên tệp và quyết định lấy một cái mới tuy nhiên get_available_namevẫn trả về cái trùng lặp. Vì vậy, bạn cần ghi đè cả hai.
x1a0

1
Rất tiếc, chúng tôi đang có cuộc thảo luận này trong hai câu hỏi, nhưng chỉ bây giờ tôi nhận thấy rằng họ là hơi khác nhau) Vì vậy, tôi đúng trong câu hỏi đó, và bạn đang ở trong) này
Michael Gendin

1

Tôi đã có cùng vấn đề y hệt! thì tôi nhận ra rằng Mô hình của tôi đã gây ra điều đó. ví dụ tôi tự hào về các mô hình của mình như thế này:

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

Sau đó, tôi muốn có thêm một ô tham chiếu đến cùng một tệp trong đĩa! Cách mà tôi tìm ra để giải quyết vấn đề đó là thay đổi cấu trúc Mô hình của tôi thành thế này:

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

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

Sau đó, tôi nhận ra điều đó có ý nghĩa hơn, bởi vì nếu tôi muốn cùng một tệp được lưu nhiều hơn thì một tệp trong DB của tôi, tôi phải tạo một bảng khác cho nó!

Tôi đoán bạn cũng có thể giải quyết vấn đề của bạn như vậy, chỉ hy vọng rằng bạn có thể thay đổi mô hình!

BIÊN TẬP

Ngoài ra, tôi đoán bạn có thể sử dụng một bộ nhớ khác, chẳng hạn như thế này: SymlinkOrCopyStorage

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


có ý nghĩa trong trường hợp của bạn, không phải trong trường hợp của tôi. Tôi không muốn nó được tham chiếu nhiều lần. Tôi tạo một đối tượng tham chiếu đến một tệp, sau đó tôi nhận ra có lỗi trong các tệp đính kèm khác và tôi mở lại biểu mẫu tạo. Trên nộp lại nó tôi không muốn để mất các tập tin đó đã được lưu trên đĩa
Guard

vì vậy tôi đoán bạn có thể sử dụng cách tiếp cận của tôi! bởi vì bạn sẽ có một FormFile bảng sẽ chỉ chứa tệp khi đó bạn có! thì trong bảng Biểu mẫu của bạn, bạn sẽ có FK cho tệp đó! vì vậy Bạn có thể thay đổi / tạo biểu mẫu mới cho cùng một tệp! (btw Tôi đang thay đổi thứ tự của FK trong ví dụ chính của tôi)
Arthur Neves

Nếu bạn muốn đăng tên miền (mô hình) của mình trong bài đăng của bạn! tôi cũng có thể có một Ideia tốt hơn!
Arthur Neves

miền thực sự không quan trọng - tôi có một mô hình có ảnh được liên kết với nó và tôi có màn hình chỉnh sửa tùy chỉnh. một lần tải lên tôi muốn ảnh để lại trên máy chủ, nhưng tôi không thực sự thích sinh sản một mô hình riêng biệt, bảng và FK tra cứu chỉ vì the're vẻ là một giới hạn khuôn khổ
Guard

Hạn chế ở đây tôi đoán là do khi bạn lưu FileField trong django, nó luôn đi qua Django Storages! vì vậy sẽ không hợp lý khi bạn chỉ buộc một đường dẫn tệp! cũng làm cách nào Django biết rằng tệp đã tồn tại trong đường dẫn? một cách tiếp cận khác mà bạn có thể sử dụng là sử dụng FilePathField thay thế! vì vậy bạn có thể chỉ cần thiết lập đường dẫn trong DB của mình và thực hiện tra cứu theo cách bạn nghĩ là tốt nhất!
Arthur Neves

1

Bạn nên xác định bộ nhớ của riêng mình, kế thừa nó từ FileSystemStorage và ghi đè OS_OPEN_FLAGSthuộc tính và get_available_name()phương thức lớp :

Phiên bản 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

Trong mô hình của bạn, hãy sử dụng OverwriteStorage tùy chỉnh của bạn

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())
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.