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.