Django 6.1:管理文件

Django 提供了一组访问文件的 API,例如处理用户上传的文件。底层 API 足够通用,也可以用于其他文件处理场景。若要处理 JavaScript、CSS 等静态文件,参见管理静态文件。

默认情况下,Django 使用 MEDIA_ROOT 和 MEDIA_URL 设置本地文件存储。下面的示例以这些默认设置为前提。也可以编写自定义文件存储系统,完全控制文件存储的位置和方式;本文后半部分介绍存储系统。

在模型中使用文件

使用 FileField 或 ImageField 时,Django 会提供处理文件的 API。下面的模型用 ImageField 保存照片:

from django.db import models


class Car(models.Model):
    name = models.CharField(max_length=255)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    photo = models.ImageField(upload_to="cars")
    specs = models.FileField(upload_to="specs")

每个 Car 实例都有 photo 属性,可获取照片的详细信息:

>>> car = Car.objects.get(name="57 Chevy")
>>> car.photo
<ImageFieldFile: cars/chevy.jpg>
>>> car.photo.name
'cars/chevy.jpg'
>>> car.photo.path
'/media/cars/chevy.jpg'
>>> car.photo.url
'https://media.example.com/cars/chevy.jpg'

car.photo 是 File 对象,拥有后文所述的方法和属性。

注意:文件的保存发生在模型保存流程中;模型尚未保存时,不应依赖最终在磁盘上使用的实际文件名。

例如,可以把文件的 name 设置为相对于存储位置的路径来更名。默认 FileSystemStorage 的基准目录是 MEDIA_ROOT:

>>> import os
>>> from django.conf import settings
>>> initial_path = car.photo.path
>>> car.photo.name = "cars/chevy_ii.jpg"
>>> new_path = os.path.join(settings.MEDIA_ROOT, car.photo.name)
>>> # Move the file on the filesystem
>>> os.rename(initial_path, new_path)
>>> car.save()
>>> car.photo.path
'/media/cars/chevy_ii.jpg'
>>> car.photo.path == new_path
True

把磁盘上的现有文件保存到 FileField:

>>> from pathlib import Path
>>> from django.core.files import File
>>> path = Path("/some/external/specs.pdf")
>>> car = Car.objects.get(name="57 Chevy")
>>> with path.open(mode="rb") as f:
...     car.specs = File(f, name=path.name)
...     car.save()
...

虽然 ImageField 实例可以提供 height、width、size 等非图像数据属性,但要访问底层图像数据,仍须重新打开图像。例如:

>>> from PIL import Image
>>> car = Car.objects.get(name="57 Chevy")
>>> car.photo.width
191
>>> car.photo.height
287
>>> image = Image.open(car.photo)
# Raises ValueError: seek of closed file.
>>> car.photo.open()
<ImageFieldFile: cars/chevy.jpg>
>>> image = Image.open(car.photo)
>>> image
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=191x287 at 0x7F99A94E9048>

File 对象

Django 内部在需要表示文件时使用 django.core.files.File。多数情况下,直接使用 Django 提供的 File 即可,例如附加到模型的文件或用户上传的文件。

如果需要自行构造 File,最简单的方式是使用 Python 内置的文件对象:

>>> from django.core.files import File

# Create a Python file object using open()
>>> f = open("/path/to/hello.world", "w")
>>> myfile = File(f)

现在可以使用 File 类 的属性和方法。按上面的方式打开文件后,它不会自动关闭;可以用 with 自动关闭:

>>> from django.core.files import File

# Create a Python file object using open() and the with statement
>>> with open("/path/to/hello.world", "w") as f:
...     myfile = File(f)
...     myfile.write("Hello World")
...
>>> myfile.closed
True
>>> f.closed
True

遍历大量对象并访问文件字段时,关闭文件尤其重要。访问后不关闭,可能耗尽文件描述符并触发:

OSError: [Errno 24] Too many open files

文件存储

