def notify(cls, resource_id): """ Asynchronous task to notify a subscriber about updates, runs a POST?format=msg request against the subscribed controller which extracts the data and renders and sends the notification message (see send()). @param resource_id: the pr_subscription_resource record ID """ _debug = current.log.debug _debug("S3Notifications.notify(resource_id=%s)" % resource_id) db = current.db s3db = current.s3db stable = s3db.pr_subscription rtable = db.pr_subscription_resource ftable = s3db.pr_filter # Extract the subscription data join = stable.on(rtable.subscription_id == stable.id) left = ftable.on(ftable.id == stable.filter_id) # @todo: should not need rtable.resource here row = db(rtable.id == resource_id).select(stable.id, stable.pe_id, stable.frequency, stable.notify_on, stable.method, stable.email_format, stable.attachment, rtable.id, rtable.resource, rtable.url, rtable.last_check_time, ftable.query, join=join, left=left).first() if not row: return True s = getattr(row, "pr_subscription") r = getattr(row, "pr_subscription_resource") f = getattr(row, "pr_filter") # Create a temporary token to authorize the lookup request auth_token = str(uuid4()) # Store the auth_token in the subscription record r.update_record(auth_token=auth_token) db.commit() # Construct the send-URL public_url = current.deployment_settings.get_base_public_url() lookup_url = "%s/%s/%s" % (public_url, current.request.application, r.url.lstrip("/")) # Break up the URL into its components purl = list(urlparse.urlparse(lookup_url)) # Subscription parameters last_check_time = s3_encode_iso_datetime(r.last_check_time) query = {"subscription": auth_token, "format": "msg"} if "upd" in s.notify_on: query["~.modified_on__ge"] = last_check_time else: query["~.created_on__ge"] = last_check_time # Filters if f.query: from s3filter import S3FilterString resource = s3db.resource(r.resource) fstring = S3FilterString(resource, f.query) for k, v in fstring.get_vars.iteritems(): if v is not None: if k in query: value = query[k] if type(value) is list: value.append(v) else: query[k] = [value, v] else: query[k] = v query_nice = s3_unicode(fstring.represent()) else: query_nice = None # Add subscription parameters and filters to the URL query, and # put the URL back together query = urlencode(query) if purl[4]: query = "&".join((purl[4], query)) page_url = urlparse.urlunparse([ purl[0], # scheme purl[1], # netloc purl[2], # path purl[3], # params query, # query purl[5], # fragment ]) # Serialize data for send (avoid second lookup in send) data = json.dumps({ "pe_id": s.pe_id, "notify_on": s.notify_on, "method": s.method, "email_format": s.email_format, "attachment": s.attachment, "resource": r.resource, "last_check_time": last_check_time, "filter_query": query_nice, "page_url": lookup_url, "item_url": None, }) # Send the request _debug("Requesting %s" % page_url) req = urllib2.Request(page_url, data=data) req.add_header("Content-Type", "application/json") success = False try: response = json.loads(urllib2.urlopen(req).read()) message = response["message"] if response["status"] == "success": success = True except urllib2.HTTPError, e: message = ("HTTP %s: %s" % (e.code, e.read()))
def notify(cls, resource_id): """ Asynchronous task to notify a subscriber about updates, runs a POST?format=msg request against the subscribed controller which extracts the data and renders and sends the notification message (see send()). @param resource_id: the pr_subscription_resource record ID """ _debug("S3Notifications.notify(resource_id=%s)", resource_id) db = current.db s3db = current.s3db stable = s3db.pr_subscription rtable = db.pr_subscription_resource ftable = s3db.pr_filter # Extract the subscription data join = stable.on(rtable.subscription_id == stable.id) left = ftable.on(ftable.id == stable.filter_id) # @todo: should not need rtable.resource here row = db(rtable.id == resource_id).select(stable.id, stable.pe_id, stable.frequency, stable.notify_on, stable.method, stable.email_format, rtable.id, rtable.resource, rtable.url, rtable.last_check_time, ftable.query, join=join, left=left).first() if not row: return True s = getattr(row, "pr_subscription") r = getattr(row, "pr_subscription_resource") f = getattr(row, "pr_filter") # Create a temporary token to authorize the lookup request auth_token = str(uuid4()) # Store the auth_token in the subscription record r.update_record(auth_token=auth_token) db.commit() # Construct the send-URL settings = current.deployment_settings public_url = settings.get_base_public_url() lookup_url = "%s/%s/%s" % (public_url, current.request.application, r.url.lstrip("/")) # Break up the URL into its components purl = list(urlparse.urlparse(lookup_url)) # Subscription parameters last_check_time = s3_encode_iso_datetime(r.last_check_time) query = {"subscription": auth_token, "format": "msg"} if "upd" in s.notify_on: query["~.modified_on__ge"] = last_check_time else: query["~.created_on__ge"] = last_check_time # Filters if f.query: from s3filter import S3FilterString resource = s3db.resource(r.resource) fstring = S3FilterString(resource, f.query) for k, v in fstring.get_vars.iteritems(): if v is not None: if k in query: value = query[k] if type(value) is list: value.append(v) else: query[k] = [value, v] else: query[k] = v query_nice = s3_unicode(fstring.represent()) else: query_nice = None # Add subscription parameters and filters to the URL query, and # put the URL back together query = urlencode(query) if purl[4]: query = "&".join((purl[4], query)) page_url = urlparse.urlunparse([purl[0], # scheme purl[1], # netloc purl[2], # path purl[3], # params query, # query purl[5], # fragment ]) # Serialize data for send (avoid second lookup in send) data = json.dumps({"pe_id": s.pe_id, "notify_on": s.notify_on, "method": s.method, "email_format": s.email_format, "resource": r.resource, "last_check_time": last_check_time, "filter_query": query_nice, "page_url": lookup_url, "item_url": None, }) # Send the request _debug("Requesting %s", page_url) req = urllib2.Request(page_url, data=data) req.add_header("Content-Type", "application/json") success = False try: response = json.loads(urllib2.urlopen(req).read()) message = response["message"] if response["status"] == "success": success = True except urllib2.HTTPError, e: message = ("HTTP %s: %s" % (e.code, e.read()))
def mdata_export(r, **attr): """ Provide data for mobile app @param r: the S3Request instance @param attr: controller attributes @returns: a JSON string """ ID = "id" PID = "pe_id" UID = current.xml.UID s3db = current.s3db resource = r.resource resource_tablename = resource.tablename # Output in mdata format output = {resource_tablename: []} # Lookup to ensure we extract ID, UID fields & joining FKs for all tables tablenames = [resource_tablename] # Keep track of which FKs we have so that we can replace them with UID fks = {resource_tablename: {}} # Default to the 'mobile_list_fields' setting list_fields = resource.get_config("mobile_list_fields") if not list_fields: # Fallback to the 'list_fields' setting list_fields = resource.get_config("list_fields") if not list_fields: # Fallback to all readable fields list_fields = [field.name for field in resource.readable_fields()] else: # Ensure that we have all UID fields included # Ensure we have FKs between all linked records components = resource.components def find_fks(join): """ Helper function to get the FKs from a Join """ for q in (join.first, join.second): if isinstance(q, Field): if q.referent: # FK if q.tablename == resource_tablename: fieldname = q.name if fieldname not in list_fields: list_fields.append(fieldname) fks[resource_tablename][fieldname] = str( q.referent) else: tn = q.tablename if tn not in fks: fks[tn] = {} fks[tn][q.name] = str(q.referent) elif isinstance(q, Query): find_fks(q) for selector in list_fields: rfield = S3ResourceField(resource, selector) tablename = rfield.tname if tablename not in tablenames: output[tablename] = [] tablenames.append(tablename) # Find the FKs if tablename in fks: this_fks = fks[tablename] else: fks[tablename] = this_fks = {} for tn in rfield.join: join = rfield.join[tn] find_fks(join) # Add the ID, UUID & any FKs to list_fields if "." in selector: component, fieldname = selector.split(".", 1) id_field = "%s.%s" % (component, ID) if id_field not in list_fields: list_fields.append(id_field) uuid_field = "%s.%s" % (component, UID) if uuid_field not in list_fields: list_fields.append(uuid_field) if "$" in fieldname: fk, fieldname = fieldname.split("$", 1) fk_field = "%s.%s" % (component, fk) if fk_field not in list_fields: list_fields.append(fk_field) ctablename = components[ component].table._tablename # Format to handle Aliases if ctablename not in fks: fks[ctablename] = {} #referent = s3db[ctablename][fk].referent if fk not in fks[ctablename]: #fks[ctablename][fk] = str(referent) fks[ctablename][fk] = str( s3db[ctablename][fk].referent) id_field = "%s.%s$%s" % (component, fk, ID) if id_field not in list_fields: list_fields.append(id_field) uuid_field = "%s.%s$%s" % (component, fk, UID) if uuid_field not in list_fields: list_fields.append(uuid_field) # Restore once list_fields working #if "$" in fieldname: # # e.g. address.location_id$parent$uuid # fk2, fieldname = fieldname.split("$", 1) # tablename = referent.tablename # if fk2 not in fks[tablename]: # fks[tablename][fk2] = str(s3db[tablename][fk2].referent) # id_field = "%s.%s$%s$%s" % (component, fk, fk2, ID) # if id_field not in list_fields: # list_fields.append(id_field) # uuid_field = "%s.%s$%s$%s" % (component, fk, fk2, UID) # if uuid_field not in list_fields: # list_fields.append(uuid_field) else: for fk in this_fks: fk_field = "%s.%s" % (component, fk) if fk_field not in list_fields: list_fields.append(fk_field) elif "$" in selector: fk, fieldname = selector.split("$", 1) if fk not in list_fields: list_fields.append(fk) #referent = s3db[resource_tablename][fk].referent if fk not in fks[resource_tablename]: #fks[resource_tablename][fk] = str(referent) fks[resource_tablename][fk] = str( s3db[resource_tablename][fk].referent) id_field = "%s$%s" % (fk, ID) if id_field not in list_fields: list_fields.append(id_field) uuid_field = "%s$%s" % (fk, UID) if uuid_field not in list_fields: list_fields.append(uuid_field) # Restore once list_fields working #if "$" in fieldname: # # e.g. location_id$parent$uuid # fk2, fieldname = fieldname.split("$", 1) # tablename = referent.tablename # if fk2 not in fks[tablename]: # fks[tablename][fk2] = str(s3db[tablename][fk2].referent) # id_field = "%s$%s$%s" % (fk, fk2, ID) # if id_field not in list_fields: # list_fields.append(id_field) # uuid_field = "%s$%s$%s" % (fk, fk2, UID) # if uuid_field not in list_fields: # list_fields.append(uuid_field) if ID not in list_fields: list_fields.append(ID) if UID not in list_fields: list_fields.append(UID) data = resource.select(list_fields, raw_data=True) rows = data.rows # Build lookups of IDs to UIDs & PIDs to UIDs id_lookup = {} pid_lookup = {} for record in rows: tablenames = {} for field in record: value = record[field] tablename, field = field.split(".", 1) if tablename not in tablenames: tablenames[tablename] = { ID: None, PID: None, UID: None, } if field == ID: tablenames[tablename][ID] = value elif field == PID: tablenames[tablename][PID] = value elif field == UID: tablenames[tablename][UID] = value for tablename in tablenames: if tablename not in id_lookup: id_lookup[tablename] = {} id_lookup[tablename][tablenames[tablename] [ID]] = tablenames[tablename][UID] pid = tablenames[tablename][PID] if pid: pid_lookup[pid] = tablenames[tablename][UID] # Convert to S3Mobile format # & replace FKs with UUID for record in rows: # Keep track of which tables have no data empty = [] row = {tablename: [] for tablename in tablenames} for field in record: value = record[field] tablename, field = field.split(".", 1) if field == ID: # Don't include these in output continue this_fks = fks[tablename] if field in this_fks: if value: # Replace ID with UUID: referent = this_fks[field] tn, fn = referent.split(".", 1) if fn == PID: value = pid_lookup[value] else: value = id_lookup[tn][value] elif field == "uuid": if value is None: empty.append(tablename) continue elif isinstance(value, datetime.date) or \ isinstance(value, datetime.datetime): value = s3_encode_iso_datetime(value).decode("utf-8") row[tablename].append((field, value)) for tablename in tablenames: if tablename not in empty: output[tablename].append(row[tablename]) output = json.dumps(output, separators=SEPARATORS) current.response.headers = {"Content-Type": "application/json"} return output
def mdata_export(r, **attr): """ Provide data for mobile app @param r: the S3Request instance @param attr: controller attributes @returns: a JSON string """ ID = "id" PID = "pe_id" UID = current.xml.UID s3db = current.s3db resource = r.resource resource_tablename = resource.tablename # Output in mdata format output = {resource_tablename: []} # Lookup to ensure we extract ID, UID fields & joining FKs for all tables tablenames = [resource_tablename] # Keep track of which FKs we have so that we can replace them with UID fks = {resource_tablename: {}} # Default to the 'mobile_list_fields' setting list_fields = resource.get_config("mobile_list_fields") if not list_fields: # Fallback to the 'list_fields' setting list_fields = resource.get_config("list_fields") if not list_fields: # Fallback to all readable fields list_fields = [field.name for field in resource.readable_fields()] else: # Ensure that we have all UID fields included # Ensure we have FKs between all linked records components = resource.components def find_fks(join): """ Helper function to get the FKs from a Join """ for q in (join.first, join.second): if isinstance(q, Field): if q.referent: # FK if q.tablename == resource_tablename: fieldname = q.name if fieldname not in list_fields: list_fields.append(fieldname) fks[resource_tablename][fieldname] = str(q.referent) else: tn = q.tablename if tn not in fks: fks[tn] = {} fks[tn][q.name] = str(q.referent) elif isinstance(q, Query): find_fks(q) for selector in list_fields: rfield = S3ResourceField(resource, selector) tablename = rfield.tname if tablename not in tablenames: output[tablename] = [] tablenames.append(tablename) # Find the FKs if tablename in fks: this_fks = fks[tablename] else: fks[tablename] = this_fks = {} for tn in rfield.join: join = rfield.join[tn] find_fks(join) # Add the ID, UUID & any FKs to list_fields if "." in selector: component, fieldname = selector.split(".", 1) id_field = "%s.%s" % (component, ID) if id_field not in list_fields: list_fields.append(id_field) uuid_field = "%s.%s" % (component, UID) if uuid_field not in list_fields: list_fields.append(uuid_field) if "$" in fieldname: fk, fieldname = fieldname.split("$", 1) fk_field = "%s.%s" % (component, fk) if fk_field not in list_fields: list_fields.append(fk_field) ctablename = components[component].table._tablename # Format to handle Aliases if ctablename not in fks: fks[ctablename] = {} #referent = s3db[ctablename][fk].referent if fk not in fks[ctablename]: #fks[ctablename][fk] = str(referent) fks[ctablename][fk] = str(s3db[ctablename][fk].referent) id_field = "%s.%s$%s" % (component, fk, ID) if id_field not in list_fields: list_fields.append(id_field) uuid_field = "%s.%s$%s" % (component, fk, UID) if uuid_field not in list_fields: list_fields.append(uuid_field) # Restore once list_fields working #if "$" in fieldname: # # e.g. address.location_id$parent$uuid # fk2, fieldname = fieldname.split("$", 1) # tablename = referent.tablename # if fk2 not in fks[tablename]: # fks[tablename][fk2] = str(s3db[tablename][fk2].referent) # id_field = "%s.%s$%s$%s" % (component, fk, fk2, ID) # if id_field not in list_fields: # list_fields.append(id_field) # uuid_field = "%s.%s$%s$%s" % (component, fk, fk2, UID) # if uuid_field not in list_fields: # list_fields.append(uuid_field) else: for fk in this_fks: fk_field = "%s.%s" % (component, fk) if fk_field not in list_fields: list_fields.append(fk_field) elif "$" in selector: fk, fieldname = selector.split("$", 1) if fk not in list_fields: list_fields.append(fk) #referent = s3db[resource_tablename][fk].referent if fk not in fks[resource_tablename]: #fks[resource_tablename][fk] = str(referent) fks[resource_tablename][fk] = str(s3db[resource_tablename][fk].referent) id_field = "%s$%s" % (fk, ID) if id_field not in list_fields: list_fields.append(id_field) uuid_field = "%s$%s" % (fk, UID) if uuid_field not in list_fields: list_fields.append(uuid_field) # Restore once list_fields working #if "$" in fieldname: # # e.g. location_id$parent$uuid # fk2, fieldname = fieldname.split("$", 1) # tablename = referent.tablename # if fk2 not in fks[tablename]: # fks[tablename][fk2] = str(s3db[tablename][fk2].referent) # id_field = "%s$%s$%s" % (fk, fk2, ID) # if id_field not in list_fields: # list_fields.append(id_field) # uuid_field = "%s$%s$%s" % (fk, fk2, UID) # if uuid_field not in list_fields: # list_fields.append(uuid_field) if ID not in list_fields: list_fields.append(ID) if UID not in list_fields: list_fields.append(UID) data = resource.select(list_fields, raw_data=True) rows = data.rows # Build lookups of IDs to UIDs & PIDs to UIDs id_lookup = {} pid_lookup = {} for record in rows: tablenames = {} for field in record: value = record[field] tablename, field = field.split(".", 1) if tablename not in tablenames: tablenames[tablename] = {ID: None, PID: None, UID: None, } if field == ID: tablenames[tablename][ID] = value elif field == PID: tablenames[tablename][PID] = value elif field == UID: tablenames[tablename][UID] = value for tablename in tablenames: if tablename not in id_lookup: id_lookup[tablename] = {} id_lookup[tablename][tablenames[tablename][ID]] = tablenames[tablename][UID] pid = tablenames[tablename][PID] if pid: pid_lookup[pid] = tablenames[tablename][UID] # Convert to S3Mobile format # & replace FKs with UUID for record in rows: # Keep track of which tables have no data empty = [] row = {tablename: [] for tablename in tablenames} for field in record: value = record[field] tablename, field = field.split(".", 1) if field == ID: # Don't include these in output continue this_fks = fks[tablename] if field in this_fks: if value: # Replace ID with UUID: referent = this_fks[field] tn, fn = referent.split(".", 1) if fn == PID: value = pid_lookup[value] else: value = id_lookup[tn][value] elif field == "uuid": if value is None: empty.append(tablename) continue elif isinstance(value, datetime.date) or \ isinstance(value, datetime.datetime): value = s3_encode_iso_datetime(value).decode("utf-8") row[tablename].append((field, value)) for tablename in tablenames: if tablename not in empty: output[tablename].append(row[tablename]) output = json.dumps(output, separators=SEPARATORS) current.response.headers = {"Content-Type": "application/json"} return output