or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-types.mddebug.mdfields.mdfiltering.mdforms.mdindex.mdrest-framework.mdtesting.mdviews.md

forms.mddocs/

0

# Forms Integration

1

2

Form-based GraphQL mutations with Django form validation, error handling, and automatic input type generation. Provides seamless integration between Django forms and GraphQL mutations with comprehensive validation and error reporting.

3

4

## Capabilities

5

6

### BaseDjangoFormMutation

7

8

Abstract base class for Django form-based mutations with customizable form handling and validation logic.

9

10

```python { .api }

11

class BaseDjangoFormMutation(graphene.relay.ClientIDMutation):

12

"""

13

Base class for Django form-based mutations.

14

15

Provides foundation for form-based mutations with form instantiation,

16

validation, and error handling. Designed to be subclassed for specific

17

form types and validation requirements.

18

"""

19

20

@classmethod

21

def mutate_and_get_payload(cls, root, info, **input):

22

"""

23

Main mutation logic with form processing.

24

25

Parameters:

26

- root: GraphQL root object

27

- info: GraphQL execution info

28

- **input: Mutation input arguments

29

30

Returns:

31

Mutation result with form data or errors

32

"""

33

34

@classmethod

35

def get_form(cls, root, info, **input):

36

"""

37

Get form instance for validation.

38

39

Parameters:

40

- root: GraphQL root object

41

- info: GraphQL execution info

42

- **input: Form input data

43

44

Returns:

45

django.forms.Form: Form instance

46

"""

47

48

@classmethod

49

def get_form_kwargs(cls, root, info, **input):

50

"""

51

Get form constructor arguments.

52

53

Parameters:

54

- root: GraphQL root object

55

- info: GraphQL execution info

56

- **input: Form input data

57

58

Returns:

59

dict: Form constructor keyword arguments

60

"""

61

62

@classmethod

63

def perform_mutate(cls, form, info):

64

"""

65

Perform mutation with validated form.

66

67

Parameters:

68

- form: Validated Django form

69

- info: GraphQL execution info

70

71

Returns:

72

Mutation result object

73

"""

74

```

75

76

### DjangoFormMutation

77

78

Concrete form mutation with automatic error handling and input type generation from Django forms.

79

80

```python { .api }

81

class DjangoFormMutation(BaseDjangoFormMutation):

82

"""

83

Concrete form mutation with error handling.

84

85

Automatically generates GraphQL input types from Django forms and

86

provides standardized error reporting through ErrorType objects.

87

"""

88

errors = graphene.List(ErrorType)

89

90

class Meta:

91

"""

92

Meta options for form mutations.

93

94

Attributes:

95

- form_class: Django form class to use

96

- only_fields: Fields to include in GraphQL input (list or tuple)

97

- exclude_fields: Fields to exclude from GraphQL input (list or tuple)

98

- return_field_name: Name for successful result field

99

"""

100

form_class = None

101

only_fields = None

102

exclude_fields = None

103

return_field_name = None

104

105

@classmethod

106

def __init_subclass_with_meta__(cls, form_class=None, only_fields=None,

107

exclude_fields=None, return_field_name=None,

108

**options):

109

"""

110

Configure form mutation subclass.

111

112

Parameters:

113

- form_class: Django form class

114

- only_fields: Fields to include

115

- exclude_fields: Fields to exclude

116

- return_field_name: Result field name

117

- **options: Additional mutation options

118

"""

119

120

@classmethod

121

def perform_mutate(cls, form, info):

122

"""

123

Perform mutation with validated form data.

124

125

Parameters:

126

- form: Validated Django form

127

- info: GraphQL execution info

128

129

Returns:

130

Mutation result with form data

131

"""

132

```

133

134

### DjangoModelFormMutation

135

136

Model form-based mutations with CRUD operations and automatic model instance handling.

137

138

