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

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

""" 

Generic views that provide commonly needed behaviour. 

""" 

from __future__ import unicode_literals 

 

from django.core.exceptions import ImproperlyConfigured, PermissionDenied 

from django.core.paginator import Paginator, InvalidPage 

from django.http import Http404 

from django.shortcuts import get_object_or_404 as _get_object_or_404 

from django.utils.translation import ugettext as _ 

from rest_framework import views, mixins, exceptions 

from rest_framework.request import clone_request 

from rest_framework.settings import api_settings 

import warnings 

 

 

def get_object_or_404(queryset, **filter_kwargs): 

    """ 

    Same as Django's standard shortcut, but make sure to raise 404 

    if the filter_kwargs don't match the required types. 

    """ 

    try: 

        return _get_object_or_404(queryset, **filter_kwargs) 

    except (TypeError, ValueError): 

        raise Http404 

 

 

class GenericAPIView(views.APIView): 

    """ 

    Base class for all other generic views. 

    """ 

 

    # You'll need to either set these attributes, 

    # or override `get_queryset()`/`get_serializer_class()`. 

    queryset = None 

    serializer_class = None 

 

    # This shortcut may be used instead of setting either or both 

    # of the `queryset`/`serializer_class` attributes, although using 

    # the explicit style is generally preferred. 

    model = None 

 

    # If you want to use object lookups other than pk, set this attribute. 

    # For more complex lookup requirements override `get_object()`. 

    lookup_field = 'pk' 

 

    # Pagination settings 

    paginate_by = api_settings.PAGINATE_BY 

    paginate_by_param = api_settings.PAGINATE_BY_PARAM 

    pagination_serializer_class = api_settings.DEFAULT_PAGINATION_SERIALIZER_CLASS 

    page_kwarg = 'page' 

 

    # The filter backend classes to use for queryset filtering 

    filter_backends = api_settings.DEFAULT_FILTER_BACKENDS 

 

    # The following attributes may be subject to change, 

    # and should be considered private API. 

    model_serializer_class = api_settings.DEFAULT_MODEL_SERIALIZER_CLASS 

    paginator_class = Paginator 

 

    ###################################### 

    # These are pending deprecation... 

 

    pk_url_kwarg = 'pk' 

    slug_url_kwarg = 'slug' 

    slug_field = 'slug' 

    allow_empty = True 

    filter_backend = api_settings.FILTER_BACKEND 

 

    def get_serializer_context(self): 

        """ 

        Extra context provided to the serializer class. 

        """ 

        return { 

            'request': self.request, 

            'format': self.format_kwarg, 

            'view': self 

        } 

 

    def get_serializer(self, instance=None, data=None, 

                       files=None, many=False, partial=False): 

        """ 

        Return the serializer instance that should be used for validating and 

        deserializing input, and for serializing output. 

        """ 

        serializer_class = self.get_serializer_class() 

        context = self.get_serializer_context() 

        return serializer_class(instance, data=data, files=files, 

                                many=many, partial=partial, context=context) 

 

    def get_pagination_serializer(self, page): 

        """ 

        Return a serializer instance to use with paginated data. 

        """ 

        class SerializerClass(self.pagination_serializer_class): 

            class Meta: 

                object_serializer_class = self.get_serializer_class() 

 

        pagination_serializer_class = SerializerClass 

        context = self.get_serializer_context() 

        return pagination_serializer_class(instance=page, context=context) 

 

    def paginate_queryset(self, queryset, page_size=None): 

        """ 

        Paginate a queryset if required, either returning a page object, 

        or `None` if pagination is not configured for this view. 

        """ 

        deprecated_style = False 

        if page_size is not None: 

            warnings.warn('The `page_size` parameter to `paginate_queryset()` ' 

                          'is due to be deprecated. ' 

                          'Note that the return style of this method is also ' 

                          'changed, and will simply return a page object ' 

                          'when called without a `page_size` argument.', 

                          PendingDeprecationWarning, stacklevel=2) 

            deprecated_style = True 

        else: 

            # Determine the required page size. 

            # If pagination is not configured, simply return None. 

            page_size = self.get_paginate_by() 

            if not page_size: 

                return None 

 

        if not self.allow_empty: 

            warnings.warn( 

                'The `allow_empty` parameter is due to be deprecated. ' 

                'To use `allow_empty=False` style behavior, You should override ' 

                '`get_queryset()` and explicitly raise a 404 on empty querysets.', 

                PendingDeprecationWarning, stacklevel=2 

            ) 

 

        paginator = self.paginator_class(queryset, page_size, 

                                         allow_empty_first_page=self.allow_empty) 

        page_kwarg = self.kwargs.get(self.page_kwarg) 

        page_query_param = self.request.QUERY_PARAMS.get(self.page_kwarg) 

        page = page_kwarg or page_query_param or 1 

        try: 

            page_number = int(page) 

        except ValueError: 

            if page == 'last': 

                page_number = paginator.num_pages 

            else: 

                raise Http404(_("Page is not 'last', nor can it be converted to an int.")) 

        try: 

            page = paginator.page(page_number) 

        except InvalidPage as e: 

            raise Http404(_('Invalid page (%(page_number)s): %(message)s') % { 

                                'page_number': page_number, 

                                'message': str(e) 

            }) 

 

        if deprecated_style: 

            return (paginator, page, page.object_list, page.has_other_pages()) 

        return page 

 

    def filter_queryset(self, queryset): 

        """ 

        Given a queryset, filter it with whichever filter backend is in use. 

 

        You are unlikely to want to override this method, although you may need 

        to call it either from a list view, or from a custom `get_object` 

        method if you want to apply the configured filtering backend to the 

        default queryset. 

        """ 

        filter_backends = self.filter_backends or [] 

        if not filter_backends and self.filter_backend: 

            warnings.warn( 

                'The `filter_backend` attribute and `FILTER_BACKEND` setting ' 

                'are due to be deprecated in favor of a `filter_backends` ' 

                'attribute and `DEFAULT_FILTER_BACKENDS` setting, that take ' 

                'a *list* of filter backend classes.', 

                PendingDeprecationWarning, stacklevel=2 

            ) 

            filter_backends = [self.filter_backend] 

 

        for backend in filter_backends: 

            queryset = backend().filter_queryset(self.request, queryset, self) 

        return queryset 

 

    ######################## 

    ### The following methods provide default implementations 

    ### that you may want to override for more complex cases. 

 

    def get_paginate_by(self, queryset=None): 

        """ 

        Return the size of pages to use with pagination. 

 

        If `PAGINATE_BY_PARAM` is set it will attempt to get the page size 

        from a named query parameter in the url, eg. ?page_size=100 

 

        Otherwise defaults to using `self.paginate_by`. 

        """ 

        if queryset is not None: 

            warnings.warn('The `queryset` parameter to `get_paginate_by()` ' 

                          'is due to be deprecated.', 

                          PendingDeprecationWarning, stacklevel=2) 

 

        if self.paginate_by_param: 

            query_params = self.request.QUERY_PARAMS 

            try: 

                return int(query_params[self.paginate_by_param]) 

            except (KeyError, ValueError): 

                pass 

 

        return self.paginate_by 

 

    def get_serializer_class(self): 

        """ 

        Return the class to use for the serializer. 

        Defaults to using `self.serializer_class`. 

 

        You may want to override this if you need to provide different 

        serializations depending on the incoming request. 

 

        (Eg. admins get full serialization, others get basic serialization) 

        """ 

        serializer_class = self.serializer_class 

        if serializer_class is not None: 

            return serializer_class 

 

        assert self.model is not None, \ 

            "'%s' should either include a 'serializer_class' attribute, " \ 

            "or use the 'model' attribute as a shortcut for " \ 

            "automatically generating a serializer class." \ 

            % self.__class__.__name__ 

 

        class DefaultSerializer(self.model_serializer_class): 

            class Meta: 

                model = self.model 

        return DefaultSerializer 

 

    def get_queryset(self): 

        """ 

        Get the list of items for this view. 

        This must be an iterable, and may be a queryset. 

        Defaults to using `self.queryset`. 

 

        You may want to override this if you need to provide different 

        querysets depending on the incoming request. 

 

        (Eg. return a list of items that is specific to the user) 

        """ 

        if self.queryset is not None: 

            return self.queryset._clone() 

 

        if self.model is not None: 

            return self.model._default_manager.all() 

 

        raise ImproperlyConfigured("'%s' must define 'queryset' or 'model'" 

                                    % self.__class__.__name__) 

 

    def get_object(self, queryset=None): 

        """ 

        Returns the object the view is displaying. 

 

        You may want to override this if you need to provide non-standard 

        queryset lookups.  Eg if objects are referenced using multiple 

        keyword arguments in the url conf. 

        """ 

        # Determine the base queryset to use. 

        if queryset is None: 

            queryset = self.filter_queryset(self.get_queryset()) 

        else: 

            pass  # Deprecation warning 

 

        # Perform the lookup filtering. 

        pk = self.kwargs.get(self.pk_url_kwarg, None) 

        slug = self.kwargs.get(self.slug_url_kwarg, None) 

        lookup = self.kwargs.get(self.lookup_field, None) 

 

        if lookup is not None: 

            filter_kwargs = {self.lookup_field: lookup} 

        elif pk is not None and self.lookup_field == 'pk': 

            warnings.warn( 

                'The `pk_url_kwarg` attribute is due to be deprecated. ' 

                'Use the `lookup_field` attribute instead', 

                PendingDeprecationWarning 

            ) 

            filter_kwargs = {'pk': pk} 

        elif slug is not None and self.lookup_field == 'pk': 

            warnings.warn( 

                'The `slug_url_kwarg` attribute is due to be deprecated. ' 

                'Use the `lookup_field` attribute instead', 

                PendingDeprecationWarning 

            ) 

            filter_kwargs = {self.slug_field: slug} 

        else: 

            raise ImproperlyConfigured( 

                'Expected view %s to be called with a URL keyword argument ' 

                'named "%s". Fix your URL conf, or set the `.lookup_field` ' 

                'attribute on the view correctly.' % 

                (self.__class__.__name__, self.lookup_field) 

            ) 

 

        obj = get_object_or_404(queryset, **filter_kwargs) 

 

        # May raise a permission denied 

        self.check_object_permissions(self.request, obj) 

 

        return obj 

 

    ######################## 

    ### The following are placeholder methods, 

    ### and are intended to be overridden. 

    ### 

    ### The are not called by GenericAPIView directly, 

    ### but are used by the mixin methods. 

 

    def pre_save(self, obj): 

        """ 

        Placeholder method for calling before saving an object. 

 

        May be used to set attributes on the object that are implicit 

        in either the request, or the url. 

        """ 

        pass 

 

    def post_save(self, obj, created=False): 

        """ 

        Placeholder method for calling after saving an object. 

        """ 

        pass 

 

    def metadata(self, request): 

        """ 

        Return a dictionary of metadata about the view. 

        Used to return responses for OPTIONS requests. 

 

        We override the default behavior, and add some extra information 

        about the required request body for POST and PUT operations. 

        """ 

        ret = super(GenericAPIView, self).metadata(request) 

 

        actions = {} 

        for method in ('PUT', 'POST'): 

            if method not in self.allowed_methods: 

                continue 

 

            cloned_request = clone_request(request, method) 

            try: 

                # Test global permissions 

                self.check_permissions(cloned_request) 

                # Test object permissions 

                if method == 'PUT': 

                    self.get_object() 

            except (exceptions.APIException, PermissionDenied, Http404): 

                pass 

            else: 

                # If user has appropriate permissions for the view, include 

                # appropriate metadata about the fields that should be supplied. 

                serializer = self.get_serializer() 

                actions[method] = serializer.metadata() 

 

        if actions: 

            ret['actions'] = actions 

 

        return ret 

 

 

