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

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

""" 

Routers provide a convenient and consistent way of automatically 

determining the URL conf for your API. 

 

They are used by simply instantiating a Router class, and then registering 

all the required ViewSets with that router. 

 

For example, you might have a `urls.py` that looks something like this: 

 

    router = routers.DefaultRouter() 

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

    router.register('accounts', AccountViewSet, 'account') 

 

    urlpatterns = router.urls 

""" 

from __future__ import unicode_literals 

 

from collections import namedtuple 

from rest_framework import views 

from rest_framework.compat import patterns, url 

from rest_framework.response import Response 

from rest_framework.reverse import reverse 

from rest_framework.urlpatterns import format_suffix_patterns 

 

 

Route = namedtuple('Route', ['url', 'mapping', 'name', 'initkwargs']) 

 

 

def replace_methodname(format_string, methodname): 

    """ 

    Partially format a format_string, swapping out any 

    '{methodname}' or '{methodnamehyphen}' components. 

    """ 

    methodnamehyphen = methodname.replace('_', '-') 

    ret = format_string 

    ret = ret.replace('{methodname}', methodname) 

    ret = ret.replace('{methodnamehyphen}', methodnamehyphen) 

    return ret 

 

 

class BaseRouter(object): 

    def __init__(self): 

        self.registry = [] 

 

    def register(self, prefix, viewset, base_name=None): 

        if base_name is None: 

            base_name = self.get_default_base_name(viewset) 

        self.registry.append((prefix, viewset, base_name)) 

 

    def get_default_base_name(self, viewset): 

        """ 

        If `base_name` is not specified, attempt to automatically determine 

        it from the viewset. 

        """ 

        raise NotImplemented('get_default_base_name must be overridden') 

 

    def get_urls(self): 

        """ 

        Return a list of URL patterns, given the registered viewsets. 

        """ 

        raise NotImplemented('get_urls must be overridden') 

 

    @property 

    def urls(self): 

        if not hasattr(self, '_urls'): 

            self._urls = patterns('', *self.get_urls()) 

        return self._urls 

 

 

