Skip to content

Email notification

Reimplementation of the core CKAN email notifications.

This module reimplements the core CKAN email notifications in order to:

  • Be able to provide more visibility onto what is going on. This includes returning the number of emails being sent.

  • Modify the default implementation in order to not require an active request.

get_notifications

get_notifications(user_dict, since)

Return any email notifications for the given user since since.

For example email notifications about activity streams will be returned for any activities the occurred since since.

Parameters:

Name Type Description Default
user_dict dictionary

a dictionary representing the user, should contain 'id' and 'name'

required
since

datetime after which to return notifications from

required

Returns:

Type Description
list of dicts with keys 'subject' and 'body'

a list of email notifications

Source code in ckanext/saeoss/email_notifications.py
def get_notifications(user_dict, since):
    """Return any email notifications for the given user since `since`.

    For example email notifications about activity streams will be returned for
    any activities the occurred since `since`.

    :param user_dict: a dictionary representing the user, should contain 'id'
        and 'name'
    :type user_dict: dictionary

    :param since: datetime after which to return notifications from
    :rtype since: datetime.datetime

    :returns: a list of email notifications
    :rtype: list of dicts with keys 'subject' and 'body'

    """
    notifications = []
    logger.debug(f"Retrieving notifications for {user_dict['name']!r} since {since!r}")
    for function in _notifications_functions:
        notifications.extend(function(user_dict, since))
    return notifications

send_notification

send_notification(user, email_dict)

Email email_dict to user.

Source code in ckanext/saeoss/email_notifications.py
def send_notification(user, email_dict):
    """Email `email_dict` to `user`."""
    import ckan.lib.mailer

    if not user.get("email"):
        logger.debug(
            f"User {user.get('name')!r} does not have an email address configured"
        )
        # FIXME: Raise an exception.
        return

    try:
        ckan.lib.mailer.mail_recipient(
            user["display_name"],
            user["email"],
            email_dict["subject"],
            email_dict["body"],
        )
    except ckan.lib.mailer.MailerException:
        logger.error(ckan.lib.mailer.MailerException)
        raise
    else:
        logger.debug(f"Email sent!")

string_to_timedelta

string_to_timedelta(s)

Parse a string s and return a standard datetime.timedelta object.

Handles days, hours, minutes, seconds, and microseconds.

Accepts strings in these formats:

2 days 14 days 4:35:00 (hours, minutes and seconds) 4:35:12.087465 (hours, minutes, seconds and microseconds) 7 days, 3:23:34 7 days, 3:23:34.087465 .087465 (microseconds only)

Raises:

Type Description
ckan.logic.ValidationError

if the given string does not match any of the recognised formats

Source code in ckanext/saeoss/email_notifications.py
def string_to_timedelta(s):
    """Parse a string s and return a standard datetime.timedelta object.

    Handles days, hours, minutes, seconds, and microseconds.

    Accepts strings in these formats:

    2 days
    14 days
    4:35:00 (hours, minutes and seconds)
    4:35:12.087465 (hours, minutes, seconds and microseconds)
    7 days, 3:23:34
    7 days, 3:23:34.087465
    .087465 (microseconds only)

    :raises ckan.logic.ValidationError: if the given string does not match any
        of the recognised formats

    """
    patterns = []
    days_only_pattern = "(?P<days>\d+)\s+day(s)?"
    patterns.append(days_only_pattern)
    hms_only_pattern = "(?P<hours>\d?\d):(?P<minutes>\d\d):(?P<seconds>\d\d)"
    patterns.append(hms_only_pattern)
    ms_only_pattern = ".(?P<milliseconds>\d\d\d)(?P<microseconds>\d\d\d)"
    patterns.append(ms_only_pattern)
    hms_and_ms_pattern = hms_only_pattern + ms_only_pattern
    patterns.append(hms_and_ms_pattern)
    days_and_hms_pattern = "{0},\s+{1}".format(days_only_pattern, hms_only_pattern)
    patterns.append(days_and_hms_pattern)
    days_and_hms_and_ms_pattern = days_and_hms_pattern + ms_only_pattern
    patterns.append(days_and_hms_and_ms_pattern)

    for pattern in patterns:
        match = re.match("^{0}$".format(pattern), s)
        if match:
            break

    if not match:
        raise logic.ValidationError("Not a valid time: {0}".format(s))

    gd = match.groupdict()
    days = int(gd.get("days", "0"))
    hours = int(gd.get("hours", "0"))
    minutes = int(gd.get("minutes", "0"))
    seconds = int(gd.get("seconds", "0"))
    milliseconds = int(gd.get("milliseconds", "0"))
    microseconds = int(gd.get("microseconds", "0"))
    delta = dt.timedelta(
        days=days,
        hours=hours,
        minutes=minutes,
        seconds=seconds,
        milliseconds=milliseconds,
        microseconds=microseconds,
    )
    return delta