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_serializers.html | 2011 +++++++++++++++++++++++++++++++ 1 file changed, 2011 insertions(+) create mode 100644 htmlcov/rest_framework_serializers.html (limited to 'htmlcov/rest_framework_serializers.html') diff --git a/htmlcov/rest_framework_serializers.html b/htmlcov/rest_framework_serializers.html new file mode 100644 index 00000000..79dc5647 --- /dev/null +++ b/htmlcov/rest_framework_serializers.html @@ -0,0 +1,2011 @@ + + + + + + + + Coverage for rest_framework/serializers: 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

+

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

+

500

+

501

+

502

+

503

+

504

+

505

+

506

+

507

+

508

+

509

+

510

+

511

+

512

+

513

+

514

+

515

+

516

+

517

+

518

+

519

+

520

+

521

+

522

+

523

+

524

+

525

+

526

+

527

+

528

+

529

+

530

+

531

+

532

+

533

+

534

+

535

+

536

+

537

+

538

+

539

+

540

+

541

+

542

+

543

+

544

+

545

+

546

+

547

+

548

+

549

+

550

+

551

+

552

+

553

+

554

+

555

+

556

+

557

+

558

+

559

+

560

+

561

+

562

+

563

+

564

+

565

+

566

+

567

+

568

+

569

+

570

+

571

+

572

+

573

+

574

+

575

+

576

+

577

+

578

+

579

+

580

+

581

+

582

+

583

+

584

+

585

+

586

+

587

+

588

+

589

+

590

+

591

+

592

+

593

+

594

+

595

+

596

+

597

+

598

+

599

+

600

+

601

+

602

+

603

+

604

+

605

+

606

+

607

+

608

+

609

+

610

+

611

+

612

+

613

+

614

+

615

+

616

+

617

+

618

+

619

+

620

+

621

+

622

+

623

+

624

+

625

+

626

+

627

+

628

+

629

+

630

+

631

+

632

+

633

+

634

+

635

+

636

+

637

+

638

+

639

+

640

+

641

+

642

+

643

+

644

+

645

+

646

+

647

+

648

+

649

+

650

+

651

+

652

+

653

+

654

+

655

+

656

+

657

+

658

+

659

+

660

+

661

+

662

+

663

+

664

+

665

+

666

+

667

+

668

+

669

+

670

+

671

+

672

+

673

+

674

+

675

+

676

+

677

+

678

+

679

+

680

+

681

+

682

+

683

+

684

+

685

+

686

+

687

+

688

+

689

+

690

+

691

+

692

+

693

+

694

+

695

+

696

+

697

+

698

+

699

+

700

+

701

+

702

+

703

+

704

+

705

+

706

+

707

+

708

+

709

+

710

+

711

+

712

+

713

+

714

+

715

+

716

+

717

+

718

+

719

+

720

+

721

+

722

+

723

+

724

+

725

+

726

+

727

+

728

+

729

+

730

+

731

+

732

+

733

+

734

+

735

+

736

+

737

+

738

+

739

+

740

+

741

+

742

+

743

+

744

+

745

+

746

+

747

+

748

+

749

+

750

+

751

+

752

+

753

+

754

+

755

+

756

+

757

+

758

+

759

+

760

+

761

+

762

+

763

+

764

+

765

+

766

+

767

+

768

+

769

+

770

+

771

+

772

+

773

+

774

+

775

+

776

+

777

+

778

+

779

+

780

+

781

+

782

+

783

+

784

+

785

+

786

+

787

+

788

+

789

+

790

+

791

+

792

+

793

+

794

+

795

+

796

+

797

+

798

+

799

+

800

+

801

+

802

+

803

+

804

+

805

+

806

+

807

+

808

+

809

+

810

+

811

+

812

+

813

+

814

+

815

+

816

+

817

+

818

+

819

+

820

+

821

+

822

+

823

+

824

+

825

+

826

+

827

+

828

+

829

+

830

+

831

+

832

+

833

+

834

+

835

+

836

+

837

+

838

+

839

+

840

+

841

+

842

+

843

+

844

+

845

+

846

+

847

+

848

+

849

+

850

+

851

+

852

+

853

+

854

+

855

+

856

+

857

+

858

+

859

+

860

+

861

+

862

+

863

+

864

+

865

+

866

+

867

+

868

+

869

+

870

+

871

+

872

+

873

+

874

+

875

+

876

+

877

+

878

+

879

+

880

+

881

+

882

+

883

+

884

+

885

+

886

+

887

+

888

+

889

+

890

+

891

+

892

+

893

+

894

+

895

+

896

+

897

+

898

+

899

+

900

+

901

+

902

+

903

+

904

+

905

+

906

+

907

+

908

+

909

+

910

+

911

+

912

+

913

+

914

+

915

+

916

+

917

+

918

+

919

+

920

+

921

+

922

+

923

+

924

+

925

+

926

+

927

+

928

+

929

+

930

+

931

+

932

+

933

+

934

+

935

+

936

+

937

+

938

+

939

+

940

+

941

+

942

+

943

+

944

+

