def parse_interval(intervalstr): """ Parse an interval string of the format "<ISO8601>--<ISO8601>" into a pair of datetimes @param intervalstr: the interval string @returns: tuple of datetimes (start, end) """ start = end = None if intervalstr: dates = intervalstr.split("--") if len(dates) != 2: return start, end try: start = s3_decode_iso_datetime(dates[0]) except ValueError: pass try: end = s3_decode_iso_datetime(dates[1]) except ValueError: pass return start, end
def send(cls, r, resource): """ Method to retrieve updates for a subscription, render the notification message and send it - responds to POST?format=msg requests to the respective resource. @param r: the S3Request @param resource: the S3Resource """ _debug = current.log.debug _debug("S3Notifications.send()") json_message = current.xml.json_message # Read subscription data source = r.body source.seek(0) data = source.read() subscription = json.loads(data) #_debug("Notify PE #%s by %s on %s of %s since %s" % \ # (subscription["pe_id"], # str(subscription["method"]), # str(subscription["notify_on"]), # subscription["resource"], # subscription["last_check_time"], # )) # Check notification settings notify_on = subscription["notify_on"] methods = subscription["method"] if not notify_on or not methods: return json_message(message="No notifications configured " "for this subscription") # Authorization (pe_id must not be None) pe_id = subscription["pe_id"] if not pe_id: r.unauthorised() # Fields to extract fields = resource.list_fields(key="notify_fields") if "created_on" not in fields: fields.append("created_on") # Extract the data data = resource.select(fields, represent=True, raw_data=True) rows = data["rows"] # How many records do we have? numrows = len(rows) if not numrows: return json_message(message="No records found") #_debug("%s rows:" % numrows) # Prepare meta-data get_config = resource.get_config settings = current.deployment_settings page_url = subscription["page_url"] crud_strings = current.response.s3.crud_strings.get(resource.tablename) if crud_strings: resource_name = crud_strings.title_list else: resource_name = string.capwords(resource.name, "_") last_check_time = s3_decode_iso_datetime( subscription["last_check_time"]) email_format = subscription["email_format"] if not email_format: email_format = settings.get_msg_notify_email_format() filter_query = subscription.get("filter_query") meta_data = { "systemname": settings.get_system_name(), "systemname_short": settings.get_system_name_short(), "resource": resource_name, "page_url": page_url, "notify_on": notify_on, "last_check_time": last_check_time, "filter_query": filter_query, "total_rows": numrows, } # Render contents for the message template(s) renderer = get_config("notify_renderer") if not renderer: renderer = settings.get_msg_notify_renderer() if not renderer: renderer = cls._render contents = {} if email_format == "html" and "EMAIL" in methods: contents["html"] = renderer(resource, data, meta_data, "html") contents["default"] = contents["html"] if email_format != "html" or "EMAIL" not in methods or len( methods) > 1: contents["text"] = renderer(resource, data, meta_data, "text") contents["default"] = contents["text"] # Subject line subject = get_config("notify_subject") if not subject: subject = settings.get_msg_notify_subject() if callable(subject): subject = subject(resource, data, meta_data) from string import Template subject = Template(subject).safe_substitute(S="%(systemname)s", s="%(systemname_short)s", r="%(resource)s") subject = subject % meta_data # Attachment attachment = subscription.get("attachment", False) document_ids = None if attachment: attachment_fnc = settings.get_msg_notify_attachment() if attachment_fnc: document_ids = attachment_fnc(resource, data, meta_data) # **data for send_by_pe_id function in s3msg send_data = {} send_data_fnc = settings.get_msg_notify_send_data() if callable(send_data_fnc): send_data = send_data_fnc(resource, data, meta_data) # Helper function to find templates from a priority list join = lambda *f: os.path.join(current.request.folder, *f) def get_template(path, filenames): for fn in filenames: filepath = join(path, fn) if os.path.exists(filepath): try: return open(filepath, "rb") except: pass return None # Render and send the message(s) themes = settings.get_template() prefix = resource.get_config("notify_template", "notify") send = current.msg.send_by_pe_id success = False errors = [] for method in methods: error = None # Get the message template template = None filenames = ["%s_%s.html" % (prefix, method.lower())] if method == "EMAIL" and email_format: filenames.insert(0, "%s_email_%s.html" % (prefix, email_format)) if themes != "default": location = settings.get_template_location() if not isinstance(themes, (tuple, list)): themes = (themes, ) for theme in themes[::-1]: path = join(location, "templates", theme, "views", "msg") template = get_template(path, filenames) if template is not None: break if template is None: path = join("views", "msg") template = get_template(path, filenames) if template is None: template = StringIO( s3_str(current.T("New updates are available."))) # Select contents format if method == "EMAIL" and email_format == "html": output = contents["html"] else: output = contents["text"] # Render the message try: message = current.response.render(template, output) except: exc_info = sys.exc_info()[:2] error = ("%s: %s" % (exc_info[0].__name__, exc_info[1])) errors.append(error) continue if not message: continue # Send the message #_debug("Sending message per %s" % method) #_debug(message) try: sent = send( pe_id, # RFC 2822 subject=s3_truncate(subject, 78), message=message, contact_method=method, system_generated=True, document_ids=document_ids, **send_data) except: exc_info = sys.exc_info()[:2] error = ("%s: %s" % (exc_info[0].__name__, exc_info[1])) sent = False if sent: # Successful if at least one notification went out success = True else: if not error: error = current.session.error if isinstance(error, list): error = "/".join(error) if error: errors.append(error) # Done if errors: message = ", ".join(errors) else: message = "Success" return json_message(success=success, statuscode=200 if success else 403, message=message)
def send(cls, r, resource): """ Method to retrieve updates for a subscription, render the notification message and send it - responds to POST?format=msg requests to the respective resource. @param r: the S3Request @param resource: the S3Resource """ _debug("S3Notifications.send()") json_message = current.xml.json_message # Read subscription data source = r.body source.seek(0) data = source.read() subscription = json.loads(data) #_debug("Notify PE #%s by %s on %s of %s since %s", # subscription["pe_id"], # str(subscription["method"]), # str(subscription["notify_on"]), # subscription["resource"], # subscription["last_check_time"], # ) # Check notification settings notify_on = subscription["notify_on"] methods = subscription["method"] if not notify_on or not methods: return json_message(message="No notifications configured " "for this subscription") # Authorization (subscriber must be logged in) auth = current.auth pe_id = subscription["pe_id"] if not auth.s3_logged_in() or auth.user.pe_id != pe_id: r.unauthorised() # Fields to extract fields = resource.list_fields(key="notify_fields") if "created_on" not in fields: fields.append("created_on") # Extract the data data = resource.select(fields, represent=True, raw_data=True) rows = data["rows"] # How many records do we have? numrows = len(rows) if not numrows: return json_message(message="No records found") #_debug("%s rows:", numrows) # Prepare meta-data get_config = resource.get_config settings = current.deployment_settings page_url = subscription["page_url"] crud_strings = current.response.s3.crud_strings.get(resource.tablename) if crud_strings: resource_name = crud_strings.title_list else: resource_name = string.capwords(resource.name, "_") last_check_time = s3_decode_iso_datetime(subscription["last_check_time"]) email_format = subscription["email_format"] if not email_format: email_format = settings.get_msg_notify_email_format() filter_query = subscription.get("filter_query") meta_data = {"systemname": settings.get_system_name(), "systemname_short": settings.get_system_name_short(), "resource": resource_name, "page_url": page_url, "notify_on": notify_on, "last_check_time": last_check_time, "filter_query": filter_query, "total_rows": numrows, } # Render contents for the message template(s) renderer = get_config("notify_renderer") if not renderer: renderer = settings.get_msg_notify_renderer() if not renderer: renderer = cls._render contents = {} if email_format == "html" and "EMAIL" in methods: contents["html"] = renderer(resource, data, meta_data, "html") contents["default"] = contents["html"] if email_format != "html" or "EMAIL" not in methods or len(methods) > 1: contents["text"] = renderer(resource, data, meta_data, "text") contents["default"] = contents["text"] # Subject line subject = get_config("notify_subject") if not subject: subject = settings.get_msg_notify_subject() from string import Template subject = Template(subject).safe_substitute(S="%(systemname)s", s="%(systemname_short)s", r="%(resource)s") subject = subject % meta_data # Helper function to find templates from a priority list join = lambda *f: os.path.join(current.request.folder, *f) def get_template(path, filenames): for fn in filenames: filepath = join(path, fn) if os.path.exists(filepath): try: return open(filepath, "rb") except: pass return None # Render and send the message(s) themes = settings.get_template() prefix = resource.get_config("notify_template", "notify") send = current.msg.send_by_pe_id success = False errors = [] for method in methods: error = None # Get the message template template = None filenames = ["%s_%s.html" % (prefix, method.lower())] if method == "EMAIL" and email_format: filenames.insert(0, "%s_email_%s.html" % (prefix, email_format)) if themes != "default": location = settings.get_template_location() if not isinstance(themes, (tuple, list)): themes = (themes,) for theme in themes[::-1]: path = join(location, "templates", theme, "views", "msg") template = get_template(path, filenames) if template is not None: break if template is None: path = join("views", "msg") template = get_template(path, filenames) if template is None: template = StringIO(T("New updates are available.")) # Select contents format if method == "EMAIL" and email_format == "html": output = contents["html"] else: output = contents["text"] # Render the message try: message = current.response.render(template, output) except: exc_info = sys.exc_info()[:2] error = ("%s: %s" % (exc_info[0].__name__, exc_info[1])) errors.append(error) continue # Send the message #_debug("Sending message per %s", method) #_debug(message) try: sent = send(pe_id, subject=s3_truncate(subject, 78), message=message, contact_method=method, system_generated=True) except: exc_info = sys.exc_info()[:2] error = ("%s: %s" % (exc_info[0].__name__, exc_info[1])) sent = False if sent: # Successful if at least one notification went out success = True else: if not error: error = current.session.error if isinstance(error, list): error = "/".join(error) if error: errors.append(error) # Done if errors: message = ", ".join(errors) else: message = "Success" return json_message(success=success, statuscode=200 if success else 403, message=message)
def update_json(self, r, **attr): """ Update or delete calendar items (Ajax method) @param r: the S3Request instance @param attr: controller attributes """ # Read+parse body JSON s = r.body s.seek(0) try: options = json.load(s) except JSONERRORS: options = None if not isinstance(options, dict): r.error(400, "Invalid request options") # Verify formkey keyname = "_formkey[%s]" % self.formname(r) formkey = options.get("k") if not formkey or formkey not in current.session.get(keyname, []): r.error(403, current.ERROR.NOT_PERMITTED) resource = self.resource tablename = resource.tablename # Updates items = options.get("u") if items and type(items) is list: # Error if resource is not editable if not resource.get_config("editable", True): r.error(403, current.ERROR.NOT_PERMITTED) # Parse the organizer-config of the target resource config = self.parse_config(resource) start = config.get("start") if start: if not start.field or start.tname != tablename: # Field must be in target resource # TODO support fields in subtables start = None end = config.get("end") if end: if not end.field or end.tname != tablename: # Field must be in target resource # TODO support fields in subtables end = None # Resource details db = current.db table = resource.table prefix, name = resource.prefix, resource.name # Model methods s3db = current.s3db onaccept = s3db.onaccept update_super = s3db.update_super # Auth methods auth = current.auth audit = current.audit permitted = auth.s3_has_permission set_realm_entity = auth.set_realm_entity # Process the updates for item in items: # Get the record ID record_id = item.get("id") if not record_id: continue # Check permission to update the record if not permitted("update", table, record_id=record_id): r.unauthorised() # Collect and validate the update-data data = {} error = None if "s" in item: if not start: error = "Event start not editable" else: try: dt = s3_decode_iso_datetime(item["s"]) except ValueError: error = "Invalid start date" if start.field: dt, error = start.field.validate(dt) data[start.fname] = dt if not error and "e" in item: if not end: error = "Event end not editable" else: try: dt = s3_decode_iso_datetime(item["e"]) except ValueError: error = "Invalid end date" if end.field: dt, error = end.field.validate(dt) data[end.fname] = dt if error: r.error(400, error) # Update the record, postprocess update if data: success = db(table._id == record_id).update(**data) if not success: r.error( 400, "Failed to update %s#%s" % (tablename, record_id)) else: data[table._id.name] = record_id # Audit update audit("update", prefix, name, record=record_id, representation="json") # Update super entity links update_super(table, data) # Update realm if resource.get_config("update_realm"): set_realm_entity(table, record_id, force_update=True) # Onaccept onaccept(table, data, method="update") # Deletions items = options.get("d") if items and type(items) is list: # Error if resource is not deletable if not resource.get_config("deletable", True): r.error(403, current.ERROR.NOT_PERMITTED) # Collect record IDs delete_ids = [] for item in items: record_id = item.get("id") if not record_id: continue delete_ids.append(record_id) # Delete the records # (includes permission check, audit and ondelete-postprocess) if delete_ids: dresource = current.s3db.resource(tablename, id=delete_ids) deleted = dresource.delete(cascade=True) if deleted != len(delete_ids): r.error(400, "Failed to delete %s items" % tablename) return current.xml.json_message()