From 57d7b9ca401ebc8ecb8747ab9c499c4bffa9cc89 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 10 Apr 2024 01:23:23 +0200 Subject: [PATCH 01/14] feat: address autocomplete (first draft) --- .../address_autocomplete_settings/__init__.py | 0 .../address_autocomplete_settings.js | 8 +++ .../address_autocomplete_settings.json | 52 +++++++++++++++++++ .../address_autocomplete_settings.py | 40 ++++++++++++++ .../providers/geoapify.py | 29 +++++++++++ .../test_address_autocomplete_settings.py | 9 ++++ frappe/public/js/desk.bundle.js | 2 + .../autocomplete_dialog.js | 43 +++++++++++++++ 8 files changed, 183 insertions(+) create mode 100644 frappe/integrations/doctype/address_autocomplete_settings/__init__.py create mode 100644 frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.js create mode 100644 frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.json create mode 100644 frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.py create mode 100644 frappe/integrations/doctype/address_autocomplete_settings/providers/geoapify.py create mode 100644 frappe/integrations/doctype/address_autocomplete_settings/test_address_autocomplete_settings.py create mode 100644 frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js diff --git a/frappe/integrations/doctype/address_autocomplete_settings/__init__.py b/frappe/integrations/doctype/address_autocomplete_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.js b/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.js new file mode 100644 index 0000000000..e12df4b0e1 --- /dev/null +++ b/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, Frappe Technologies and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Address Autocomplete Settings", { +// refresh(frm) { + +// }, +// }); diff --git a/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.json b/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.json new file mode 100644 index 0000000000..583484a23d --- /dev/null +++ b/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.json @@ -0,0 +1,52 @@ +{ + "actions": [], + "creation": "2024-04-09 23:41:49.747820", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "enabled", + "provider", + "api_key" + ], + "fields": [ + { + "fieldname": "provider", + "fieldtype": "Select", + "label": "Provider", + "options": "Geoapify" + }, + { + "fieldname": "api_key", + "fieldtype": "Password", + "label": "Api Key" + }, + { + "default": "0", + "fieldname": "enabled", + "fieldtype": "Check", + "label": "Enabled" + } + ], + "issingle": 1, + "links": [], + "modified": "2024-04-09 23:43:27.454479", + "modified_by": "Administrator", + "module": "Integrations", + "name": "Address Autocomplete Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.py b/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.py new file mode 100644 index 0000000000..7a5caf79a0 --- /dev/null +++ b/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.py @@ -0,0 +1,40 @@ +# Copyright (c) 2024, Frappe Technologies and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document + +from .providers.geoapify import GeoapifyProvider + + +class AddressAutocompleteSettings(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + api_key: DF.Password | None + enabled: DF.Check + provider: DF.Literal["Geoapify"] + # end: auto-generated types + + pass + + +@frappe.whitelist() +def autocomplete(txt: str) -> list[dict]: + if not txt: + return [] + + settings = frappe.get_single("Address Autocomplete Settings") + if not settings.enabled: + return [] + + if settings.provider == "Geoapify": + provider = GeoapifyProvider(settings.get_password("api_key"), frappe.local.lang) + return provider.autocomplete(txt) + + frappe.throw("Invalid provider") diff --git a/frappe/integrations/doctype/address_autocomplete_settings/providers/geoapify.py b/frappe/integrations/doctype/address_autocomplete_settings/providers/geoapify.py new file mode 100644 index 0000000000..4db6c1e0fb --- /dev/null +++ b/frappe/integrations/doctype/address_autocomplete_settings/providers/geoapify.py @@ -0,0 +1,29 @@ +import requests + + +class GeoapifyProvider: + def __init__(self, api_key: str, lang: str | None = None): + self.api_key = api_key + self.lang = lang + self.base_url = "https://api.geoapify.com" + + def autocomplete(self, query: str) -> list[dict]: + params = { + "text": query, + "apiKey": self.api_key, + "limit": 20, + "format": "json", + "lang": self.lang, + } + response = requests.get(f"{self.base_url}/v1/geocode/autocomplete", params=params) + response.raise_for_status() + + results = response.json()["results"] + # TODO: need to return the full address data here, as value or extra data + return [ + { + "label": result["formatted"], + "value": result["place_id"], + } + for result in results + ] diff --git a/frappe/integrations/doctype/address_autocomplete_settings/test_address_autocomplete_settings.py b/frappe/integrations/doctype/address_autocomplete_settings/test_address_autocomplete_settings.py new file mode 100644 index 0000000000..9da1271354 --- /dev/null +++ b/frappe/integrations/doctype/address_autocomplete_settings/test_address_autocomplete_settings.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, Frappe Technologies and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestAddressAutocompleteSettings(FrappeTestCase): + pass diff --git a/frappe/public/js/desk.bundle.js b/frappe/public/js/desk.bundle.js index 6697c034bc..d8cfd9a69b 100644 --- a/frappe/public/js/desk.bundle.js +++ b/frappe/public/js/desk.bundle.js @@ -108,3 +108,5 @@ import "./frappe/ui/chart.js"; import "./frappe/ui/datatable.js"; import "./frappe/ui/driver.js"; import "./frappe/scanner"; + +import "./frappe/ui/address_autocomplete/autocomplete_dialog.js"; diff --git a/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js b/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js new file mode 100644 index 0000000000..47ce52e4fb --- /dev/null +++ b/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js @@ -0,0 +1,43 @@ +frappe.provide("frappe.ui"); + +frappe.ui.AddressAutocompleteDialog = class AddressAutocompleteDialog { + constructor(opts) { + this.title = opts?.title || __("New Address"); + this.link_doctype = opts?.link_doctype; + this.link_name = opts?.link_name; + this.dialog = this._get_dialog(); + } + + _get_dialog() { + // sourcery skip: inline-immediately-returned-variable + const dialog = new frappe.ui.Dialog({ + title: this.title, + fields: [ + { + fieldname: "search", + fieldtype: "Autocomplete", + label: __("Search"), + reqd: 1, + get_query: + "frappe.integrations.doctype.address_autocomplete_settings.address_autocomplete_settings.autocomplete", + }, + ], + primary_action_label: __("Save Address"), + primary_action: () => { + dialog.hide(); + // TODO: save the selected address to the database + }, + secondary_action_label: __("Edit Address"), + secondary_action: () => { + dialog.hide(); + // TODO: open the selected address in the address form + }, + }); + + return dialog; + } + + show() { + this.dialog.show(); + } +}; From 7a45c76da24a6d22481634aaa7c164d06faf748e Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 9 May 2024 22:06:55 +0200 Subject: [PATCH 02/14] fix(Address Autocomplete Settings): provider and API Key mandatory if enabled --- .../address_autocomplete_settings.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.json b/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.json index 583484a23d..c8649e1bb5 100644 --- a/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.json +++ b/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.json @@ -13,12 +13,14 @@ "fieldname": "provider", "fieldtype": "Select", "label": "Provider", + "mandatory_depends_on": "enabled", "options": "Geoapify" }, { "fieldname": "api_key", "fieldtype": "Password", - "label": "Api Key" + "label": "Api Key", + "mandatory_depends_on": "enabled" }, { "default": "0", @@ -29,7 +31,7 @@ ], "issingle": 1, "links": [], - "modified": "2024-04-09 23:43:27.454479", + "modified": "2024-05-09 21:40:38.625256", "modified_by": "Administrator", "module": "Integrations", "name": "Address Autocomplete Settings", From 388046491ef19adece560d0b3c2ad323249efa3c Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 9 May 2024 22:08:12 +0200 Subject: [PATCH 03/14] fix: return structured result from provider --- .../providers/geoapify.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/frappe/integrations/doctype/address_autocomplete_settings/providers/geoapify.py b/frappe/integrations/doctype/address_autocomplete_settings/providers/geoapify.py index 4db6c1e0fb..4689c4b46b 100644 --- a/frappe/integrations/doctype/address_autocomplete_settings/providers/geoapify.py +++ b/frappe/integrations/doctype/address_autocomplete_settings/providers/geoapify.py @@ -1,3 +1,5 @@ +import json + import requests @@ -23,7 +25,15 @@ class GeoapifyProvider: return [ { "label": result["formatted"], - "value": result["place_id"], + "value": json.dumps( + { + "address_line1": result.get("address_line1"), + "city": result.get("city"), + "state": result.get("state"), + "pincode": result.get("postcode"), + "country": result.get("country"), + } + ), } for result in results ] From ae505b769c9b679157efd8151c0f1036c381e1a2 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 9 May 2024 22:08:56 +0200 Subject: [PATCH 04/14] feat: implement primary and secondary action for autocomplete dialog --- .../autocomplete_dialog.js | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js b/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js index 47ce52e4fb..1a62a76bd1 100644 --- a/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js +++ b/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js @@ -5,6 +5,7 @@ frappe.ui.AddressAutocompleteDialog = class AddressAutocompleteDialog { this.title = opts?.title || __("New Address"); this.link_doctype = opts?.link_doctype; this.link_name = opts?.link_name; + this.after_insert = opts?.after_insert; this.dialog = this._get_dialog(); } @@ -24,19 +25,39 @@ frappe.ui.AddressAutocompleteDialog = class AddressAutocompleteDialog { ], primary_action_label: __("Save Address"), primary_action: () => { + // Insert the address into the database dialog.hide(); - // TODO: save the selected address to the database + + const address = this.parse_selected_value(); + address["doctype"] = "Address"; + address["links"] = [ + { + link_doctype: this.link_doctype, + link_name: this.link_name, + }, + ]; + frappe.db.insert(address).then((doc) => { + this.after_insert && this.after_insert(doc); + }); }, secondary_action_label: __("Edit Address"), secondary_action: () => { + // Open the address in the form view dialog.hide(); - // TODO: open the selected address in the address form + + const address = this.parse_selected_value(); + frappe.new_doc("Address", address); }, }); return dialog; } + parse_selected_value() { + const data = this.dialog.get_values(); + return JSON.parse(data.search); + } + show() { this.dialog.show(); } From 553918f36bbe6fbd4dfd35a67c17931906b231b0 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 9 May 2024 22:10:47 +0200 Subject: [PATCH 05/14] feat: use autocomplete on "New Address" button --- frappe/boot.py | 3 +++ .../js/frappe/utils/address_and_contact.js | 23 ++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/frappe/boot.py b/frappe/boot.py index 040c695773..9c8bd43f60 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -109,6 +109,9 @@ def get_bootinfo(): bootinfo.subscription_conf = add_subscription_conf() bootinfo.marketplace_apps = get_marketplace_apps() bootinfo.changelog_feed = get_changelog_feed_items() + bootinfo.address_autocomplete_enabled = frappe.db.get_single_value( + "Address Autocomplete Settings", "enabled" + ) return bootinfo diff --git a/frappe/public/js/frappe/utils/address_and_contact.js b/frappe/public/js/frappe/utils/address_and_contact.js index 1cea4d1514..14ac101049 100644 --- a/frappe/public/js/frappe/utils/address_and_contact.js +++ b/frappe/public/js/frappe/utils/address_and_contact.js @@ -12,7 +12,7 @@ $.extend(frappe.contacts, { $(frm.fields_dict["address_html"].wrapper) .html(frappe.render_template("address_list", frm.doc.__onload)) .find(".btn-address") - .on("click", () => new_record("Address", frm.doc)); + .on("click", () => new_record("Address", frm)); } // render contact @@ -20,7 +20,7 @@ $.extend(frappe.contacts, { $(frm.fields_dict["contact_html"].wrapper) .html(frappe.render_template("contact_list", frm.doc.__onload)) .find(".btn-contact") - .on("click", () => new_record("Contact", frm.doc)); + .on("click", () => new_record("Contact", frm)); } }, get_last_doc: function (frm) { @@ -59,12 +59,23 @@ $.extend(frappe.contacts, { }, }); -function new_record(doctype, source_doc) { +function new_record(doctype, frm) { frappe.dynamic_link = { - doctype: source_doc.doctype, - doc: source_doc, + doctype: frm.doc.doctype, + doc: frm.doc, fieldname: "name", }; - return frappe.new_doc(doctype); + if (frappe.boot.address_autocomplete_enabled === 1 && doctype === "Address") { + new frappe.ui.AddressAutocompleteDialog({ + title: __("New Address"), + link_doctype: frm.doc.doctype, + link_name: frm.doc.name, + after_insert: function (doc) { + frm.reload_doc(); + }, + }).show(); + } else { + frappe.new_doc(doctype); + } } From a1aa86589feac7f345178d082c79adbfd8f85df8 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 9 May 2024 22:31:49 +0200 Subject: [PATCH 06/14] fix: limit to five results, remove outdated comment --- .../address_autocomplete_settings/providers/geoapify.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/integrations/doctype/address_autocomplete_settings/providers/geoapify.py b/frappe/integrations/doctype/address_autocomplete_settings/providers/geoapify.py index 4689c4b46b..47bc0a883e 100644 --- a/frappe/integrations/doctype/address_autocomplete_settings/providers/geoapify.py +++ b/frappe/integrations/doctype/address_autocomplete_settings/providers/geoapify.py @@ -13,7 +13,7 @@ class GeoapifyProvider: params = { "text": query, "apiKey": self.api_key, - "limit": 20, + "limit": 5, "format": "json", "lang": self.lang, } @@ -21,7 +21,6 @@ class GeoapifyProvider: response.raise_for_status() results = response.json()["results"] - # TODO: need to return the full address data here, as value or extra data return [ { "label": result["formatted"], From 1520888f79d0d950e9a0c4a9e9c28dc3b9e4c04f Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 9 May 2024 22:32:02 +0200 Subject: [PATCH 07/14] fix: API Key label --- .../address_autocomplete_settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.json b/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.json index c8649e1bb5..cd6b997bd1 100644 --- a/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.json +++ b/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.json @@ -19,7 +19,7 @@ { "fieldname": "api_key", "fieldtype": "Password", - "label": "Api Key", + "label": "API Key", "mandatory_depends_on": "enabled" }, { @@ -31,7 +31,7 @@ ], "issingle": 1, "links": [], - "modified": "2024-05-09 21:40:38.625256", + "modified": "2024-05-09 22:13:41.223754", "modified_by": "Administrator", "module": "Integrations", "name": "Address Autocomplete Settings", From 529319517909917d7a6ec44b97f81521efa4798e Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 9 May 2024 22:57:32 +0200 Subject: [PATCH 08/14] refactor: provider naming --- .../address_autocomplete_settings.py | 11 +++++++---- .../providers/geoapify.py | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.py b/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.py index 7a5caf79a0..7356cef51c 100644 --- a/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.py +++ b/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.py @@ -2,9 +2,10 @@ # For license information, please see license.txt import frappe +from frappe import _ from frappe.model.document import Document -from .providers.geoapify import GeoapifyProvider +from .providers.geoapify import Geoapify class AddressAutocompleteSettings(Document): @@ -34,7 +35,9 @@ def autocomplete(txt: str) -> list[dict]: return [] if settings.provider == "Geoapify": - provider = GeoapifyProvider(settings.get_password("api_key"), frappe.local.lang) - return provider.autocomplete(txt) + AutocompleteProvider = Geoapify + else: + frappe.throw(_("This address autocomplete provider is not supported yet.")) - frappe.throw("Invalid provider") + provider = AutocompleteProvider(settings.get_password("api_key"), frappe.local.lang) + return provider.autocomplete(txt) diff --git a/frappe/integrations/doctype/address_autocomplete_settings/providers/geoapify.py b/frappe/integrations/doctype/address_autocomplete_settings/providers/geoapify.py index 47bc0a883e..0f6c250850 100644 --- a/frappe/integrations/doctype/address_autocomplete_settings/providers/geoapify.py +++ b/frappe/integrations/doctype/address_autocomplete_settings/providers/geoapify.py @@ -3,7 +3,7 @@ import json import requests -class GeoapifyProvider: +class Geoapify: def __init__(self, api_key: str, lang: str | None = None): self.api_key = api_key self.lang = lang From 531b177c919996a5641058f306a1dc29590e5d12 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 19 May 2024 20:51:58 +0200 Subject: [PATCH 09/14] refactor: rename to Geolocation Settings --- frappe/boot.py | 4 ++-- .../__init__.py | 0 .../geolocation_settings.js} | 2 +- .../geolocation_settings.json} | 14 +++++++------- .../geolocation_settings.py} | 14 +++++++------- .../providers/geoapify.py | 0 .../test_geolocation_settings.py} | 2 +- .../ui/address_autocomplete/autocomplete_dialog.js | 2 +- .../public/js/frappe/utils/address_and_contact.js | 2 +- 9 files changed, 20 insertions(+), 20 deletions(-) rename frappe/integrations/doctype/{address_autocomplete_settings => geolocation_settings}/__init__.py (100%) rename frappe/integrations/doctype/{address_autocomplete_settings/address_autocomplete_settings.js => geolocation_settings/geolocation_settings.js} (72%) rename frappe/integrations/doctype/{address_autocomplete_settings/address_autocomplete_settings.json => geolocation_settings/geolocation_settings.json} (70%) rename frappe/integrations/doctype/{address_autocomplete_settings/address_autocomplete_settings.py => geolocation_settings/geolocation_settings.py} (65%) rename frappe/integrations/doctype/{address_autocomplete_settings => geolocation_settings}/providers/geoapify.py (100%) rename frappe/integrations/doctype/{address_autocomplete_settings/test_address_autocomplete_settings.py => geolocation_settings/test_geolocation_settings.py} (72%) diff --git a/frappe/boot.py b/frappe/boot.py index 6fd7c56014..5dd53474b9 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -111,8 +111,8 @@ def get_bootinfo(): bootinfo.subscription_conf = add_subscription_conf() bootinfo.marketplace_apps = get_marketplace_apps() bootinfo.changelog_feed = get_changelog_feed_items() - bootinfo.address_autocomplete_enabled = frappe.db.get_single_value( - "Address Autocomplete Settings", "enabled" + bootinfo.enable_address_autocompletion = frappe.db.get_single_value( + "Geolocation Settings", "enable_address_autocompletion" ) if sentry_dsn := get_sentry_dsn(): diff --git a/frappe/integrations/doctype/address_autocomplete_settings/__init__.py b/frappe/integrations/doctype/geolocation_settings/__init__.py similarity index 100% rename from frappe/integrations/doctype/address_autocomplete_settings/__init__.py rename to frappe/integrations/doctype/geolocation_settings/__init__.py diff --git a/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.js b/frappe/integrations/doctype/geolocation_settings/geolocation_settings.js similarity index 72% rename from frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.js rename to frappe/integrations/doctype/geolocation_settings/geolocation_settings.js index e12df4b0e1..269b388265 100644 --- a/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.js +++ b/frappe/integrations/doctype/geolocation_settings/geolocation_settings.js @@ -1,7 +1,7 @@ // Copyright (c) 2024, Frappe Technologies and contributors // For license information, please see license.txt -// frappe.ui.form.on("Address Autocomplete Settings", { +// frappe.ui.form.on("Geolocation Settings", { // refresh(frm) { // }, diff --git a/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.json b/frappe/integrations/doctype/geolocation_settings/geolocation_settings.json similarity index 70% rename from frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.json rename to frappe/integrations/doctype/geolocation_settings/geolocation_settings.json index cd6b997bd1..52fa6691b3 100644 --- a/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.json +++ b/frappe/integrations/doctype/geolocation_settings/geolocation_settings.json @@ -4,7 +4,7 @@ "doctype": "DocType", "engine": "InnoDB", "field_order": [ - "enabled", + "enable_address_autocompletion", "provider", "api_key" ], @@ -13,28 +13,28 @@ "fieldname": "provider", "fieldtype": "Select", "label": "Provider", - "mandatory_depends_on": "enabled", + "mandatory_depends_on": "enable_address_autocompletion", "options": "Geoapify" }, { "fieldname": "api_key", "fieldtype": "Password", "label": "API Key", - "mandatory_depends_on": "enabled" + "mandatory_depends_on": "enable_address_autocompletion" }, { "default": "0", - "fieldname": "enabled", + "fieldname": "enable_address_autocompletion", "fieldtype": "Check", - "label": "Enabled" + "label": "Enable Address Autocompletion" } ], "issingle": 1, "links": [], - "modified": "2024-05-09 22:13:41.223754", + "modified": "2024-05-19 20:43:49.711209", "modified_by": "Administrator", "module": "Integrations", - "name": "Address Autocomplete Settings", + "name": "Geolocation Settings", "owner": "Administrator", "permissions": [ { diff --git a/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.py b/frappe/integrations/doctype/geolocation_settings/geolocation_settings.py similarity index 65% rename from frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.py rename to frappe/integrations/doctype/geolocation_settings/geolocation_settings.py index 7356cef51c..b7e9365133 100644 --- a/frappe/integrations/doctype/address_autocomplete_settings/address_autocomplete_settings.py +++ b/frappe/integrations/doctype/geolocation_settings/geolocation_settings.py @@ -8,7 +8,7 @@ from frappe.model.document import Document from .providers.geoapify import Geoapify -class AddressAutocompleteSettings(Document): +class GeolocationSettings(Document): # begin: auto-generated types # This code is auto-generated. Do not modify anything in this block. @@ -18,7 +18,7 @@ class AddressAutocompleteSettings(Document): from frappe.types import DF api_key: DF.Password | None - enabled: DF.Check + enable_address_autocompletion: DF.Check provider: DF.Literal["Geoapify"] # end: auto-generated types @@ -30,14 +30,14 @@ def autocomplete(txt: str) -> list[dict]: if not txt: return [] - settings = frappe.get_single("Address Autocomplete Settings") - if not settings.enabled: + settings = frappe.get_single("Geolocation Settings") + if not settings.enable_address_autocompletion: return [] if settings.provider == "Geoapify": - AutocompleteProvider = Geoapify + GeolocationProvider = Geoapify else: - frappe.throw(_("This address autocomplete provider is not supported yet.")) + frappe.throw(_("This geolocation provider is not supported yet.")) - provider = AutocompleteProvider(settings.get_password("api_key"), frappe.local.lang) + provider = GeolocationProvider(settings.get_password("api_key"), frappe.local.lang) return provider.autocomplete(txt) diff --git a/frappe/integrations/doctype/address_autocomplete_settings/providers/geoapify.py b/frappe/integrations/doctype/geolocation_settings/providers/geoapify.py similarity index 100% rename from frappe/integrations/doctype/address_autocomplete_settings/providers/geoapify.py rename to frappe/integrations/doctype/geolocation_settings/providers/geoapify.py diff --git a/frappe/integrations/doctype/address_autocomplete_settings/test_address_autocomplete_settings.py b/frappe/integrations/doctype/geolocation_settings/test_geolocation_settings.py similarity index 72% rename from frappe/integrations/doctype/address_autocomplete_settings/test_address_autocomplete_settings.py rename to frappe/integrations/doctype/geolocation_settings/test_geolocation_settings.py index 9da1271354..90979b2949 100644 --- a/frappe/integrations/doctype/address_autocomplete_settings/test_address_autocomplete_settings.py +++ b/frappe/integrations/doctype/geolocation_settings/test_geolocation_settings.py @@ -5,5 +5,5 @@ from frappe.tests.utils import FrappeTestCase -class TestAddressAutocompleteSettings(FrappeTestCase): +class TestGeolocationSettings(FrappeTestCase): pass diff --git a/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js b/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js index 1a62a76bd1..3b300547ba 100644 --- a/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js +++ b/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js @@ -20,7 +20,7 @@ frappe.ui.AddressAutocompleteDialog = class AddressAutocompleteDialog { label: __("Search"), reqd: 1, get_query: - "frappe.integrations.doctype.address_autocomplete_settings.address_autocomplete_settings.autocomplete", + "frappe.integrations.doctype.geolocation_settings.geolocation_settings.autocomplete", }, ], primary_action_label: __("Save Address"), diff --git a/frappe/public/js/frappe/utils/address_and_contact.js b/frappe/public/js/frappe/utils/address_and_contact.js index 14ac101049..5e3217366f 100644 --- a/frappe/public/js/frappe/utils/address_and_contact.js +++ b/frappe/public/js/frappe/utils/address_and_contact.js @@ -66,7 +66,7 @@ function new_record(doctype, frm) { fieldname: "name", }; - if (frappe.boot.address_autocomplete_enabled === 1 && doctype === "Address") { + if (frappe.boot.enable_address_autocompletion === 1 && doctype === "Address") { new frappe.ui.AddressAutocompleteDialog({ title: __("New Address"), link_doctype: frm.doc.doctype, From 97db7aa64b1ba0bf85cb43a11c9064695dca1e5e Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 19 May 2024 21:37:43 +0200 Subject: [PATCH 10/14] refactor: return generator --- .../doctype/geolocation_settings/geolocation_settings.py | 5 ++--- .../doctype/geolocation_settings/providers/geoapify.py | 8 +++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/frappe/integrations/doctype/geolocation_settings/geolocation_settings.py b/frappe/integrations/doctype/geolocation_settings/geolocation_settings.py index b7e9365133..62fa0dd276 100644 --- a/frappe/integrations/doctype/geolocation_settings/geolocation_settings.py +++ b/frappe/integrations/doctype/geolocation_settings/geolocation_settings.py @@ -35,9 +35,8 @@ def autocomplete(txt: str) -> list[dict]: return [] if settings.provider == "Geoapify": - GeolocationProvider = Geoapify + provider = Geoapify(settings.get_password("api_key"), frappe.local.lang) else: frappe.throw(_("This geolocation provider is not supported yet.")) - provider = GeolocationProvider(settings.get_password("api_key"), frappe.local.lang) - return provider.autocomplete(txt) + return list(provider.autocomplete(txt)) diff --git a/frappe/integrations/doctype/geolocation_settings/providers/geoapify.py b/frappe/integrations/doctype/geolocation_settings/providers/geoapify.py index 0f6c250850..8cdd8a3d20 100644 --- a/frappe/integrations/doctype/geolocation_settings/providers/geoapify.py +++ b/frappe/integrations/doctype/geolocation_settings/providers/geoapify.py @@ -9,7 +9,7 @@ class Geoapify: self.lang = lang self.base_url = "https://api.geoapify.com" - def autocomplete(self, query: str) -> list[dict]: + def autocomplete(self, query: str): params = { "text": query, "apiKey": self.api_key, @@ -21,8 +21,8 @@ class Geoapify: response.raise_for_status() results = response.json()["results"] - return [ - { + for result in results: + yield { "label": result["formatted"], "value": json.dumps( { @@ -34,5 +34,3 @@ class Geoapify: } ), } - for result in results - ] From 3933725c6e8fbd04aa1ce5663fbe4d6da2b26d8a Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 19 May 2024 22:02:59 +0200 Subject: [PATCH 11/14] feat: add Nomatim as geolocation provider --- .../geolocation_settings.json | 16 +++++-- .../geolocation_settings.py | 11 ++++- .../geolocation_settings/providers/nomatim.py | 47 +++++++++++++++++++ 3 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 frappe/integrations/doctype/geolocation_settings/providers/nomatim.py diff --git a/frappe/integrations/doctype/geolocation_settings/geolocation_settings.json b/frappe/integrations/doctype/geolocation_settings/geolocation_settings.json index 52fa6691b3..ed69716cd8 100644 --- a/frappe/integrations/doctype/geolocation_settings/geolocation_settings.json +++ b/frappe/integrations/doctype/geolocation_settings/geolocation_settings.json @@ -6,6 +6,7 @@ "field_order": [ "enable_address_autocompletion", "provider", + "base_url", "api_key" ], "fields": [ @@ -14,24 +15,33 @@ "fieldtype": "Select", "label": "Provider", "mandatory_depends_on": "enable_address_autocompletion", - "options": "Geoapify" + "options": "Geoapify\nNomatim" }, { + "depends_on": "eval: doc.provider === \"Geoapify\"", "fieldname": "api_key", "fieldtype": "Password", "label": "API Key", - "mandatory_depends_on": "enable_address_autocompletion" + "mandatory_depends_on": "eval: doc.enable_address_autocompletion && doc.provider === \"Geoapify\"" }, { "default": "0", "fieldname": "enable_address_autocompletion", "fieldtype": "Check", "label": "Enable Address Autocompletion" + }, + { + "depends_on": "eval: doc.provider === \"Nomatim\"", + "fieldname": "base_url", + "fieldtype": "Data", + "label": "Base URL", + "mandatory_depends_on": "eval: doc.provider === \"Nomatim\" && doc.enable_address_autocompletion", + "options": "URL" } ], "issingle": 1, "links": [], - "modified": "2024-05-19 20:43:49.711209", + "modified": "2024-05-19 22:00:01.118978", "modified_by": "Administrator", "module": "Integrations", "name": "Geolocation Settings", diff --git a/frappe/integrations/doctype/geolocation_settings/geolocation_settings.py b/frappe/integrations/doctype/geolocation_settings/geolocation_settings.py index 62fa0dd276..287ce23398 100644 --- a/frappe/integrations/doctype/geolocation_settings/geolocation_settings.py +++ b/frappe/integrations/doctype/geolocation_settings/geolocation_settings.py @@ -4,8 +4,10 @@ import frappe from frappe import _ from frappe.model.document import Document +from frappe.utils import get_url from .providers.geoapify import Geoapify +from .providers.nomatim import Nomatim class GeolocationSettings(Document): @@ -18,8 +20,9 @@ class GeolocationSettings(Document): from frappe.types import DF api_key: DF.Password | None + base_url: DF.Data | None enable_address_autocompletion: DF.Check - provider: DF.Literal["Geoapify"] + provider: DF.Literal["Geoapify", "Nomatim"] # end: auto-generated types pass @@ -36,6 +39,12 @@ def autocomplete(txt: str) -> list[dict]: if settings.provider == "Geoapify": provider = Geoapify(settings.get_password("api_key"), frappe.local.lang) + elif settings.provider == "Nomatim": + provider = Nomatim( + base_url=settings.base_url, + referer=get_url(), + lang=frappe.local.lang, + ) else: frappe.throw(_("This geolocation provider is not supported yet.")) diff --git a/frappe/integrations/doctype/geolocation_settings/providers/nomatim.py b/frappe/integrations/doctype/geolocation_settings/providers/nomatim.py new file mode 100644 index 0000000000..c02e14ad68 --- /dev/null +++ b/frappe/integrations/doctype/geolocation_settings/providers/nomatim.py @@ -0,0 +1,47 @@ +import json + +import requests + +import frappe + + +class Nomatim: + def __init__(self, base_url: str, referer: str, lang: str | None = None): + self.lang = lang + self.referer = referer + self.base_url = base_url + + def autocomplete(self, query: str): + params = { + "q": query, + "format": "json", + "limit": 5, + "addressdetails": 1, + "accept-language": self.lang, + "layer": "address", + } + response = requests.get( + f"{self.base_url}/search", + params=params, + headers={"Referer": self.referer}, + ) + response.raise_for_status() + + results = response.json() + for result in results: + if "address" not in result: + continue + + address = result["address"] + yield { + "label": result["display_name"], + "value": json.dumps( + { + "address_line1": f'{address.get("road")} {address.get("house_number", "")}'.strip(), + "city": address.get("city") or address.get("town") or address.get("village"), + "state": address.get("state"), + "pincode": address.get("postcode"), + "country": frappe.db.get_value("Country", {"code": address.get("country_code")}), + } + ), + } From f6c4c94f70bde07d8f400df89349f8125e324503 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sat, 22 Jun 2024 15:40:11 +0200 Subject: [PATCH 12/14] feat: add HERE as geolocation provider --- .../geolocation_settings.json | 8 ++-- .../geolocation_settings.py | 5 ++- .../geolocation_settings/providers/here.py | 44 +++++++++++++++++++ pyproject.toml | 1 + 4 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 frappe/integrations/doctype/geolocation_settings/providers/here.py diff --git a/frappe/integrations/doctype/geolocation_settings/geolocation_settings.json b/frappe/integrations/doctype/geolocation_settings/geolocation_settings.json index ed69716cd8..c172ba44c7 100644 --- a/frappe/integrations/doctype/geolocation_settings/geolocation_settings.json +++ b/frappe/integrations/doctype/geolocation_settings/geolocation_settings.json @@ -15,14 +15,14 @@ "fieldtype": "Select", "label": "Provider", "mandatory_depends_on": "enable_address_autocompletion", - "options": "Geoapify\nNomatim" + "options": "Geoapify\nNomatim\nHERE" }, { - "depends_on": "eval: doc.provider === \"Geoapify\"", + "depends_on": "eval: [\"Geoapify\", \"HERE\"].includes(doc.provider)", "fieldname": "api_key", "fieldtype": "Password", "label": "API Key", - "mandatory_depends_on": "eval: doc.enable_address_autocompletion && doc.provider === \"Geoapify\"" + "mandatory_depends_on": "eval: doc.enable_address_autocompletion && [\"Geoapify\", \"HERE\"].includes(doc.provider)" }, { "default": "0", @@ -41,7 +41,7 @@ ], "issingle": 1, "links": [], - "modified": "2024-05-19 22:00:01.118978", + "modified": "2024-06-22 09:18:34.306542", "modified_by": "Administrator", "module": "Integrations", "name": "Geolocation Settings", diff --git a/frappe/integrations/doctype/geolocation_settings/geolocation_settings.py b/frappe/integrations/doctype/geolocation_settings/geolocation_settings.py index 287ce23398..0959b58c7a 100644 --- a/frappe/integrations/doctype/geolocation_settings/geolocation_settings.py +++ b/frappe/integrations/doctype/geolocation_settings/geolocation_settings.py @@ -7,6 +7,7 @@ from frappe.model.document import Document from frappe.utils import get_url from .providers.geoapify import Geoapify +from .providers.here import Here from .providers.nomatim import Nomatim @@ -22,7 +23,7 @@ class GeolocationSettings(Document): api_key: DF.Password | None base_url: DF.Data | None enable_address_autocompletion: DF.Check - provider: DF.Literal["Geoapify", "Nomatim"] + provider: DF.Literal["Geoapify", "Nomatim", "HERE"] # end: auto-generated types pass @@ -45,6 +46,8 @@ def autocomplete(txt: str) -> list[dict]: referer=get_url(), lang=frappe.local.lang, ) + elif settings.provider == "HERE": + provider = Here(settings.get_password("api_key"), frappe.local.lang) else: frappe.throw(_("This geolocation provider is not supported yet.")) diff --git a/frappe/integrations/doctype/geolocation_settings/providers/here.py b/frappe/integrations/doctype/geolocation_settings/providers/here.py new file mode 100644 index 0000000000..176af32f2b --- /dev/null +++ b/frappe/integrations/doctype/geolocation_settings/providers/here.py @@ -0,0 +1,44 @@ +import json + +import pycountry +import requests + +import frappe + + +class Here: + def __init__(self, api_key: str, lang: str | None = None): + self.lang = lang + self.api_key = api_key + self.base_url = "https://autocomplete.search.hereapi.com/v1" + + def autocomplete(self, query: str): + params = { + "q": query, + "apiKey": self.api_key, + "limit": 5, + "lang": self.lang, + } + response = requests.get( + f"{self.base_url}/autocomplete", + params=params, + ) + response.raise_for_status() + + results = response.json()["items"] + for result in results: + address = result["address"] + py_country = pycountry.countries.get(alpha_3=address.get("countryCode")) + frappe_country = frappe.db.get_value("Country", {"code": py_country.alpha_2.lower()}) + yield { + "label": address["label"], + "value": json.dumps( + { + "address_line1": f'{address.get("street")} {address.get("houseNumber", "")}'.strip(), + "city": address.get("city"), + "state": address.get("state"), + "pincode": address.get("postalCode"), + "country": frappe_country, + } + ), + } diff --git a/pyproject.toml b/pyproject.toml index 67d98546e1..9aa7564228 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,6 +86,7 @@ dependencies = [ "google-auth-oauthlib~=0.4.4", "google-auth~=1.29.0", "posthog~=3.0.1", + "pycountry~=22.3.5", ] [project.urls] From c5d603065448ef13a9c706b56f6bfa57670e38c7 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sat, 22 Jun 2024 16:05:16 +0200 Subject: [PATCH 13/14] feat: disable "Create Address" button if mandatory fields are missing --- .../geolocation_settings/providers/here.py | 10 +++---- .../autocomplete_dialog.js | 27 +++++++++++++++++-- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/frappe/integrations/doctype/geolocation_settings/providers/here.py b/frappe/integrations/doctype/geolocation_settings/providers/here.py index 176af32f2b..0ecac02d9c 100644 --- a/frappe/integrations/doctype/geolocation_settings/providers/here.py +++ b/frappe/integrations/doctype/geolocation_settings/providers/here.py @@ -34,11 +34,11 @@ class Here: "label": address["label"], "value": json.dumps( { - "address_line1": f'{address.get("street")} {address.get("houseNumber", "")}'.strip(), - "city": address.get("city"), - "state": address.get("state"), - "pincode": address.get("postalCode"), - "country": frappe_country, + "address_line1": f'{address.get("street", "")} {address.get("houseNumber", "")}'.strip(), + "city": address.get("city", ""), + "state": address.get("state", ""), + "pincode": address.get("postalCode", ""), + "country": frappe_country or "", } ), } diff --git a/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js b/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js index 3b300547ba..53d8405ceb 100644 --- a/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js +++ b/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js @@ -21,9 +21,32 @@ frappe.ui.AddressAutocompleteDialog = class AddressAutocompleteDialog { reqd: 1, get_query: "frappe.integrations.doctype.geolocation_settings.geolocation_settings.autocomplete", + onchange: () => { + // Disable "Create Address" button if mandatory fields are missing + frappe.model.with_doctype("Address", () => { + const address = this.parse_selected_value(); + const mandatory_fields = frappe + .get_meta("Address") + .fields.filter( + (field) => + field.reqd && + !field.default && + field.fieldname !== "address_type" + ); + const missing_fields = mandatory_fields.filter( + (field) => !address[field.fieldname] + ); + const is_valid = missing_fields.length === 0; + if (is_valid) { + dialog.enable_primary_action(); + } else { + dialog.disable_primary_action(); + } + }); + }, }, ], - primary_action_label: __("Save Address"), + primary_action_label: __("Create Address"), primary_action: () => { // Insert the address into the database dialog.hide(); @@ -40,7 +63,7 @@ frappe.ui.AddressAutocompleteDialog = class AddressAutocompleteDialog { this.after_insert && this.after_insert(doc); }); }, - secondary_action_label: __("Edit Address"), + secondary_action_label: __("Edit Address in Form"), secondary_action: () => { // Open the address in the form view dialog.hide(); From 81b8b84b649882e4b461125a67f7be8254cecc3b Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Wed, 28 Aug 2024 16:48:01 +0200 Subject: [PATCH 14/14] chore: bump pycountry to v24.6.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b1dd1a96df..512d2de7f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,7 +88,7 @@ dependencies = [ "google-auth~=1.29.0", "posthog~=3.0.1", "vobject~=0.9.7", - "pycountry~=22.3.5", + "pycountry~=24.6.1", ] [project.urls]