[IMP] attachment_zipped_download: provide ir.attachment.action_download mixin

This helps to download multiple attachments from any models.
This commit is contained in:
Pierre Verkest 2023-04-28 16:13:20 +02:00
parent 1ab97f3e3f
commit 420ae570e6
14 changed files with 391 additions and 1 deletions

View File

@ -13,6 +13,14 @@ msgstr ""
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: attachment_zipped_download
#: model:ir.model,name:attachment_zipped_download.model_ir_attachment_action_download
msgid ""
"\n"
" Mixin to help download attachments linked to record(s).\n"
" "
msgstr ""
#. module: attachment_zipped_download
#: model:ir.model,name:attachment_zipped_download.model_ir_attachment
msgid "Attachment"
@ -20,6 +28,7 @@ msgstr ""
#. module: attachment_zipped_download
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment__display_name
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment_action_download__display_name
msgid "Display Name"
msgstr ""
@ -30,22 +39,40 @@ msgstr ""
#. module: attachment_zipped_download
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment__id
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment_action_download__id
msgid "ID"
msgstr ""
#. module: attachment_zipped_download
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment____last_update
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment_action_download____last_update
msgid "Last Modified on"
msgstr ""
#. module: attachment_zipped_download
#: code:addons/attachment_zipped_download/models/ir_attachment_action_download.py:0
#: code:addons/src/knowledge/attachment_zipped_download/models/ir_attachment_action_download.py:0
#, python-format
msgid "No attachment!"
msgstr ""
#. module: attachment_zipped_download
#: code:addons/attachment_zipped_download/models/ir_attachment.py:0
#: code:addons/src/knowledge/attachment_zipped_download/models/ir_attachment.py:0
#, python-format
msgid "None attachment selected. Only binary attachments allowed."
msgstr ""
#. module: attachment_zipped_download
#: code:addons/attachment_zipped_download/models/ir_attachment_action_download.py:0
#: code:addons/src/knowledge/attachment_zipped_download/models/ir_attachment_action_download.py:0
#, python-format
msgid "There is no document found to download."
msgstr ""
#. module: attachment_zipped_download
#: code:addons/attachment_zipped_download/controllers/main.py:0
#: code:addons/src/knowledge/attachment_zipped_download/controllers/main.py:0
#, python-format
msgid "attachments.zip"
msgstr ""

View File

@ -17,6 +17,14 @@ msgstr ""
"Plural-Forms: \n"
"X-Generator: Poedit 2.3\n"
#. module: attachment_zipped_download
#: model:ir.model,name:attachment_zipped_download.model_ir_attachment_action_download
msgid ""
"\n"
" Mixin to help download attachments linked to record(s).\n"
" "
msgstr ""
#. module: attachment_zipped_download
#: model:ir.model,name:attachment_zipped_download.model_ir_attachment
msgid "Attachment"
@ -24,6 +32,7 @@ msgstr "Adjunto"
#. module: attachment_zipped_download
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment__display_name
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment_action_download__display_name
msgid "Display Name"
msgstr "Nombre mostrado"
@ -34,22 +43,42 @@ msgstr "Descargar"
#. module: attachment_zipped_download
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment__id
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment_action_download__id
msgid "ID"
msgstr "ID"
#. module: attachment_zipped_download
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment____last_update
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment_action_download____last_update
msgid "Last Modified on"
msgstr "Última modificación el"
#. module: attachment_zipped_download
#: code:addons/attachment_zipped_download/models/ir_attachment_action_download.py:0
#: code:addons/src/knowledge/attachment_zipped_download/models/ir_attachment_action_download.py:0
#, python-format
msgid "No attachment!"
msgstr ""
#. module: attachment_zipped_download
#: code:addons/attachment_zipped_download/models/ir_attachment.py:0
#: code:addons/src/knowledge/attachment_zipped_download/models/ir_attachment.py:0
#, python-format
msgid "None attachment selected. Only binary attachments allowed."
msgstr "No se seleccionó ningún archivo adjunto. Solo se permiten archivos adjuntos binarios."
msgstr ""
"No se seleccionó ningún archivo adjunto. Solo se permiten archivos adjuntos "
"binarios."
#. module: attachment_zipped_download
#: code:addons/attachment_zipped_download/models/ir_attachment_action_download.py:0
#: code:addons/src/knowledge/attachment_zipped_download/models/ir_attachment_action_download.py:0
#, python-format
msgid "There is no document found to download."
msgstr ""
#. module: attachment_zipped_download
#: code:addons/attachment_zipped_download/controllers/main.py:0
#: code:addons/src/knowledge/attachment_zipped_download/controllers/main.py:0
#, python-format
msgid "attachments.zip"
msgstr "adjuntos.zip"

