From 3d4bb4b5533fa281c2f11c12ceb0a9ae61aa0d54 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 22:03:07 +0100 Subject: Ensure action kwargs properly handdled. Refs #940. --- htmlcov/rest_framework_routers.html | 595 ++++++++++++++++++++++++++++++++++++ 1 file changed, 595 insertions(+) create mode 100644 htmlcov/rest_framework_routers.html (limited to 'htmlcov/rest_framework_routers.html') diff --git a/htmlcov/rest_framework_routers.html b/htmlcov/rest_framework_routers.html new file mode 100644 index 00000000..f08d5007 --- /dev/null +++ b/htmlcov/rest_framework_routers.html @@ -0,0 +1,595 @@ + + + + + + + + Coverage for rest_framework/routers: 94% + + + + + + + + + + + +
+ +

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 

+ +
+
+ + + + + -- cgit v1.2.3