Welcome to django-popupcrud’s documentation!¶
Quickstart¶
Install
django-popupcrud
using pip:pip install django-popucrud
Or install it directly from the source repository:
pip intall git+https://github.com/harikvpy/django-popupcrud.git
Yet another way would be to clone this repository and install from the cloned root folder via
pip install -e .
.Install the dependencies -
django-bootstrap3
anddjango-pure-pagination
. Add the dependencies andpopupcrud
toINSTALLED_APPS
in your project’ssettings.py
:INSTALLED_APPS = [ ... 'bootstrap3', 'pure_pagination', 'popupcrud', ... ]
Let
PopupCrudViewSet
know of your base template file name. This defaults tobase.html
, but if your project uses a different base template filename, informPopupCrudViewSet
about it insettings.py
:POPUPCRUD = { 'base_template': 'mybase.html', }
Include Bootstrap CSS & JS resources in this base template. If you were to use
django-bootstrap3
tags for these, your base template should look something like this:<head> {% bootstrap_css %} <script src="{% bootstrap_jquery_url %}" type="text/javascript" charset="utf-8"></script> {% bootstrap_javascript %} {% block extrahead %}{% endblock extrahead %} </head>
Also, define a block named
extrahead
within the<head>
element.PopupCrudViewSet
views use a few custom CSS styles to show column sorting options and sort priority. These styles are defined instatic/popupcrud/css/popupcrud.css
which is inserted into theextrahead
block. If you don’t declare this block, you will have to explicitly load the stylesheet into your base template.In your app’s
views.py
, create aViewSet
for each model for which you want to support CRUD operations.Models.py:
from django.db import models class Author(models.Model): name = models.CharField("Name", max_length=128) penname = models.CharField("Pen Name", max_length=128) age = models.SmallIntegerField("Age", null=True, blank=True) class Meta: ordering = ('name',) verbose_name = "Author" verbose_name_plural = "Authors" def __str__(self): return self.name
Views.py:
from django.core.urlresolvers import reverse_lazy, reverse from popupcrud.views import PopupCrudViewSet class AuthorViewSet(PopupCrudViewSet): model = Author fields = ('name', 'penname', 'age') list_display = ('name', 'penname', 'age') list_url = reverse_lazy("library:authors:list") new_url = reverse_lazy("library:authors:create") def get_edit_url(self, obj): return reverse("library:authors:update", kwargs={'pk': obj.pk}) def get_delete_url(self, obj): return reverse("library:authos:delete", kwargs={'pk': obj.pk})
Wire up the CRUD views generated by the viewset to the URLconf:
urlpatterns= [ url(r'^authors/', views.AuthorCrudViewset.urls()), ]
This will register the following urls:
authors/
- list viewauthors/create/
- create viewauthors/<pk>/
- detail viewauthors/<pk>/update/
- update viewauthors/<pk>/delete/
- delete view
The urls are registered under its own namespace, which defaults to the model’s
verbose_name_plural
meta value.Thats it! Your modern HTML popup based CRUD for your table is up and running.
PopupCrudViewSet has many options to customize the fields displayed in list view, form used for create/update operations, permission control and more. Refer to the Reference and How-to sections of the documentation for more details.
Reference¶
Classes¶
PopupCrudViewSet¶
-
class
popupcrud.views.
PopupCrudViewSet
(*args, **kwargs)¶ This is the base class from which you derive a class in your project for each model that you need to build CRUD views for.
-
model
= None¶ The model to build CRUD views for. This is a required attribute.
-
new_url
= None¶ URL to the create view for creating a new object. This is a required attribute.
-
list_display
= ()¶ Lists the fields to be displayed in the list view columns. This attribute is modelled after ModelAdmin.list_display and supports model methods as as ViewSet methods much like ModelAdmin. This is a required attribute.
So you have four possible values that can be used in list_display:
- A field of the model
- A callable that accepts one parameter for the model instance.
- A string representing an attribute on ViewSet class.
- A string representing an attribute on the model
See ModelAdmin.list_display documentation for examples.
A note about
list_display
fields with respect to how it differs fromModelAdmin
’slist_display
.In
ModelAdmin
, if a field specified inlist_display
is not a database field, it can be set as a sortable field by setting the method’sadmin_order_field
attribute to the relevant database field that can be used as the sort field. InPopupCrudViewSet
, this attribute is namedorder_Field
.
-
fields
= ()¶ A list of names of fields. This is interpreted the same as the Meta.fields attribute of ModelForm. This is a required attribute.
-
form_class
= None¶ The form class to instantiate for Create and Update views. This is optional and if not specified a ModelForm using the values of fields attribute will be instantiated. An optional attribute, if specified, overrides fields attribute value.
-
list_url
= None¶ The url where the list view is rooted. This will be used as the success_url attribute value for the individual CRUD views. This is a required attribute.
-
paginate_by
= 10¶ Number of entries per page in list view. Defaults to 10. Setting this to None will disable pagination. This is an optional attribute.
-
list_permission_required
= ()¶ List of permission names for the list view. Permission names are of the same format as what is specified in
permission_required()
decorator. Defaults to no permissions, meaning no permission is required.Depracated. Use permissions_required dictionary instead.
-
create_permission_required
= ()¶ List of permission names for the create view. Defaults to no permissions, meaning no permission is required.
Depracated. Use permissions_required dictionary instead.
-
detail_permission_required
= ()¶ List of permission names for the detail view. Defaults to no permissions, meaning no permission is required.
Depracated. Use permissions_required dictionary instead.
-
update_permission_required
= ()¶ List of permission names for the update view. Defaults to no permissions, meaning no permission is required.
Depracated. Use permissions_required dictionary instead.
-
delete_permission_required
= ()¶ List of permission names for the delete view. Defaults to no permissions, meaning no permission is required.
Depracated. Use permissions_required dictionary instead.
-
permissions_required
= {}¶ Permissions table for the various CRUD views. Use this instead of list_permission_required, create_permission_required, etc. Entries in this table are indexed by the CRUD view code and its required permissions tuple. CRUD view codes are: ‘list’, ‘create’, ‘detail’, ‘update’ & ‘delete’.
Note that if both the legacy <crud-op>_permission_required and permissons_required are specified, permissions_required setting value takes effect.
For example you can specify:
permissions_required = { 'list': ('library.list_author',), 'create': ('library.list_author',), 'detail': ('library.view_author',), 'update': ('library.update_author',), 'delete': ('library.delete_author',) }
-
list_template
= None¶ The template file to use for list view. If not specified, defaults to the internal template.
A table that maps foreign keys to its target model’s
PopupCrudViewSet.create()
view url. This would result in the select box for the foreign key to display a ‘New {model}’ link at its bottom, which the user can click to add a new {model} object from another popup. The newly created {model} object will be added to the select’s options and set as its selected option.Defaults to empty dict, meaning creation of target model objects, for the foreign keys of a model, from a popup is disabled.
-
page_title
= ''¶ Page title for the list view page.
-
legacy_crud
= False¶ Enables legacy CRUD views where each of the Create, Detail, Update & Delete views are performed from their own dedicated web views like Django admin (hence the term
legacy_crud
:-)).This property can accept either a boolean value, which in turn enables/ disables the legacy mode for all the CRUD views or it can accept a dict of CRUD operation codes and its corresponding legacy mode specified as boolean value.
This dict looks like:
legacy_crud = { 'create': False, 'detail': False, 'update': False, 'delete': False }
So by setting
legacy_crud[detail] = True
, you can enable legacy style crud for the detail view whereas the rest of the CRUD operations are performed from a modal popup.In other words,
legacy_crud
boolean value results in a dict that consists ofTrue
orFalse
values for all its keys, as the case may be.This defaults to
False
, which translates into a dict consisting ofFalse
values for all its keys.
-
login_url
= None¶ Same as
django.contrib.auth.mixins.AccessMixin
login_url
, but applicable for all CRUD views.
-
raise_exception
= False¶ Same as
django.contrib.auth.mixins.AccessMixin
raise_exception
, but applicable for all CRUD views.
-
empty_list_icon
= None¶ Icon to be displayed above the empty list state message. Defaults to None, which displays no icon. To specify an icon, set this property to the CSS class of the required icon.
For example to use the glyphicon-book icon, set this property to:
empty_list_icon = 'glyphicon glyphicon-book'
Icons displayed are enlarged to 5 times the standard font size.
-
empty_list_message
= 'No records found.'¶ Message to be displayed when list view contains no records, that is, empty list state. Defaults to ‘No records found`.
Empty list state rendering can be customized further by overriding
popupcrud/empty_list.html
template in your own project.
List of breadcrumbs that will be added to ViewSet views’ context, allowing you build a breadcrumb hierarchy that reflects the ViewSet’s location in the site.
Note that for
legacy_crud
views, system would add thelist view
url to the breadcrumbs list.
The template context variable name that will be initialized with the value of
breadcrumbs
property. You can enumerate this variable in your base template to build a breadcrumbs list that reflects the hierarchy of the page.
-
item_actions
= []¶ - its title
its icon css such as
glyphicon glyphicon-ok
its action handler, which is the name of the CrudViewSet method to be called when user selects the action. This method has the following signature:
def action_handler(self, request, item): # action processing return (True, "Action completed")
The return value from the action handler is a 2-tuple that consists of a boolean success indicator and a message. The message is displayed to the user when the action is completed.
Also see
get_item_actions()
documentation below.
-
modal_sizes
= {}¶ Allows specifying the size of the modal windows used for the CRUD operations. Value is a dictionary, where the key names indicate the modal whose size you want to adjust. Allowed values for these are:
create_update
,delete
&detail
. Value for the keys indicate the size of the modal and has to be one of:{small|normal|large}
.Defaults to:
modal_sizes = { 'create_update': 'normal', 'delete': 'normal', 'detail': 'normal' }
You only need to specify the modal whose size you want to adjust. So if you want to adjust the size of the
create_update
modal to large while leaving the rest to their default size, you may specify modal_sizes as:modal_sizes = { 'create_update': 'large', }
-
context_object_name
= None¶ If specified, the name of the context variable that will be used to assign the object instance value. By default the context variable
object
will be assigned the object instance. Ifcontext_object_name
is specified, that too will be assigned the object instance.
-
pk_url_kwarg
= 'pk'¶ Same as SingleObjectMixin.pk_url_kwarg, which is the name of the URLConf keyword argument that contains the primary key. By default,
pk_url_kwarg
is pk`.
-
slug_field
= 'slug'¶ Same as SingleObjectMixin.slug_field, which is the name of the field on the model that contains the slug. By default,
slug_field
isslug
.To use
slug_field
as the key to access object instances (for detail, update & delete views), setpk_url_kwarg = None
in the ViewSet class and initializeslug_field
andslug_url_kwarg
to the relevant slug field’s name & its corresponding URLconf parameter name respectively.
-
slug_url_kwarg
= 'slug'¶ Same as SingleObjectMixin.slug_url_kwarg, which is the name of the URLConf keyword argument that contains the slug. By default,
slug_url_kwarg
isslug
.
-
classmethod
list
(**initkwargs)¶ Returns the list view that can be specified as the second argument to url() in urls.py.
-
classmethod
create
(**initkwargs)¶ Returns the create view that can be specified as the second argument to url() in urls.py.
-
classmethod
detail
(**initkwargs)¶ Returns the create view that can be specified as the second argument to url() in urls.py.
-
classmethod
update
(**initkwargs)¶ Returns the update view that can be specified as the second argument to url() in urls.py.
-
classmethod
delete
(**initkwargs)¶ Returns the delete view that can be specified as the second argument to url() in urls.py.
-
get_new_url
()¶ Returns the URL to create a new model object. Returning None would disable the new object creation feature and will hide the
New {model}
button.You may override this to dynamically determine if new object creation ought to be allowed. Default implementation returns the value of
ViewSet.new_url
.
-
get_detail_url
(obj)¶ Override this returning the URL where
PopupCrudViewSet.detail()
is placed in the URL namespace such that ViewSet can generate the appropriate href to display item detail in list view.When this hyperlink is clicked, a popup containing the object’s detail will be shown. By default this popup only shows the object’s string representation. To show additional information in this popup, implement
<object>_detail.html
in your project, typically in the app’s template folder. If this file exists, it will be used to render the object detail popup. True to Django’sDetailView
convention, you may use the{{ object }}
template variable in the template file to access the object and its properties.Default implementations returns None, which results in object detail popup being disabled.
-
get_edit_url
(obj)¶ Override this returning the URL where PopupCrudViewSet.update() is placed in the URL namespace such that ViewSet can generate the appropriate href to the item edit hyperlink in list view.
If None is returned, link to edit the specified item won’t be shown in the object row.
-
get_delete_url
(obj)¶ Override this returning the URL where PopupCrudViewSet.delete() is placed in the URL namespace such that ViewSet can generate the appropriate href to the item delete hyperlink in list view.
If None is returned, link to delete the specified item won’t be shown in the object row.
-
get_obj_name
(obj)¶ Return the name of the object that will be displayed in item action prompts for confirmation. Defaults to
str(obj)
, ie., the string representation of the object. Override this to provide the user with additional object details. The returned string may contain embedded HTML tags.For example, you might want to display the balance due from a customer when confirming user action to delete the customer record.
-
get_permission_required
(op)¶ Return the permission required for the CRUD operation specified in op. Default implementation returns the value of one
{list|create|detail|update|delete}_permission_required
class attributes. Overriding this allows you to return dynamically computed permissions.Parameters: op – The CRUD operation code. One of {'list'|'create'|'detail'|'update'|'delete'}
.Return type: The permission_required
tuple for the specified operation. Determined by looking up the givenop
from the table:permission_table = { 'list': self.list_permission_required, 'create': self.create_permission_required, 'detail': self.detail_permission_required, 'update': self.update_permission_required, 'delete': self.delete_permission_required }
-
get_page_title
(view, obj=None)¶ Returns page title for the CRUD view. Parameter view specifies the CRUD view whose page title is being queried and is one of create, detail, update, delete or list.
For detail, update & delete views, the model instance is is passed as second argument. For the rest of the views, this is set to None.
-
classmethod
urls
(namespace=None, views=('create', 'update', 'delete', 'detail'))¶ Returns the CRUD urls for the viewset that can be added to the URLconf. The URLs returned can be controlled by the
views
parameter which is tuple of strings specifying the CRUD operations URLs to be returned. This defaults to all the CRUD operations: create, read(detail), update & delete (List view URL is added by default).This method can be seen as a wrapper to calling the individual view generator methods,
list()
,detail()
,create()
,update()
&delete()
, to register them with the URLconf.The urls for CRUD actions involving a single object (detail, update & delete) are by default composed using
pk
as URLConf keyword argument. However, ifpk_url_kwarg
is set to None andslug_field
andslug_url_kwarg
are initialized, it will be based as the field used to locate the individual object and URLConf keyword argument respectively.Parameters: - namespace – The namespace under which the CRUD urls are registered.
Defaults to the value of
<model>.Meta.verbose_name_plural
(in lowercase and in English). - views – A tuple of strings representing the CRUD views whose URL
patterns are to be registered. Defaults to
('create', 'update', 'delete', 'detail')
, that is all the CRUD operations for the model.
Return type: A collection of URLs, packaged using
django.conf.urls.include()
, that can be used as argument 2 tourl()
(see example below).Example: The following pattern registers all the CRUD urls for model Book (in app
library
), generated by BooksCrudViewSet:urlpatterns += [ url(r'^books/', BooksCrudViewSet.urls()) ]
This allows us to refer to individual CRUD operation url as:
reverse("library:books:list") reverse("library:books:create") reverse("library:books:detail", kwargs={'pk': book.pk}) reverse("library:books:update", kwargs={'pk': book.pk}) reverse("library:books:delete", kwargs={'pk': book.pk})
- namespace – The namespace under which the CRUD urls are registered.
Defaults to the value of
-
popups
¶ Provides a normalized dict of crud view types to use for the viewset depending on client.legacy_crud setting.
Computes this dict only once per object as an optimization.
-
get_empty_list_icon
()¶ Determine the icon used to display empty list state.
Returns the value of
empty_list_icon
property by default.
-
get_empty_list_message
()¶ Determine the message used to display empty table state.
Returns the value of
empty_list_message
property by default.
Returns the value of
ViewSet.breadcrumbs
property. You can use this method to return breadcrumbs that contain runtime computed values.
-
get_queryset
(qs)¶ Called by ListView allowing ViewSet to do further filtering of the queryset, if necessary. By default returns the queryset argument unchanged.
Parameters: qs – Queryset that is used for rendering ListView content. Return type: A valid Django queryset.
-
get_form_kwargs
()¶ For Create and Update views, this method allows passing custom arguments to the form class constructor. The return value from this method is combined with the default form constructor
**kwargs
before it is passed to the form class’__init__()
routine’s**kwargs
.Since Django CBVs use kwargs
initial
&instance
, be careful when using these, unless of course, you want to override the objects provided by these keys.
-
get_formset_class
()¶ Return the formset class to the child model of this parent model allowing create/edit of multiple objects of the child model.
Typically, though not required, the return value will be a class generated by the django formset factory function inlineformset_factory().
By default, this method returns None, which indicates that the model Create/Edit forms do not have a formset for a child model.
-
get_formset
()¶ Returns the formset object instantiated from the class returned by the get_formset_class() method.
By default returns None indicating no formset is associated with the model.
-
get_item_actions
(obj)¶ Determine the custom actions for the given model object that is displayed after the standard Edit & Delete actions in list view.
Parameters: obj – The row object for which actions are being queried. Return type: A list of action 3-tuple (as explained in item_actions
) objects relevant for the given object. If no actions are to be presented for the object, an empty list([]
) can be returned.Default implementation returns the value of
item_actions
class variable.Since this method is called once for each row item, you can customize the actions that is presented for each object. You can also altogether turn off all actions for an object by returning an empty list(
[]
).
-
invoke_action
(request, index, item)¶ Invokes the custom action specified by the index.
- Parameters:
- request - HttpRequest object index - the index of the action into get_item_actions() list item_or_list - the item upon which action is to be performed
- Return:
- Action result as a 2-tuple: (bool, message)
- Raises:
- Index error if the action index specified is outside the scope of the array returned by get_item_actions().
-
get_context_data
(kwargs)¶ Called for every CRUD view’s get_context_data() method, this method allows additional data to be added to the CRUD view’s template context.
Parameters: kwargs – The kwargs dictionary that is the usual argument to View.get_context_data() Return type: None If you want to add context data for a specific CRUD view, you can achieve this by checking the view object’s class type. For example the following code adds context data only for DetailView:
if isinstance(self.view, DetailView): obj = kwargs['object'] kwargs['user_fullname'] = obj.user.first_name + ' ' + obj.user.last_name
-
Template Tags¶
bsmodal¶
A tag to help creation of Bootstrap modal dialogs. You may use this tag as:
{% bsmodal dialogTitle dialogId [close_title_button={Yes|No}] %} <dialog content goes here> {% endbsmodal %}
dialogTitle: Required. The title of the modal window. This can be a template variable (created with {% trans 'something' as var %}
) or a string literal.dialogId: Required. The id of the modal window specified as string literal. close_title_button: Optional. A flag indicating whether to show the modal window close button on the titlebar. Specify one of Yes
orNo
.size: Optional. Dialog size hint. Acceptable values: {small|normal|large}
. Defaults tonormal
.
This would create a hidden dialog with title dialogTitle
and id dialogId
.
The content of the dialog body is to be written between the pair of tags
{% bsmodal %}
and {% endbsmodal %}
.
The final rendered html fragment would look like this:
<div class="modal fade" tabindex="-1" role="dialog"> <div class="modal-dialog modal-dialog-top" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title">{{dialogTitle}}</h4> </div> <div class="modal-body"> <..content between bsmodal & endbsmodal tags..> </div> </div> </div> </div>
The html template for the modal is stored in popupcrud/modal.html
. So if you
want to custom styling of the modal windows, you may define your own template
in your projects templates
folder.
Refer to Boostrap docs on modals for more information on how to show and hide the modal windows.
How-tos¶
Create CRUD views for a model¶
Given a model in app named library
(source code taken from the demo
project
) in project’s repo:
# library/models.py
class Author(models.Model):
name = models.CharField("Name", max_length=128)
penname = models.CharField("Pen Name", max_length=128)
age = models.SmallIntegerField("Age", null=True, blank=True)
class Meta:
ordering = ('name',)
verbose_name = "Author"
verbose_name_plural = "Authors"
def __str__(self):
return self.name
Declare a PopupCrudViewSet derived class in app’s views.py:
# library/views.py
from popupcrud.views import PopupCrudViewSet
class AuthorViewSet(PopupCrudViewSet):
model = Author
fields = ('name', 'penname', 'age')
list_display = ('name', 'penname', 'age')
list_url = reverse_lazy("library:authors")
new_url = reverse_lazy("library:new-author")
def get_edit_url(self, obj):
return reverse_lazy("library:edit-author", kwargs={'pk': obj.pk})
def get_delete_url(self, obj):
return reverse_lazy("library:delete-author", kwargs={'pk': obj.pk})
Wire up the individual CRUD views generated by the viewset to the app URL namespace in urls.py:
# library/urls.py
urlpatterns= [
url(r'^authors/$', views.AuthorCrudViewset.list(), name='authors'),
url(r'^authors/new/$', views.AuthorCrudViewset.create(), name='new-author'),
url(r'^authors(?P<pk>\d+)/edit/$', views.AuthorCrudViewset.update(), name='edit-author'),
url(r'^authors(?P<pk>\d+)/delete/$', views.AuthorCrudViewset.delete(), name='delete-author'),
]
In the projects root urls.py:
# demo/urls.py
urlpatterns + [
url(r'^library/', include('library.urls', namespace='library')),
]
Control access using permissions¶
In your CRUD ViewSet, declare the permissions required for each CRUD view as:
class AuthorViewSet(PopupCrudViewSet):
model = Author
...
list_permission_required = ('library.list_authors',)
create_permission_required = ('library.add_author',)
update_permission_required = ('library.change_author',)
delete_permission_required = ('library.delete_author',)
However, if you want to determine the permission dynamically, override the
get_permission_required()
method and implement your custom permission logic:
class AuthorViewSet(PopupCrudViewSet):
model = Author
...
def get_permission_required(self, op):
if op == 'create':
# custom permission for creating new objects
elif op == 'delete':
# custom permission for updating existing objects
else:
return super(AuthorViewSet, self).get_permission_required(op)
Create a model object from its FK select box in another form¶
This allows user to create new instances of a model while they are working on a form which has a FK reference to the model for which PopupCrudViewSet views exist. This allows objects to be added seamlessly without the user switching context to another page to add the object and then coming back to work on the form.
To illustrate with an example:
from popupcrud.widgets import RelatedFieldPopupFormWidget
class AuthorRatingForm(forms.Form):
author = forms.ModelChoiceField(queryset=Author.objects.all())
rating = forms.ChoiceField(label="Rating", choices=(
('1', '1 Star'),
('2', '2 Stars'),
('3', '3 Stars'),
('4', '4 Stars')
))
def __init__(self, *args, **kwargs):
super(AuthorRatingForm, self).__init__(*args, **kwargs)
author = self.fields['author']
# Replace the default Select widget with PopupCrudViewSet's
# RelatedFieldPopupFormWidget. Note the url argument to the widget.
author.widget = RelatedFieldPopupFormWidget(
widget=forms.Select(choices=author.choices),
new_url=reverse_lazy("library:new-author"))
class AuthorRatingView(generic.FormView):
form_class = AuthorRatingForm
# rest of the View handling code as per Django norms
In the above form, the default widget for author
, django.forms.widgets.Select
has been replaced by RelatedFieldPopupFormWidget
. Note the arguments to the
widget constructor – it takes the underlying Select widget and a url to create
a new instance of the model.
Use Select2 instead of native Select widget¶
Select2 is an advanced version the browser native Select box allowing users
navigate through fairly large selection list using keystrokes. Select2 is
excellently supported in Django through the thirdparty app django-select2.
Replacing the native django.forms.Select
control with equivalent
django_select2.forms.Select2Widget
widget is extremely easy:
from django_select2.forms import Select2Widget
from popupcrud.widgets import RelatedFieldPopupFormWidget
class AuthorRatingForm(forms.Form):
author = forms.ModelChoiceField(queryset=Author.objects.all())
rating = forms.ChoiceField(label="Rating", choices=(
('1', '1 Star'),
('2', '2 Stars'),
('3', '3 Stars'),
('4', '4 Stars')
))
def __init__(self, *args, **kwargs):
super(AuthorRatingForm, self).__init__(*args, **kwargs)
author = self.fields['author']
# Replace the default Select widget with PopupCrudViewSet's
# RelatedFieldPopupFormWidget. Note the url argument to the widget.
author.widget = RelatedFieldPopupFormWidget(
widget=forms.Select2Widget(choices=author.choices),
new_url=reverse_lazy("library:new-author"))
Note how Select2Widget
is essentially a drop in replacement for the native
django.forms.Select
widget. Consult django-select2
docs
for instructions on integrating it with your project.
Providing your own templates¶
Out of the box, popupcrud
comes with its own templates for rendering all
the CRUD views. For most use cases this ought to suffice. For the detail
view, the default template just renders the object name in the popup. Typically,
you might want to include additional information about an object in its detail
view. To do this, implement <model>_detail.html
in your app’s template folder
and this template will be used to display details about an object.
One point to highlight about templates is that since popupcrud can work in both legacy(like Django admin) and the more modern Web 2.0 modal dialog based modes, it needs two templates to render the content for the two modes. This is necessary as contents of a modal popup window should only contain details of the object without site-wide common elements such as headers and menu that is usually provided through a base template whereas the dedicated legacy crud page requires all the site-wide common artifacts. This problem exists for all CRUD views - create, update, delete and detail. Therefore, for consistency across different CRUD views, popupcurd uses a standard file naming convention to determine the template name to use for the given CRUD view mode.
This convention gives first priority to Django generic CRUD views’ default
template file name. If it’s present it will be used for the CRUD view. However,
if the view is to be rendered in a modal popup window, which should not have
site-wide common artifacts, popupcrud appends _inner
to the base template
filename (the part before .html
). So if you want to display
details of a object of class Book
in a modal popup, you have to implement
the template file book_detail_inner.html
. However, if you disable popups
for the detail
view, you have to implement book_detail.html
. The
difference between the two being that *_inner.html
only renders the object’s
details whereas book_detail.html
renders the object’s details along with
site-wide page common artifacts such as header, footers and/or sidebars.
One strategy is to provide both templates and organize them using the
{% include %}
tag. With this pattern, book_detail.html
would
look like this:
{% extends "base.html" %}
{% block content %}
{% include "book_detail_inner.html" %}
{% endblock content %}
The same pattern is applicable to other CRUD views as well where template files
such as book_form.html
, confirm_book_delete.html
are looked for first
before using popupcrud’s own internal templates.
Use the formset feature¶
To add a formset to edit objects of a child model, override the
PopupCrudViewSet.get_formset_class()
method in your derived class returning
the BaseModelFormSet
class which will be used to render the formset along
with the model form. Formsets are always rendered at the bottom of the model form.
To illustrate with an example, assume that we have a Book
table with
the following definition:
class Book(models.Model):
title = models.CharField('Title', max_length=128)
isbn = models.CharField('ISBN', max_length=12)
author = models.ForeignKey(Author)
class Meta:
ordering = ('title',)
verbose_name = "Book"
verbose_name_plural = "Books"
def __str__(self):
return self.title
To allow the user to edit one or more Book
objects while
creating or editing a Author
object, you just need to extend the
AuthorCrudViewset
in the previous example to:
from django import forms
from popupcrud.views import PopupCrudViewSet
class AuthorViewSet(PopupCrudViewSet):
model = Author
...
def get_formset_class(self):
return forms.models.inlineformset_factory(
Author,
Book,
fields=('title', 'isbn'),
can_delete=True,
extra=1)
Now when the modal for create or edit views will show a formset
at the bottom with two fields – Book.title
and Book.isbn
.
A button at the bottom of the formset allows additional formset rows
to be added. Each formset row will also have a button at the
right to delete the row.
The sample above uses the django formset factory function to
dynamically build a formset class based on models parent-child
relationship. You may also return a custom formset class that is
derived from BaseModelFormSet
with appropriate specializations
to suit your requirements.
BaseModelFormSet
base class requirement is due to
PopupCrudViewSet
invoking the save()
method of the class
to save formset data if all of them pass the field validation rules.
A note about formset feature. Since formset forms are rendered in a
tabular format, and since the modal dialogs are not resizable, there
is a limit to the number of formset form fields that can be specified
before it becomes unusable for the user. To cater for this,
PopupCrudViewSet
now allows the modal sizes to be adjusted through
the modal_sizes
class attribute. This allows you to specify the
appropriate modal size based on your form and formset field count &
sizes. See modal sizes.
Demo Project¶
The demo project in folder demo
shows four usage scenarios of
PopupCrudViewSet
. To run the demo, issue the following commands from
demo
folder:
./manage migrate --settings demo.settings
./manage runserver --settings demo.settings
Homepage has links to the various views in the project that demonstrates different use cases. Each link has a brief description on the type of use case it demonstrates.
One of the forms in the demo MultipleRelatedObjectForm
, shows how the
advanced Select2
can be used instead of the django’s native ‘Select`
widget. For this to work, you need to install django-select2
in the virtual
environment where demo
is run.
Settings¶
-
popupcrud.views.
POPUPCRUD_DEFAULTS
¶ django-popupcrud global settings are specified as the dict variable
POPUPCRUD
in settings.py.POPUPCRUD
currently supports the following settings with their default values:base_template
: The prjoject base template from which all popupcrud templates should be derived.Defaults to
base.html
.page_title_context_variable
: Name of the context variable whose value will be set as the title for the CRUD list view page. This title is specified as the value for the class attributeViewSet.page_title
or as the return value ofViewSet.get_page_title()
.Defaults to
page_title
.paginate_by
: Default number of rows per page for queryset pagination. This is the same as ListView.paginate_by.Defaults to 10.