Машинный перевод под контролем человека
Этот контент переведён с помощью машинной генерации, направляемой составленными людьми глоссариями и руководствами по стилю. Поскольку текст не проверяется вручную построчно, возможны отдельные ошибки или неестественные формулировки.
В случае любых расхождений авторитетным источником считается оригинальная версия на английском языке.
Файловое хранилище
FileField и ImageField сохраняют загруженные файлы через storage backend, который вы задаёте с помощью параметра storage поля.
Создайте storage backend один раз и используйте его повторно во всех полях, которые хранят файлы в одном и том же месте.
Минимальный пример
from contextlib import asynccontextmanager
from fastapi import FastAPI
from sqlalchemy import JSON, Integer, String, create_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from starlette_admin import ImageField
from starlette_admin.contrib.sqla import Admin, ModelView
from starlette_admin.storage import LocalStorage
engine = create_engine("sqlite:///admin.sqlite")
local = LocalStorage(base_dir="uploads", name="local")
class Base(DeclarativeBase):
pass
class Book(Base):
__tablename__ = "book"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
title: Mapped[str] = mapped_column(String(200))
cover: Mapped[dict | None] = mapped_column(JSON, nullable=True)
class BookView(ModelView):
fields = [
"id",
"title",
ImageField("cover", storage=local, upload_folder="covers"),
]
@asynccontextmanager
async def lifespan(app: FastAPI):
Base.metadata.create_all(engine)
yield
app = FastAPI(lifespan=lifespan)
admin = Admin(engine, title="Bookstore", secret_key="change-me")
admin.add_view(BookView(Book))
admin.mount_to(app)
Когда пользователь загружает обложку через админку, она:
- сохраняет файл в
uploads/covers/ - записывает JSON-объект метаданных в столбец
cover
База данных никогда не хранит сам файл, путь к нему в файловой системе или бинарные данные.
Что записывается в базу данных
Админка представляет загруженный файл как сериализованный объект FileInfo в поле модели.
{
"filename": "product-photo.jpg",
"content_type": "image/jpeg",
"size": 204800,
"storage": "s3",
"key": "uploads/products/a1b2c3_product-photo.jpg",
"url": "https://..."
}
filename: очищенное исходное имя файла, используется для отображенияcontent_type: MIME-тип, определённый в момент загрузкиsize: размер файла в байтахstorage: зарегистрированное имя backend'а, по которому определяется расположение файла для генерации URL и удаленияkey: путь относительно хранилища или ключ объектаurl: кэшированный публичный URL
LocalStorage сохраняет пустое значение url, поскольку URL зависит от текущего запроса. S3Storage сохраняет публичный или presigned URL в зависимости от вашей конфигурации.
Независимо от backend'а, FileField заново генерирует URL при отрисовке с помощью storage.url(), а не доверяет сохранённому значению.
ImageField дополнительно добавляет width и height.
Админка очищает каждое имя файла с помощью secure_filename перед сохранением: она удаляет компоненты пути и заменяет символы вне диапазона [A-Za-z0-9_.-] на _. Подробнее см. раздел Безопасность.
Storage backends
Локальное хранилище
from starlette_admin.storage import LocalStorage
local = LocalStorage(base_dir="uploads", name="local")
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
base_dir |
str | Path |
обязательный | Корневой каталог для хранения файлов. Создаётся автоматически, если не существует. |
name |
str | None |
"local" |
Имя в реестре, идентифицирующее backend. Должно быть уникальным при использовании нескольких экземпляров. |
Админка отдаёт файлы через этот маршрут:
Дополнительная настройка статических файлов не требуется.
LocalStorage.url() формирует URL на основе контекста текущего запроса, поэтому сохранённое поле url остаётся пустым и вычисляется по мере необходимости.
Note
Пример кода см. в examples/04-filestorage.
Amazon S3 storage
from starlette_admin.storage import S3Storage
s3 = S3Storage(
bucket="my-bucket",
prefix="admin/",
region="eu-west-1",
public=False,
)
Установите необязательные зависимости:
Это установит пакет aiobotocore.
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
bucket |
str |
обязательный | Имя S3-бакета. |
prefix |
str |
"uploads/" |
Префикс ключа, применяемый ко всем сохраняемым объектам. |
region |
str |
"us-east-1" |
Регион AWS, используемый для подписи запросов и генерации URL. |
access_key и secret_key |
str | None |
None |
Необязательные учётные данные. При отсутствии используется стандартная цепочка учётных данных AWS. |
public |
bool |
True |
Если True, возвращается публичный URL. Если False, генерируются presigned URL. |
expires |
int |
3600 |
Время жизни presigned URL в секундах. |
endpoint_url |
str | None |
None |
Кастомный S3-совместимый endpoint, например MinIO, R2 или B2. |
name |
str | None |
"s3" |
Имя в реестре, идентифицирующее backend. |
Если указан endpoint_url, админка строит URL в формате:
вместо виртуального хостинга AWS (virtual-hosted style).
Important
Поля файлов должны быть сопоставлены со столбцом базы данных, поддерживающим JSON. База данных хранит только метаданные, а сам файл — storage backend.
Несколько файлов (multiple=True)
Установите multiple=True для FileField или ImageField, чтобы принимать несколько загрузок в одном поле.
from starlette_admin import FileField
from starlette_admin.contrib.sqla import ModelView
from starlette_admin.storage import LocalStorage
local = LocalStorage(base_dir="uploads/attachments", name="attachments")
class TicketView(ModelView):
fields = [
"id",
"subject",
FileField(
"attachments",
storage=local,
upload_folder="tickets/",
multiple=True,
),
]
В базе данных сохраняется JSON-список объектов FileInfo, и админка обрабатывает каждый файл независимо — сначала валидацию, затем сохранение.
Warning
Сохранение формы полностью заменяет весь список файлов на отправленные. Добавить или удалить отдельный файл невозможно. Для управления жизненным циклом каждого файла используйте inline-модель со своим FileField.
Important
ListField(FileField(...)) не поддерживается. Используйте multiple=True для простых коллекций, а inline-модели — для структурированных данных о файлах.
Валидация
Валидация выполняется в следующем порядке:
acceptmax_size- пользовательские
validators
Пользовательский валидатор — это вызываемый объект, который получает запрос, поле, объект UploadFile и все значения отправленной формы. Он должен вернуть None или выбросить исключение ValueError.
В следующем примере проверяется фактическое содержимое файла с помощью библиотеки filetype:
import filetype
from starlette.datastructures import UploadFile
from starlette.requests import Request
from starlette_admin.fields import BaseField
ALLOWED_DOCUMENT_MIME_TYPES = {
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
}
def validate_document_type(
request: Request, field: BaseField, upload: UploadFile, form_values: dict
) -> None:
upload.file.seek(0)
try:
header = upload.file.read(2048)
kind = filetype.guess(header)
detected = kind.mime if kind else "application/octet-stream"
finally:
upload.file.seek(0)
if detected not in ALLOWED_DOCUMENT_MIME_TYPES:
raise ValueError(
f"Invalid file type '{detected}'. Only PDF, DOC, and DOCX are allowed."
)
Сбросьте указатель файла
Всегда сбрасывайте указатель файла с помощью seek(0) до и после проверки содержимого, чтобы слой хранения мог прочитать файл целиком.
Note
Валидаторы выполняются для каждого файла отдельно, поэтому при multiple=True каждый файл проверяется независимо. ImageField применяет собственную проверку изображений до любого пользовательского валидатора.
Рекомендации
Используйте accept и max_size для быстрой базовой проверки.
Используйте пользовательские валидаторы, когда нужно проверить содержимое файла или применить специфичные для приложения правила.
Не полагайтесь на расширения файлов или заголовки Content-Type при проверках, критичных для безопасности. Вместо этого проверяйте содержимое с помощью библиотеки вроде filetype или python-magic.
Ограничения очистки файлов
starlette-admin загружает файлы в storage backend и записывает метаданные FileInfo в базу данных, но не удаляет файлы после сбоя или удаления записи. Из этого следуют два поведения:
- Неудачные транзакции: если транзакция базы данных откатывается после завершения загрузки, файл остаётся в storage backend. У операций записи в хранилище нет механизма отката.
- Удаление и обновление: удаление строки или замена файла удаляет ссылку
FileInfoиз базы данных, однако старый файл остаётся вLocalStorageилиS3Storage.
Такая архитектура упрощает слой хранения и предотвращает запуск деструктивных операций из-за ошибок уровня приложения. Обратная сторона — накопление «осиротевших» файлов. Чтобы хранилище не росло бесконтрольно, сверьте их самостоятельно. Распространённый подход — периодическая фоновая задача, которая сравнивает ключи в storage backend с активными ссылками FileInfo в вашей базе данных.
Транзакционная альтернатива
Если вашему приложению нужны транзакционные операции с файловым хранилищем, согласованные с записями в базу данных, используйте библиотеку, связывающую файловое хранилище с unit of work в SQLAlchemy.
Вместо параметра storage= поля используйте sqlalchemy-file. Она сохраняет файлы как часть цикла flush и rollback ORM, поэтому неудачная транзакция или удаление строки отменяет соответствующую запись файла. Рабочий пример см. в examples/13-sqlachemy-file.
Что дальше
- Поля: справочник по
FileFieldиImageField. - Экспорт и импорт: как файлы включаются в экспортируемые пакеты.
- Безопасность: автоматическая очистка имён файлов и поведение валидации.