django-bulk-drf


Namedjango-bulk-drf JSON
Version 0.2.46 PyPI version JSON
download
home_pagehttps://github.com/AugendLimited/django-bulk-drf
SummaryDjango REST Framework for synchronous bulk operations with N+1 prevention
upload_time2025-11-02 22:21:05
maintainerNone
docs_urlNone
authorKonrad Beck
requires_python<4.0,>=3.11
licenseMIT
keywords django drf rest-framework upsert sync operations bulk
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Django Bulk DRF

High-performance bulk operations for Django REST Framework with a clean, RESTful API design.

**Note:** This package provides bulk operations through standard REST endpoints - no separate bulk endpoints needed. All collection-level operations are bulk by default with automatic detection of single vs. batch requests.

## Installation

```bash
pip install django-bulk-drf
```

### Requirements

- Python 3.11+
- Django 4.1+ (for native upsert support)
- Django REST Framework 3.14+

## Quick Setup

1. Add to your `INSTALLED_APPS`:
```python
INSTALLED_APPS = [
    # ... your other apps
    'rest_framework',
    'django_bulk_drf',
]
```

2. (Optional) Configure bulk operations in your Django settings:
```python
BULK_DRF = {
    'DEFAULT_BATCH_SIZE': 1000,
    'MAX_BATCH_SIZE': 5000,
    'ATOMIC_OPERATIONS': True,
    'ENABLE_M2M_HANDLING': True,
    # ... other settings
}
```

## Overview

This package extends Django REST Framework with high-performance bulk operations that work through your existing REST endpoints. No URL changes required!

### Key Features

1. **Bulk by Default**: Collection endpoints handle both single and bulk operations automatically
2. **Performance Optimized**: Single-query fetches, batch processing, query optimization
3. **Transaction Safety**: Atomic operations with configurable failure strategies
4. **Clean API Design**: No separate bulk endpoints - uses standard REST URLs
5. **DRF Integration**: Works seamlessly with existing DRF patterns

## Package Philosophy

This package provides a modern approach to bulk operations:

1. **Clean URLs**: Enhances existing endpoints rather than creating parallel ones
2. **Performance First**: Optimized database operations for maximum speed
3. **DRF Native**: Uses standard DRF patterns and integrates seamlessly
4. **Production Ready**: Built-in monitoring, error handling, and transaction management

## Usage

### Basic Setup

```python
# serializers.py
from django_bulk_drf import BulkModelSerializer

class ProductSerializer(BulkModelSerializer):
    class Meta:
        model = Product
        fields = ['id', 'sku', 'name', 'price', 'category']

# views.py
from django_bulk_drf import BulkModelViewSet

class ProductViewSet(BulkModelViewSet):
    """
    ViewSet with bulk operations on collection endpoints.
    
    Collection endpoints (POST, PUT, PATCH, DELETE) handle bulk automatically.
    Detail endpoints (/products/{id}/) work as standard DRF.
    """
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    unique_fields = ['sku']  # For upsert operations

# urls.py
from django_bulk_drf import BulkRouter
from rest_framework.routers import DefaultRouter

# Option 1: Use BulkRouter (Recommended for bulk operations)
router = BulkRouter()
router.register('products', ProductViewSet)

# Option 2: Use standard DefaultRouter (only POST will work for bulk)
# router = DefaultRouter()
# router.register('products', ProductViewSet)

urlpatterns = router.urls
```

**Important:** To enable PATCH, PUT, and DELETE on collection endpoints (`/products/`), you must use `BulkRouter` or `BulkSimpleRouter` instead of DRF's standard routers. The standard DRF routers only map these methods to detail endpoints (`/products/{id}/`).

## API Design

### URL Structure

```
Collection Endpoints (Bulk by Default):
POST   /products/        → Bulk create (accepts [...] or {...})
PUT    /products/        → Bulk update (requires unique_fields)  
PATCH  /products/        → Bulk upsert (create or update)
DELETE /products/        → Bulk delete (by unique_fields)

Detail Endpoints (Standard DRF):
GET    /products/{id}/   → Retrieve single
PUT    /products/{id}/   → Update single
PATCH  /products/{id}/   → Partial update single
DELETE /products/{id}/   → Delete single
```