```python { .api }

139

class DjangoModelFormMutation(BaseDjangoFormMutation):

140

"""

141

Model form-based mutations with CRUD operations.

142

143

Automatically handles model instance creation and updates through

144

Django ModelForm validation with comprehensive error handling.

145

"""

146

errors = graphene.List(ErrorType)

147

148

class Meta:

149

"""

150

Meta options for model form mutations.

151

152

Inherits from DjangoFormMutation.Meta with additional model-specific

153

options for instance handling and field mapping.

154

"""

155

form_class = None

156

model = None

157

only_fields = None

158

exclude_fields = None

159

return_field_name = None

160

161

@classmethod

162

def get_form_kwargs(cls, root, info, **input):

163

"""

164

Get model form constructor arguments with instance handling.

165

166

Parameters:

167

- root: GraphQL root object

168

- info: GraphQL execution info

169

- **input: Form input data

170

171

Returns:

172

dict: Form constructor arguments including model instance

173

"""

174

175

@classmethod

176

def perform_mutate(cls, form, info):

177

"""

178

Perform mutation with model form save.

179

180

Parameters:

181

- form: Validated Django model form

182

- info: GraphQL execution info

183

184

Returns:

185

Mutation result with saved model instance

186

"""

187

```

188

189

### Form Field Types

190

191

Global ID form fields for GraphQL integration with proper ID encoding and validation.

192

193

```python { .api }

194

class GlobalIDFormField(forms.CharField):

195

"""Django form field for global IDs with automatic validation."""

196

197

def to_python(self, value):

198

"""

199

Convert global ID to Python value.

200

201

Parameters:

202

- value: Global ID string

203

204

Returns:

205

Decoded ID value

206

"""

207

208

class GlobalIDMultipleChoiceField(forms.MultipleChoiceField):

209

"""Multiple choice field for global IDs with batch processing."""

210

211

def to_python(self, value):

212

"""

213

Convert multiple global IDs to Python values.

214

215

Parameters:

216

- value: List of global ID strings

217

218

Returns:

219

List of decoded ID values

220

"""

221

```

222

223

## Usage Examples

224

225

### Basic Form Mutation

226

227

```python

228

from django import forms

229

from graphene_django.forms.mutation import DjangoFormMutation

230

import graphene

231

232

class ContactForm(forms.Form):

233

name = forms.CharField(max_length=100)

234

email = forms.EmailField()

235

message = forms.CharField(widget=forms.Textarea)

236

237

def save(self):

238

# Custom save logic

239

data = self.cleaned_data

240

# Send email, save to database, etc.

241

return data

242

243

class ContactMutation(DjangoFormMutation):

244

class Meta:

245

form_class = ContactForm

246

247

@classmethod

248

def perform_mutate(cls, form, info):

249

result = form.save()

250

return cls(errors=[], **result)

251

252

class Mutation(graphene.ObjectType):

253

contact_form = ContactMutation.Field()

254

255

# GraphQL mutation:

256

# mutation {

257

# contactForm(input: {

258

# name: "John Doe"

259

# email: "john@example.com"

260

# message: "Hello world"

261

# }) {

262

# errors {

263

# field

264

# messages

265

# }

266

# name

267

# email

268

# }

269

# }

270

```

271

272

### Model Form Mutation

273

274

```python

275

from django.db import models

276

from django import forms

277

from graphene_django.forms.mutation import DjangoModelFormMutation

278

279

class User(models.Model):

280

username = models.CharField(max_length=150, unique=True)

281

email = models.EmailField()

282

first_name = models.CharField(max_length=30)

283

last_name = models.CharField(max_length=30)

284

285

class UserForm(forms.ModelForm):

286

class Meta:

287

model = User

288

fields = ['username', 'email', 'first_name', 'last_name']

289

290

def clean_email(self):

291

email = self.cleaned_data['email']

292

if User.objects.filter(email=email).exists():

293

raise forms.ValidationError("Email already exists")

294

return email

295

296

class CreateUserMutation(DjangoModelFormMutation):

297

user = graphene.Field('myapp.schema.UserType')

298

299

class Meta:

300

form_class = UserForm

301

return_field_name = 'user'

302

303

class UpdateUserMutation(DjangoModelFormMutation):

304

user = graphene.Field('myapp.schema.UserType')

305

306

class Meta:

307

form_class = UserForm

308

return_field_name = 'user'

309

310

@classmethod

311

def get_form_kwargs(cls, root, info, **input):

312

kwargs = super().get_form_kwargs(root, info, **input)

313

# Get existing user for update

314

user_id = input.get('id')

315

if user_id:

316

kwargs['instance'] = User.objects.get(pk=user_id)

317

return kwargs

318

```

319

320

### Custom Form with File Upload

321

322