945

+

946

+

947

+

948

+

949

+

950

+

951

+

952

+

953

+

954

+

955

+

956

+

957

+

958

+

959

+

960

+

961

+

962

+

963

+

964

+

965

+ +
+

""" 

+

Serializers and ModelSerializers are similar to Forms and ModelForms. 

+

Unlike forms, they are not constrained to dealing with HTML output, and 

+

form encoded input. 

+

 

+

Serialization in REST framework is a two-phase process: 

+

 

+

1. Serializers marshal between complex types like model instances, and 

+

python primatives. 

+

2. The process of marshalling between python primatives and request and 

+

response content is handled by parsers and renderers. 

+

""" 

+

from __future__ import unicode_literals 

+

import copy 

+

import datetime 

+

import types 

+

from decimal import Decimal 

+

from django.core.paginator import Page 

+

from django.db import models 

+

from django.forms import widgets 

+

from django.utils.datastructures import SortedDict 

+

from rest_framework.compat import get_concrete_model, six 

+

 

+

# Note: We do the following so that users of the framework can use this style: 

+

# 

+

#     example_field = serializers.CharField(...) 

+

# 

+

# This helps keep the separation between model fields, form fields, and 

+

# serializer fields more explicit. 

+

 

+

from rest_framework.relations import * 

+

from rest_framework.fields import * 

+

 

+

 

+

class NestedValidationError(ValidationError): 

+

    """ 

+

    The default ValidationError behavior is to stringify each item in the list 

+

    if the messages are a list of error messages. 

+

 

+

    In the case of nested serializers, where the parent has many children, 

+

    then the child's `serializer.errors` will be a list of dicts.  In the case 

+

    of a single child, the `serializer.errors` will be a dict. 

+

 

+

    We need to override the default behavior to get properly nested error dicts. 

+

    """ 

+

 

+

    def __init__(self, message): 

+

        if isinstance(message, dict): 

+

            self.messages = [message] 

+

        else: 

+

            self.messages = message 

+

 

+

 

+

class DictWithMetadata(dict): 

+

    """ 

+

    A dict-like object, that can have additional properties attached. 

+

    """ 

+

    def __getstate__(self): 

+

        """ 

+

        Used by pickle (e.g., caching). 

+

        Overridden to remove the metadata from the dict, since it shouldn't be 

+

        pickled and may in some instances be unpickleable. 

+

        """ 

+

        return dict(self) 

+

 

+

 

+

class SortedDictWithMetadata(SortedDict): 

+

    """ 

+

    A sorted dict-like object, that can have additional properties attached. 

+

    """ 

+

    def __getstate__(self): 

+

        """ 

+

        Used by pickle (e.g., caching). 

+

        Overriden to remove the metadata from the dict, since it shouldn't be 

+

        pickle and may in some instances be unpickleable. 

+

        """ 

+

        return SortedDict(self).__dict__ 

+

 

+

 

+

def _is_protected_type(obj): 

+

    """ 

+

    True if the object is a native datatype that does not need to 

+

    be serialized further. 

+

    """ 

+

    return isinstance(obj, ( 

+

        types.NoneType, 

+

        int, long, 

+

        datetime.datetime, datetime.date, datetime.time, 

+

        float, Decimal, 

+

        basestring) 

+

    ) 

+

 

+

 

+

def _get_declared_fields(bases, attrs): 

+

    """ 

+

    Create a list of serializer field instances from the passed in 'attrs', 

+

    plus any fields on the base classes (in 'bases'). 

+

 

+

    Note that all fields from the base classes are used. 

+

    """ 

+

    fields = [(field_name, attrs.pop(field_name)) 

+

              for field_name, obj in list(six.iteritems(attrs)) 

+

              if isinstance(obj, Field)] 

+

    fields.sort(key=lambda x: x[1].creation_counter) 

+

 

+

    # If this class is subclassing another Serializer, add that Serializer's 

+

    # fields.  Note that we loop over the bases in *reverse*. This is necessary 

+

    # in order to maintain the correct order of fields. 

+

    for base in bases[::-1]: 

+

        if hasattr(base, 'base_fields'): 

+

            fields = list(base.base_fields.items()) + fields 

+

 

+

    return SortedDict(fields) 

+

 

+

 

+

class SerializerMetaclass(type): 

+

    def __new__(cls, name, bases, attrs): 

+

        attrs['base_fields'] = _get_declared_fields(bases, attrs) 

+

        return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs) 

+

 

+

 

+

class SerializerOptions(object): 

+

    """ 

+

    Meta class options for Serializer 

+

    """ 

+

    def __init__(self, meta): 

+

        self.depth = getattr(meta, 'depth', 0) 

+

        self.fields = getattr(meta, 'fields', ()) 

+

        self.exclude = getattr(meta, 'exclude', ()) 

+

 

+

 

+

class BaseSerializer(WritableField): 

+

    """ 

+

    This is the Serializer implementation. 

+

    We need to implement it as `BaseSerializer` due to metaclass magicks. 

+

    """ 

+

    class Meta(object): 

+

        pass 

