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\d+)/edit/$', views.AuthorCrudViewset.update(), name='edit-author'), url(r'^authors(?P\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: 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 ``_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 :ref:`modal sizes `.