django-crudbuilder : Generic way to create model CRUD

Contents:

Overview

Django-Crudbuilder allows you to generate class based views for CRUD (Create, Read, Update and Delete) for specific model

Features

  • Generates class based views for CRUD
  • Uses django-tables2 to display objects in ListView
  • Define multiple crud builders for same model with separate URL
  • Allows custom forms/tables as additional arguments
  • Context provides additional template variables APP_LABEL and MODEL for all CRUD templates
  • Enable/disable login required option for CRUD views
  • Enable/disable permission required option for CRUD views
  • All the generated views/tables/forms/url are extendable.
  • post_create and post_update signals to handle specific actions in Create and Update views
  • Add your own templates for List/Create/Detail/Update/Delete views
  • Separate CREATE and UPDATE forms
  • Define your own custom queryset for list view
  • Inline Formset support for parent child models
  • Default Bootstrap3 CSS
  • All the generated views are extendable.

Requirements and Compatibility

The 0.0.x series currently supports Django >= 1.8.x and corresponding versions of Python also supported by Django (including Python 3). Development of django-crudbuilder follows Django’s Supported Version Policy and testing for older versions of Django/Python will be removed as time marches on.

Installation and Usage

Install django-crudbuilder:

pip install django-crudbuilder

Add django-crudbuilder to your INSTALLED_APPS:

# settings.py
INSTALLED_APPS = (
    ...
    'django_tables2',
    'crudbuilder'
)

View the additional settings section for a list of the django-crudbuilder settings that are available.

For a working implementation, you can view the example project on Github.

Generating CRUD class based views

The main business-logic for generating class based views for CRUD is done in crudbuilder.views.ViewBuilder(). You can use this class method directly in your own Views or you can use one of the Views packaged with this app.

We’re going to run through creating a tutorial app. Let’s start with a simple model:

# tutorial/models.py
class Person(models.Model):
    name = models.CharField(blank=True, max_length=100)
    email = models.EmailField()
    created_at = models.DateTimeField(auto_now=True)
    created_by = models.ForeignKey(User, blank=True, null=True)

Then create the CRUD class for Person model:

# tutorial/crud.py
from crudbuilder.abstract import BaseCrudBuilder

class PersonCrud(BaseCrudBuilder):
    model = Person
    search_fields = ['name']
    tables2_fields = ('name', 'email')
    tables2_css_class = "table table-bordered table-condensed"
    tables2_pagination = 20  # default is 10
    modelform_excludes = ['created_by', 'updated_by']
    login_required=True
    permission_required=True

    @classmethod
    def custom_queryset(cls, request, **kwargs):
        """Define your own custom queryset for list view"""
        qset = cls.model.objects.filter(created_by=request.user)
        return qset

    @classmethod
    def custom_context(cls, request, context, **kwargs):
        """Define your own custom context for list view"""
        context['custom_data'] = "Some custom data"
        return context

    # permissions = {
    #     'list': 'example.person_list',
    #     'create': 'example.person_create'
    # }
    # createupdate_forms = {
    #     'create': PersonCreateForm,
    #     'update': PersonUpdateForm
    # }

Finally implement the urls for the CRUD:

# tutorail/urls.py
urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^crud/',  include('crudbuilder.urls')),
    ]

The above will generate following URL’s:

http://127.0.0.1:8000/crud/yourappname/yourmodelname
http://127.0.0.1:8000/crud/yourappname/yourmodelname/create/
http://127.0.0.1:8000/crud/yourappname/yourmodelname/<pk>/detail/
http://127.0.0.1:8000/crud/yourappname/yourmodelname/<pk>/update/
http://127.0.0.1:8000/crud/yourappname/yourmodelname/<pk>/delete/

View all your registered CRUDS:

http://127.0.0.1:8000/crud/

CRUD class Attributes

Usage of all below attributes you can view in CRUD class of example project on Github.

  • model – Actual model name
  • search_fields – Search fields for list view
  • custom_postfix_url – Your own custom url postfix, if this value set then the resulted URL will become /crud/appname/<custom_postfix_url>.
  • tables2_fields – Fields which will be used in django-tables2 fields attribute (in list view)
  • tables2_css_class – CSS class for list view table (for django-tables2)
  • tables2_pagination – By default crudbuilder will set pagination to 10, you can oveeride this value by setting this attribute
  • modelform_excludes – Exclude fields for model form
  • detailview_excludes – Exclude fields in Deatail view
  • custom_modelform – Your custom model form
  • custom_table2 – Your custom Tables2 class
  • custom_templates – Your own custom templates. For more details on custom templates, you can check custom templates
  • login_required – Enable login required for specific model CRUD (by default False)
  • permission_required – Enable permission required for specific model CRUD (by default False)
  • permissions – By default crudbuilder will generate crud permissions, if you want to define your own permissions then add permissions dictionary on the CRUD class. For more details on permission, you can check custom permission
  • createupdate_forms – Define separate CREATE and UPDATE forms
  • custom_queryset – Define your own custom queryset for list view
  • custom_context – Define your own custom context for list view
  • inlineformset – Define your Inline Formset for parent child relation, you can check inline-formset-parent-child-relation for more detail.

