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_request.html | 819 ++++++++++++++++++++++++++++++++++++ 1 file changed, 819 insertions(+) create mode 100644 htmlcov/rest_framework_request.html (limited to 'htmlcov/rest_framework_request.html') diff --git a/htmlcov/rest_framework_request.html b/htmlcov/rest_framework_request.html new file mode 100644 index 00000000..03f2c3e3 --- /dev/null +++ b/htmlcov/rest_framework_request.html @@ -0,0 +1,819 @@ + + + + + + + + Coverage for rest_framework/request: 95% + + + + + + + + + + + +
+ +

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

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+

296

+

297

+

298

+

299

+

300

+

301

+

302

+

303

+

304

+

305

+

306

+

307

+

308

+

309

+

310

+

311

+

312

+

313

+

314

+

315

+

316

+

317

+

318

+

319

+

320

+

321

+

322

+

323

+

324

+

325

+

326

+

327

+

328

+

329

+

330

+

331

+

332

+

333

+

334

+

335

+

336

+

337

+

338

+

339

+

340

+

341

+

342

+

343

+

344

+

345

+

346

+

347

+

348

+

349

+

350

+

351

+

352

+

353

+

354

+

355

+

356

+

357

+

358

+

359

+

360

+

361

+

362

+

363

+

364

+

365

+

366

+

367

+

368

+

369

+ +
+

""" 

+

The Request class is used as a wrapper around the standard request object. 

+

 

+

The wrapped request then offers a richer API, in particular : 

+

 

+

    - content automatically parsed according to `Content-Type` header, 

+

      and available as `request.DATA` 

+

    - full support of PUT method, including support for file uploads 

+

    - form overloading of HTTP method, content type and content 

+

""" 

+

from __future__ import unicode_literals 

+

from django.conf import settings 

+

from django.http import QueryDict 

+

from django.http.multipartparser import parse_header 

+

from django.utils.datastructures import MultiValueDict 

+

from rest_framework import HTTP_HEADER_ENCODING 

+

from rest_framework import exceptions 

+

from rest_framework.compat import BytesIO 

+

from rest_framework.settings import api_settings 

+

 

+

 

+

def is_form_media_type(media_type): 

+

    """ 

+

    Return True if the media type is a valid form media type. 

+

    """ 

+

    base_media_type, params = parse_header(media_type.encode(HTTP_HEADER_ENCODING)) 

+

    return (base_media_type == 'application/x-www-form-urlencoded' or 

+

            base_media_type == 'multipart/form-data') 

+

 

+

 

+

class Empty(object): 

+

    """ 

+

    Placeholder for unset attributes. 

+

    Cannot use `None`, as that may be a valid value. 

+

    """ 

+

    pass 

+

 

+

 

+

def _hasattr(obj, name): 

+

    return not getattr(obj, name) is Empty 

+

 

+

 

+

def clone_request(request, method): 

+

    """ 

+

    Internal helper method to clone a request, replacing with a different 

+

    HTTP method.  Used for checking permissions against other methods. 

+

    """ 

+

    ret = Request(request=request._request, 

+

                  parsers=request.parsers, 

+

                  authenticators=request.authenticators, 

+

                  negotiator=request.negotiator, 

+

                  parser_context=request.parser_context) 

+

    ret._data = request._data 

+

    ret._files = request._files 

+

    ret._content_type = request._content_type 

+

    ret._stream = request._stream 

+

    ret._method = method 

+

    if hasattr(request, '_user'): 

+

        ret._user = request._user 

+

    if hasattr(request, '_auth'): 

+

        ret._auth = request._auth 

+

    if hasattr(request, '_authenticator'): 

+

        ret._authenticator = request._authenticator 

+

    return ret 

+

 

+

 

+

class Request(object): 

+

    """ 

+

    Wrapper allowing to enhance a standard `HttpRequest` instance. 

+

 

+

    Kwargs: 

+

        - request(HttpRequest). The original request instance. 

+

        - parsers_classes(list/tuple). The parsers to use for parsing the 

+

          request content. 

+

        - authentication_classes(list/tuple). The authentications used to try 

+

          authenticating the request's user. 

+

    """ 

+

 

+

    _METHOD_PARAM = api_settings.FORM_METHOD_OVERRIDE 

+

    _CONTENT_PARAM = api_settings.FORM_CONTENT_OVERRIDE 

+

    _CONTENTTYPE_PARAM = api_settings.FORM_CONTENTTYPE_OVERRIDE 

+

 

+

    def __init__(self, request, parsers=None, authenticators=None, 

+

                 negotiator=None, parser_context=None): 

+

        self._request = request 

+

        self.parsers = parsers or () 

+

        self.authenticators = authenticators or () 

+

        self.negotiator = negotiator or self._default_negotiator() 

+

        self.parser_context = parser_context 

+

        self._data = Empty 

+

        self._files = Empty 

