Ejemplo n.º 1
0
    def reloadAndUpdateCache(self, applet, editor):
        from girderformindlogger.models.protocol import Protocol

        protocolUrl = applet.get('meta', {}).get('protocol', applet).get(
            'http://schema.org/url',
            applet.get('meta', {}).get('protocol', applet).get('url'))

        if protocolUrl is not None:
            protocol = Protocol().getFromUrl(protocolUrl,
                                             'protocol',
                                             editor,
                                             thread=False,
                                             refreshCache=True)

            protocol = protocol[0].get('protocol', protocol[0])
            if protocol.get('_id'):
                self.update({'_id': ObjectId(applet['_id'])},
                            {'$set': {
                                'meta.protocol._id': protocol['_id']
                            }})
                if 'meta' in applet and 'protocol' in applet['meta']:
                    applet['meta']['protocol']['_id'] = protocol['_id']

            from girderformindlogger.utility import jsonld_expander

            jsonld_expander.formatLdObject(applet,
                                           'applet',
                                           editor,
                                           refreshCache=False,
                                           responseDates=False)
    def createAppletFromUrl(self,
                            name,
                            protocolUrl,
                            user=None,
                            roles=None,
                            constraints=None,
                            sendEmail=True):
        from girderformindlogger.models.protocol import Protocol
        # get a protocol from a URL
        protocol = Protocol().getFromUrl(protocolUrl,
                                         'protocol',
                                         user,
                                         thread=False,
                                         refreshCache=True)
        protocol = protocol[0].get('protocol', protocol[0])

        displayName = Protocol().preferredName(protocol)

        name = name if name is not None and len(name) else displayName
        if len(displayName) == 0:
            displayName = name if len(name) else "activity"

        applet = self.createApplet(
            name=name,
            protocol={
                '_id':
                'protocol/{}'.format(str(protocol.get('_id')).split('/')[-1]),
                'url':
                protocol.get('meta', {}).get('protocol',
                                             {}).get('url', protocolUrl)
            },
            user=user,
            roles=roles,
            constraints=constraints,
            displayName=displayName)
        emailMessage = "Your applet, {}, has been successfully created. The "  \
            "applet's ID is {}".format(
                name,
                str(applet.get('applet', applet).get('_id')
            )
        )
        if sendEmail and 'email' in user:
            from girderformindlogger.utility.mail_utils import sendMail
            sendMail(subject=name, text=emailMessage, to=[user['email']])
        print(emailMessage)
Ejemplo n.º 3
0
    def createAppletFromUrl(self,
                            name,
                            protocolUrl,
                            user=None,
                            roles=None,
                            constraints=None,
                            email='',
                            sendEmail=True):
        from girderformindlogger.models.protocol import Protocol
        from girderformindlogger.utility import mail_utils

        # we have cases to show manager's email to users
        if mail_utils.validateEmailAddress(email):
            user['email'] = email
            user['email_encrypted'] = False
            UserModel().save(user)

        # get a protocol from a URL
        protocol = Protocol().getFromUrl(protocolUrl,
                                         'protocol',
                                         user,
                                         thread=False,
                                         refreshCache=True)

        protocol = protocol[0].get('protocol', protocol[0])

        displayName = Protocol().preferredName(protocol)

        name = name if name is not None and len(name) else displayName

        appletName = '{}/'.format(protocolUrl)

        applet = self.createApplet(
            name=name,
            protocol={
                '_id':
                'protocol/{}'.format(str(protocol.get('_id')).split('/')[-1]),
                'url':
                protocol.get('meta', {}).get('protocol',
                                             {}).get('url', protocolUrl)
            },
            user=user,
            roles=roles,
            constraints=constraints,
            appletName=appletName)