View File

@ -0,0 +1,78 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * attachment_zipped_download
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: attachment_zipped_download
#: model:ir.model,name:attachment_zipped_download.model_ir_attachment_action_download
msgid ""
"\n"
" Mixin to help download attachments linked to record(s).\n"
" "
msgstr "Mixin pour faciliter le téléchargements des pièces jointes lié aux enregistrements."
#. module: attachment_zipped_download
#: model:ir.model,name:attachment_zipped_download.model_ir_attachment
msgid "Attachment"
msgstr "Pièce jointe"
#. module: attachment_zipped_download
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment__display_name
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment_action_download__display_name
msgid "Display Name"
msgstr "Nom affiché"
#. module: attachment_zipped_download
#: model:ir.actions.server,name:attachment_zipped_download.action_attachments_download
msgid "Download"
msgstr "Télécharger"
#. module: attachment_zipped_download
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment__id
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment_action_download__id
msgid "ID"
msgstr ""
#. module: attachment_zipped_download
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment____last_update
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment_action_download____last_update
msgid "Last Modified on"
msgstr "Dernière modification le"
#. module: attachment_zipped_download
#: code:addons/attachment_zipped_download/models/ir_attachment_action_download.py:0
#: code:addons/src/knowledge/attachment_zipped_download/models/ir_attachment_action_download.py:0
#, python-format
msgid "No attachment!"
msgstr "Aucune pièce jointe !"
#. module: attachment_zipped_download
#: code:addons/attachment_zipped_download/models/ir_attachment.py:0
#: code:addons/src/knowledge/attachment_zipped_download/models/ir_attachment.py:0
#, python-format
msgid "None attachment selected. Only binary attachments allowed."
msgstr "Aucune pièce jointe sélectionnée. Seul les picèces jointes de type Binaire sont permises."
#. module: attachment_zipped_download
#: code:addons/attachment_zipped_download/models/ir_attachment_action_download.py:0
#: code:addons/src/knowledge/attachment_zipped_download/models/ir_attachment_action_download.py:0
#, python-format
msgid "There is no document found to download."
msgstr "Aucune pièce jointe téléchargeable trouvé."
#. module: attachment_zipped_download
#: code:addons/attachment_zipped_download/controllers/main.py:0
#: code:addons/src/knowledge/attachment_zipped_download/controllers/main.py:0
#, python-format
msgid "attachments.zip"
msgstr "pieces-jointes.zip"

View File

@ -16,6 +16,14 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.14.1\n"
#. module: attachment_zipped_download
#: model:ir.model,name:attachment_zipped_download.model_ir_attachment_action_download
msgid ""
"\n"
" Mixin to help download attachments linked to record(s).\n"
" "
msgstr ""
#. module: attachment_zipped_download
#: model:ir.model,name:attachment_zipped_download.model_ir_attachment
msgid "Attachment"
@ -23,6 +31,7 @@ msgstr "Allegato"
#. module: attachment_zipped_download
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment__display_name
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment_action_download__display_name
msgid "Display Name"
msgstr "Nome visualizzato"
@ -33,22 +42,40 @@ msgstr "Scarica"
#. module: attachment_zipped_download
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment__id
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment_action_download__id
msgid "ID"
msgstr "ID"
#. module: attachment_zipped_download
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment____last_update
#: model:ir.model.fields,field_description:attachment_zipped_download.field_ir_attachment_action_download____last_update
msgid "Last Modified on"
msgstr "Ultima modifica il"
#. module: attachment_zipped_download
#: code:addons/attachment_zipped_download/models/ir_attachment_action_download.py:0
#: code:addons/src/knowledge/attachment_zipped_download/models/ir_attachment_action_download.py:0
#, python-format
msgid "No attachment!"
msgstr ""
#. module: attachment_zipped_download
#: code:addons/attachment_zipped_download/models/ir_attachment.py:0
#: code:addons/src/knowledge/attachment_zipped_download/models/ir_attachment.py:0
#, python-format
msgid "None attachment selected. Only binary attachments allowed."
msgstr "Nessun allegato selezionato. Consentiti solo allegati binari."
#. module: attachment_zipped_download
#: code:addons/attachment_zipped_download/models/ir_attachment_action_download.py:0
#: code:addons/src/knowledge/attachment_zipped_download/models/ir_attachment_action_download.py:0
#, python-format
msgid "There is no document found to download."
msgstr ""
#. module: attachment_zipped_download
#: code:addons/attachment_zipped_download/controllers/main.py:0
#: code:addons/src/knowledge/attachment_zipped_download/controllers/main.py:0
#, python-format
msgid "attachments.zip"
msgstr "attachments.zip"

