Bazis Uploadable
An extension package for Bazis, providing a ready-to-use solution for file uploading and management with support for various storage backends.
Quick Start
# Install the package
uv add bazis-uploadable
# Create a model for file uploads
from bazis.contrib.uploadable.models_abstract import FileUploadAbstract
from bazis.core.models_abstract import DtMixin, UuidMixin
class Document(FileUploadAbstract, DtMixin, UuidMixin):
"""Uploadable document"""
class Meta:
verbose_name = 'Document'
verbose_name_plural = 'Documents'
# Create a route for uploads
from bazis.contrib.uploadable.routes_abstract import FileUploadRouteSet
from django.apps import apps
class DocumentRouteSet(FileUploadRouteSet):
model = apps.get_model('myapp.Document')
Table of Contents
- Description
- Requirements
- Installation
- Core Components
- Usage
- Storage Configuration
- Examples
- License
- Links
Description
Bazis Uploadable is an extension package for the Bazis framework that provides a ready-to-use solution for file uploading and management. The package includes:
- FileUploadAbstract — abstract model with fields for storing files and metadata
- FileUploadRouteSet — ready-to-use route with multipart/form-data upload support
- Flexible storage configuration — support for local storage, S3, and other backends
- Automatic metadata extraction — filename, extension, size
- JSON:API integration — standardized response format
This package requires the base bazis package to be installed.
Requirements
- Python: 3.12+
- bazis: latest version
- PostgreSQL: 12+
- Redis: For caching
Installation
Using uv (recommended)
uv add bazis-uploadable
Using pip
pip install bazis-uploadable
Core Components
FileUploadAbstract
Abstract model for storing uploaded files.
Location: bazis.contrib.uploadable.models_abstract.FileUploadAbstract
Fields:
file— FileField for storing the filename— filename (optional, automatically filled from file name)extension— file extension (optional, automatically extracted)
Properties:
size— file size in bytes (cached_property)
Features:
- Automatically extracts filename and extension on save
- Uses
get_file_pathto generate storage path - Supports custom storage backends via settings
- Returns 0 for
sizewhen file is absent instead of raising an error
Usage example:
from bazis.contrib.uploadable.models_abstract import FileUploadAbstract
from bazis.core.models_abstract import DtMixin, UuidMixin, JsonApiMixin
class Document(FileUploadAbstract, DtMixin, UuidMixin):
"""Document with additional fields"""
description = models.TextField('Description', blank=True, null=True)
class Meta:
verbose_name = 'Document'
verbose_name_plural = 'Documents'
FileUploadRouteSet
Ready-to-use route for file uploading via multipart/form-data.
Location: bazis.contrib.uploadable.routes_abstract.FileUploadRouteSet
Features:
- Accepts files via
multipart/form-data - Automatically includes
sizeandextensionfields in schema - Supports optional
namefield for renaming files - Returns data in JSON:API format
Endpoint:
- POST / — upload a new file
Usage example:
from bazis.contrib.uploadable.routes_abstract import FileUploadRouteSet
from django.apps import apps
class DocumentRouteSet(FileUploadRouteSet):
model = apps.get_model('myapp.Document')
Storage Configuration
By default, Django's standard FileSystemStorage is used. You can configure custom storage via settings.
settings.py:
# Local storage (default)
BAZIS_STORAGE_FILE_UPLOAD = None # or 'django.core.files.storage.FileSystemStorage'
# S3-compatible storage
BAZIS_STORAGE_FILE_UPLOAD = 'storages.backends.s3boto3.S3Boto3Storage'
# Other storage backends
BAZIS_STORAGE_FILE_UPLOAD = 'path.to.your.CustomStorage'
Size Limit
BAZIS_FILE_UPLOAD_MAX_SIZE (bytes, 0 — no limit, the default) rejects larger files with
413 and the error code ERR_FILE_TOO_LARGE. The check runs after the request body is
received, so also limit the request size on the reverse proxy (e.g. client_max_body_size).
Access Control
FileUploadRouteSet does not check who calls it: anyone who can reach the API can upload,
list, change and delete files. In production, register a subclass that requires a user
(e.g. with UserRequiredRouteBase of bazis-users) or permissions (bazis-permit) instead
of the bundled router.
Usage
Creating a File Model
from bazis.contrib.uploadable.models_abstract import FileUploadAbstract
from bazis.core.models_abstract import DtMixin, UuidMixin, JsonApiMixin
from django.db import models
class Document(FileUploadAbstract, DtMixin, UuidMixin):
"""Document with metadata"""
description = models.TextField('Description', blank=True, null=True)
category = models.CharField('Category', max_length=100, blank=True, null=True)
class Meta:
verbose_name = 'Document'
verbose_name_plural = 'Documents'
def __str__(self):
return self.name or self.file.name
Creating an Upload Route
routes.py:
from bazis.contrib.uploadable.routes_abstract import FileUploadRouteSet
from bazis.core.schemas.fields import SchemaField, SchemaFields
from django.apps import apps
class DocumentRouteSet(FileUploadRouteSet):
model = apps.get_model('myapp.Document')
# Adding additional fields to schema
fields = {
None: SchemaFields(
include={
'description': None,
'category': None,
},
),
}
router.py:
from bazis.core.routing import BazisRouter
from . import routes
router = BazisRouter(tags=['Documents'])
router.register(routes.DocumentRouteSet.as_router())
Uploading Files via API
Uploading a file with automatic name
POST /api/v1/documents/document/
Content-Type: multipart/form-data
--boundary
Content-Disposition: form-data; name="file"; filename="report.pdf"
Content-Type: application/pdf
[binary data]
--boundary--
Response:
{
"data": {
"type": "myapp.document",
"id": "123e4567-e89b-12d3-a456-426614174000",
"attributes": {
"name": "report.pdf",
"extension": "pdf",
"size": 102400,
"dt_created": "2024-01-15T10:30:00Z"
},
"relationships": {
"file": {
"data": {
"url": "/media/uploads/2024/01/15/report.pdf"
}
}
}
}
}
Uploading a file with custom name
POST /api/v1/documents/document/
Content-Type: multipart/form-data
--boundary
Content-Disposition: form-data; name="file"; filename="original.pdf"
Content-Type: application/pdf
[binary data]
--boundary
Content-Disposition: form-data; name="name"
Monthly Report January 2024.pdf
--boundary--
Response:
{
"data": {
"type": "myapp.document",
"id": "123e4567-e89b-12d3-a456-426614174000",
"attributes": {
"name": "Monthly Report January 2024.pdf",
"extension": "pdf",
"size": 102400
}
}
}
Uploading with additional fields
POST /api/v1/documents/document/
Content-Type: multipart/form-data
--boundary
Content-Disposition: form-data; name="file"; filename="invoice.pdf"
[binary data]
--boundary
Content-Disposition: form-data; name="name"
Invoice #12345
--boundary
Content-Disposition: form-data; name="description"
Invoice for January 2024 services
--boundary
Content-Disposition: form-data; name="category"
Finance
--boundary--
Storage Configuration
Local Storage (default)
# settings.py
MEDIA_ROOT = '/var/www/media/'
MEDIA_URL = '/media/'
# FileSystemStorage is used by default
BAZIS_STORAGE_FILE_UPLOAD = None
Amazon S3
# Install dependencies
uv add django-storages boto3
# settings.py
BAZIS_STORAGE_FILE_UPLOAD = 'storages.backends.s3boto3.S3Boto3Storage'
# S3 configuration
AWS_ACCESS_KEY_ID = 'your-access-key'
AWS_SECRET_ACCESS_KEY = 'your-secret-key'
AWS_STORAGE_BUCKET_NAME = 'your-bucket-name'
AWS_S3_REGION_NAME = 'us-east-1'
AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'
AWS_DEFAULT_ACL = 'public-read'
MinIO (S3-compatible storage)
# settings.py
BAZIS_STORAGE_FILE_UPLOAD = 'storages.backends.s3boto3.S3Boto3Storage'
AWS_ACCESS_KEY_ID = 'minio-access-key'
AWS_SECRET_ACCESS_KEY = 'minio-secret-key'
AWS_STORAGE_BUCKET_NAME = 'uploads'
AWS_S3_ENDPOINT_URL = 'http://minio:9000'
AWS_S3_USE_SSL = False
AWS_DEFAULT_ACL = 'public-read'
Google Cloud Storage
# Install dependencies
uv add django-storages google-cloud-storage
# settings.py
BAZIS_STORAGE_FILE_UPLOAD = 'storages.backends.gcloud.GoogleCloudStorage'
GS_BUCKET_NAME = 'your-bucket-name'
GS_PROJECT_ID = 'your-project-id'
GS_CREDENTIALS = service_account.Credentials.from_service_account_file(
'path/to/service-account.json'
)
Custom Storage
You can create your own storage by inheriting from Django Storage:
# myapp/storage.py
from django.core.files.storage import FileSystemStorage
class CustomStorage(FileSystemStorage):
def __init__(self):
super().__init__(
location='/custom/path/',
base_url='/custom/'
)
def _save(self, name, content):
# Custom save logic
return super()._save(name, content)
# settings.py
BAZIS_STORAGE_FILE_UPLOAD = 'myapp.storage.CustomStorage'
Examples
Complete document application example
models.py:
from bazis.contrib.uploadable.models_abstract import FileUploadAbstract
from bazis.core.models_abstract import DtMixin, UuidMixin, JsonApiMixin
from django.db import models
class DocumentCategory(models.TextChoices):
INVOICE = 'invoice', 'Invoice'
CONTRACT = 'contract', 'Contract'
REPORT = 'report', 'Report'
OTHER = 'other', 'Other'
class Document(FileUploadAbstract, DtMixin, UuidMixin):
"""Document with category and description"""
description = models.TextField('Description', blank=True, null=True)
category = models.CharField(
'Category',
max_length=20,
choices=DocumentCategory.choices,
default=DocumentCategory.OTHER
)
class Meta:
verbose_name = 'Document'
verbose_name_plural = 'Documents'
def __str__(self):
return f'{self.get_category_display()}: {self.name}'
routes.py:
from bazis.contrib.uploadable.routes_abstract import FileUploadRouteSet
from bazis.core.schemas.fields import SchemaFields
from django.apps import apps
class DocumentRouteSet(FileUploadRouteSet):
model = apps.get_model('myapp.Document')
fields = {
None: SchemaFields(
include={
'description': None,
'category': None,
},
),
}
router.py:
from bazis.core.routing import BazisRouter
from . import routes
router = BazisRouter(tags=['Documents'])
router.register(routes.DocumentRouteSet.as_router())
admin.py:
from django.contrib import admin
from bazis.core.admin_abstract import DtAdminMixin
from .models import Document
@admin.register(Document)
class DocumentAdmin(DtAdminMixin, admin.ModelAdmin):
list_display = ('id', 'name', 'category', 'extension', 'size_display', 'dt_created')
list_filter = ('category', 'extension')
search_fields = ('name', 'description')
readonly_fields = ('extension', 'size', 'dt_created', 'dt_updated')
def size_display(self, obj):
"""Human-readable file size"""
size = obj.size
if size < 1024:
return f'{size} B'
elif size < 1024 * 1024:
return f'{size / 1024:.2f} KB'
else:
return f'{size / (1024 * 1024):.2f} MB'
size_display.short_description = 'Size'
Client application usage example
JavaScript (file upload):
async function uploadDocument(file, category, description) {
const formData = new FormData();
formData.append('file', file);
formData.append('category', category);
if (description) {
formData.append('description', description);
}
const response = await fetch('/api/v1/documents/document/', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
},
body: formData
});
if (!response.ok) {
throw new Error('Upload failed');
}
return await response.json();
}
// Usage
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
try {
const result = await uploadDocument(file, 'invoice', 'Monthly invoice');
console.log('Uploaded:', result.data.id);
console.log('File URL:', result.data.attributes.file);
} catch (error) {
console.error('Error:', error);
}
Python (file upload via requests):
import requests
def upload_document(file_path, category, description=None):
url = 'http://api.example.com/api/v1/documents/document/'
with open(file_path, 'rb') as file:
files = {'file': file}
data = {
'category': category,
}
if description:
data['description'] = description
headers = {
'Authorization': f'Bearer {token}'
}
response = requests.post(url, files=files, data=data, headers=headers)
response.raise_for_status()
return response.json()
# Usage
result = upload_document(
'/path/to/invoice.pdf',
category='invoice',
description='Monthly invoice for January'
)
print(f"Uploaded: {result['data']['id']}")
print(f"Size: {result['data']['attributes']['size']} bytes")
Getting list of files
GET /api/v1/documents/document/
Authorization: Bearer <token>
Response:
{
"data": [
{
"type": "myapp.document",
"id": "123e4567-e89b-12d3-a456-426614174000",
"attributes": {
"name": "invoice.pdf",
"extension": "pdf",
"size": 102400,
"category": "invoice",
"description": "Monthly invoice",
"dt_created": "2024-01-15T10:30:00Z"
}
},
{
"type": "myapp.document",
"id": "987e6543-e21b-32d1-b654-426614174001",
"attributes": {
"name": "report.docx",
"extension": "docx",
"size": 256000,
"category": "report",
"dt_created": "2024-01-16T14:20:00Z"
}
}
],
"meta": {
"count": 2
}
}
Filtering by extension
GET /api/v1/documents/document/?filter[extension]=pdf
Filtering by category
GET /api/v1/documents/document/?filter[category]=invoice
License
Apache License 2.0
See LICENSE file for details.
Links
- Bazis Documentation — main repository
- Bazis Uploadable Repository — package repository
- Issue Tracker — report bugs or request features
- Django Storages — storage backend documentation
Support
If you have questions or issues:
- Check the Bazis documentation
- Search through existing issues
- Create a new issue with detailed information
Made with ❤️ by Bazis team
Metadata
Release files for bazis-uploadable 2.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bazis_uploadable-2.4.0.tar.gz | 80.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bazis_uploadable-2.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 104.0 kB
Release files / bazis_uploadable-2.4.0.tar.gz
| Download URL | bazis_uploadable-2.4.0.tar.gz |
|---|---|
| Size | 80.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
29a03496a1f69e46b188473dcefdeded0cd85ceac557505c81f29710f13cf01d
|
|
BLAKE2b-256 checksum How to use checksums |
5e21659c4cade5ae9d66d5e3de7f14b2dfc4afa1151db53f1f8f4f74f6e81ee1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.21 {"installer":{"name":"uv","version":"0.12.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / bazis_uploadable-2.4.0-py3-none-any.whl
| Download URL | bazis_uploadable-2.4.0-py3-none-any.whl |
|---|---|
| Size | 23.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3790bf86c0dc02bacab8b6a7eb44f5620e52c1a2fe6ff0ae1f65669fde9db798
|
|
BLAKE2b-256 checksum How to use checksums |
c4fb76f9028b0cbb75b002b9cf5f7f976c20f3e939a5808006f0f98298506100
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.21 {"installer":{"name":"uv","version":"0.12.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|