Как в Django создать поле модели или форму, позволяющее загружать и хранить несколько изображений для галереи?

Как реализовать поле для галереи в Django? Кто-нибудь уже делал что-то подобное? Удивляет отсутствие актуальных пакетов — все найденные решения 5–8 летней давности. <br/> Не могу понять, как создать полноценное поле вместо использования инлайнов в админке.
  • 17 марта 2026 г. 9:03
Ответы на вопрос 1
Коротко: правильный и «более нормальный» способ — хранить изображения в отдельной модели (GalleryImage) и связывать её с родительской через ForeignKey / ManyToMany. Это даёт максимум гибкости (порядок, подписи, миниатюры, удаление, фильтрация) и совместимо с инструментами Django (валидация, storage, миграции). Если принципиально хочется единого поля в модели — можно сделать поле, которое хранит список путей (JSONField или кастомный TextField), и написать для него form-field + widget, но это более «нестандартно» и потребует ручной работы с загрузкой/удалением файлов.

Ниже — два варианта с примерами кода и рекомендациями.

1) Рекомендованный (нормализованный) вариант — отдельная модель изображений
- Плюсы: простота, гибкость, порядок, подписи, интеграция с ORM, простая миграция/бэкап.
- Минусы: в админке по умолчанию используются инлайны; если не хотите инлайнов — можно сделать собственный виджет/JS для массовой загрузки и endpoint для AJAX-загрузки.

Пример моделей:

```
# models.py
from django.db import models

class Gallery(models.Model):
    title = models.CharField(max_length=200)

class GalleryImage(models.Model):
    gallery = models.ForeignKey(Gallery, related_name='images', on_delete=models.CASCADE)
    image = models.ImageField(upload_to='galleries/%Y/%m/%d')
    caption = models.CharField(max_length=200, blank=True)
    order = models.PositiveIntegerField(default=0)

    class Meta:
        ordering = ['order']
```

Простой админ (с инлайнами):

```
# admin.py
from django.contrib import admin
from .models import Gallery, GalleryImage

class GalleryImageInline(admin.TabularInline):
    model = GalleryImage
    extra = 1

@admin.register(Gallery)
class GalleryAdmin(admin.ModelAdmin):
    inlines = [GalleryImageInline]
```

Если не хотите инлайнов — делаете:
- отдельный view/REST endpoint для загрузки файлов (при загрузке создаёт GalleryImage с fk на родителя и возвращает JSON с id и url),
- в форме/шаблоне админки подключаете JS (Dropzone/Plupload/vanilla) для множественной загрузки и управления порядком,
- на сохранении формы — JS сохраняет/удаляет связанные изображения через AJAX или передаёт список существующих id.

Это — промышленный подход (много сайтов так делают).

2) Альтернативный вариант — поле, хранящее список путей (JSONField) + form-field+widget
- Плюсы: в модели — одно поле, легче отрисовать форму, можно быстро сделать «галерею» без новой таблицы.
- Минусы: приходится вручную сохранять файлы в storage, управлять удалением старых файлов, сложнее делать выборки/фильтрацию по изображениям, теряется нормализация.

Пример минимальной реализации:

```
# models.py (Django 3.1+)
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=200)
    images = models.JSONField(default=list, blank=True)  # список путей/URL
```

Form field + widget (упрощённо):

```
# forms.py
from django import forms
from django.core.files.storage import default_storage

class MultiImageWidget(forms.ClearableFileInput):
    allow_multiple_selected = True

class MultiImageFormField(forms.Field):
    widget = MultiImageWidget

    def to_python(self, data):
        # data — может быть список загруженных файлов и/или строк с уже загруженными путями
        if not data:
            return []
        if not isinstance(data, list):
            data = [data]
        return data

    def clean(self, data):
        files = []
        existing = []
        for item in data:
            if hasattr(item, 'read'):  # UploadedFile
                files.append(item)
            elif isinstance(item, str):
                existing.append(item)
        saved = []
        for f in files:
            name = default_storage.save('galleries/' + f.name, f)
            saved.append(default_storage.url(name))  # или name, в зависимости от ваших нужд
        return existing + saved
```

Использование в ModelForm и админке:

```
class ProductForm(forms.ModelForm):
    images = MultiImageFormField(required=False)

    class Meta:
        model = Product
        fields = '__all__'

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.instance and self.instance.pk:
            self.fields['images'].initial = self.instance.images

    def save(self, commit=True):
        self.instance.images = self.cleaned_data['images']
        return super().save(commit=commit)
```

Widget-шаблон должен показывать превью существующих изображений и иметь <input type="file" multiple>. В админке можно заменить change_form_template или подключить Media (JS/CSS) к ModelAdmin.

Советы по реализации:
- Обязательно подумайте о том, как удалять неиспользуемые файлы (старые файлы в storage) — реализуйте удаление при перезаписи/удалении записи.
- Храните в JSON либо относительные пути (storage.save возвращает имя), либо абсолютные URL — решите один стиль и используйте consistently.
- Валидация: ограничение типа файла, размера, количества изображений.
- Для порядка добавьте поле order в JSON (список объектов), либо храните порядок в массиве путей.
- Для удобства покройте фронтенд превью/drag&drop и AJAX-загрузку (Dropzone.js, FilePond и т. п.). Тогда вы можете полностью скрыть инлайны и работать через API.

Пакеты и инструменты
- django-filer — популярный файл-менеджер, всё ещё используется.
- django-versatileimagefield, django-imagekit, sorl-thumbnail — для обработки/кеширования/thumbnail'ов.
- django-photologue — старый, но иногда полезен.
- Для админ-множественной загрузки полюбому придётся писать немного JS или интегрировать Dropzone/FilePond. Готовых «под ключ» современного пакета для одного поля-галереи я не встречал, поэтому обычно делают либо инлайны, либо кастомный виджет + endpoint.

Резюме / рекомендация
- Если проект серьёзный: используйте отдельную модель GalleryImage + интерфейс загрузки (инлайн или AJAX). Это лучший баланс гибкости и надёжности.
- Если нужен быстрый и компактный вариант: JSONField + кастомный form-field/widget, но будьте готовы к ручному управлению storage/удалением/версированием.

Если хотите, могу:
- прислать готовый пример widget + template + JS (Dropzone/FilePond) и view для AJAX-загрузки, чтобы в админке получить «одно поле» для галереи, или
- помочь написать конкретную модель/форму по вашей задаче (пример структуры данных, требования: порядок, подписи, макс. количество, хранение URL/имени файла, миниатюры и т. п.).
Похожие вопросы