[IMP] web: allow users to download multiple vcards at once
This commit adds a download vCard server action in the list view of contacts, making it possible to download multiple vCards at once. Task-3385058 closes odoo/odoo#135435 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -1,20 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import importlib.util
|
||||
import io
|
||||
import zipfile
|
||||
|
||||
import odoo.http as http
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.http import request, content_disposition
|
||||
|
||||
|
||||
class Partner(http.Controller):
|
||||
|
||||
@http.route('/web/partner/<model("res.partner"):partner>/vcard', type='http', auth="user")
|
||||
def download_vcard(self, partner, **kwargs):
|
||||
content = partner._get_vcard_file()
|
||||
if not content:
|
||||
return request.not_found()
|
||||
return request.make_response(content, [
|
||||
('Content-Type', 'text/vcard'),
|
||||
('Content-Length', len(content)),
|
||||
('Content-Disposition', content_disposition('%s.vcf' % partner.name))
|
||||
])
|
||||
@http.route(['/web_enterprise/partner/<model("res.partner"):partner>/vcard',
|
||||
'/web/partner/vcard'], type='http', auth="user")
|
||||
def download_vcard(self, partner_ids=None, partner=None, **kwargs):
|
||||
if importlib.util.find_spec('vobject') is None:
|
||||
raise UserError('vobject library is not installed')
|
||||
|
||||
if partner_ids:
|
||||
partner_ids = list(filter(None, (int(pid) for pid in partner_ids.split(',') if pid.isdigit())))
|
||||
partners = request.env['res.partner'].browse(partner_ids)
|
||||
if len(partners) > 1:
|
||||
with io.BytesIO() as buffer:
|
||||
with zipfile.ZipFile(buffer, 'w') as zipf:
|
||||
for partner in partners:
|
||||
filename = f"{partner.name or partner.email}.vcf"
|
||||
content = partner._get_vcard_file()
|
||||
zipf.writestr(filename, content)
|
||||
|
||||
return request.make_response(buffer.getvalue(), [
|
||||
('Content-Type', 'application/zip'),
|
||||
('Content-Length', len(content)),
|
||||
('Content-Disposition', content_disposition('Contacts.zip'))
|
||||
])
|
||||
|
||||
if partner or partners:
|
||||
partner = partner or partners
|
||||
content = partner._get_vcard_file()
|
||||
return request.make_response(content, [
|
||||
('Content-Type', 'text/vcard'),
|
||||
('Content-Length', len(content)),
|
||||
('Content-Disposition', content_disposition(f"{partner.name or partner.email}.vcf")),
|
||||
])
|
||||
|
||||
return request.not_found()
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import io
|
||||
import logging
|
||||
import unittest
|
||||
import zipfile
|
||||
from odoo.fields import Command
|
||||
|
||||
from odoo.tests.common import HttpCase, tagged
|
||||
from base64 import b64decode
|
||||
|
||||
from odoo.tools import mute_logger
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
@@ -24,7 +28,7 @@ class TestPartnerVCard(HttpCase):
|
||||
if not vobject:
|
||||
raise unittest.SkipTest("Skip tests when `vobject` Python module is not found.")
|
||||
|
||||
self.partner = self.env['res.partner'].create({
|
||||
self.partners = self.env['res.partner'].create([{
|
||||
'name': 'John Doe',
|
||||
'email': 'john.doe@test.example.com',
|
||||
'mobile': '+1 202 555 0888',
|
||||
@@ -35,25 +39,49 @@ class TestPartnerVCard(HttpCase):
|
||||
'country_id': self.env.ref('base.us').id,
|
||||
'zip': '97648',
|
||||
'website': 'https://test.exemple.com',
|
||||
})
|
||||
}, {
|
||||
'name': 'shut',
|
||||
'email': 'shut@test.example.com',
|
||||
'mobile': '+1 202 555 0999',
|
||||
'phone': '+1 202 555 0123',
|
||||
'function': 'Developer',
|
||||
'street': 'Donutville Maximum-Security Orphanarium',
|
||||
'city': 'Washington DC',
|
||||
'country_id': self.env.ref('base.us').id,
|
||||
'zip': '97649',
|
||||
'website': 'https://test.example.com',
|
||||
}])
|
||||
self.authenticate("admin", "admin")
|
||||
|
||||
def test_fetch_partner_vcard(self):
|
||||
res = self.url_open('/web/partner/%d/vcard' % self.partner.id)
|
||||
vcard = vobject.readOne(res.text)
|
||||
self.assertEqual(vcard.contents["n"][0].value.family, self.partner.name, "Vcard should have the same name")
|
||||
self.assertEqual(vcard.contents["adr"][0].value.street, self.partner.street, "Vcard should have the same street")
|
||||
self.assertEqual(vcard.contents["adr"][0].value.city, self.partner.city, "Vcard should have the same city")
|
||||
self.assertEqual(vcard.contents["adr"][0].value.code, self.partner.zip, "Vcard should have the same zip")
|
||||
def check_vcard_contents(self, vcard, partner):
|
||||
self.assertEqual(vcard.contents["n"][0].value.family, partner.name, "Vcard should have the same name")
|
||||
self.assertEqual(vcard.contents["adr"][0].value.street, partner.street, "Vcard should have the same street")
|
||||
self.assertEqual(vcard.contents["adr"][0].value.city, partner.city, "Vcard should have the same city")
|
||||
self.assertEqual(vcard.contents["adr"][0].value.code, partner.zip, "Vcard should have the same zip")
|
||||
self.assertEqual(vcard.contents["adr"][0].value.country, self.env.ref('base.us').name, "Vcard should have the same country")
|
||||
self.assertEqual(vcard.contents["email"][0].value, self.partner.email, "Vcard should have the same email")
|
||||
self.assertEqual(vcard.contents["url"][0].value, self.partner.website, "Vcard should have the same website")
|
||||
self.assertEqual(vcard.contents["email"][0].value, partner.email, "Vcard should have the same email")
|
||||
self.assertEqual(vcard.contents["url"][0].value, partner.website, "Vcard should have the same website")
|
||||
self.assertEqual(vcard.contents["tel"][0].params['TYPE'], ["work"], "Vcard should have the same phone")
|
||||
self.assertEqual(vcard.contents["tel"][0].value, self.partner.phone, "Vcard should have the same phone")
|
||||
self.assertEqual(vcard.contents["tel"][0].value, partner.phone, "Vcard should have the same phone")
|
||||
self.assertEqual(vcard.contents["tel"][1].params['TYPE'], ["cell"], "Vcard should have the same mobile")
|
||||
self.assertEqual(vcard.contents["tel"][1].value, self.partner.mobile, "Vcard should have the same mobile")
|
||||
self.assertEqual(vcard.contents["title"][0].value, self.partner.function, "Vcard should have the same function")
|
||||
self.assertEqual(len(vcard.contents['photo'][0].value), len(b64decode(self.partner.avatar_512)), "Vcard should have the same photo")
|
||||
self.assertEqual(vcard.contents["tel"][1].value, partner.mobile, "Vcard should have the same mobile")
|
||||
self.assertEqual(vcard.contents["title"][0].value, partner.function, "Vcard should have the same function")
|
||||
self.assertEqual(len(vcard.contents['photo'][0].value), len(b64decode(partner.avatar_512)), "Vcard should have the same photo")
|
||||
|
||||
def test_fetch_single_partner_vcard(self):
|
||||
res = self.url_open('/web_enterprise/partner/%d/vcard' % self.partners[0].id)
|
||||
vcard = vobject.readOne(res.text)
|
||||
self.check_vcard_contents(vcard, self.partners[0])
|
||||
|
||||
def test_fetch_multiple_partners_vcard(self):
|
||||
res = self.url_open('/web/partner/vcard?partner_ids=%s,%s'
|
||||
% (self.partners[0].id, self.partners[1].id))
|
||||
with io.BytesIO(res.content) as buffer:
|
||||
with zipfile.ZipFile(buffer, 'r') as zipf:
|
||||
vcfFileList = zipf.namelist()
|
||||
for i, vcfFile in enumerate(vcfFileList):
|
||||
vcardFile = zipf.read(vcfFile).decode()
|
||||
self.check_vcard_contents(vobject.readOne(vcardFile), self.partners[i])
|
||||
|
||||
@unittest.skip
|
||||
def test_not_exist_partner_vcard(self):
|
||||
@@ -61,3 +89,16 @@ class TestPartnerVCard(HttpCase):
|
||||
self.partner.unlink()
|
||||
res = self.url_open('/web/partner/%d/vcard' % partner_id)
|
||||
self.assertEqual(res.status_code, 404)
|
||||
|
||||
def test_check_partner_access_for_user(self):
|
||||
self.env['res.users'].create({
|
||||
'groups_id': [Command.set([self.env.ref('base.group_public').id])],
|
||||
'name': 'Test User',
|
||||
'login': 'testuser',
|
||||
'password': 'testuser',
|
||||
})
|
||||
self.authenticate('testuser', 'testuser')
|
||||
with mute_logger('odoo.http'): # mute 403 warning
|
||||
res = self.url_open('/web/partner/vcard?partner_ids=%s,%s' %
|
||||
(self.partners[0].id, self.partners[1].id))
|
||||
self.assertEqual(res.status_code, 403)
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
<field name="name">Download (vCard)</field>
|
||||
<field name="model_id" ref="model_res_partner"/>
|
||||
<field name="binding_model_id" ref="model_res_partner"/>
|
||||
<field name="binding_view_types">form</field>
|
||||
<field name="binding_view_types">form,list</field>
|
||||
<field name="state">code</field>
|
||||
<field name="code">
|
||||
action = {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': '/web/partner/%d/vcard' % record.id,
|
||||
'url': '/web/partner/vcard?partner_ids=' + ','.join(map(str, records.ids)),
|
||||
'target': 'download',
|
||||
}
|
||||
</field>
|
||||
|
||||
Reference in New Issue
Block a user