Django. Набір форм.

Основи Python и Django. -> Набір форм.

Набір форм. Formset.

Набір форм - це абстрактний шар для роботи з безліччю форм на одній сторінці. Його можна порівняти з таблицею даних.

Ви можете дозволити користувачеві створювати декілька об’єктів моделі за один раз.

Припустимо, у нас

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


class Cv(models.Model): 
    name = models.CharField(max_length=255)
    user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True)


class WorkExperience(models.Model):
    cv = models.ForeignKey(Cv, on_delete=models.CASCADE, blank=True)
    position = models.CharField(max_length=255, blank=True)
    company = models.CharField(max_length=255, blank=True)

class Certification(models.Model):
    cv = models.ForeignKey(Cv, on_delete=models.CASCADE, blank=True)
    name = models.CharField(max_length=255, blank=True)
    provider = models.CharField(max_length=255, blank=True)

Ми пов’язали одне резюме користувача з безліччю моделей WorkExperience і Certification.

Адмін інтерфейс.

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.contrib import admin
from .models import *

class WorkExperienceInine(admin.TabularInline):
    model = WorkExperience

class CertificationAdmin(admin.TabularInline):
    model = Certification


class CvAdmin(admin.ModelAdmin):
    list_display = ['name']
    inlines = [WorkExperienceInine, CertificationAdmin]

admin.site.register(Cv, CvAdmin)

start page

Як видно в адмінці ми вже маємо можливість за допомогою набору форм створити відразу кілька об’єктів, прив’язаних до об’єкта резюме.

Спробуймо реалізувати подібне на фронтенді.

Створимо класи форм у в forms.py.

from django.forms import ModelForm
from .models import *

class CvForm(ModelForm):
    class Meta:
        model = Cv
        fields = ['name', 'user']

class WorkExperienceForm(ModelForm):
    class Meta:
        model = WorkExperience#
        fields = ['cv', 'position', 'company']

class Certification(ModelForm):
    class Meta:
        model = Certification
        fields = ['cv', 'name', 'provider']

Передаємо одну форму в вьюшке.

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.shortcuts import render
from .forms import *

def create_cv(request):
    form = CvForm()
    return render(request,'create_cv.html',{'form': form})

Роутинг.

from django.conf.urls import url
from django.contrib import admin
from main.views import create_cv

urlpatterns = [
    url(r'', create_cv),
    url(r'^admin/', admin.site.urls),
]

Шаблон.

<html>
<head></head>
<body>
    <form action="" method="POST">
        {% csrf_token %}
        {{ form }}
        <input type="submit" value="Submit" />
    </form>
</body>
</html>

start page

Тепер збережемо форму та переправимо користувача на іншу вьюшку.

...
def create_cv(request):
    form = CvForm(request.POST or None)
    if request.method == 'POST':
        cv = form.save()
        return HttpResponseRedirect(reverse('create_options', kwargs={'id':cv.pk}))
    return render(request,'create_cv.html',{'form': form})

def create_options(request,id):
    cv = Cv.objects.get(pk=id)
    form = WorkExperienceForm()
    return render(request,'create_options.html', {'cv': cv, 'form': form})

Роутинг

from django.conf.urls import url
from django.contrib import admin
from main.views import create_cv, create_options
from django.urls import path

urlpatterns = [
    path('', create_cv),
    path(r'create/options/<int:id>/', create_options, name='create_options'),
    url(r'^admin/', admin.site.urls),
]

Шаблон другої сторінки.

<html>
<head></head>
<body>
    <h1>Create options for {{ cv.name }}</h1>
    <form action="" method="POST">
        {{ form }}
        <input type="submit" value="Submit" />
    </form>
</body>
</html>

start page

Тепер спробуємо додати кілька форм як набір.

from django.forms.formsets import formset_factory

def create_options(request,id):
    cv = Cv.objects.get(pk=id)
    expirience_formset = formset_factory(WorkExperienceForm, extra=2)
    return render(request,'create_options.html', {'cv': cv, 'expirience_formset': expirience_formset})

Шаблон.

<html>
<head></head>
<body>
    <h1>Create options for {{ cv.name }}</h1>
    <form action="" method="POST">
        {% for form in expirience_formset %}
            {{ form.as_p }}
        {% endfor %}
        <input type="submit" value="Submit" />
    </form>
</body>
</html>

Заповнимо початковими значеннями з об’єкта резюме.

def create_options(request,id):
    cv = Cv.objects.get(pk=id)
    ExpirienceFormSet = formset_factory(WorkExperienceForm, extra=2)
    expirience_formset = ExpirienceFormSet(initial=[
          {'cv': cv }
     ])

start page

Як бачимо, заповнилася тільки перша форма і тепер їх стало 3.

Змінимо трохи структуру та визначимо збереження постом.

if request.method == 'POST':
    expirience_formset = ExpirienceFormSet(request.POST)
    for form in expirience_formset:
        form.save()
else:
    expirience_formset = ExpirienceFormSet(initial=[
        {'cv': cv },
        {'cv': cv }
    ])

При збереженні помилку.

Start page

Необхідно було додати шаблон поля менеджера форм.

    ...
    {{ expirience_formset.management_form }}
    {% for form in expirience_formset %}
        {{ form.as_p }}
    {% endfor %}
    ...

При перезбереженні додаються дублікати та не редагуються вже створені записи.

Для цього спробувавши ввести у форму ідентифікатори

class WorkExperienceForm(ModelForm):
    id = forms.CharField(label='Id', max_length=100)
    class Meta:
        model = WorkExperience
        fields = ['cv', 'position', 'company', 'id']

Перевизначимо метод save.

def save(self, commit=False, *args, **kwargs):
    m = super(WorkExperienceForm, self).save(commit=False, *args, **kwargs)
    if not m.id:
        ex = WorkExperience()
        ex.position = m.position
        ex.cv = m.cv
    else:
        ex = WorkExperience.objects.get(pk=m.id)
        ex.position = m.position

    ex.save()
    return ex.cv

Додамо об’єкти у завірюсі у формсет.

def create_options(request,id):
    cv = Cv.objects.get(pk=id)
    objs = []
    for exp in WorkExperience.objects.filter(cv=cv):
        objs.append({'cv': exp.cv, 'position': exp.position, 'company': exp.company, 'id': exp.id})
    ExpirienceFormSet = formset_factory(WorkExperienceForm,extra=0)
    objs.append({'cv': cv})

    if request.method == 'POST':
        expirience_formset = ExpirienceFormSet(request.POST)
        for form in expirience_formset:
            if form.is_valid():
                cv = form.save()
                return HttpResponseRedirect(reverse('create_options', kwargs={'id':cv.pk}))
    else:
        expirience_formset = ExpirienceFormSet(initial=objs)

    return render(request,'create_options.html', {'cv': cv, 'expirience_formset': expirience_formset})

Виведемо приховані поля формою.

    {% for form in expirience_formset %}
        {{ form.as_p }}
        {% for hidden in form.hidden_fields %}
            {{ hidden }}
        {% endfor %}
    {% endfor %}