Example #1
0
    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
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
    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()