From 56bec1eed7c238c127f3f04fbfab1982fdcb4c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phan=20Sainl=C3=A9ger?= Date: Thu, 20 Apr 2023 21:59:06 +0200 Subject: [PATCH] [IMP] partner_profiles_portal: new portal structures data management refactors the way a portal user can edit the data of the structures he is affiliated with. All main, public and position profiles data are gathered in one "structure" form. Adds several navigation improvement (back to structures list, validation message, ...) --- partner_profiles_portal/__manifest__.py | 6 +- .../controllers/__init__.py | 4 +- ...my_profiles.py => portal_my_structures.py} | 59 ++-- .../controllers/portal_partner_profile.py | 130 ------- .../controllers/portal_structure_profile.py | 198 +++++++++++ partner_profiles_portal/models/res_partner.py | 37 +- .../security/members_security.xml | 21 +- .../views/portal_home_template.xml | 5 +- .../views/portal_my_profiles_template.xml | 97 ------ .../views/portal_my_structures_template.xml | 38 +++ .../views/portal_partner_profile_template.xml | 189 ----------- .../portal_partner_structure_template.xml | 316 ++++++++++++++++++ .../views/res_partner_view.xml | 22 +- 13 files changed, 629 insertions(+), 493 deletions(-) rename partner_profiles_portal/controllers/{portal_my_profiles.py => portal_my_structures.py} (50%) delete mode 100644 partner_profiles_portal/controllers/portal_partner_profile.py create mode 100644 partner_profiles_portal/controllers/portal_structure_profile.py delete mode 100644 partner_profiles_portal/views/portal_my_profiles_template.xml create mode 100644 partner_profiles_portal/views/portal_my_structures_template.xml delete mode 100644 partner_profiles_portal/views/portal_partner_profile_template.xml create mode 100644 partner_profiles_portal/views/portal_partner_structure_template.xml diff --git a/partner_profiles_portal/__manifest__.py b/partner_profiles_portal/__manifest__.py index 57b1583..1842494 100644 --- a/partner_profiles_portal/__manifest__.py +++ b/partner_profiles_portal/__manifest__.py @@ -3,7 +3,7 @@ { "name": "partner_profiles_portal", - "version": "12.0.2.0.0", + "version": "12.0.2.1.0", "author": "Elabore", "website": "https://elabore.coop", "maintainer": "Stéphan Sainléger", @@ -26,8 +26,8 @@ "data": [ "security/members_security.xml", "views/portal_home_template.xml", - "views/portal_my_profiles_template.xml", - "views/portal_partner_profile_template.xml", + "views/portal_my_structures_template.xml", + "views/portal_partner_structure_template.xml", "views/portal_my_account.xml", "views/res_partner_view.xml", ], diff --git a/partner_profiles_portal/controllers/__init__.py b/partner_profiles_portal/controllers/__init__.py index 9866964..dad4392 100644 --- a/partner_profiles_portal/controllers/__init__.py +++ b/partner_profiles_portal/controllers/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from . import portal_my_profiles -from . import portal_partner_profile +from . import portal_my_structures +from . import portal_structure_profile from . import portal_my_account \ No newline at end of file diff --git a/partner_profiles_portal/controllers/portal_my_profiles.py b/partner_profiles_portal/controllers/portal_my_structures.py similarity index 50% rename from partner_profiles_portal/controllers/portal_my_profiles.py rename to partner_profiles_portal/controllers/portal_my_structures.py index 5a7cd65..b6b4897 100644 --- a/partner_profiles_portal/controllers/portal_my_profiles.py +++ b/partner_profiles_portal/controllers/portal_my_structures.py @@ -5,49 +5,38 @@ from odoo.http import request from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager -class CustomerPortalMyProfiles(CustomerPortal): +class CustomerPortalMyStructures(CustomerPortal): - def _get_domain_my_profiles(self, user): + def _get_domain_my_structures(self, user): if user.partner_id.other_contact_ids: - main_profile_ids = user.partner_id.other_contact_ids.filtered( - "edit_structure_main_profile" - ).mapped("parent_id") - public_profile_ids = user.partner_id.other_contact_ids.filtered( - "edit_structure_public_profile" - ).mapped("parent_id.public_profile_id") - return [ - "|", - "|", - ("contact_id", "=", user.partner_id.id), - ("id", "in", main_profile_ids.ids), - ("id", "in", public_profile_ids.ids), + main_structure_ids = user.partner_id.other_contact_ids.mapped("parent_id") + return [("id", "in", main_structure_ids.ids), + ("is_company", "=", True) ] else: - return [("contact_id", "=", user.partner_id.id)] + return None def _prepare_portal_layout_values(self): - values = super(CustomerPortalMyProfiles, self)._prepare_portal_layout_values() - values["profile_count"] = request.env["res.partner"].search_count( - self._get_domain_my_profiles(request.env.user) - ) + values = super(CustomerPortalMyStructures, self)._prepare_portal_layout_values() + domain = self._get_domain_my_structures(request.env.user) + values["structure_count"] = request.env["res.partner"].search_count(domain) if domain else 0 return values @http.route( - ["/my/profiles", "/my/profiles/page/"], + ["/my/structures", "/my/structures/page/"], type="http", auth="user", website=True, ) - def portal_my_profiles( + def portal_my_structures( self, page=1, date_begin=None, date_end=None, sortby=None, **kw ): values = self._prepare_portal_layout_values() - profile = request.env["res.partner"] - domain = self._get_domain_my_profiles(request.env.user) + structure = request.env["res.partner"] + domain = self._get_domain_my_structures(request.env.user) searchbar_sortings = { "name": {"label": _("Name"), "order": "name"}, - "partner_profile": {"label": _("Profile Type"), "order": "partner_profile"}, "parent_id": {"label": _("Company"), "order": "parent_id"}, } if not sortby: @@ -57,35 +46,35 @@ class CustomerPortalMyProfiles(CustomerPortal): # archive groups - Default Group By 'create_date' archive_groups = self._get_archive_groups("res.partner", domain) - # profiles count - profile_count = profile.search_count(domain) + # structures count + structure_count = structure.search_count(domain) if domain else 0 # pager pager = portal_pager( - url="/my/profiles", + url="/my/structures", url_args={"sortby": sortby}, - total=profile_count, + total=structure_count, page=page, step=self._items_per_page, ) # content according to pager and archive selected - profiles = profile.search( + structures = structure.search( domain, order=order, limit=self._items_per_page, offset=pager["offset"], - ) - request.session["my_profiles_history"] = profiles.ids[:100] + ) if domain else None + request.session["my_structures_history"] = structures.ids[:100] if structures else None values.update( { - "profiles": profiles, - "page_name": "profile", + "structures": structures, + "page_name": "structure", "archive_groups": archive_groups, - "default_url": "/my/profiles", + "default_url": "/my/structures", "pager": pager, "searchbar_sortings": searchbar_sortings, "sortby": sortby, } ) - return request.render("partner_profiles_portal.portal_my_profiles", values) \ No newline at end of file + return request.render("partner_profiles_portal.portal_my_structures", values) \ No newline at end of file diff --git a/partner_profiles_portal/controllers/portal_partner_profile.py b/partner_profiles_portal/controllers/portal_partner_profile.py deleted file mode 100644 index 74ffa57..0000000 --- a/partner_profiles_portal/controllers/portal_partner_profile.py +++ /dev/null @@ -1,130 +0,0 @@ -# Copyright 2020 Lokavaluto () -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import http, tools, _ -from odoo.exceptions import AccessError, MissingError -from odoo.http import request -from odoo.addons.portal.controllers.portal import CustomerPortal - - -class CustomerPortalPartnerProfile(CustomerPortal): - - def _profile_get_page_view_values(self, profile, access_token, **kwargs): - values = { - "page_name": "profile", - "profile": profile, - } - return self._get_page_view_values( - profile, access_token, values, "my_profiles_history", False, **kwargs - ) - - def _details_profile_form_validate(self, data, profile_id): - error = dict() - error_message = [] - # nickname uniqueness - if data.get("nickname") and request.env["res.partner"].sudo().search( - [ - ("name", "=", data.get("nickname")), - ("partner_profile.ref", "=", "partner_profile_public"), - ("id", "!=", profile_id), - ] - ): - error["nickname"] = "error" - error_message.append( - _("This nickname is already used, please find an other idea.") - ) - - # email validation - if data.get("email") and not tools.single_email_re.match(data.get("email")): - error["email"] = "error" - error_message.append( - _("Invalid Email! Please enter a valid email address.") - ) - return error, error_message - - def _get_profile_fields(self): - fields = [ - "nickname", - "function", - "phone", - "mobile", - "email", - "website_url", - "street", - "street2", - "city", - "country_id", - "zipcode", - ] - return fields - - def _get_page_saving_values(self, profile, kw): - profile_fields = self._get_profile_fields() - values = {key: kw[key] for key in profile_fields if key in kw} - values.update( - { - "name": values.pop("nickname", profile.name), - "zip": values.pop("zipcode", ""), - "website": values.pop("website_url", ""), - } - ) - return values - - def _get_page_opening_values(self): - # Just retrieve the values to display for Selection fields - countries = request.env["res.country"].sudo().search([]) - values = { - "countries": countries, - } - return values - - @http.route( - ["/my/profile/", "/my/profile/save"], - type="http", - auth="user", - website=True, - ) - def portal_my_profile( - self, profile_id=None, access_token=None, redirect=None, **kw - ): - # The following condition is to transform profile_id to an int, as it is sent as a string from the templace "portal_my_profile" - # TODO: find a better way to retrieve the profile_id at form submit step - if not isinstance(profile_id, int): - profile_id = int(profile_id) - - # Check that the user has the right to see this profile - try: - profile_sudo = self._document_check_access( - "res.partner", profile_id, access_token - ) - except (AccessError, MissingError): - return request.redirect("/my/profiles") - - values = self._profile_get_page_view_values(profile_sudo, access_token, **kw) - values.update( - { - "error": {}, - "error_message": [], - } - ) - if kw and request.httprequest.method == "POST": - # the user has clicked in the Save button to save new data - error, error_message = self._details_profile_form_validate(kw, profile_id) - values.update({"error": error, "error_message": error_message}) - values.update(kw) - if not error: - profile = request.env["res.partner"].browse(profile_id) - values = self._get_page_saving_values(profile, kw) - profile.sudo().write(values) - if redirect: - return request.redirect(redirect) - return request.redirect("/my/profiles") - - # This is just the form page opening. We send all the data needed for the form fields - values.update(self._get_page_opening_values()) - values.update( - { - "profile_id": profile_id, # Sent in order to retrieve it at submit time - "redirect": redirect - } - ) - return request.render("partner_profiles_portal.portal_my_profile", values) diff --git a/partner_profiles_portal/controllers/portal_structure_profile.py b/partner_profiles_portal/controllers/portal_structure_profile.py new file mode 100644 index 0000000..8f5324f --- /dev/null +++ b/partner_profiles_portal/controllers/portal_structure_profile.py @@ -0,0 +1,198 @@ +# Copyright 2020 Lokavaluto () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import base64 +from odoo import http, tools, _ +from odoo.exceptions import AccessError, MissingError +from odoo.http import request +from odoo.addons.portal.controllers.portal import CustomerPortal + + +class CustomerPortalStructureProfile(CustomerPortal): + + def _structure_get_page_view_values(self, structure, access_token, **kwargs): + values = { + "page_name": "structure", + "structure": structure, + } + return self._get_page_view_values( + structure, access_token, values, "my_structures_history", False, **kwargs + ) + + def _details_structure_form_validate(self, data, structure_id): + error = dict() + error_message = [] + # public name uniqueness + if data.get("public_name") and request.env["res.partner"].sudo().search( + [ + ("name", "=", data.get("public_name")), + ("is_public_profile", "=", True), + ("contact_id", "!=", structure_id), + ] + ): + error["public_name"] = "error" + error_message.append( + _("This public name is already used, please find an other idea.") + ) + + # email validation + if data.get("email") and not tools.single_email_re.match(data.get("email")): + error["email"] = "error" + error_message.append( + _("Invalid Email! Please enter a valid email address.") + ) + return error, error_message + + def _get_main_profile_fields(self): + '''Provides all the fields that must fill the structure's main profile. + All of them MUST start with "main_".''' + fields = [ + "main_name", + "main_street", + "main_street2", + "main_zip", + "main_city", + "main_country_id", + "main_phone", + "main_mobile", + "main_email", + "main_website", + ] + return fields + + def _get_public_profile_fields(self): + '''Provides all the fields that must fill the structure's public profile. + All of them MUST start with "public_".''' + fields = [ + "public_name", + "public_street2", + "public_street", + "public_zip", + "public_city", + "public_phone", + "public_mobile", + "public_email", + "public_website", + ] + return fields + + def _get_position_profile_fields(self): + '''Provides all the fields that must fill the structure's position profile of the user. + All of them MUST start with "position_".''' + fields = [ + "position_function", + "position_phone", + "position_email", + ] + return fields + + def _transform_in_res_partner_fields(self, kw, profile_fields, prefix=""): + '''Transforms kw's values in res_partner fields and values''' + return {key[len(prefix):]: kw[key] for key in profile_fields if key in kw} + + def _get_page_saving_main_values(self, kw): + profile_fields = self._get_main_profile_fields() + values = self._transform_in_res_partner_fields(kw, profile_fields, "main_") + if 'logo' in kw: + image = kw.get('logo') + if image: + image = image.read() + image = base64.b64encode(image) + values.update({ + 'image': image + }) + return values + + def _get_page_saving_public_values(self, kw): + profile_fields = self._get_public_profile_fields() + values = self._transform_in_res_partner_fields(kw, profile_fields, "public_") + return values + + def _get_page_saving_position_values(self, kw): + profile_fields = self._get_position_profile_fields() + values = self._transform_in_res_partner_fields(kw, profile_fields, "position_") + return values + + def _get_page_opening_values(self): + # Just retrieve the values to display for Selection fields + countries = request.env["res.country"].sudo().search([]) + values = { + "countries": countries, + } + return values + + @http.route( + ["/my/structure/", "/my/structure/save"], + type="http", + auth="user", + website=True, + ) + def portal_my_structure( + self,structure_id=None, access_token=None, redirect=None, **kw + ): + # The following condition is to transform profile_id to an int, as it is sent as a string from the templace "portal_my_profile" + # TODO: find a better way to retrieve the profile_id at form submit step + if not isinstance(structure_id, int): + structure_id = int(structure_id) + + # Check that the user has the right to see this profile + try: + structure_sudo = self._document_check_access( + "res.partner", structure_id, access_token + ) + except (AccessError, MissingError): + return request.redirect("/my/structures") + + Partner = request.env["res.partner"] + partner_id = request.env.user.partner_id + main_profile = Partner.browse(structure_id) + public_profile = Partner.browse(main_profile.public_profile_id.id) + position_profile = Partner.search( + [ + ("parent_id", "=", structure_id), + ("contact_id", "=", partner_id.id), + ("is_position_profile", "=", True), + ("active", "=", True) + ], + limit=1 + )[0] + + values = self._structure_get_page_view_values(structure_sudo, access_token, **kw) + values.update( + { + "error": {}, + "error_message": [], + } + ) + if kw and request.httprequest.method == "POST": + # the user has clicked in the Save button to save new data + error, error_message = self._details_structure_form_validate(kw, structure_id) + values.update({"error": error, "error_message": error_message}) + values.update(kw) + if not error: + # Update main profile + new_values = self._get_page_saving_main_values(kw) + main_profile.sudo().write(new_values) + # Update public profile + new_values = self._get_page_saving_public_values(kw) + public_profile.sudo().write(new_values) + # Update position profile + new_values = self._get_page_saving_position_values(kw) + position_profile.sudo().write(new_values) + # End of updates + if redirect: + return request.redirect(redirect) + return request.redirect("/my/structures") + + # This is just the form page opening. We send all the data needed for the form fields + can_edit_structure = partner_id in main_profile.can_edit_structure_profiles_ids + values.update(self._get_page_opening_values()) + values.update( + { + "structure_id": structure_id, # Sent in order to retrieve it at submit time + "public_profile": public_profile, + "position_profile": position_profile, + "can_edit_structure": can_edit_structure, + "redirect": "/my/structure/" + str(structure_id) + "?success=True" + } + ) + return request.render("partner_profiles_portal.portal_structure", values) diff --git a/partner_profiles_portal/models/res_partner.py b/partner_profiles_portal/models/res_partner.py index 3eb9777..7173cc9 100644 --- a/partner_profiles_portal/models/res_partner.py +++ b/partner_profiles_portal/models/res_partner.py @@ -10,44 +10,37 @@ _logger = logging.getLogger(__name__) class res_partner(models.Model): _inherit = "res.partner" - edit_structure_main_profile = fields.Boolean( - string=_("Manage structure's main profile") + edit_structure_profiles = fields.Boolean( + string="Manage structure's profiles" ) - edit_structure_public_profile = fields.Boolean( - string=_("Manage structure's public profile") - ) - can_edit_main_profile_ids = fields.Many2many( + can_edit_structure_profiles_ids = fields.Many2many( "res.partner", relation="res_partner_main_profile_rel", column1="partner_id", column2="profile_id", store=True, - compute="_compute_can_edit", - string="Can edit main profile", + compute="_compute_can_read_edit", + string="Can edit struture profiles", ) - can_edit_public_profile_ids = fields.Many2many( + child_main_contact_ids = fields.Many2many( "res.partner", - relation="res_partner_public_profile_rel", + relation="res_partner_child_contacts_rel", column1="partner_id", column2="profile_id", store=True, - compute="_compute_can_edit", - string="Can edit public profile", + compute="_compute_can_read_edit", + string="Can read structure profiles", ) @api.depends( "other_contact_ids", - "other_contact_ids.edit_structure_main_profile", - "other_contact_ids.edit_structure_public_profile", + "other_contact_ids.edit_structure_profiles", "child_ids", - "child_ids.edit_structure_main_profile", - "child_ids.edit_structure_public_profile", + "child_ids.edit_structure_profiles", ) - def _compute_can_edit(self): + def _compute_can_read_edit(self): for partner in self: - partner.can_edit_main_profile_ids = partner.child_ids.filtered( - "edit_structure_main_profile" + partner.can_edit_structure_profiles_ids = partner.child_ids.filtered( + "edit_structure_profiles" ).mapped("contact_id") - partner.can_edit_public_profile_ids = partner.child_ids.filtered( - "edit_structure_public_profile" - ).mapped("contact_id") \ No newline at end of file + partner.child_main_contact_ids = partner.child_ids.mapped("contact_id") \ No newline at end of file diff --git a/partner_profiles_portal/security/members_security.xml b/partner_profiles_portal/security/members_security.xml index a7267cf..a7a14f3 100644 --- a/partner_profiles_portal/security/members_security.xml +++ b/partner_profiles_portal/security/members_security.xml @@ -1,11 +1,24 @@ - - res_partner: portal: read/write access on my profiles + + res_partner: portal: read access on my structures ['|','|',('contact_id', '=', user.partner_id.id), - ('can_edit_main_profile_ids', 'in', [user.partner_id.id]), - ('contact_id.can_edit_public_profile_ids', 'in', [user.partner_id.id])] + ('child_main_contact_ids', 'in', [user.partner_id.id]), + ('contact_id.child_main_contact_ids', 'in', [user.partner_id.id])] + + + + + + + + + res_partner: portal: write access on my structures + + ['|','|',('contact_id', '=', user.partner_id.id), + ('can_edit_structure_profiles_ids', 'in', [user.partner_id.id]), + ('contact_id.can_edit_structure_profiles_ids', 'in', [user.partner_id.id])] diff --git a/partner_profiles_portal/views/portal_home_template.xml b/partner_profiles_portal/views/portal_home_template.xml index 84e09bc..a37c3fd 100644 --- a/partner_profiles_portal/views/portal_home_template.xml +++ b/partner_profiles_portal/views/portal_home_template.xml @@ -14,9 +14,10 @@ My information - +
+
diff --git a/partner_profiles_portal/views/portal_my_profiles_template.xml b/partner_profiles_portal/views/portal_my_profiles_template.xml deleted file mode 100644 index cc6f84f..0000000 --- a/partner_profiles_portal/views/portal_my_profiles_template.xml +++ /dev/null @@ -1,97 +0,0 @@ - - - - \ No newline at end of file diff --git a/partner_profiles_portal/views/portal_my_structures_template.xml b/partner_profiles_portal/views/portal_my_structures_template.xml new file mode 100644 index 0000000..f99c0cd --- /dev/null +++ b/partner_profiles_portal/views/portal_my_structures_template.xml @@ -0,0 +1,38 @@ + + + + \ No newline at end of file diff --git a/partner_profiles_portal/views/portal_partner_profile_template.xml b/partner_profiles_portal/views/portal_partner_profile_template.xml deleted file mode 100644 index 298136d..0000000 --- a/partner_profiles_portal/views/portal_partner_profile_template.xml +++ /dev/null @@ -1,189 +0,0 @@ - - - - \ No newline at end of file diff --git a/partner_profiles_portal/views/portal_partner_structure_template.xml b/partner_profiles_portal/views/portal_partner_structure_template.xml new file mode 100644 index 0000000..9ff5fcc --- /dev/null +++ b/partner_profiles_portal/views/portal_partner_structure_template.xml @@ -0,0 +1,316 @@ + + + + \ No newline at end of file diff --git a/partner_profiles_portal/views/res_partner_view.xml b/partner_profiles_portal/views/res_partner_view.xml index 0904fc1..51c98f0 100644 --- a/partner_profiles_portal/views/res_partner_view.xml +++ b/partner_profiles_portal/views/res_partner_view.xml @@ -9,22 +9,26 @@ - - - + + - - - + + - - - + +