**Key Insight**: The presence of `self.kwargs.get(self.lookup_field)` determines single vs. bulk operation.

### Request/Response Examples

#### Bulk Create
```bash
# Single create (backward compatible)
POST /products/
{
    "sku": "PROD-001",
    "name": "Widget", 
    "price": "19.99",
    "category": 1
}

# Bulk create
POST /products/
[
    {
        "sku": "PROD-001",
        "name": "Widget",
        "price": "19.99", 
        "category": 1
    },
    {
        "sku": "PROD-002",
        "name": "Gadget",
        "price": "29.99",
        "category": 1
    }
]

# Response
{
    "created": 2,
    "updated": 0,
    "failed": 0,
    "data": [...]
}
```

#### Bulk Upsert
```bash
# Bulk upsert (create or update)
PATCH /products/
[
    {
        "sku": "PROD-001",  # Exists - will update
        "name": "Updated Widget",
        "price": "24.99"
    },
    {
        "sku": "PROD-003",  # New - will create
        "name": "New Product", 
        "price": "39.99",
        "category": 2
    }
]

# Response
{
    "created": 1,
    "updated": 1,
    "failed": 0,
    "data": [...]
}
```

#### Bulk Delete
```bash
# Bulk delete by unique fields
DELETE /products/
[
    {"sku": "PROD-001"},
    {"sku": "PROD-002"},
    {"id": 5}
]

# Response  
{
    "deleted": 3
}
```

#### Single Operations (Backward Compatible)
```bash
# Single create still works
POST /products/
{
    "sku": "PROD-004", 
    "name": "Single Product",
    "price": "49.99"
}

# Response (standard DRF format)
{
    "id": 4,
    "sku": "PROD-004",
    "name": "Single Product", 
    "price": "49.99"
}
```

## Configuration

### Basic Settings

```python
# settings.py
BULK_DRF = {
    'DEFAULT_BATCH_SIZE': 1000,           # Records per database batch
    'MAX_BATCH_SIZE': 5000,               # Maximum request size
    'ATOMIC_OPERATIONS': True,            # Wrap operations in transactions
    'ENABLE_M2M_HANDLING': True,          # Handle many-to-many relationships
    'ALLOW_SINGULAR': True,               # Allow single-object requests
    'PREFER_MINIMAL_RESPONSE': False,     # Return minimal response format
    'PARTIAL_FAILURE_STRATEGY': 'ROLLBACK_ALL',  # How to handle partial failures
    'ENABLE_PERFORMANCE_MONITORING': False,      # Track performance metrics
    'AUTO_OPTIMIZE_QUERIES': True,        # Auto-optimize database queries
}
```

### Per-ViewSet Configuration

```python
class ProductViewSet(BulkModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    unique_fields = ['sku']              # Fields for upsert matching
    batch_size = 500                     # Override default batch size
    
    def get_unique_fields(self):
        """Dynamic unique fields based on request"""
        if self.request.query_params.get('match_by') == 'name':
            return ['name', 'category']
        return self.unique_fields
```

## Advanced Features

### Foreign Key Handling

The package automatically handles FK relationships with support for:
- **Integer PKs**: `{"category": 1}` → `{"category_id": 1}`
- **Slug Fields**: `{"category": "electronics"}` → resolves slug to ID in batch

### Many-to-Many Relationships

M2M fields are automatically handled:
```python
# Request
{
    "name": "Product",
    "tags": [1, 2, 3]  # M2M field
}

# Automatically creates Product and sets M2M relationships
```

### Error Handling

#### Validation Errors
```python
# Request with errors
POST /products/
[
    {"sku": "PROD-001", "name": "Valid Product"},
    {"sku": "", "name": "Invalid Product"},  # Missing required field
    {"sku": "PROD-003", "price": "invalid"}  # Invalid data type
]

# Response
{
    "created": 1,
    "updated": 0,
    "failed": 2,
    "errors": {
        "1": {"sku": ["This field may not be blank."]},
        "2": {"price": ["A valid number is required."]}
    }
}
```

#### Partial Failures

