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_fields.html | 1991 ++++++++++++++++++++++++++++++++++++ 1 file changed, 1991 insertions(+) create mode 100644 htmlcov/rest_framework_fields.html (limited to 'htmlcov/rest_framework_fields.html') diff --git a/htmlcov/rest_framework_fields.html b/htmlcov/rest_framework_fields.html new file mode 100644 index 00000000..cf2731d2 --- /dev/null +++ b/htmlcov/rest_framework_fields.html @@ -0,0 +1,1991 @@ + + + + + + + + Coverage for rest_framework/fields: 87% + + + + + + + + + + + +
+ +

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

+ +
+

""" 

+

Serializer fields perform validation on incoming data. 

+

 

+

They are very similar to Django's form fields. 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

import copy 

+

import datetime 

+

import inspect 

+

import re 

+

import warnings 

+

from decimal import Decimal, DecimalException 

+

from django import forms 

+

from django.core import validators 

+

from django.core.exceptions import ValidationError 

+

from django.conf import settings 

+

from django.db.models.fields import BLANK_CHOICE_DASH 

+

from django.forms import widgets 

+

from django.utils.encoding import is_protected_type 

+

from django.utils.translation import ugettext_lazy as _ 

+

from django.utils.datastructures import SortedDict 

+

from rest_framework import ISO_8601 

+

from rest_framework.compat import ( 

+

    timezone, parse_date, parse_datetime, parse_time, BytesIO, six, smart_text, 

+

    force_text, is_non_str_iterable 

+

) 

+

from rest_framework.settings import api_settings 

+

 

+

 

+

def is_simple_callable(obj): 

+

    """ 

+

    True if the object is a callable that takes no arguments. 

+

    """ 

+

    function = inspect.isfunction(obj) 

+

    method = inspect.ismethod(obj) 

+

 

+

    if not (function or method): 

+

        return False 

+

 

+

    args, _, _, defaults = inspect.getargspec(obj) 

+

    len_args = len(args) if function else len(args) - 1 

+

    len_defaults = len(defaults) if defaults else 0 

+

    return len_args <= len_defaults 

+

 

+

 

+

def get_component(obj, attr_name): 

+

    """ 

+

    Given an object, and an attribute name, 

+

    return that attribute on the object. 

+

    """ 

+

    if isinstance(obj, dict): 

+

        val = obj.get(attr_name) 

+

    else: 

+

        val = getattr(obj, attr_name) 

+

 

+

    if is_simple_callable(val): 

+

        return val() 

+

    return val 

+

 

+

 

+

def readable_datetime_formats(formats): 

+

    format = ', '.join(formats).replace(ISO_8601, 

+

             'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HHMM|-HHMM|Z]') 

+

    return humanize_strptime(format) 

+

 

+

 

+

def readable_date_formats(formats): 

+

    format = ', '.join(formats).replace(ISO_8601, 'YYYY[-MM[-DD]]') 

+

    return humanize_strptime(format) 

+

 

+

 

+

def readable_time_formats(formats): 

+

    format = ', '.join(formats).replace(ISO_8601, 'hh:mm[:ss[.uuuuuu]]') 

+

    return humanize_strptime(format) 

+

 

+

 

+

def humanize_strptime(format_string): 

+

    # Note that we're missing some of the locale specific mappings that 

+

    # don't really make sense. 

+

    mapping = { 

+

        "%Y": "YYYY", 

+

        "%y": "YY", 

+

        "%m": "MM", 

+

        "%b": "[Jan-Dec]", 

+

        "%B": "[January-December]", 

+

        "%d": "DD", 

+

        "%H": "hh", 

+

        "%I": "hh",  # Requires '%p' to differentiate from '%H'. 

+

        "%M": "mm", 

+

        "%S": "ss", 

+

        "%f": "uuuuuu", 

+

        "%a": "[Mon-Sun]", 

+

        "%A": "[Monday-Sunday]", 

+

        "%p": "[AM|PM]", 

+

        "%z": "[+HHMM|-HHMM]" 

+

    } 

+

    for key, val in mapping.items(): 

+

        format_string = format_string.replace(key, val) 

+

    return format_string 

+

 

+

 

+

class Field(object): 

+

    read_only = True 

+

    creation_counter = 0 

+

    empty = '' 

+

    type_name = None 

+

    partial = False 

+

    use_files = False 

+

    form_field_class = forms.CharField 

+

    type_label = 'field' 

+

 

+

    def __init__(self, source=None, label=None, help_text=None): 

+

        self.parent = None 

+

 

+

        self.creation_counter = Field.creation_counter 

+

        Field.creation_counter += 1 

+

 

+

        self.source = source 

+

 

+

        if label is not None: 

+

            self.label = smart_text(label) 

+

 

+

        if help_text is not None: 

+

            self.help_text = smart_text(help_text) 

+

 

+

    def initialize(self, parent, field_name): 

+

        """ 