Ejemplo n.º 4
0
def getOldVersions(responses, applet):
    IRIs = {}
    insertedIRI = {}
    for IRI in responses:
        IRIs[IRI] = []
        for response in responses[IRI]:
            if 'version' not in response:
                continue

            identifier = '{}/{}'.format(IRI, response['version'])
            if identifier not in insertedIRI:
                IRIs[IRI].append(response['version'])
                insertedIRI[identifier] = True

    return Protocol().getHistoryDataFromItemIRIs(
        applet.get('meta', {}).get('protocol',
                                   {}).get('_id', '').split('/')[-1], IRIs)
Ejemplo n.º 5
0
    def createAppletFromProtocolData(self,
                                     name,
                                     protocol,
                                     user=None,
                                     roles=None,
                                     constraints=None,
                                     email='',
                                     sendEmail=True):
        from girderformindlogger.models.protocol import Protocol
        from girderformindlogger.utility import mail_utils

        # we have cases to show manager's email to users
        if mail_utils.validateEmailAddress(email):
            user['email'] = email
            user['email_encrypted'] = False
            UserModel().save(user)

        # get a protocol from single json file
        protocol = Protocol().createProtocol(protocol, user)

        protocol = protocol.get('protocol', protocol)

        displayName = Protocol().preferredName(protocol)

        name = name if name is not None and len(name) else displayName

        appletName = '{}/'.format(protocol.get('@id'))

        applet = self.createApplet(
            name=name,
            protocol={
                '_id':
                'protocol/{}'.format(str(protocol.get('_id')).split('/')[-1])
            },
            user=user,
            roles=roles,
            constraints=constraints,
            appletName=appletName)
