Skip to content
Merged

Dev #33

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/docs/en/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,15 +281,14 @@ Alternatively, set NINJA_PAGINATION_CLASS in settings.py to override the default
---
## File Upload Support

Lazy Ninja now supports handling file uploads for models with `FileField` and `ImageField` using `multipart/form-data`. This feature allows you to define which fields should use `multipart/form-data` and provides flexibility to handle mixed models where some routes use JSON while others use `multipart/form-data`.
Lazy Ninja supports handling file uploads for models with `FileField` and `ImageField` using `multipart/form-data`. This feature allows you to define which fields should use `multipart/form-data` and provides flexibility to handle mixed models where some routes use JSON while others use `multipart/form-data`.

### How to Use File Upload Parameters

When initializing `DynamicAPI`, you can configure the following parameters:

- **`file_fields`**: Specify which fields in a model should use `multipart/form-data`.
- **`use_multipart`**: Explicitly define whether `create` and `update` operations for specific models should use `multipart/form-data`.
- **`auto_multipart`**: Automatically detect file fields in models and enable `multipart/form-data` for them (default: `True`).
- **`auto_detect_files`**: Automatically detect `FileField` and `ImageField` in models (default: `True`).
- **`auto_multipart`**: Automatically enable `multipart/form-data` for models with detected file fields (default: `True`).

Expand All @@ -310,8 +309,7 @@ auto_api = DynamicAPI(
"update": True # Use multipart/form-data for updates
}
},
auto_detect_files=True, # Automatically detect file fields in models
auto_multipart=True # Automatically enable multipart/form-data for detected file fields
auto_multipart=False # Disable automatic multipart/form-data for detected file fields
)

auto_api.register_all_models()
Expand All @@ -322,6 +320,8 @@ In this example:
- The `Product` model will use `multipart/form-data` for the `pimages` field during `create` and `update` operations.
- Models without file fields will continue to use JSON by default.

>By default, `auto_multipart` is True and routes for models with file fields will use `multipart/form-data` automatically. If you want to disable this behavior, set auto_multipart=False.

---

## Custom Middleware for PUT/PATCH with Multipart
Expand Down
103 changes: 55 additions & 48 deletions src/lazy_ninja/file_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from django.db import models


class FileUploadConfig:
"""
Configuration for file upload fields in a model.
Expand Down Expand Up @@ -40,61 +41,67 @@ def is_multiple_file_field(self, model_name: str, field_name: str) -> bool:
return field_name in self.get_model_multiple_file_fields(model_name)


def detect_file_fields(model) -> Tuple[List[str], List[str]]:
class FileFieldDetector:
"""
Automatically detect file fields in a Django model.
Utility class for detecting file fields in Django models.

Returns a tuple of (single_file_fields, multiple_file_fields)
Separated from the main detect_file_fields function for better organization.
"""
single_file_fields = []
multiple_file_fields = []

for field in model._meta.get_fields():
if isinstance(field, (models.FileField, models.ImageField)):
single_file_fields.append(field.name)
def detect_file_fields(self, model) -> Tuple[List[str], List[str]]:
"""
Automatically detect file fields in a Django model.

Args:
model: Django model class to analyze

elif isinstance(field, models.ManyToManyField):
related_model = field.related_model
if related_model:

for related_field in related_model._meta.get_fields():
if isinstance(related_field, (models.FileField, models.ImageField)):
multiple_file_fields.append(field.name)
break

elif isinstance(field, models.ManyToOneRel):
related_model = field.related_model
if related_model:
has_file_field = False
Returns:
Tuple of (single_file_fields, multiple_file_fields)
"""
single_file_fields = []
multiple_file_fields = []

for field in model._meta.get_fields():
if isinstance(field, (models.FileField, models.ImageField)):
single_file_fields.append(field.name)

for related_field in related_model._meta.get_fields():
if isinstance(related_field, (models.FileField, models.ImageField)):
has_file_field = True
break
elif isinstance(field, models.ManyToManyField):
related_model = field.related_model
if related_model and self._has_file_fields(related_model):
multiple_file_fields.append(field.name)

if has_file_field:
multiple_file_fields.append(field.get_accessor_name())
# elif isinstance(field, models.ManyToOneRel):
# related_model = field.related_model
# if related_model and self._has_file_fields(related_model):
# multiple_file_fields.append(field.get_accessor_name())

elif isinstance(field, models.OneToOneField):
related_model = field.related_model
if related_model:

for related_model in related_model._meta.get_fields():
if isinstance(related_field, (models.FileField, models.ImageField)):
single_file_fields.append(field.name)
break
# elif isinstance(field, models.OneToOneField):
# related_model = field.related_model
# if related_model and self._has_file_fields(related_model):
# single_file_fields.append(field.name)

elif isinstance(field, models.OneToOneRel):
related_model = field.related_model
if related_model:
has_file_field = False
# elif isinstance(field, models.OneToOneRel):
# related_model = field.related_model
# if related_model and self._has_file_fields(related_model):
# single_file_fields.append(field.get_accessor_name())

for related_field in related_model._meta.get_fields():
if isinstance(related_field, (models.FileField, models.ImageField)):
has_file_field = True
break

if has_file_field:
single_file_fields.append(field.get_accessor_name())

return single_file_fields, multiple_file_fields
return single_file_fields, multiple_file_fields

def _has_file_fields(self, model) -> bool:
"""Check if a model has any file fields."""
for field in model._meta.get_fields():
if isinstance(field, (models.FileField, models.ImageField)):
return True
return False


# Legacy function for backward compatibility
def detect_file_fields(model) -> Tuple[List[str], List[str]]:
"""
Legacy function for detecting file fields.

This function is kept for backward compatibility.
Use FileFieldDetector.detect_file_fields() for new code.
"""
detector = FileFieldDetector()
return detector.detect_file_fields(model)
Loading