Тег {% crispy %} с формами

django-crispy-forms реализует класс FormHelper, который определяет поведение рендеринга формы. Помощники дают вам возможность управлять атрибутами формы и ее макетом, делая это программным способом с помощью Python. Таким образом, вы пишете как можно меньше HTML, а вся логика остается в файлах форм и представлений.

Основы

В остальной части этого документа мы будем использовать следующий пример формы, чтобы показать, как использовать помощника. Эта форма отвечает за сбор некоторой информации о пользователе:

class ExampleForm(forms.Form):
    like_website = forms.TypedChoiceField(
        label = "Do you like this website?",
        choices = ((1, "Yes"), (0, "No")),
        coerce = lambda x: bool(int(x)),
        widget = forms.RadioSelect,
        initial = '1',
        required = True,
    )

    favorite_food = forms.CharField(
        label = "What is your favorite food?",
        max_length = 80,
        required = True,
    )

    favorite_color = forms.CharField(
        label = "What is your favorite color?",
        max_length = 80,
        required = True,
    )

    favorite_number = forms.IntegerField(
        label = "Favorite number",
        required = False,
    )

    notes = forms.CharField(
        label = "Additional notes or feedback",
        required = False,
    )

Давайте посмотрим, как работают хелперы, шаг за шагом, с пояснениями на некоторых примерах. Сначала вам нужно импортировать FormHelper:

from crispy_forms.helper import FormHelper

Ваш хелпер может быть переменной уровня класса или переменной уровня экземпляра, если вы не знаете, что это значит, вам стоит прочитать статью «Be careful how you use static variables in forms». Как правило, если вы не собираетесь манипулировать FormHelper в своем коде, например, в представлении, вам следует использовать статический хелпер, в противном случае вам следует использовать хелпер уровня экземпляра. Если вы все еще не понимаете тонких различий между ними, используйте хелпер уровня экземпляра, потому что вы не будете страдать от побочных эффектов. Поскольку в следующих шагах я покажу вам, как работать с помощником формы, мы создадим помощника уровня экземпляра. Вот как вы это сделаете:

from crispy_forms.helper import FormHelper

class ExampleForm(forms.Form):
    [...]
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()

Как вы видите, вам нужно вызвать конструктор базового класса с помощью super и переопределить конструктор. Этот помощник не устанавливает никаких атрибутов формы, поэтому он бесполезен. Давайте посмотрим, как установить некоторые основные атрибуты FormHelper:

from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit

class ExampleForm(forms.Form):
    [...]
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_id = 'id-exampleForm'
        self.helper.form_class = 'blueForms'
        self.helper.form_method = 'post'
        self.helper.form_action = 'submit_survey'

        self.helper.add_input(Submit('submit', 'Submit'))

Обратите внимание, что мы импортируем класс Submit, который является объектом макета. Позже мы подробно рассмотрим, что такое объекты компоновки, а пока скажем, что это добавляет кнопку отправки в нашу форму, чтобы люди могли отправить свой опрос.

Мы также сделали немного вспомогательной магии. FormHelper имеет список атрибутов, которые могут быть установлены, которые влияют в основном на атрибуты формы. Наша форма будет иметь DOM id id-exampleForm, DOM CSS class blueForms, она будет использовать http POST для отправки информации, а ее действие будет установлено на reverse(submit_survey).

Давайте посмотрим, как отобразить форму в шаблоне. Предположим, что у нас есть форма в контексте шаблона как example_form, мы отобразим ее, делая:

{% load crispy_forms_tags %}
{% crispy example_form example_form.helper %}

Обратите внимание, что тег {% crispy %} ожидает два параметра: сначала переменную формы, а затем помощника. В данном случае мы используем FormHelper, прикрепленный к форме, но вы также можете создать экземпляр FormHelper и передать его в качестве контекстной переменной. В большинстве случаев вы захотите использовать прикрепленный хелпер. Обратите внимание, что если вы назовете свой FormHelper атрибут helper, вам нужно будет сделать только:

{% crispy form %}

Это именно тот html, который вы получите:

