feat: address autocomplete (first draft)

This commit is contained in:
barredterra 2024-04-10 01:23:23 +02:00
parent bef9bdc5ee
commit 57d7b9ca40
8 changed files with 183 additions and 0 deletions

View file

@ -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) {
// },
// });

View file

@ -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": []
}

View file

@ -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")

View file

@ -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
]

View file

@ -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

View file

@ -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";

View file

@ -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();
}
};