mirror of
https://github.com/OCA/knowledge.git
synced 2025-07-28 03:16:29 -06:00
[8.0][ADD] attachment_zipped_download
This commit is contained in:
parent
484cc67eff
commit
19be196209
3
attachment_zipped_download/__init__.py
Normal file
3
attachment_zipped_download/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import controllers
|
||||||
|
from . import models
|
16
attachment_zipped_download/__openerp__.py
Normal file
16
attachment_zipped_download/__openerp__.py
Normal 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,
|
||||||
|
}
|
2
attachment_zipped_download/controllers/__init__.py
Normal file
2
attachment_zipped_download/controllers/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import main
|
23
attachment_zipped_download/controllers/main.py
Normal file
23
attachment_zipped_download/controllers/main.py
Normal 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"),
|
||||||
|
)
|
3
attachment_zipped_download/models/__init__.py
Normal file
3
attachment_zipped_download/models/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import ir_attachment
|
||||||
|
from . import ir_attachment_action_download
|
52
attachment_zipped_download/models/ir_attachment.py
Normal file
52
attachment_zipped_download/models/ir_attachment.py
Normal 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
|
@ -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()
|
12
attachment_zipped_download/readme/CONTRIBUTORS.rst
Normal file
12
attachment_zipped_download/readme/CONTRIBUTORS.rst
Normal 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>
|
4
attachment_zipped_download/readme/DESCRIPTION.rst
Normal file
4
attachment_zipped_download/readme/DESCRIPTION.rst
Normal 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.
|
86
attachment_zipped_download/readme/USAGE.rst
Normal file
86
attachment_zipped_download/readme/USAGE.rst
Normal 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()
|
BIN
attachment_zipped_download/static/description/icon.png
Normal file
BIN
attachment_zipped_download/static/description/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
23
attachment_zipped_download/views/ir_attachment_view.xml
Normal file
23
attachment_zipped_download/views/ir_attachment_view.xml
Normal 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>
|
Loading…
Reference in New Issue
Block a user