+

        Called to set up a field prior to field_to_native or field_from_native. 

+

 

+

        parent - The parent serializer. 

+

        model_field - The model field this field corresponds to, if one exists. 

+

        """ 

+

        self.parent = parent 

+

        self.root = parent.root or parent 

+

        self.context = self.root.context 

+

        self.partial = self.root.partial 

+

        if self.partial: 

+

            self.required = False 

+

 

+

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

+

        """ 

+

        Given a dictionary and a field name, updates the dictionary `into`, 

+

        with the field and it's deserialized value. 

+

        """ 

+

        return 

+

 

+

    def field_to_native(self, obj, field_name): 

+

        """ 

+

        Given and object and a field name, returns the value that should be 

+

        serialized for that field. 

+

        """ 

+

        if obj is None: 

+

            return self.empty 

+

 

+

        if self.source == '*': 

+

            return self.to_native(obj) 

+

 

+

        source = self.source or field_name 

+

        value = obj 

+

 

+

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

+

            value = get_component(value, component) 

+

            if value is None: 

+

                break 

+

 

+

        return self.to_native(value) 

+

 

+

    def to_native(self, value): 

+

        """ 

+

        Converts the field's value into it's simple representation. 

+

        """ 

+

        if is_simple_callable(value): 

+

            value = value() 

+

 

+

        if is_protected_type(value): 

+

            return value 

+

        elif (is_non_str_iterable(value) and 

+

              not isinstance(value, (dict, six.string_types))): 

+

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

+

        elif isinstance(value, dict): 

+

            # Make sure we preserve field ordering, if it exists 

+

            ret = SortedDict() 

+

            for key, val in value.items(): 

+

                ret[key] = self.to_native(val) 

+

            return ret 

+

        return force_text(value) 

+

 

+

    def attributes(self): 

+

        """ 

+

        Returns a dictionary of attributes to be used when serializing to xml. 

+

        """ 

+

        if self.type_name: 

+

            return {'type': self.type_name} 

+

        return {} 

+

 

+

    def metadata(self): 

+

        metadata = SortedDict() 

+

        metadata['type'] = self.type_label 

+

        metadata['required'] = getattr(self, 'required', False) 

+

        optional_attrs = ['read_only', 'label', 'help_text', 

+

                          'min_length', 'max_length'] 

+

        for attr in optional_attrs: 

+

            value = getattr(self, attr, None) 

+

            if value is not None and value != '': 

+

                metadata[attr] = force_text(value, strings_only=True) 

+

        return metadata 

+

 

+

 

+

class WritableField(Field): 

+

    """ 

+

    Base for read/write fields. 