def formatLdObject(obj,
                   mesoPrefix='folder',
                   user=None,
                   keepUndefined=False,
                   dropErrors=False,
                   refreshCache=False,
                   responseDates=False):
    """
    Function to take a compacted JSON-LD Object within a Girder for Mindlogger
    database and return an exapanded JSON-LD Object including an _id.

    :param obj: Compacted JSON-LD Object
    :type obj: dict or list
    :param mesoPrefix: Girder for Mindlogger entity type, defaults to 'folder'
                       if not provided
    :type mesoPrefix: str
    :param user: User making the call
    :type user: User
    :param keepUndefined: Keep undefined properties
    :type keepUndefined: bool
    :param dropErrors: Return `None` instead of raising an error for illegal
        JSON-LD definitions.
    :type dropErrors: bool
    :param refreshCache: Refresh from Dereferencing URLs?
    :type refreshCache: bool
    :param responseDates: Include list of ISO date strings of responses
    :type responseDates: bool
    :returns: Expanded JSON-LD Object (dict or list)
    """
    from girderformindlogger.models import pluralize

    refreshCache = False if refreshCache is None else refreshCache

    try:
        if obj is None:
            return (None)
        if isinstance(obj, dict):
            oc = obj.get("cached")
            if all([not refreshCache, oc is not None]):
                return (loadCache(oc))
            if 'meta' not in obj.keys():
                return (_fixUpFormat(obj))
        mesoPrefix = camelCase(mesoPrefix)
        if type(obj) == list:
            return (_fixUpFormat([
                formatLdObject(o,
                               mesoPrefix,
                               refreshCache=refreshCache,
                               user=user) for o in obj if o is not None
            ]))
        if not type(obj) == dict and not dropErrors:
            raise TypeError("JSON-LD must be an Object or Array.")
        newObj = obj.get('meta', obj)
        newObj = newObj.get(mesoPrefix, newObj)
        newObj = expand(newObj, keepUndefined=keepUndefined)
        if type(newObj) == list and len(newObj) == 1:
            try:
                newObj = newObj[0]
            except:
                raise ValidationException(str(newObj))
        if type(newObj) != dict:
            newObj = {}
        objID = str(obj.get('_id', 'undefined'))
        if objID == 'undefined':
            raise ResourcePathNotFound()
        newObj['_id'] = "/".join([snake_case(mesoPrefix), objID])
        if mesoPrefix == 'applet':
            protocolUrl = obj.get('meta', {}).get('protocol', obj).get(
                'http://schema.org/url',
                obj.get('meta', {}).get('protocol', obj).get('url'))
            protocol = ProtocolModel().getFromUrl(
                protocolUrl,
                'protocol',
                user,
                thread=False,
                refreshCache=refreshCache
            )[0] if protocolUrl is not None else {}
            protocol = formatLdObject(protocol,
                                      'protocol',
                                      user,
                                      refreshCache=refreshCache)
            applet = {}
            applet['activities'] = protocol.pop('activities', {})
            applet['items'] = protocol.pop('items', {})
            applet['protocol'] = {
                key: protocol.get('protocol', protocol.get('activitySet',
                                                           {})).pop(key)
                for key in
                ['@type', '_id', 'http://schema.org/url', 'schema:url', 'url']
                if key in list(protocol.get('protocol', {}).keys())
            }
            applet['applet'] = {
                **protocol.pop('protocol', {}),
                **obj.get('meta', {}).get(mesoPrefix, {}), '_id':
                "/".join([snake_case(mesoPrefix), objID]),
                'url':
                "#".join(
                    [obj.get('meta', {}).get('protocol', {}).get("url", "")])
            }
            createCache(obj, applet, 'applet', user)
            if responseDates:
                try:
                    applet["applet"]["responseDates"] = responseDateList(
                        obj.get('_id'), user.get('_id'), user)
                except:
                    applet["applet"]["responseDates"] = []
            return (applet)
        elif mesoPrefix == 'protocol':
            protocol = {'protocol': newObj, 'activities': {}, "items": {}}
            activitiesNow = set()
            itemsNow = set()
            try:
                protocol = componentImport(newObj,
                                           deepcopy(protocol),
                                           user,
                                           refreshCache=refreshCache)
            except:
                print("636")
                protocol = componentImport(newObj,
                                           deepcopy(protocol),
                                           user,
                                           refreshCache=True)
            newActivities = [
                a for a in protocol.get('activities', {}).keys()
                if a not in activitiesNow
            ]
            newItems = [
                i for i in protocol.get('items', {}).keys()
                if i not in itemsNow
            ]
            while (any([len(newActivities), len(newItems)])):
                activitiesNow = set(protocol.get('activities', {}).keys())
                itemsNow = set(protocol.get('items', {}).keys())
                for activityURL in newActivities:
                    activity = protocol['activities'][activityURL]
                    activity = activity.get('meta',
                                            {}).get('activity', activity)
                    try:
                        protocol = componentImport(deepcopy(activity),
                                                   deepcopy(protocol),
                                                   user,
                                                   refreshCache=refreshCache)
                    except:
                        print("670")
                        protocol = componentImport(deepcopy(activity),
                                                   deepcopy(protocol),
                                                   user,
                                                   refreshCache=True)
                for itemURL in newItems:
                    activity = protocol['items'][itemURL]
                    activity = activity.get('meta', {}).get('screen', activity)
                    try:
                        protocol = componentImport(deepcopy(activity),
                                                   deepcopy(protocol),
                                                   user,
                                                   refreshCache=refreshCache)
                    except:
                        print("691")
                        protocol = componentImport(deepcopy(activity),
                                                   deepcopy(protocol),
                                                   user,
                                                   refreshCache=True)
                newActivities = list(
                    set(protocol.get('activities', {}).keys()) - activitiesNow)
                newItems = list(
                    set(protocol.get('items', {}).keys()) - itemsNow)
            return (_fixUpFormat(protocol))
        else:
            return (_fixUpFormat(newObj))
    except:
        if refreshCache == False:
            return (_fixUpFormat(
                formatLdObject(obj,
                               mesoPrefix,
                               user,
                               keepUndefined,
                               dropErrors,
                               refreshCache=False,
                               responseDates=responseDates)))
        import sys, traceback
        print(sys.exc_info())
        print(traceback.print_tb(sys.exc_info()[2]))