class SimpleRouter(BaseRouter): 

    routes = [ 

        # List route. 

        Route( 

            url=r'^{prefix}{trailing_slash}$', 

            mapping={ 

                'get': 'list', 

                'post': 'create' 

            }, 

            name='{basename}-list', 

            initkwargs={'suffix': 'List'} 

        ), 

        # Detail route. 

        Route( 

            url=r'^{prefix}/{lookup}{trailing_slash}$', 

            mapping={ 

                'get': 'retrieve', 

                'put': 'update', 

                'patch': 'partial_update', 

                'delete': 'destroy' 

            }, 

            name='{basename}-detail', 

            initkwargs={'suffix': 'Instance'} 

        ), 

        # Dynamically generated routes. 

        # Generated using @action or @link decorators on methods of the viewset. 

        Route( 

            url=r'^{prefix}/{lookup}/{methodname}{trailing_slash}$', 

            mapping={ 

                '{httpmethod}': '{methodname}', 

            }, 

            name='{basename}-{methodnamehyphen}', 

            initkwargs={} 

        ), 

    ] 

 

    def __init__(self, trailing_slash=True): 

        self.trailing_slash = trailing_slash and '/' or '' 

        super(SimpleRouter, self).__init__() 

 

    def get_default_base_name(self, viewset): 

        """ 

        If `base_name` is not specified, attempt to automatically determine 

        it from the viewset. 

        """ 

        model_cls = getattr(viewset, 'model', None) 

        queryset = getattr(viewset, 'queryset', None) 

        if model_cls is None and queryset is not None: 

            model_cls = queryset.model 

 

        assert model_cls, '`name` not argument not specified, and could ' \ 

            'not automatically determine the name from the viewset, as ' \ 

            'it does not have a `.model` or `.queryset` attribute.' 

 

        return model_cls._meta.object_name.lower() 

 

    def get_routes(self, viewset): 

        """ 

        Augment `self.routes` with any dynamically generated routes. 

 

        Returns a list of the Route namedtuple. 

        """ 

 

        # Determine any `@action` or `@link` decorated methods on the viewset 

        dynamic_routes = [] 

        for methodname in dir(viewset): 

            attr = getattr(viewset, methodname) 

            httpmethods = getattr(attr, 'bind_to_methods', None) 

            if httpmethods: 

                dynamic_routes.append((httpmethods, methodname)) 

 

        ret = [] 

        for route in self.routes: 

            if route.mapping == {'{httpmethod}': '{methodname}'}: 

                # Dynamic routes (@link or @action decorator) 

                for httpmethods, methodname in dynamic_routes: 

                    initkwargs = route.initkwargs.copy() 

                    initkwargs.update(getattr(viewset, methodname).kwargs) 

                    ret.append(Route( 

                        url=replace_methodname(route.url, methodname), 

                        mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), 

                        name=replace_methodname(route.name, methodname), 

                        initkwargs=initkwargs, 

                    )) 

            else: 

                # Standard route 

                ret.append(route) 

 

        return ret 

 

    def get_method_map(self, viewset, method_map): 

        """ 

        Given a viewset, and a mapping of http methods to actions, 

        return a new mapping which only includes any mappings that 

        are actually implemented by the viewset. 

        """ 

        bound_methods = {} 

        for method, action in method_map.items(): 

            if hasattr(viewset, action): 

                bound_methods[method] = action 

        return bound_methods 

 

    def get_lookup_regex(self, viewset): 

        """ 

        Given a viewset, return the portion of URL regex that is used 

        to match against a single instance. 

        """ 

        base_regex = '(?P<{lookup_field}>[^/]+)' 

        lookup_field = getattr(viewset, 'lookup_field', 'pk') 

        return base_regex.format(lookup_field=lookup_field) 

 

    def get_urls(self): 

        """ 

        Use the registered viewsets to generate a list of URL patterns. 

        """ 

        ret = [] 

 

        for prefix, viewset, basename in self.registry: 

            lookup = self.get_lookup_regex(viewset) 

            routes = self.get_routes(viewset) 

 

            for route in routes: 

 

                # Only actions which actually exist on the viewset will be bound 

                mapping = self.get_method_map(viewset, route.mapping) 

                if not mapping: 

                    continue 

 

                # Build the url pattern 

                regex = route.url.format( 

                    prefix=prefix, 

                    lookup=lookup, 

                    trailing_slash=self.trailing_slash 

                ) 

                view = viewset.as_view(mapping, **route.initkwargs) 

                name = route.name.format(basename=basename) 

                ret.append(url(regex, view, name=name)) 

 

        return ret 

 

 

class DefaultRouter(SimpleRouter): 

    """ 

    The default router extends the SimpleRouter, but also adds in a default 

    API root view, and adds format suffix patterns to the URLs. 

    """ 

    include_root_view = True 

    include_format_suffixes = True 

    root_view_name = 'api-root' 

 

    def get_api_root_view(self): 

        """ 

        Return a view to use as the API root. 

        """ 

        api_root_dict = {} 

        list_name = self.routes[0].name 

        for prefix, viewset, basename in self.registry: 

            api_root_dict[prefix] = list_name.format(basename=basename) 

 

        class APIRoot(views.APIView): 

            _ignore_model_permissions = True 

 

            def get(self, request, format=None): 

                ret = {} 

                for key, url_name in api_root_dict.items(): 

                    ret[key] = reverse(url_name, request=request, format=format) 

                return Response(ret) 

 

        return APIRoot.as_view() 

 

    def get_urls(self): 

        """ 

        Generate the list of URL patterns, including a default root view 

        for the API, and appending `.json` style format suffixes. 

        """ 

        urls = [] 

 

        if self.include_root_view: 

            root_url = url(r'^$', self.get_api_root_view(), name=self.root_view_name) 

            urls.append(root_url) 

 

        default_urls = super(DefaultRouter, self).get_urls() 

        urls.extend(default_urls) 

 

        if self.include_format_suffixes: 

            urls = format_suffix_patterns(urls) 

 

        return urls