########################################################## 

### Concrete view classes that provide method handlers ### 

### by composing the mixin classes with the base view. ### 

########################################################## 

 

class CreateAPIView(mixins.CreateModelMixin, 

                    GenericAPIView): 

 

    """ 

    Concrete view for creating a model instance. 

    """ 

    def post(self, request, *args, **kwargs): 

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

 

 

class ListAPIView(mixins.ListModelMixin, 

                  GenericAPIView): 

    """ 

    Concrete view for listing a queryset. 

    """ 

    def get(self, request, *args, **kwargs): 

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

 

 

class RetrieveAPIView(mixins.RetrieveModelMixin, 

                      GenericAPIView): 

    """ 

    Concrete view for retrieving a model instance. 

    """ 

    def get(self, request, *args, **kwargs): 

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

 

 

class DestroyAPIView(mixins.DestroyModelMixin, 

                     GenericAPIView): 

 

    """ 

    Concrete view for deleting a model instance. 

    """ 

    def delete(self, request, *args, **kwargs): 

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

 

 

class UpdateAPIView(mixins.UpdateModelMixin, 

                    GenericAPIView): 

 

    """ 

    Concrete view for updating a model instance. 

    """ 

    def put(self, request, *args, **kwargs): 

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

 

    def patch(self, request, *args, **kwargs): 

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

 

 