Additional Settings

There are a few additional settings you can use to add additional functionali for login and permission required and are set in your settings.py file.

LOGIN_REQUIRED_FOR_CRUD

  • If LOGIN_REQUIRED_FOR_CRUD has been marked as True in settings.py, then login_required decorator will be enabled on all CRUD views globally.
  • If you want to enable login required only for specific model crud, then you need to add following to crud class
# myapp/crud.py
login_required = True
  • By default LOGIN_REQUIRED_FOR_CRUD is False, which means any user can view all the CRUD views.

PERMISSION_REQUIRED_FOR_CRUD

  • If PERMISSION_REQUIRED_FOR_CRUD has been marked as True in settings.py, then permission_required decorator will be enabled on all CRUD views globally.
  • If you want to enable permission required only for specific model crud, then you need to add following to crud class
# myapp/crud.py
permission_required = True
  • By default PERMISSION_REQUIRED_FOR_CRUD is False, which means any user can view all the CRUD views.

By enabling either of above flag, by default crudbuilder checks for following permissions:

  • For ListView : <your app_name>.<your model>_list
  • For CreateView : <your app_name>.<your model>_create
  • For DetailView : <your app_name>.<your model>_detail
  • For UpdateView : <your app_name>.<your model>_update
  • For DeleteView : <your app_name>.<your model>_delete

If you want to add your own permissions, then define your own permission required dictionary exlicitly in CRUD class.:

permissions = {
'list'  : 'example.permission1',
'create': 'example.permission2'
'detail': 'example.permission3',
'update': 'example.permission4',
'delete': 'example.permission5',
}

Forms

If NO custom forms defined in CRUD class, then by default crudbuilder will generate modelform from Django modelform factory.

Custom Modelform in CRUD class

You can define your own custom modelform in yourapp/forms.py and the same will be used for CRUD class. As shown below:

# yourapp/forms.py
class PersonEmploymentForm(forms.ModelForm):
        class Meta:
                model = PersonEmployment
                fields = '__all__'
                # exclude = ('person',)

        def __init__(self, *args, **kwargs):
                self.request = kwargs.pop('request', None)
                super(PersonEmploymentForm, self).__init__(*args, **kwargs)

# yourapp/crud.py
class PersonEmploymentCrud(BaseCrudBuilder):
        model = PersonEmployment
        custom_modelform = PersonEmploymentForm

Note

If you want to plug custom modelform, then make sure to override form’s __init__ to get request param as mentioned above.

Separate CREATE and UPDATE forms

You can also define separate forms for CreateView and UpdateView.:

# yourapp/forms.py
class PersonEmployementCreateForm(forms.ModelForm):
        class Meta:
                model = PersonEmployment
                exclude = ('person', 'medical_allowance')

        def __init__(self, *args, **kwargs):
                self.request = kwargs.pop('request', None)
                super(PersonEmployementCreateForm, self).__init__(*args, **kwargs)

class PersonEmployementUpdateForm(forms.ModelForm):
        class Meta:
                model = PersonEmployment
                exclude = ('salary', 'year')

        def __init__(self, *args, **kwargs):
                self.request = kwargs.pop('request', None)
                super(PersonEmployementUpdateForm, self).__init__(*args, **kwargs)

# yourapp/crud.py
class PersonEmploymentCrud(BaseCrudBuilder):
        model = PersonEmployment
        createupdate_forms = {
                'create': PersonEmployementCreateForm,
                'update': PersonEmployementUpdateForm
        }

You can check forms.py of example project on Github.

Note

If you want to plug custom modelform, then make sure to override form’s __init__ to get request param as mentioned above.

Inline Formset (Parent child relation)

The latest version of django-crudbuilder supports inline formset. You can define your own InlineFormset and give it to CRUD class.:

# yourapp/crud.py
from crudbuilder.formset import BaseInlineFormset

class PersonEmploymentInlineFormset(BaseInlineFormset):
        inline_model = PersonEmployment
        parent_model = Person
        exclude = ['created_by', 'updated_by']
        #formset_class = YourBaseInlineFormset
        #child_form = ChildModelForm

class PersonCrud(BaseCrudBuilder):
        model = Person
        search_fields = ['name']
        tables2_fields = ('name', 'email')
        inlineformset = PersonEmploymentInlineFormset

You can check crud.py of example project on Github.

You can set the following arguments for InlineFormset class.:

