diff --git a/addons/note/note.py b/addons/note/note.py index fb3f0cee853..9d62f7a2cb7 100644 --- a/addons/note/note.py +++ b/addons/note/note.py @@ -185,5 +185,4 @@ class note_base_config_settings(osv.osv_memory): _columns = { 'module_note_pad': fields.boolean('Use collaborative pads (etherpad)'), 'group_note_fancy': fields.boolean('Use fancy layouts for notes', implied_group='note.group_note_fancy'), - 'group_note_tags': fields.boolean('Allow setting tags on notes', implied_group='note.group_note_tags'), } diff --git a/addons/note/note_demo.xml b/addons/note/note_demo.xml index 16fa5a3f66b..7769e3ee303 100644 --- a/addons/note/note_demo.xml +++ b/addons/note/note_demo.xml @@ -4,28 +4,27 @@ Today 1 - + Tomorrow 2 - + Later 3 - + - Notes + Morning 4 - + - Customer report #349872 Customer report #349872 @@ -35,8 +34,9 @@
* We should use the term Calendar, not Meeting. ]]>
- + 2 +
@@ -44,14 +44,7 @@

* Followed by the telephone conversation and mail about D.544.3 ]]> - -
- - - Call Marc -

]]> -
- +
@@ -67,7 +60,8 @@
* wine ]]> - + +
@@ -79,15 +73,6 @@ - - Read some documentation about OpenERP before diving into the code -

* Open ERP: a modern approach to integrated business management -
* Open ERP for Retail and Industrial Management - ]]> -
- -
- New computer specs

* Motherboard @@ -108,6 +93,7 @@
3 +
@@ -116,16 +102,7 @@
* Open ERP for Retail and Industrial Management ]]> - -
- - - Read some documentation about OpenERP before diving into the code -

* Open ERP: a modern approach to integrated business management -
* Open ERP for Retail and Industrial Management - ]]> -
- +
@@ -134,8 +111,9 @@
* Open ERP for Retail and Industrial Management ]]> - + 7 +
diff --git a/addons/note/note_view.xml b/addons/note/note_view.xml index a92c6abe30a..62b007aab6e 100644 --- a/addons/note/note_view.xml +++ b/addons/note/note_view.xml @@ -101,7 +101,7 @@ - + @@ -130,7 +130,7 @@ - + @@ -159,10 +159,6 @@ diff --git a/addons/note/security/res.groups.csv b/addons/note/security/res.groups.csv index 61c54c964ff..88d7b2cd667 100644 --- a/addons/note/security/res.groups.csv +++ b/addons/note/security/res.groups.csv @@ -1,3 +1,2 @@ id,name,implied_ids/id -group_note_tags,Memo / Display tags, group_note_fancy,Memo / Fancy mode, diff --git a/addons/pad/pad.py b/addons/pad/pad.py index 48ac940ccfa..0cbb6107979 100644 --- a/addons/pad/pad.py +++ b/addons/pad/pad.py @@ -5,24 +5,56 @@ import re import string import urllib2 from tools.translate import _ +from openerp.tools.misc import html2plaintext +from py_etherpad import EtherpadLiteClient class pad_common(osv.osv_memory): _name = 'pad.common' def pad_generate_url(self, cr, uid, context=None): - pad_server = self.pool.get('res.users').browse(cr, uid, uid, context).company_id.pad_server + company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id; + + pad = { + "server" : company.pad_server, + "key" : company.pad_key or "4DxmsNIbnQUVQMW9S9tx2oLOSjFdrx1l", + } + # make sure pad server in the form of http://hostname - if not pad_server: + if not pad["server"]: return '' - if not pad_server.startswith('http'): - pad_server = 'http://' + pad_server - pad_server = pad_server.rstrip('/') + if not pad["server"].startswith('http'): + pad["server"] = 'http://' + pad["server"] + pad["server"] = pad["server"].rstrip('/') # generate a salt s = string.ascii_uppercase + string.digits salt = ''.join([s[random.randint(0, len(s) - 1)] for i in range(10)]) + #path + path = '%s-%s-%s' % (cr.dbname.replace('_','-'), self._name, salt) # contruct the url - url = '%s/p/%s-%s-%s' % (pad_server, cr.dbname.replace('_','-'), self._name, salt) - return url + url = '%s/p/%s' % (pad["server"], path) + + #if create with content + if "field_name" in context and "model" in context and "object_id" in context: + myPad = EtherpadLiteClient( pad["key"], pad["server"]+'/api') + myPad.createPad(path) + + #get attr on the field model + model = self.pool.get(context["model"]) + field = model._all_columns[context['field_name']] + real_field = field.column.pad_content_field + + #get content of the real field + for record in model.browse(cr, uid, [context["object_id"]]): + if record[real_field]: + myPad.setText(path, html2plaintext(record[real_field])) + #Etherpad for html not functional + #myPad.setHTML(path, record[real_field]) + + return { + "server": pad["server"], + "path": path, + "url": url, + } def pad_get_content(self, cr, uid, url, context=None): content = '' @@ -57,7 +89,8 @@ class pad_common(osv.osv_memory): for k,v in self._all_columns: field = v.column if hasattr(field,'pad_content_field'): - default[k] = self.pad_generate_url(cr, uid, context) + pad = self.pad_generate_url(cr, uid, context) + default[k] = pad['url'] 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/py_etherpad/__init__.py b/addons/pad/py_etherpad/__init__.py new file mode 100644 index 00000000000..6af310b48aa --- /dev/null +++ b/addons/pad/py_etherpad/__init__.py @@ -0,0 +1,253 @@ +#!/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 = "http://localhost:9001/api" + + 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/static/src/js/pad.js b/addons/pad/static/src/js/pad.js index b7c13ba1b89..e598ecca4f5 100644 --- a/addons/pad/static/src/js/pad.js +++ b/addons/pad/static/src/js/pad.js @@ -16,10 +16,17 @@ instance.web.form.FieldPad = instance.web.form.AbstractField.extend({ var self = this; var _super = self._super; _super.apply(self,[val]); + if (val === false || val === "") { - self.field_manager.dataset.call('pad_generate_url').then(function(r) { - _super.apply(self,[r]); - self.render_value(); + self.field_manager.dataset.call('pad_generate_url',{context:{ + model: self.field_manager.model, + field_name: self.name, + object_id: self.field_manager.datarecord.id + }}).then(function(data) { + if(data&&data.url){ + _super.apply(self,[data.url]); + self.render_value(); + } }); } else { self.render_value(); @@ -27,7 +34,6 @@ instance.web.form.FieldPad = instance.web.form.AbstractField.extend({ this._dirty_flag = true; }, render_value: function() { - console.log("display"); var self = this; var value = this.get('value');