diff --git a/addons/.bzrignore b/addons/.bzrignore
index 8d98f9debde..5fee3e96f5d 100644
--- a/addons/.bzrignore
+++ b/addons/.bzrignore
@@ -1 +1,5 @@
.*
+pad/__init__.pyc
+pad/ir_attachment.pyc
+pad/res_company.pyc
+web_etherpad
diff --git a/addons/pad/__init__.py b/addons/pad/__init__.py
index 663cac2cbf0..ee80bcaf8d0 100644
--- a/addons/pad/__init__.py
+++ b/addons/pad/__init__.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
+import pad
import res_company
-import ir_attachment
+
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/pad/__openerp__.py b/addons/pad/__openerp__.py
index 267f595c7e5..bc4159089d7 100644
--- a/addons/pad/__openerp__.py
+++ b/addons/pad/__openerp__.py
@@ -25,6 +25,7 @@ Lets the company customize which Pad installation should be used to link to new
"static/src/xml/*.xml",
],
'images': ['static/src/img/pad_link_companies.jpeg'],
+ "css": ['static/src/css/etherpad.css',],
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/pad/etherpad.py b/addons/pad/etherpad.py
new file mode 100644
index 00000000000..547720c1cc8
--- /dev/null
+++ b/addons/pad/etherpad.py
@@ -0,0 +1,254 @@
+#!/usr/bin/env python
+"""Module to talk to EtherpadLite API."""
+
+import json
+import urllib
+import urllib2
+
+
+class EtherpadLiteClient:
+ """Client to talk to EtherpadLite API."""
+ API_VERSION = 1 # TODO probably 1.1 sometime soon
+
+ CODE_OK = 0
+ CODE_INVALID_PARAMETERS = 1
+ CODE_INTERNAL_ERROR = 2
+ CODE_INVALID_FUNCTION = 3
+ CODE_INVALID_API_KEY = 4
+ TIMEOUT = 20
+
+ apiKey = ""
+ baseUrl = ""
+
+ def __init__(self, apiKey=None, baseUrl=None):
+ if apiKey:
+ self.apiKey = apiKey
+
+ if baseUrl:
+ self.baseUrl = baseUrl
+
+ def call(self, function, arguments=None):
+ """Create a dictionary of all parameters"""
+ url = '%s/%d/%s' % (self.baseUrl, self.API_VERSION, function)
+
+ params = arguments or {}
+ params.update({'apikey': self.apiKey})
+ data = urllib.urlencode(params, True)
+
+ try:
+ opener = urllib2.build_opener()
+ request = urllib2.Request(url=url, data=data)
+ response = opener.open(request, timeout=self.TIMEOUT)
+ result = response.read()
+ response.close()
+ except urllib2.HTTPError:
+ raise
+
+ result = json.loads(result)
+ if result is None:
+ raise ValueError("JSON response could not be decoded")
+
+ return self.handleResult(result)
+
+ def handleResult(self, result):
+ """Handle API call result"""
+ if 'code' not in result:
+ raise Exception("API response has no code")
+ if 'message' not in result:
+ raise Exception("API response has no message")
+
+ if 'data' not in result:
+ result['data'] = None
+
+ if result['code'] == self.CODE_OK:
+ return result['data']
+ elif result['code'] == self.CODE_INVALID_PARAMETERS or result['code'] == self.CODE_INVALID_API_KEY:
+ raise ValueError(result['message'])
+ elif result['code'] == self.CODE_INTERNAL_ERROR:
+ raise Exception(result['message'])
+ elif result['code'] == self.CODE_INVALID_FUNCTION:
+ raise Exception(result['message'])
+ else:
+ raise Exception("An unexpected error occurred whilst handling the response")
+
+ # GROUPS
+ # Pads can belong to a group. There will always be public pads that do not belong to a group (or we give this group the id 0)
+
+ def createGroup(self):
+ """creates a new group"""
+ return self.call("createGroup")
+
+ def createGroupIfNotExistsFor(self, groupMapper):
+ """this functions helps you to map your application group ids to etherpad lite group ids"""
+ return self.call("createGroupIfNotExistsFor", {
+ "groupMapper": groupMapper
+ })
+
+ def deleteGroup(self, groupID):
+ """deletes a group"""
+ return self.call("deleteGroup", {
+ "groupID": groupID
+ })
+
+ def listPads(self, groupID):
+ """returns all pads of this group"""
+ return self.call("listPads", {
+ "groupID": groupID
+ })
+
+ def createGroupPad(self, groupID, padName, text=''):
+ """creates a new pad in this group"""
+ params = {
+ "groupID": groupID,
+ "padName": padName,
+ }
+ if text:
+ params['text'] = text
+ return self.call("createGroupPad", params)
+
+ # AUTHORS
+ # Theses authors are bind to the attributes the users choose (color and name).
+
+ def createAuthor(self, name=''):
+ """creates a new author"""
+ params = {}
+ if name:
+ params['name'] = name
+ return self.call("createAuthor", params)
+
+ def createAuthorIfNotExistsFor(self, authorMapper, name=''):
+ """this functions helps you to map your application author ids to etherpad lite author ids"""
+ params = {
+ 'authorMapper': authorMapper
+ }
+ if name:
+ params['name'] = name
+ return self.call("createAuthorIfNotExistsFor", params)
+
+ # SESSIONS
+ # Sessions can be created between a group and a author. This allows
+ # an author to access more than one group. The sessionID will be set as
+ # a cookie to the client and is valid until a certain date.
+
+ def createSession(self, groupID, authorID, validUntil):
+ """creates a new session"""
+ return self.call("createSession", {
+ "groupID": groupID,
+ "authorID": authorID,
+ "validUntil": validUntil
+ })
+
+ def deleteSession(self, sessionID):
+ """deletes a session"""
+ return self.call("deleteSession", {
+ "sessionID": sessionID
+ })
+
+ def getSessionInfo(self, sessionID):
+ """returns informations about a session"""
+ return self.call("getSessionInfo", {
+ "sessionID": sessionID
+ })
+
+ def listSessionsOfGroup(self, groupID):
+ """returns all sessions of a group"""
+ return self.call("listSessionsOfGroup", {
+ "groupID": groupID
+ })
+
+ def listSessionsOfAuthor(self, authorID):
+ """returns all sessions of an author"""
+ return self.call("listSessionsOfAuthor", {
+ "authorID": authorID
+ })
+
+ # PAD CONTENT
+ # Pad content can be updated and retrieved through the API
+
+ def getText(self, padID, rev=None):
+ """returns the text of a pad"""
+ params = {"padID": padID}
+ if rev is not None:
+ params['rev'] = rev
+ return self.call("getText", params)
+
+ # introduced with pull request merge
+ def getHtml(self, padID, rev=None):
+ """returns the html of a pad"""
+ params = {"padID": padID}
+ if rev is not None:
+ params['rev'] = rev
+ return self.call("getHTML", params)
+
+ def setText(self, padID, text):
+ """sets the text of a pad"""
+ return self.call("setText", {
+ "padID": padID,
+ "text": text
+ })
+
+ def setHtml(self, padID, html):
+ """sets the text of a pad from html"""
+ return self.call("setHTML", {
+ "padID": padID,
+ "html": html
+ })
+
+ # PAD
+ # Group pads are normal pads, but with the name schema
+ # GROUPID$PADNAME. A security manager controls access of them and its
+ # forbidden for normal pads to include a in the name.
+
+ def createPad(self, padID, text=''):
+ """creates a new pad"""
+ params = {
+ "padID": padID,
+ }
+ if text:
+ params['text'] = text
+ return self.call("createPad", params)
+
+ def getRevisionsCount(self, padID):
+ """returns the number of revisions of this pad"""
+ return self.call("getRevisionsCount", {
+ "padID": padID
+ })
+
+ def deletePad(self, padID):
+ """deletes a pad"""
+ return self.call("deletePad", {
+ "padID": padID
+ })
+
+ def getReadOnlyID(self, padID):
+ """returns the read only link of a pad"""
+ return self.call("getReadOnlyID", {
+ "padID": padID
+ })
+
+ def setPublicStatus(self, padID, publicStatus):
+ """sets a boolean for the public status of a pad"""
+ return self.call("setPublicStatus", {
+ "padID": padID,
+ "publicStatus": publicStatus
+ })
+
+ def getPublicStatus(self, padID):
+ """return true of false"""
+ return self.call("getPublicStatus", {
+ "padID": padID
+ })
+
+ def setPassword(self, padID, password):
+ """returns ok or a error message"""
+ return self.call("setPassword", {
+ "padID": padID,
+ "password": password
+ })
+
+ def isPasswordProtected(self, padID):
+ """returns true or false"""
+ return self.call("isPasswordProtected", {
+ "padID": padID
+ })
+
diff --git a/addons/pad/ir_attachment.py b/addons/pad/ir_attachment.py
deleted file mode 100644
index ba8c88632dd..00000000000
--- a/addons/pad/ir_attachment.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-from osv import fields, osv
-import random
-import string
-
-class ir_attachment(osv.osv):
- _inherit = 'ir.attachment'
-
- def pad_generate_url(self, cr, uid, model, id):
- pad_url_template = self.pool.get('res.users').browse(cr,uid,[uid])[0].company_id.pad_url_template
- s = string.ascii_uppercase + string.digits
- salt = ''.join([s[random.randint(0, len(s) - 1)] for i in range(8)])
- template_vars = {
- 'db' : cr.dbname,
- 'model' : model,
- 'id' : id,
- 'salt' : salt,
- 'name' : '',
- }
- return pad_url_template % template_vars
-
- def pad_get(self, cr, uid, model, id):
- attachment = self.search(cr, uid, [('res_model', '=', model), ('res_id', '=', id), ('type', '=', 'url'), ('name', '=', 'Pad')])
- if attachment:
- return self.read(cr, uid, attachment)[0]['url']
- else:
- url = self.pad_generate_url(cr, uid, model, id)
- self.create(cr, uid, {
- 'res_model' : model,
- 'res_id' : id,
- 'type' : 'url',
- 'name' : 'Pad',
- 'url' : url,
- })
- return url
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/pad/pad.py b/addons/pad/pad.py
new file mode 100644
index 00000000000..c7e448767bc
--- /dev/null
+++ b/addons/pad/pad.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+from osv import fields, osv
+import random
+import string
+from etherpad import EtherpadLiteClient
+import urllib2
+from tools.translate import _
+
+
+class pad_common(osv.osv_memory):
+ _name = 'pad.common'
+ _pad_url = None # name of the field for the etherpad
+ def pad_generate_url(self, cr, uid, model, context=None):
+ pad_url_template = self._pad_url_template(cr, uid, context)
+ s = string.ascii_uppercase + string.digits
+ salt = ''.join([s[random.randint(0, len(s) - 1)] for i in range(8)])
+ template_vars = {
+ 'db' : cr.dbname,
+ 'model' : model,
+ 'salt' : salt,
+ }
+ url = pad_url_template % template_vars
+ api_key = self._pad_api_key(cr, uid, context)
+ if api_key:
+ urls = url.split('/')
+ api_url = '/'.join(urls[:3]) + "/api"
+ pad_id = urls[-1]
+ ep_client = EtherpadLiteClient(api_key, api_url)
+ try:
+ ep_client.createPad(pad_id," ")
+ except ValueError as strerror:
+ raise osv.except_osv(_('Configuration Error !'),_("Etherpad Have Wrong API Key."))
+ except urllib2.HTTPError as e:
+ raise osv.except_osv(_('Configuration Error !'),_("Etherpad Have Wrong API URL."))
+ except urllib2.URLError as e:
+ raise osv.except_osv(_('Configuration Error !'),_("Etherpad Have Wrong Pad URL Template."))
+ return url
+
+ def _pad_api_key(self, cr, uid, context=None):
+ return self.pool.get('res.users').browse(cr,uid, uid, context).company_id.etherpad_api_key
+
+ def _pad_url_template(self, cr, uid, context=None):
+ return self.pool.get('res.users').browse(cr,uid, uid, context).company_id.pad_url_template
+
+ def copy(self, cr, uid, id, default=None, context=None):
+ if not default:
+ default = {}
+ default.update({
+ self._pad_url:self.pad_generate_url(cr, uid, self._name),
+ })
+ return super(pad_common, self).copy(cr, uid, id, default, context)
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/pad/res_company.py b/addons/pad/res_company.py
index 0c1dc7c4929..4ce27fd4d22 100644
--- a/addons/pad/res_company.py
+++ b/addons/pad/res_company.py
@@ -1,14 +1,19 @@
# -*- coding: utf-8 -*-
from osv import fields, osv
+PAD_TEMPLATE = 'http://beta.etherpad.org/p/%(db).10s-%(model)s-%(salt)s'
+PAD_API_KEY = 'EtherpadFTW'
+
class company_pad(osv.osv):
_inherit = 'res.company'
_columns = {
'pad_url_template': fields.char('Pad URL Template', size=128, required=True,
help="Template used to generate pad URL."),
+ 'etherpad_api_key': fields.char('Pad API Key', size=128),
}
_defaults = {
- 'pad_url_template': 'http://ietherpad.com/%(db)s-%(model)s-%(id)d-%(salt)s-%(name)s'
+ 'pad_url_template': PAD_TEMPLATE,
+ 'etherpad_api_key': PAD_API_KEY,
}
diff --git a/addons/pad/res_company.xml b/addons/pad/res_company.xml
index 9b1c0656470..96c3c004bef 100644
--- a/addons/pad/res_company.xml
+++ b/addons/pad/res_company.xml
@@ -9,6 +9,7 @@