+

 

+

    _options_class = SerializerOptions 

+

    _dict_class = SortedDictWithMetadata 

+

 

+

    def __init__(self, instance=None, data=None, files=None, 

+

                 context=None, partial=False, many=None, 

+

                 allow_add_remove=False, **kwargs): 

+

        super(BaseSerializer, self).__init__(**kwargs) 

+

        self.opts = self._options_class(self.Meta) 

+

        self.parent = None 

+

        self.root = None 

+

        self.partial = partial 

+

        self.many = many 

+

        self.allow_add_remove = allow_add_remove 

+

 

+

        self.context = context or {} 

+

 

+

        self.init_data = data 

+

        self.init_files = files 

+

        self.object = instance 

+

        self.fields = self.get_fields() 

+

 

+

        self._data = None 

+

        self._files = None 

+

        self._errors = None 

+

        self._deleted = None 

+

 

+

        if many and instance is not None and not hasattr(instance, '__iter__'): 

+

            raise ValueError('instance should be a queryset or other iterable with many=True') 

+

 

+

        if allow_add_remove and not many: 

+

            raise ValueError('allow_add_remove should only be used for bulk updates, but you have not set many=True') 

+

 

+

    ##### 

+

    # Methods to determine which fields to use when (de)serializing objects. 

+

 

+

    def get_default_fields(self): 

+

        """ 

+

        Return the complete set of default fields for the object, as a dict. 

+

        """ 

+

        return {} 

+

 

+

    def get_fields(self): 

+

        """ 

+

        Returns the complete set of fields for the object as a dict. 

+

 

+

        This will be the set of any explicitly declared fields, 

+

        plus the set of fields returned by get_default_fields(). 

+

        """ 

+

        ret = SortedDict() 

+

 

+

        # Get the explicitly declared fields 

+

        base_fields = copy.deepcopy(self.base_fields) 

+

        for key, field in base_fields.items(): 

+

            ret[key] = field 

+

 

+

        # Add in the default fields 

+

        default_fields = self.get_default_fields() 

+

        for key, val in default_fields.items(): 

+

            if key not in ret: 

+

                ret[key] = val 

+

 

+

        # If 'fields' is specified, use those fields, in that order. 

+

        if self.opts.fields: 

+

            assert isinstance(self.opts.fields, (list, tuple)), '`fields` must be a list or tuple' 

+

            new = SortedDict() 

+

            for key in self.opts.fields: 

+

                new[key] = ret[key] 

+

            ret = new 

+

 

+

        # Remove anything in 'exclude' 

+

        if self.opts.exclude: 

+

            assert isinstance(self.opts.exclude, (list, tuple)), '`exclude` must be a list or tuple' 

+

            for key in self.opts.exclude: 

+

                ret.pop(key, None) 

+

 

+

        for key, field in ret.items(): 

+

            field.initialize(parent=self, field_name=key) 

+

 

+

        return ret 

+

 

+

    ##### 

+

    # Methods to convert or revert from objects <--> primitive representations. 

+

 

+

    def get_field_key(self, field_name): 

+

        """ 

+

        Return the key that should be used for a given field. 

+

        """ 

+

        return field_name 

+

 

+

    def restore_fields(self, data, files): 

+

        """ 

+

        Core of deserialization, together with `restore_object`. 

+

        Converts a dictionary of data into a dictionary of deserialized fields. 

+

        """ 

+

        reverted_data = {} 

+

 

+

        if data is not None and not isinstance(data, dict): 

+

            self._errors['non_field_errors'] = ['Invalid data'] 

+

            return None 

+

 

+

        for field_name, field in self.fields.items(): 

+

            field.initialize(parent=self, field_name=field_name) 

+

            try: 

+

                field.field_from_native(data, files, field_name, reverted_data) 

+

            except ValidationError as err: 

+

                self._errors[field_name] = list(err.messages) 

+

 

+

        return reverted_data 

+

 

+

    def perform_validation(self, attrs): 

+

        """ 

+

        Run `validate_<fieldname>()` and `validate()` methods on the serializer 

+

        """ 

+

        for field_name, field in self.fields.items(): 

+

            if field_name in self._errors: 

+

                continue 

+

            try: 

+

                validate_method = getattr(self, 'validate_%s' % field_name, None) 

+

                if validate_method: 

+

                    source = field.source or field_name 

+

                    attrs = validate_method(attrs, source) 

+

            except ValidationError as err: 

+

                self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages) 

+

 

+

        # If there are already errors, we don't run .validate() because 

+

        # field-validation failed and thus `attrs` may not be complete. 

+

        # which in turn can cause inconsistent validation errors. 

+

        if not self._errors: 

+

            try: 

+

                attrs = self.validate(attrs) 

+

            except ValidationError as err: 

+

                if hasattr(err, 'message_dict'): 

+

                    for field_name, error_messages in err.message_dict.items(): 

+

                        self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) 

+

                elif hasattr(err, 'messages'): 

+

                    self._errors['non_field_errors'] = err.messages 

+

 

+

        return attrs 

+

 

