흔들림 꼬리로 이미지 업로드

51592 단어 djangowagtailpython
Wagtail CMS를 사용하고 이미지 업로드 필드를 추가하려는 개발자의 경우

Heads up - This is an update of my earlier post Image Uploads in Wagtail Forms which was written for Wagtail v1.12, this new post is written for v2.10/v2.11.


문제는 귀하의 팀은 Wagtail CMS의 사용자 정의 폼 생성기를 좋아해서 사람들이 폼과 함께 그림을 업로드할 수 있도록 합니다.
솔루션 - CMS 관리자에서 필드를 편집할 때 선택할 수 있는 새 양식 필드 유형을 정의합니다. 이 필드 유형은 이미지 업로드라고 합니다.이 필드는 보기에 일반 업로드 필드로 표시되고 Wagtail 이미지 시스템처럼 파일 형식과 크기를 제한해야 합니다.


대상: 이미지 업로드 필드를 추가하면 폼 보기에 표시됩니다.

흔들림, 이미지 및 형식


만약 당신이 이곳의 기본 지식을 알고 있다면 건너뛰세요.
WagtailDjango Web Framework 위에 구축된 컨텐츠 관리 시스템(CMS)입니다.내가 Wagtail을 좋아하는 것은 Django 생태계와 일하는 방식을 포옹하는 데 있다.그것은 또한 매우 좋은 관리 인터페이스를 가지고 있어 사용자가 쉽게 내용과 상호작용할 수 있다.
Wagtail에는 이미지 업로드, 저장 및 서비스에 사용할 수 있는 내장된 인터페이스와 프레임워크가 있습니다.이것은 Wagtail Images로 적절하게 명명되었습니다. Using Images in Templates 또는 Advanced Image Usage에 대한 문서를 보고 더 많은 정보를 얻을 수 있습니다.
Wagtail은 사용자가 관리 인터페이스에서 자신의 폼을 구축할 수 있도록 하는 아주 좋은 Form Builder 모듈을 첨부했다.이 폼들은 일련의 필드가 있을 수 있다. 예를 들어 텍스트, 여러 줄 텍스트, 전자메일, URL, 복선상자 등이다. 이 필드는 웹 사이트 앞에서 볼 수 있는 폼 페이지를 구성한다.사용자는 기본값, 이 필드가 필요한지, 이 필드와 관련된 도움말 텍스트를 사용자 정의할 수 있습니다.

저희가 시작하기 전에.


