Skip to content
Open
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
106 changes: 106 additions & 0 deletions dms_spreadsheet/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
=================
DMS Spreadsheet
=================

.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:placeholder
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3

|badge1|

Create and edit OCA spreadsheets directly within the DMS file manager.

Requires ``dms`` and ``spreadsheet_oca``.

**Features**

* **New Spreadsheet** button in DMS directory views (form and kanban)
* Opening a ``.o-spreadsheet`` file launches the full OCA spreadsheet editor
* Spreadsheet data stored transparently via DMS file storage (database,
attachment, or filesystem)
* Read-only mode for users without write access to the DMS file
* Revision history via ``spreadsheet.abstract`` mixin

**Table of contents**

.. contents::
:local:

Usage
=====

**Creating a new spreadsheet**

1. Open **DMS** → navigate to the target directory.
2. Click the **New Spreadsheet** button (available in the form header and the
kanban directory card).
3. Enter a name and confirm — the OCA spreadsheet editor opens immediately.
4. Edit your spreadsheet; changes are saved automatically via the revision
protocol.

**Opening an existing spreadsheet**

1. Browse to the directory containing the file.
2. Open the file record; click **Open Spreadsheet** in the form header.
Alternatively, double-click the file in the kanban/list view — if the MIME
type is ``application/o-spreadsheet`` the editor is launched directly.

**Read-only access**

Users who have read but not write access to a DMS file will have the
spreadsheet opened in **read-only** mode (the editor toolbar is disabled).

Configuration
=============

No additional configuration is required after installing the module.

Spreadsheet files are identified by the MIME type
``application/o-spreadsheet`` and the ``handler`` field value ``spreadsheet``
on ``dms.file`` records. Both are set automatically when a file is created
through the **New Spreadsheet** wizard or when an existing DMS file's MIME
type is set to ``application/o-spreadsheet``.

Access to individual spreadsheets is governed by the existing DMS permission
system (storage-level groups and directory-level ACLs).

Known issues / Roadmap
======================

* Template library: pre-built spreadsheet templates selectable from the
creation wizard.
* Import XLSX: convert an uploaded Excel file to an OCA spreadsheet in-place.
* Thumbnail preview: render a small image of the spreadsheet content as the
DMS file thumbnail.
* Collaborative editing: expose the OCA collaborative-revision WebSocket
endpoint for DMS files.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/dms/issues>`_.

Credits
=======

Authors
~~~~~~~

* Ledo Enterprises

Contributors
~~~~~~~~~~~~

* Ledo Enterprises <https://ledoweb.com>

Maintainers
~~~~~~~~~~~

This module is part of the `OCA/dms <https://github.com/OCA/dms>`_ project.
4 changes: 4 additions & 0 deletions dms_spreadsheet/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2026 Ledo Enterprises
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from . import models, wizard
24 changes: 24 additions & 0 deletions dms_spreadsheet/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright 2026 Ledo Enterprises
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

{
"name": "DMS Spreadsheet",
"summary": "Create and edit spreadsheets directly within the DMS file manager",
"version": "18.0.1.0.0",
"category": "Document Management",
"license": "AGPL-3",
"author": "Ledo Enterprises, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/dms",
"depends": [
"dms",
"spreadsheet_oca",
],
"data": [
"security/ir.model.access.csv",
"wizard/dms_spreadsheet_create_views.xml",
"views/dms_file_views.xml",
"views/dms_directory_views.xml",
],
"installable": True,
"auto_install": False,
}
4 changes: 4 additions & 0 deletions dms_spreadsheet/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2026 Ledo Enterprises
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from . import dms_directory, dms_file
20 changes: 20 additions & 0 deletions dms_spreadsheet/models/dms_directory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2026 Ledo Enterprises
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import models


class DmsDirectory(models.Model):
_inherit = "dms.directory"

def action_new_spreadsheet(self):
"""Open the New Spreadsheet wizard pre-filled with this directory."""
self.ensure_one()
return {
"type": "ir.actions.act_window",
"name": "New Spreadsheet",
"res_model": "dms.spreadsheet.create",
"view_mode": "form",
"target": "new",
"context": {"default_directory_id": self.id},
}
145 changes: 145 additions & 0 deletions dms_spreadsheet/models/dms_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Copyright 2026 Ledo Enterprises
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

import base64
import json
import logging

from odoo import api, fields, models
from odoo.exceptions import AccessError

_logger = logging.getLogger(__name__)

SPREADSHEET_MIMETYPE = "application/o-spreadsheet"


class DmsFile(models.Model):
_name = "dms.file"
_inherit = ["dms.file", "spreadsheet.abstract"]

handler = fields.Selection(
selection=[("spreadsheet", "Spreadsheet")],
index=True,
)

# ── Lifecycle ────────────────────────────────────────────────────────────

@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get("mimetype") == SPREADSHEET_MIMETYPE:
vals["handler"] = "spreadsheet"
if vals.get("content") and not vals.get("spreadsheet_binary_data"):
vals["spreadsheet_binary_data"] = vals["content"]
return super().create(vals_list)

def write(self, vals):
if vals.get("mimetype") == SPREADSHEET_MIMETYPE:
vals["handler"] = "spreadsheet"
return super().write(vals)

# ── Storage bridge ───────────────────────────────────────────────────────

def _compute_content(self):
"""For spreadsheet files, content mirrors spreadsheet_binary_data."""
spreadsheets = self.filtered(lambda f: f.handler == "spreadsheet")
for rec in spreadsheets:
rec.content = rec.spreadsheet_binary_data
return super(DmsFile, self - spreadsheets)._compute_content()