With `PARTIAL_FAILURE_STRATEGY = 'COMMIT_SUCCESSFUL'`:
```python
# Response when some items fail
{
    "created": 1,
    "updated": 0,
    "failed": 2,
    "data": [...],  # Only successful items
    "errors": {
        "1": {"field": ["error"]},
        "2": {"field": ["error"]}
    }
}
```

## Performance Characteristics

### Bulk Create
- **Single INSERT query** per batch (default 1000 records)
- **Typical Speed**: 10,000 records/second
- **Memory**: O(batch_size) instances in memory

### Bulk Update/Upsert  
- **Bulk Update**: Single SELECT query + single UPDATE query per batch
  - Typical Speed: 7,000-8,000 records/second
- **Bulk Upsert** (Native): Single native upsert query (no SELECT needed)
  - Uses Django's `bulk_create` with `update_conflicts=True`
  - Typical Speed: 10,000-12,000 records/second
  - True database-level atomicity

### Bulk Delete
- **Single DELETE query** with OR conditions
- **Typical Speed**: 15,000 records/second

### Native Upsert Performance

Bulk upsert operations use Django's native `bulk_create` with `update_conflicts=True` for maximum performance:

**How It Works:**
```python
# Single database operation handles both creates and updates
PATCH /products/
[
    {"sku": "PROD-001", "name": "Updated", "price": "24.99"},  # Exists - updates
    {"sku": "PROD-003", "name": "New Product", "price": "39.99"}  # New - creates
]

# Response
{
    "created": 2,  # Native upsert doesn't distinguish created vs updated
    "updated": 0,
    "failed": 0,
    "data": [...]
}
```

**Benefits:**
- **Single Database Query**: No SELECT needed - the database handles conflict detection
- **True Atomicity**: Database-level atomic operation (no race conditions)
- **Better Performance**: 10,000-12,000 records/second vs 7,000-8,000 for traditional update
- **Simpler Code**: One code path, easier to maintain

**Database Support:**
- PostgreSQL (all versions)
- SQLite 3.24+
- MySQL 8.0.19+
- Oracle (with limitations)

### Performance Tips

#### 1. Minimal Responses for Large Operations
```python
# Use Prefer header for large datasets
headers = {'Prefer': 'return=minimal'}
response = requests.post('/products/', json=large_dataset, headers=headers)

# Response: {"created": 10000, "updated": 0, "failed": 0}
```

#### 2. Optimize Batch Sizes
```python
# For smaller records (few fields)
BULK_DRF = {'DEFAULT_BATCH_SIZE': 2000}

# For larger records (many fields, large text)  
BULK_DRF = {'DEFAULT_BATCH_SIZE': 500}
```

#### 3. Database Indexing
Ensure your `unique_fields` are properly indexed for optimal upsert performance.

## Migration Guide

### From Standard DRF
```python
# Before
class ProductViewSet(viewsets.ModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer

# After  
class ProductViewSet(BulkModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    unique_fields = ['sku']  # Add for upsert support
```

### From drf-bulk
```python
# Before (drf-bulk)
class ProductViewSet(BulkModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
# API: POST /products/bulk/

# After (django-bulk-drf)  
class ProductViewSet(BulkModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    unique_fields = ['sku']
# API: POST /products/ (cleaner URLs)
```

## Limitations

- **Django Version**: Requires Django 4.1+ for native upsert support
- **Database Support**: PostgreSQL, MySQL 8.0.19+, SQLite 3.24+, Oracle (with limitations)
- **Not Supported**: Nested serializers, file uploads in bulk, complex validations requiring per-object database queries
- **Upsert Response**: Native upsert doesn't distinguish between created and updated records (all count as "created")
- **Best For**: Simple to medium complexity models with standard field types

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## License

