Package s3 :: Package sync_adapter :: Module ccrm
[frames] | no frames]

Source Code for Module s3.sync_adapter.ccrm

  1  # -*- coding: utf-8 -*- 
  2   
  3  """ S3 Synchronization: Peer Repository Adapter 
  4   
  5      @copyright: 2012-2019 (c) Sahana Software Foundation 
  6      @license: MIT 
  7   
  8      Permission is hereby granted, free of charge, to any person 
  9      obtaining a copy of this software and associated documentation 
 10      files (the "Software"), to deal in the Software without 
 11      restriction, including without limitation the rights to use, 
 12      copy, modify, merge, publish, distribute, sublicense, and/or sell 
 13      copies of the Software, and to permit persons to whom the 
 14      Software is furnished to do so, subject to the following 
 15      conditions: 
 16   
 17      The above copyright notice and this permission notice shall be 
 18      included in all copies or substantial portions of the Software. 
 19   
 20      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 21      EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
 22      OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 23      NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
 24      HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 25      WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 26      FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
 27      OTHER DEALINGS IN THE SOFTWARE. 
 28  """ 
 29   
 30  import sys 
 31  import urllib, urllib2 
 32   
 33  from gluon import * 
 34  from gluon.storage import Storage 
 35   
 36  from ..s3sync import S3SyncBaseAdapter 
 37   
 38  # ============================================================================= 