<form action="/submit/survey/" class="uniForm blueForms" method="post" id="id-exampleForm">
    <div style='display:none'>
        <input type="hidden" name="csrfmiddlewaretoken" value="a643fab735d5ce6377ff456e73c4b1af" />
    </div>
    <fieldset>
        <legend></legend>
        <div id="div_id_like_website" class="ctrlHolder">
            <label for="id_like_website" class="requiredField">¿Do you like this website?<span class="asteriskField">*</span></label>
            <ul>
                <li><label for="id_like_website_0"><input checked="checked" name="like_website" value="1" id="id_like_website_0" type="radio" class="radioselect" /> Yes</label></li>
                <li><label for="id_like_website_1"><input value="0" type="radio" class="radioselect" name="like_website" id="id_like_website_1" /> No</label></li>
            </ul>
        </div>
        <div id="div_id_favorite_food" class="ctrlHolder">
            <label for="id_favorite_food" class="requiredField">What is you favorite food?<span class="asteriskField">*</span></label>
            <input id="id_favorite_food" class="textinput textInput" type="text" name="favorite_food" maxlength="80" required="required" />
        </div>
        <div id="div_id_favorite_color" class="ctrlHolder">
            <label for="id_favorite_color" class="requiredField">What is you favorite color?<span class="asteriskField">*</span></label>
            <input id="id_favorite_color" class="textinput textInput" type="text" name="favorite_color" maxlength="80" required="required" />
        </div>
        <div id="div_id_favorite_number" class="ctrlHolder">
            <label for="id_favorite_number">Favorite number</label>
            <input id="id_favorite_number" type="text" name="favorite_number" class="textinput textInput" />
        </div>
        <div id="div_id_notes" class="ctrlHolder">
            <label for="id_notes">Additional notes or feedback</label>
            <input id="id_notes" type="text" name="notes" class="textinput textInput" />
        </div>
    </fieldset>
    <div class="buttonHolder">
        <input type="submit" name="submit" value="Submit" class="submit submitButton" id="submit-id-submit" />
    </div>
</form>

В результате вы получите форму, отображенную в виде HTML с удивительными элементами. В частности…

  • Открывающие и закрывающие теги формы, с id, классом, действием и методом, установленными как в помощнике:

    <form action="/submit/survey/" class="uniForm blueForms" method="post" id="id-exampleForm">
        [...]
    </form>
    
  • Контроль CSRF в Django:

    <div style='display:none'>
        <input type="hidden" name="csrfmiddlewaretoken" value="a643fab735d5ce6377ff456e73c4b1af" />
    </div>
    
  • Кнопка «Отправить»:

    <div class="buttonHolder">
        <input type="submit" name="submit" value="Submit" class="submit submitButton" id="submit-id-submit" />
    </div>
    

Манипулирование помощником в представлении

Давайте посмотрим, как мы можем изменить любое вспомогательное свойство в представлении:

@login_required()
def inbox(request, template_name):
    example_form = ExampleForm()
    redirect_url = request.GET.get('next')

    # Form handling logic
    [...]

    if redirect_url is not None:
        example_form.helper.form_action = reverse('submit_survey') + '?next=' + redirectUrl

    return render_to_response(template_name, {'example_form': example_form}, context_instance=RequestContext(request))

Мы изменяем вспомогательное свойство form_action в случае, если представление было вызвано с параметром next GET.

Рендеринг нескольких форм с помощью помощников

Часто нас спрашивают: «Как отобразить две или более формы с соответствующими хелперами, используя тег {% crispy %}, без того, чтобы теги <form> отображались дважды?». Легко, нужно установить свойство хелпера form_tag на << 3 >>> в каждом хелпере:

self.helper.form_tag = False

Затем вам нужно будет написать немного html-кода вокруг форм:

<form action="{% url 'submit_survey' %}" class="uniForm" method="post">
    {% crispy first_form %}
    {% crispy second_form %}
</form>

Вы можете прочитать список Атрибуты помощника, которые вы можете установить и для чего они нужны.

Изменить „*“ обязательных полей

Если вам не нравится использование * (звездочка) для обозначения обязательных полей, у вас есть два варианта:

  • Звездочки имеют набор классов asteriskField. Поэтому вы можете скрыть их с помощью правила CSS:

    .asteriskField {
        display: none;
    }
    
  • Переопределите шаблон field.html на собственный.

Сделайте хрустящие формы громкими

По умолчанию, когда crispy-forms сталкивается с ошибками, он молча завершает работу, записывает их в журнал и продолжает работу, если это возможно. Переменная настроек под названием CRISPY_FAIL_SILENTLY была добавлена для того, чтобы вы могли управлять этим поведением. Если вы хотите, чтобы при разработке в режиме отладки вместо протоколирования возникали исключения, сообщающие о том, что происходит, вы можете установить значение:

CRISPY_FAIL_SILENTLY = not DEBUG

Изменение классов по умолчанию в crispy-forms <input>

