django-extra-views¶
Django Extra Views provides a number of additional class-based generic views to complement those provide by Django itself. These mimic some of the functionality available through the standard admin interface, including Model, Inline and Generic Formsets.
Features¶
FormSet
andModelFormSet
views - The formset equivalents ofFormView
andModelFormView
.InlineFormSetView
- Lets you edit a formset related to a model (using Django’sinlineformset_factory
).CreateWithInlinesView
andUpdateWithInlinesView
- Lets you edit a model and multiple inline formsets all in one view.GenericInlineFormSetView
, the equivalent ofInlineFormSetView
but forGenericForeignKeys
.- Support for generic inlines in
CreateWithInlinesView
andUpdateWithInlinesView
. - Support for naming each inline or formset in the template context with
NamedFormsetsMixin
. SortableListMixin
- Generic mixin for sorting functionality in your views.SearchableListMixin
- Generic mixin for search functionality in your views.SuccessMessageMixin
andFormSetSuccessMessageMixin
- Generic mixins to display success messages after form submission.
Table of Contents¶
Getting Started¶
Installation¶
Install the stable release from pypi (using pip):
pip install django-extra-views
Or install the current master branch from github:
pip install -e git://github.com/AndrewIngram/django-extra-views.git#egg=django-extra-views
Then add 'extra_views'
to your INSTALLED_APPS
:
INSTALLED_APPS = [
...
'extra_views',
...
]
Quick Examples¶
FormSetView¶
Define a FormSetView
, a view which creates a single formset from
django.forms.formset_factory
and adds it to the context.
from extra_views import FormSetView
from my_forms import AddressForm
class AddressFormSet(FormSetView):
form_class = AddressForm
template_name = 'address_formset.html'
Then within address_formset.html
, render the formset like this:
<form method="post">
...
{{ formset }}
...
<input type="submit" value="Submit" />
</form>
ModelFormSetView¶
Define a ModelFormSetView
, a view which works as FormSetView
but instead renders a model formset using
django.forms.modelformset_factory
.
from extra_views import ModelFormSetView
class ItemFormSetView(ModelFormSetView):
model = Item
fields = ['name', 'sku']
template_name = 'item_formset.html'
CreateWithInlinesView or UpdateWithInlinesView¶
Define CreateWithInlinesView
and UpdateWithInlinesView
,
views which render a form to create/update a model instance and its related
inline formsets. Each of the InlineFormSetFactory
classes use similar
class definitions as the ModelFormSetView
.
from extra_views import CreateWithInlinesView, UpdateWithInlinesView, InlineFormSetFactory
class ItemInline(InlineFormSetFactory):
model = Item
fields = ['sku', 'price', 'name']
class ContactInline(InlineFormSetFactory):
model = Contact
fields = ['name', 'email']
class CreateOrderView(CreateWithInlinesView):
model = Order
inlines = [ItemInline, ContactInline]
fields = ['customer', 'name']
template_name = 'order_and_items.html'
class UpdateOrderView(UpdateWithInlinesView):
model = Order
inlines = [ItemInline, ContactInline]
fields = ['customer', 'name']
template_name = 'order_and_items.html'
Then within order_and_items.html
, render the formset like this:
<form method="post">
...
{{ form }}
{% for formset in inlines %}
{{ formset }}
{% endfor %}
...
<input type="submit" value="Submit" />
</form>
Formset Views¶
For all of these views we’ve tried to mimic the API of Django’s existing class-based views as closely as possible, so they should feel natural to anyone who’s already familiar with Django’s views.
FormSetView¶
This is the formset equivalent of Django’s FormView. Use it when you want to display a single (non-model) formset on a page.
A simple formset:
from extra_views import FormSetView
from my_app.forms import AddressForm
class AddressFormSetView(FormSetView):
template_name = 'address_formset.html'
form_class = AddressForm
success_url = 'success/'
def get_initial(self):
# return whatever you'd normally use as the initial data for your formset.
return data
def formset_valid(self, formset):
# do whatever you'd like to do with the valid formset
return super(AddressFormSetView, self).formset_valid(formset)
and in address_formset.html
:
<form method="post">
...
{{ formset }}
...
<input type="submit" value="Submit" />
</form>
This view will render the template address_formset.html
with a context variable
formset
representing the AddressFormSet
. Once POSTed and successfully
validated, formset_valid
will be called (which is where your handling logic
goes), then the view will redirect to success_url
.
Formset constructor and factory kwargs¶
FormSetView exposes all the parameters you’d normally be able to pass to the
django.forms.BaseFormSet
constructor and
django.forms.formset_factory()
. This can be done by setting the
respective attribute on the class, or formset_kwargs
and
factory_kwargs
at the class level.
Below is an exhaustive list of all formset-related attributes which can be set
at the class level for FormSetView
:
...
from my_app.forms import AddressForm, BaseAddressFormSet
class AddressFormSetView(FormSetView):
template_name = 'address_formset.html'
form_class = AddressForm
formset_class = BaseAddressFormSet
initial = [{'type': 'home'}, {'type': 'work'}]
prefix = 'address-form'
success_url = 'success/'
factory_kwargs = {'extra': 2, 'max_num': None,
'can_order': False, 'can_delete': False}
formset_kwargs = {'auto_id': 'my_id_%s'}
In the above example, BaseAddressFormSet would be a subclass of
django.forms.BaseFormSet
.
ModelFormSetView¶
ModelFormSetView makes use of django.forms.modelformset_factory()
, using the
declarative syntax used in FormSetView
as well as Django’s own class-based
views. So as you’d expect, the simplest usage is as follows:
from extra_views import ModelFormSetView
from my_app.models import Item
class ItemFormSetView(ModelFormSetView):
model = Item
fields = ['name', 'sku', 'price']
template_name = 'item_formset.html'
Rather than setting fields
, exclude
can be defined
at the class level as a list of fields to be excluded.
It is not necessary to define fields
or exclude
if a
form_class
is defined at the class level:
...
from django.forms import ModelForm
class ItemForm(ModelForm):
# Custom form definition goes here
fields = ['name', 'sku', 'price']
class ItemFormSetView(ModelFormSetView):
model = Item
form_class = ItemForm
template_name = 'item_formset.html'
Like FormSetView
, the formset
variable is made available in the template
context. By default this will populate the formset with all the instances of
Item
in the database. You can control this by overriding get_queryset
on
the class, which could filter on a URL kwarg (self.kwargs
), for example:
class ItemFormSetView(ModelFormSetView):
model = Item
template_name = 'item_formset.html'
def get_queryset(self):
sku = self.kwargs['sku']
return super(ItemFormSetView, self).get_queryset().filter(sku=sku)
InlineFormSetView¶
When you want to edit instances of a particular model related to a parent model (using a ForeignKey), you’ll want to use InlineFormSetView. An example use case would be editing addresses associated with a particular contact.
from extra_views import InlineFormSetView
class EditContactAddresses(InlineFormSetView):
model = Contact
inline_model = Address
...
Aside from the use of model
and inline_model
,
InlineFormSetView
works more-or-less in the same way as
ModelFormSetView
, instead calling django.forms.inlineformset_factory()
.
CreateWithInlinesView and UpdateWithInlinesView¶
These are the most powerful views in the library, they are effectively
replacements for Django’s own CreateView
and UpdateView
. The key
difference is that they let you include any number of inline formsets (as well
as the parent model’s form). This provides functionality much like the Django
Admin change forms. The API should be fairly familiar as well. The list of the
inlines will be passed to the template as context variable inlines.
Here is a simple example that demonstrates the use of each view with normal inline relationships:
from extra_views import CreateWithInlinesView, UpdateWithInlinesView, InlineFormSetFactory
class ItemInline(InlineFormSetFactory):
model = Item
fields = ['sku', 'price', 'name']
class ContactInline(InlineFormSetFactory):
model = Contact
fields = ['name', 'email']
class CreateOrderView(CreateWithInlinesView):
model = Order
inlines = [ItemInline, ContactInline]
fields = ['customer', 'name']
template_name = 'order_and_items.html'
def get_success_url(self):
return self.object.get_absolute_url()
class UpdateOrderView(UpdateWithInlinesView):
model = Order
inlines = [ItemInline, ContactInline]
fields = ['customer', 'name']
template_name = 'order_and_items.html'
def get_success_url(self):
return self.object.get_absolute_url()
and in the html template:
<form method="post">
...
{{ form }}
{% for formset in inlines %}
{{ formset }}
{% endfor %}
...
<input type="submit" value="Submit" />
</form>
InlineFormSetFactory¶
This class represents all the configuration necessary to generate an inline formset
from django.inlineformset_factory()
. Each class within in
CreateWithInlines.inlines
and UpdateWithInlines.inlines
should be a subclass of InlineFormSetFactory
. All the
same methods and attributes as InlineFormSetView
are available, with the
exception of any view-related attributes and methods, such as success_url
or formset_valid()
:
from my_app.forms import ItemForm, BaseItemFormSet
from extra_views import InlineFormSetFactory
class ItemInline(InlineFormSetFactory):
model = Item
form_class = ItemForm
formset_class = BaseItemFormSet
initial = [{'name': 'example1'}, {'name', 'example2'}]
prefix = 'item-form'
factory_kwargs = {'extra': 2, 'max_num': None,
'can_order': False, 'can_delete': False}
formset_kwargs = {'auto_id': 'my_id_%s'}
IMPORTANT: Note that when using InlineFormSetFactory
, model
should be the
inline model and not the parent model.
GenericInlineFormSetView¶
In the specific case when you would usually use Django’s
django.contrib.contenttypes.forms.generic_inlineformset_factory()
, you
should use GenericInlineFormSetView
. The kwargs ct_field
and
fk_field
should be set in factory_kwargs
if they need to be
changed from their default values:
from extra_views.generic import GenericInlineFormSetView
class EditOrderTags(GenericInlineFormSetView):
model = Order
inline_model = Tag
factory_kwargs = {'ct_field': 'content_type', 'fk_field': 'object_id',
'max_num': 1}
formset_kwargs = {'save_as_new': True}
...
There is a GenericInlineFormSetFactory
which is analogous to
InlineFormSetFactory
for use with generic inline formsets.
GenericInlineFormSetFactory
can be used in
CreateWithInlines.inlines
and UpdateWithInlines.inlines
in the
obvious way.
Formset Customization Examples¶
Overriding formset_kwargs and factory_kwargs at run time¶
If the values in formset_kwargs
and factory_kwargs
need to be
modified at run time, they can be set by overloading the get_formset_kwargs()
and get_factory_kwargs()
methods on any formset view (model, inline or generic)
and the InlineFormSetFactory
classes:
class AddressFormSetView(FormSetView):
...
def get_formset_kwargs(self):
kwargs = super(AddressFormSetView, self).get_formset_kwargs()
# modify kwargs here
return kwargs
def get_factory_kwargs(self):
kwargs = super(AddressFormSetView, self).get_factory_kwargs()
# modify kwargs here
return kwargs
Overriding the the base formset class¶
The formset_class
option should be used if you intend to override the
formset methods of a view or a subclass of InlineFormSetFactory
.
For example, imagine you’d like to add your custom clean
method
for an inline formset view. Then, define a custom formset class, a subclass of
Django’s BaseInlineFormSet
, like this:
from django.forms.models import BaseInlineFormSet
class ItemInlineFormSet(BaseInlineFormSet):
def clean(self):
# ...
# Your custom clean logic goes here
Now, in your InlineFormSetView
sub-class, use your formset class via
formset_class
setting, like this:
from extra_views import InlineFormSetView
from my_app.models import Item
from my_app.forms import ItemForm
class ItemInlineView(InlineFormSetView):
model = Item
form_class = ItemForm
formset_class = ItemInlineFormSet # enables our custom inline
This will enable clean
method being executed on the formset used by
ItemInlineView
.
Initial data for ModelFormSet and InlineFormSet¶
Passing initial data into ModelFormSet and InlineFormSet works slightly
differently to a regular FormSet. The data passed in from initial
will
be inserted into the extra
forms of the formset. Only the data from
get_queryset()
will be inserted into the initial rows:
from extra_views import ModelFormSetView
from my_app.models import Item
class ItemFormSetView(ModelFormSetView):
template_name = 'item_formset.html'
model = Item
factory_kwargs = {'extra': 10}
initial = [{'name': 'example1'}, {'name': 'example2'}]
The above will result in a formset containing a form for each instance of
Item
in the database, followed by 2 forms containing the extra initial data,
followed by 8 empty forms.
Altenatively, initial data can be determined at run time and passed in by
overloading get_initial()
:
...
class ItemFormSetView(ModelFormSetView):
model = Item
template_name = 'item_formset.html'
...
def get_initial(self):
# Get a list of initial values for the formset here
initial = [...]
return initial
Passing arguments to the form constructor¶
In order to change the arguments which are passed into each form within the formset, this can be done by the ‘form_kwargs’ argument passed in to the FormSet constructor. For example, to give every form an initial value of ‘example’ in the ‘name’ field:
from extra_views import InlineFormSetFactory
class ItemInline(InlineFormSetFactory):
model = Item
formset_kwargs = {'form_kwargs': {'initial': {'name': 'example'}}}
If these need to be modified at run time, it can be done by
get_formset_kwargs()
:
from extra_views import InlineFormSetFactory
class ItemInline(InlineFormSetFactory):
model = Item
def get_formset_kwargs(self):
kwargs = super(ItemInline, self).get_formset_kwargs()
initial = get_some_initial_values()
kwargs['form_kwargs'].update({'initial': initial})
return kwargs
Named formsets¶
If you want more control over the names of your formsets (as opposed to
iterating over inlines
), you can use NamedFormsetsMixin
:
from extra_views import NamedFormsetsMixin
class CreateOrderView(NamedFormsetsMixin, CreateWithInlinesView):
model = Order
inlines = [ItemInline, TagInline]
inlines_names = ['Items', 'Tags']
fields = '__all__'
Then use the appropriate names to render them in the html template:
...
{{ Tags }}
...
{{ Items }}
...
Success messages¶
When using Django’s messages framework, mixins are available to send success
messages in a similar way to django.contrib.messages.views.SuccessMessageMixin
.
Ensure that 'django.contrib.messages.middleware.MessageMiddleware'
is included
in the MIDDLEWARE
section of settings.py.
extra_views.SuccessMessageMixin
is for use with views with multiple
inline formsets. It is used in an identical manner to Django’s
SuccessMessageMixin, making form.cleaned_data
available for string
interpolation using the %(field_name)s
syntax:
from extra_views import CreateWithInlinesView, SuccessMessageMixin
...
class CreateOrderView(SuccessMessageMixin, CreateWithInlinesView):
model = Order
inlines = [ItemInline, ContactInline]
success_message = 'Order %(name)s successfully created!'
...
# or instead, set at runtime:
def get_success_message(self, cleaned_data, inlines):
return 'Order with id {} successfully created'.format(self.object.pk)
Note that the success message mixins should be placed ahead of the main view in order of class inheritance.
extra_views.FormSetSuccessMessageMixin
is for use with views which handle a single
formset. In order to parse any data from the formset, you should override the
get_success_message
method as below:
from extra_views import FormSetView, FormSetSuccessMessageMixin
from my_app.forms import AddressForm
class AddressFormSetView(FormSetView):
form_class = AddressForm
success_url = 'success/'
...
success_message = 'Addresses Updated!'
# or instead, set at runtime
def get_success_message(self, formset)
# Here you can use the formset in the message if required
return '{} addresses were updated.'.format(len(formset.forms))
List Views¶
Searchable List Views¶
You can add search functionality to your ListViews by adding SearchableListMixin and by setting search_fields:
from django.views.generic import ListView
from extra_views import SearchableListMixin
class SearchableItemListView(SearchableListMixin, ListView):
template_name = 'extra_views/item_list.html'
search_fields = ['name', 'sku']
model = Item
In this case object_list
will be filtered if the ‘q’ query string is provided
(like /searchable/?q=query), or you can manually override get_search_query
method, to define your own search functionality.
Also you can define some items in search_fields
as tuple (e.g.
[('name', 'iexact', ), 'sku']
) to provide custom lookups for searching.
Default lookup is icontains
. We strongly recommend to use only string lookups,
when number fields will convert to strings before comparison to prevent converting errors.
This controlled by check_lookups
setting of SearchableMixin.
Sortable List View¶
from django.views.generic import ListView
from extra_views import SortableListMixin
class SortableItemListView(SortableListMixin, ListView):
sort_fields_aliases = [('name', 'by_name'), ('id', 'by_id'), ]
model = Item
You can hide real field names in query string by define sort_fields_aliases
attribute (see example) or show they as is by define sort_fields.
SortableListMixin adds sort_helper
variable of SortHelper class,
then in template you can use helper functions:
{{ sort_helper.get_sort_query_by_FOO }}
,
{{ sort_helper.get_sort_query_by_FOO_asc }}
,
{{ sort_helper.get_sort_query_by_FOO_desc }}
and
{{ sort_helper.is_sorted_by_FOO }}
Reference¶
Change History¶
0.14.0 (2021-06-08)¶
Changes:¶
Supported Versions:
Python | Django |
---|---|
3.5 | 2.1–2.2 |
3.6-3.7 | 2.1–3.1 |
3.8 | 2.2–3.1 |
- Removed support for Python 2.7.
- Added support for Python 3.8 and Django 3.1.
- Removed the following classes (use the class in parentheses instead):
BaseFormSetMixin
(useBaseFormSetFactory
).BaseInlineFormSetMixin
(useBaseInlineFormSetFactory
).InlineFormSet
(useInlineFormSetFactory
).BaseGenericInlineFormSetMixin
(useBaseGenericInlineFormSetFactory
).GenericInlineFormSet
(useGenericInlineFormSetFactory
).
0.13.0 (2019-12-20)¶
Changes:¶
Supported Versions:
Python | Django |
---|---|
2.7 | 1.11 |
3.5 | 1.11–2.2 |
3.6-3.7 | 1.11–3.0 |
- Added
SuccessMessageMixin
andFormSetSuccessMessageMixin
. CreateWithInlinesView
andUpdateWithInlinesView
now callself.form_valid
method withinself.forms_valid
.- Revert
view.object
back to it’s original value from the GET request if validation fails for the inline formsets inCreateWithInlinesView
andUpdateWithInlinesview
. - Added support for Django 3.0.
0.12.0 (2018-10-21)¶
Supported Versions:
Python | Django |
---|---|
2.7 | 1.11 |
3.4 | 1.11–2.0 |
3.5-3.7 | 1.11–2.1 |
Changes:¶
- Removed setting of
BaseInlineFormSetMixin.formset_class
andGenericInlineFormSetMixin.formset_class
so thatformset
can be set infactory_kwargs
instead. - Removed
ModelFormSetMixin.get_context_data
andBaseInlineFormSetMixin.get_context_data
as this code was duplicated from Django’sMultipleObjectMixin
andSingleObjectMixin
respectively. - Renamed
BaseFormSetMixin
toBaseFormSetFactory
. - Renamed
BaseInlineFormSetMixin
toBaseInlineFormSetFactory
. - Renamed
InlineFormSet
toInlineFormSetFactory
. - Renamed
BaseGenericInlineFormSetMixin
toBaseGenericInlineFormSetFactory
. - Renamed
GenericInlineFormSet
toGenericInlineFormSetFactory
.
All renamed classes will be removed in a future release.
0.11.0 (2018-04-24)¶
Supported Versions:
Python | Django |
---|---|
2.7 | 1.11 |
3.4–3.6 | 1.11–2.0 |
Backwards-incompatible changes¶
- Dropped support for Django 1.7–1.10.
- Removed support for factory kwargs
extra
,max_num
,can_order
,can_delete
,ct_field
,formfield_callback
,fk_name
,widgets
,ct_fk_field
being set onBaseFormSetMixin
and its subclasses. UseBaseFormSetMixin.factory_kwargs
instead. - Removed support for formset_kwarg
save_as_new
being set onBaseInlineFormSetMixin
and its subclasses. UseBaseInlineFormSetMixin.formset_kwargs
instead. - Removed support for
get_extra_form_kwargs
. This can be set in the dictionary keyform_kwargs
inBaseFormSetMixin.formset_kwargs
instead.
0.10.0 (2018-02-28)¶
New features:
- Added SuccessMessageWithInlinesMixin (#151)
- Allow the formset prefix to be overridden (#154)
Bug fixes:
- SearchableMixin: Fix reduce() of empty sequence error (#149)
- Add fields attributes (Issue #144, PR #150)
- Fix Django 1.11 AttributeError: This QueryDict instance is immutable (#156)
0.9.0 (2017-03-08)¶
This version supports Django 1.7, 1.8, 1.9, 1.10 (latest minor versions), and Python 2.7, 3.4, 3.5 (latest minor versions).
- Added Django 1.10 support
- Dropped Django 1.6 support
0.8 (2016-06-14)¶
This version supports Django 1.6, 1.7, 1.8, 1.9 (latest minor versions), and Python 2.7, 3.4, 3.5 (latest minor versions).
- Added
widgets
attribute setting; allow to change form widgets in theModelFormSetView
. - Added Django 1.9 support.
- Fixed
get_context_data()
usage of*args, **kwargs
. - Fixed silent overwriting of
ModelForm
fields to__all__
.
Backwards-incompatible changes¶
- Dropped support for Django <= 1.5 and Python 3.3.
- Removed the
extra_views.multi
module as it had neither documentation nor test coverage and was broken for some of the supported Django/Python versions. - This package no longer implicitly set
fields = '__all__'
. If you faceImproperlyConfigured
exceptions, you should have a look at the Django 1.6 release notes and set thefields
orexclude
attributes on yourModelForm
or extra-views views.
0.7.1 (2015-06-15)¶
Beginning of this changelog.