def _inverse_content(self):
"""For spreadsheet files, write-through to spreadsheet_binary_data."""
spreadsheets = self.filtered(lambda f: f.handler == "spreadsheet")
for rec in spreadsheets:
rec.spreadsheet_binary_data = rec.content
return super(DmsFile, self - spreadsheets)._inverse_content()

# ── spreadsheet.abstract interface ───────────────────────────────────────

def get_spreadsheet_data(self):
"""Return data payload consumed by action_spreadsheet_oca client action."""
self.ensure_one()
mode = "normal"
try:
self.check_access("write")
except AccessError:
mode = "readonly"

raw = {}
if self.spreadsheet_binary_data:
try:
raw = json.loads(
base64.decodebytes(self.spreadsheet_binary_data).decode("utf-8")
)
except (ValueError, UnicodeDecodeError):
_logger.warning(
"Could not decode spreadsheet data for dms.file %s", self.id
)

revisions = []
for rev in self.spreadsheet_revision_ids:
try:
revisions.append(
dict(
json.loads(rev.commands),
nextRevisionId=rev.next_revision_id,
serverRevisionId=rev.server_revision_id,
)
)
except (ValueError, UnicodeDecodeError):
_logger.warning(
"Skipping malformed revision %s on dms.file %s", rev.id, self.id
)

return {
"name": self.name,
"spreadsheet_raw": raw,
"revisions": revisions,
"mode": mode,
"default_currency": self.env[
"res.currency"
].get_company_currency_for_spreadsheet(),
"user_locale": self.env["res.lang"]._get_user_spreadsheet_locale(),
}

def open_spreadsheet(self):
self.ensure_one()
return {
"type": "ir.actions.client",
"tag": "action_spreadsheet_oca",
"params": {"spreadsheet_id": self.id, "model": self._name},
}

@api.model
def action_open_new_spreadsheet(self):
"""Open the New Spreadsheet wizard, pre-filling directory from context.

Called from the server action bound to the file kanban/list views so
that 'New Spreadsheet' appears in the Files ⚙ action menu.
"""
ctx = self.env.context
directory_id = ctx.get("default_directory_id") or ctx.get("active_id")
return {
"type": "ir.actions.act_window",
"res_model": "dms.spreadsheet.create",
"view_mode": "form",
"target": "new",
"context": {"default_directory_id": directory_id},
}

# ── Computed helpers ─────────────────────────────────────────────────────

@api.depends("handler")
def _compute_extension(self):
"""Spreadsheet files use 'oss' extension (MIME not in Python stdlib)."""
spreadsheets = self.filtered(lambda f: f.handler == "spreadsheet")
for rec in spreadsheets:
rec.extension = "oss"
return super(DmsFile, self - spreadsheets)._compute_extension()

@api.depends("handler")
def _compute_mimetype(self):
spreadsheets = self.filtered(lambda f: f.handler == "spreadsheet")
for rec in spreadsheets:
rec.mimetype = SPREADSHEET_MIMETYPE
return super(DmsFile, self - spreadsheets)._compute_mimetype()
3 changes: 3 additions & 0 deletions dms_spreadsheet/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
9 changes: 9 additions & 0 deletions dms_spreadsheet/readme/CONFIGURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
No additional configuration is required after installing the module.

Spreadsheet files are identified by the MIME type `application/o-spreadsheet` and the
`handler` field value `spreadsheet` on `dms.file` records. Both are set automatically
when a file is created through the **New Spreadsheet** wizard or when an existing DMS
file's MIME type is set to `application/o-spreadsheet`.

Access to individual spreadsheets is governed by the existing DMS permission system
(storage-level groups and directory-level ACLs).
1 change: 1 addition & 0 deletions dms_spreadsheet/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Ledo Enterprises <https://ledoweb.com>
11 changes: 11 additions & 0 deletions dms_spreadsheet/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Create and edit OCA spreadsheets directly within the DMS file manager.

Requires `dms` and `spreadsheet_oca`.

**Features**

- **New Spreadsheet** button in DMS directory views (form and kanban)
- Opening a `.o-spreadsheet` file launches the full OCA spreadsheet editor
- Spreadsheet data stored transparently via DMS file storage (database, attachment, or filesystem)
- Read-only mode for users without write access to the DMS file
- Revision history via `spreadsheet.abstract` mixin
4 changes: 4 additions & 0 deletions dms_spreadsheet/readme/ROADMAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- Template library: pre-built spreadsheet templates selectable from the creation wizard.
- Import XLSX: convert an uploaded Excel file to an OCA spreadsheet in-place.
- Thumbnail preview: render a small image of the spreadsheet content as the DMS file thumbnail.
- Collaborative editing: expose the OCA collaborative-revision WebSocket endpoint for DMS files.
18 changes: 18 additions & 0 deletions dms_spreadsheet/readme/USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
**Creating a new spreadsheet**

1. Open **DMS** → navigate to the target directory.
2. Click the **New Spreadsheet** button (available in the form header and the kanban directory card).
3. Enter a name and confirm — the OCA spreadsheet editor opens immediately.
4. Edit your spreadsheet; changes are saved automatically via the revision protocol.

**Opening an existing spreadsheet**

1. Browse to the directory containing the file.
2. Open the file record; click **Open Spreadsheet** in the form header.
Alternatively, double-click the file in the kanban/list view — if the MIME type is
`application/o-spreadsheet` the editor is launched directly.

**Read-only access**

Users who have read but not write access to a DMS file will have the spreadsheet opened
in **read-only** mode (the editor toolbar is disabled).
2 changes: 2 additions & 0 deletions dms_spreadsheet/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_dms_spreadsheet_create_user,dms.spreadsheet.create user,model_dms_spreadsheet_create,base.group_user,1,1,1,1
Binary file added dms_spreadsheet/static/description/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions dms_spreadsheet/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_dms_spreadsheet
Loading