+

    def validate(self, attrs): 

+

        """ 

+

        Stub method, to be overridden in Serializer subclasses 

+

        """ 

+

        return attrs 

+

 

+

    def restore_object(self, attrs, instance=None): 

+

        """ 

+

        Deserialize a dictionary of attributes into an object instance. 

+

        You should override this method to control how deserialized objects 

+

        are instantiated. 

+

        """ 

+

        if instance is not None: 

+

            instance.update(attrs) 

+

            return instance 

+

        return attrs 

+

 

+

    def to_native(self, obj): 

+

        """ 

+

        Serialize objects -> primitives. 

+

        """ 

+

        ret = self._dict_class() 

+

        ret.fields = {} 

+

 

+

        for field_name, field in self.fields.items(): 

+

            field.initialize(parent=self, field_name=field_name) 

+

            key = self.get_field_key(field_name) 

+

            value = field.field_to_native(obj, field_name) 

+

            ret[key] = value 

+

            ret.fields[key] = field 

+

        return ret 

+

 

+

    def from_native(self, data, files): 

+

        """ 

+

        Deserialize primitives -> objects. 

+

        """ 

+

        self._errors = {} 

+

        if data is not None or files is not None: 

+

            attrs = self.restore_fields(data, files) 

+

            if attrs is not None: 

+

                attrs = self.perform_validation(attrs) 

+

        else: 

+

            self._errors['non_field_errors'] = ['No input provided'] 

+

 

+

        if not self._errors: 

+

            return self.restore_object(attrs, instance=getattr(self, 'object', None)) 

+

 

+

    def field_to_native(self, obj, field_name): 

+

        """ 

+

        Override default so that the serializer can be used as a nested field 

+

        across relationships. 

+

        """ 

+

        if self.source == '*': 

+

            return self.to_native(obj) 

+

 

+

        try: 

+

            source = self.source or field_name 

+

            value = obj 

+

 

+

            for component in source.split('.'): 

+

                value = get_component(value, component) 

+

                if value is None: 

+

                    break 

+

        except ObjectDoesNotExist: 

+

            return None 

+

 

+

        if is_simple_callable(getattr(value, 'all', None)): 

+

            return [self.to_native(item) for item in value.all()] 

+

 

+

        if value is None: 

+

            return None 

+

 

+

        if self.many is not None: 

+

            many = self.many 

+

        else: 

+

            many = hasattr(value, '__iter__') and not isinstance(value, (Page, dict, six.text_type)) 

+

 

+

        if many: 

+

            return [self.to_native(item) for item in value] 

+

        return self.to_native(value) 

+

 

+

    def field_from_native(self, data, files, field_name, into): 

+

        """ 

+

        Override default so that the serializer can be used as a writable 

+

        nested field across relationships. 

+

        """ 

+

        if self.read_only: 

+

            return 

+

 

+

        try: 

+

            value = data[field_name] 

+

        except KeyError: 

+

            if self.default is not None and not self.partial: 

+

                # Note: partial updates shouldn't set defaults 

+

                value = copy.deepcopy(self.default) 

+

            else: 

+

                if self.required: 

+

                    raise ValidationError(self.error_messages['required']) 

+

                return 

+

 

+

        # Set the serializer object if it exists 

+

        obj = getattr(self.parent.object, field_name) if self.parent.object else None 

+

 

+

        if self.source == '*': 

+

            if value: 

+

                into.update(value) 

+

        else: 

+

            if value in (None, ''): 

+

                into[(self.source or field_name)] = None 

+

            else: 

+

                kwargs = { 

+

                    'instance': obj, 

+

                    'data': value, 

+

                    'context': self.context, 

+

                    'partial': self.partial, 

+

                    'many': self.many 

+

                } 

+

                serializer = self.__class__(**kwargs) 

+

 

+

                if serializer.is_valid(): 

+

                    into[self.source or field_name] = serializer.object 

+

                else: 

+

                    # Propagate errors up to our parent 

+

                    raise NestedValidationError(serializer.errors) 

+

 

+

    def get_identity(self, data): 

+

        """ 

+

        This hook is required for bulk update. 

+

        It is used to determine the canonical identity of a given object. 

+

 

+

        Note that the data has not been validated at this point, so we need 

+

        to make sure that we catch any cases of incorrect datatypes being 

+

        passed to this method. 

+

        """ 

+

        try: 

+

            return data.get('id', None) 

+

        except AttributeError: 

+

            return None 

+

 

+

    @property 

+

    def errors(self): 

+

        """ 

+

        Run deserialization and return error data, 

+

        setting self.object if no errors occurred. 

+

        """ 

+

        if self._errors is None: 

+

            data, files = self.init_data, self.init_files 

+

 

+

            if self.many is not None: 

+

                many = self.many 

+

            else: 

+

                many = hasattr(data, '__iter__') and not isinstance(data, (Page, dict, six.text_type)) 

+

                if many: 

+

                    warnings.warn('Implict list/queryset serialization is deprecated. ' 

+

                                  'Use the `many=True` flag when instantiating the serializer.', 

+

                                  DeprecationWarning, stacklevel=3) 