This project is licensed under the MIT License.
            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/AugendLimited/django-bulk-drf",
    "name": "django-bulk-drf",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.11",
    "maintainer_email": null,
    "keywords": "django, drf, rest-framework, upsert, sync, operations, bulk",
    "author": "Konrad Beck",
    "author_email": "konrad.beck@merchantcapital.co.za",
    "download_url": "https://files.pythonhosted.org/packages/ff/2f/e6ae46c5d19bef3a00107ee397939a9af1500d02fd9e967dad3082207ccc/django_bulk_drf-0.2.46.tar.gz",
    "platform": null,
    "description": "# Django Bulk DRF\n\nHigh-performance bulk operations for Django REST Framework with a clean, RESTful API design.\n\n**Note:** This package provides bulk operations through standard REST endpoints - no separate bulk endpoints needed. All collection-level operations are bulk by default with automatic detection of single vs. batch requests.\n\n## Installation\n\n```bash\npip install django-bulk-drf\n```\n\n### Requirements\n\n- Python 3.11+\n- Django 4.1+ (for native upsert support)\n- Django REST Framework 3.14+\n\n## Quick Setup\n\n1. Add to your `INSTALLED_APPS`:\n```python\nINSTALLED_APPS = [\n    # ... your other apps\n    'rest_framework',\n    'django_bulk_drf',\n]\n```\n\n2. (Optional) Configure bulk operations in your Django settings:\n```python\nBULK_DRF = {\n    'DEFAULT_BATCH_SIZE': 1000,\n    'MAX_BATCH_SIZE': 5000,\n    'ATOMIC_OPERATIONS': True,\n    'ENABLE_M2M_HANDLING': True,\n    # ... other settings\n}\n```\n\n## Overview\n\nThis package extends Django REST Framework with high-performance bulk operations that work through your existing REST endpoints. No URL changes required!\n\n### Key Features\n\n1. **Bulk by Default**: Collection endpoints handle both single and bulk operations automatically\n2. **Performance Optimized**: Single-query fetches, batch processing, query optimization\n3. **Transaction Safety**: Atomic operations with configurable failure strategies\n4. **Clean API Design**: No separate bulk endpoints - uses standard REST URLs\n5. **DRF Integration**: Works seamlessly with existing DRF patterns\n\n## Package Philosophy\n\nThis package provides a modern approach to bulk operations:\n\n1. **Clean URLs**: Enhances existing endpoints rather than creating parallel ones\n2. **Performance First**: Optimized database operations for maximum speed\n3. **DRF Native**: Uses standard DRF patterns and integrates seamlessly\n4. **Production Ready**: Built-in monitoring, error handling, and transaction management\n\n## Usage\n\n### Basic Setup\n\n```python\n# serializers.py\nfrom django_bulk_drf import BulkModelSerializer\n\nclass ProductSerializer(BulkModelSerializer):\n    class Meta:\n        model = Product\n        fields = ['id', 'sku', 'name', 'price', 'category']\n\n# views.py\nfrom django_bulk_drf import BulkModelViewSet\n\nclass ProductViewSet(BulkModelViewSet):\n    \"\"\"\n    ViewSet with bulk operations on collection endpoints.\n    \n    Collection endpoints (POST, PUT, PATCH, DELETE) handle bulk automatically.\n    Detail endpoints (/products/{id}/) work as standard DRF.\n    \"\"\"\n    queryset = Product.objects.all()\n    serializer_class = ProductSerializer\n    unique_fields = ['sku']  # For upsert operations\n\n# urls.py\nfrom django_bulk_drf import BulkRouter\nfrom rest_framework.routers import DefaultRouter\n\n# Option 1: Use BulkRouter (Recommended for bulk operations)\nrouter = BulkRouter()\nrouter.register('products', ProductViewSet)\n\n# Option 2: Use standard DefaultRouter (only POST will work for bulk)\n# router = DefaultRouter()\n# router.register('products', ProductViewSet)\n\nurlpatterns = router.urls\n```\n\n**Important:** To enable PATCH, PUT, and DELETE on collection endpoints (`/products/`), you must use `BulkRouter` or `BulkSimpleRouter` instead of DRF's standard routers. The standard DRF routers only map these methods to detail endpoints (`/products/{id}/`).\n\n## API Design\n\n### URL Structure\n\n```\nCollection Endpoints (Bulk by Default):\nPOST   /products/        \u2192 Bulk create (accepts [...] or {...})\nPUT    /products/        \u2192 Bulk update (requires unique_fields)  \nPATCH  /products/        \u2192 Bulk upsert (create or update)\nDELETE /products/        \u2192 Bulk delete (by unique_fields)\n\nDetail Endpoints (Standard DRF):\nGET    /products/{id}/   \u2192 Retrieve single\nPUT    /products/{id}/   \u2192 Update single\nPATCH  /products/{id}/   \u2192 Partial update single\nDELETE /products/{id}/   \u2192 Delete single\n```\n\n**Key Insight**: The presence of `self.kwargs.get(self.lookup_field)` determines single vs. bulk operation.\n\n### Request/Response Examples\n\n#### Bulk Create\n```bash\n# Single create (backward compatible)\nPOST /products/\n{\n    \"sku\": \"PROD-001\",\n    \"name\": \"Widget\", \n    \"price\": \"19.99\",\n    \"category\": 1\n}\n\n# Bulk create\nPOST /products/\n[\n    {\n        \"sku\": \"PROD-001\",\n        \"name\": \"Widget\",\n        \"price\": \"19.99\", \n        \"category\": 1\n    },\n    {\n        \"sku\": \"PROD-002\",\n        \"name\": \"Gadget\",\n        \"price\": \"29.99\",\n        \"category\": 1\n    }\n]\n\n# Response\n{\n    \"created\": 2,\n    \"updated\": 0,\n    \"failed\": 0,\n    \"data\": [...]\n}\n```\n\n#### Bulk Upsert\n```bash\n# Bulk upsert (create or update)\nPATCH /products/\n[\n    {\n        \"sku\": \"PROD-001\",  # Exists - will update\n        \"name\": \"Updated Widget\",\n        \"price\": \"24.99\"\n    },\n    {\n        \"sku\": \"PROD-003\",  # New - will create\n        \"name\": \"New Product\", \n        \"price\": \"39.99\",\n        \"category\": 2\n    }\n]\n\n# Response\n{\n    \"created\": 1,\n    \"updated\": 1,\n    \"failed\": 0,\n    \"data\": [...]\n}\n```\n\n#### Bulk Delete\n```bash\n# Bulk delete by unique fields\nDELETE /products/\n[\n    {\"sku\": \"PROD-001\"},\n    {\"sku\": \"PROD-002\"},\n    {\"id\": 5}\n]\n\n# Response  \n{\n    \"deleted\": 3\n}\n```\n\n#### Single Operations (Backward Compatible)\n```bash\n# Single create still works\nPOST /products/\n{\n    \"sku\": \"PROD-004\", \n    \"name\": \"Single Product\",\n    \"price\": \"49.99\"\n}\n\n# Response (standard DRF format)\n{\n    \"id\": 4,\n    \"sku\": \"PROD-004\",\n    \"name\": \"Single Product\", \n    \"price\": \"49.99\"\n}\n```\n\n## Configuration\n\n### Basic Settings\n\n```python\n# settings.py\nBULK_DRF = {\n    'DEFAULT_BATCH_SIZE': 1000,           # Records per database batch\n    'MAX_BATCH_SIZE': 5000,               # Maximum request size\n    'ATOMIC_OPERATIONS': True,            # Wrap operations in transactions\n    'ENABLE_M2M_HANDLING': True,          # Handle many-to-many relationships\n    'ALLOW_SINGULAR': True,               # Allow single-object requests\n    'PREFER_MINIMAL_RESPONSE': False,     # Return minimal response format\n    'PARTIAL_FAILURE_STRATEGY': 'ROLLBACK_ALL',  # How to handle partial failures\n    'ENABLE_PERFORMANCE_MONITORING': False,      # Track performance metrics\n    'AUTO_OPTIMIZE_QUERIES': True,        # Auto-optimize database queries\n}\n```\n\n### Per-ViewSet Configuration\n\n```python\nclass ProductViewSet(BulkModelViewSet):\n    queryset = Product.objects.all()\n    serializer_class = ProductSerializer\n    unique_fields = ['sku']              # Fields for upsert matching\n    batch_size = 500                     # Override default batch size\n    \n    def get_unique_fields(self):\n        \"\"\"Dynamic unique fields based on request\"\"\"\n        if self.request.query_params.get('match_by') == 'name':\n            return ['name', 'category']\n        return self.unique_fields\n```\n\n## Advanced Features\n\n### Foreign Key Handling\n\nThe package automatically handles FK relationships with support for:\n- **Integer PKs**: `{\"category\": 1}` \u2192 `{\"category_id\": 1}`\n- **Slug Fields**: `{\"category\": \"electronics\"}` \u2192 resolves slug to ID in batch\n\n### Many-to-Many Relationships\n\nM2M fields are automatically handled:\n```python\n# Request\n{\n    \"name\": \"Product\",\n    \"tags\": [1, 2, 3]  # M2M field\n}\n\n# Automatically creates Product and sets M2M relationships\n```\n\n### Error Handling\n\n#### Validation Errors\n```python\n# Request with errors\nPOST /products/\n[\n    {\"sku\": \"PROD-001\", \"name\": \"Valid Product\"},\n    {\"sku\": \"\", \"name\": \"Invalid Product\"},  # Missing required field\n    {\"sku\": \"PROD-003\", \"price\": \"invalid\"}  # Invalid data type\n]\n\n# Response\n{\n    \"created\": 1,\n    \"updated\": 0,\n    \"failed\": 2,\n    \"errors\": {\n        \"1\": {\"sku\": [\"This field may not be blank.\"]},\n        \"2\": {\"price\": [\"A valid number is required.\"]}\n    }\n}\n```\n\n#### Partial Failures\n\nWith `PARTIAL_FAILURE_STRATEGY = 'COMMIT_SUCCESSFUL'`:\n```python\n# Response when some items fail\n{\n    \"created\": 1,\n    \"updated\": 0,\n    \"failed\": 2,\n    \"data\": [...],  # Only successful items\n    \"errors\": {\n        \"1\": {\"field\": [\"error\"]},\n        \"2\": {\"field\": [\"error\"]}\n    }\n}\n```\n\n## Performance Characteristics\n\n### Bulk Create\n- **Single INSERT query** per batch (default 1000 records)\n- **Typical Speed**: 10,000 records/second\n- **Memory**: O(batch_size) instances in memory\n\n### Bulk Update/Upsert  \n- **Bulk Update**: Single SELECT query + single UPDATE query per batch\n  - Typical Speed: 7,000-8,000 records/second\n- **Bulk Upsert** (Native): Single native upsert query (no SELECT needed)\n  - Uses Django's `bulk_create` with `update_conflicts=True`\n  - Typical Speed: 10,000-12,000 records/second\n  - True database-level atomicity\n\n### Bulk Delete\n- **Single DELETE query** with OR conditions\n- **Typical Speed**: 15,000 records/second\n\n### Native Upsert Performance\n\nBulk upsert operations use Django's native `bulk_create` with `update_conflicts=True` for maximum performance:\n\n**How It Works:**\n```python\n# Single database operation handles both creates and updates\nPATCH /products/\n[\n    {\"sku\": \"PROD-001\", \"name\": \"Updated\", \"price\": \"24.99\"},  # Exists - updates\n    {\"sku\": \"PROD-003\", \"name\": \"New Product\", \"price\": \"39.99\"}  # New - creates\n]\n\n# Response\n{\n    \"created\": 2,  # Native upsert doesn't distinguish created vs updated\n    \"updated\": 0,\n    \"failed\": 0,\n    \"data\": [...]\n}\n```\n\n**Benefits:**\n- **Single Database Query**: No SELECT needed - the database handles conflict detection\n- **True Atomicity**: Database-level atomic operation (no race conditions)\n- **Better Performance**: 10,000-12,000 records/second vs 7,000-8,000 for traditional update\n- **Simpler Code**: One code path, easier to maintain\n\n**Database Support:**\n- PostgreSQL (all versions)\n- SQLite 3.24+\n- MySQL 8.0.19+\n- Oracle (with limitations)\n\n### Performance Tips\n\n#### 1. Minimal Responses for Large Operations\n```python\n# Use Prefer header for large datasets\nheaders = {'Prefer': 'return=minimal'}\nresponse = requests.post('/products/', json=large_dataset, headers=headers)\n\n# Response: {\"created\": 10000, \"updated\": 0, \"failed\": 0}\n```\n\n#### 2. Optimize Batch Sizes\n```python\n# For smaller records (few fields)\nBULK_DRF = {'DEFAULT_BATCH_SIZE': 2000}\n\n# For larger records (many fields, large text)  \nBULK_DRF = {'DEFAULT_BATCH_SIZE': 500}\n```\n\n#### 3. Database Indexing\nEnsure your `unique_fields` are properly indexed for optimal upsert performance.\n\n## Migration Guide\n\n### From Standard DRF\n```python\n# Before\nclass ProductViewSet(viewsets.ModelViewSet):\n    queryset = Product.objects.all()\n    serializer_class = ProductSerializer\n\n# After  \nclass ProductViewSet(BulkModelViewSet):\n    queryset = Product.objects.all()\n    serializer_class = ProductSerializer\n    unique_fields = ['sku']  # Add for upsert support\n```\n\n### From drf-bulk\n```python\n# Before (drf-bulk)\nclass ProductViewSet(BulkModelViewSet):\n    queryset = Product.objects.all()\n    serializer_class = ProductSerializer\n# API: POST /products/bulk/\n\n# After (django-bulk-drf)  \nclass ProductViewSet(BulkModelViewSet):\n    queryset = Product.objects.all()\n    serializer_class = ProductSerializer\n    unique_fields = ['sku']\n# API: POST /products/ (cleaner URLs)\n```\n\n## Limitations\n\n- **Django Version**: Requires Django 4.1+ for native upsert support\n- **Database Support**: PostgreSQL, MySQL 8.0.19+, SQLite 3.24+, Oracle (with limitations)\n- **Not Supported**: Nested serializers, file uploads in bulk, complex validations requiring per-object database queries\n- **Upsert Response**: Native upsert doesn't distinguish between created and updated records (all count as \"created\")\n- **Best For**: Simple to medium complexity models with standard field types\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## License\n\nThis project is licensed under the MIT License.",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Django REST Framework for synchronous bulk operations with N+1 prevention",
    "version": "0.2.46",
    "project_urls": {
        "Homepage": "https://github.com/AugendLimited/django-bulk-drf",
        "Repository": "https://github.com/AugendLimited/django-bulk-drf"
    },
    "split_keywords": [
        "django",
        " drf",
        " rest-framework",
        " upsert",
        " sync",
        " operations",
        " bulk"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d3a40bb7d2888f24f2fb420df01c860bfe9d328233534f8efd64830d5762b9fb",
                "md5": "25e6f9fd4b3fc288a97aca48a848fd7b",
                "sha256": "d6a84c4516884a745cebe72153b79227214125c07155ab70b07c09159ff6fb87"
            },
            "downloads": -1,
            "filename": "django_bulk_drf-0.2.46-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "25e6f9fd4b3fc288a97aca48a848fd7b",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.11",
            "size": 44842,
            "upload_time": "2025-11-02T22:21:04",
            "upload_time_iso_8601": "2025-11-02T22:21:04.392636Z",
            "url": "https://files.pythonhosted.org/packages/d3/a4/0bb7d2888f24f2fb420df01c860bfe9d328233534f8efd64830d5762b9fb/django_bulk_drf-0.2.46-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ff2fe6ae46c5d19bef3a00107ee397939a9af1500d02fd9e967dad3082207ccc",
                "md5": "df387921f1913a71d8174441bafc2d31",
                "sha256": "c42df26499e5cec009a8c8ba2bf023938d477c7c46596a7f68643fa769f5bdd2"
            },
            "downloads": -1,
            "filename": "django_bulk_drf-0.2.46.tar.gz",
            "has_sig": false,
            "md5_digest": "df387921f1913a71d8174441bafc2d31",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.11",
            "size": 37954,
            "upload_time": "2025-11-02T22:21:05",
            "upload_time_iso_8601": "2025-11-02T22:21:05.981902Z",
            "url": "https://files.pythonhosted.org/packages/ff/2f/e6ae46c5d19bef3a00107ee397939a9af1500d02fd9e967dad3082207ccc/django_bulk_drf-0.2.46.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-11-02 22:21:05",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "AugendLimited",
    "github_project": "django-bulk-drf",
    "github_not_found": true,
    "lcname": "django-bulk-drf"
}
        
Elapsed time: 2.36506s