+

    """ 

+

    default_validators = [] 

+

    default_error_messages = { 

+

        'required': _('This field is required.'), 

+

        'invalid': _('Invalid value.'), 

+

    } 

+

    widget = widgets.TextInput 

+

    default = None 

+

 

+

    def __init__(self, source=None, label=None, help_text=None, 

+

                 read_only=False, required=None, 

+

                 validators=[], error_messages=None, widget=None, 

+

                 default=None, blank=None): 

+

 

+

        # 'blank' is to be deprecated in favor of 'required' 

+

        if blank is not None: 

+

            warnings.warn('The `blank` keyword argument is deprecated. ' 

+

                          'Use the `required` keyword argument instead.', 

+

                          DeprecationWarning, stacklevel=2) 

+

            required = not(blank) 

+

 

+

        super(WritableField, self).__init__(source=source, label=label, help_text=help_text) 

+

 

+

        self.read_only = read_only 

+

        if required is None: 

+

            self.required = not(read_only) 

+

        else: 

+

            assert not (read_only and required), "Cannot set required=True and read_only=True" 

+

            self.required = required 

+

 

+

        messages = {} 

+

        for c in reversed(self.__class__.__mro__): 

+

            messages.update(getattr(c, 'default_error_messages', {})) 

+

        messages.update(error_messages or {}) 

+

        self.error_messages = messages 

+

 

+

        self.validators = self.default_validators + validators 

+

        self.default = default if default is not None else self.default 

+

 

+

        # Widgets are ony used for HTML forms. 

+

        widget = widget or self.widget 

+

        if isinstance(widget, type): 

+

            widget = widget() 

+

        self.widget = widget 

+

 

+

    def __deepcopy__(self, memo): 

+

        result = copy.copy(self) 

+

        memo[id(self)] = result 

+

        result.validators = self.validators[:] 

+

        return result 

+

 

+

    def validate(self, value): 

+

        if value in validators.EMPTY_VALUES and self.required: 

+

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

+

 

+

    def run_validators(self, value): 

+

        if value in validators.EMPTY_VALUES: 

+

            return 

+

        errors = [] 

+

        for v in self.validators: 

+

            try: 

+

                v(value) 

+

            except ValidationError as e: 

+

                if hasattr(e, 'code') and e.code in self.error_messages: 

+

                    message = self.error_messages[e.code] 

+

                    if e.params: 

+

                        message = message % e.params 

+

                    errors.append(message) 

+

                else: 

+

                    errors.extend(e.messages) 

+

        if errors: 

+

            raise ValidationError(errors) 

+

 

+

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

+

        """ 

+

        Given a dictionary and a field name, updates the dictionary `into`, 

+

        with the field and it's deserialized value. 

+

        """ 

+

        if self.read_only: 

+

            return 

+

 

+

        try: 

+

            if self.use_files: 

+

                files = files or {} 

+

                native = files[field_name] 

+

            else: 

+

                native = data[field_name] 

+

        except KeyError: 

+

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

+

                # Note: partial updates shouldn't set defaults 

+

                if is_simple_callable(self.default): 

+

                    native = self.default() 

+

                else: 

+

                    native = self.default 

+

            else: 

+

                if self.required: 

+

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

+

                return 

+

 

+

        value = self.from_native(native) 

+

        if self.source == '*': 

+

            if value: 

+

                into.update(value) 

+

        else: 

+

            self.validate(value) 

+

            self.run_validators(value) 

+

            into[self.source or field_name] = value 

+

 

+

    def from_native(self, value): 

+

        """ 

+

        Reverts a simple representation back to the field's value. 

+

        """ 

+

        return value 

+

 

+

 

+

class ModelField(WritableField): 

+

    """ 

+

    A generic field that can be used against an arbitrary model field. 