View File

@ -1 +1,2 @@
from . import ir_attachment
from . import ir_attachment_action_download

View File

@ -0,0 +1,55 @@
# Copyright 2023 Foodles (https://www.foodles.com/)
# @author Pierre Verkest <pierreverkest84@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, models
class IrAttachmentActionDownloadMixin(models.AbstractModel):
_name = "ir.attachment.action_download"
_description = """
Mixin to help download attachments linked to record(s).
"""
def _get_downloadable_attachments(self):
"""Give a chance to easily overwrite this method
on sub modules to limit restict attachement able to downlaods
In some case we probably want the user download some specific
document that are probably related to the current model
By default return all attachment link the the record.
"""
return self.env["ir.attachment"].search(
[("res_model", "=", self._name), ("res_id", "in", self.ids)]
)
def action_download_attachments(self):
"""Return action to:
* emit a warning message if no attachment found
* download a file if only 1 file found
* zip and download the list of attachment returns by `_get_downloadable_attachments`
"""
attachments = self._get_downloadable_attachments()
if not attachments:
title = _("No attachment!")
message = _("There is no document found to download.")
return {
"type": "ir.actions.client",
"tag": "display_notification",
"params": {
"type": "warning",
"title": title,
"message": message,
"sticky": True,
},
}
if len(attachments) == 1:
return {
"target": "self",
"type": "ir.actions.act_url",
"url": "/web/content/%s?download=1" % attachments.id,
}
else:
return attachments.action_attachments_download()

View File

@ -4,3 +4,5 @@
* Víctor Martínez
* Pedro M. Baeza
* Pierre Verkest <pierreverkest@gmail.com>

View File

@ -1 +1,4 @@
This module allows downloading multiple attachments as a zip file.
This also provide a helper class `IrAttachmentActionDownloadMixin`
to be used by developer to add action method on models.

View File

@ -1,2 +1,78 @@
#. Go to *Settings > Technical > Database Structure > Attachments* and select some files.
#. Go to *Actions > Download* and a zip file containing the selected files will be downloaded.
## For developer
You can reuse the `IrAttachmentActionDownloadMixin` on your
favorite models::
from odoo import models
class StockPicking(models.Model):
_name = "stock.picking"
_inherit = ["stock.picking", "ir.attachment.action_download"]
Then you can add an action button on list view line or on the action button
(when multiple lines are selected) to download all files::
<odoo>
<!--
add a button on list view to download all attachement present
on the given transfert
-->
<record id="vpicktree" model="ir.ui.view">
<field name="inherit_id" ref="stock.vpicktree"/>
<field name="name">stock.picking.tree download attachments</field>
<field name="model">stock.picking</field>
<field name="arch" type="xml">
<field name="json_popover" position="after">
<button name="action_download_attachments"
type="object"
icon="fa-download"
string="Download attachment(s)"
aria-label="Download Proof documents"
class="float-right"/>
</field>
</field>
</record>
<!--
Add "Download attachments" item in the Action menu when
multiple records are selected
-->
<record id="action_download_picking_attachements" model="ir.actions.server">
<field name="name">Download attachments</field>
<field name="model_id" ref="stock.model_stock_picking"/>
<field name="binding_model_id" ref="stock.model_stock_picking"/>
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="code">
action = records.action_download_attachments()
</field>
</record>
</odoo>
.. note::
Even you will be able to generate a zip file with multiple document with the
same name it's advice to overwrite `_compute_zip_file_name` to improve the
name. When a slash (`/`) is present in the path it will create a directory.
This example will create a directory per stock.picking using its name::
class IrAttachment(models.Model):
_inherit = "ir.attachment"
def _compute_zip_file_name(self):
self.ensure_one()
if self.res_model and self.res_model == "stock.picking":
return (
self.env[self.res_model]
.browse(self.res_id)
.display_name.replace("/", "-")
+ "/"
+ self.name
)
return super()._compute_zip_file_name()

