Download Multiple Attachment with Zipped File

Bismillah,
Assalamu'alaikum Warohmatullah Wabarokatuh,


Pada kesempatan kali ini saya akan berbagi sedikit tips terkait customize odoo 14, yaitu cara download multiple attachment menjadi file zip.

Sebenarnya di App store odoo sudah ada addons untuk mendownload beberapa file attachment menjadi 1 dokumen zip melalui menu attachment, tapi akan saya customize fiturnya agar bisa melakukan download dari object yang memiliki field one2many yang di dalamnya ada field binary.

Berikut langkah - langkahnya :

1. Install addons attachment_zipped_download

2. Pastikan ada field one2many yang di dalamnya ada field binary dan berikan atribut attachment=True agar disimpan ke ir.attachment

class ChecklistDocumentLine(models.Model):
_name = 'checklist.document.line'
_description = 'Checklist Document Line'

document_id = fields.Many2one('document.type', string='Document Name')
checked = fields.Boolean('Checked')
completed = fields.Boolean('Completed', required=True)
attachment = fields.Binary(string='Attachment', attachment=True)
payment_approval_id = fields.Many2one('payment.approval.form', string='')
is_required = fields.Boolean('Required')
filename = fields.Char('Filename')



3. Overiding method create dan write di object one one2many agar nama attachment tercatat di ir.attachment dan tercatat juga extension filenya

    @api.model
def create(self, vals):
res = super(ChecklistDocumentLine, self).create(vals)
# Write name of file in ir.attachment
attachment_obj = self.env['ir.attachment']
if res.attachment:
data = attachment_obj.search([('res_model', '=', self._name),
('res_field', '=', 'attachment'),
('res_id', '=', res.id)])
if data:
for d in data:
d.name = res.filename
return res

def write(self, vals):
res = super(ChecklistDocumentLine, self).write(vals)
# Write name of file in ir.attachment
attachment_obj = self.env['ir.attachment']
if self.attachment:
data = attachment_obj.search([('res_model', '=', self._name),
('res_field', '=', 'attachment'),
('res_id', '=', self.id)])
if data:
for d in data:
d.name = self.filename
return res



4. Buat function di object header one2many tersebut untuk action downloadnya

class PaymentApprovalForm(models.Model):
_name = 'payment.approval.form'
_inherit = ["mail.thread", "mail.activity.mixin"]
_description = 'Payment Approval Form'


    def mass_download_attachment(self):
attachment_obj = self.env['ir.attachment']
attachment_ids = []
for line in self.checklist_document_line:
if line.attachment:
data = attachment_obj.search([('res_model', '=', line._name),
('res_field', '=', 'attachment'),
('res_id', '=', line.id)])
for d in data:
attachment_ids.append(d.id)
ids = ",".join(map(str, attachment_ids))
return {
"type": "ir.actions.act_url",
"url": "/web/attachment/download_zip?ids=%s" % (ids),
"target": "self",
}



5. Overiding method check di object ir.attachment dan nonaktifkan raise AccessError(_("Sorry, you are not allowed to access this document.")) yang kedua agar user selain admin atau yang tidak memiliki akses 'Administration' bisa melakukan download attachment

class IrAttachment(models.Model):
_inherit = 'ir.attachment'
_description = 'Ir Attachment'

@api.model
def check(self, mode, values=None):
""" Restricts the access to an ir.attachment, according to referred mode """
if self.env.is_superuser():
return True
# Always require an internal user (aka, employee) to access to a attachment
if not (self.env.is_admin() or self.env.user.has_group('base.group_user')):
raise AccessError(_("Sorry, you are not allowed to access this document."))
# collect the records to check (by model)
model_ids = defaultdict(set) # {model_name: set(ids)}
if self:
# DLE P173: `test_01_portal_attachment`
self.env['ir.attachment'].flush(['res_model', 'res_id', 'create_uid', 'public', 'res_field'])
self._cr.execute('SELECT res_model, res_id, create_uid, public, res_field FROM ir_attachment WHERE id IN %s', [tuple(self.ids)])
for res_model, res_id, create_uid, public, res_field in self._cr.fetchall():
if not self.env.is_system() and res_field:
pass
# raise AccessError(_("Sorry, you are not allowed to access this document."))
if public and mode == 'read':
continue
if not (res_model and res_id):
continue
model_ids[res_model].add(res_id)
if values and values.get('res_model') and values.get('res_id'):
model_ids[values['res_model']].add(values['res_id'])

# check access rights on the records
for res_model, res_ids in model_ids.items():
# ignore attachments that are not attached to a resource anymore
# when checking access rights (resource was deleted but attachment
# was not)
if res_model not in self.env:
continue
if res_model == 'res.users' and len(res_ids) == 1 and self.env.uid == list(res_ids)[0]:
# by default a user cannot write on itself, despite the list of writeable fields
# e.g. in the case of a user inserting an image into his image signature
# we need to bypass this check which would needlessly throw us away
continue
records = self.env[res_model].browse(res_ids).exists()
# For related models, check if we can write to the model, as unlinking
# and creating attachments can be seen as an update to the model
access_mode = 'write' if mode in ('create', 'unlink') else mode
records.check_access_rights(access_mode)
records.check_access_rule(access_mode)

note : Agar lebih aman bisa juga dibuatkan grup user baru dan memindahkan kondisi ke grup user baru tersebut agar object ir.attachment tetap terbatasi untuk user tertentu saja

Terima kasih, semoga bermanfaat.
Kahfi

Wassalamu'alaikum Warohmatullah Wabarokatuh







Membuat Domain berdasarkan field Many2many
Memakai method Onchange