Backend Views

A backend view is responsible for managing additional functionality for the backend system. Usually, a backend view is derived from cubane.views.View or cubane.views.ModelView.

Views are organised into backend sections which then drives the navigation system.

Backend Sections

Cubane’s backend system is using a strict two-level hierarchy for its navigation system. The first level of navigation usually correlates to a specific area of interest, such as content items, settings, media assets, enquiries or accounts. Each section is represented by a backend section, each can have multiple sub-sections.

For example, the content management system is creating a backend section called Content and then creates a separate sub-section for each content type that is used by the system, such as Page, Case Studies or Projects.

Most of Cubane’s sub-systems, such as Cubane’s CMS system will automatically populate various sections based on model classes declared by your application. However, you may create your own backend sections in order to add your own functionality to the backend.

In the following example, we will add a new section to the backend system which shall manage books. In order to install a backend section within the backend system, declare the following function in your app’s __init__.py file:

def install_backend(backend):
    from myApp.backend import BookShopBackendSection
    backend.register_section(BookShopBackendSection())

Of course, you would need to create your own implementation for a backend section first. In the example above, we implemented our backend section MyBackendSection in the file myApp.backend.py in the following way:

from cubane.backend.views import BackendSection

class BookShopBackendSection(BackendSection):
    title = 'Book Shop'
    sections = [
        BooksBackendSection(),
        ...
    ]

See also

Please refer to section Modules and Extensions for more information about Cubane’s extension system.

The title field declares the visual name of the section as it is presented within the backend system, which is Book Shop in our case.

Note

We placed our implementation for BookShopBackendSection in the file backend.py for arbitrary reasons. There is no strict requirement on where your implementation and extensions for the backend system should live.

Next, we declare a list of sub-sections to be listed within the Book Shop section, which is BooksBackendSection, which is implemented in a very similar way to the Book Shop section itself:

class BooksBackendSection(BackendSection):
    title = 'Books'
    view = BookView

The main difference is that the sub-section does not declare any further sub-sections but declares a view instead. A view is a class that has been derived from cubane.views.View. In most cases, you would want to derive your view class from cubane.views.ModelView since it provides a lot of model-related management capabilities with it, such as listing, searching and filtering records, as well as the usual, create, edit and delete actions.

However, we will start by looking at its base class first, which is cubane.views.View:

View

A View encapsulates Django’s URL patterns (which you would usually declare within the urls.py file) together with view handlers (which would usually go into the views.py file). Let’s look at an example first:

from django.shortcuts import get_object_or_404, render
from cubane.views import View, view, view_url
from models import Book

class BookView(View):
    patterns = [
        view_url(r'books/',             'index'),
        view_url(r'books/(?P<pk>\d+)/', 'book')
    ]

    @view(require_GET)
    def index(self, request):
        return render(request, 'index.html', {
            'books': Book.objects.all()
        })

    @view(require_GET)
    def book(self, request, pk):
        book = get_object_or_404(Book, pk=pk)
        return render(request, 'book.html', {
            'book': book
        })

You declare URL patterns via the patterns field, which is a list of URL patterns very similar to how you would declare URL patterns within a urls.py file: The first argument to view_url() declares the regular expression of the URL pattern, the second argument gives the name of the view handler as the name of any method of the same class as a string.

Other than that, the implementation behaves in exactly the same way as a regular Django view handler. You can add decorators, but you need to use the view() function in order to do so as shown in the code example above.

The benefit of combining multiple endpoints into a View is that more complex views can be build by deriving from an already existing view.

You can use a view fully independently of Cubane’s backend system (which is why it is not bundled within the namespace cubane.backend), but the concept of a View was originally created in order to facilitate Cubane’s backend system.

You can hook up any number of views within your urls.py file in the following way:

from django.conf.urls import url, include
from views import BookView

books = BooksView()

urlpatterns = [
    url(r'^books/', include(books.urls))
]

A view may implement before and after actions. These are code implementations that are executed before or after any other view handler method of the View is executed:

class BookView(View):
    def before(self, request, handler):
        print('before')


    def after(self, request, handler, response):
        print('after')


    def index(request):
        print('index')

If you executed the index method of the view, then you should see the following debug output in your console window:

before
index
after

Which represents the order in which those methods are executed by the system. Before any view handler method is executed, the before method is executed. Then the actual view handler method is executed (in this case index) and finally, after the view handler method returned, the after method is executed.

The handler method refers to the actual view handler method that is currently executed. The response argument of the after method refers to the response object that has been returned by executing the view handler method.

Both methods, before and after may return a response object, in which case the response of the entire request is the response that was returned. If nothing is returned (None), then the system will simply return the result of the view handler, which is usually the result of rendering a template.

If the before handler returns a response object, then the actual view handler method is never executed.

The after method may return a response object, in which case the returned response object replaces any response that was previously returned by the view handler method.

Template View

The previous example defined a View class for presenting books. Each view handler was using Django’s render() method for rendering a template. Cubane provides a simple decorator for rendering a template via a decorator:

from django.shortcuts import get_object_or_404, render
from cubane.views import View, view, view_url
from cubane.decorators import template
from models import Book

