Загрузка файла формы трясогузки - PullRequest
0 голосов
/ 18 апреля 2020

У меня проблема при добавлении поля загрузки файла в Wagtail Forms Builder. Я получаю эту ошибку:

Тип исключения: TypeError

Значение исключения: Объект типа InMemoryUploadedFile не является JSON сериализуемым

Это мой код:

class FormField(AbstractFormField):
    CHOICES = FORM_FIELD_CHOICES + (('fileupload', 'File Upload'),)
    page = ParentalKey('FormPage', on_delete=models.CASCADE, related_name='form_fields')

    field_type = models.CharField(
        verbose_name='field type',
        max_length=16,
        # use the choices tuple defined above
        choices=CHOICES
    )

    api_fields = [
        APIField('page'),
    ]

class CustomFormBuilder(FormBuilder):

    def create_fileupload_field(self, field, options):
        return forms.FileField(**options)

class FormPage(AbstractEmailForm):
    form_builder = CustomFormBuilder
    intro = RichTextField(blank=True)
    thank_you_text = RichTextField(blank=True)
    content_panels = AbstractEmailForm.content_panels + [
        FieldPanel('intro', classname="full"),
        InlinePanel('form_fields', label="Form fields"),
        FieldPanel('thank_you_text', classname="full"),
        MultiFieldPanel([
            FieldRowPanel([
                FieldPanel('from_address', classname="col6"),
                FieldPanel('to_address', classname="col6"),
            ]),
            FieldPanel('subject'),
        ], "Email"),
    ]
    # Export fields over the API
    api_fields = [
        APIField('intro'),
        APIField('thank_you_text'),
    ]

Это мой шаблон:

{% load wagtailcore_tags %}
<html>
    <head>
        <title>{{ page.title }}</title>
    </head>
    <body>
        <h1>{{ page.title }}</h1>
        {{ page.intro|richtext }}
        <form action="{% pageurl page %}" method="POST" enctype="multipart/form-data">
            {% csrf_token %}
            {{ form.as_p }}
            <input type="submit">
        </form>
    </body>
</html>

Wagtail версии 2.8 .1 Django версия 3.0.5 есть идеи с этой проблемой?

1 Ответ

0 голосов
/ 21 апреля 2020

Основная проблема заключается в том, что вы пытаетесь сохранить загруженный файл как JSON. * Wagtail FormBuilder не сохраняет части данных отправки в качестве своих собственных моделей БД, но вместо этого комплекты имеют значение json (например, {'field-a': 'value'}) и сохраняют их в виде строки в базе данных.

Причина Дело в том, что хранимые данные гибки для каждой страницы и могут меняться со временем в зависимости от настроек страницы.

Таким образом, чтобы полностью реализовать поле загрузки файла, вам нужно где-то сохранить эти файлы, а также решить несколько других проблем.

1. Где хранить файл

  • В зависимости от настроек Django вам необходимо получить базовое c представление о том, как хранить файлы в Django
  • Вам нужно будет создать новую модель, в которой будут храниться эти файлы, см. FormUploadedFile в приведенном ниже примере.
  • В зависимости от вашего варианта использования, вам нужно будет учитывать несколько файлов, загруженных в каждой отправке формы. , поскольку пользовательский интерфейс FormPage позволяет пользователям создавать несколько полей любого типа, следовательно, может быть полезно сохранить ссылку на имя поля, в котором оно хранится.

2. Что сохранить в JSON как ссылку на файл

  • Это может быть простая ссылка на pk (первичный ключ), как показано в примере кода ниже.
  • Вы можете Вы хотите добавить более продвинутую связь между моделью загрузки файла и моделью FormSubmission для лучшей целостности данных
  • Вам нужно будет переопределить process_form_submission в вашей модели FormPage, вы можете увидеть исходный код здесь https://github.com/wagtail/wagtail/blob/master/wagtail/contrib/forms/models.py#L195