39 -class S3SyncAdapter(S3SyncBaseAdapter):
40 """ 41 CiviCRM Synchronization Adapter 42 43 @status: experimental 44 """ 45 46 # Resource map 47 RESOURCE = { 48 "pr_person": { 49 "q": "civicrm/contact", 50 "contact_type": "Individual" 51 }, 52 } 53 54 # ------------------------------------------------------------------------- 55 # Methods to be implemented by subclasses: 56 # -------------------------------------------------------------------------
57 - def register(self):
58 """ 59 Register this site at the peer repository 60 61 @return: True to indicate success, otherwise False 62 """ 63 64 # CiviCRM does not support via-web peer registration 65 return True
66 67 # -------------------------------------------------------------------------
68 - def login(self):
69 """ 70 Login at the peer repository 71 72 @return: None if successful, otherwise the error 73 """ 74 75 _debug = current.log.debug 76 77 _debug("S3SyncCiviCRM.login()") 78 79 repository = self.repository 80 81 request = { 82 "q": "civicrm/login", 83 "name": repository.username, 84 "pass": repository.password, 85 } 86 response, error = self._send_request(**request) 87 88 if error: 89 _debug("S3SyncCiviCRM.login FAILURE: %s" % error) 90 return error 91 92 api_key = response.findall("//api_key") 93 if len(api_key): 94 self.api_key = api_key[0].text 95 else: 96 error = "No API Key returned by CiviCRM" 97 _debug("S3SyncCiviCRM.login FAILURE: %s" % error) 98 return error 99 PHPSESSID = response.findall("//PHPSESSID") 100 if len(PHPSESSID): 101 self.PHPSESSID = PHPSESSID[0].text 102 else: 103 error = "No PHPSESSID returned by CiviCRM" 104 _debug("S3SyncCiviCRM.login FAILURE: %s" % error) 105 return error 106 107 _debug("S3SyncCiviCRM.login SUCCESS") 108 return None
109 110 # -------------------------------------------------------------------------
111 - def pull(self, task, onconflict=None):
112 """ 113 Fetch updates from the peer repository and import them 114 into the local database (active pull) 115 116 @param task: the synchronization task (sync_task Row) 117 @param onconflict: callback for automatic conflict resolution 118 119 @return: tuple (error, mtime), with error=None if successful, 120 else error=message, and mtime=modification timestamp 121 of the youngest record sent 122 """ 123 124 xml = current.xml 125 _debug = current.log.debug 126 repository = self.repository 127 log = repository.log 128 resource_name = task.resource_name 129 130 _debug("S3SyncCiviCRM.pull(%s, %s)" % (repository.url, resource_name)) 131 132 mtime = None 133 message = "" 134 remote = False 135 136 # Construct the request 137 if resource_name not in self.RESOURCE: 138 result = log.FATAL 139 message = "Resource type %s currently not supported for CiviCRM synchronization" % \ 140 resource_name 141 output = xml.json_message(False, 400, message) 142 else: 143 args = Storage(self.RESOURCE[resource_name]) 144 args["q"] += "/get" 145 146 tree, error = self._send_request(method="GET", **args) 147 if error: 148 149 result = log.FATAL 150 remote = True 151 message = error 152 output = xml.json_message(False, 400, error) 153 154 elif len(tree.getroot()): 155 156 result = log.SUCCESS 157 remote = False 158 159 # Get import strategy and update policy 160 strategy = task.strategy 161 update_policy = task.update_policy 162 conflict_policy = task.conflict_policy 163 164 # Import stylesheet 165 folder = current.request.folder 166 import os 167 stylesheet = os.path.join(folder, 168 "static", 169 "formats", 170 "ccrm", 171 "import.xsl") 172 173 # Host name of the peer, 174 # used by the import stylesheet 175 import urlparse 176 hostname = urlparse.urlsplit(repository.url).hostname 177 178 # Import the data 179 resource = current.s3db.resource(resource_name) 180 if onconflict: 181 onconflict_callback = lambda item: onconflict(item, 182 repository, 183 resource) 184 else: 185 onconflict_callback = None 186 count = 0 187 success = True 188 try: 189 success = resource.import_xml(tree, 190 stylesheet=stylesheet, 191 ignore_errors=True, 192 strategy=strategy, 193 update_policy=update_policy, 194 conflict_policy=conflict_policy, 195 last_sync=task.last_pull, 196 onconflict=onconflict_callback, 197 site=hostname) 198 count = resource.import_count 199 except IOError, e: 200 result = log.FATAL 201 message = "%s" % e 202 output = xml.json_message(False, 400, message) 203 mtime = resource.mtime 204 205 # Log all validation errors 206 if resource.error_tree is not None: 207 result = log.WARNING 208 message = "%s" % resource.error 209 for element in resource.error_tree.findall("resource"): 210 for field in element.findall("data[@error]"): 211 error_msg = field.get("error", None) 212 if error_msg: 213 msg = "(UID: %s) %s.%s=%s: %s" % \ 214 (element.get("uuid", None), 215 element.get("name", None), 216 field.get("field", None), 217 field.get("value", field.text), 218 field.get("error", None)) 219 message = "%s, %s" % (message, msg) 220 221 # Check for failure 222 if not success: 223 result = log.FATAL 224 if not message: 225 message = "%s" % resource.error 226 output = xml.json_message(False, 400, message) 227 mtime = None 228 229 # ...or report success 230 elif not message: 231 message = "Data imported successfully (%s records)" % count 232 output = None 233 234 else: 235 # No data received from peer 236 result = log.ERROR 237 remote = True 238 message = "No data received from peer" 239 output = None 240 241 # Log the operation 242 log.write(repository_id=repository.id, 243 resource_name=resource_name, 244 transmission=log.OUT, 245 mode=log.PULL, 246 action=None, 247 remote=remote, 248 result=result, 249 message=message) 250 251 _debug("S3SyncCiviCRM.pull import %s: %s" % (result, message)) 252 return (output, mtime)
253 254 # -------------------------------------------------------------------------
255 - def push(self, task):
256 """ 257 Extract new updates from the local database and send 258 them to the peer repository (active push) 259 260 @param task: the synchronization task (sync_task Row) 261 262 @return: tuple (error, mtime), with error=None if successful, 263 else error=message, and mtime=modification timestamp 264 of the youngest record sent 265 """ 266 267 xml = current.xml 268 _debug = current.log.debug 269 repository = self.repository 270 271 log = repository.log 272 resource_name = task.resource_name 273 274 _debug("S3SyncCiviCRM.push(%s, %s)" % (repository.url, resource_name)) 275 276 result = log.FATAL 277 remote = False 278 message = "Push to CiviCRM currently not supported" 279 output = xml.json_message(False, 400, message) 280 281 # Log the operation 282 log.write(repository_id=repository.id, 283 resource_name=resource_name, 284 transmission=log.OUT, 285 mode=log.PUSH, 286 action=None, 287 remote=remote, 288 result=result, 289 message=message) 290 291 _debug("S3SyncCiviCRM.push export %s: %s" % (result, message)) 292 return(output, None)
293 294 # ------------------------------------------------------------------------- 295 # Internal methods: 296 # -------------------------------------------------------------------------
297 - def _send_request(self, method="GET", **args):
298 299 repository = self.repository 300 config = repository.config 301 302 # Authentication 303 args = Storage(args) 304 if hasattr(self, "PHPSESSID") and self.PHPSESSID: 305 args["PHPSESSID"] = self.PHPSESSID 306 if hasattr(self, "api_key") and self.api_key: 307 args["api_key"] = self.api_key 308 if repository.site_key: 309 args["key"] = repository.site_key 310 311 # Create the request 312 url = repository.url + "?" + urllib.urlencode(args) 313 req = urllib2.Request(url=url) 314 handlers = [] 315 316 # Proxy handling 317 proxy = repository.proxy or config.proxy or None 318 if proxy: 319 current.log.debug("using proxy=%s", proxy) 320 proxy_handler = urllib2.ProxyHandler({protocol: proxy}) 321 handlers.append(proxy_handler) 322 323 # Install all handlers 324 if handlers: 325 opener = urllib2.build_opener(*handlers) 326 urllib2.install_opener(opener) 327 328 # Execute the request 329 response = None 330 message = None 331 332 try: 333 if method == "POST": 334 f = urllib2.urlopen(req, data="") 335 else: 336 f = urllib2.urlopen(req) 337 except urllib2.HTTPError, e: 338 message = "HTTP %s: %s" % (e.code, e.reason) 339 else: 340 # Parse the response 341 tree = current.xml.parse(f) 342 root = tree.getroot() 343 is_error = root.xpath("//ResultSet[1]/Result[1]/is_error") 344 if len(is_error) and int(is_error[0].text): 345 error = root.xpath("//ResultSet[1]/Result[1]/error_message") 346 if len(error): 347 message = error[0].text 348 else: 349 message = "Unknown error" 350 else: 351 response = tree 352 353 return response, message
354 355 # End ========================================================================= 356