+

 

+

            if many: 

+

                ret = [] 

+

                errors = [] 

+

                update = self.object is not None 

+

 

+

                if update: 

+

                    # If this is a bulk update we need to map all the objects 

+

                    # to a canonical identity so we can determine which 

+

                    # individual object is being updated for each item in the 

+

                    # incoming data 

+

                    objects = self.object 

+

                    identities = [self.get_identity(self.to_native(obj)) for obj in objects] 

+

                    identity_to_objects = dict(zip(identities, objects)) 

+

 

+

                if hasattr(data, '__iter__') and not isinstance(data, (dict, six.text_type)): 

+

                    for item in data: 

+

                        if update: 

+

                            # Determine which object we're updating 

+

                            identity = self.get_identity(item) 

+

                            self.object = identity_to_objects.pop(identity, None) 

+

                            if self.object is None and not self.allow_add_remove: 

+

                                ret.append(None) 

+

                                errors.append({'non_field_errors': ['Cannot create a new item, only existing items may be updated.']}) 

+

                                continue 

+

 

+

                        ret.append(self.from_native(item, None)) 

+

                        errors.append(self._errors) 

+

 

+

                    if update: 

+

                        self._deleted = identity_to_objects.values() 

+

 

+

                    self._errors = any(errors) and errors or [] 

+

                else: 

+

                    self._errors = {'non_field_errors': ['Expected a list of items.']} 

+

            else: 

+

                ret = self.from_native(data, files) 

+

 

+

            if not self._errors: 

+

                self.object = ret 

+

 

+

        return self._errors 

+

 

+

    def is_valid(self): 

+

        return not self.errors 

+

 

+

    @property 

+

    def data(self): 

+

        """ 

+

        Returns the serialized data on the serializer. 

+

        """ 

+

        if self._data is None: 

+

            obj = self.object 

+

 

+

            if self.many is not None: 

+

                many = self.many 

+

            else: 

+

                many = hasattr(obj, '__iter__') and not isinstance(obj, (Page, dict)) 

+

                if many: 

+

                    warnings.warn('Implict list/queryset serialization is deprecated. ' 

+

                                  'Use the `many=True` flag when instantiating the serializer.', 

+

                                  DeprecationWarning, stacklevel=2) 

+

 

+

            if many: 

+

                self._data = [self.to_native(item) for item in obj] 

+

            else: 

+

                self._data = self.to_native(obj) 

+

 

+

        return self._data 

+

 

+

    def save_object(self, obj, **kwargs): 

+

        obj.save(**kwargs) 

+

 

+

    def delete_object(self, obj): 

+

        obj.delete() 

+

 

+

    def save(self, **kwargs): 

+

        """ 

+

        Save the deserialized object and return it. 

+

        """ 

+

        if isinstance(self.object, list): 

+

            [self.save_object(item, **kwargs) for item in self.object] 

+

        else: 

+

            self.save_object(self.object, **kwargs) 

+

 

+

        if self.allow_add_remove and self._deleted: 

+

            [self.delete_object(item) for item in self._deleted] 

+

 

+

        return self.object 

+

 

+

    def metadata(self): 

+

        """ 

+

        Return a dictionary of metadata about the fields on the serializer. 

+

        Useful for things like responding to OPTIONS requests, or generating 

+

        API schemas for auto-documentation. 

+

        """ 

+

        return SortedDict( 

+

            [(field_name, field.metadata()) 

+

            for field_name, field in six.iteritems(self.fields)] 

+

        ) 

+

 

+

 

+

class Serializer(six.with_metaclass(SerializerMetaclass, BaseSerializer)): 

+

    pass 

+

 

+

 

+

class ModelSerializerOptions(SerializerOptions): 

+

    """ 

+

    Meta class options for ModelSerializer 

+

    """ 

+

    def __init__(self, meta): 

+

        super(ModelSerializerOptions, self).__init__(meta) 

+

        self.model = getattr(meta, 'model', None) 

+

        self.read_only_fields = getattr(meta, 'read_only_fields', ()) 

+

 

+

 

+

class ModelSerializer(Serializer): 

+

    """ 

+

    A serializer that deals with model instances and querysets. 

+

    """ 

+

    _options_class = ModelSerializerOptions 

+

 

+

    field_mapping = { 

+

        models.AutoField: IntegerField, 

+

        models.FloatField: FloatField, 

+

        models.IntegerField: IntegerField, 

+

        models.PositiveIntegerField: IntegerField, 

+

        models.SmallIntegerField: IntegerField, 

+

        models.PositiveSmallIntegerField: IntegerField, 

+

        models.DateTimeField: DateTimeField, 

+

        models.DateField: DateField, 

+

        models.TimeField: TimeField, 

+

        models.DecimalField: DecimalField, 

+

        models.EmailField: EmailField, 

+

        models.CharField: CharField, 

+

        models.URLField: URLField, 

+

        models.SlugField: SlugField, 

+

        models.TextField: CharField, 

+

        models.CommaSeparatedIntegerField: CharField, 

+

        models.BooleanField: BooleanField, 

+

        models.FileField: FileField, 

+

        models.ImageField: ImageField, 

+

    } 