+

        self._method = Empty 

+

        self._content_type = Empty 

+

        self._stream = Empty 

+

 

+

        if self.parser_context is None: 

+

            self.parser_context = {} 

+

        self.parser_context['request'] = self 

+

        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET 

+

 

+

    def _default_negotiator(self): 

+

        return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS() 

+

 

+

    @property 

+

    def method(self): 

+

        """ 

+

        Returns the HTTP method. 

+

 

+

        This allows the `method` to be overridden by using a hidden `form` 

+

        field on a form POST request. 

+

        """ 

+

        if not _hasattr(self, '_method'): 

+

            self._load_method_and_content_type() 

+

        return self._method 

+

 

+

    @property 

+

    def content_type(self): 

+

        """ 

+

        Returns the content type header. 

+

 

+

        This should be used instead of `request.META.get('HTTP_CONTENT_TYPE')`, 

+

        as it allows the content type to be overridden by using a hidden form 

+

        field on a form POST request. 

+

        """ 

+

        if not _hasattr(self, '_content_type'): 

+

            self._load_method_and_content_type() 

+

        return self._content_type 

+

 

+

    @property 

+

    def stream(self): 

+

        """ 

+

        Returns an object that may be used to stream the request content. 

+

        """ 

+

        if not _hasattr(self, '_stream'): 

+

            self._load_stream() 

+

        return self._stream 

+

 

+

    @property 

+

    def QUERY_PARAMS(self): 

+

        """ 

+

        More semantically correct name for request.GET. 

+

        """ 

+

        return self._request.GET 

+

 

+

    @property 

+

    def DATA(self): 

+

        """ 

+

        Parses the request body and returns the data. 

+

 

+

        Similar to usual behaviour of `request.POST`, except that it handles 

+

        arbitrary parsers, and also works on methods other than POST (eg PUT). 

+

        """ 

+

        if not _hasattr(self, '_data'): 

+

            self._load_data_and_files() 

+

        return self._data 

+

 

+

    @property 

+

    def FILES(self): 

+

        """ 

+

        Parses the request body and returns any files uploaded in the request. 

+

 

+

        Similar to usual behaviour of `request.FILES`, except that it handles 

+

        arbitrary parsers, and also works on methods other than POST (eg PUT). 

+

        """ 

+

        if not _hasattr(self, '_files'): 

+

            self._load_data_and_files() 

+

        return self._files 

+

 

+

    @property 

+

    def user(self): 

+

        """ 

+

        Returns the user associated with the current request, as authenticated 

+

        by the authentication classes provided to the request. 

+

        """ 

+

        if not hasattr(self, '_user'): 

+

            self._authenticate() 

+

        return self._user 

+

 

+

    @user.setter 

+

    def user(self, value): 

+

        """ 

+

        Sets the user on the current request. This is necessary to maintain 

+

        compatilbility with django.contrib.auth where the user proprety is 

+

        set in the login and logout functions. 

+

        """ 

+

        self._user = value 

+

 

+

    @property 

+

    def auth(self): 

+

        """ 

+

        Returns any non-user authentication information associated with the 

+

        request, such as an authentication token. 

+

        """ 

+

        if not hasattr(self, '_auth'): 

+

            self._authenticate() 

+

        return self._auth 

+

 

+

    @auth.setter 

+

    def auth(self, value): 

+

        """ 

+

        Sets any non-user authentication information associated with the 

+

        request, such as an authentication token. 

+

        """ 

+

        self._auth = value 

+

 

+

    @property 

+

    def successful_authenticator(self): 

+

        """ 

+

        Return the instance of the authentication instance class that was used 

+

        to authenticate the request, or `None`. 

+

        """ 

+

        if not hasattr(self, '_authenticator'): 

+

            self._authenticate() 

+

        return self._authenticator 

+

 

+

    def _load_data_and_files(self): 

+

        """ 

+

        Parses the request content into self.DATA and self.FILES. 

+

        """ 

+

        if not _hasattr(self, '_content_type'): 

+

            self._load_method_and_content_type() 

+

 

+

        if not _hasattr(self, '_data'): 

+

            self._data, self._files = self._parse() 

+

 

+

    def _load_method_and_content_type(self): 

+

        """ 

+

        Sets the method and content_type, and then check if they've 

+

        been overridden. 

+

        """ 

+

        self._content_type = self.META.get('HTTP_CONTENT_TYPE', 

+

                                           self.META.get('CONTENT_TYPE', '')) 

+

 

+

        self._perform_form_overloading() 

+

 

+

        if not _hasattr(self, '_method'): 

+

            self._method = self._request.method 

+

 

+

            if self._method == 'POST': 

+

                # Allow X-HTTP-METHOD-OVERRIDE header 

+

                self._method = self.META.get('HTTP_X_HTTP_METHOD_OVERRIDE', 

+

                                             self._method) 

+

 

