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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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.