우리가 사물을 바꾸기 시작하기 전에 중요한 것은 네가 아래의 항목을 완성하는 것이다.
  • Wagtail v2.10.x 또는 v2.11.x 를 시작하고 main documentation 에 따라 실행합니다.

  • Wagtailforms module가 설치되어 실행 중이며 폼을 사용할 수 있습니다.'wagtail.contrib.forms'을(를) INSTALLED_APPS에 추가해야 합니다.
  • Wagtail에서 양식에 이미지 업로드 필드 추가


    우리의 변화를 기획하다


    우리는 다음과 같은 사용자 상호작용을 실현하기를 희망한다.
  • 관리 인터페이스는 기존 폼을 편집하고 새 폼을 정상적으로 만드는 능력을 제공해야 한다.
  • 양식 페이지를 편집할 때 필드 유형 필드에 이미지 업로드라는 새 드롭다운 옵션이 있어야 합니다.
  • 양식 페이지 보기는 관리자가 정의한 이미지 업로드 필드마다 파일 업로드 필드가 있어야 합니다.
  • 양식 페이지 보기는 Wagtail 이미지와 동일한 제한이 있는 이미지를 받아들여야 합니다(10mb 미만, PNG/JPG/GIF*만).
  • 필드가 관리에서 필수로 정의되면 양식 페이지 보기에 이미지가 필요합니다.
  • 이미지가 유효하면 Wagtail Images 영역에 저장해야 합니다.
  • 이미지 링크를 양식 제출(양식 응답이라고도 함)에 저장하여 전자 우편이나 보고서에 나타나도록 해야 합니다.
  • * 기본 GIF 지원은 Wagtail에서 매우 기본적입니다. 애니메이션 GIF를 지원하려면 Animated GIFs에 관한 문서를 읽어야 합니다.

    1. AbstractFormField 클래스 확장

    FormPage류의 정의를 포함하는 모델 파일에는 FormField류의 정의가 있어야 한다.원래 정의에서 AbstractFormField classFORM_FIELD_CHOICES 의 고정 메타그룹을 사용합니다.필드 _ 형식을 덮어쓰는 추가 그룹을 선택해야 합니다.
    # models.py
    
    from wagtail.contrib.forms.models import AbstractForm, AbstractFormField, FORM_FIELD_CHOICES
    
    class FormField(AbstractFormField):
    
        field_type = models.CharField(
            verbose_name='field type',
            max_length=16,
            choices=list(FORM_FIELD_CHOICES) + [('image', 'Upload Image')]
        )
    
        page = ParentalKey('FormPage', related_name='form_fields', on_delete=models.CASCADE)
    
    
    위의 코드에서 우리가wagtail에서 원시 코드FORM_FIELD_CHOICES를 가져온 것을 볼 수 있습니다.contrib.형식모형.그리고 목록으로 변환해서 새로운 필드 형식을 추가한 다음 field_type 필드의choices 매개 변수에 사용합니다.
    네가 이렇게 할 때, 너는 make a migration, and run that migration 필요하다.테스트해 보세요. 관리자의 폼은 현재 이런 종류를 선택할 수 있지만, 다른 일을 많이 할 수 없습니다.

    2, 확장 FormBuilder 클래스


    모델 파일에서 확장된formbuilder 클래스를 만들어야 합니다.원본 정의에서 FormBuilder class 각 FormPage 실례에 저장된 field_type 목록을 바탕으로 폼을 구축합니다.우리는 문서의 Adding a custom field type에 관한 예시를 따를 수 있다.
    이 방법은 필드 이름 (이 예에서 "image") 을 기반으로 방법 이름 create_image_field 에 따라 이 방법을 호출하고 Django form widget 의 실례를 되돌려야 합니다.Wagtail을 사용하여 사용자 정의 이미지 필드를 만드는 것이 아니라 자신만의 WagtailImageField 를 사용할 수 있습니다.
    # models.py
    
    from wagtail.contrib.forms.forms import FormBuilder
    
    from wagtail.images.fields import WagtailImageField
    
    
    class CustomFormBuilder(FormBuilder):
    
        def create_image_field(self, field, options):
            return WagtailImageField(**options)
    
    
    위의 코드에서, 우리는 FormBuilderwagtail.contrib.forms.forms 에서 WagtailImageField 을 가져온 다음, 새로운 클래스로 우리의 사용자 정의 wagtail.images.fields 를 만들었다.우리는createdFormBuilder를 되돌려주고 제공하는 모든 옵션을 전달하는 방법create_date_field을 추가했습니다.

    3. WagtailImageField를 사용하여 FormPage 클래스 설정


    이 단계는 매우 간단합니다. 우리는 FormPage 모델의 CustomFormBuilder 정의를 덮어쓰기를 원합니다.이것은 매우 교묘한 방식입니다. Wagtail은 당신이 사용하는form_생성기를 덮어쓸 수 있습니다.
    # models.py
    
    from wagtail.contrib.forms.models import AbstractForm
    
    class FormPage(AbstractForm):
    
        form_builder = CustomFormBuilder
    
        #... rest of the FormPage definition
    
    

    4. 양식 페이지 템플릿을 업데이트하여 파일 데이터를 적용합니다.


    폼 페이지 보기에 form_builder 표시가 있어야 합니다. Wagtail이 제안한 구현은 폼에 파일 데이터를 제출하는 것을 허락하지 않습니다.
    <!-- templates/form_page.html -->
    
    {% extends "base.html" %}
    
    {% load wagtailcore_tags %}
    
    {% block content %}
    
        {{ self.intro }}
    
        <form action="{% pageurl self %}" method="POST" enctype="multipart/form-data">
            {% csrf_token %}
            {{ form.as_p }}
            <input type="submit" />
        </form>
    
    {% endblock %}
    
    기본 폼과 유일한 차이점은 폼 속성에 <form /> 을 추가했다는 것이다.만약 당신이 이렇게 하지 않는다면, 당신은 요청을 통해 어떤 파일도 보내지 않을 것이며, 이유를 제시하는 오류도 없을 것입니다.
    우리가 왜 이렇게 해야 하는지에 대한 더 많은 정보는 Django Docs File Uploads 페이지를 보고 깊이 있게 이해할 수 있습니다 enctype form attribute on MDN.

    5. 이미지 업로드를 위한 컬렉션 선택 기능 추가


    관리 인터페이스를 통해 이미지를 업로드할 때 각 이미지를 하나의 집합에 추가할 수 있는 옵션이 있습니다. 기본값은'루트'입니다. 이것은 이미지의 폴더와 같습니다.
    우리는 양식 제출에 업로드된 모든 이미지를 "Root"에 저장하는 것이 아니라, 사용자가 각 양식의 이미지를 어느 페이지에 추가할지 선택할 수 있도록 하려고 합니다. enctype="multipart/form-data"
    # models.py
    
    from wagtail.admin.edit_handlers import FieldPanel
    from wagtail.core.models import Collection
    
    
    class FormPage(AbstractForm):
    
        form_builder = CustomFormBuilder
    
        # other fields...
    
        uploaded_image_collection = models.ForeignKey(
            'wagtailcore.Collection',
            null=True,
            blank=True,
            on_delete=models.SET_NULL,
        )
    
        # content_panels...
    
        settings_panels = AbstractForm.settings_panels + [
            FieldPanel('uploaded_image_collection')
        ]
    
    
        def get_uploaded_image_collection(self):
            """
            Returns a Wagtail Collection, using this form's saved value if present,
            otherwise returns the 'Root' Collection.
            """
            collection = self.uploaded_image_collection
            return collection or Collection.get_first_root_node()
    
    
    위의 코드에서 우리는 Collection모델을 가져왔고 Collections모델에 FormPage라는 새로운 필드를 추가했다. 이것은 uploaded_image_collection모델의 ForeignKey relation이다.
    페이지에서 검색하고 get_first_root_node 방법을 통해 루트 집합으로 돌아가는 방법도 추가했습니다. (Wagtail 집합은 트리 구조를 사용하여 트리 구조를 정의하기 때문입니다.)
    이 코드 단계를 완료하려면 make a migration, and run that migration 이 필요합니다.

    6. 검증 후 이미지 (파일) 데이터 처리


    이제 FormPage 클래스'wagtailCore.Collection'를 덮어씁니다.process_form_submission method의 원시적인 정의는 처리process_form_submission 데이터 이외의 어떠한 내용의 개념이 없다.그것은 폼 제출 실례에 저장하기 위해 정리된 데이터를 JSON으로 변환하기만 하면 된다.모든 필드를 훑어보고 Wagtail Image Field의 모든 실례를 찾은 다음 데이터를 가져와 이 파일 데이터로 새 Wagtail 이미지를 만들고 응답에 이미지에 저장된 링크를 만듭니다.
    # models.py
    
    import json
    from os.path import splitext
    
    from django.core.serializers.json import DjangoJSONEncoder
    
    from wagtail.images import get_image_model
    
    
    class FormPage(AbstractForm):
    
        form_builder = CustomFormBuilder
    
        # fields & panels definitions...
    
        @staticmethod
        def get_image_title(filename):
            """
            Generates a usable title from the filename of an image upload.
            Note: The filename will be provided as a 'path/to/file.jpg'
            """
    
            if filename:
                result = splitext(filename)[0]
                result = result.replace('-', ' ').replace('_', ' ')
                return result.title()
            return ''
    
        def process_form_submission(self, form):
            """
            Processes the form submission, if an Image upload is found, pull out the
            files data, create an actual Wgtail Image and reference its ID only in the
            stored form response.
            """
    
            cleaned_data = form.cleaned_data
    
            for name, field in form.fields.items():
                if isinstance(field, WagtailImageField):
                    image_file_data = cleaned_data[name]
                    if image_file_data:
                        ImageModel = get_image_model()
    
                        kwargs = {
                            'file': cleaned_data[name],
                            'title': self.get_image_title(cleaned_data[name].name),
                            'collection': self.get_uploaded_image_collection(),
                        }
    
                        if form.user and not form.user.is_anonymous:
                            kwargs['uploaded_by_user'] = form.user
    
                        image = ImageModel(**kwargs)
                        image.save()
                        # saving the image id
                        # alternatively we can store a path to the image via image.get_rendition
                        cleaned_data.update({name: image.pk})
                    else:
                        # remove the value from the data
                        del cleaned_data[name]
    
            submission = self.get_submission_class().objects.create(
                form_data=json.dumps(form.cleaned_data, cls=DjangoJSONEncoder),
                page=self,
            )
    
            # important: if extending AbstractEmailForm, email logic must be re-added here
            # if self.to_address:
            #    self.send_mail(form)
    
            return submission
    
    이 옵션을 적용하면 업로드 이미지가 있는 폼 응답을 제출할 수 있습니다.

    여기에는 몇 가지 주의해야 할 점이 있다.
  • 사용 request.POST 은 Wagtail에서 사용하는 이미지 모델을 얻는 가장 좋은 실천 방법이다.
  • get_image_model 파일 데이터(모든 파일에 대한)를 포함하고 있으며, Django 양식 모듈은 이를 실현합니다.JSON 해석기는 파일 데이터를 해석할 수 없으므로 이러한 경우 URL 또는 이미지 ID로 처리해야 합니다.
  • staticmethodcleaned_data는 당신이 원하는 것처럼 보일 수 있습니다. 저는 대소문자를 빼고 파일 제목과 대소문자를 만들었습니다.이렇게 할 필요는 없지만, 삽입 get_image_title 시 제목이 있는지 확인해야 합니다.
  • FormPage가 실제로 확장되고 있는 경우WagtailImage, 양식을 제출하고 이메일을 보내는 경우send_mail code를 추가해야 합니다.
  • 이미지에 대한 JSON 정렬식 참조를 저장하려면 AbstractEmailForm 를 사용해야 하므로 파일 데이터가 작동하지 않습니다.
  • 7. 양식 제출 목록을 통해 이미지 보기


    마지막 단계는 제출 목록 보기에서 이 그림을 쉽게 볼 수 있는 방법을 제공하는 것입니다. customising how this list generates 를 통해 완성할 수 있습니다.
    우리는 이미 이미지의 id를 저장했지만, cleaned_data.update 를 사용하고 싶습니다. 이것은 Wagtail Documentation 에서 상세하게 소개한 매우 유용한 함수입니다.이 함수는 템플릿 보조 프로그램을 시뮬레이션하지만 Python에서 사용할 수 있습니다.기본적으로 URL은 상대적입니다. (http/https 또는 필드를 포함하지 않습니다.) 이것은 전자메일로 보내는 링크가 작용하지 않는다는 것을 의미합니다.만약 이것이 문제라면, 네가 그것을 어떻게 가장 잘 해결할 것인지를 결정해라.
    # models.py
    
    from django.utils.html import format_html
    from django.urls import reverse
    
    from wagtail.contrib.forms.views import SubmissionsListView
    
    
    class CustomSubmissionsListView(SubmissionsListView):
    
        def get_context_data(self, **kwargs):
            context = super().get_context_data(**kwargs)
    
            if not self.is_export:
                # generate a list of field types, the first being the injected 'submission date'
                field_types = ['submission_date'] + [field.field_type for field in self.form_page.get_form_fields()]
                data_rows = context['data_rows']
    
                ImageModel = get_image_model()
    
                for data_row in data_rows:
    
                    fields = data_row['fields']
    
                    for idx, (value, field_type) in enumerate(zip(fields, field_types)):
                        if field_type == 'image' and value:
                            image = ImageModel.objects.get(pk=value)
                            rendition = image.get_rendition('fill-100x75|jpegquality-40')
                            preview_url = rendition.url
                            url = reverse('wagtailimages:edit', args=(image.id,))
                            # build up a link to the image, using the image title & id
                            fields[idx] = format_html(
                                "<a href='{}'><img alt='Uploaded image - {}' src='{}' />{} ({})</a>",
                                url,
                                image.title,
                                preview_url,
                                image.title,
                                value
                            )
    
            return context
    
    class FormPage(AbstractForm):
    
        form_builder = CustomFormBuilder
        submissions_list_view_class = CustomSubmissionsListView # added
    
    
    위의 코드에 새 image.get_rendition 를 추가했습니다. 이것은 Wagtail CustomSubmissionsListView 을 확장했습니다. 이 Wagtail은 사용자 정의 SubmissionsListView 방법을 가지고 있습니다.이 방법에서, 우리는 원시get_context_data를 호출하여 생성된 상하문 데이터를 얻는다.
    그런 다음 사용자에게 제출 (내보내기 대신) 을 표시하고 각 제출 행을 매핑하며 값이 이미지인지 확인하고 일부 HTML을 사용하여 표시된 값을 업데이트합니다.이 HTML에는 이미지 미리 보기 (변환 사용) 가 포함되며, 제목과 id를 기반으로 한 설명이 포함되며, 이 설명은 이 이미지의 관리 페이지를 가리키는 링크에 있습니다.

    완료 중


    양식 모델입니다.py 파일은 현재 아래와 같이 보입니다.
    전체 코드 세그먼트
    # models.py
    
    import json
    from os.path import splitext
    
    from django.core.serializers.json import DjangoJSONEncoder
    from django.db import models
    from django.utils.html import format_html
    from django.urls import reverse
    
    from modelcluster.fields import ParentalKey
    
    from wagtail.admin.edit_handlers import (
        FieldPanel,
        FieldRowPanel,
        InlinePanel,
        MultiFieldPanel,
        PageChooserPanel,
        StreamFieldPanel,
    )
    from wagtail.core.models import Collection
    from wagtail.contrib.forms.forms import FormBuilder
    from wagtail.contrib.forms.models import AbstractForm, AbstractFormField, FORM_FIELD_CHOICES
    from wagtail.contrib.forms.views import SubmissionsListView
    from wagtail.images import get_image_model
    from wagtail.images.fields import WagtailImageField
    
    
    class CustomFormBuilder(FormBuilder):
    
        def create_image_field(self, field, options):
            return WagtailImageField(**options)
    
    
    class CustomSubmissionsListView(SubmissionsListView):
    
        def get_context_data(self, **kwargs):
            context = super().get_context_data(**kwargs)
    
            if not self.is_export:
    
                # generate a list of field types, the first being the injected 'submission date'
                field_types = ['submission_date'] + [field.field_type for field in self.form_page.get_form_fields()]
                data_rows = context['data_rows']
                ImageModel = get_image_model()
    
                for data_row in data_rows:
    
                    fields = data_row['fields']
    
                    for idx, (value, field_type) in enumerate(zip(fields, field_types)):
                        if field_type == 'image' and value:
                            image = ImageModel.objects.get(pk=value)
                            rendition = image.get_rendition('fill-100x75|jpegquality-40')
                            preview_url = rendition.url
                            url = reverse('wagtailimages:edit', args=(image.id,))
                            # build up a link to the image, using the image title & id
                            fields[idx] = format_html(
                                "<a href='{}'><img alt='Uploaded image - {}' src='{}' />{} ({})</a>",
                                url,
                                image.title,
                                preview_url,
                                image.title,
                                value
                            )
    
            return context
    
    
    class FormField(AbstractFormField):
    
        field_type = models.CharField(
            verbose_name='field type',
            max_length=16,
            choices=list(FORM_FIELD_CHOICES) + [('image', 'Upload Image')]
        )
    
        page = ParentalKey('FormPage', related_name='form_fields', on_delete=models.CASCADE)
    
    
    class FormPage(AbstractForm):
    
        form_builder = CustomFormBuilder
        submissions_list_view_class = CustomSubmissionsListView
    
        # ... fields
    
        uploaded_image_collection = models.ForeignKey(
            'wagtailcore.Collection',
            null=True,
            blank=True,
            on_delete=models.SET_NULL,
        )
    
        content_panels = AbstractForm.content_panels + [
            # ... panels
        ]
    
        settings_panels = AbstractForm.settings_panels + [
            FieldPanel('uploaded_image_collection')
        ]
    
        def get_uploaded_image_collection(self):
            """
            Returns a Wagtail Collection, using this form's saved value if present,
            otherwise returns the 'Root' Collection.
            """
            collection = self.uploaded_image_collection
    
            return collection or Collection.get_first_root_node()
    
        @staticmethod
        def get_image_title(filename):
            """
            Generates a usable title from the filename of an image upload.
            Note: The filename will be provided as a 'path/to/file.jpg'
            """
    
            if filename:
                result = splitext(filename)[0]
                result = result.replace('-', ' ').replace('_', ' ')
                return result.title()
            return ''
    
        def process_form_submission(self, form):
            """
            Processes the form submission, if an Image upload is found, pull out the
            files data, create an actual Wgtail Image and reference its ID only in the
            stored form response.
            """
    
            cleaned_data = form.cleaned_data
    
            for name, field in form.fields.items():
                if isinstance(field, WagtailImageField):
                    image_file_data = cleaned_data[name]
                    if image_file_data:
                        ImageModel = get_image_model()
    
                        kwargs = {
                            'file': cleaned_data[name],
                            'title': self.get_image_title(cleaned_data[name].name),
                            'collection': self.get_uploaded_image_collection(),
                        }
    
                        if form.user and not form.user.is_anonymous:
                            kwargs['uploaded_by_user'] = form.user
    
                        image = ImageModel(**kwargs)
                        image.save()
                        # saving the image id
                        # alternatively we can store a path to the image via image.get_rendition
                        cleaned_data.update({name: image.pk})
                    else:
                        # remove the value from the data
                        del cleaned_data[name]
    
            submission = self.get_submission_class().objects.create(
                form_data=json.dumps(form.cleaned_data, cls=DjangoJSONEncoder),
                page=self,
            )
    
            # important: if extending AbstractEmailForm, email logic must be re-added here
            # if self.to_address:
            #    self.send_mail(form)
    
            return submission
    
    
    이제 양식에 CMS 편집기로 정의된 이미지 업로드 필드가 하나 이상 있을 수 있습니다.이 이미지는 관리 중인 이미지 섹션에서 제공되며 Wagtail의 나머지 섹션에서 사용할 수 있습니다.색인 검색, 템플릿에서의 사용, 압축된 크기의 다양한 이미지의 URL 등 Wagtail 이미지의 모든 장점을 얻을 수 있습니다.

    양식 응답의 관리 보기에 get_context_data 에서 저장한 내용이 표시됩니다.
    만약 본문에서 문제가 있거나 맞춤법 오류가 발견되면 저에게 알려주십시오.Torchbox의 우수한 팀과 Wagtail의 모든 개발자들에게 감사드리며, 이 놀라운 도구를 만들어 주셔서 감사합니다.주연Wagtail repo on Github을 통해 Wagtail에 대한 지지를 표시합니다.
    Github의 모든 코드 변경 사항을 image-uploads branch 에서 확인할 수 있습니다.
    내 친구 아담이 그걸 증명해줘서 고마워.

    좋은 웹페이지 즐겨찾기