클래스 기반 보기 및 바삭한 양식이 포함된 Django 인라인 양식

27163 단어 djangopythonwebdev
최근에 저는 Django 프로젝트 중 하나에서 인라인 폼셋을 사용했고 그것이 어떻게 작동하는지 매우 좋아했습니다. 클래스 기반 보기, inline formsetscrispy-forms jQuery 플러그인과의 통합django-dynamic-formset 예제를 공유하기로 결정했습니다. 주제를 조사했을 때 많은 예제를 찾지 못했고 Django 문서는 이 문제에 대해 광범위하지 않기 때문에 이 솔루션을 시도하려는 사람들과 미래의 나를 위해 이 게시물을 작성했습니다.

우선, 인라인 폼셋을 사용하는 이유:
사용자가 한 페이지에서 참조된 객체의 생성/업데이트 보기에서 외래 키 객체를 통해 관련 항목을 생성하고 업데이트할 수 있습니다.

여러 언어로 된 제목을 가질 수 있는 컬렉션이 있지만 사용자가 제공할 제목 번역이 정확히 몇 개인지 모른다고 가정합니다. 컬렉션 생성 양식에 새 행을 추가하는 '추가' 버튼을 클릭하기만 하면 사용자가 필요한 만큼 많은 제목을 추가할 수 있도록 하고 싶습니다.
이것이 우리 모델의 모습입니다.

models.py:

from django.db import models
from django.contrib.auth.models import User


class Collection(models.Model):
    subject = models.CharField(max_length=300, blank=True)
    owner = models.CharField(max_length=300, blank=True)
    note = models.TextField(blank=True)
    created_by = models.ForeignKey(User,
        related_name="collections", blank=True, null=True,
        on_delete=models.SET_NULL)

    def __str__(self):
        return str(self.id)


class CollectionTitle(models.Model):
    """
    A Class for Collection titles.

    """
    collection = models.ForeignKey(Collection,
        related_name="has_titles", on_delete=models.CASCADE)
    name = models.CharField(max_length=500, verbose_name="Title")
    language = models.CharField(max_length=3)



이제 CollectionTitle에 대한 양식과 부모 모델 Collection 및 FK 관련 모델 CollectionTitle을 포함하는 formset(inlineformset_factory 사용)를 만들어 보겠습니다.

form.py

from django import forms
from .models import *
from django.forms.models import inlineformset_factory


class CollectionTitleForm(forms.ModelForm):

    class Meta:
        model = CollectionTitle
        exclude = ()

CollectionTitleFormSet = inlineformset_factory(
    Collection, CollectionTitle, form=CollectionTitleForm,
    fields=['name', 'language'], extra=1, can_delete=True
    )



다음으로 이 폼셋을 CollectionCreate 보기에 추가합니다.

views.py:

from .models import *
from .forms import *
from django.views.generic.edit import CreateView, UpdateView
from django.urls import reverse_lazy
from django.db import transaction

class CollectionCreate(CreateView):
    model = Collection
    template_name = 'mycollections/collection_create.html'
    form_class = CollectionForm
    success_url = None

    def get_context_data(self, **kwargs):
        data = super(CollectionCreate, self).get_context_data(**kwargs)
        if self.request.POST:
            data['titles'] = CollectionTitleFormSet(self.request.POST)
        else:
            data['titles'] = CollectionTitleFormSet()
        return data

    def form_valid(self, form):
        context = self.get_context_data()
        titles = context['titles']
        with transaction.atomic():
            form.instance.created_by = self.request.user
            self.object = form.save()
            if titles.is_valid():
                titles.instance = self.object
                titles.save()
        return super(CollectionCreate, self).form_valid(form)

    def get_success_url(self):
        return reverse_lazy('mycollections:collection_detail', kwargs={'pk': self.object.pk})



CollectionUpdate 보기는 get_context_data()에서 인스턴스 개체를 전달해야 한다는 점을 제외하면 비슷해 보입니다.

views.py:

def get_context_data(self, **kwargs):
        data = super(CollectionUpdate, self).get_context_data(**kwargs)
        if self.request.POST:
            data['titles'] = CollectionTitleFormSet(self.request.POST, instance=self.object)
        else:
            data['titles'] = CollectionTitleFormSet(instance=self.object)
        return data



