Pull to refresh

Немного подробностей про Class Based Views, ч.4

Reading time 5 min
Views 53K
Здравствуйте! В продолжении серии статей про Class Based Views (далее CBV) переходим к разделу, посвященному редактированию объектов. В данной статье мы рассмотрим четыре класса с говорящими названиями: FormView, CreateView, UpdateView, DeleteView.

Часть 1, часть 2, часть 3, часть 4


Создание и обработка формы с помощью CBV

Для ряда действий, будь то регистрация или авторизация на сайте, публикация новости, комментария или добавление товара в магазине, невозможно обойтись без форм. В качестве универсального инструмента создания форм в Django выступает класс FormView. В самом просто случае для создания работоспособной формы достаточно лишь передать ему лишь ссылку на класс, описывающий необходимую форму:
from django.views.generic.edit import FormView

class RegisterForm(FormView):
    form_class = Register
    success_url = '/thanks/'


или передать нужные данные непосредственно экземпляру класса FormView в нашем urlconf:

url(r'^register/$', FormView.as_view(form_class=Register, success_url='/thanks/')

Note: Пример синтетический и, разумеется, в таком виде использовать для страницы регистрации не получится.

Класс формы, который необходимо обработать, возвращается методом get_form_class. По умолчанию данный метод возвращает атрибут form_class, которому мы присваивали класс нашей формы в примере выше. Мы можем переопределить данный метод, если нам требуется более сложная логика в определении класса формы.
Метод get_success_url возвращает url ссылку, на которую будет осуществляться переход после успешной обработки формы. По умолчанию данный метод возвращает атрибут success_url.
Для указания полям формы значений по умолчанию мы можем передать их непосредственно в атрибут initial, представляющий из себя словарь, ключи которого должны иметь имена требуемых полей формы. Значение данного атрибута возвращается по умолчанию методом get_initial.
Часто возникает необходимость передать в форму определенные данные, например объект пользователя или заранее определенный список разделов. Для данного действия подходит метод get_form_kwargs. При переопределении данного метода необходимо соблюдать осторожность и не переписать случайно данные, передаваемые в форму по умолчанию. Среди них:

def get_form_kwargs(self):
        """
        Возвращает словарь аргументов для экземпляра формы
        """
        kwargs = {'initial': self.get_initial()}
        if self.request.method in ('POST', 'PUT'):
            kwargs.update({
                'data': self.request.POST,
                'files': self.request.FILES,
            })
        return kwargs


Чтобы избежать потери этих данных мы должны сначала получить словарь из родительского класса, затем добавить в него требуемые данные:

class ProductForm(FormView):

    def get_form_kwargs(self):
        kwargs = super(ProductForm, self).get_form_kwargs()
        kwargs.update({
            'sections': Section.objects.filter(is_active=True)
        })
        return kwargs


Для получения класса нашей формы мы можем использовать метод get_form, который по умолчанию возвращает класс формы, указанный через метод form_class, с переданным ему словарем метода get_form_kwargs:

def get_form(self, form_class):
        return form_class(**self.get_form_kwargs())


При обработке формы, в случае успеха, Django вызывает метод form_valid нашего отображения. По умолчанию данный метод осуществляет редирект по ссылке, возвращаемой методом get_success_url.
В случае, когда форме переданы некорректные данные вызывается метод form_invalid, который по умолчанию возвращает пользователя обратно на страницу формы, передавая ей объект со списком ошибок валидации.

class CreatePost(FormView):
    form_class = PostForm
    template_name = 'create_post.html'
    success_url = '/success/'

    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        super(CreatePost, self).dispatch(request, *args, **kwargs)

    def form_valid(self, form):
        Post.objects.create(**form.cleaned_data)
        return redirect(self.get_success_url())


Теперь рассмотрим разные сферы применения форм более подробно.

Создание нового экземпляра объекта

С помощью примера выше мы можем с легкостью создать новый объект статьи, однако в Django есть средства, позволяющие создать объект с еще большей легкостью, это класс CreateView. Для применения данного класса, передаваемая ему форма должна наследоваться от ModelForm:

from django import forms

class NewsForm(forms.ModelForm):
    class Meta(object):
        model = News
        exclude = ('status',)


Теперь мы можем передать объект данной формы в наше отображение. Пример отображения почти аналогичен предыдущему:

from django.views.generic.edit import CreateView

class CreateNews(CreateView):
    form_class = NewsForm
    template_name = 'create_news.html'
    succes_url = '/success/'

    def form_valid(self, form):
        Post.objects.create(**form.cleaned_data)
        return redirect(self.get_success_url())


Если необходимо провести более сложные действия перед сохранением модели (проставить внешние ключи, добавить дополнительную информацию), то более подходящим способом это сделать будет следующий пример (спасибо пользователю marazmiki):


def form_valid(self, form):
        # Мы используем ModelForm, а его метод save() возвращает инстанс
        # модели, связанный с формой. Аргумент commit=False говорит о том, что
        # записывать модель в базу рановато.
        instance = form.save(commit=False)

        # Теперь, когда у нас есть несохранённая модель, можно ей чего-нибудь
        # накрутить. Например, заполнить внешний ключ на auth.User. У нас же
        # блог, а не анонимный имижборд, правда?
        instance.user = request.user

        # А теперь можно сохранить в базу
        instance.save() 

        return redirect(self.get_success_url())


Note: Определение метода form_valid может быть излишне, так как CreateView наследует ModelFormMixin, который сохраняет экземпляр объекта из формы автоматически.

Для обработки ошибок также используется метод form_invalid, функциональность которого аналогична оной класса FormView.

Обновление экземпляра объекта

Основное отличие класса UpdateView от CreateView, это передача экземпляра изменяемого объекта атрибуту object данного класса, в остальном данные классы идентичны. Для редактирования нам достаточно передать в url первичный ключ или slug изменяемого объекта:

# Маршрут в urlconf
url(r'^item/(?P<pk>\d+)/edit/$', ItemUpdate.as_view()),


# Отображение в views.py

from django.views.generic.edit import UpdateView

class ItemUpdate(UpdateView):
    form_class = ItemForm
    model = Item
    template_name = 'create_item.html'
    success_url = '/success/'


# Форма

form django import forms

class NewsForm(forms.ModelForm):
    class Meta(object):
        model = Item
        exclude = ('status',)


# Модель

from django.db import models

class Item(models.Model):
    name = models.CharField(max_length=32, verbose_name=u'Название')
    description = models.TextField(verbose_name=u'Описание')
    status = models.BooleanField(default=True)

    def __unicode__(self):
        return self.name


Описание формы и модели для класса CreateView выглядит аналогичным образом.

Удаление экземпляра объекта

Логика отображения для удаления объекта несколько отличается от рассмотренных предыдущих классов в данной статье. Удаление объекта, в целях безопасности, доступно лишь после передачи post или delete запроса. В случае get запроса будет отображена страница с подтверждением удаления объекта. В самом минималистичном варианте она может представлять из себя форму с кнопкой отправки и csrf токеном:

<!-- item_confirm_delete.html -->
<form action="" method="post">
    {% csrf_token %}
    <button>Удалить</button>
</form>


Отображение может выглядеть следующим образом:
from django.views.generic.edit import DeleteView

class ItemDelete(DeleteView):
    model = Item
    template_name = 'item_confirm_delete.html'
    success_url = '/success/'


Разумеется если вам потребуются соответсвующие проверки на наличие авторизации у пользователей или соответствующих прав, то вы можете сделать это используя декораторы для метода dispatch или добавить свою логику проверки:

from django.views.generic.edit import DeleteView

class ItemDelete(DeleteView):
    model = Item
    template_name = 'item_confirm_delete.html'
    success_url = '/success/'

    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        super(ItemDelete, self).dispatch(request, *args, **kwargs)


В данной статье мы рассмотрели работу с формами и управлением объектами, если у вас возникнут какие-либо вопросы, то я или другие читатели, надеюсь, смогут на них ответить. Также прошу сообщить обо всех найденных ошибках или неточностях и предложения по добавлению информации в статью, если я что-то пропустил. Спасибо, что прочитали статью и желаю счастливых выходных =)
Tags:
Hubs:
+21
Comments 14
Comments Comments 14

Articles