```python

323

class ProfileForm(forms.Form):

324

avatar = forms.ImageField()

325

bio = forms.CharField(widget=forms.Textarea, required=False)

326

327

def save(self, user):

328

cleaned_data = self.cleaned_data

329

user.profile.avatar = cleaned_data['avatar']

330

user.profile.bio = cleaned_data['bio']

331

user.profile.save()

332

return user.profile

333

334

class UpdateProfileMutation(DjangoFormMutation):

335

profile = graphene.Field('myapp.schema.ProfileType')

336

337

class Meta:

338

form_class = ProfileForm

339

340

@classmethod

341

def get_form_kwargs(cls, root, info, **input):

342

kwargs = super().get_form_kwargs(root, info, **input)

343

# Add files from request

344

if hasattr(info.context, 'FILES'):

345

kwargs['files'] = info.context.FILES

346

return kwargs

347

348

@classmethod

349

def perform_mutate(cls, form, info):

350

profile = form.save(info.context.user)

351

return cls(profile=profile, errors=[])

352

```

353

354

### Form with Custom Validation

355

356

```python

357

class PasswordChangeForm(forms.Form):

358

old_password = forms.CharField(widget=forms.PasswordInput)

359

new_password = forms.CharField(widget=forms.PasswordInput)

360

confirm_password = forms.CharField(widget=forms.PasswordInput)

361

362

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

363

self.user = user

364

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

365

366

def clean_old_password(self):

367

old_password = self.cleaned_data['old_password']

368

if not self.user.check_password(old_password):

369

raise forms.ValidationError("Invalid current password")

370

return old_password

371

372

def clean(self):

373

cleaned_data = super().clean()

374

new_password = cleaned_data.get('new_password')

375

confirm_password = cleaned_data.get('confirm_password')

376

377

if new_password and confirm_password:

378

if new_password != confirm_password:

379

raise forms.ValidationError("Passwords don't match")

380

381

return cleaned_data

382

383

def save(self):

384

self.user.set_password(self.cleaned_data['new_password'])

385

self.user.save()

386

387

class ChangePasswordMutation(DjangoFormMutation):

388

success = graphene.Boolean()

389

390

class Meta:

391

form_class = PasswordChangeForm

392

exclude_fields = ('old_password',) # Don't expose in GraphQL

393

394

@classmethod

395

def get_form_kwargs(cls, root, info, **input):

396

kwargs = super().get_form_kwargs(root, info, **input)

397

kwargs['user'] = info.context.user

398

return kwargs

399

400

@classmethod

401

def perform_mutate(cls, form, info):

402

form.save()

403

return cls(success=True, errors=[])

404

```

405

406

### Form with Global ID Fields

407

408

```python

409

from graphene_django.forms.forms import GlobalIDFormField, GlobalIDMultipleChoiceField

410

411

class AssignTaskForm(forms.Form):

412

task_id = GlobalIDFormField()

413

assignee_ids = GlobalIDMultipleChoiceField()

414

due_date = forms.DateTimeField()

415

416

def save(self):

417

task = Task.objects.get(pk=self.cleaned_data['task_id'])

418

assignees = User.objects.filter(pk__in=self.cleaned_data['assignee_ids'])

419

420

task.assignees.set(assignees)

421

task.due_date = self.cleaned_data['due_date']

422

task.save()

423

return task

424

425

class AssignTaskMutation(DjangoFormMutation):

426

task = graphene.Field('myapp.schema.TaskType')

427

428

class Meta:

429

form_class = AssignTaskForm

430

431

@classmethod

432

def perform_mutate(cls, form, info):

433

task = form.save()

434

return cls(task=task, errors=[])

435

```

436

437

### Nested Form Mutation

438

439

```python

440

class AddressForm(forms.Form):

441

street = forms.CharField(max_length=200)

442

city = forms.CharField(max_length=100)

443

postal_code = forms.CharField(max_length=10)

444

445

class UserWithAddressForm(forms.Form):

446

username = forms.CharField(max_length=150)

447

email = forms.EmailField()

448

449

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

450

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

451

# Extract address data

452

address_data = {}

453

for key in list(kwargs.get('data', {}).keys()):

454

if key.startswith('address_'):

455

address_key = key.replace('address_', '')

456

address_data[address_key] = kwargs['data'].pop(key)

457

458

self.address_form = AddressForm(data=address_data)

459

460

def is_valid(self):

461

return super().is_valid() and self.address_form.is_valid()

462

463

def save(self):

464

user = User.objects.create(

465

username=self.cleaned_data['username'],

466

email=self.cleaned_data['email']

467

)

468

469

if self.address_form.is_valid():

470

Address.objects.create(

471

user=user,

472

**self.address_form.cleaned_data

473

)

474

475

return user

476

```