Ejemplo n.º 7
0
    def htmlInvitation(
        self,
        invitation,
        invitee=None,
        fullDoc=False,
        includeLink=True
    ):
        """
        Returns an HTML document rendering the invitation.

        :param invitation: Invitation to render
        :type invitation: dict
        :param invitee: Invited user
        :type invitee: dict or None

        :returns: html document
        """
        from girderformindlogger.models.applet import Applet
        from girderformindlogger.models.profile import Profile
        from girderformindlogger.models.protocol import Protocol
        from girderformindlogger.models.token import Token
        from girderformindlogger.models.user import User
        from girderformindlogger.exceptions import GirderException
        from girderformindlogger.api.rest import getApiUrl
        from girderformindlogger.utility import context as contextUtil,        \
            mail_utils

        accept = (
            "To accept or decline, visit <a href=\"{u}\">{u}</a>".format(
                u="https://web.mindlogger.org/#/invitation/{}".format(str(
                    invitation['_id']
                ))
            )
        ) if includeLink else ""
        applet = Applet().load(ObjectId(invitation['appletId']), force=True)
        appletName = applet.get(
            'displayName',
            'a new applet'
        )
        try:
            skin = contextUtil.getSkin()
        except:
            skin = {}
        instanceName = skin.get("name", "MindLogger")
        role = invitation.get("role", "user")
        try:
            coordinator = Profile().coordinatorProfile(
                applet['_id'],
                invitation["invitedBy"]
            )
        except:
            coordinator = None
        displayProfile = Profile().displayProfileFields(invitation, invitee)
        description = applet.get('meta', {}).get(
            'applet',
            {}
        ).get(
            "schema:desciription",
            Protocol().load(
                applet['meta']['protocol']['_id'].split('protocol/')[-1],
                force=True
            ).get('meta', {}).get('protocol') if 'protocol' in applet.get(
                'meta',
                {}
            ) else {}
        ).get("schema:description", "")
        managers = mail_utils.htmlUserList(
            Applet().listUsers(applet, 'manager', force=True)
        )
        coordinators = mail_utils.htmlUserList(
            Applet().listUsers(applet, 'coordinator', force=True)
        )
        reviewers = mail_utils.htmlUserList(
            Applet().listUsers(applet, 'reviewer', force=True)
        )
        body = """
{greeting}ou were invited {byCoordinator}to be {role} of <b>{appletName}</b>{instanceName}.
<br/>
Below are the users that have access to your data:
{reviewers}
{managers}
{coordinators}
<br/>
{accept}
        """.format(
            accept=accept,
            appletName=appletName,
            byCoordinator="by {} ({}) ".format(
                coordinator.get("displayName", "an anonymous entity"),
                "<a href=\"mailto:{email}\">{email}</a>".format(
                    email=coordinator["email"]
                ) if "email" in coordinator and coordinator["email"] is not None else "email not available"
            ) if isinstance(coordinator, dict) else "",
            coordinators="<h3>Users who can change this applet's settings, "
                "but who cannot change who can see your data: </h3>{}"
                "".format(
                    coordinators if len(
                        coordinators
                    ) else "<ul><li>None</li></ul>"
                ),
            greeting="Welcome to MindLogger! Y",
            instanceName=" on {}".format(
                instanceName
            ) if instanceName is not None and len(instanceName) else "",
            managers="<h3>Users who can change this applet's settings, "
                " including who can access your data: </h3>{}"
                "".format(
                    managers if len(managers) else "<ul><li>None</li></ul>"
                ),
            reviewers="<h3>Users who can see your data for this "
                "applet: </h3>{}"
                "".format(
                    reviewers if len(reviewers) else "<ul><li>None</li></ul>"
                ),
            role="an editor" if role=="editor" else "a {}".format(role)
        ).strip()
        return(body if not fullDoc else """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Invitation to {appletName} on {instanceName}</title>
</head>
<body>
{body}
</body>
</html>
        """.format(
            appletName=appletName,
            instanceName=instanceName,
            body=body
        ).strip())