Поля Django генерируют классы по умолчанию, crispy-forms обрабатывает их и добавляет другие классы для совместимости с CSS-фреймворками.

Например, CharField генерирует <input class="textinput" .... Но в форме универа нам нужно, чтобы класс был textInput (с заглавной «I»), поэтому crispy-forms оставляет его как <input class="textinput textInput".... Все официальные пакеты шаблонов обрабатываются автоматически, но возможно вы интегрируете новый CSS фреймворк или собственный фреймворк вашей компании с crispy-forms и вам нужно изменить преобразования по умолчанию. Для этого вам нужно использовать переменную настроек под названием CRISPY_CLASS_CONVERTERS, которая должна быть словарем Python:

CRISPY_CLASS_CONVERTERS = {'textinput': "textinput inputtext"}

Например, эта настройка будет генерировать <input class"textinput inputtext" .... Ключ словаря textinput - это класс по умолчанию Django, значение - то, чем вы хотите его заменить, в данном случае мы оставляем textinput.

Рендеринг формы в коде Python

Иногда бывает полезно вывести форму, использующую crispy-forms, внутри кода Python, например, в представлении Django, для этого есть хороший помощник render_crispy_form. Прототипом метода является render_crispy_form(form, helper=None, context=None). Вы можете использовать его следующим образом. Не забудьте передать CSRF-токен в метод-помощник, используя контекстный словарь, если вы хотите, чтобы отрисованная форма могла быть отправлена.

Рецепт валидации AJAX

Вы можете захотеть проверить crispy-форму через AJAX, чтобы повторно отобразить все возникающие ошибки формы. Один из способов сделать это - установить представление, которое проверяет форму и отображает ее html с помощью render_crispy_form. Затем этот html возвращается в AJAX-запрос клиента. Рассмотрим пример.

Наш код на стороне сервера может быть:

from django.template.context_processors import csrf
from crispy_forms.utils import render_crispy_form

@json_view
def save_example_form(request):
    form = ExampleForm(request.POST or None)
    if form.is_valid():
        # You could actually save through AJAX and return a success code here
        form.save()
        return {'success': True}


    ctx = {}
    ctx.update(csrf(request))
    form_html = render_crispy_form(form, context=ctx)
    return {'success': False, 'form_html': form_html}

Я использую декоратор jsonview из django-jsonview.

Обратите внимание, что вы должны предоставить render_crispy_form необходимый токен CSRF, иначе он не будет работать.

На стороне клиента использование jQuery будет выглядеть следующим образом:

var example_form = '#example-form';

$.ajax({
    url: "{% url 'save_example_form' %}",
    type: "POST",
    data: $(example_form).serialize(),
    success: function(data) {
        if (!(data['success'])) {
            // Here we replace the form, for the
            $(example_form).replaceWith(data['form_html']);
        }
        else {
            // Here you can show the user a success message or do whatever you need
            $(example_form).find('.success-message').show();
        }
    },
    error: function () {
        $(example_form).find('.error-message').show()
    }
});

Предупреждение

При замене html формы необходимо привязать события с помощью метода live или << 1 >>> jQuery.

Горизонтальные формы Bootstrap3

../_images/bootstrap3_horizontal_form.jpg

Способ создания горизонтальных форм в Bootstrap версии 3 заключается в установке некоторых классов col-lg-X в метках и divs, оборачивающих поля. Это означало бы много хлопот с обновлением объектов макета для настройки этих классов, слишком многословный подход. Вместо этого были добавлены некоторые атрибуты FormHelper, чтобы помочь вам легко достичь этого. Вам нужно будет установить только три атрибута:

helper.form_class = 'form-horizontal'
helper.label_class = 'col-lg-2'
helper.field_class = 'col-lg-8'
helper.layout = Layout(
    'email',
    'password',
    'remember_me',
    StrictButton('Sign in', css_class='btn-default'),
)

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

Встраиваемые формы Bootstrap3

../_images/bootstrap3_inline_form.jpg

Способ создания встроенных форм в Bootstrap версии 3 следующий:

helper.form_class = 'form-inline'
helper.field_template = 'bootstrap3/layout/inline_field.html'
helper.layout = Layout(
    'email',
    'password',
    'remember_me',
    StrictButton('Sign in', css_class='btn-default'),
)

Если вам нужно установить атрибуты в поле, вы должны использовать InlineField вместо Field:

from crispy_forms.bootstrap import InlineField

helper.layout = Layout(
    InlineField('email', readonly=True),
    'password',
    [...]
)
Вернуться на верх