Using DRF to replace Django's ModelForm and using HTML form templates

1,101 views
Skip to first unread message

James Beith

unread,
Aug 12, 2015, 9:22:48 AM8/12/15
to Django REST framework
I'm interested in the idea of using DRF (Django REST Framework) to replace the use of Django's model forms in a more traditional project that uses HTML forms in templates. I had a few questions about whether this was feasible.

Why? This blog post, Django models, encapsulation and data integrity from Tom Christie struck a chord with me and in particular I'm hoping DRF can address the issues mentioned in the Breaking the contract chapter.

I'm aware that the work on the 3.3.0 release may offer solutions to a lot of this, and if so then that's great. Hopefully if such an idea is feasible, either now or with 3.3.0, then a section in the DRF documentation would be a helpful guide to others.

I'll outline an example project and some cases/questions to address.

An example project would consist of:

- Django 1.8
- Django Vanilla Views for class-based views
- Jinja2 as the template engine


1. Using a Serializer with a HTML form

A basic model serializer:

class BookSerializer(serializers.ModelSerializer):
   
class Meta:
        fields
= ('title',)
        model
= Book

A Django Vanilla Views UpdateView (using Django's built in class-based views would be pretty similar):

class BookUpdateView(vanilla.UpdateView):
    form_class
= BookSerializer
    success_url
= reverse_lazy('books:list')
    template_name
= 'books/update.html'

A template using Jinja2:

{% with field = form.title.as_form_field() %}
 
<label for="{{ field.name }}">{{ field.label }}</label>
 
<input id="{{ field.name }}" name="{{ field.name }}" value="{{ field.value }}">
 
{% if field.errors %}
   
{% for error in field.errors %}
     
<span class="error">{{ error }}</span>
   
{% endfor %}
 
{% endif %}
{% endwith %}

There was one change I had to make to get this to work. Creating the serializer in the `get_form()` method which in Django Vanilla Views consists of:

    def get_form(self, data=None, files=None, **kwargs):
        cls
= self.get_form_class()
       
return cls(data=data, files=files, **kwargs)

needed to be changed to:

    def get_form(self, data=empty, files=None, **kwargs):
        cls
= self.get_form_class()
       
return cls(data=data, **kwargs)

This doesn't pass `files` and uses the DRF `empty` class from `from rest_framework.fields import empty` as otherwise I would run in to the following two errors when creating the serializer.

- TypeError
    - `__init__() got an unexpected keyword argument 'files'`
- AssertionError
    - `When a serializer is passed a `data` keyword argument you must call `.is_valid()` before attempting to access the serialized `.data` representation. You should either call `.is_valid()` first, or access `.initial_data` instead. `

The `files` are not being passed to the serializer this way as I don't have a DRF `Request` with `request.data`, just a `WSGIRequest`.

- Does the above seem a logical approach to using DRF with HTML form templates?
    - Would there also be an option of using the views DRF provides to render the HTML form templates, though using a renderer that supports 3rd party template engines (Jinja2)? Ideally this could be achieved without _requiring_ to use DRF views.
- How could I ensure my class-based views receive a DRF `Request` so I can pass `request.data` to the serializer and not lose `files` when its created in `get_form()`?


2. Replacing `modelformset_factory`

Taking this `modelformset_factory` example:

class AuthorForm(forms.ModelForm):
   
class Meta:
        model
= Author
        fields
= ('name',)


AuthorFormSet = modelformset_factory(Author, form=AuthorForm)
formset
= AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))


- How could I recreate `modelformset_factory` using DRF serializers?
    - With the ability to provide the `queryset` that will be used to populate the forms.
- How would you go about rendering each form in the HTML template?


3. Replacing `inlineformset_factory`

I had some success with this but was unable to render all the forms in the template.

Here's an example of the serializers:

class BookSerializer(serializers.ModelSerializer):
   
class Meta:
        fields
= ('title',)
        model
= Book


class AuthorSerializer(serializers.ModelSerializer):
    books
= BookSerializer(many=True)

   
class Meta:
        fields
= ('name', 'books',)
        model
= Author


I then set `AuthorSerializer` as the form in the view and the author `instance` is from `get_object()`. However, as I said, I was unsure how to render all the forms in the template.

- How could I recreate `inlineformset_factory` using DRF serializers?
- How would you go about rendering each form in the HTML template?

I believe this could be related. Nested lists in HTML forms #2483

---

I hope my example has not been too specific in its requirements. Would be great to see DRF as a viable alternative to fully replace Django's forms without completely changing a project's views and templates.

Tom Christie

unread,
Aug 24, 2015, 5:13:00 PM8/24/15
to Django REST framework
Hi James,

This is just a rough pass (on my phone) feel free to expand in any of these that I've not done a good job on and can discuss further...

1) would be completely reasonable, yup. Vanilla views wouldn't quite be ideal as-is because it doesn't inherit for rest frameworks view class. However it's a simple project so it'd be easy enough to create an equivalent DRF specific project. (And yup the serializer init arguments would be slightly different as you point out)

The generic views provided by DRF are also not quite suitable as they use API conventions rather than browser conventions and have a slightly diff set of behaviour.

2) don't bother replicating modelformset_factory. I deliberately removed the .model shortcut to force users to explicitly specify the serializer class and queryset and reduce the amount of magic and indirection. I think it's presence is slightly poor API, and removing it from a GCBV implementation ends up with super simple behaviour.

To render a serializer instance in a template you'll need to have a go at wrapping HTMLFormRenderer in a template tag.

3) again, don't bother replicating inlineformset-somethingsomething-factory. Either ensure that the serializer is called with many=True or enforce that an explicit ListSerializer is used for those cases.

The template rendering there similar to before but relys on the ticket you've referenced. There's some support there ATM but it's not documented or well used/tested yet.

Cheers,

Tom

James Beith

unread,
Aug 25, 2015, 12:36:51 PM8/25/15
to Django REST framework
Thanks Tom.

1) If I get the chance to look at creating an equivalent project do you think it's something that you'd like to see included within DRF or kept as a standalone project?

2 & 3) I'll have another look at these and in particular the template rendering as things progress with the v3.3.0 release.

Cheers,
James
Reply all
Reply to author
Forward
0 new messages