Ejemplo n.º 8
0
def formatLdObject(obj,
                   mesoPrefix='folder',
                   user=None,
                   keepUndefined=False,
                   dropErrors=False,
                   refreshCache=False,
                   responseDates=False):
    """
    Function to take a compacted JSON-LD Object within a Girder for Mindlogger
    database and return an exapanded JSON-LD Object including an _id.

    :param obj: Compacted JSON-LD Object
    :type obj: dict or list
    :param mesoPrefix: Girder for Mindlogger entity type, defaults to 'folder'
                       if not provided
    :type mesoPrefix: str
    :param user: User making the call
    :type user: User
    :param keepUndefined: Keep undefined properties
    :type keepUndefined: bool
    :param dropErrors: Return `None` instead of raising an error for illegal
        JSON-LD definitions.
    :type dropErrors: bool
    :param refreshCache: Refresh from Dereferencing URLs?
    :type refreshCache: bool
    :param responseDates: Include list of ISO date strings of responses
    :type responseDates: bool
    :returns: Expanded JSON-LD Object (dict or list)
    """
    from girderformindlogger.models import pluralize

    refreshCache = False if refreshCache is None else refreshCache

    try:
        if obj is None:
            return (None)
        if isinstance(obj, dict):
            oc = obj.get("cached")
            if all([not refreshCache, oc is not None]):
                return (loadCache(oc))
            if 'meta' not in obj.keys():
                return (_fixUpFormat(obj))
        mesoPrefix = camelCase(mesoPrefix)
        if type(obj) == list:
            return (_fixUpFormat([
                formatLdObject(o,
                               mesoPrefix,
                               refreshCache=refreshCache,
                               user=user) for o in obj if o is not None
            ]))
        if not type(obj) == dict and not dropErrors:
            raise TypeError("JSON-LD must be an Object or Array.")
        newObj = obj.get('meta', obj)
        newObj = newObj.get(mesoPrefix, newObj)

        if not obj.get('loadedFromSingleFile', False):
            newObj = expand(newObj, keepUndefined=keepUndefined)

        if type(newObj) == list and len(newObj) == 1:
            try:
                newObj = newObj[0]
            except:
                raise ValidationException(str(newObj))
        if type(newObj) != dict:
            newObj = {}
        objID = str(obj.get('_id', 'undefined'))
        if objID == 'undefined':
            raise ResourcePathNotFound('unable to load object')
        newObj['_id'] = "/".join([snake_case(mesoPrefix), objID])
        if mesoPrefix == 'applet':
            protocolUrl = obj.get('meta', {}).get('protocol', obj).get(
                'http://schema.org/url',
                obj.get('meta', {}).get('protocol', obj).get('url'))

            # get protocol data from id
            protocol = None
            protocolId = obj.get('meta', {}).get('protocol',
                                                 {}).get('_id',
                                                         '').split('/')[-1]
            if protocolId:
                cache = ProtocolModel().getCache(protocolId)
                if cache:
                    protocol = loadCache(cache)

            if protocolUrl is not None and not protocol:
                # get protocol from url
                protocol = ProtocolModel().getFromUrl(
                    protocolUrl,
                    'protocol',
                    user,
                    thread=False,
                    refreshCache=refreshCache)[0]

            # format protocol data
            protocol = formatLdObject(protocol,
                                      'protocol',
                                      user,
                                      refreshCache=refreshCache)

            applet = {}
            applet['activities'] = protocol.pop('activities', {})
            applet['items'] = protocol.pop('items', {})
            applet['protocol'] = {
                key: protocol.get('protocol', protocol.get('activitySet',
                                                           {})).pop(key)
                for key in
                ['@type', '_id', 'http://schema.org/url', 'schema:url', 'url']
                if key in list(protocol.get('protocol', {}).keys())
            }
            applet['applet'] = {
                **protocol.pop('protocol', {}),
                **obj.get('meta', {}).get(mesoPrefix, {}), '_id':
                "/".join([snake_case(mesoPrefix), objID]),
                'url':
                "#".join(
                    [obj.get('meta', {}).get('protocol', {}).get("url", "")])
            }

            if 'appletName' in obj and obj['appletName']:
                suffix = obj['appletName'].split('/')[-1]
                inserted = False

                candidates = ['prefLabel', 'altLabel']
                for candidate in candidates:
                    for key in applet['applet']:
                        if not inserted and str(key).endswith(
                                candidate) and len(
                                    applet['applet'][key]) and len(
                                        applet['applet'][key][0].get(
                                            '@value', '')):
                            if len(suffix):
                                applet['applet'][key][0]['@value'] += (' ' +
                                                                       suffix)

                            if not len(applet['applet']
                                       ['url']):  # for development
                                applet['applet'][key][0][
                                    '@value'] += ' ( single-file )'

                            AppletModel().update({'_id': obj['_id']}, {
                                '$set': {
                                    'displayName':
                                    applet['applet'][key][0]['@value']
                                }
                            })

                            inserted = True
            createCache(obj, applet, 'applet', user)
            if responseDates:
                try:
                    applet["applet"]["responseDates"] = responseDateList(
                        obj.get('_id'), user.get('_id'), user)
                except:
                    applet["applet"]["responseDates"] = []
            return (applet)
        elif mesoPrefix == 'protocol':
            protocol = {'protocol': newObj, 'activities': {}, "items": {}}

            if obj.get('loadedFromSingleFile', False):
                activities = list(ActivityModel().find({
                    'meta.protocolId':
                    '{}/{}'.format(MODELS()['protocol']().name, obj['_id'])
                }))
                items = list(ScreenModel().find({
                    'meta.protocolId':
                    '{}/{}'.format(MODELS()['protocol']().name, obj['_id'])
                }))

                for activity in activities:
                    formatted = formatLdObject(activity, 'activity', user)
                    protocol['activities'][formatted['@id']] = formatted
                for item in items:
                    formatted = formatLdObject(item, 'screen', user)
                    protocol['items'][formatted['@id']] = formatted
            else:
                try:
                    protocol = componentImport(newObj,
                                               deepcopy(protocol),
                                               user,
                                               refreshCache=refreshCache)
                except:
                    print("636")
                    protocol = componentImport(newObj,
                                               deepcopy(protocol),
                                               user,
                                               refreshCache=True)

                newActivities = protocol.get('activities', {}).keys()
                newItems = protocol.get('items', {}).keys()

                while (any([len(newActivities), len(newItems)])):
                    activitiesNow = set(protocol.get('activities', {}).keys())
                    itemsNow = set(protocol.get('items', {}).keys())
                    for activityURL in newActivities:
                        activity = protocol['activities'][activityURL]
                        activity = activity.get('meta',
                                                {}).get('activity', activity)
                        try:
                            protocol = componentImport(
                                deepcopy(activity),
                                deepcopy(protocol),
                                user,
                                refreshCache=refreshCache)
                        except:
                            print("670")
                            protocol = componentImport(deepcopy(activity),
                                                       deepcopy(protocol),
                                                       user,
                                                       refreshCache=True)
                    for itemURL in newItems:
                        activity = protocol['items'][itemURL]
                        activity = activity.get('meta',
                                                {}).get('screen', activity)
                        try:
                            protocol = componentImport(
                                deepcopy(activity),
                                deepcopy(protocol),
                                user,
                                refreshCache=refreshCache)
                        except:
                            print("691")
                            protocol = componentImport(deepcopy(activity),
                                                       deepcopy(protocol),
                                                       user,
                                                       refreshCache=True)
                    newActivities = list(
                        set(protocol.get('activities', {}).keys()) -
                        activitiesNow)
                    newItems = list(
                        set(protocol.get('items', {}).keys()) - itemsNow)

            formatted = _fixUpFormat(protocol)

            createCache(obj, formatted, 'protocol')
            return formatted
        else:
            return (_fixUpFormat(newObj))
    except:
        if refreshCache == False:
            return (_fixUpFormat(
                formatLdObject(obj,
                               mesoPrefix,
                               user,
                               keepUndefined,
                               dropErrors,
                               refreshCache=True,
                               responseDates=responseDates)))
        import sys, traceback
        print(sys.exc_info())
        print(traceback.print_tb(sys.exc_info()[2]))