+

    def _load_stream(self): 

+

        """ 

+

        Return the content body of the request, as a stream. 

+

        """ 

+

        try: 

+

            content_length = int(self.META.get('CONTENT_LENGTH', 

+

                                    self.META.get('HTTP_CONTENT_LENGTH'))) 

+

        except (ValueError, TypeError): 

+

            content_length = 0 

+

 

+

        if content_length == 0: 

+

            self._stream = None 

+

        elif hasattr(self._request, 'read'): 

+

            self._stream = self._request 

+

        else: 

+

            self._stream = BytesIO(self.raw_post_data) 

+

 

+

    def _perform_form_overloading(self): 

+

        """ 

+

        If this is a form POST request, then we need to check if the method and 

+

        content/content_type have been overridden by setting them in hidden 

+

        form fields or not. 

+

        """ 

+

 

+

        USE_FORM_OVERLOADING = ( 

+

            self._METHOD_PARAM or 

+

            (self._CONTENT_PARAM and self._CONTENTTYPE_PARAM) 

+

        ) 

+

 

+

        # We only need to use form overloading on form POST requests. 

+

        if (not USE_FORM_OVERLOADING 

+

            or self._request.method != 'POST' 

+

            or not is_form_media_type(self._content_type)): 

+

            return 

+

 

+

        # At this point we're committed to parsing the request as form data. 

+

        self._data = self._request.POST 

+

        self._files = self._request.FILES 

+

 

+

        # Method overloading - change the method and remove the param from the content. 

+

        if (self._METHOD_PARAM and 

+

            self._METHOD_PARAM in self._data): 

+

            self._method = self._data[self._METHOD_PARAM].upper() 

+

 

+

        # Content overloading - modify the content type, and force re-parse. 

+

        if (self._CONTENT_PARAM and 

+

            self._CONTENTTYPE_PARAM and 

+

            self._CONTENT_PARAM in self._data and 

+

            self._CONTENTTYPE_PARAM in self._data): 

+

            self._content_type = self._data[self._CONTENTTYPE_PARAM] 

+

            self._stream = BytesIO(self._data[self._CONTENT_PARAM].encode(HTTP_HEADER_ENCODING)) 

+

            self._data, self._files = (Empty, Empty) 

+

 

+

    def _parse(self): 

+

        """ 

+

        Parse the request content, returning a two-tuple of (data, files) 

+

 

+

        May raise an `UnsupportedMediaType`, or `ParseError` exception. 

+

        """ 

+

        stream = self.stream 

+

        media_type = self.content_type 

+

 

+

        if stream is None or media_type is None: 

+

            empty_data = QueryDict('', self._request._encoding) 

+

            empty_files = MultiValueDict() 

+

            return (empty_data, empty_files) 

+

 

+

        parser = self.negotiator.select_parser(self, self.parsers) 

+

 

+

        if not parser: 

+

            raise exceptions.UnsupportedMediaType(media_type) 

+

 

+

        parsed = parser.parse(stream, media_type, self.parser_context) 

+

 

+

        # Parser classes may return the raw data, or a 

+

        # DataAndFiles object.  Unpack the result as required. 

+

        try: 

+

            return (parsed.data, parsed.files) 

+

        except AttributeError: 

+

            empty_files = MultiValueDict() 

+

            return (parsed, empty_files) 

+

 

+

    def _authenticate(self): 

+

        """ 

+

        Attempt to authenticate the request using each authentication instance 

+

        in turn. 

+

        Returns a three-tuple of (authenticator, user, authtoken). 

+

        """ 

+

        for authenticator in self.authenticators: 

+

            try: 

+

                user_auth_tuple = authenticator.authenticate(self) 

+

            except exceptions.APIException: 

+

                self._not_authenticated() 

+

                raise 

+

 

+

            if not user_auth_tuple is None: 

+

                self._authenticator = authenticator 

+

                self._user, self._auth = user_auth_tuple 

+

                return 

+

 

+

        self._not_authenticated() 

+

 

+

    def _not_authenticated(self): 

+

        """ 

+

        Return a three-tuple of (authenticator, user, authtoken), representing 

+

        an unauthenticated request. 

+

 

+

        By default this will be (None, AnonymousUser, None). 

+

        """ 

+

        self._authenticator = None 

+

 

+

        if api_settings.UNAUTHENTICATED_USER: 

+

            self._user = api_settings.UNAUTHENTICATED_USER() 

+

        else: 

+

            self._user = None 

+

 

+

        if api_settings.UNAUTHENTICATED_TOKEN: 

+

            self._auth = api_settings.UNAUTHENTICATED_TOKEN() 

+

        else: 

+

            self._auth = None 

+

 

+

    def __getattr__(self, attr): 

+

        """ 

+

        Proxy other attributes to the underlying HttpRequest object. 

+

        """ 

+

        return getattr(self._request, attr) 

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