Services¶
drf-commons includes two production-ready service classes for file-based data operations: file export and file import.
File Export Service¶
ExportService¶
from drf_commons.services.export_file import ExportService
Generates file exports in CSV, XLSX, and PDF formats from structured data.
Supported formats:
Format |
Requirement |
Notes |
|---|---|---|
|
Core (no extras) |
UTF-8 encoded, suitable for data exchange |
|
|
Full Excel workbook with configurable column widths and headers |
|
|
Formatted table via weasyprint; suitable for human-readable reports |
Direct usage:
from drf_commons.services.export_file import ExportService
service = ExportService()
data = ArticleSerializer(queryset, many=True).data
# CSV
response = service.export_csv(data, field_config={"title": "Title", "published": "Published"})
# XLSX
response = service.export_xlsx(
data,
field_config={"title": "Title", "created_at": "Date"},
column_config={"title": {"width": 40}},
)
# PDF
response = service.export_pdf(data, field_config={"title": "Title"})
Via FileExportMixin (recommended):
class ArticleViewSet(BaseViewSet):
export_field_config = {
"title": "Article Title",
"published": "Status",
"created_at": "Date Created",
"created_by__username": "Author",
}
# POST /articles/export/
{
"file_type": "xlsx",
"includes": ["title", "published", "created_at"],
"column_config": {
"title": {"width": 50, "header": "Article Title"}
}
}
The exported file is returned as an HttpResponse with appropriate
Content-Disposition and MIME type headers.
File Import Service¶
FileImportService¶
from drf_commons.services.import_from_file import FileImportService
Processes file imports from CSV, XLS, and XLSX formats. Supports multi-model imports with dependency ordering, foreign key resolution, data transformation hooks, and progress callbacks.
Import Configuration Reference¶
The import_file_config dict (set on the ViewSet or passed directly to the
service) defines the complete import specification:
import_file_config = {
# Required: file format
"file_format": "xlsx", # "csv", "xls", "xlsx"
# Required: processing order for models (dependency-first)
"order": ["country", "city", "person"],
# Required: model definitions
"models": {
"country": {
"model": Country, # Django model class
"fields": ["name", "code"], # Columns to import
"unique_fields": ["code"], # Fields for upsert lookup
},
"city": {
"model": City,
"fields": ["name", "country_code"],
"unique_fields": ["name", "country_code"],
"foreign_keys": {
# Map a column to a related model
"country": {
"model": Country,
"lookup_field": "code",
"source_field": "country_code",
}
},
},
"person": {
"model": Person,
"fields": ["first_name", "last_name", "email", "city_name"],
"unique_fields": ["email"],
"foreign_keys": {
"city": {
"model": City,
"lookup_field": "name",
"source_field": "city_name",
}
},
},
},
# Optional: process large files in chunks
"chunk_size": 500,
}
Transformation Hooks¶
The import_transforms dict on the ViewSet applies per-field transformations
before data is written to the database:
class PersonViewSet(ImportableViewSet):
import_file_config = { ... }
import_transforms = {
"email": lambda v: v.strip().lower(),
"first_name": lambda v: v.strip().title(),
"last_name": lambda v: v.strip().title(),
"phone": lambda v: re.sub(r"\D", "", v), # digits only
}
Progress Callbacks¶
For long-running imports, pass a progress callback to the service:
def on_progress(processed: int, total: int, errors: list):
logger.info(f"Import progress: {processed}/{total} rows ({len(errors)} errors)")
service = FileImportService(
config=import_file_config,
transforms=import_transforms,
)
result = service.import_file(
file_obj=uploaded_file,
progress_callback=on_progress,
)
Import Result¶
The service returns a structured result dict:
{
"success": True,
"processed": 1200,
"failed": 3,
"errors": [
{"row": 45, "field": "email", "message": "Email already exists"},
{"row": 89, "field": "country_code", "message": "Country 'XX' not found"},
],
"created": 800,
"updated": 397,
}
Import Template Generation¶
The generate_import_template management command generates a sample import
file based on the ViewSet’s import_file_config:
python manage.py generate_import_template PersonViewSet
This generates a file (XLSX by default) with:
One sheet per model in the import config
Column headers matching the
fieldslistExample data in the first data row
Chunk-Based Processing¶
For very large files, set chunk_size in the import config. The service
reads the file in chunks and processes each chunk in its own transaction:
import_file_config = {
"file_format": "xlsx",
"chunk_size": 250, # Process 250 rows per transaction
"order": ["person"],
"models": { ... },
}
This bounds peak memory usage and ensures that a failure in one chunk does not roll back the entire import.
DataProcessor¶
The import pipeline internal components:
Component |
Responsibility |
|---|---|
|
Reads CSV/XLS/XLSX into a pandas DataFrame |
|
Validates the |
|
Applies transforms, resolves foreign keys, validates rows |
|
Executes |
These components are internal to the service and are not part of the public API. Customize import behavior through the configuration interface, transforms, and progress callbacks.