Ejemplo n.º 9
0
    def htmlInvitation(self,
                       invitation,
                       invitee=None,
                       fullDoc=False,
                       includeLink=True):
        """
        Returns an HTML document rendering the invitation.

        :param invitation: Invitation to render
        :type invitation: dict
        :param invitee: Invited user
        :type invitee: dict or None

        :returns: html document
        """
        from girderformindlogger.models.applet import Applet
        from girderformindlogger.models.profile import Profile
        from girderformindlogger.models.protocol import Protocol
        from girderformindlogger.models.token import Token
        from girderformindlogger.models.user import User
        from girderformindlogger.exceptions import GirderException
        from girderformindlogger.api.rest import getApiUrl
        from girderformindlogger.utility import context as contextUtil,        \
            mail_utils

        web_url = os.getenv('WEB_URI') or 'localhost:8082'

        accept = ("To accept or decline, visit <a href=\"{u}\">{u}</a>".format(
            u=f"https://{web_url}/#/invitation/{str(invitation['_id'])}")
                  ) if includeLink else ""
        applet = Applet().load(ObjectId(invitation['appletId']), force=True)
        appletName = applet['meta']['applet'].get(
            'displayName', applet.get('displayName', 'new applet'))
        try:
            skin = contextUtil.getSkin()
        except:
            skin = {}
        instanceName = skin.get("name", "MindLogger")
        role = invitation.get("role", "user")
        try:
            coordinator = Profile().coordinatorProfile(applet['_id'],
                                                       invitation["invitedBy"])
        except:
            coordinator = None
        displayProfile = Profile().displayProfileFields(invitation, invitee)
        description = applet.get('meta', {}).get('applet', {}).get(
            "schema:description",
            Protocol().load(
                applet['meta']['protocol']['_id'].split('protocol/')[-1],
                force=True).get('meta', {}).get('protocol') if 'protocol'
            in applet.get('meta', {}) else {}).get("schema:description", "")
        managers = mail_utils.htmlUserList(Applet().listUsers(applet,
                                                              'manager',
                                                              force=True))
        coordinators = mail_utils.htmlUserList(Applet().listUsers(
            applet, 'coordinator', force=True))
        reviewers = mail_utils.htmlUserList(Applet().listUsers(applet,
                                                               'reviewer',
                                                               force=True))

        body = mail_utils.renderTemplate(
            f'welcome{"Owner" if role == "owner" else ""}.{invitation.get("lang", "en")}.mako',
            {
                'accept':
                accept,
                'appletName':
                appletName,
                'byCoordinator':
                "by {} ({}) ".format(
                    coordinator.get("displayName", "an anonymous entity"),
                    "<a href=\"mailto:{email}\">{email}</a>".format(
                        email=coordinator["email"]) if "email" in coordinator
                    and coordinator["email"] is not None
                    else "email not available") if isinstance(
                        coordinator, dict) else "",
                'coordinators':
                "{}".format(coordinators if len(coordinators) else
                            "<ul><li>None</li></ul>"),
                'instanceName':
                " on {}".format(instanceName)
                if instanceName is not None and len(instanceName) else "",
                'managers':
                "{}".format(
                    managers if len(managers) else "<ul><li>None</li></ul>"),
                'reviewers':
                "{}".format(
                    reviewers if len(reviewers) else "<ul><li>None</li></ul>"),
                'role':
                "an editor" if role == "editor" else "a {}".format(role),
                'url':
                f'https://{web_url}/#/invitation/{str(invitation["_id"])}'
            })

        return (body if not fullDoc else """
            <!DOCTYPE html>
            <html>
            <head>
            <meta charset="UTF-8">
            <title>Invitation to {appletName} on {instanceName}</title>
            </head>
            <body>
            {body}
            </body>
            </html>
        """.format(appletName=appletName, instanceName=instanceName,
                   body=body).strip())