+

    """ 

+

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

+

        try: 

+

            self.model_field = kwargs.pop('model_field') 

+

        except KeyError: 

+

            raise ValueError("ModelField requires 'model_field' kwarg") 

+

 

+

        self.min_length = kwargs.pop('min_length', 

+

                                     getattr(self.model_field, 'min_length', None)) 

+

        self.max_length = kwargs.pop('max_length', 

+

                                     getattr(self.model_field, 'max_length', None)) 

+

        self.min_value = kwargs.pop('min_value', 

+

                                    getattr(self.model_field, 'min_value', None)) 

+

        self.max_value = kwargs.pop('max_value', 

+

                                    getattr(self.model_field, 'max_value', None)) 

+

 

+

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

+

 

+

        if self.min_length is not None: 

+

            self.validators.append(validators.MinLengthValidator(self.min_length)) 

+

        if self.max_length is not None: 

+

            self.validators.append(validators.MaxLengthValidator(self.max_length)) 

+

        if self.min_value is not None: 

+

            self.validators.append(validators.MinValueValidator(self.min_value)) 

+

        if self.max_value is not None: 

+

            self.validators.append(validators.MaxValueValidator(self.max_value)) 

+

 

+

    def from_native(self, value): 

+

        rel = getattr(self.model_field, "rel", None) 

+

        if rel is not None: 

+

            return rel.to._meta.get_field(rel.field_name).to_python(value) 

+

        else: 

+

            return self.model_field.to_python(value) 

+

 

+

    def field_to_native(self, obj, field_name): 

+

        value = self.model_field._get_val_from_obj(obj) 

+

        if is_protected_type(value): 

+

            return value 

+

        return self.model_field.value_to_string(obj) 

+

 

+

    def attributes(self): 

+

        return { 

+

            "type": self.model_field.get_internal_type() 

+

        } 

+

 

+

 

+

##### Typed Fields ##### 

+

 

+

class BooleanField(WritableField): 

+

    type_name = 'BooleanField' 

+

    type_label = 'boolean' 

+

    form_field_class = forms.BooleanField 

+

    widget = widgets.CheckboxInput 

+

    default_error_messages = { 

+

        'invalid': _("'%s' value must be either True or False."), 

+

    } 

+

    empty = False 

+

 

+

    # Note: we set default to `False` in order to fill in missing value not 

+

    # supplied by html form.  TODO: Fix so that only html form input gets 

+

    # this behavior. 

+

    default = False 

+

 

+

    def from_native(self, value): 

+

        if value in ('true', 't', 'True', '1'): 

+

            return True 

+

        if value in ('false', 'f', 'False', '0'): 

+

            return False 

+

        return bool(value) 

+

 

+

 

+

class CharField(WritableField): 

+

    type_name = 'CharField' 

+

    type_label = 'string' 

+

    form_field_class = forms.CharField 

+

 

+

    def __init__(self, max_length=None, min_length=None, *args, **kwargs): 

+

        self.max_length, self.min_length = max_length, min_length 

+

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

+

        if min_length is not None: 

+

            self.validators.append(validators.MinLengthValidator(min_length)) 

+

        if max_length is not None: 

+

            self.validators.append(validators.MaxLengthValidator(max_length)) 

+

 

+

    def from_native(self, value): 

+

        if isinstance(value, six.string_types) or value is None: 

+

            return value 

+

        return smart_text(value) 

+

 

+

 

+

class URLField(CharField): 

+

    type_name = 'URLField' 

+

    type_label = 'url' 

+

 

+

    def __init__(self, **kwargs): 

+

        kwargs['validators'] = [validators.URLValidator()] 

+

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

+

 

+

 

+

class SlugField(CharField): 

+

    type_name = 'SlugField' 

+

    type_label = 'slug' 

+

    form_field_class = forms.SlugField 

+

 

+

    default_error_messages = { 

+

        'invalid': _("Enter a valid 'slug' consisting of letters, numbers," 

+

                     " underscores or hyphens."), 

+

    } 

+

    default_validators = [validators.validate_slug] 

+

 

+

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

+

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

+

 

+

 

+

class ChoiceField(WritableField): 

+

    type_name = 'ChoiceField' 

+

    type_label = 'multiple choice' 

+

    form_field_class = forms.ChoiceField 

+

    widget = widgets.Select 

+

    default_error_messages = { 

+

        'invalid_choice': _('Select a valid choice. %(value)s is not one of ' 

+

                            'the available choices.'), 

+

    } 

+

 

+

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

+

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

+

        self.choices = choices 

+

        if not self.required: 

+

            self.choices = BLANK_CHOICE_DASH + self.choices 

+

 

+

    def _get_choices(self): 

+

        return self._choices 

+

 

+

    def _set_choices(self, value): 

+

        # Setting choices also sets the choices on the widget. 

+

        # choices can be any iterable, but we call list() on it because 

+

        # it will be consumed more than once. 

+

        self._choices = self.widget.choices = list(value) 

+

 

+

    choices = property(_get_choices, _set_choices) 

+

 

+

    def validate(self, value): 

+

        """ 

+

        Validates that the input is in self.choices. 

+

        """ 

+

        super(ChoiceField, self).validate(value) 

+

        if value and not self.valid_value(value): 

+

            raise ValidationError(self.error_messages['invalid_choice'] % {'value': value}) 

+

 

+

    def valid_value(self, value): 

+

        """ 

+

        Check to see if the provided value is a valid choice. 