+

 

+

    def get_default_fields(self): 

+

        """ 

+

        Return all the fields that should be serialized for the model. 

+

        """ 

+

 

+

        cls = self.opts.model 

+

        assert cls is not None, \ 

+

                "Serializer class '%s' is missing 'model' Meta option" % self.__class__.__name__ 

+

        opts = get_concrete_model(cls)._meta 

+

        ret = SortedDict() 

+

        nested = bool(self.opts.depth) 

+

 

+

        # Deal with adding the primary key field 

+

        pk_field = opts.pk 

+

        while pk_field.rel and pk_field.rel.parent_link: 

+

            # If model is a child via multitable inheritance, use parent's pk 

+

            pk_field = pk_field.rel.to._meta.pk 

+

 

+

        field = self.get_pk_field(pk_field) 

+

        if field: 

+

            ret[pk_field.name] = field 

+

 

+

        # Deal with forward relationships 

+

        forward_rels = [field for field in opts.fields if field.serialize] 

+

        forward_rels += [field for field in opts.many_to_many if field.serialize] 

+

 

+

        for model_field in forward_rels: 

+

            has_through_model = False 

+

 

+

            if model_field.rel: 

+

                to_many = isinstance(model_field, 

+

                                     models.fields.related.ManyToManyField) 

+

                related_model = model_field.rel.to 

+

 

+

                if to_many and not model_field.rel.through._meta.auto_created: 

+

                    has_through_model = True 

+

 

+

            if model_field.rel and nested: 

+

                if len(inspect.getargspec(self.get_nested_field).args) == 2: 

+

                    warnings.warn( 

+

                        'The `get_nested_field(model_field)` call signature ' 

+

                        'is due to be deprecated. ' 

+

                        'Use `get_nested_field(model_field, related_model, ' 

+

                        'to_many) instead', 

+

                        PendingDeprecationWarning 

+

                    ) 

+

                    field = self.get_nested_field(model_field) 

+

                else: 

+

                    field = self.get_nested_field(model_field, related_model, to_many) 

+

            elif model_field.rel: 

+

                if len(inspect.getargspec(self.get_nested_field).args) == 3: 

+

                    warnings.warn( 

+

                        'The `get_related_field(model_field, to_many)` call ' 

+

                        'signature is due to be deprecated. ' 

+

                        'Use `get_related_field(model_field, related_model, ' 

+

                        'to_many) instead', 

+

                        PendingDeprecationWarning 

+

                    ) 

+

                    field = self.get_related_field(model_field, to_many=to_many) 

+

                else: 

+

                    field = self.get_related_field(model_field, related_model, to_many) 

+

            else: 

+

                field = self.get_field(model_field) 

+

 

+

            if field: 

+

                if has_through_model: 

+

                    field.read_only = True 

+

 

+

                ret[model_field.name] = field 

+

 

+

        # Deal with reverse relationships 

+

        if not self.opts.fields: 

+

            reverse_rels = [] 

+

        else: 

+

            # Reverse relationships are only included if they are explicitly 

+

            # present in the `fields` option on the serializer 

+

            reverse_rels = opts.get_all_related_objects() 

+

            reverse_rels += opts.get_all_related_many_to_many_objects() 

+

 

+

        for relation in reverse_rels: 

+

            accessor_name = relation.get_accessor_name() 

+

            if not self.opts.fields or accessor_name not in self.opts.fields: 

+

                continue 

+

            related_model = relation.model 

+

            to_many = relation.field.rel.multiple 

+

            has_through_model = False 

+

            is_m2m = isinstance(relation.field, 

+

                                models.fields.related.ManyToManyField) 

+

 

+

            if is_m2m and not relation.field.rel.through._meta.auto_created: 

+

                has_through_model = True 

+

 

+

            if nested: 

+

                field = self.get_nested_field(None, related_model, to_many) 

+

            else: 

+

                field = self.get_related_field(None, related_model, to_many) 

+

 

+

            if field: 

+

                if has_through_model: 

+

                    field.read_only = True 

+

 

+

                ret[accessor_name] = field 

+

 

+

        # Add the `read_only` flag to any fields that have bee specified 

+

        # in the `read_only_fields` option 

+

        for field_name in self.opts.read_only_fields: 

+

            assert field_name not in self.base_fields.keys(), \ 

+

                "field '%s' on serializer '%s' specfied in " \ 

+

                "`read_only_fields`, but also added " \ 

+

                "as an explict field.  Remove it from `read_only_fields`." % \ 

+

                (field_name, self.__class__.__name__) 

+

            assert field_name in ret, \ 

+

                "Noexistant field '%s' specified in `read_only_fields` " \ 

+

                "on serializer '%s'." % \ 

+

                (self.__class__.__name__, field_name) 

+

            ret[field_name].read_only = True 

+

 

+

        return ret 

+

 

+

    def get_pk_field(self, model_field): 

