Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

""" 

ViewSets are essentially just a type of class based view, that doesn't provide 

any method handlers, such as `get()`, `post()`, etc... but instead has actions, 

such as `list()`, `retrieve()`, `create()`, etc... 

 

Actions are only bound to methods at the point of instantiating the views. 

 

    user_list = UserViewSet.as_view({'get': 'list'}) 

    user_detail = UserViewSet.as_view({'get': 'retrieve'}) 

 

Typically, rather than instantiate views from viewsets directly, you'll 

regsiter the viewset with a router and let the URL conf be determined 

automatically. 

 

    router = DefaultRouter() 

    router.register(r'users', UserViewSet, 'user') 

    urlpatterns = router.urls 

""" 

from __future__ import unicode_literals 

 

from functools import update_wrapper 

from django.utils.decorators import classonlymethod 

from rest_framework import views, generics, mixins 

 

 

class ViewSetMixin(object): 

    """ 

    This is the magic. 

 

    Overrides `.as_view()` so that it takes an `actions` keyword that performs 

    the binding of HTTP methods to actions on the Resource. 

 

    For example, to create a concrete view binding the 'GET' and 'POST' methods 

    to the 'list' and 'create' actions... 

 

    view = MyViewSet.as_view({'get': 'list', 'post': 'create'}) 

    """ 

 

    @classonlymethod 

    def as_view(cls, actions=None, **initkwargs): 

        """ 

        Because of the way class based views create a closure around the 

        instantiated view, we need to totally reimplement `.as_view`, 

        and slightly modify the view function that is created and returned. 

        """ 

        # The suffix initkwarg is reserved for identifing the viewset type 

        # eg. 'List' or 'Instance'. 

        cls.suffix = None 

 

        # sanitize keyword arguments 

        for key in initkwargs: 

            if key in cls.http_method_names: 

                raise TypeError("You tried to pass in the %s method name as a " 

                                "keyword argument to %s(). Don't do that." 

                                % (key, cls.__name__)) 

            if not hasattr(cls, key): 

                raise TypeError("%s() received an invalid keyword %r" % ( 

                    cls.__name__, key)) 

 

        def view(request, *args, **kwargs): 

            self = cls(**initkwargs) 

            # We also store the mapping of request methods to actions, 

            # so that we can later set the action attribute. 

            # eg. `self.action = 'list'` on an incoming GET request. 

            self.action_map = actions 

 

            # Bind methods to actions 

            # This is the bit that's different to a standard view 

            for method, action in actions.items(): 

                handler = getattr(self, action) 

                setattr(self, method, handler) 

 

            # Patch this in as it's otherwise only present from 1.5 onwards 

            if hasattr(self, 'get') and not hasattr(self, 'head'): 

                self.head = self.get 

 

            # And continue as usual 

            return self.dispatch(request, *args, **kwargs) 

 

        # take name and docstring from class 

        update_wrapper(view, cls, updated=()) 

 

        # and possible attributes set by decorators 

        # like csrf_exempt from dispatch 

        update_wrapper(view, cls.dispatch, assigned=()) 

 

        # We need to set these on the view function, so that breadcrumb 

        # generation can pick out these bits of information from a 

        # resolved URL. 

        view.cls = cls 

        view.suffix = initkwargs.get('suffix', None) 

        return view 

 

    def initialize_request(self, request, *args, **kargs): 

        """ 

        Set the `.action` attribute on the view, 

        depending on the request method. 

        """ 

        request = super(ViewSetMixin, self).initialize_request(request, *args, **kargs) 

        self.action = self.action_map.get(request.method.lower()) 

        return request 

 

 

class ViewSet(ViewSetMixin, views.APIView): 

    """ 

    The base ViewSet class does not provide any actions by default. 

    """ 

    pass 

 

 

class GenericViewSet(ViewSetMixin, generics.GenericAPIView): 

    """ 

    The GenericViewSet class does not provide any actions by default, 

    but does include the base set of generic view behavior, such as 

    the `get_object` and `get_queryset` methods. 

    """ 

    pass 

 

 

class ReadOnlyModelViewSet(mixins.RetrieveModelMixin, 

                           mixins.ListModelMixin, 

                           GenericViewSet): 

    """ 

    A viewset that provides default `list()` and `retrieve()` actions. 

    """ 

    pass 

 

 

class ModelViewSet(mixins.CreateModelMixin, 

                    mixins.RetrieveModelMixin, 

                    mixins.UpdateModelMixin, 

                    mixins.DestroyModelMixin, 

                    mixins.ListModelMixin, 

                    GenericViewSet): 

    """ 

    A viewset that provides default `create()`, `retrieve()`, `update()`, 

    `partial_update()`, `destroy()` and `list()` actions. 

    """ 

    pass