+

        """ 

+

        for k, v in self.choices: 

+

            if isinstance(v, (list, tuple)): 

+

                # This is an optgroup, so look inside the group for options 

+

                for k2, v2 in v: 

+

                    if value == smart_text(k2): 

+

                        return True 

+

            else: 

+

                if value == smart_text(k) or value == k: 

+

                    return True 

+

        return False 

+

 

+

 

+

class EmailField(CharField): 

+

    type_name = 'EmailField' 

+

    type_label = 'email' 

+

    form_field_class = forms.EmailField 

+

 

+

    default_error_messages = { 

+

        'invalid': _('Enter a valid e-mail address.'), 

+

    } 

+

    default_validators = [validators.validate_email] 

+

 

+

    def from_native(self, value): 

+

        ret = super(EmailField, self).from_native(value) 

+

        if ret is None: 

+

            return None 

+

        return ret.strip() 

+

 

+

 

+

class RegexField(CharField): 

+

    type_name = 'RegexField' 

+

    type_label = 'regex' 

+

    form_field_class = forms.RegexField 

+

 

+

    def __init__(self, regex, max_length=None, min_length=None, *args, **kwargs): 

+

        super(RegexField, self).__init__(max_length, min_length, *args, **kwargs) 

+

        self.regex = regex 

+

 

+

    def _get_regex(self): 

+

        return self._regex 

+

 

+

    def _set_regex(self, regex): 

+

        if isinstance(regex, six.string_types): 

+

            regex = re.compile(regex) 

+

        self._regex = regex 

+

        if hasattr(self, '_regex_validator') and self._regex_validator in self.validators: 

+

            self.validators.remove(self._regex_validator) 

+

        self._regex_validator = validators.RegexValidator(regex=regex) 

+

        self.validators.append(self._regex_validator) 

+

 

+

    regex = property(_get_regex, _set_regex) 

+

 

+

 

+

class DateField(WritableField): 

+

    type_name = 'DateField' 

+

    type_label = 'date' 

+

    widget = widgets.DateInput 

+

    form_field_class = forms.DateField 

+

 

+

    default_error_messages = { 

+

        'invalid': _("Date has wrong format. Use one of these formats instead: %s"), 

+

    } 

+

    empty = None 

+

    input_formats = api_settings.DATE_INPUT_FORMATS 

+

    format = api_settings.DATE_FORMAT 

+

 

+

    def __init__(self, input_formats=None, format=None, *args, **kwargs): 

+

        self.input_formats = input_formats if input_formats is not None else self.input_formats 

+

        self.format = format if format is not None else self.format 

+

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

+

 

+

    def from_native(self, value): 

+

        if value in validators.EMPTY_VALUES: 

+

            return None 

+

 

+

        if isinstance(value, datetime.datetime): 

+

            if timezone and settings.USE_TZ and timezone.is_aware(value): 

+

                # Convert aware datetimes to the default time zone 

+

                # before casting them to dates (#17742). 

+

                default_timezone = timezone.get_default_timezone() 

+

                value = timezone.make_naive(value, default_timezone) 

+

            return value.date() 

+

        if isinstance(value, datetime.date): 

+

            return value 

+

 

+

        for format in self.input_formats: 

+

            if format.lower() == ISO_8601: 

+

                try: 

+

                    parsed = parse_date(value) 

+

                except (ValueError, TypeError): 

+

                    pass 

+

                else: 

+

                    if parsed is not None: 

+

                        return parsed 

+

            else: 

+

                try: 

+

                    parsed = datetime.datetime.strptime(value, format) 

+

                except (ValueError, TypeError): 

+

                    pass 

+

                else: 

+

                    return parsed.date() 

+

 

+

        msg = self.error_messages['invalid'] % readable_date_formats(self.input_formats) 

+

        raise ValidationError(msg) 

+

 

+

    def to_native(self, value): 

+

        if value is None or self.format is None: 

+

            return value 

+

 

+

        if isinstance(value, datetime.datetime): 

+

            value = value.date() 

+

 

+

        if self.format.lower() == ISO_8601: 

+

            return value.isoformat() 

+

        return value.strftime(self.format) 

+

 

+

 

+

class DateTimeField(WritableField): 

+

    type_name = 'DateTimeField' 

+

    type_label = 'datetime' 

+

    widget = widgets.DateTimeInput 

+

    form_field_class = forms.DateTimeField 

+

 

+

    default_error_messages = { 

+

        'invalid': _("Datetime has wrong format. Use one of these formats instead: %s"), 

+

    } 

+

    empty = None 

+

    input_formats = api_settings.DATETIME_INPUT_FORMATS 

+

    format = api_settings.DATETIME_FORMAT 

+

 

+

    def __init__(self, input_formats=None, format=None, *args, **kwargs): 

+

        self.input_formats = input_formats if input_formats is not None else self.input_formats 

+

        self.format = format if format is not None else self.format 

+

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

+

 

+

    def from_native(self, value): 

+

        if value in validators.EMPTY_VALUES: 

+

            return None 

+

 

+

        if isinstance(value, datetime.datetime): 

+

            return value 

+

        if isinstance(value, datetime.date): 

+

            value = datetime.datetime(value.year, value.month, value.day) 

+

            if settings.USE_TZ: 

+

                # For backwards compatibility, interpret naive datetimes in 

+

                # local time. This won't work during DST change, but we can't 

+

                # do much about it, so we let the exceptions percolate up the 

+

                # call stack. 

+

                warnings.warn("DateTimeField received a naive datetime (%s)" 

+

                              " while time zone support is active." % value, 

+

                              RuntimeWarning) 

+

                default_timezone = timezone.get_default_timezone() 

+

                value = timezone.make_aware(value, default_timezone) 

+

            return value 

+

 

+

        for format in self.input_formats: 

+

            if format.lower() == ISO_8601: 

+

                try: 

+

                    parsed = parse_datetime(value) 

+

                except (ValueError, TypeError): 

+

                    pass 

+

                else: 

+

                    if parsed is not None: 

+

                        return parsed 

+

            else: 

+

                try: 

+

                    parsed = datetime.datetime.strptime(value, format) 

+

                except (ValueError, TypeError): 

+

                    pass 

+

                else: 

+

                    return parsed 

+

 

+

        msg = self.error_messages['invalid'] % readable_datetime_formats(self.input_formats) 

+

        raise ValidationError(msg) 

+

 

+

    def to_native(self, value): 

+

        if value is None or self.format is None: 

+

            return value 

+

 

+

        if self.format.lower() == ISO_8601: 

+

            ret = value.isoformat() 

+

            if ret.endswith('+00:00'): 

+

                ret = ret[:-6] + 'Z' 

+

            return ret 

+

        return value.strftime(self.format) 

+

 

+

 

+

class TimeField(WritableField): 

+

    type_name = 'TimeField' 

+

    type_label = 'time' 

+

    widget = widgets.TimeInput 

+

    form_field_class = forms.TimeField 

+

 

+

    default_error_messages = { 

+

        'invalid': _("Time has wrong format. Use one of these formats instead: %s"), 

+

    } 

+

    empty = None 

+

    input_formats = api_settings.TIME_INPUT_FORMATS 

+

    format = api_settings.TIME_FORMAT 

+

 

+

    def __init__(self, input_formats=None, format=None, *args, **kwargs): 

+

        self.input_formats = input_formats if input_formats is not None else self.input_formats 

+

        self.format = format if format is not None else self.format 

+

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

+

 

+

    def from_native(self, value): 

+

        if value in validators.EMPTY_VALUES: 

+

            return None 

+

 

+

        if isinstance(value, datetime.time): 

+

            return value 

+

 

+

        for format in self.input_formats: 

+

            if format.lower() == ISO_8601: 

+

                try: 

+

                    parsed = parse_time(value) 

+

                except (ValueError, TypeError): 

+

                    pass 

+

                else: 

+

                    if parsed is not None: 

+

                        return parsed 

+

            else: 

+

                try: 

+

                    parsed = datetime.datetime.strptime(value, format) 

+

                except (ValueError, TypeError): 

+

                    pass 

+

                else: 

+

                    return parsed.time() 

+

 

+

        msg = self.error_messages['invalid'] % readable_time_formats(self.input_formats) 

+

        raise ValidationError(msg) 

+

 

+

    def to_native(self, value): 

+

        if value is None or self.format is None: 

+

            return value 

+

 

+

        if isinstance(value, datetime.datetime): 

+

            value = value.time() 

+

 

+

        if self.format.lower() == ISO_8601: 

+

            return value.isoformat() 

+

        return value.strftime(self.format) 

+

 

+

 

+

class IntegerField(WritableField): 

+

    type_name = 'IntegerField' 

+

    type_label = 'integer' 

+

    form_field_class = forms.IntegerField 

+

 

+

    default_error_messages = { 

+

        'invalid': _('Enter a whole number.'), 

+

        'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), 

+

        'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), 

+

    } 

+

 

+

    def __init__(self, max_value=None, min_value=None, *args, **kwargs): 

+

        self.max_value, self.min_value = max_value, min_value 

+

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

+

 

+

        if max_value is not None: 

+

            self.validators.append(validators.MaxValueValidator(max_value)) 

+

        if min_value is not None: 

+

            self.validators.append(validators.MinValueValidator(min_value)) 

+

 

+

    def from_native(self, value): 

+

        if value in validators.EMPTY_VALUES: 

+

            return None 

+

 

+

        try: 

+

            value = int(str(value)) 

+

        except (ValueError, TypeError): 

+

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

+

        return value 

+

 

+

 

+

class FloatField(WritableField): 

+

    type_name = 'FloatField' 

+

    type_label = 'float' 

+

    form_field_class = forms.FloatField 

+

 

+

    default_error_messages = { 

+

        'invalid': _("'%s' value must be a float."), 

+

    } 

+

 

+

    def from_native(self, value): 

+

        if value in validators.EMPTY_VALUES: 

+

            return None 

+

 

+

        try: 

+

            return float(value) 

+

        except (TypeError, ValueError): 

+

            msg = self.error_messages['invalid'] % value 

+

            raise ValidationError(msg) 

+

 

+

 

+

class DecimalField(WritableField): 

+

    type_name = 'DecimalField' 

+

    type_label = 'decimal' 

+

    form_field_class = forms.DecimalField 

+

 

+

    default_error_messages = { 

+

        'invalid': _('Enter a number.'), 

+

        'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), 

+

        'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), 

+

        'max_digits': _('Ensure that there are no more than %s digits in total.'), 

+

        'max_decimal_places': _('Ensure that there are no more than %s decimal places.'), 

+

        'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.') 

+

    } 

+

 

+

    def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs): 

+

        self.max_value, self.min_value = max_value, min_value 

+

        self.max_digits, self.decimal_places = max_digits, decimal_places 

+

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

+

 

+

        if max_value is not None: 

+

            self.validators.append(validators.MaxValueValidator(max_value)) 

+

        if min_value is not None: 

+

            self.validators.append(validators.MinValueValidator(min_value)) 

+

 

+

    def from_native(self, value): 

+

        """ 