class BookView(View):
    patterns = [
        view_url(r'books/',             'index'),
        view_url(r'books/(?P<pk>\d+)/', 'book')
    ]

    @view(require_GET)
    @view(template('index.html'))
    def index(self, request):
        return {
            'books': Book.objects.all()
        }

    @view(require_GET)
    @view(template('book.html'))
    def book(self, request, pk):
        book = get_object_or_404(Book, pk=pk)
        return {
            'book': book
        }

By using the template() decorator, we can specify the template that is used to render any particular view in a more declarative way. The view simply returns the template context as a dictionary which then becomes the template context when rendering the corresponding template file.

Please note how the name of the view method (for example index) correlates with the name of the template file (index.html). This is on purpose for this example, but there is no rule that this has to be this way; neither the less, this is very common practice when declaring view handlers.

For this reason, Cubane provides a cubane.views.TemplateView class that is internally derived from cubane.views.View and makes use of the commonly found correlation between the method name and the name of the template file:

from django.shortcuts import get_object_or_404, render
from cubane.views import TemplateView, view, view_url
from models import Book

class BookView(TemplateView):
    patterns = [
        view_url(r'books/',             'index'),
        view_url(r'books/(?P<pk>\d+)/', 'book')
    ]

    @view(require_GET)
    def index(self, request):
        return {
            'books': Book.objects.all()
        }

    @view(require_GET)
    def book(self, request, pk):
        book = get_object_or_404(Book, pk=pk)
        return {
            'book': book
        }

This example demonstrates the use of a cubane.views.TemplateView. By deriving the class from TemplateView instead of View, the system will automatically render the template file which name is derived from the corresponding method name.

Because the name of the index method is index, the system will automatically render the template index.html.

You may want to prefix all templates with a specific path, which can be declared in the following way:

class BookView(TemplateView):
    template_path = 'myApp/books/'

    ...

template_path declares the path to the template files that are used by the TemplateView class. In this example, the index method would render to template myApp/books/index.html.

Internally this behaviour is implemented by overriding the after method of the View class. If you happened to override the after method as well, please make sure to call the derived implementation in order to keep the default behaviour of the TemplateView, if this is what you wanted:

class BookView(TemplateView):
    def after(self, request, handler, response):
        # TODO: Insert your code here for example
        return super(BookView, self).after(request, handler, response)

The TemplateView class also provides a mechanism to inject specific render context information for every request:

class BookView(TemplateView):
    context = {
        'page_title': 'Books'
    }

When rendering any view handler method of the BookView class, the template context declared as context is always part of the resulting template context that is used to render a template.

Be aware that the context will override any template information that is returned by a view handler method. If the index method would return a dictionary containing page_title, then such value would always be overridden by the general context that has been declared for the entire template view class.

API View

Sometimes a set of view handlers are concerned about generating JSON responses. We can imagine a JSON-based service endpoint that is concerned with books, where each view handler will return JSON-encoded data.

Cubane provides the class ApiView for implementing API-related views as the next example demonstrates:

from django.shortcuts import get_object_or_404, render
from cubane.views import ApiView, view, view_url
from models import Book

class BookApiView(ApiView):
    patterns = [
        view_url(r'books/',             'index'),
        view_url(r'books/(?P<pk>\d+)/', 'book')
    ]

    @view(require_GET)
    def index(self, request):
        return {
            'books': Book.objects.all()
        }

    @view(require_GET)
    def book(self, request, pk):
        book = get_object_or_404(Book, pk=pk)
        return {
            'book': book
        }

The only difference from the previous example is the fact that we derived from ApiView and not from TemplateView.

While TemplateView will render a particular template file, ApiView will simply take the result returned from a view handler method and encodes it into JSON. Encoded JSON is then packed into an HTTP response object with the content encoding text/javascript.

Model View

A ModelView is derived from TemplateView and forms the basis for most views as part of the backend system.

A model view provides a wast amount of functionality for a particular model:

  • Multi-column listing of model instances
  • Create, Update, Duplicate, Delete
  • Sequential Ordering
  • Sorting, Searching and Filtering
  • Folders and Drag and Drop

Let’s look at a simple example, where we derive our own class from ModelView in order to provide backend functionality for managing books:

from cubane.backend.views import BackendSection
from cubane.views import ModelView
from models import Book

class BookView(ModelView):
    template_path = 'cubane/backend/'
    model = Book

class BooksBackendSection(BackendSection):
    title = 'Books'
    view = BookView

class BookShopBackendSection(BackendSection):
    title = 'Book Shop'
    sections = [
        BooksBackendSection()
    ]

The example above links all parts together: It declares a new top-level backend section BookShopBackendSection with the name Book Shop that contains one sub-section BooksBackendSection with the name Books which then presents a model view BookView which operates on the Book model.

Note

For most entities, you do not necessarily have to declare this structure by yourself. For example, if you derived any models from cubane.cms.models.Entity then Cubane’s CMS component will automatically generate appropriate backend views for you.

Of course, the presentation and functionality of a ModelView can be customised and extended. This section will provide more information on extending the ModelView itself.

However, the behaviour of a ModelView is also driven by the model itself, since under most circumstances we are not involved in customising the ModelView directly.

See also

Please refer to the Model View Options section for more information on how to control the presentation of a ModelView by declaring backend-specific options through the corresponding model class.