Django application and library for importing and exporting data with included admin integration.
—
Comprehensive widget system for transforming data between Python objects and serialized formats, including support for various data types and relationships.
The foundation widget class that all other widgets extend.
class Widget:
def clean(self, value, row=None, **kwargs):
"""
Transform imported value into appropriate Python object.
Parameters:
- value: Raw value from import data
- row: dict, complete row data (optional)
- **kwargs: Additional cleaning options
Returns:
Cleaned Python value
"""
def render(self, value, obj=None, **kwargs):
"""
Transform Python value into export representation.
Parameters:
- value: Python value to export
- obj: Model instance being exported (optional)
- **kwargs: Additional rendering options
Returns:
Serialized value for export
"""Widgets for handling numeric data types with validation and formatting.
class NumberWidget(Widget):
"""Base widget for numeric data with locale support."""
def __init__(self, **kwargs):
"""
Initialize numeric widget.
Parameters:
- **kwargs: Widget configuration options
"""
class FloatWidget(NumberWidget):
"""Widget for floating-point numbers."""
class IntegerWidget(NumberWidget):
"""Widget for integer numbers."""
class DecimalWidget(NumberWidget):
"""Widget for decimal numbers with precision control."""Widgets for handling text and character data.
class CharWidget(Widget):
def __init__(self, allow_blank=False, **kwargs):
"""
Widget for character/string data.
Parameters:
- allow_blank: bool, allow blank values (default: False)
- **kwargs: Additional widget options
"""Widget for handling boolean data with customizable true/false values.
class BooleanWidget(Widget):
TRUE_VALUES = ['1', 1, True, 'true', 'TRUE', 'True', 'yes', 'YES', 'Yes', 'y', 'Y']
FALSE_VALUES = ['0', 0, False, 'false', 'FALSE', 'False', 'no', 'NO', 'No', 'n', 'N']
NULL_VALUES = ['', None]
def __init__(self, **kwargs):
"""
Widget for boolean data with customizable value mapping.
Parameters:
- **kwargs: Widget configuration options
"""Widgets for handling temporal data with format support.
class DateWidget(Widget):
def __init__(self, format='%Y-%m-%d', **kwargs):
"""
Widget for date data.
Parameters:
- format: str, date format string (default: '%Y-%m-%d')
- **kwargs: Additional widget options
"""
class DateTimeWidget(Widget):
def __init__(self, format='%Y-%m-%d %H:%M:%S', **kwargs):
"""
Widget for datetime data.
Parameters:
- format: str, datetime format string (default: '%Y-%m-%d %H:%M:%S')
- **kwargs: Additional widget options
"""
class TimeWidget(Widget):
def __init__(self, format='%H:%M:%S', **kwargs):
"""
Widget for time data.
Parameters:
- format: str, time format string (default: '%H:%M:%S')
- **kwargs: Additional widget options
"""
class DurationWidget(Widget):
"""Widget for duration/timedelta data."""Widgets for complex data structures like JSON and arrays.
class JSONWidget(Widget):
def __init__(self, **kwargs):
"""
Widget for JSON data serialization/deserialization.
Parameters:
- **kwargs: JSON serialization options
"""
class SimpleArrayWidget(Widget):
def __init__(self, separator=',', **kwargs):
"""
Widget for simple array data.
Parameters:
- separator: str, separator character (default: ',')
- **kwargs: Additional widget options
"""Widgets for handling Django model relationships.
class ForeignKeyWidget(Widget):
def __init__(self, model, field='pk', use_natural_foreign_keys=False, key_is_id=False, **kwargs):
"""
Widget for foreign key relationships.
Parameters:
- model: Related model class
- field: str, field to use for lookup (default: 'pk')
- use_natural_foreign_keys: bool, use natural keys for lookup
- key_is_id: bool, whether key is the model ID
- **kwargs: Additional widget options
"""
def get_queryset(self, value, row, *args, **kwargs):
"""
Get queryset for foreign key lookup.
Parameters:
- value: Lookup value
- row: dict, complete row data
- *args: Additional arguments
- **kwargs: Additional keyword arguments
Returns:
QuerySet for lookup operations
"""
def get_lookup_kwargs(self, value, row, **kwargs):
"""
Get lookup kwargs for foreign key resolution.
Parameters:
- value: Lookup value
- row: dict, complete row data
- **kwargs: Additional options
Returns:
Dict of lookup kwargs
"""
class ManyToManyWidget(Widget):
def __init__(self, model, separator=None, field=None, **kwargs):
"""
Widget for many-to-many relationships.
Parameters:
- model: Related model class
- separator: str, separator for multiple values
- field: str, field to use for lookup
- **kwargs: Additional widget options
"""def format_datetime(value, datetime_format):
"""
Format datetime value using specified format string.
Parameters:
- value: datetime object or string
- datetime_format: str, format string
Returns:
Formatted datetime string
"""from import_export import fields, widgets
class BookResource(resources.ModelResource):
# Use date widget with custom format
published_date = fields.Field(
attribute='published_date',
widget=widgets.DateWidget(format='%d/%m/%Y')
)
# Use boolean widget for active status
is_active = fields.Field(
attribute='is_active',
widget=widgets.BooleanWidget()
)
# Use JSON widget for metadata
metadata = fields.Field(
attribute='metadata',
widget=widgets.JSONWidget()
)class UpperCaseWidget(widgets.CharWidget):
"""Custom widget that converts text to uppercase."""
def clean(self, value, row=None, **kwargs):
value = super().clean(value, row, **kwargs)
return value.upper() if value else value
def render(self, value, obj=None, **kwargs):
return value.upper() if value else value
class BookResource(resources.ModelResource):
title = fields.Field(
attribute='title',
widget=UpperCaseWidget()
)class AuthorWidget(widgets.ForeignKeyWidget):
"""Custom foreign key widget for author lookup."""
def __init__(self, **kwargs):
super().__init__(Author, field='name', **kwargs)
def get_queryset(self, value, row, *args, **kwargs):
# Custom queryset filtering
return Author.objects.filter(active=True)
def get_lookup_kwargs(self, value, row, **kwargs):
# Custom lookup logic
return {'name__iexact': value.strip()}
class BookResource(resources.ModelResource):
author = fields.Field(
attribute='author',
widget=AuthorWidget()
)class TagWidget(widgets.ManyToManyWidget):
"""Custom M2M widget for tags."""
def __init__(self, **kwargs):
super().__init__(Tag, separator='|', field='name', **kwargs)
def clean(self, value, row=None, **kwargs):
if not value:
return []
# Split by separator and clean each tag
tag_names = [name.strip() for name in value.split(self.separator)]
tags = []
for name in tag_names:
if name:
tag, created = Tag.objects.get_or_create(name=name)
tags.append(tag)
return tags
class BookResource(resources.ModelResource):
tags = fields.Field(
attribute='tags',
widget=TagWidget()
)class FlexibleDateWidget(widgets.DateWidget):
"""Date widget that accepts multiple input formats."""
FORMATS = ['%Y-%m-%d', '%d/%m/%Y', '%m/%d/%Y', '%Y%m%d']
def clean(self, value, row=None, **kwargs):
if not value:
return None
if isinstance(value, date):
return value
# Try multiple formats
for fmt in self.FORMATS:
try:
return datetime.strptime(str(value), fmt).date()
except ValueError:
continue
raise ValueError(f"Unable to parse date: {value}")
class BookResource(resources.ModelResource):
published_date = fields.Field(
attribute='published_date',
widget=FlexibleDateWidget()
)class ConditionalWidget(widgets.CharWidget):
"""Widget that behaves differently based on row data."""
def clean(self, value, row=None, **kwargs):
value = super().clean(value, row, **kwargs)
# Modify value based on other row data
if row and row.get('category') == 'special':
return f"SPECIAL: {value}"
return value
class BookResource(resources.ModelResource):
title = fields.Field(
attribute='title',
widget=ConditionalWidget()
)class PriceWidget(widgets.Widget):
"""Widget for handling price data with currency conversion."""
def __init__(self, currency='USD', **kwargs):
self.currency = currency
super().__init__(**kwargs)
def clean(self, value, row=None, **kwargs):
if not value:
return None
# Handle different price formats
if isinstance(value, str):
# Remove currency symbols and convert
value = value.replace('$', '').replace(',', '').strip()
try:
price = Decimal(str(value))
# Convert currency if needed
if row and row.get('currency') != self.currency:
price = self.convert_currency(
price, row.get('currency'), self.currency
)
return price
except (ValueError, TypeError):
raise ValueError(f"Invalid price format: {value}")
def render(self, value, obj=None, **kwargs):
if value is None:
return ''
return f"${value:.2f}"
def convert_currency(self, amount, from_currency, to_currency):
# Mock currency conversion
rates = {'USD': 1.0, 'EUR': 0.85, 'GBP': 0.75}
return amount * rates.get(to_currency, 1.0) / rates.get(from_currency, 1.0)
class BookResource(resources.ModelResource):
price = fields.Field(
attribute='price',
widget=PriceWidget(currency='USD')
)class ISBNWidget(widgets.CharWidget):
"""Widget for ISBN validation and formatting."""
def clean(self, value, row=None, **kwargs):
value = super().clean(value, row, **kwargs)
if not value:
return value
# Clean ISBN format
isbn = ''.join(c for c in value if c.isdigit() or c.upper() == 'X')
# Validate ISBN length
if len(isbn) not in [10, 13]:
raise ValueError(f"Invalid ISBN length: {isbn}")
# Add hyphens for readability
if len(isbn) == 13:
return f"{isbn[:3]}-{isbn[3:4]}-{isbn[4:6]}-{isbn[6:12]}-{isbn[12]}"
else:
return f"{isbn[:1]}-{isbn[1:4]}-{isbn[4:9]}-{isbn[9]}"
def render(self, value, obj=None, **kwargs):
# Remove hyphens for export
return ''.join(c for c in (value or '') if c.isdigit() or c.upper() == 'X')
class BookResource(resources.ModelResource):
isbn = fields.Field(
attribute='isbn',
widget=ISBNWidget()
)class BookResource(resources.ModelResource):
class Meta:
model = Book
widgets = {
'published_date': {'format': '%d/%m/%Y'},
'price': {'currency': 'EUR'},
'is_active': {'true_values': ['yes', 'y', '1']},
'tags': {'separator': ';'},
}Install with Tessl CLI
npx tessl i tessl/pypi-django-import-export