+

        Validates that the input is a decimal number. Returns a Decimal 

+

        instance. Returns None for empty values. Ensures that there are no more 

+

        than max_digits in the number, and no more than decimal_places digits 

+

        after the decimal point. 

+

        """ 

+

        if value in validators.EMPTY_VALUES: 

+

            return None 

+

        value = smart_text(value).strip() 

+

        try: 

+

            value = Decimal(value) 

+

        except DecimalException: 

+

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

+

        return value 

+

 

+

    def validate(self, value): 

+

        super(DecimalField, self).validate(value) 

+

        if value in validators.EMPTY_VALUES: 

+

            return 

+

        # Check for NaN, Inf and -Inf values. We can't compare directly for NaN, 

+

        # since it is never equal to itself. However, NaN is the only value that 

+

        # isn't equal to itself, so we can use this to identify NaN 

+

        if value != value or value == Decimal("Inf") or value == Decimal("-Inf"): 

+

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

+

        sign, digittuple, exponent = value.as_tuple() 

+

        decimals = abs(exponent) 

+

        # digittuple doesn't include any leading zeros. 

+

        digits = len(digittuple) 

+

        if decimals > digits: 

+

            # We have leading zeros up to or past the decimal point.  Count 

+

            # everything past the decimal point as a digit.  We do not count 

+

            # 0 before the decimal point as a digit since that would mean 

+

            # we would not allow max_digits = decimal_places. 

+

            digits = decimals 

+

        whole_digits = digits - decimals 

+

 

+

        if self.max_digits is not None and digits > self.max_digits: 

+

            raise ValidationError(self.error_messages['max_digits'] % self.max_digits) 

+

        if self.decimal_places is not None and decimals > self.decimal_places: 

+

            raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places) 

+

        if self.max_digits is not None and self.decimal_places is not None and whole_digits > (self.max_digits - self.decimal_places): 

+

            raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places)) 

+

        return value 

+

 

+

 

+

class FileField(WritableField): 

+

    use_files = True 

+

    type_name = 'FileField' 

+

    type_label = 'file upload' 

+

    form_field_class = forms.FileField 

+

    widget = widgets.FileInput 

+

 

+

    default_error_messages = { 

+

        'invalid': _("No file was submitted. Check the encoding type on the form."), 

+

        'missing': _("No file was submitted."), 

+

        'empty': _("The submitted file is empty."), 

+

        'max_length': _('Ensure this filename has at most %(max)d characters (it has %(length)d).'), 

+

        'contradiction': _('Please either submit a file or check the clear checkbox, not both.') 

+

    } 

+

 

+

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

+

        self.max_length = kwargs.pop('max_length', None) 

+

        self.allow_empty_file = kwargs.pop('allow_empty_file', False) 

+

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

+

 

+

    def from_native(self, data): 

+

        if data in validators.EMPTY_VALUES: 

+

            return None 

+

 

+

        # UploadedFile objects should have name and size attributes. 

+

        try: 

+

            file_name = data.name 

+

            file_size = data.size 

+

        except AttributeError: 

+

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

+

 

+

        if self.max_length is not None and len(file_name) > self.max_length: 

+

            error_values = {'max': self.max_length, 'length': len(file_name)} 

+

            raise ValidationError(self.error_messages['max_length'] % error_values) 

+

        if not file_name: 

+

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

+

        if not self.allow_empty_file and not file_size: 

+

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

+

 

+

        return data 

+

 

+

    def to_native(self, value): 

+

        return value.name 

+

 

+

 

+

class ImageField(FileField): 

+

    use_files = True 

+

    type_name = 'ImageField' 

+

    type_label = 'image upload' 

+

    form_field_class = forms.ImageField 

+

 

+

    default_error_messages = { 

+

        'invalid_image': _("Upload a valid image. The file you uploaded was " 

+

                           "either not an image or a corrupted image."), 

+

    } 

+

 

+

    def from_native(self, data): 

+

        """ 

