Newer
Older
import logging
import time
import requests
from odoo.exceptions import ValidationError
try:
from urllib.parse import urljoin
except ImportError:
from urlparse import urljoin
logger = logging.getLogger(__name__)
class Redner(object):
def __init__(self, api_key, server_url, account, timeout):
"""Initialize the API client
Args:
api_key(str): provide your Redner API key.
server_url(str): Redner server URL.
timeout(float): Timeout per Redner call, in seconds.
self.api_key = api_key
self.server_url = server_url
self.timeout = timeout
self.session = requests.sessions.Session()
self.templates = Templates(self)
def call(self, path, http_verb="post", **params):
"""Call redner with the specified paramters.
Delegate to ``call_impl``; this is a wrapper to have some retries
before giving up as redner sometimes mistakenly rejects our queries.
"""
MAX_REDNERD_TRIES = 3
for retry_counter in range(MAX_REDNERD_TRIES):
try:
return self.call_impl(path, http_verb=http_verb, **params)
except Exception as error:
if retry_counter == MAX_REDNERD_TRIES - 1:
raise error
def call_impl(self, path, http_verb="post", **params):
"""Actually make the API call with the given params -
this should only be called by the namespace methods
Args:
path(str): URL path to query, eg. '/template/'
http_verb(str): http verb to use, default: 'post'
params(dict): json payload
This method can raise anything; callers are expected to catch.
"""
if not self.server_url:
raise ValidationError(
"Cannot find redner config url. "
"Please add it in odoo.conf or in ir.config_parameter"
)
logger.info("Redner: Calling %s...", _http_verb)
logger.debug("Redner: Sending to %s > %s", url, params)
start = time.time()
r = getattr(self.session, http_verb, "post")(
url,
json=params,
headers={"Rednerd-API-Key": self.api_key},
timeout=self.timeout,
)
complete_time = time.time() - start
logger.info(
"Redner: Received %s in %.2fms.",
r.status_code,
complete_time * 1000,
try:
response = r.json()
except Exception:
# If we cannot decode JSON then it's an API error
# having response as text could help debuggin with sentry
response = r.text
if not str(r.status_code).startswith("2"):
logger.error("Bad response from Redner: %s" % response)
raise ValidationError("Unexpected redner error: %r" % response)
return r.json()
def ping(self):
"""Try to establish a connection to server"""
conn = self.session.get(self.server_url, timeout=self.timeout)
if conn.status_code != requests.codes.ok:
raise ValidationError("Cannot Establish a connection to server")
return conn
def __repr__(self):
return "<Redner %s>" % self.api_key
class Templates(object):
def __init__(self, master):
self.master = master
def render(
self,
template_id,
data,
accept="text/html",
body_format="base64",
metadata=None,
):
"""Inject content and optionally merge fields into a template,
returning the HTML that results.
Args:
template_id(str): Redner template ID.
data(dict): Template variables.
accept: format of a request or response body data.
body_format (string): The body attribute format.
Can be 'text' or 'base64'. Default 'base64',
metadata (dict):
Returns:
Array of dictionaries: API response
"""
if isinstance(data, dict):
data = [data]
params = {
"accept": accept,
"data": data,
"template": {"account": self.master.account, "name": template_id},
"body-format": body_format,
"metadata": metadata or {},
}
return self.master.call("v1/render", http_verb="post", **params)
def account_template_add(
self,
language,
body,

adrien.bonamy
committed
produces="text/html",

adrien.bonamy
committed
version="N/A",
):
"""Store template in Redner
Args:
name(string): Name of your template. This is to help the user find
its templates in a list.
language(string): Language your template is written with.

adrien.bonamy
committed
Can be mustache, handlebar or od+mustache.

adrien.bonamy
committed
produces(string): Can be text/html or
body_format (string): The body attribute format. Can be 'text' or
'base64'. Default 'base64'
locale(string):

adrien.bonamy
committed
version(string):
Returns:
name(string): Redner template Name.
"""
params = {
"name": name,
"language": language,
"body": body,
"produces": produces,
"body-format": body_format,
"locale": locale,
"version": version,
}
res = self.master.call(
"v1/template/%s" % self.master.account, http_verb="post", **params
)
return res["name"]
def account_template_update(
self,

adrien.bonamy
committed
name="",
produces="text/html",

adrien.bonamy
committed
version="N/A",
template_id(string): Name of your template.
This is to help the user find its templates in a list.
name(string): The new template name (optional)
language(string): Language your template is written with.

adrien.bonamy
committed
Can be mustache, handlebar or od+mustache

adrien.bonamy
committed
produces(string): Can be text/html or
body_format (string): The body attribute format. Can be 'text' or
'base64'. Default 'base64'
locale(string):

adrien.bonamy
committed
version(string):
Returns:
name(string): Redner template Name.
"""
params = {
"name": name,
"language": language,
"body": body,
"produces": produces,
"body-format": body_format,
"locale": locale,
"version": version,
}
res = self.master.call(
"v1/template/%s/%s" % (self.master.account, template_id),
http_verb="put",
**params
)
return res["name"]
def account_template_delete(self, name):
"""Delete a given template name
Args:
name(string): Redner template Name.
Returns:
dict: API response.
"""
return self.master.call(
"v1/template/%s/%s" % (self.master.account, name),
http_verb="delete",
)

adrien.bonamy
committed
def account_template_varlist(self, name):
"""Extract the list of variables present in the template.
The list is not quaranteed to be accurate depending on the
template language.
Args:
name(string): Redner template name.
Returns:
dict: API response.
"""
params = {"account": self.master.account, "name": name}
return self.master.call("v1/varlist", **params)