From 66d902396fbb8bba5abd3673724fceb1a0f9153f Mon Sep 17 00:00:00 2001 From: SUKHPREET SINGH <82471879+sukhlotey@users.noreply.github.com> Date: Fri, 28 Feb 2025 21:36:10 +0530 Subject: [PATCH 1/5] Added a file api.py which fills address automatically when clicked on map --- contact_address_app/api.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 contact_address_app/api.py diff --git a/contact_address_app/api.py b/contact_address_app/api.py new file mode 100644 index 0000000..b53f60a --- /dev/null +++ b/contact_address_app/api.py @@ -0,0 +1,33 @@ +import frappe +import requests + +@frappe.whitelist() +def get_address_from_coordinates(lat, lon): + """Fetch address from OpenStreetMap using latitude & longitude""" + url = f"https://nominatim.openstreetmap.org/reverse?format=json&lat={lat}&lon={lon}" + + headers = { + "User-Agent": "contact_address_app/1.0 (sukh.singhlotey@gmail.com)", + "Accept-Language": "en" + } + + try: + response = requests.get(url, headers=headers, timeout=5) + + if response.status_code == 403: + frappe.throw("Error: OpenStreetMap API returned 403 Forbidden. Try using a different network or add a proper User-Agent header.") + + if response.status_code != 200: + frappe.throw(f"Error: Received {response.status_code} from OpenStreetMap API") + + data = response.json() + if "display_name" in data: + return data["display_name"] + else: + frappe.throw("Error: Address not found in API response") + + except requests.exceptions.RequestException as e: + frappe.throw(f"Network Error: {str(e)}") + + except requests.exceptions.JSONDecodeError: + frappe.throw("Error: Unable to parse JSON from OpenStreetMap API") From 63faf6a4f3688e9ab297a9795c493fc3d3ac9467 Mon Sep 17 00:00:00 2001 From: SUKHPREET SINGH <82471879+sukhlotey@users.noreply.github.com> Date: Sat, 1 Mar 2025 05:38:30 +0530 Subject: [PATCH 2/5] modified but error facing --- contact_address_app/api.py | 69 ++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/contact_address_app/api.py b/contact_address_app/api.py index b53f60a..f96d675 100644 --- a/contact_address_app/api.py +++ b/contact_address_app/api.py @@ -3,31 +3,58 @@ @frappe.whitelist() def get_address_from_coordinates(lat, lon): - """Fetch address from OpenStreetMap using latitude & longitude""" - url = f"https://nominatim.openstreetmap.org/reverse?format=json&lat={lat}&lon={lon}" - - headers = { - "User-Agent": "contact_address_app/1.0 (sukh.singhlotey@gmail.com)", - "Accept-Language": "en" - } - try: - response = requests.get(url, headers=headers, timeout=5) + headers = { + "User-Agent": "contact_address_app (sukh.singhlotey@gmail.com)" + } - if response.status_code == 403: - frappe.throw("Error: OpenStreetMap API returned 403 Forbidden. Try using a different network or add a proper User-Agent header.") + response = requests.get( + f"https://nominatim.openstreetmap.org/reverse?format=json&lat={lat}&lon={lon}", + headers=headers + ) if response.status_code != 200: - frappe.throw(f"Error: Received {response.status_code} from OpenStreetMap API") + frappe.throw(f"Failed to fetch address. Status Code: {response.status_code}") data = response.json() - if "display_name" in data: - return data["display_name"] - else: - frappe.throw("Error: Address not found in API response") - - except requests.exceptions.RequestException as e: - frappe.throw(f"Network Error: {str(e)}") - except requests.exceptions.JSONDecodeError: - frappe.throw("Error: Unable to parse JSON from OpenStreetMap API") + if "address" not in data: + frappe.throw("Address data not found in response.") + + address_data = data["address"] + + road = address_data.get("road", "")[:140] + suburb = address_data.get("suburb", "")[:140] + city_district = address_data.get("city_district", "")[:140] + city = address_data.get("city", "")[:140] + state = address_data.get("state", "")[:140] + county = address_data.get("ISO3166-2-lvl4", "")[:140] + pincode = address_data.get("postcode", "")[:140] + country = address_data.get("country", "")[:140] + + full_address = f"{road}, {suburb}, {city_district}, {city}, {state}, {pincode}, {country}".strip(", ") + + frappe.log_error(f"Fetched Address Data: {address_data}", "get_address_from_coordinates") + frappe.log_error(f"Formatted Address: {full_address}", "get_address_from_coordinates") + + address_doc = frappe.get_doc({ + "doctype": "Address", + "address_line1": full_address, + "address_line2": city_district, + "city": city, + "county": county, + "state": state, + "country": country, + "pincode": pincode, + "email_id": "sukh.singhlotey@gmail.com", + "phone": "", + "fax": "", + }) + + address_doc.insert(ignore_permissions=True) + + return {"status": "success", "message": "Address saved successfully!"} + + except Exception as e: + frappe.log_error(f"Error saving address: {str(e)}", "get_address_from_coordinates") + return {"status": "error", "message": str(e)} From 99c843dc5890c6be04feab1e1142905e03ec6b0d Mon Sep 17 00:00:00 2001 From: SUKHPREET SINGH <82471879+sukhlotey@users.noreply.github.com> Date: Tue, 4 Mar 2025 00:45:13 +0530 Subject: [PATCH 3/5] Modified api.py and client script --- contact_address_app/api.py | 82 +++++++++++++++--------------------- contact_address_app/hooks.py | 1 + 2 files changed, 34 insertions(+), 49 deletions(-) diff --git a/contact_address_app/api.py b/contact_address_app/api.py index f96d675..f885d0b 100644 --- a/contact_address_app/api.py +++ b/contact_address_app/api.py @@ -3,58 +3,42 @@ @frappe.whitelist() def get_address_from_coordinates(lat, lon): + """Fetch detailed address from OpenStreetMap using latitude & longitude""" + url = f"https://nominatim.openstreetmap.org/reverse?format=json&lat={lat}&lon={lon}" + + headers = { + "User-Agent": "contact_address_app/1.0 (sukh.singhlotey@gmail.com)", + "Accept-Language": "en" + } + try: - headers = { - "User-Agent": "contact_address_app (sukh.singhlotey@gmail.com)" - } + response = requests.get(url, headers=headers, timeout=5) - response = requests.get( - f"https://nominatim.openstreetmap.org/reverse?format=json&lat={lat}&lon={lon}", - headers=headers - ) + if response.status_code == 403: + frappe.throw("Error: OpenStreetMap API returned 403 Forbidden. Check your network or User-Agent header.") if response.status_code != 200: - frappe.throw(f"Failed to fetch address. Status Code: {response.status_code}") + frappe.throw(f"Error: Received {response.status_code} from OpenStreetMap API") data = response.json() - - if "address" not in data: - frappe.throw("Address data not found in response.") - - address_data = data["address"] - - road = address_data.get("road", "")[:140] - suburb = address_data.get("suburb", "")[:140] - city_district = address_data.get("city_district", "")[:140] - city = address_data.get("city", "")[:140] - state = address_data.get("state", "")[:140] - county = address_data.get("ISO3166-2-lvl4", "")[:140] - pincode = address_data.get("postcode", "")[:140] - country = address_data.get("country", "")[:140] - - full_address = f"{road}, {suburb}, {city_district}, {city}, {state}, {pincode}, {country}".strip(", ") - - frappe.log_error(f"Fetched Address Data: {address_data}", "get_address_from_coordinates") - frappe.log_error(f"Formatted Address: {full_address}", "get_address_from_coordinates") - - address_doc = frappe.get_doc({ - "doctype": "Address", - "address_line1": full_address, - "address_line2": city_district, - "city": city, - "county": county, - "state": state, - "country": country, - "pincode": pincode, - "email_id": "sukh.singhlotey@gmail.com", - "phone": "", - "fax": "", - }) - - address_doc.insert(ignore_permissions=True) - - return {"status": "success", "message": "Address saved successfully!"} - - except Exception as e: - frappe.log_error(f"Error saving address: {str(e)}", "get_address_from_coordinates") - return {"status": "error", "message": str(e)} + if "address" in data: + address_data = data["address"] + + return { + "road": address_data.get("road", ""), + "suburb": address_data.get("suburb", ""), + "city": address_data.get("city", ""), + "state": address_data.get("state", ""), + "county": address_data.get("county", ""), + "country": address_data.get("country", ""), + "pincode": address_data.get("postcode", ""), + "country_code": address_data.get("country_code", ""), + } + else: + frappe.throw("Error: Address details not found in API response") + + except requests.exceptions.RequestException as e: + frappe.throw(f"Network Error: {str(e)}") + + except requests.exceptions.JSONDecodeError: + frappe.throw("Error: Unable to parse JSON from OpenStreetMap API") diff --git a/contact_address_app/hooks.py b/contact_address_app/hooks.py index efda7dd..307f517 100644 --- a/contact_address_app/hooks.py +++ b/contact_address_app/hooks.py @@ -242,3 +242,4 @@ # "Logging DocType Name": 30 # days to retain logs # } + From 2d0255706e9714f969b5a105a9c852b5d2fc7e35 Mon Sep 17 00:00:00 2001 From: SUKHPREET SINGH <82471879+sukhlotey@users.noreply.github.com> Date: Sun, 27 Apr 2025 23:30:17 +0530 Subject: [PATCH 4/5] Added Client script for Address doctype under contact module --- contact_address_app/api.py | 36 ++++++++++--------- .../fixtures/client_script.json | 13 +++++++ contact_address_app/hooks.py | 3 ++ 3 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 contact_address_app/fixtures/client_script.json diff --git a/contact_address_app/api.py b/contact_address_app/api.py index f885d0b..a92d7fd 100644 --- a/contact_address_app/api.py +++ b/contact_address_app/api.py @@ -1,9 +1,11 @@ import frappe import requests +from openlocationcode import openlocationcode as olc # Import the library @frappe.whitelist() def get_address_from_coordinates(lat, lon): - """Fetch detailed address from OpenStreetMap using latitude & longitude""" + """Fetch detailed address from OpenStreetMap and generate Google Plus Code using latitude & longitude""" + # Existing OpenStreetMap API call url = f"https://nominatim.openstreetmap.org/reverse?format=json&lat={lat}&lon={lon}" headers = { @@ -21,21 +23,23 @@ def get_address_from_coordinates(lat, lon): frappe.throw(f"Error: Received {response.status_code} from OpenStreetMap API") data = response.json() - if "address" in data: - address_data = data["address"] - - return { - "road": address_data.get("road", ""), - "suburb": address_data.get("suburb", ""), - "city": address_data.get("city", ""), - "state": address_data.get("state", ""), - "county": address_data.get("county", ""), - "country": address_data.get("country", ""), - "pincode": address_data.get("postcode", ""), - "country_code": address_data.get("country_code", ""), - } - else: - frappe.throw("Error: Address details not found in API response") + address_data = data.get("address", {}) + + # Generate Google Plus Code from coordinates + plus_code = olc.encode(float(lat), float(lon)) # Convert lat/lon to float and generate Plus Code + + # Return address data plus the new Plus Code + return { + "road": address_data.get("road", ""), + "suburb": address_data.get("suburb", ""), + "city": address_data.get("city", ""), + "state": address_data.get("state", ""), + "county": address_data.get("county", ""), + "country": address_data.get("country", ""), + "pincode": address_data.get("postcode", ""), + "country_code": address_data.get("country_code", ""), + "plus_code": plus_code # Add the Plus Code to the response + } except requests.exceptions.RequestException as e: frappe.throw(f"Network Error: {str(e)}") diff --git a/contact_address_app/fixtures/client_script.json b/contact_address_app/fixtures/client_script.json new file mode 100644 index 0000000..eb2fe40 --- /dev/null +++ b/contact_address_app/fixtures/client_script.json @@ -0,0 +1,13 @@ +[ + { + "docstatus": 0, + "doctype": "Client Script", + "dt": "Address", + "enabled": 1, + "modified": "2025-03-28 23:55:43.035109", + "module": null, + "name": "Map location", + "script": "frappe.ui.form.on(\"Address\", {\n map: function (frm) {\n let map_data = frm.doc.map;\n if (!map_data) return;\n\n try {\n let jsonData = JSON.parse(map_data);\n let coordinates = jsonData.features[0].geometry.coordinates;\n let lon = coordinates[0], lat = coordinates[1];\n\n frappe.call({\n method: \"contact_address_app.api.get_address_from_coordinates\",\n args: { lat: lat, lon: lon },\n callback: function (r) {\n if (r.message) {\n frm.set_value(\"road\", r.message.road || \"N/A\");\n frm.set_value(\"suburb\", r.message.suburb || \"N/A\");\n frm.set_value(\"city\", r.message.city || \"N/A\");\n frm.set_value(\"state\", r.message.state || \"N/A\");\n frm.set_value(\"county\", r.message.county || \"N/A\");\n frm.set_value(\"country\", r.message.country || \"N/A\");\n frm.set_value(\"pincode\", r.message.pincode || \"N/A\");\n frm.set_value(\"country_code\", r.message.country_code || \"N/A\");\n frm.set_value(\"plus_code\", r.message.plus_code || \"N/A\"); // Set the Plus Code field\n }\n }\n });\n } catch (e) {\n console.error(\"Error parsing map data:\", e);\n }\n }\n});", + "view": "Form" + } +] \ No newline at end of file diff --git a/contact_address_app/hooks.py b/contact_address_app/hooks.py index 307f517..90b34a0 100644 --- a/contact_address_app/hooks.py +++ b/contact_address_app/hooks.py @@ -243,3 +243,6 @@ # } +fixtures = [ + "Client Script" +] From 149a4fee36748b493741126609f6c9648d39f89d Mon Sep 17 00:00:00 2001 From: SUKHPREET SINGH <82471879+sukhlotey@users.noreply.github.com> Date: Tue, 29 Apr 2025 00:37:30 +0530 Subject: [PATCH 5/5] Added customized fields file --- .../fixtures/client_script.json | 4 +- .../fixtures/custom_field.json | 287 ++++++++++++++++++ 2 files changed, 289 insertions(+), 2 deletions(-) create mode 100644 contact_address_app/fixtures/custom_field.json diff --git a/contact_address_app/fixtures/client_script.json b/contact_address_app/fixtures/client_script.json index eb2fe40..0af64db 100644 --- a/contact_address_app/fixtures/client_script.json +++ b/contact_address_app/fixtures/client_script.json @@ -4,10 +4,10 @@ "doctype": "Client Script", "dt": "Address", "enabled": 1, - "modified": "2025-03-28 23:55:43.035109", + "modified": "2025-04-29 00:20:41.803213", "module": null, "name": "Map location", - "script": "frappe.ui.form.on(\"Address\", {\n map: function (frm) {\n let map_data = frm.doc.map;\n if (!map_data) return;\n\n try {\n let jsonData = JSON.parse(map_data);\n let coordinates = jsonData.features[0].geometry.coordinates;\n let lon = coordinates[0], lat = coordinates[1];\n\n frappe.call({\n method: \"contact_address_app.api.get_address_from_coordinates\",\n args: { lat: lat, lon: lon },\n callback: function (r) {\n if (r.message) {\n frm.set_value(\"road\", r.message.road || \"N/A\");\n frm.set_value(\"suburb\", r.message.suburb || \"N/A\");\n frm.set_value(\"city\", r.message.city || \"N/A\");\n frm.set_value(\"state\", r.message.state || \"N/A\");\n frm.set_value(\"county\", r.message.county || \"N/A\");\n frm.set_value(\"country\", r.message.country || \"N/A\");\n frm.set_value(\"pincode\", r.message.pincode || \"N/A\");\n frm.set_value(\"country_code\", r.message.country_code || \"N/A\");\n frm.set_value(\"plus_code\", r.message.plus_code || \"N/A\"); // Set the Plus Code field\n }\n }\n });\n } catch (e) {\n console.error(\"Error parsing map data:\", e);\n }\n }\n});", + "script": "frappe.ui.form.on(\"Address\", {\n custom_map: function (frm) {\n let map_data = frm.doc.custom_map;\n if (!map_data) return;\n\n try {\n let jsonData = JSON.parse(map_data);\n let coordinates = jsonData.features[0].geometry.coordinates;\n let lon = coordinates[0], lat = coordinates[1];\n\n frappe.call({\n method: \"contact_address_app.api.get_address_from_coordinates\",\n args: { lat: lat, lon: lon },\n callback: function (r) {\n if (r.message) {\n frm.set_value(\"custom_road\", r.message.custom_road || \"N/A\");\n frm.set_value(\"custom_suburb\", r.message.custom_suburb || \"N/A\");\n frm.set_value(\"city\", r.message.city || \"N/A\");\n frm.set_value(\"state\", r.message.state || \"N/A\");\n frm.set_value(\"county\", r.message.county || \"N/A\");\n frm.set_value(\"country\", r.message.country || \"N/A\");\n frm.set_value(\"pincode\", r.message.pincode || \"N/A\");\n frm.set_value(\"custom_country_code\", r.message.custom_country_code || \"N/A\");\n frm.set_value(\"custom_plus_code\", r.message.custom_plus_code || \"N/A\"); // Set the Plus Code field\n }\n }\n });\n } catch (e) {\n console.error(\"Error parsing map data:\", e);\n }\n }\n});\n", "view": "Form" } ] \ No newline at end of file diff --git a/contact_address_app/fixtures/custom_field.json b/contact_address_app/fixtures/custom_field.json new file mode 100644 index 0000000..5978ee4 --- /dev/null +++ b/contact_address_app/fixtures/custom_field.json @@ -0,0 +1,287 @@ +[ + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Address", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "custom_suburb", + "fieldtype": "Data", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "address_line1", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Suburb", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2025-04-28 23:45:04.762452", + "module": null, + "name": "Address-custom_suburb", + "no_copy": 0, + "non_negative": 0, + "options": null, + "permlevel": 0, + "placeholder": null, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 1, + "unique": 0, + "width": null + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Address", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "custom_road", + "fieldtype": "Data", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "custom_suburb", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Road", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2025-04-28 23:45:05.010560", + "module": null, + "name": "Address-custom_road", + "no_copy": 0, + "non_negative": 0, + "options": null, + "permlevel": 0, + "placeholder": null, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 1, + "unique": 0, + "width": null + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Address", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "custom_country_code", + "fieldtype": "Data", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "country", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Country Code", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2025-04-28 23:45:05.275325", + "module": null, + "name": "Address-custom_country_code", + "no_copy": 0, + "non_negative": 0, + "options": null, + "permlevel": 0, + "placeholder": null, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 1, + "unique": 0, + "width": null + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Address", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "custom_plus_code", + "fieldtype": "Data", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "custom_country_code", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Plus Code", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2025-04-28 23:45:05.472827", + "module": null, + "name": "Address-custom_plus_code", + "no_copy": 0, + "non_negative": 0, + "options": null, + "permlevel": 0, + "placeholder": null, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 1, + "unique": 0, + "width": null + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Address", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "custom_map", + "fieldtype": "Geolocation", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "disabled", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Map", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2025-04-28 23:45:05.662251", + "module": null, + "name": "Address-custom_map", + "no_copy": 0, + "non_negative": 0, + "options": null, + "permlevel": 0, + "placeholder": null, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + } +] \ No newline at end of file