Как загрузить несколько изображений с флагами в Django

Я создаю веб-страницу, на которой автор блога может писать контент и загружать изображения. Используя поле изображения, я позволяю автору загружать несколько изображений и с помощью некоторой логики Javascript отображаю изображения перед загрузкой. Под каждым изображением у меня есть флажок, который указывает, является ли это изображение главным в посте (например, первым в слайд-шоу и используется в качестве миниатюры самого поста).

На этой странице я показываю две формы с общей кнопкой отправки, и она работает нормально.

Мои проблемы начинаются, когда я пытаюсь сохранить изображение, а также указать, является ли оно основным.

Моя модель изображений имеет несколько полезных методов, свойство thumbnail для Django admin и метод save, который будет устанавливать все изображения для поста в false, перед редактированием/добавлением нового main_image. Мне пришлось включить логику аргумента фиксации из-за моих экспериментов. Пожалуйста, не обращайте на это внимания. Модель включает 3 поля: изображение, флаг main и внешний ключ к посту следующим образом:


class PostImages(models.Model):
    """
    Post images for the blog app.
    """
    image = models.ImageField(
        verbose_name=_("Изображение"),
        upload_to='blog/images/',
        blank=False,
        null=False,
        default='blog/images/default.jpg'
    )
    post = models.ForeignKey(
        to=Post,
        on_delete=models.CASCADE,
        verbose_name=_('Статия')
    )
    main_image = models.BooleanField(
        default=False,
        verbose_name=_('Основно изображение')
    )

    class Meta:
        verbose_name = _('Изображение')
        verbose_name_plural = _('Изображения')
        permissions = [
            ('can_manage_post_image', _('Може да управлява изображенията на публикациите')),
            ('can_add_post_image', _('Може да добавя изображения към публикациите')),
        ]

    def __str__(self):
        return f"{self.post.title} - {self.image}"

    def get_absolute_url(self):
        return f"/blog/post/image/{self.id}"

    def get_edit_url(self):
        return f"/blog/post/image/{self.id}/edit"

    def get_delete_url(self):
        return f"/blog/post/image/{self.id}/delete"

    def get_image(self):
        return mark_safe(f'<img src="{self.image.url}" width="100" height="100" />')

    def get_image_url(self):
        return self.image.url

    @property
    def thumbnail_preview(self):
        if self.image:
            return mark_safe('<img src="{}" width="400" height="auto" />'.format(self.image.url))
        return ''

    def save(self, *args, **kwargs):
        if self.main_image and kwargs.get("commit", True):
            PostImages.objects.filter(post=self.post).update(main_image=False)
        kwargs.pop("commit", None)
        super().save(*args, **kwargs)

Я также использую модельную форму. В модельной форме я не запрашиваю должность, потому что она еще не создана. Моя идея заключалась в том, чтобы заполнить должность на этапе сохранения, следуя процессу:

  1. сохранить пост
  2. сохранить изображения Для меня это логичный ход, но зная, сколько времени это стоило мне до сих пор -> возможно, это не лучший ход.
class PostImagesForm(forms.ModelForm):
    """
    Form for uploading images for posts.
    """
    image = forms.ImageField(
        label='Снимка',
        widget=forms.ClearableFileInput(
            attrs={
                'class': 'form-control',
                'multiple': True,
            }
        )
    )

    main_image = forms.BooleanField(
        label='Основна снимка',
        required=False,
        widget=forms.CheckboxInput(
            # attrs={
            #     'class': 'form-control',
            # }
        )
    )

    class Meta:
        model = PostImages
        fields = ['image', 'main_image']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['image'].required = False

    def save(self, commit=True):
        """
        Save the form and return the created or edited post.
        """
        post_image = super().save(commit=False)
        post_image.save(commit=commit)
        return post_image

    def clean(self):
        """
        Clean the form and return the cleaned data.
        """
        cleaned_data = super().clean()
        return cleaned_data

Пока что, так .... хорошо, я полагаю.

Вот вопросы:

  • Как сказать Django, что я загружаю пару полей (файл и чекбокс)?
  • Как передать экземпляр поста изображению?
  • Почему мои файлы запроса пустые?
  • Как я должен группировать пары изображение-чекбокс? Это что-то внутри html?
  • Я пытался использовать саму форму, но она пропускает экземпляр поста.

    Я пытался перебрать все файлы, но они пустые.

    Я попытался перебрать поля формы, но не вижу всех загруженных файлов.

    Если кому-то это когда-нибудь понадобится:

    Я обнаружил несколько проблем, которыми, как мне кажется, было бы полезно поделиться.

    1. Я использую несколько форм под одной кнопкой отправки. В какой-то момент я заигрался и удалил enctype="multipart/form-data" из тега формы. (oops)
    2. Мой javascript имел странное поведение, потому что он подкачивал изображение как "data:sjdfhsdkjbfksd" в мой идентификатор для чекбокса. И снова ошибка новичка. Я обнаружил ее, когда начал приклеивать console.log() ко всему и вся.
    3. Если вы похожи на меня (основной язык - Python, а JS - нечто случайно известное), помните - в браузере есть фантастический отладчик для JS, под sources.
    4. Области действия JS странные, они не такие, как в Python. При использовании отладчика вы увидите, что циклы, выполняющие действия и вызывающие функции событий, всегда будут делать все вне функций событий, а затем переходить к вашей функции событий. Вы можете передавать вещи путем переназначения в операторах var, но будьте готовы к тому, что на вас накричит парень из JS.
    Вернуться на верх