+

        """ 

+

        Returns a default instance of the pk field. 

+

        """ 

+

        return self.get_field(model_field) 

+

 

+

    def get_nested_field(self, model_field, related_model, to_many): 

+

        """ 

+

        Creates a default instance of a nested relational field. 

+

 

+

        Note that model_field will be `None` for reverse relationships. 

+

        """ 

+

        class NestedModelSerializer(ModelSerializer): 

+

            class Meta: 

+

                model = related_model 

+

                depth = self.opts.depth - 1 

+

 

+

        return NestedModelSerializer(many=to_many) 

+

 

+

    def get_related_field(self, model_field, related_model, to_many): 

+

        """ 

+

        Creates a default instance of a flat relational field. 

+

 

+

        Note that model_field will be `None` for reverse relationships. 

+

        """ 

+

        # TODO: filter queryset using: 

+

        # .using(db).complex_filter(self.rel.limit_choices_to) 

+

 

+

        kwargs = { 

+

            'queryset': related_model._default_manager, 

+

            'many': to_many 

+

        } 

+

 

+

        if model_field: 

+

            kwargs['required'] = not(model_field.null or model_field.blank) 

+

 

+

        return PrimaryKeyRelatedField(**kwargs) 

+

 

+

    def get_field(self, model_field): 

+

        """ 

+

        Creates a default instance of a basic non-relational field. 

+

        """ 

+

        kwargs = {} 

+

 

+

        if model_field.null or model_field.blank: 

+

            kwargs['required'] = False 

+

 

+

        if isinstance(model_field, models.AutoField) or not model_field.editable: 

+

            kwargs['read_only'] = True 

+

 

+

        if model_field.has_default(): 

+

            kwargs['default'] = model_field.get_default() 

+

 

+

        if issubclass(model_field.__class__, models.TextField): 

+

            kwargs['widget'] = widgets.Textarea 

+

 

+

        if model_field.verbose_name is not None: 

+

            kwargs['label'] = model_field.verbose_name 

+

 

+

        if model_field.help_text is not None: 

+

            kwargs['help_text'] = model_field.help_text 

+

 

+

        # TODO: TypedChoiceField? 

+

        if model_field.flatchoices:  # This ModelField contains choices 

+

            kwargs['choices'] = model_field.flatchoices 

+

            return ChoiceField(**kwargs) 

+

 

+

        # put this below the ChoiceField because min_value isn't a valid initializer 

+

        if issubclass(model_field.__class__, models.PositiveIntegerField) or\ 

+

                issubclass(model_field.__class__, models.PositiveSmallIntegerField): 

+

            kwargs['min_value'] = 0 

+

 

+

        attribute_dict = { 

+

            models.CharField: ['max_length'], 

+

            models.CommaSeparatedIntegerField: ['max_length'], 

+

            models.DecimalField: ['max_digits', 'decimal_places'], 

+

            models.EmailField: ['max_length'], 

+

            models.FileField: ['max_length'], 

+

            models.ImageField: ['max_length'], 

+

            models.SlugField: ['max_length'], 

+

            models.URLField: ['max_length'], 

+

        } 

+

 

+

        if model_field.__class__ in attribute_dict: 

+

            attributes = attribute_dict[model_field.__class__] 

+

            for attribute in attributes: 

+

                kwargs.update({attribute: getattr(model_field, attribute)}) 

+

 

+

        try: 

+

            return self.field_mapping[model_field.__class__](**kwargs) 

+

        except KeyError: 

+

            return ModelField(model_field=model_field, **kwargs) 

+

 

+

    def get_validation_exclusions(self): 

+

        """ 

+

        Return a list of field names to exclude from model validation. 

+

        """ 

+

        cls = self.opts.model 

+

        opts = get_concrete_model(cls)._meta 

+

        exclusions = [field.name for field in opts.fields + opts.many_to_many] 

+

        for field_name, field in self.fields.items(): 

+

            field_name = field.source or field_name 

+

            if field_name in exclusions and not field.read_only: 

+

                exclusions.remove(field_name) 

+

        return exclusions 

+

 

+

    def full_clean(self, instance): 

+

        """ 

+

        Perform Django's full_clean, and populate the `errors` dictionary 

+

        if any validation errors occur. 

+

 

+

        Note that we don't perform this inside the `.restore_object()` method, 

+

        so that subclasses can override `.restore_object()`, and still get 

+

        the full_clean validation checking. 

+

        """ 

+

        try: 

+

            instance.full_clean(exclude=self.get_validation_exclusions()) 

+

        except ValidationError as err: 

+

            self._errors = err.message_dict 

+

            return None 

+

        return instance 

+

 

+

    def restore_object(self, attrs, instance=None): 

+

        """ 

+

        Restore the model instance. 

+

        """ 

+

        m2m_data = {} 

+

        related_data = {} 

+

        meta = self.opts.model._meta 

+

 

+

        # Reverse fk or one-to-one relations 

+

        for (obj, model) in meta.get_all_related_objects_with_model(): 

+

            field_name = obj.field.related_query_name() 

+

            if field_name in attrs: 