Django 把“如何存储”和“存储在哪里”交给文件存储系统。由存储对象负责与底层文件系统交互、打开和读取文件等操作。

默认文件存储是 django.core.files.storage.FileSystemStorage。如果没有在 STORAGES 设置的 default 键中明确指定其他存储系统,就会使用它。下文介绍内置默认实现;自定义实现参见编写自定义文件存储类。

存储对象

多数情况下应使用 File 对象,由它委托给相应的存储系统;也可以直接操作存储系统。既可以实例化自定义存储类,也可以使用全局默认存储:

>>> from django.core.files.base import ContentFile
>>> from django.core.files.storage import default_storage

>>> path = default_storage.save("path/to/file", ContentFile(b"new content"))
>>> path
'path/to/file'

>>> default_storage.size(path)
11
>>> default_storage.open(path).read()
b'new content'

>>> default_storage.delete(path)
>>> default_storage.exists(path)
False

完整接口参见文件存储 API。

内置文件存储类

django.core.files.storage.FileSystemStorage 实现了基本的本地文件系统存储。例如,下面的代码把上传文件存入 /media/photos,而不采用 MEDIA_ROOT 指定的目录:

from django.core.files.storage import FileSystemStorage
from django.db import models

fs = FileSystemStorage(location="/media/photos")


class Car(models.Model):
    ...
    photo = models.ImageField(storage=fs)

自定义存储系统的用法相同:将其实例作为 storage 参数传给 FileField。

使用可调用对象

可以把可调用对象传给 FileField 或 ImageField 的 storage 参数,以便根据运行环境选择不同的存储。

此可调用对象会在模型类加载时求值,必须返回一个 Storage 实例:

from django.conf import settings
from django.db import models
from .storages import MyLocalStorage, MyRemoteStorage


def select_storage():
    return MyLocalStorage() if settings.DEBUG else MyRemoteStorage()


class MyModel(models.Model):
    my_file = models.FileField(storage=select_storage)

若要选择 STORAGES 中定义的存储,可以使用 storages:

from django.core.files.storage import storages


def select_storage():
    return storages["mystorage"]


class MyModel(models.Model):
    upload = models.FileField(storage=select_storage)

由于可调用对象在模型类加载时就会求值,如果测试需要覆盖 STORAGES,应改用 LazyObject 子类:

from django.core.files.storage import storages
from django.utils.functional import LazyObject


class OtherStorage(LazyObject):
    def _setup(self):
        self._wrapped = storages["mystorage"]


my_storage = OtherStorage()


class MyModel(models.Model):
    upload = models.FileField(storage=my_storage)

LazyObject 把存储的求值延迟到真正需要时,让 override_settings() 能够生效:

@override_settings(
    STORAGES={
        "mystorage": {
            "BACKEND": "django.core.files.storage.InMemoryStorage",
        }
    }
)
def test_storage():
    model = MyModel()
    assert isinstance(model.upload.storage, InMemoryStorage)

来源:管理文件,Django 6.1 文档。本文整理已有中文并翻译剩余英文,保留全部示例。

Copyright (c) Django Software Foundation and individual contributors. All rights reserved. 采用 BSD 三条款许可。

允许以源代码或二进制形式再分发和使用,无论是否修改,但须满足:源代码再分发保留上述版权声明、条件与下列免责声明;二进制再分发在文档和/或其他随附材料中重现上述版权声明、条件与下列免责声明;未经事先书面许可,不得使用 Django 或贡献者的名称为衍生产品背书或推广。

本软件由版权持有人与贡献者按原样提供,不作任何明示或默示保证,包括但不限于适销性及特定用途适用性。无论依据合同、严格责任或侵权(包括过失或其他原因),版权持有人与贡献者均不对因使用本软件产生的任何直接、间接、附带、特殊、惩罚性或后果性损失负责,包括替代商品或服务采购、使用损失、数据损失、利润损失或业务中断,即使已被告知可能发生此类损失。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容