ViewSets

A ViewSet is a class-based view that allows you to combine a set of related views into a single class. The most typical usage of ViewSets is to combine CRUD operations for a particular model in a single class. ViewSets allow you define methods that handle both detail and list operations in a single class. Unlike a APIView class that defines methods such as get() or post(), an APIViewSet defines actions like retrive() and create().

Example

Below we define a single APIViewSet that can be used to retrieve a single user or all the users in the system:

from pyramid.response import Response
from pyramid.httpexceptions import HTTPNotFound

from pyramid_restful import viewsets

from myapp.models import User
from myyapp.schemas import UserSchema

class UserViewSet(viewsets.APIViewSet):
    model = User
    schema = UserSchema

    def list(self, request):
        users = request.dbsession.query(User).all()
        schema = UserSchema()
        content = schema.dump(users, many=True)[0]
        return Response(json=content)


    def retrieve(self, request, id):
        user = request.dbsession.query(User).get(id)

        if not user:
            raise HTTPNotFound()

        schema = UserSchema()
        content = schema.dump(user)[0]
        return Response(json=content)

To route this view in Pyramid we bind the view to two different routes:

from . import views


def includeme(config):
    config.add_route('user-list', '/users/')
    config.add_view(views.UserViewSet.as_view({'get': 'list'}), route_name='user-list')

    config.add_route('user-detail', '/users/{id}/')
    config.add_view(views.UserViewSet.as_view({'get': 'retrieve'}), route_name='user-detail')

Typically you wont do this. Instead you would use the ViewSetRouter to configure the routes for you:

from pyramid_restful.routers import ViewSetRouter

from . import views


def includeme(config):
    router = ViewSetRouter(config)
    router.register('users', views.UserViewSet, 'user')

ViewSet Actions

The ViewSetRouter provides defaults for the standard CRUD actions, as shown below:

class UserViewSet(viewsets.APIViewSet):
    """
    Example empty viewset demonstrating the standard
    actions that will be handled by a router class.
    """

    def list(self, request):
        pass

    def create(self, request):
        pass

    def retrieve(self, request, id=None):
        pass

    def update(self, request, id=None):
        pass

    def partial_update(self, request, id=None):
        pass

    def destroy(self, request, id=None):
        pass

Including extra actions for routing

You can add ad-hoc methods to ViewSets that will automatically be routed by the ViewSetRouter by using the @detail_route or @list_route decorators. The @detail_route includes id in it’s url pattern and is used for methods that operate on a single instance of model. @list_route decorator is used for methods that operate on many instances of a model.

Example:

from pyramid.response import Response

from pyramid_restful.viewsets import ModelCRPDViewSet
from pyramid_restful.decorators import list_route, detail_route

from .models import User
from .schemas import UserSchema


class UserViewSet(ModelCRPDViewSet):
    model = User
    schema = UserSchema

    @detail_route(methods=['post'])
    def lock(request, id):
        user = request.dbsession.query(User).get(id)

        if not user:
            raise HTTPNotFound()

        user.is_locked = True
        return Response(status=204)

    @list_route(methods=['get'])
    def active(request):
        users = request.dbsession.query(User).filter(User.is_active == True).all()
        schema = UserSchema()
        content = schema.dump(users, many=True)[0]
        return Response(json=content)

By default the router will append the name of method to the url pattern generated. The two decorated routes above would result in the following url patterns:

'/users/{id}/lock'
'/users/active'

You can override this behavior by setting the kwarg url_path on the decorator.

Base ViewSet Classes

Generally your not going to need to write your own viewsets. Instead you will use one of the base ViewSet classes provided by PRF or use a number of mixin classes in your ViewSet to compose a class that only includes the actions you need for a particular resource.

APIViewSet

The APIViewSet class extends the APIView class and does not provide any actions by default. You will have to add the action methods explicitly to the class. You can use the standard APIView attributes such as permissions.

GenericAPIViewSet

The GenericAPIViewSet class extends GenericAPIView and does not provide any actions by default, but does include the base set of generic view behavior, such as the get_object() and get_query() methods. To use the class you will typically mixin the actions you need from the mixins module or write the action methods explicitly.

The ModelViewSets

PRF provide you with several ModelViewSet implementations. ModelViewSets are simply classes in which several action mixins are combined with GenericAPIViewSet. They provide all the functionality that comes with a GenericAPIView, such as the filter_classes and permission_classes attributes and well as the get_query() and get_object() methods. The base ModelViewSets provided by PRF along with their default actions are listed below:

  • ReadOnlyModelViewSet: list(), retrieve()
  • ModelCRUDViewSet: list(), create(), retrieve(), update(), destroy()
  • ModelCRPDViewSet: list(), create(), retrieve(), partial_update(), destroy()
  • ModelCRUPDViewSet: list(), create(), retrieve(), update(), partial_update(), destroy()

Custom ViewSets

If one of the predefined ViewSets doesn’t meet your needs you can always compose your own ViewSet and override its actions.

Example:

from pyramid_restful import mixins
from pyramid_restful import viewsets

from .models import User
from .schema import UserSchema


class UserViewSet(mixins.CreateModelMixin,
                  mixins.RetrieveModelMixin,
                  mixins.UpdateModelMixin):

    model = User
    schema = UserSchema

    def get_query():
        """
        Restrict user to the authenticated user.
        """

        return super(UserViewSet, self).get_query() \
            .filter(User.id == request.user.id)