extra = 3               --> default number of forms of Child model
can_delete = True       --> Delete check box for child instance
formset_class = None    --> Django BaseInlineFormset class to override the methods of InlineFormset (For example, if you want to override clean())
child_form              --> Modelform for Child model
inline_model = None     --> Child Model
parent_model = None     --> Parent Model
exclude = []            --> Exclude fields for Inline Formset
fields = None           --> Fields to disaply in inline formset
fk_name = None          --> More than one foreign key for the same parent model, then specify this

Templates

By default django-crudbuilder uses Bootstrap3 style for its CRUD templates. You can view these templates in template folder of crudbuilder on Github.

Use your own HTML templates for crudbuilder

You can use your own templates for the crudbuilder in following two ways:

5 common templates for all models CRUD

You can create your own 5 common HTML templates for CRUD in templates/crudbuilder, then crudbuilder will use your defined templates.

Model

For single object crud.:

templates/crudbuilder/instance
        list.html
        create.html
        update.html
        delete.html
        detail.html
Inline Formset

For inline formset.:

templates/crudbuilder/inline
    list.html
    create.html
    update.html
    delete.html
    detail.html
Custom templates for specific model:

If you want to create custom templates for specific model, then update the CRUD class with custom template path as shown below.:

class PersonCrud(BaseCrudBuilder):
    model = Person
    search_fields = ['name']
    tables2_fields = ('name', 'email')
    tables2_css_class = "table table-bordered table-condensed"
    tables2_pagination = 20  # default is 10
    modelform_excludes = ['created_by', 'updated_by']

    custom_templates = {
        'list': 'yourtemplates/your_list_template.html',
        'create': 'yourtemplates/your_create_template.html',
        'detail': 'yourtemplates/your_detail_template.html',
        'update': 'yourtemplates/your_update_template.html',
        'delete': 'yourtemplates/your_delete_template.html'
        }

Enable search in ListView template

If you are writing your own custom templates, then please add the following to your list view template to enable the search.:

<form action="." method="GET">
    <input type="text" name='search'>
    <button type="submit" >Search</button>
</form>

Signals

django-crudbuilder comes with built-in signals to perform/execute specific actions after post create and post update views. You can able to access request and instance in your own handlers.

Usage of these signals you can view in handlers of example project on Github.

Following are the list of supported signals.:

post_create_signal         --> runs after modelform.save() in CreateView
post_update_signal         --> runs after modelform.save() in UpdateView
post_inline_create_signal  --> runs after inlineformset.save() in CreateView
post_inline_update_signal  --> runs after inlineformset.save() in UpdateView

post_create_signal

This signal will loaded/executed soon after the object gets created for specific model. In otherwords this will fires right after the modelform.save() method gets called during the CreateView.:

# tutorial/handlers.py
from django.dispatch import receiver
from crudbuilder.signals import post_create_signal

from example.models import Person

@receiver(post_create_signal, sender=Person)
def post_create_signal_handler(sender, **kwargs):
    # print "execute after create action"
    request = kwargs['request']
    instance = kwargs['instance']

    instance.created_by = request.user
    instance.save()

post_update_signal

This signal will loaded/executed soon after the object gets updated for specific model. In otherwords this will fires right after the modelform.save() method gets called during the UpdateView.:

# tutorial/handlers.py
from django.dispatch import receiver
from crudbuilder.signals import post_update_signal

from example.models import Person

@receiver(post_update_signal, sender=Person)
def post_update_signal_handler(sender, **kwargs):
    # print "execute after update action"
    request = kwargs['request']
    instance = kwargs['instance']

    instance.updated_by = request.user
    instance.save()

post_inline_create_signal

This signal will get executed soon after the inline formset gets saved which means this get fires right after the inlineformset.save() method gets called in CreateView.:

# tutorial/handlers.py
from django.dispatch import receiver
from crudbuilder.signals import post_inline_create_signal

@receiver(post_inline_create_signal, sender=Person)
def post_inline_create_handler(sender, **kwargs):
        request = kwargs['request']
        parent = kwargs['parent']
        children = kwargs['children']

        parent.created_by = request.user
        parent.save()

        for child in children:
                child.created_by = request.user
                child.save()

post_inline_update_signal

This signal will get executed soon after the inline formset gets updated which means this get fires right after the inlineformset.save() method gets called in UpdateView.:

# tutorial/handlers.py
from django.dispatch import receiver
from crudbuilder.signals import post_inline_update_signal

@receiver(post_inline_update_signal, sender=Person)
def post_inline_update_handler(sender, **kwargs):
        request = kwargs['request']
        parent = kwargs['parent']
        children = kwargs['children']

        parent.updated_by = request.user
        parent.save()

        for child in children:
                child.updated_by = request.user
                child.save()

Issues:

Use the GitHub issue tracker for django-crudbuilder to submit bugs, issues, and feature requests.