File upload management module for Bazis framework.
Project description
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'
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bazis_uploadable-2.2.0.tar.gz.
File metadata
- Download URL: bazis_uploadable-2.2.0.tar.gz
- Upload date:
- Size: 72.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
942cd6361223786e9dddb3dbf0d34eee5c9e80cb5466121c85deba44357eea92
|
|
| MD5 |
d296a3d3a311886950fbba251dcd361e
|
|
| BLAKE2b-256 |
72c6a907eb8ca0e99f8accf88f7cfef3a369212cefcf9629fe90eadf0f493807
|
File details
Details for the file bazis_uploadable-2.2.0-py3-none-any.whl.
File metadata
- Download URL: bazis_uploadable-2.2.0-py3-none-any.whl
- Upload date:
- Size: 18.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b8528460cea67a99c16bae75e789101bdf5f599c3e6776708aae41543c4616d
|
|
| MD5 |
9c4ae2c1a308d726b7d05198c0d1e82c
|
|
| BLAKE2b-256 |
ffc210e5ff7bd5321dc4ca39baff9e17b9169e00a53bdabbc95ff76660d1177a
|