다음으로 폼셋 내부에 필드로 렌더링될 CollectionForm을 생성해야 합니다. 바삭한 양식에는 Div 또는 HTML에 대해 있는 것과 같은 양식 집합에 대한 레이아웃 개체가 없기 때문에 이것은 간단하지 않습니다.
best solution(대단히 감사합니다!)는 사용자 정의 크리스피 레이아웃 개체를 만드는 것입니다.

custom_layout_object.py:

from crispy_forms.layout import LayoutObject, TEMPLATE_PACK
from django.shortcuts import render
from django.template.loader import render_to_string

class Formset(LayoutObject):
    template = "mycollections/formset.html"

    def __init__(self, formset_name_in_context, template=None):
        self.formset_name_in_context = formset_name_in_context
        self.fields = []
        if template:
            self.template = template

    def render(self, form, form_style, context, template_pack=TEMPLATE_PACK):
        formset = context[self.formset_name_in_context]
        return render_to_string(self.template, {'formset': formset})



다음 단계는 템플릿을 추가하여 formset을 렌더링하는 것입니다.
정확히 내가 렌더링하고 싶은 것은: 각각의 새 제목에 대해 - '이름' 및 '언어' 필드가 있는 행과 행을 제거하기 위한 '제거' 버튼(컬렉션을 업데이트할 때 데이터베이스의 데이터 삭제) 및 하나 행 아래에 있는 추가 버튼 - 새 제목에 대해 다른 행을 추가합니다.
더 많은 행을 동적으로 추가하기 위해 django-dynamic-formset jQuery 플러그인을 사용하고 있습니다.
모든 인라인 formset 사례(예: 하나의 Form에 여러 인라인 formset를 추가하는 경우)에 대해 하나의 formset 템플릿을 갖기 위해 접두사( docs )를 사용하는 것이 좋습니다. Formset 접두사는 참조된 클래스의 related_name입니다. 제 경우에는 'has_titles'입니다.

formset.html:


{% load crispy_forms_tags %}
<table>
{{ formset.management_form|crispy }}

    {% for form in formset.forms %}
            <tr class="{% cycle 'row1' 'row2' %} formset_row-{{ formset.prefix }}">
                {% for field in form.visible_fields %}
                <td>
                    {# Include the hidden fields in the form #}
                    {% if forloop.first %}
                        {% for hidden in form.hidden_fields %}
                            {{ hidden }}
                        {% endfor %}
                    {% endif %}
                    {{ field.errors.as_ul }}
                    {{ field|as_crispy_field }}
                </td>
                {% endfor %}
            </tr>
    {% endfor %}

</table>
<br>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js">
</script>
<script src="{% static 'mycollections/libraries/django-dynamic-formset/jquery.formset.js' %}">
</script>
<script type="text/javascript">
    $('.formset_row-{{ formset.prefix }}').formset({
        addText: 'add another',
        deleteText: 'remove',
        prefix: '{{ formset.prefix }}',
    });
</script>


마지막으로 기존 레이아웃 개체와 사용자 정의 Formset 개체를 함께 사용하여 CollectionForm에 대한 자체 양식 레이아웃을 구성할 수 있습니다.

form.py:

from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field, Fieldset, Div, HTML, ButtonHolder, Submit
from .custom_layout_object import *


class CollectionForm(forms.ModelForm):

    class Meta:
        model = Collection
        exclude = ['created_by', ]

    def __init__(self, *args, **kwargs):
        super(CollectionForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_tag = True
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-md-3 create-label'
        self.helper.field_class = 'col-md-9'
        self.helper.layout = Layout(
            Div(
                Field('subject'),
                Field('owner'),
                Fieldset('Add titles',
                    Formset('titles')),
                Field('note'),
                HTML("<br>"),
                ButtonHolder(Submit('submit', 'save')),
                )
            )



collection_create.html:

{% extends "mycollections/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="container">
    <div class="card">
        <div class="card-header">
            Create collection
        </div>
        <div class="card-body">
             {% crispy form %}
        </div>
    </div>
</div>
{% endblock content %}


이제 모든 것이 제자리에 있으며 저장 버튼을 한 번만 누르면 한 페이지에서 한 형식으로 컬렉션과 제목을 만들 수 있습니다.



이 게시물의 소스 코드는 here 입니다.

인라인 양식 세트 솔루션을 분류하는 데 도움이 된 this awesome blog post에 대한 크레딧입니다.

좋은 웹페이지 즐겨찾기