[8.0][ADD] attachment_zipped_download

This commit is contained in:
newtratip 2023-11-18 00:49:41 +07:00
parent 484cc67eff
commit 19be196209
12 changed files with 280 additions and 0 deletions

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import controllers
from . import models

View File

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Copyright 2022 Tecnativa - Víctor Martínez
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Attachment Zipped Download",
"version": "8.0.1.0.0",
"category": "Tools",
"website": "https://github.com/OCA/knowledge",
"author": "Tecnativa, Odoo Community Association (OCA)",
"license": "AGPL-3",
"depends": ["base"],
"data": [
"views/ir_attachment_view.xml",
],
"installable": True,
}

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import main

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Copyright 2019 César Fernández Domínguez <cesfernandez@outlook.com>
# Copyright 2022 Tecnativa - Víctor Martínez
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from openerp import _, http
from openerp.http import request
class AttachmentZippedDownloadController(http.Controller):
@http.route("/web/attachment/download_zip", type="http", auth="user")
def download_zip(self, ids=None, debug=0):
ids = [] if not ids else ids
if len(ids) == 0:
return
list_ids = map(int, ids.split(","))
out_file = request.env["ir.attachment"].browse(list_ids)._create_temp_zip()
return http.send_file(
filepath_or_fp=out_file,
mimetype="application/zip",
as_attachment=True,
filename=_("attachments.zip"),
)

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import ir_attachment
from . import ir_attachment_action_download

View File

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
# Copyright 2019 César Fernández Domínguez <cesfernandez@outlook.com>
# Copyright 2022 Tecnativa - Víctor Martínez
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
import base64
import zipfile
from io import BytesIO
from openerp import _, api, models
from openerp.exceptions import Warning as UserError
class IrAttachment(models.Model):
_inherit = "ir.attachment"
@api.multi
def action_attachments_download(self):
items = self.filtered(lambda x: x.type == "binary")
if not items:
raise UserError(
_("None attachment selected. Only binary attachments allowed.")
)
item_names = [it._compute_zip_file_name() for it in items]
if len(item_names) != len(set(item_names)):
raise UserError(
_("All file names must be unique.")
)
ids = ",".join(map(str, items.ids))
return {
"type": "ir.actions.act_url",
"url": "/web/attachment/download_zip?ids=%s" % (ids),
"target": "self",
}
@api.multi
def _create_temp_zip(self):
zip_buffer = BytesIO()
with zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED, False) as zip_file:
for attachment in self:
attachment.check("read")
zip_file.writestr(
attachment._compute_zip_file_name(),
base64.b64decode(attachment.datas),
)
zip_file.close()
return zip_buffer
@api.multi
def _compute_zip_file_name(self):
"""Give a chance of easily changing the name of the file inside the ZIP."""
self.ensure_one()
return self.name

View File

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
# 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 openerp import _, api, models
class IrAttachmentActionDownloadMixin(models.AbstractModel):
_name = "ir.attachment.action_download"
_description = """
Mixin to help download attachments linked to record(s).
"""
@api.multi
def _get_downloadable_attachments(self):
"""Give a chance to easily overwrite this method
on sub modules to limit restict attachement able to downloads
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)]
)
@api.multi
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!")
text = _("There is no document found to download.")
return {
"type": "ir.actions.client",
"tag": "action_warn",
"params": {
"title": title,
"text": text,
"sticky": True,
},
}
if len(attachments) == 1:
return {
"target": "self",
"type": "ir.actions.act_url",
"url": "/web/binary/saveas?model=ir.attachment&field=datas&filename_field=name&id=%s" % attachments.id,
}
else:
return attachments.action_attachments_download()

View File

@ -0,0 +1,12 @@
* César Fernández Domínguez <cesfernandez@outlook.com>
* `Tecnativa <https://www.tecnativa.com>`_:
* Víctor Martínez
* Pedro M. Baeza
* Pierre Verkest <pierreverkest@gmail.com>
* `Ecosoft <https://ecosoft.co.th>`_:
* Tharathip Chaweewongphan <tharathipc@ecosoft.co.th>

View File

@ -0,0 +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

@ -0,0 +1,86 @@
#. Go to *Settings > Technical > Database Structure > Attachments* and select some files.
#. Go to *More > Download* and a zip file containing the selected files will be downloaded.
## For developer
You can reuse the `IrAttachmentActionDownloadMixin` on your
favorite models::
from openerp 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::
<openerp>
<!--
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="picking_type_id" 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="state">code</field>
<field name="code">
action = self.action_download_attachments(cr, uid, context.get('active_ids', []), context=context)
</field>
</record>
<record id="action_download_picking_attachements_values" model="ir.values">
<field name="name">Download attachments</field>
<field name="model">stock.picking</field>
<field name="key2">client_action_multi</field>
<field
name="value"
eval="'ir.actions.server,%d'%action_download_picking_attachements"
/>
<field name="object" eval="True"/>
</record>
</openerp>
.. 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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8" ?>
<openerp>
<data>
<record id="action_attachments_download" model="ir.actions.server">
<field name="name">Download</field>
<field name="model_id" ref="base.model_ir_attachment" />
<field name="state">code</field>
<field name="code">
action = self.action_attachments_download(cr, uid, context.get('active_ids', []), context=context)
</field>
</record>
<record id="action_attachments_download_values" model="ir.values">
<field name="name">Download</field>
<field name="model">ir.attachment</field>
<field name="key2">client_action_multi</field>
<field
name="value"
eval="'ir.actions.server,%d'%action_attachments_download"
/>
<field name="object" eval="True"/>
</record>
</data>
</openerp>