View File

@ -1,3 +1,4 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from . import test_attachment_zipped_download
from . import test_ir_attachment_action_download

View File

@ -0,0 +1,9 @@
# Copyright 2023 Foodles (https://www.foodles.com/)
# @author Pierre Verkest <pierreverkest84@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models
class ResPartner(models.Model):
_name = "res.partner"
_inherit = ["res.partner", "ir.attachment.action_download"]

View File

@ -0,0 +1,81 @@
# Copyright 2023 Foodles (https://www.foodles.com/)
# @author Pierre Verkest <pierreverkest84@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo_test_helper import FakeModelLoader
from odoo.tests import SavepointCase
from .test_attachment_zipped_download import TestAttachmentZippedDownloadBase
class TestMixin(SavepointCase, TestAttachmentZippedDownloadBase):
@classmethod
def setUpClass(cls):
super(TestMixin, cls).setUpClass()
cls.loader = FakeModelLoader(cls.env, cls.__module__)
cls.addClassCleanup(cls.loader.restore_registry)
cls.loader.backup_registry()
# Imported Test model must be done after the backup_registry
from .models.res_partner import ResPartner
cls.loader.update_registry((ResPartner,))
cls.partner_1 = cls.env.ref("base.res_partner_1")
cls.partner_2 = cls.env.ref("base.res_partner_2")
cls.partner_3 = cls.env.ref("base.res_partner_3")
cls.partner_1_f1 = cls._create_attachment(
cls.env,
cls.env.uid,
"partner_1-f1.txt",
model="res.partner",
res_id=cls.partner_1.id,
)
cls.partner_1_f2 = cls._create_attachment(
cls.env,
cls.env.uid,
"partner_1-f2.txt",
model="res.partner",
res_id=cls.partner_1.id,
)
cls.partner_2_f1 = cls._create_attachment(
cls.env,
cls.env.uid,
"partner_2-f1.txt",
model="res.partner",
res_id=cls.partner_2.id,
)
def test_action_download_attachments_no_attachment(self):
action = self.partner_3.action_download_attachments()
self.assertEqual(action["type"], "ir.actions.client")
self.assertEqual(action["tag"], "display_notification")
def test_action_download_attachments_one_attachment(self):
action = (self.partner_2 | self.partner_3).action_download_attachments()
self.assertEqual(action["type"], "ir.actions.act_url")
self.assertEqual(action["target"], "self")
self.assertEqual(
action["url"], "/web/content/%s?download=1" % self.partner_2_f1.id
)
def test_action_download_attachments_two_attachment_one_record(self):
action = (self.partner_1).action_download_attachments()
self.assertEqual(action["type"], "ir.actions.act_url")
self.assertEqual(action["target"], "self")
self.assertTrue(action["url"].startswith("/web/attachment/download_zip?ids="))
ids = sorted(map(int, action["url"].split("=")[1].split(",")))
self.assertEqual(ids, (self.partner_1_f1 | self.partner_1_f2).ids)
def test_action_download_attachments_three_attachment_n_records(self):
action = (
self.partner_1 | self.partner_2 | self.partner_3
).action_download_attachments()
self.assertEqual(action["type"], "ir.actions.act_url")
self.assertEqual(action["target"], "self")
self.assertTrue(action["url"].startswith("/web/attachment/download_zip?ids="))
ids = sorted(map(int, action["url"].split("=")[1].split(",")))
self.assertEqual(
ids, (self.partner_1_f1 + self.partner_1_f2 + self.partner_2_f1).ids
)

1
test-requirements.txt Normal file
View File

@ -0,0 +1 @@
odoo_test_helper