Odoo
Thin integration over Core. Adapts Werkzeug FileStorage from Odoo controllers and maps errors to JSON. Optional Odoo 17/18 addon wires settings, a service model, and an HTTP route. Python 3.10–3.12.
Install
Shell
pip install uploadkit-odoo uploadkit-security
Shell
uv add uploadkit-odoo uploadkit-security
Shell
poetry add uploadkit-odoo uploadkit-security
Adapter glue
Policy setup, validators, UploaderError, and the JSON response shape are shared — see Common patterns. Supply your own StorageProvider; creating ir.attachment records after upload is left to your module.
Uses as_uploadable() on Werkzeug FileStorage and json_error_response().
controllers.py
from odoo import http
from odoo.http import request
from uploadkit import Uploader, UploadPolicy, UploaderError
from uploadkit_odoo import as_uploadable, json_error_response
from uploadkit_security import default_validators
def notify(result):
...
class MyController(http.Controller):
@http.route("/my/upload", type="http", auth="user", methods=["POST"], csrf=True)
def upload(self, **kw):
storage = get_provider() # your StorageProvider factory
policy = UploadPolicy(
max_size=5 * 1024 * 1024,
allowed_extensions=frozenset({"png"}),
allowed_mime_types=frozenset({"image/png"}),
validators=default_validators(),
)
uploaded = kw.get("file")
try:
result = Uploader(policy, storage).upload(
as_uploadable(uploaded),
bucket="uploads",
object_name=uploaded.filename,
after_upload=notify, # or Celery-like .delay
)
except UploaderError as exc:
return json_error_response(exc)
return request.make_json_response(result.as_task_kwargs())
AWS: leave endpoint_url unset. MinIO: set it to your endpoint. Full class also in Storage.
storage.py
# my_module/storage.py
import boto3
from botocore.client import Config
from odoo.tools import config
class Boto3S3Storage:
def __init__(
self,
*,
access_key: str,
secret_key: str,
region: str = "us-east-1",
endpoint_url: str | None = None,
) -> None:
kwargs: dict = {
"service_name": "s3",
"aws_access_key_id": access_key,
"aws_secret_access_key": secret_key,
"region_name": region,
"config": Config(signature_version="s3v4"),
}
if endpoint_url:
kwargs["endpoint_url"] = endpoint_url
self.client = boto3.client(**kwargs)
def put(self, *, bucket, object_name, body, content_type):
resp = self.client.put_object(
Bucket=bucket,
Key=object_name,
Body=body,
ContentType=content_type,
)
return resp.get("ETag")
def get_provider():
"""Factory used by uploadkit.storage_provider config parameter."""
return Boto3S3Storage(
access_key=config.get("uploadkit_access_key", ""),
secret_key=config.get("uploadkit_secret_key", ""),
region=config.get("uploadkit_region", "us-east-1"),
endpoint_url=config.get("uploadkit_endpoint_url") or None,
)
Add this repo’s addons/ to Odoo addons_path, install UploadKit in Apps, then configure under Settings → UploadKit (storage factory, bucket, optional prefix / max size).
service.py
# After installing the UploadKit Odoo addon:
result = env["uploadkit.service"].upload(
file_storage,
object_name="docs/a.pdf",
)
# result is UploadResult.as_task_kwargs() — no after_upload parameter
# Enqueue from the dict, or call Uploader.upload(..., after_upload=...) yourself
# Or POST multipart to /uploadkit/upload (auth=user, CSRF)
# field: file | optional: object_name
After-upload on library controllers: Common patterns
·
Full Boto3S3Storage class:
Storage
·
uploadkit-odoo README