+

                related_data[field_name] = attrs.pop(field_name) 

+

 

+

        # Reverse m2m relations 

+

        for (obj, model) in meta.get_all_related_m2m_objects_with_model(): 

+

            field_name = obj.field.related_query_name() 

+

            if field_name in attrs: 

+

                m2m_data[field_name] = attrs.pop(field_name) 

+

 

+

        # Forward m2m relations 

+

        for field in meta.many_to_many: 

+

            if field.name in attrs: 

+

                m2m_data[field.name] = attrs.pop(field.name) 

+

 

+

        # Update an existing instance... 

+

        if instance is not None: 

+

            for key, val in attrs.items(): 

+

                setattr(instance, key, val) 

+

 

+

        # ...or create a new instance 

+

        else: 

+

            instance = self.opts.model(**attrs) 

+

 

+

        # Any relations that cannot be set until we've 

+

        # saved the model get hidden away on these 

+

        # private attributes, so we can deal with them 

+

        # at the point of save. 

+

        instance._related_data = related_data 

+

        instance._m2m_data = m2m_data 

+

 

+

        return instance 

+

 

+

    def from_native(self, data, files): 

+

        """ 

+

        Override the default method to also include model field validation. 

+

        """ 

+

        instance = super(ModelSerializer, self).from_native(data, files) 

+

        if not self._errors: 

+

            return self.full_clean(instance) 

+

 

+

    def save_object(self, obj, **kwargs): 

+

        """ 

+

        Save the deserialized object and return it. 

+

        """ 

+

        obj.save(**kwargs) 

+

 

+

        if getattr(obj, '_m2m_data', None): 

+

            for accessor_name, object_list in obj._m2m_data.items(): 

+

                setattr(obj, accessor_name, object_list) 

+

            del(obj._m2m_data) 

+

 

+

        if getattr(obj, '_related_data', None): 

+

            for accessor_name, related in obj._related_data.items(): 

+

                setattr(obj, accessor_name, related) 

+

            del(obj._related_data) 

+

 

+

 

+

class HyperlinkedModelSerializerOptions(ModelSerializerOptions): 

+

    """ 

+

    Options for HyperlinkedModelSerializer 

+

    """ 

+

    def __init__(self, meta): 

+

        super(HyperlinkedModelSerializerOptions, self).__init__(meta) 

+

        self.view_name = getattr(meta, 'view_name', None) 

+

        self.lookup_field = getattr(meta, 'lookup_field', None) 

+

 

+

 

+

class HyperlinkedModelSerializer(ModelSerializer): 

+

    """ 

+

    A subclass of ModelSerializer that uses hyperlinked relationships, 

+

    instead of primary key relationships. 

+

    """ 

+

    _options_class = HyperlinkedModelSerializerOptions 

+

    _default_view_name = '%(model_name)s-detail' 

+

    _hyperlink_field_class = HyperlinkedRelatedField 

+

 

+

    def get_default_fields(self): 

+

        fields = super(HyperlinkedModelSerializer, self).get_default_fields() 

+

 

+

        if self.opts.view_name is None: 

+

            self.opts.view_name = self._get_default_view_name(self.opts.model) 

+

 

+

        if 'url' not in fields: 

+

            url_field = HyperlinkedIdentityField( 

+

                view_name=self.opts.view_name, 

+

                lookup_field=self.opts.lookup_field 

+

            ) 

+

            fields.insert(0, 'url', url_field) 

+

 

+

        return fields 

+

 

+

    def get_pk_field(self, model_field): 

+

        if self.opts.fields and model_field.name in self.opts.fields: 

+

            return self.get_field(model_field) 

+

 

+

    def get_related_field(self, model_field, related_model, to_many): 

+

        """ 

+

        Creates a default instance of a flat relational field. 

+

        """ 

+

        # TODO: filter queryset using: 

+

        # .using(db).complex_filter(self.rel.limit_choices_to) 

+

        kwargs = { 

+

            'queryset': related_model._default_manager, 

+

            'view_name': self._get_default_view_name(related_model), 

+

            'many': to_many 

+

        } 

+

 

+

        if model_field: 

+

            kwargs['required'] = not(model_field.null or model_field.blank) 

+

 

+

        if self.opts.lookup_field: 

+

            kwargs['lookup_field'] = self.opts.lookup_field 

+

 

+

        return self._hyperlink_field_class(**kwargs) 

+

 

+

    def get_identity(self, data): 

+

        """ 

+

        This hook is required for bulk update. 

+

        We need to override the default, to use the url as the identity. 

+

        """ 

+

        try: 

+

            return data.get('url', None) 

+

        except AttributeError: 

+

            return None 

+

 

+

    def _get_default_view_name(self, model): 

+

        """ 

+

        Return the view name to use if 'view_name' is not specified in 'Meta' 

+

        """ 

+

        model_meta = model._meta 

+

        format_kwargs = { 

+

            'app_label': model_meta.app_label, 

+

            'model_name': model_meta.object_name.lower() 

+

        } 

+

        return self._default_view_name % format_kwargs 

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