0
# Forms and UI Components
1
2
Form classes for handling file uploads, format selection, field selection, and import confirmation workflows in web interfaces.
3
4
## Capabilities
5
6
### Base Import/Export Form
7
8
Foundation form class providing common functionality for import/export operations.
9
10
```python { .api }
11
class ImportExportFormBase(forms.Form):
12
file_format = forms.ChoiceField(label='Format', choices=())
13
14
def __init__(self, import_formats, **kwargs):
15
"""
16
Initialize form with available formats.
17
18
Parameters:
19
- import_formats: List of format classes available for import/export
20
- **kwargs: Additional form initialization options
21
"""
22
23
def get_format_choices(self):
24
"""
25
Get format choices for the file_format field.
26
27
Returns:
28
List of (value, label) tuples for format selection
29
"""
30
```
31
32
### Import Forms
33
34
Forms specifically designed for data import workflows.
35
36
```python { .api }
37
class ImportForm(ImportExportFormBase):
38
import_file = forms.FileField(label='File to import')
39
resource = forms.ChoiceField(label='Resource', choices=(), required=False)
40
41
def __init__(self, import_formats, resources=None, **kwargs):
42
"""
43
Initialize import form with formats and resources.
44
45
Parameters:
46
- import_formats: List of format classes for import
47
- resources: List of resource classes (optional)
48
- **kwargs: Additional form options
49
"""
50
51
def is_valid(self):
52
"""
53
Validate form data including file upload and format selection.
54
55
Returns:
56
bool, True if form is valid
57
"""
58
59
class ConfirmImportForm(forms.Form):
60
import_file_name = forms.CharField(widget=forms.HiddenInput())
61
original_file_name = forms.CharField(widget=forms.HiddenInput())
62
input_format = forms.CharField(widget=forms.HiddenInput())
63
resource = forms.CharField(widget=forms.HiddenInput(), required=False)
64
65
def __init__(self, confirm_form_class, import_form_data, **kwargs):
66
"""
67
Initialize confirmation form with import data.
68
69
Parameters:
70
- confirm_form_class: Form class for confirmation
71
- import_form_data: Data from initial import form
72
- **kwargs: Additional form options
73
"""
74
```
75
76
### Export Forms
77
78
Forms for data export operations with format and field selection.
79
80
```python { .api }
81
class ExportForm(ImportExportFormBase):
82
resource = forms.ChoiceField(label='Resource', choices=(), required=False)
83
84
def __init__(self, export_formats, resources=None, **kwargs):
85
"""
86
Initialize export form with formats and resources.
87
88
Parameters:
89
- export_formats: List of format classes for export
90
- resources: List of resource classes (optional)
91
- **kwargs: Additional form options
92
"""
93
94
class SelectableFieldsExportForm(ExportForm):
95
"""Export form with field selection capability."""
96
97
def __init__(self, export_formats, resources=None, resource_class=None, **kwargs):
98
"""
99
Initialize export form with field selection.
100
101
Parameters:
102
- export_formats: List of format classes for export
103
- resources: List of resource classes (optional)
104
- resource_class: Specific resource class for field extraction
105
- **kwargs: Additional form options
106
"""
107
108
def get_export_fields(self):
109
"""
110
Get selected fields for export.
111
112
Returns:
113
List of field names selected for export
114
"""
115
```
116
117
## Usage Examples
118
119
### Basic Import Form Usage
120
121
```python
122
from import_export.forms import ImportForm
123
from import_export.formats.base_formats import CSV, XLSX, JSON
124
from django.shortcuts import render, redirect
125
from django.contrib import messages
126
127
def import_view(request):
128
"""View for handling data import."""
129
130
formats = [CSV, XLSX, JSON]
131
132
if request.method == 'POST':
133
form = ImportForm(formats, data=request.POST, files=request.FILES)
134
if form.is_valid():
135
import_file = form.cleaned_data['import_file']
136
file_format = form.cleaned_data['file_format']
137
138
# Process import
139
resource = BookResource()
140
format_class = next(f for f in formats if f().get_title() == file_format)
141
142
try:
143
dataset = format_class().create_dataset(import_file.read().decode('utf-8'))
144
result = resource.import_data(dataset, dry_run=True)
145
146
if result.has_errors():
147
messages.error(request, f"Import errors: {len(result.base_errors)} errors found")
148
else:
149
messages.success(request, f"Import preview: {result.total_rows} rows to import")
150
# Store data for confirmation
151
request.session['import_data'] = {
152
'dataset': dataset.dict,
153
'format': file_format,
154
}
155
return redirect('confirm_import')
156
157
except Exception as e:
158
messages.error(request, f"Import failed: {e}")
159
else:
160
form = ImportForm(formats)
161
162
return render(request, 'import.html', {'form': form})
163
```
164
165
### Export Form with Field Selection
166
167
```python
168
from import_export.forms import SelectableFieldsExportForm
169
from django.http import HttpResponse
170
171
def export_view(request):
172
"""View for handling data export with field selection."""
173
174
formats = [CSV, XLSX, JSON]
175
resource_class = BookResource
176
177
if request.method == 'POST':
178
form = SelectableFieldsExportForm(
179
formats,
180
resource_class=resource_class,
181
data=request.POST
182
)
183
if form.is_valid():
184
file_format = form.cleaned_data['file_format']
185
selected_fields = form.get_export_fields()
186
187
# Perform export with selected fields
188
resource = resource_class()
189
queryset = Book.objects.all()
190
dataset = resource.export(queryset, selected_fields=selected_fields)
191
192
# Get format class and export data
193
format_class = next(f for f in formats if f().get_title() == file_format)
194
export_data = format_class().export_data(dataset)
195
196
# Create response
197
response = HttpResponse(
198
export_data,
199
content_type=format_class().get_content_type()
200
)
201
filename = f"books.{format_class().get_extension()}"
202
response['Content-Disposition'] = f'attachment; filename="{filename}"'
203
204
return response
205
else:
206
form = SelectableFieldsExportForm(formats, resource_class=resource_class)
207
208
return render(request, 'export.html', {'form': form})
209
```
210
211
### Custom Import Form with Validation
212
213
```python
214
from import_export.forms import ImportForm
215
from django import forms
216
217
class CustomImportForm(ImportForm):
218
"""Custom import form with additional validation and fields."""
219
220
encoding = forms.ChoiceField(
221
choices=[
222
('utf-8', 'UTF-8'),
223
('latin-1', 'Latin-1'),
224
('cp1252', 'Windows-1252'),
225
],
226
initial='utf-8',
227
help_text='Character encoding of the import file'
228
)
229
230
skip_errors = forms.BooleanField(
231
required=False,
232
help_text='Skip rows with errors and continue import'
233
)
234
235
def clean_import_file(self):
236
"""Validate import file."""
237
import_file = self.cleaned_data.get('import_file')
238
239
if not import_file:
240
return import_file
241
242
# Check file size (limit to 10MB)
243
if import_file.size > 10 * 1024 * 1024:
244
raise forms.ValidationError('File too large. Maximum size is 10MB.')
245
246
# Check file extension
247
allowed_extensions = ['.csv', '.xlsx', '.xls', '.json', '.yaml']
248
file_extension = import_file.name.lower().split('.')[-1]
249
if f'.{file_extension}' not in allowed_extensions:
250
raise forms.ValidationError(
251
f'Unsupported file type. Allowed: {", ".join(allowed_extensions)}'
252
)
253
254
return import_file
255
256
def clean(self):
257
"""Additional form validation."""
258
cleaned_data = super().clean()
259
260
import_file = cleaned_data.get('import_file')
261
file_format = cleaned_data.get('file_format')
262
263
if import_file and file_format:
264
# Validate format matches file extension
265
file_ext = import_file.name.lower().split('.')[-1]
266
format_ext = file_format.lower()
267
268
if file_ext != format_ext and not (file_ext == 'xls' and format_ext == 'xlsx'):
269
raise forms.ValidationError(
270
f'File extension .{file_ext} does not match selected format {format_ext}'
271
)
272
273
return cleaned_data
274
275
# Usage in view
276
def custom_import_view(request):
277
formats = [CSV, XLSX, JSON]
278
279
if request.method == 'POST':
280
form = CustomImportForm(formats, data=request.POST, files=request.FILES)
281
if form.is_valid():
282
# Access custom fields
283
encoding = form.cleaned_data['encoding']
284
skip_errors = form.cleaned_data['skip_errors']
285
286
# Process import with custom options
287
# ...
288
else:
289
form = CustomImportForm(formats)
290
291
return render(request, 'custom_import.html', {'form': form})
292
```
293
294
### Confirmation Form with Preview
295
296
```python
297
from import_export.forms import ConfirmImportForm
298
from django.forms import formset_factory
299
300
class ImportPreviewForm(forms.Form):
301
"""Form for displaying import preview data."""
302
303
def __init__(self, dataset, *args, **kwargs):
304
super().__init__(*args, **kwargs)
305
self.dataset = dataset
306
307
# Add fields for each row to allow editing
308
for i, row in enumerate(dataset):
309
for j, header in enumerate(dataset.headers):
310
field_name = f'row_{i}_col_{j}'
311
self.fields[field_name] = forms.CharField(
312
initial=row[j],
313
required=False,
314
widget=forms.TextInput(attrs={'class': 'form-control'})
315
)
316
317
class CustomConfirmImportForm(ConfirmImportForm):
318
"""Enhanced confirmation form with options."""
319
320
dry_run = forms.BooleanField(
321
required=False,
322
initial=True,
323
help_text='Perform dry run (no actual import)'
324
)
325
326
use_transactions = forms.BooleanField(
327
required=False,
328
initial=True,
329
help_text='Use database transactions'
330
)
331
332
skip_unchanged = forms.BooleanField(
333
required=False,
334
help_text='Skip unchanged rows'
335
)
336
337
def confirm_import_view(request):
338
"""View for import confirmation with preview."""
339
340
if 'import_data' not in request.session:
341
return redirect('import_view')
342
343
import_data = request.session['import_data']
344
dataset = Dataset()
345
dataset.dict = import_data['dataset']
346
347
if request.method == 'POST':
348
confirm_form = CustomConfirmImportForm(data=request.POST)
349
if confirm_form.is_valid():
350
# Perform actual import
351
resource = BookResource()
352
result = resource.import_data(
353
dataset,
354
dry_run=confirm_form.cleaned_data.get('dry_run', True),
355
use_transactions=confirm_form.cleaned_data.get('use_transactions', True),
356
skip_unchanged=confirm_form.cleaned_data.get('skip_unchanged', False)
357
)
358
359
if result.has_errors():
360
messages.error(request, f"Import failed with {len(result.base_errors)} errors")
361
else:
362
messages.success(request, f"Successfully imported {result.total_rows} rows")
363
del request.session['import_data']
364
return redirect('success_view')
365
else:
366
confirm_form = CustomConfirmImportForm(initial=import_data)
367
368
preview_form = ImportPreviewForm(dataset)
369
370
return render(request, 'confirm_import.html', {
371
'confirm_form': confirm_form,
372
'preview_form': preview_form,
373
'dataset': dataset,
374
})
375
```
376
377
### Multi-Step Import Wizard
378
379
```python
380
from django.contrib.formtools.wizard.views import SessionWizardView
381
from django.core.files.storage import default_storage
382
383
class ImportWizard(SessionWizardView):
384
"""Multi-step import wizard."""
385
386
form_list = [
387
('upload', CustomImportForm),
388
('preview', ImportPreviewForm),
389
('confirm', CustomConfirmImportForm),
390
]
391
392
templates = {
393
'upload': 'wizard/upload.html',
394
'preview': 'wizard/preview.html',
395
'confirm': 'wizard/confirm.html',
396
}
397
398
def get_form_kwargs(self, step=None):
399
"""Get form kwargs for each step."""
400
kwargs = super().get_form_kwargs(step)
401
402
if step == 'upload':
403
kwargs['import_formats'] = [CSV, XLSX, JSON]
404
elif step == 'preview':
405
# Get dataset from previous step
406
upload_data = self.get_cleaned_data_for_step('upload')
407
if upload_data:
408
import_file = upload_data['import_file']
409
file_format = upload_data['file_format']
410
411
# Create dataset
412
format_class = self.get_format_class(file_format)
413
dataset = format_class.create_dataset(import_file.read().decode('utf-8'))
414
kwargs['dataset'] = dataset
415
416
return kwargs
417
418
def done(self, form_list, **kwargs):
419
"""Process completed wizard."""
420
upload_form, preview_form, confirm_form = form_list
421
422
# Get final import settings
423
import_file = upload_form.cleaned_data['import_file']
424
file_format = upload_form.cleaned_data['file_format']
425
dry_run = confirm_form.cleaned_data.get('dry_run', False)
426
427
# Perform import
428
resource = BookResource()
429
format_class = self.get_format_class(file_format)
430
dataset = format_class.create_dataset(import_file.read().decode('utf-8'))
431
432
result = resource.import_data(dataset, dry_run=dry_run)
433
434
return render(self.request, 'wizard/complete.html', {
435
'result': result,
436
})
437
438
def get_format_class(self, format_name):
439
"""Get format class by name."""
440
formats = {'csv': CSV, 'xlsx': XLSX, 'json': JSON}
441
return formats.get(format_name.lower(), CSV)
442
```
443
444
### AJAX Import Form
445
446
```python
447
from django.http import JsonResponse
448
from django.views.decorators.csrf import csrf_exempt
449
import json
450
451
@csrf_exempt
452
def ajax_import_view(request):
453
"""AJAX endpoint for import operations."""
454
455
if request.method != 'POST':
456
return JsonResponse({'error': 'Method not allowed'}, status=405)
457
458
form = ImportForm([CSV, XLSX, JSON], data=request.POST, files=request.FILES)
459
460
if not form.is_valid():
461
return JsonResponse({
462
'success': False,
463
'errors': form.errors
464
})
465
466
try:
467
import_file = form.cleaned_data['import_file']
468
file_format = form.cleaned_data['file_format']
469
470
# Process import
471
resource = BookResource()
472
format_class = {'csv': CSV, 'xlsx': XLSX, 'json': JSON}[file_format.lower()]
473
474
dataset = format_class().create_dataset(import_file.read().decode('utf-8'))
475
result = resource.import_data(dataset, dry_run=True)
476
477
return JsonResponse({
478
'success': True,
479
'preview': {
480
'total_rows': result.total_rows,
481
'has_errors': result.has_errors(),
482
'errors': [str(error) for error in result.base_errors],
483
'valid_rows': len(result.valid_rows()),
484
}
485
})
486
487
except Exception as e:
488
return JsonResponse({
489
'success': False,
490
'error': str(e)
491
})
492
493
# JavaScript for AJAX form
494
"""
495
$('#import-form').on('submit', function(e) {
496
e.preventDefault();
497
498
var formData = new FormData(this);
499
500
$.ajax({
501
url: '{% url "ajax_import" %}',
502
type: 'POST',
503
data: formData,
504
processData: false,
505
contentType: false,
506
success: function(response) {
507
if (response.success) {
508
$('#preview-section').html(
509
'<p>Preview: ' + response.preview.total_rows + ' rows</p>' +
510
'<p>Valid rows: ' + response.preview.valid_rows + '</p>'
511
);
512
if (response.preview.has_errors) {
513
$('#errors-section').html(
514
'<ul><li>' + response.preview.errors.join('</li><li>') + '</li></ul>'
515
);
516
}
517
} else {
518
alert('Import failed: ' + response.error);
519
}
520
},
521
error: function() {
522
alert('Request failed');
523
}
524
});
525
});
526
"""
527
```
528
529
### Form Integration with Class-Based Views
530
531
```python
532
from django.views.generic import FormView
533
from django.urls import reverse_lazy
534
535
class ImportFormView(FormView):
536
"""Class-based view for import form."""
537
538
template_name = 'import_form.html'
539
form_class = CustomImportForm
540
success_url = reverse_lazy('import_success')
541
542
def get_form_kwargs(self):
543
kwargs = super().get_form_kwargs()
544
kwargs['import_formats'] = [CSV, XLSX, JSON]
545
return kwargs
546
547
def form_valid(self, form):
548
# Process import
549
import_file = form.cleaned_data['import_file']
550
file_format = form.cleaned_data['file_format']
551
552
resource = BookResource()
553
format_class = self.get_format_class(file_format)
554
dataset = format_class.create_dataset(import_file.read().decode('utf-8'))
555
556
result = resource.import_data(dataset, dry_run=False)
557
558
# Store result in session for success page
559
self.request.session['import_result'] = {
560
'total_rows': result.total_rows,
561
'has_errors': result.has_errors(),
562
'errors': [str(error) for error in result.base_errors],
563
}
564
565
return super().form_valid(form)
566
567
def get_format_class(self, format_name):
568
formats = {'csv': CSV, 'xlsx': XLSX, 'json': JSON}
569
return formats.get(format_name.lower(), CSV)
570
571
class ExportFormView(FormView):
572
"""Class-based view for export form."""
573
574
template_name = 'export_form.html'
575
form_class = SelectableFieldsExportForm
576
577
def get_form_kwargs(self):
578
kwargs = super().get_form_kwargs()
579
kwargs['export_formats'] = [CSV, XLSX, JSON]
580
kwargs['resource_class'] = BookResource
581
return kwargs
582
583
def form_valid(self, form):
584
file_format = form.cleaned_data['file_format']
585
selected_fields = form.get_export_fields()
586
587
# Perform export
588
resource = BookResource()
589
queryset = Book.objects.all()
590
dataset = resource.export(queryset, selected_fields=selected_fields)
591
592
# Create download response
593
format_class = self.get_format_class(file_format)
594
export_data = format_class.export_data(dataset)
595
596
response = HttpResponse(
597
export_data,
598
content_type=format_class.get_content_type()
599
)
600
filename = f"books.{format_class.get_extension()}"
601
response['Content-Disposition'] = f'attachment; filename="{filename}"'
602
603
return response
604
```