[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:
Shubham Thanki
2023-09-18 10:14:06 +00:00
parent fd1f6e1621
commit 66e8e65682
3 changed files with 96 additions and 27 deletions
+38 -10
View File
@@ -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()
+56 -15
View File
@@ -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)
+2 -2
View File
@@ -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>