class ListCreateAPIView(mixins.ListModelMixin, 

                        mixins.CreateModelMixin, 

                        GenericAPIView): 

    """ 

    Concrete view for listing a queryset or creating a model instance. 

    """ 

    def get(self, request, *args, **kwargs): 

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

 

    def post(self, request, *args, **kwargs): 

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

 

 

class RetrieveUpdateAPIView(mixins.RetrieveModelMixin, 

                            mixins.UpdateModelMixin, 

                            GenericAPIView): 

    """ 

    Concrete view for retrieving, updating a model instance. 

    """ 

    def get(self, request, *args, **kwargs): 

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

 

    def put(self, request, *args, **kwargs): 

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

 

    def patch(self, request, *args, **kwargs): 

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

 

 

class RetrieveDestroyAPIView(mixins.RetrieveModelMixin, 

                             mixins.DestroyModelMixin, 

                             GenericAPIView): 

    """ 

    Concrete view for retrieving or deleting a model instance. 

    """ 

    def get(self, request, *args, **kwargs): 

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

 

    def delete(self, request, *args, **kwargs): 

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

 

 

class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, 

                                   mixins.UpdateModelMixin, 

                                   mixins.DestroyModelMixin, 

                                   GenericAPIView): 

    """ 

    Concrete view for retrieving, updating or deleting a model instance. 

    """ 

    def get(self, request, *args, **kwargs): 

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

 

    def put(self, request, *args, **kwargs): 

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

 

    def patch(self, request, *args, **kwargs): 

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

 

    def delete(self, request, *args, **kwargs): 

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

 

 

########################## 

### Deprecated classes ### 

########################## 

 

class MultipleObjectAPIView(GenericAPIView): 

    def __init__(self, *args, **kwargs): 

        warnings.warn( 

            'Subclassing `MultipleObjectAPIView` is due to be deprecated. ' 

            'You should simply subclass `GenericAPIView` instead.', 

            PendingDeprecationWarning, stacklevel=2 

        ) 

        super(MultipleObjectAPIView, self).__init__(*args, **kwargs) 

 

 

class SingleObjectAPIView(GenericAPIView): 

    def __init__(self, *args, **kwargs): 

        warnings.warn( 

            'Subclassing `SingleObjectAPIView` is due to be deprecated. ' 

            'You should simply subclass `GenericAPIView` instead.', 

            PendingDeprecationWarning, stacklevel=2 

        ) 

        super(SingleObjectAPIView, self).__init__(*args, **kwargs)