3. Чтение файла и что представлять как этот файл в списке представлений формы

  • Вы можете изменить вывод get_data из записей FormSubmission, вы можете сделать это, добавив пользовательский * Модель 1044 * (см. Код ниже), однако это будет вместо существующей модели (поэтому ваши существующие представления больше не будут видны без какого-либо перехода или другого обходного пути).
  • Вы можете увидеть оригинал get_data метод здесь https://github.com/wagtail/wagtail/blob/master/wagtail/contrib/forms/models.py#L48
  • Раздел документации Wagatil содержит хорошую информацию о настройке списка представлений

Пример кода

Вот грубое рабочее ПО C, с которого можно начать, надеюсь, это поможет.

import json

from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django import forms

from modelcluster.fields import ParentalKey

from wagtail.contrib.forms.models import (
    AbstractEmailForm, AbstractFormField, AbstractFormSubmission, FORM_FIELD_CHOICES)
from wagtail.contrib.forms.forms import FormBuilder
from wagtail.contrib.forms.views import SubmissionsListView


class FormField(AbstractFormField):
    page = ParentalKey('FormPage', related_name='form_fields', on_delete=models.CASCADE)
    field_type = models.CharField(
        verbose_name='field type',
        max_length=16,
        choices=FORM_FIELD_CHOICES + (('fileupload', 'File Upload'),)
    )


class CustomFormBuilder(FormBuilder):

    def create_fileupload_field(self, field, options):
        return forms.FileField(**options)


class CustomSubmissionsListView(SubmissionsListView):
    """
    further customisation of submission list can be done here
    """
    pass


class CustomFormSubmission(AbstractFormSubmission):
    # important - adding this custom model will make existing submissions unavailable
    # can be resolved with a custom migration

    def get_data(self):
        """
        Here we hook in to the data representation that the form submission returns
        Note: there is another way to do this with a custom SubmissionsListView
        However, this gives a bit more granular control
        """

        file_form_fields = [
            field.clean_name for field in self.page.specific.get_form_fields()
            if field.field_type == 'fileupload'
        ]

        data = super().get_data()

        for field_name, field_vale in data.items():
            if field_name in file_form_fields:
                # now we can update the 'representation' of this value
                # we could query the FormUploadedFile based on field_vale (pk)
                # then return the filename etc.
                pass

        return data


class FormUploadedFile(models.Model):
    file = models.FileField(upload_to="files/%Y/%m/%d")
    field_name = models.CharField(blank=True, max_length=254)


class FormPage(AbstractEmailForm):

    form_builder = CustomFormBuilder
    submissions_list_view_class = CustomSubmissionsListView

    # ... other fields (image, body etc)

    content_panels = AbstractEmailForm.content_panels + [
        # ...
    ]

    def get_submission_class(self):
        """
        Returns submission class.
        Important: will make your existing data no longer visible, only needed if you want to customise
        the get_data call on the form submission class, but might come in handy if you do it early

        You can override this method to provide custom submission class.
        Your class must be inherited from AbstractFormSubmission.
        """

        return CustomFormSubmission

    def process_form_submission(self, form):
        """
        Accepts form instance with submitted data, user and page.
        Creates submission instance.

        You can override this method if you want to have custom creation logic.
        For example, if you want to save reference to a user.
        """

        file_form_fields = [field.clean_name for field in self.get_form_fields() if field.field_type == 'fileupload']

        for (field_name, field_value) in form.cleaned_data.items():
            if field_name in file_form_fields:
                uploaded_file = FormUploadedFile.objects.create(
                    file=field_value,
                    field_name=field_name
                )

                # store a reference to the pk (as this can be converted to JSON)
                form.cleaned_data[field_name] = uploaded_file.pk

        return self.get_submission_class().objects.create(
            form_data=json.dumps(form.cleaned_data, cls=DjangoJSONEncoder),
            page=self,
        )

...