diff --git a/eslint.config.cjs b/eslint.config.cjs index dd0cbe0a..9af8a8bc 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -168,7 +168,9 @@ const config = [{ settings: { jsdoc: { + definedTags: ["odoo-module"], tagNamePreference: { + "odoo-module": "odoo-module", arg: "param", argument: "param", augments: "extends", @@ -194,7 +196,7 @@ const config = [{ }, }, { - files: ["**/*.esm.js", "**/*test.js"], + files: ["**/*.esm.js", "**/*test.js", "**/static/src/**/*.js"], languageOptions: { ecmaVersion: 2024, diff --git a/pyproject.toml b/pyproject.toml index 5f40a503..96068cd7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,6 +100,7 @@ dev = [ [tool.uv.sources] # Use fix updating account move line update membership line odoo = { git = "https://github.com/OCA/OCB", rev = "refs/pull/1319/head" } +odoo-addon-apps_product_creator = { git = "https://github.com/Therp/apps-store",branch = "18.0-mig-apps_product_creator",subdirectory = "apps_product_creator"} # unreleased dependency, add it to test-requirements.txt to let the # test pipeline to use it as well diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..8f9d9a12 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +# generated from manifests external_dependencies +responses diff --git a/test-requirements.txt b/test-requirements.txt index caae7abe..989b9ae7 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,3 +5,4 @@ odoo-addon-membership-delegated-partner-line @ git+https://github.com/gfcapalbo/ odoo-addon-github_connector_oca @ git+https://github.com/Therp/interface-github@18.0-mig-github_connector_oca#subdirectory=github_connector_oca odoo-addon-survey_xlsx @ git+https://github.com/ByteMeAsap/survey@18.0-mig-survey_xlsx#subdirectory=survey_xlsx odoo-addon-account_statement_import_online_wise @ git+https://github.com/Therp/bank-statement-import@18.0-mig-account-statement-import-online-wise#subdirectory=account_statement_import_online_wise +odoo-addon-apps_product_creator @ git+https://github.com/Therp/apps-store.git@18.0-mig-apps_product_creator#subdirectory=apps_product_creator diff --git a/website_oca_integrator/README.rst b/website_oca_integrator/README.rst new file mode 100644 index 00000000..702afbf0 --- /dev/null +++ b/website_oca_integrator/README.rst @@ -0,0 +1,81 @@ +====================== +Website OCA Integrator +====================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:fcb7b4943ae23d9f09e59e845e8a0161143b00b3d647a7e1bbe423eeae04be82 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Foca--custom-lightgray.png?logo=github + :target: https://github.com/OCA/oca-custom/tree/18.0/website_oca_integrator + :alt: OCA/oca-custom +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/oca-custom-18-0/oca-custom-18-0-website_oca_integrator + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/oca-custom&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds menu "Integrators" in website, which displays +integrators, contributors, members and modules related information. + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Surekha Technologies + +Contributors +------------ + +- `Tecnativa `__: + + - Víctor M.M. Torres + - Carlos Roca + - Ernesto Tejeda + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/oca-custom `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/website_oca_integrator/__init__.py b/website_oca_integrator/__init__.py new file mode 100644 index 00000000..91c5580f --- /dev/null +++ b/website_oca_integrator/__init__.py @@ -0,0 +1,2 @@ +from . import controllers +from . import models diff --git a/website_oca_integrator/__manifest__.py b/website_oca_integrator/__manifest__.py new file mode 100644 index 00000000..55a9e968 --- /dev/null +++ b/website_oca_integrator/__manifest__.py @@ -0,0 +1,45 @@ +# Copyright 2018 Surekha Technologies (https://www.surekhatech.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + "name": "Website OCA Integrator", + "summary": "Displays Integrators in website.", + "version": "18.0.1.0.1", + "category": "Website", + "license": "AGPL-3", + "website": "https://github.com/OCA/oca-custom", + "author": "Odoo Community Association (OCA), Surekha Technologies", + "depends": [ + "base", + "website", + "web_tour", + "website_crm_partner_assign", + "website_sale", + "membership", + "website_membership", + "website_customer", + "github_connector", + "github_connector_odoo", + "apps_product_creator", + ], + "data": [ + "security/ir.model.access.csv", + "views/website_oca_integrator_templates.xml", + "views/website_oca_integrator_contributor_templates.xml", + "views/view_portal_templates.xml", + "views/website_oca_integrator_data.xml", + "views/view_res_partner.xml", + "views/view_odoo_author.xml", + "data/ir_cron.xml", + ], + "external_dependencies": {"python": ["responses"]}, + "assets": { + "web.assets_frontend": [ + "website_oca_integrator/static/src/js/integrator_portal.js", + "website_oca_integrator/static/src/scss/website_oca_integrator.scss", + ], + "web.assets_tests": [ + "website_oca_integrator/static/src/js/integrator_portal_tour.js", + ], + }, + "installable": True, +} diff --git a/website_oca_integrator/controllers/__init__.py b/website_oca_integrator/controllers/__init__.py new file mode 100644 index 00000000..ac0444cc --- /dev/null +++ b/website_oca_integrator/controllers/__init__.py @@ -0,0 +1,3 @@ +from . import main +from . import member +from . import portal diff --git a/website_oca_integrator/controllers/main.py b/website_oca_integrator/controllers/main.py new file mode 100644 index 00000000..34a21e02 --- /dev/null +++ b/website_oca_integrator/controllers/main.py @@ -0,0 +1,346 @@ +# Copyright 2018 Surekha Technologies (https://www.surekhatech.com) +# Part of Odoo. See Odoo LICENSE file for full copyright and licensing details. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from werkzeug.urls import url_encode + +from odoo import http +from odoo.http import request +from odoo.tools.translate import _ + + +class WebsiteIntegrator(http.Controller): + _references_per_page = 40 + + @http.route( + [ + "/integrators", + "/integrators/page/", + '/integrators/country/', + '/integrators/country//page/', + ], + type="http", + auth="public", + website=True, + ) + def integrators(self, country=None, page=0, **post): + """ + Display integrators in website. + """ + country_all = post.pop("country_all", False) + partner_obj = request.env["res.partner"] + search = post.get("search", "") + + # search integrators + base_integrator_domain = [ + ("is_company", "=", True), + ("is_integrator", "=", True), + ("website_published", "=", True), + ] + + if search: + base_integrator_domain += [ + "|", + ("name", "ilike", search), + ("website_description", "ilike", search), + ] + + # group by country + country_domain = list(base_integrator_domain) + countries = partner_obj.sudo().read_group( + country_domain, + ["id", "country_id"], + groupby="country_id", + orderby="country_id", + ) + countries_partners = partner_obj.sudo().search_count(country_domain) + + # flag active country + for country_dict in countries: + country_dict["active"] = ( + country + and country_dict["country_id"] + and country_dict["country_id"][0] == country.id + ) + countries.insert( + 0, + { + "country_id_count": countries_partners, + "country_id": (0, _("All Countries")), + "active": bool(country is None), + }, + ) + + if country: + base_integrator_domain += [("country_id", "=", country.id)] + url = "/integrators/country/" + request.env["ir.http"]._slug(country) + else: + url = "/integrators" + + url_args = {} + if search: + url_args["search"] = search + if country_all: + url_args["country_all"] = True + + partner_count = partner_obj.sudo().search_count(base_integrator_domain) + pager = request.website.pager( + url=url, + total=partner_count, + page=page, + step=self._references_per_page, + scope=7, + url_args=url_args, + ) + + # search integrators matching current search parameters + integrator_ids = partner_obj.sudo().search( + base_integrator_domain, + order="grade_id ASC, implemented_partner_count DESC," + "contributor_count DESC, member_count DESC," + "name ASC", + offset=pager["offset"], + limit=self._references_per_page, + ) + integrators = integrator_ids.sudo() + + google_map_integrator_ids = ",".join(map(str, [i.id for i in integrators])) + google_maps_api_key = ( + request.env["ir.config_parameter"].sudo().get_param("google_maps_api_key") + ) + + values = { + "countries": countries, + "current_country": country, + "integrators": integrators, + "google_map_integrator_ids": google_map_integrator_ids, + "pager": pager, + "searches": post, + "search_path": f"?{url_encode(post)}", + "google_maps_api_key": google_maps_api_key, + } + + return request.render( + "website_oca_integrator.integrator_index", + values, + status=integrators and 200 or 404, + ) + + def get_integrator_modules_list(self, integrator): + """ + Returns 5 favourite modules selected by integrator. If integrator has + not selected 5 modules, then returns latest 5 developed modules. + """ + module_display_count = 5 + favourite_modules = integrator.favourite_module_ids.filtered("is_published") + developed_modules = integrator.developed_module_ids.filtered("is_published") + favourite_module_count = len(favourite_modules) + developed_module_count = len(developed_modules) + + remaining_modules = module_display_count - favourite_module_count + + # if integrator has developed less than 5 modules then + # remaining modules are just other than favourite modules. + if developed_module_count < module_display_count: + remaining_modules = developed_module_count - favourite_module_count + + if remaining_modules: + remaining_product_tmpl_ids = list( + set(developed_modules.ids) - set(favourite_modules.ids) + ) + # search latest product variant of module. + sorted_modules = request.env["product.product"].search( + [("product_tmpl_id", "in", remaining_product_tmpl_ids)], + order="create_date desc", + ) + + sorted_modules = sorted_modules.mapped("product_tmpl_id")[ + :remaining_modules + ] + + favourite_modules += sorted_modules + + return favourite_modules, developed_module_count + + def get_integrator_references(self, integrator): + # sort integrator references by implemented date. + references = integrator.implemented_partner_ids.sorted( + key=lambda r: r.implemented_date or "", reverse=True + ) + + references = list(filter(lambda x: x.website_published is True, references)) + + return references + + @http.route( + ["/integrators/"], type="http", auth="public", website=True + ) + def integrators_detail(self, integrator_id, **post): + """ + Display integrator's detail. + """ + _, integrator_id = request.env["ir.http"]._unslug(integrator_id) + current_country = None + country_id = post.get("country_id") + + if country_id: + current_country = ( + request.env["res.country"].browse(int(country_id)).exists() + ) + if integrator_id: + integrator = request.env["res.partner"].sudo().browse(integrator_id) + + is_website_publisher = request.env.user.has_group( + "website.group_website_publisher" + ) + + if integrator.sudo().exists() and ( + integrator.website_published or is_website_publisher + ): + modules_list, developed_module_count = self.get_integrator_modules_list( + integrator + ) + + references = self.get_integrator_references(integrator) + + sponsorship_lines = integrator.sponsorship_line_ids.sorted( + key=lambda r: r.date_end, reverse=True + )[:5] + + display_all_modules = True if developed_module_count > 5 else False + + values = { + "main_object": integrator, + "integrator": integrator, + "current_country": current_country, + "references": references, + "modules_list": modules_list, + "sponsorship_lines": sponsorship_lines, + "display_all_modules": display_all_modules, + } + return request.render("website_oca_integrator.integrators", values) + return self.integrators(**post) + + @http.route( + [ + "/integrators//contributors/country/", + "/integrators//contributors/country//page/", + "/integrators//contributors", + "/integrators//contributors/page/", + "/integrators//contributors/country/", + "/integrators//contributors/country/" + "/page/", + "/integrators//contributors/country/" + "-", + "/integrators//contributors/country/" + "-/page/", + ], + type="http", + auth="public", + website=True, + ) + def integrator_contributors( + self, + integrator_id=None, + country=None, + country_name=None, + country_id=0, + page=1, + **post, + ): + integrator = integrator_id + integrator_name, integrator_id = request.env["ir.http"]._unslug(integrator_id) + country = request.env["res.country"] + partner = request.env["res.partner"] + + post_name = post.get("search") or post.get("name", "") + current_country = None + + country_domain = [ + "|", + ("membership_state", "=", "paid"), + ("github_name", "!=", False), + ("website_published", "=", True), + ("parent_id", "=", integrator_id), + ] + + if country: + country_id = country.id + country_domain += [ + "|", + ("name", "ilike", post_name), + ("website_description", "ilike", post_name), + ] + + countries = partner.sudo().read_group( + country_domain, + ["id", "country_id"], + groupby="country_id", + orderby="country_id", + ) + + countries_total = sum( + country_dict["country_id_count"] for country_dict in countries + ) + + if country_id: + country_domain += [("country_id", "=", country_id)] + current_country = country.browse(country_id).read(["id", "name"])[0] + if not any( + x["country_id"][0] == country_id for x in countries if x["country_id"] + ): + countries.append( + { + "country_id_count": 0, + "country_id": (country_id, current_country["name"]), + } + ) + + countries = [d for d in countries if d["country_id"]] + countries.sort(key=lambda d: d["country_id"][1]) + + countries.insert( + 0, + { + "country_id_count": countries_total, + "country_id": (0, _("All Countries")), + }, + ) + + base_url = "/integrators/{}/contributors{}".format( + integrator, + f"/country/{country_id}" if country_id else "", + ) + + contributors_count = partner.sudo().search_count(country_domain) + + pager = request.website.pager( + url=base_url, + total=contributors_count, + page=page, + step=self._references_per_page, + scope=7, + url_args=post, + ) + + contributors = partner.sudo().search( + country_domain, + order="name ASC", + offset=pager["offset"], + limit=self._references_per_page, + ) + + values = { + "contributors": contributors, + "integrator": integrator, + "countries": countries, + "current_country": current_country + and [current_country["id"], current_country["name"]] + or None, + "current_country_id": current_country and current_country["id"] or 0, + "pager": pager, + "post": post, + "search": f"?{url_encode(post)}", + } + + return request.render("website_oca_integrator.contributor_index", values) diff --git a/website_oca_integrator/controllers/member.py b/website_oca_integrator/controllers/member.py new file mode 100644 index 00000000..c21ed0cc --- /dev/null +++ b/website_oca_integrator/controllers/member.py @@ -0,0 +1,23 @@ +# Copyright 2018 Surekha Technologies (https://www.surekhatech.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import http +from odoo.http import request + +from odoo.addons.website_membership.controllers.main import WebsiteMembership + + +class WebsiteContributorMembership(WebsiteMembership): + @http.route() + def partners_detail(self, partner_id, **post): + """ + Display contributor/member page. + """ + response = super().partners_detail(partner_id, **post) + + # if contributor/member exist then render page + # else redirect it to contributor/member list page. + if response.qcontext.get("partner", False): + return request.render("website_oca_integrator.members", response.qcontext) + else: + return response diff --git a/website_oca_integrator/controllers/portal.py b/website_oca_integrator/controllers/portal.py new file mode 100644 index 00000000..d448766d --- /dev/null +++ b/website_oca_integrator/controllers/portal.py @@ -0,0 +1,66 @@ +# Copyright 2018 Surekha Technologies (https://www.surekhatech.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import http +from odoo.http import request, route + +from odoo.addons.portal.controllers.portal import CustomerPortal + + +class IntegratorPortal(CustomerPortal): + @route() + def account(self, redirect=None, **post): + if post: + modules = request.httprequest.form.getlist("favourite_module_ids") + if not modules: + modules = post.get("favourite_module_ids") + if modules: + modules = modules.split(",") + if modules: + post["favourite_module_ids"] = [int(id) for id in modules] + else: + post["favourite_module_ids"] = False + return super().account(redirect=redirect, **post) + + @http.route( + "/my/account/get_developed_modules", + type="http", + auth="user", + methods=["GET"], + website=True, + sitemap=False, + ) + def integrator_developed_module_read(self, query="", limit=25, **post): + integrator = request.env.user.partner_id + modules_data = request.env["product.template"].search_read( + [ + ("id", "in", integrator.developed_module_ids.ids), + ("name", "=ilike", "%" + (query or "") + "%"), + ], + fields=["id", "name"], + limit=int(limit), + ) + return request.make_json_response(modules_data) + + @http.route( + "/my/account/get_favourite_modules", + type="http", + auth="user", + methods=["GET"], + website=True, + sitemap=False, + ) + def integrator_favourite_module_read(self): + integrator = request.env.user.partner_id + modules = integrator.favourite_module_ids + modules_data = [{"id": m.id, "name": m.name} for m in modules] + return request.make_json_response(modules_data) + + def details_form_validate(self, data): + # after adding HTML editor in portal page, if we click on + # 'Confirm' button then, 'files' key is passed in post data. + # this field does not exist in 'res.partner'. removed this + # key to prevent an error of "Unknown field 'files'". + data.pop("files", False) + error, error_message = super().details_form_validate(data) + return error, error_message diff --git a/website_oca_integrator/data/ir_cron.xml b/website_oca_integrator/data/ir_cron.xml new file mode 100644 index 00000000..2bd4cc32 --- /dev/null +++ b/website_oca_integrator/data/ir_cron.xml @@ -0,0 +1,17 @@ + + + + + Synchronize Contributor Modules + 1 + + + days + code + + model.cron_create_github_user_module() + + diff --git a/website_oca_integrator/i18n/website_oca_integrator.pot b/website_oca_integrator/i18n/website_oca_integrator.pot new file mode 100644 index 00000000..203bd912 --- /dev/null +++ b/website_oca_integrator/i18n/website_oca_integrator.pot @@ -0,0 +1,378 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * website_oca_integrator +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: website_oca_integrator +#: code:addons/website_oca_integrator/controllers/main.py:0 +#: code:addons/website_oca_integrator/controllers/main.py:0 +#, python-format +msgid "All Countries" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_partner__author_ids +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_users__author_ids +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.view_res_partner_form_inherit +msgid "Authors" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.view_res_partner_form_inherit +msgid "Authors & Modules" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrators +msgid "Click here" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_odoo_author__partner_id +msgid "Company" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model,name:website_oca_integrator.model_res_partner +msgid "Contact" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_partner__contributor_module_line_ids +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_users__contributor_module_line_ids +msgid "Contributed Modules" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model,name:website_oca_integrator.model_contributor_module_line +msgid "Contributor Line" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_contributor_module_line__create_uid +#: model:ir.model.fields,field_description:website_oca_integrator.field_sponsorship_line__create_uid +msgid "Created by" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_contributor_module_line__create_date +#: model:ir.model.fields,field_description:website_oca_integrator.field_sponsorship_line__create_date +msgid "Created on" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_partner__developed_module_ids +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_users__developed_module_ids +msgid "Developed Modules" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_contributor_module_line__display_name +#: model:ir.model.fields,field_description:website_oca_integrator.field_odoo_author__display_name +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_partner__display_name +#: model:ir.model.fields,field_description:website_oca_integrator.field_sponsorship_line__display_name +msgid "Display Name" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_sponsorship_line__date_end +msgid "End Date" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_partner__favourite_module_ids +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_users__favourite_module_ids +msgid "Favourite Modules" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrator_index +msgid "Filter by Country" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.website_portal_integrator_details +msgid "Github Login" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_partner__github_organization +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_users__github_organization +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.view_res_partner_form_inherit +msgid "Github Organization" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_partner__github_organization_url +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_users__github_organization_url +msgid "Github Organization URL" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_contributor_module_line__id +#: model:ir.model.fields,field_description:website_oca_integrator.field_odoo_author__id +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_partner__id +#: model:ir.model.fields,field_description:website_oca_integrator.field_sponsorship_line__id +msgid "ID" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_partner__implemented_date +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_users__implemented_date +msgid "Implemented Date" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_partner__is_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_users__is_integrator +msgid "Integrator" +msgstr "" + +#. module: website_oca_integrator +#: model:website.menu,name:website_oca_integrator.menu_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrator_footer +msgid "Integrators" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_sponsorship_line__date_from +msgid "Join Date" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_contributor_module_line____last_update +#: model:ir.model.fields,field_description:website_oca_integrator.field_odoo_author____last_update +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_partner____last_update +#: model:ir.model.fields,field_description:website_oca_integrator.field_sponsorship_line____last_update +msgid "Last Modified on" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_contributor_module_line__write_uid +#: model:ir.model.fields,field_description:website_oca_integrator.field_sponsorship_line__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_contributor_module_line__write_date +#: model:ir.model.fields,field_description:website_oca_integrator.field_sponsorship_line__write_date +msgid "Last Updated on" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.contributor_modules_list +msgid "Latest Contributed Modules" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_sponsorship_line__grade_id +msgid "Level" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.contributor_index +msgid "Location" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.members +msgid "Membership Details" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_contributor_module_line__date_pr_merged +msgid "Merged date of PR" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.view_res_partner_form_inherit +msgid "Module Information" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrator_modules_list +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.website_portal_integrator_details +msgid "Modules" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.website_portal_integrator_details +msgid "Modules..." +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrator_index +msgid "No result found" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.contributor_index +msgid "No result found." +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_partner__contributor_count +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_users__contributor_count +msgid "Number of contributors" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_partner__member_count +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_users__member_count +msgid "Number of members" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_partner__module_count +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_users__module_count +msgid "Number of modules" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.contributor_index +msgid "OCA Contributors" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrator_layout +msgid "OCA Integrators" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model,name:website_oca_integrator.model_odoo_author +msgid "Odoo Author" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_contributor_module_line__product_template_id +msgid "Odoo Module" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrator_index +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrators +msgid "Our Integrators" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_contributor_module_line__partner_id +#: model:ir.model.fields,field_description:website_oca_integrator.field_sponsorship_line__partner_id +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrator_grade_detail +msgid "Partner" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrator_references_block +msgid "References" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrator_index +msgid "Search" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,help:website_oca_integrator.field_odoo_author__partner_id +msgid "Select company which is linked to this author." +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.view_crm_partner_geo_form_inherit +msgid "Sponsorship" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_partner__sponsorship_line_ids +#: model:ir.model.fields,field_description:website_oca_integrator.field_res_users__sponsorship_line_ids +msgid "Sponsorship Activities" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrators +msgid "Sponsorship Details" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model,name:website_oca_integrator.model_sponsorship_line +msgid "Sponsorship Line" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.model.fields,field_description:website_oca_integrator.field_sponsorship_line__sponsorship_id +msgid "Sponsorship Product" +msgstr "" + +#. module: website_oca_integrator +#: model:ir.actions.server,name:website_oca_integrator.cron_update_contributor_modules_ir_actions_server +#: model:ir.cron,cron_name:website_oca_integrator.cron_update_contributor_modules +#: model:ir.cron,name:website_oca_integrator.cron_update_contributor_modules +msgid "Synchronize Contributor Modules" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrator_modules_list +msgid "" +"View\n" +" All Modules" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.website_portal_integrator_details +msgid "Website Description" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.website_portal_integrator_details +msgid "Website Short Description" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrator_index +msgid "Which organizations support the OCA?" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrator_world_map +msgid "World Map" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrator_index +msgid "contributor(s)" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrator_index +msgid "member(s)" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrator_index +msgid "module(s)" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.contributor_index +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrator_index +msgid "pull-left" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrator_index +msgid "reference(s)" +msgstr "" + +#. module: website_oca_integrator +#: model_terms:ir.ui.view,arch_db:website_oca_integrator.integrators +msgid "to view Contributors/Members" +msgstr "" diff --git a/website_oca_integrator/models/__init__.py b/website_oca_integrator/models/__init__.py new file mode 100644 index 00000000..48879143 --- /dev/null +++ b/website_oca_integrator/models/__init__.py @@ -0,0 +1,4 @@ +from . import contributor_module_line +from . import odoo_author +from . import res_partner +from . import sponsorship diff --git a/website_oca_integrator/models/contributor_module_line.py b/website_oca_integrator/models/contributor_module_line.py new file mode 100644 index 00000000..1beec541 --- /dev/null +++ b/website_oca_integrator/models/contributor_module_line.py @@ -0,0 +1,19 @@ +# Copyright 2018 Surekha Technologies (https://www.surekhatech.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class ContributorModuleLine(models.Model): + _name = "contributor.module.line" + _description = "Contributor Line" + + product_template_id = fields.Many2one( + string="Odoo Module", comodel_name="product.template", required=True + ) + + partner_id = fields.Many2one( + comodel_name="res.partner", string="Partner", required=True + ) + + date_pr_merged = fields.Datetime(string="Merged date of PR", required=True) diff --git a/website_oca_integrator/models/odoo_author.py b/website_oca_integrator/models/odoo_author.py new file mode 100644 index 00000000..4af48427 --- /dev/null +++ b/website_oca_integrator/models/odoo_author.py @@ -0,0 +1,15 @@ +# Copyright 2018 Surekha Technologies (https://www.surekhatech.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class OdooAuthor(models.Model): + _inherit = "odoo.author" + + partner_id = fields.Many2one( + comodel_name="res.partner", + string="Company", + domain="[('is_company','=', True),('website_published', '=', True)]", + help="Select company which is linked to this author.", + ) diff --git a/website_oca_integrator/models/res_partner.py b/website_oca_integrator/models/res_partner.py new file mode 100644 index 00000000..f5bc797f --- /dev/null +++ b/website_oca_integrator/models/res_partner.py @@ -0,0 +1,321 @@ +# Copyright 2018 Surekha Technologies (https://www.surekhatech.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import logging + +from odoo import api, fields, models + +_logger = logging.getLogger(__name__) + + +class ResPartner(models.Model): + _inherit = "res.partner" + + github_organization = fields.Char() + + github_organization_url = fields.Char( + string="Github Organization URL", + compute="_compute_github_organization_url", + store=True, + ) + + is_integrator = fields.Boolean( + string="Integrator", + compute="_compute_integrator", + store=True, + ) + + contributor_count = fields.Integer( + string="Number of contributors", + compute="_compute_contributor_count", + store=True, + ) + + member_count = fields.Integer( + string="Number of members", + compute="_compute_member_count", + store=True, + ) + + module_count = fields.Integer( + string="Number of modules", + compute="_compute_module_count", + store=True, + ) + + implemented_date = fields.Date() + + author_ids = fields.One2many( + comodel_name="odoo.author", + string="Authors", + inverse_name="partner_id", + readonly=True, + ) + + developed_module_ids = fields.Many2many( + string="Developed Modules", + comodel_name="product.template", + relation="product_module_res_partner_rel", + column1="partner_id", + column2="product_template_id", + compute="_compute_developed_modules", + store=True, + ) + + favourite_module_ids = fields.Many2many( + string="Favourite Modules", + comodel_name="product.template", + relation="favourite_product_module_res_partner_rel", + column1="partner_id", + column2="product_template_id", + readonly=True, + ) + + sponsorship_line_ids = fields.One2many( + string="Sponsorship Activities", + comodel_name="sponsorship.line", + inverse_name="partner_id", + ) + + contributor_module_line_ids = fields.One2many( + string="Contributed Modules", + comodel_name="contributor.module.line", + inverse_name="partner_id", + readonly=True, + ) + + @api.depends("github_organization") + def _compute_github_organization_url(self): + github_url = "https://github.com/" + for record in self: + if record.is_company and record.github_organization: + record.github_organization_url = github_url + record.github_organization + else: + record.github_organization_url = False + + @api.depends( + "child_ids", + "child_ids.github_name", + "child_ids.membership_state", + "child_ids.parent_id", + ) + def _compute_integrator(self): + """ + Integrators are partners who have any contact with a commit to OCA repositories + or a current OCA membership. + """ + for partner in self: + partner.is_integrator = any( + (child.github_name or child.membership_state == "paid") + for child in partner.child_ids + ) + + @api.depends("child_ids.github_name", "child_ids.parent_id") + def _compute_contributor_count(self): + contributor_data = self.read_group( + domain=[("parent_id", "in", self.ids), ("github_name", "!=", False)], + fields=["parent_id"], + groupby=["parent_id"], + ) + contributor_mapped_data = { + item["parent_id"][0]: item["parent_id_count"] for item in contributor_data + } + for partner in self: + partner.contributor_count = contributor_mapped_data.get(partner.id, 0) + + @api.depends("child_ids.membership_state", "child_ids.parent_id") + def _compute_member_count(self): + member_data = self.read_group( + domain=[("parent_id", "in", self.ids), ("membership_state", "=", "paid")], + fields=["parent_id"], + groupby=["parent_id"], + ) + member_mapped_data = { + item["parent_id"][0]: item["parent_id_count"] for item in member_data + } + for partner in self: + partner.member_count = member_mapped_data.get(partner.id, 0) + + @api.depends("author_ids", "author_ids.partner_id", "author_ids.module_qty") + def _compute_module_count(self): + for partner in self: + partner.module_count = sum( + author.module_qty for author in partner.author_ids + ) + + @api.depends( + "is_integrator", + "author_ids", + "author_ids.module_ids", + "author_ids.module_ids.technical_name", + ) + def _compute_developed_modules(self): + OdooModule = self.env["odoo.module"] + ProductTemplate = self.env["product.template"] + + for partner in self: + if not partner.is_integrator or not partner.author_ids: + partner.developed_module_ids = [(5, 0, 0)] + continue + + module_list = OdooModule.search( + [("author_ids", "in", partner.author_ids.ids)] + ) + products = ProductTemplate.search( + [("odoo_module_id", "in", module_list.ids)] + ) + partner.developed_module_ids = [(6, 0, products.ids)] + + def write(self, vals): + # clear github organization data if partner is not company + if not vals.get("is_company", True): + vals["github_organization"] = False + return super().write(vals) + + def update_contributor_modules(self, contributor, modules): + """ + Update contributor with fetched modules from Github. + + `modules` is a dict: {technical_name: merged_datetime} + """ + OdooModule = self.env["odoo.module"] + ProductTemplate = self.env["product.template"] + + if not modules: + return + + module_rs = OdooModule.search([("technical_name", "in", list(modules.keys()))]) + + github_templates = ProductTemplate.search( + [("odoo_module_id", "in", module_rs.ids)] + ) + + contributor_module_lines = contributor.contributor_module_line_ids + product_mapped_data = { + line.product_template_id.id: line.id for line in contributor_module_lines + } + + current_templates = contributor_module_lines.mapped("product_template_id") + total_templates = github_templates | current_templates + common_templates = github_templates & current_templates + new_templates = github_templates - current_templates + + update_records = [ + ( + 1, + product_mapped_data[product.id], + {"date_pr_merged": modules[product.odoo_module_id.technical_name]}, + ) + for product in common_templates + ] + + new_records = [ + ( + 0, + 0, + { + "product_template_id": product.id, + "date_pr_merged": modules[product.odoo_module_id.technical_name], + }, + ) + for product in new_templates + ] + + delete_records = [] + if len(total_templates) > 5: + remove_products_count = len(total_templates) - 5 + delete_records = [ + (2, line.id, False) + for line in contributor_module_lines.sorted(key="date_pr_merged")[ + :remove_products_count + ] + ] + + contributor.write( + { + "contributor_module_line_ids": update_records + + new_records + + delete_records + } + ) + + def get_github_user_modules(self, gh_events, github_orgs): + """ + Find latest 5 technical name of the modules based on the PR which are opened + for OCA organization by github user. + """ + current_page_modules = {} # holds module name and merged date of PR. + + for gh_event in gh_events: + org = gh_event.org and gh_event.org.login + if not ( + gh_event.type == "PullRequestEvent" + and org in github_orgs + and gh_event.payload["action"] == "opened" + ): + continue + + pr_number = gh_event.payload["pull_request"]["number"] + try: + gh_pull_request = gh_event.repo.get_pull(pr_number) + except Exception: + _logger.warning( + "Error while fetching pull request #'%s' of repository '%s'/'%s'.", + pr_number, + org, + gh_event.repo, + ) + continue + + if not gh_pull_request.merged: + continue + + commit_sha = gh_pull_request.head.sha + gh_commit = gh_event.repo.get_commit(commit_sha) + + for commit_file in gh_commit.files: + file_name = commit_file.filename.split("/")[0].split(".") + + if len(file_name) != 1: + continue + + technical_name = file_name[0] + if technical_name in current_page_modules: + continue + + odoo_module = self.env["odoo.module"].search( + [("technical_name", "=", technical_name)], limit=1 + ) + if odoo_module: + current_page_modules[technical_name] = gh_pull_request.merged_at + if len(current_page_modules) == 5: + return current_page_modules + + return current_page_modules + + def get_github_organization(self): + return self.env["github.organization"].search([]).mapped("github_name") + + def get_contributors(self): + return self.env["res.partner"].search( + [("github_name", "!=", False), ("website_published", "=", True)] + ) + + def contributors_fetch_modules(self): + gh_api = self.get_github_connector() + contributors = self.get_contributors() + github_orgs = self.get_github_organization() + + for contributor in contributors: + try: + gh_user = gh_api.get_user(contributor.github_name) + except Exception: + _logger.warning("Error while fetching user '%s'.", contributor.name) + continue + + gh_events = gh_user.get_events() + modules = self.get_github_user_modules(gh_events, github_orgs) + self.update_contributor_modules(contributor, modules) + + def cron_create_github_user_module(self): + self.contributors_fetch_modules() diff --git a/website_oca_integrator/models/sponsorship.py b/website_oca_integrator/models/sponsorship.py new file mode 100644 index 00000000..088cc5f5 --- /dev/null +++ b/website_oca_integrator/models/sponsorship.py @@ -0,0 +1,19 @@ +# Copyright 2018 Surekha Technologies (https://www.surekhatech.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class SponsorshipLine(models.Model): + _name = "sponsorship.line" + _description = "Sponsorship Line" + + date_from = fields.Date(string="Join Date", required=True) + date_end = fields.Date(string="End Date", required=True) + sponsorship_id = fields.Many2one( + comodel_name="product.product", string="Sponsorship Product" + ) + partner_id = fields.Many2one(comodel_name="res.partner", string="Partner") + grade_id = fields.Many2one( + comodel_name="res.partner.grade", string="Level", required=True + ) diff --git a/website_oca_integrator/pyproject.toml b/website_oca_integrator/pyproject.toml new file mode 100644 index 00000000..4231d0cc --- /dev/null +++ b/website_oca_integrator/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/website_oca_integrator/readme/CONTRIBUTORS.md b/website_oca_integrator/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..391f9820 --- /dev/null +++ b/website_oca_integrator/readme/CONTRIBUTORS.md @@ -0,0 +1,4 @@ +- [Tecnativa](https://www.tecnativa.com): + - Víctor M.M. Torres + - Carlos Roca + - Ernesto Tejeda diff --git a/website_oca_integrator/readme/DESCRIPTION.md b/website_oca_integrator/readme/DESCRIPTION.md new file mode 100644 index 00000000..501e6ea8 --- /dev/null +++ b/website_oca_integrator/readme/DESCRIPTION.md @@ -0,0 +1,2 @@ +This module adds menu "Integrators" in website, which displays +integrators, contributors, members and modules related information. diff --git a/website_oca_integrator/security/ir.model.access.csv b/website_oca_integrator/security/ir.model.access.csv new file mode 100644 index 00000000..2bb8f28e --- /dev/null +++ b/website_oca_integrator/security/ir.model.access.csv @@ -0,0 +1,6 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_sponsorship_line_partner_manager,sponsorship_line partner_manager,model_sponsorship_line,base.group_partner_manager,1,1,1,1 +access_sponsorship_line_portal,sponsorship_line portal,model_sponsorship_line,base.group_portal,1,0,0,0 +access_sponsorship_line_user,sponsorship_line user,model_sponsorship_line,base.group_user,1,0,0,0 +access_contributor_module_reader_portal,contributor_module reader portal,model_contributor_module_line,base.group_portal,1,0,0,0 +access_contributor_module_reader_user,contributor_module reader user,model_contributor_module_line,base.group_user,1,0,0,0 diff --git a/website_oca_integrator/static/description/icon.png b/website_oca_integrator/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/website_oca_integrator/static/description/icon.png differ diff --git a/website_oca_integrator/static/description/index.html b/website_oca_integrator/static/description/index.html new file mode 100644 index 00000000..45e540f0 --- /dev/null +++ b/website_oca_integrator/static/description/index.html @@ -0,0 +1,429 @@ + + + + + +Website OCA Integrator + + + +
+

Website OCA Integrator

+ + +

Beta License: AGPL-3 OCA/oca-custom Translate me on Weblate Try me on Runboat

+

This module adds menu “Integrators” in website, which displays +integrators, contributors, members and modules related information.

+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Surekha Technologies
  • +
+
+
+

Contributors

+
    +
  • Tecnativa:
      +
    • Víctor M.M. Torres
    • +
    • Carlos Roca
    • +
    • Ernesto Tejeda
    • +
    +
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/oca-custom project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/website_oca_integrator/static/src/js/integrator_portal.js b/website_oca_integrator/static/src/js/integrator_portal.js new file mode 100644 index 00000000..cc899b33 --- /dev/null +++ b/website_oca_integrator/static/src/js/integrator_portal.js @@ -0,0 +1,92 @@ +/** @odoo-module **/ + +/* Copyright 2018-2026 Surekha Technologies, Therp Bv +License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ + +import publicWidget from "@web/legacy/js/public/public_widget"; + +function hasPlugin($el, name) { + return Boolean($el && typeof $el[name] === "function"); +} + +publicWidget.registry.integratorModuleSelector = publicWidget.Widget.extend({ + selector: ".module_js_select2", + + async start() { + // Odoo 18: do NOT call this._super() here (it's not available). + // Keep code resilient to missing jQuery plugins. + + try { + if (hasPlugin(this.$el, "select2")) { + this.$el.select2({ + tags: false, + maximumInputLength: 25, + maximumSelectionLength: 5, + ajax: { + url: "/my/account/get_developed_modules", + dataType: "json", + data: (params) => ({query: params.term, limit: 25}), + processResults: (data) => ({ + results: data.map((x) => ({id: x.id, text: x.name})), + }), + }, + }); + } + } catch { + // Fallback: plain