+

        Checks that the file-upload field data contains a valid image (GIF, JPG, 

+

        PNG, possibly others -- whatever the Python Imaging Library supports). 

+

        """ 

+

        f = super(ImageField, self).from_native(data) 

+

        if f is None: 

+

            return None 

+

 

+

        from compat import Image 

+

        assert Image is not None, 'PIL must be installed for ImageField support' 

+

 

+

        # We need to get a file object for PIL. We might have a path or we might 

+

        # have to read the data into memory. 

+

        if hasattr(data, 'temporary_file_path'): 

+

            file = data.temporary_file_path() 

+

        else: 

+

            if hasattr(data, 'read'): 

+

                file = BytesIO(data.read()) 

+

            else: 

+

                file = BytesIO(data['content']) 

+

 

+

        try: 

+

            # load() could spot a truncated JPEG, but it loads the entire 

+

            # image in memory, which is a DoS vector. See #3848 and #18520. 

+

            # verify() must be called immediately after the constructor. 

+

            Image.open(file).verify() 

+

        except ImportError: 

+

            # Under PyPy, it is possible to import PIL. However, the underlying 

+

            # _imaging C module isn't available, so an ImportError will be 

+

            # raised. Catch and re-raise. 

+

            raise 

+

        except Exception:  # Python Imaging Library doesn't recognize it as an image 

+

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

+

        if hasattr(f, 'seek') and callable(f.seek): 

+

            f.seek(0) 

+

        return f 

+

 

+

 

+

class SerializerMethodField(Field): 

+

    """ 

+

    A field that gets its value by calling a method on the serializer it's attached to. 

+

    """ 

+

 

+

    def __init__(self, method_name): 

+

        self.method_name = method_name 

+

        super(SerializerMethodField, self).__init__() 

+

 

+

    def field_to_native(self, obj, field_name): 

+

        value = getattr(self.parent, self.method_name)(obj) 

+

        return self.to_native(value) 

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