solovyov.net

Django newforms

4 min read · django

Сама по себе Джанга замечательна именно тем, что многие, очень многие вещи в ней делать намного проще, чем где-либо ещё. То есть практически любой компонент сделан так, что использовать его невероятно удобно. Но вот обработка форм (в Джанге ею занимались так называемые манипуляторы) была… не слишком удобная для меня лично. ;-)

И где-то примерно в январе в trunk‘е Джанги появилась новая подсистема - newforms. Она позволяет многие вещи упростить очень сильно, и она сделана именно в духе самого фреймворка в том смысле, что она действительно проще и удобнее чего-либо другого. Самая большая проблема новых формочек ;) заключается в том, что документации как таковой по ней не было вообще - только доктесты (хотя они были достаточно хорошо написаны, чтобы понять начала пользования системой).

Чуть погодя появилась документация, которая толком не давала хороших примеров работы с newforms, и я в это время большинство форм (которые у меня не получались достаточно простые, чтобы можно было легко применить form_for_model/form_for_instance, которые генерируют форму из модели автоматом) делал руками. Чуть позже я узнал (из форумов и исходников Джанги), каким образом можно с помощью form_for_model создать такую форму, которая не полностью повторяет модель (сначала callback_function, а вот не так давно добавили параметр fields).

Но вот недавно с помощью форумов softwaremaniacs.org и тех самых исходников Джанги я-таки разобрался с созданием довольно сложных форм (когда нужно создать модель со ссылкой на текущего залогинившегося пользователя). Чтобы долго не распинаться, просто покажу код:

BaseRoomForm = forms.form_for_model(Room, 
    fields=('name', 'description', 'image', 'products'))
 
class RoomForm(BaseRoomForm):
    def __init__(self, user, *args, **kwargs):
        super(RoomForm, self).__init__(*args, **kwargs)
        self.fields['image'] = forms.Field(label=u'Image', widget=forms.FileInput())
        self.user = user

    def save(self):
        room = forms.save_instance(self, Room(user=self.user), 
            fields=('name', 'description', 'products'))
        room.save_image_file(self.cleaned_data['image']['filename'],
            self.cleaned_data['image']['content'])
        return room

Вот так вот замечательно можно наследоваться от формы, созданной с помощью form_for_model. :) Спасибо Ивану за подсказку. :)

Собственно, основная штука здесь - это save_instance. Ему надо передать инстанс, который будет сохраняться - а так как в данном случае происходит создание, то вот я и передаю новосозданный объект. :) fields я передаю, чтоб избежать передачи картинки в save_instance - он их ещё нормально обрабатывать не умеет. Вызов этой формы происходит примерно так:

if request.POST:
    data = request.POST.copy()
    data.update(request.FILES)
    form = RoomForm(request.user, data)
    if form.is_valid():
        room = form.save()
else:
    form = RoomForm(request.user)

Тут, конечно, надо учитывать, что форма непроста ещё и тем, что картинка передаётся. Без неё всё было бы проще (просто form = RoomForm(request.user, request.POST)). Возникает только один вопрос - таким ведь образом form_for_instance никак не используешь. Тут нужно просто заменить процесс создания формы form = RoomForm(request.user) на form = RoomForm(request.user, initial=room.__dict__) - передать начальные значения.

Хотелось бы ещё отметить одну штуку, которую сегодня нашёл Илья - если в модели есть ForeignKey, то form_for_model абсолютно корректно для него создаёт SelectBox, корректно заполняет его значениями, но не устанавливает стартовое значение, которое передаётся в initial. Решение довольно простое, но с одной загвоздкой - нужно в init класса установить initial нужного поля из набора base_fields в id. Т.е., если бы пользователя можно было бы выбирать, получилось бы что-то вроде этого:

self.base_fields['user'].initial = room.user.id

Я так до конца и не разобрался, почему оно не ловит само и почему никак не помогает установка initial в fields['user']. :)

Вот так вкратце и обстоят дела. Есть, конечно, некоторые недочёты и недоработки, однако в общем это всё невероятно облегчает мне жизнь. Чего и всем остальным желаю! :)

If you like what you read — subscribe to my Twitter, I always post links to new posts there. Or, in case you're an old school person longing for an ancient technology, put a link to my RSS feed in your feed reader (it's actually Atom feed, but who cares).

Other recent posts

Server-Sent Events (SSE), but with POST
ngrok for the wicked, or expose your ports comfortably
PostgreSQL collation
History Snapshotting in TwinSpark
Code streaming: hundred ounces of nuances