Skip to content

Instantly share code, notes, and snippets.

@zerg000000
Last active July 3, 2024 18:40
Show Gist options
  • Save zerg000000/9ca413b7481426c2dedde38cb1f51246 to your computer and use it in GitHub Desktop.
Save zerg000000/9ca413b7481426c2dedde38cb1f51246 to your computer and use it in GitHub Desktop.
a demo of using odoo external api
{:paths ["src"]
:deps {org.clojure/clojure {:mvn/version "1.11.1"}
necessary-evil/necessary-evil {:mvn/version "2.0.1"}
org.clojure/algo.monads {:mvn/version "0.2.0"}}}
(require '[necessary-evil.core :as xml-rpc])
;; try https://www.odoo.com/documentation/saas-17.2/developer/reference/external_api.html#ir-model
;; get demo user/pass/db
;; not working, always return 500
(xml-rpc/call (str "https://demo.odoo.com/start") "start")
;; authenticate
;; open https://demo.odoo.com in browser
;; go to settings and enable developer mode
;; you can find the db name at the top right corner under your user name
;; api_url is the url you current visit, should be https://demo<X>.odoo.com
;; login username/password is admin/admin
;; password can be replaced by api-key generated in UI
(def conn (atom {:api-url "https://demo4.odoo.com"
:db "demo_saas-172_4eb78f7ce5a1_1720028869"
:username "admin"
:password "admin"}))
;; uid is what returned by "authenticate", in my case is 2
(xml-rpc/call (str (:api-url @conn) "/xmlrpc/2/common")
"authenticate" (:db @conn) (:username @conn) (:password @conn) {})
;; => 2
;; assoc the uid into conn, so that we can use it in subsequent calls
(swap! conn assoc :uid (xml-rpc/call (str (:api-url @conn) "/xmlrpc/2/common")
"authenticate" (:db @conn) (:username @conn) (:password @conn) {}))
;; test endpoint version
(xml-rpc/call (str (:api-url @conn) "/xmlrpc/2/common") "version")
;; => {:server_version "saas~17.2+e",
;; :server_version_info ["saas~17" 2 0 "final" 0 "e"],
;; :server_serie "saas~17.2",
;; :protocol_version 1}
;; calling method
;; check access right
(xml-rpc/call (str (:api-url @conn) "/xmlrpc/2/object") "execute_kw"
(:db @conn) (:uid @conn) (:password @conn)
"res.partner" "check_access_rights"
["read"] {"raise_exception" false})
;; => true
;; search
(xml-rpc/call (str (:api-url @conn) "/xmlrpc/2/object") "execute_kw"
(:db @conn) (:uid @conn) (:password @conn)
"res.partner" "search"
[[["is_company" "=" true]]])
;; => [15 10 1 11 65 16 88 66 13 12 63 68 14 67 64 9]
;; simplify the odoo calls
(defn try-authenticate!
"Attempt acquire uid if it is not already exists in `conn`"
[conn]
(when-not (contains? @conn :uid)
(swap! conn assoc :uid (xml-rpc/call (str (:api-url @conn) "/xmlrpc/2/common")
"authenticate" (:db @conn) (:username @conn) (:password @conn) {}))))
(defn fields
"Return the definition of each field.
The returned value is a dictionary (indexed by field name) of dictionaries. The _inherits’d fields are included. The string, help, and selection (if present) attributes are translated.
https://www.odoo.com/documentation/saas-17.2/developer/reference/backend/orm.html#id7"
[conn domain & {:keys [allfields attributes]
:or {allfields []
attributes []}}]
(try-authenticate! conn)
(xml-rpc/call (str (:api-url @conn) "/xmlrpc/2/object") "execute_kw"
(:db @conn) (:uid @conn) (:password @conn)
domain "fields_get" allfields attributes))
(defn read
"Read the requested fields for the records in self, and return their values as a list of dicts.
"
[conn domain id fields]
(try-authenticate! conn)
(xml-rpc/call (str (:api-url @conn) "/xmlrpc/2/object") "execute_kw"
(:db @conn) (:uid @conn) (:password @conn)
domain "read" [id] fields))
(defn search
"Search for the records that satisfy the given domain search domain.
https://www.odoo.com/documentation/saas-17.2/developer/reference/backend/orm.html#odoo.models.Model.search
Also see. https://www.odoo.com/documentation/saas-17.2/developer/reference/backend/orm.html#search-domains"
[conn domain search-domains {:keys [limit offset order] :as args}]
(try-authenticate! conn)
(xml-rpc/call (str (:api-url @conn) "/xmlrpc/2/object") "execute_kw"
(:db @conn) (:uid @conn) (:password @conn)
domain "search"
search-domains
args))
(defn search-count
"Returns the number of records in the current model matching the provided domain.
https://www.odoo.com/documentation/saas-17.2/developer/reference/backend/orm.html#odoo.models.Model.search_fetch"
[conn domain search-domains {:keys [limit] :as args}]
(try-authenticate! conn)
(xml-rpc/call (str (:api-url @conn) "/xmlrpc/2/object") "execute_kw"
(:db @conn) (:uid @conn) (:password @conn)
domain "search_count"
search-domains
args))
(defn search-read
"Search for the records that satisfy the given domain search domain, and fetch the given fields to the cache. This method is like a combination of methods search() and fetch(), but it performs both tasks with a minimal number of SQL queries.
https://www.odoo.com/documentation/saas-17.2/developer/reference/backend/orm.html#odoo.models.Model.search_fetch"
[conn domain search-domains {:keys [limit offset order] :as args}]
(try-authenticate! conn)
(xml-rpc/call (str (:api-url @conn) "/xmlrpc/2/object") "execute_kw"
(:db @conn) (:uid @conn) (:password @conn)
domain "search_read"
search-domains
args))
(defn create
"Creates new records for the model.
The new records are initialized using the values from the list of dicts vals_list, and if necessary those from default_get().
https://www.odoo.com/documentation/saas-17.2/developer/reference/backend/orm.html#odoo.models.Model.create"
[conn domain xs]
(try-authenticate! conn)
(xml-rpc/call (str (:api-url @conn) "/xmlrpc/2/object") "execute_kw"
(:db @conn) (:uid @conn) (:password @conn)
domain "create"
xs))
(defn write
"Updates all records in self with the provided values."
[conn domain ids record]
(try-authenticate! conn)
(xml-rpc/call (str (:api-url @conn) "/xmlrpc/2/object") "execute_kw"
(:db @conn) (:uid @conn) (:password @conn)
domain "write"
[ids record]))
(search conn "res.partner" [[["is_company" "=" true]]] {:limit 1 :offset 1})
;; => [10]
(search-read conn "res.partner" [[["is_company" "=" true]]] {:limit 1 :offset 1})
;; => [{:account_represented_company_ids [],
;; :peppol_endpoint false,
;; :website_url "/partners/deco-addict-10",
;; :sla_ids [3],
;; :signup_valid true,
;; :phone_sanitized_blacklisted false,
;; :sale_order_ids [69 65 64 63 62 39 38 36 22 68 66 5 1 21],
;; :signature_count 0,
;; :payment_token_ids [],
;; :purchase_order_count 2,
;; :create_uid [1 "OdooBot"],
;; :property_payment_term_id [4 "30 Days"],
;; :message_partner_ids [],
;; :task_ids [114 113 119 118 117 116 115 112 111 110 109 108 102 101 100 99 98 97 96 95 93 92 91 90 89 88 87 86],
;; :seo_name false,
;; :property_account_position_id false,
;; :contact_address_complete "77 Santa Barbara Rd, 94523 Pleasant Hill, California, United States",
;; :email "info@agrolait.com",
;; :company_type "company",
;; :activity_user_id false,
;; :avalara_show_address_validation false,
;; :avatar_512
;; :ref_company_ids [],
;; :website_meta_keywords false,
;; :total_overdue 36512.5,
;; :online_partner_information false,
;; :pos_order_ids [10],
;; ...}]
(search-count conn "res.partner" [[["is_company" "=" true]]] {:limit 1})
;; => 1
(fields conn "res.partner" [] ["string" "help"])
;; => {:contact_address_complete
;; {:company_dependent false,
;; :store true,
;; :name "contact_address_complete",
;; :readonly true,
;; :type "char",
;; :string "Contact Address Complete",
;; :searchable true,
;; :manual false,
;; :sortable true,
;; :change_default false,
;; :translate false,
;; :default_export_compatible false,
;; :trim true,
;; :required false,
;; :exportable true,
;; :groupable true,
;; :depends ["street" "zip" "city" "country_id"]},
;; :email
;; {:company_dependent false,
;; :store true,
;; :name "email",
;; :readonly false,
;; :type "char",
;; :string "Email",
;; :searchable true,
;; :manual false,
;; :sortable true,
;; :change_default false,
;; :translate false,
;; :default_export_compatible false,
;; :trim true,
;; :required false,
;; :exportable true,
;; :groupable true,
;; :depends []},
;; ...}
(write conn "res.partner" [66] {:name "Newer name 1"})
;; => true
(read conn "res.partner" 66 {:fields ["name"]})
;; => [{:id 66, :name "Newer name 1"}]
(create conn "res.partner" [{:display_name "Created" :name "Mr. X"}])
;; => 93
;; We can also query all the 'domain'(e.g. res.partner) and their metadata
;; ir.model
(def domains
(xml-rpc/call (str (:api-url @conn) "/xmlrpc/2/object") "execute_kw"
(:db @conn) (:uid @conn) "admin"
"ir.model" "search_read"
[[]]))
(->> (map #(select-keys % [:display_name :model :info]) domains)
(sort-by :model))
;; => ({:display_name "Aged Partner Balance Custom Handler",
;; :model "account.aged.partner.balance.report.handler",
;; :info " The base model, which is implicitly inherited by all models. "}
;; {:display_name "Aged Payable Custom Handler",
;; :model "account.aged.payable.report.handler",
;; :info " The base model, which is implicitly inherited by all models. "}
;; ...)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment