def status(self): relname, signature, uid = self.resolve() adapted = IContentSubscribers(self.context) relnames = adapted.subscriptions_for(signature) if not relnames: return 'Unknown' return ', '.join(relnames)
def test_subscriber_items(self): """Subscriber items adapter search, index, unindex tests""" subitems = ISubscriberItems(self.sub) relnames = ('invited', 'confirmed', 'attended') # verify that we are starting with a clean slate: for name in relnames: assert len(subitems.find(name)) == 0 # index subscription to invited for self.content: subitems.index('invited', self.content) assert self.content in subitems.find('invited') # verify relationship from the other direction using the other adapter: csubs = IContentSubscribers(self.content) assert self.sub in csubs.find('invited') # finally, clean up: subitems.unindex('invited', self.content) for name in relnames: assert len(subitems.find(name)) == 0
def _mark_subname(self, token, relname="confirmed"): invite_relname, signature, uid = self.resolve() adapted = IContentSubscribers(self.context) if relname == 'confirmed': adapted.unindex('declined', signature) if relname == 'declined': adapted.unindex('confirmed', signature) adapted.index(relname, signature) #index subscriber signature for rel
def __init__(self, context, request): self.context = context self.request = request self.uid = getuid(context) self._site = getUtility(ISiteRoot) self._catalog = queryUtility(ISubscriptionCatalog) self._container = queryUtility(ISubscribers) self._users = getToolByName(self.context, 'acl_users') self._mtool = getToolByName(self.context, 'portal_membership') self._mail = getToolByName(self.context, 'MailHost') if self._catalog is None: raise ComponentLookupError( 'could not locate installed local subscription catalog; '\ 'is collective.inviting product correctly installed in site?') self._provider = IContentSubscribers(context) self._load_indexed() self.add_result = () #empty result, initially self.result_group = None #group name in add form, if used self.sent_to = [] self.debug_msg_log = []
class SubscribersView(object): """ Subscribers view of content item, shows lists of subscriptions to content object. Caches lookups of subscribers for all index names for this item in order to achieve constant running time (instead of quadratic efficiency for per-subscriber/per-row lookup). """ def __init__(self, context, request): self.context = context self.request = request self.uid = getuid(context) self._site = getUtility(ISiteRoot) self._catalog = queryUtility(ISubscriptionCatalog) self._container = queryUtility(ISubscribers) self._users = getToolByName(self.context, 'acl_users') self._mtool = getToolByName(self.context, 'portal_membership') self._mail = getToolByName(self.context, 'MailHost') if self._catalog is None: raise ComponentLookupError( 'could not locate installed local subscription catalog; '\ 'is collective.inviting product correctly installed in site?') self._provider = IContentSubscribers(context) self._load_indexed() self.add_result = () #empty result, initially self.result_group = None #group name in add form, if used self.sent_to = [] self.debug_msg_log = [] def _load_indexed(self): self.indexed = {} for idx in self.indexes(): self.indexed[idx] = [self._container.get(sig) for sig in self._catalog.search( {idx:self.uid})] def member_fullname(self, m): try: info = self._mtool.getMemberInfo(m) if info is None: return '--' v = self._mtool.getMemberInfo(m).get('fullname',None) except KeyError: v = '--' return v def indexes(self): """return tuple of all subscription index names""" return tuple(self._catalog.indexes) def subscribers(self, name=None): """ list subscribers of item given a subscription name; if None, list all subscribers related to the adapted content item, regardless of which named relationships they are subscribed to. """ if name is None: return self._provider.find() #all subscribers, all indexes return self._provider.find(name) def is_subscribed(self, subscriber, name): """ is subscriber subscribed to this item for relationship named name (uses cached values of subscription catalog lookups from view construction). """ indexed_for_name = self.indexed.get(name, None) if indexed_for_name is None:return False return subscriber in indexed_for_name def enumerate_groups(self): """ enumerate groups of users; returns sequence containing group objects """ return self._users.source_groups.getGroups() def group_members(self, groupname): """ enumerate/list members for group name; returns sequence of tuples containing (fullname, username). """ group = self._users.source_groups.getGroupById(groupname) users = group.getMemberIds() names = [self.member_fullname(m) for m in users] return zip(users, names) def all_users(self): users = self._users.getUserNames() names = [self.member_fullname(m) for m in users] return zip(users, names) def search_members(self, query): """ Search for users with string query; returns sequence of tuples containing (fullname, username). """ matching = [u['userid'] for u in self._users.searchUsers(fullname=query)] names = [self.member_fullname(m) for m in matching] return zip(matching, names) def _parse_email_input(self, input): """ Transform mixture of comma/whitespace into list of tuples of name, email for each address in input. """ input = input.strip() #strip leading/trailing \s input = re.sub('\r\n','\n', input) #CRLF -> LF return list(AddressList(input)) def _get_recipient(self, sub): """ given subscriber as IItemSubscriber object, get recipient as IMailRecipient (TODO: create an adapter for this?) or None. """ if sub.namespace == 'member': member = self._mtool.getMemberById(sub.user) if member is None: return None#silently ignoring members with no info mto = member.getProperty('email', None) if mto is None: return None #again, ignore if we have no email mto_name = member.getProperty('fullname', '') elif sub.namespace == 'email': mto = sub.email mto_name = '' else: return None # unhandled subscriber signature namespace return MailRecipient(mto, mto_name) def _send_invitation_message(self, sub, token): """given subscriber amd token, render and send invite email""" recipient = self._get_recipient(sub) #get IMailRecipient object if recipient is None: return self.request.form['token'] = token # passes token to message msg = queryMultiAdapter((self.context, self.request), name=u'invitation_email')(recipient=recipient) if 'nomail' not in self.request.form: self._mail.send(msg) self.sent_to.append(recipient) # can be usef by template: display log if 'debug' in self.request.form: self.debug_msg_log.append(msg) def tokenize(self, sub, idx='invited'): subkeys = queryUtility(ISubscriptionKeys) token = subkeys.add(idx, sub, self.uid) # store sub, get token return token def subscriptions_for(self, signature): adapted = IContentSubscribers(self.context) return adapted.subscriptions_for(signature) def invite(self, sub, idx='invited'): signature = sub.signature() previously_invited = 'invited' in self.subscriptions_for(signature) if not previously_invited: self._catalog.index(sub, self.uid, names=(idx,)) token = self.tokenize( signature, idx ) #store subscription generate token self._send_invitation_message(sub, token) def update(self, *args, **kwargs): form = self.request.form if 'listgroup' in form: groupname = form['membergroup'] if groupname: self.add_result = self.group_members(groupname) get_group = self._users.source_groups.getGroupById self.result_group = get_group(groupname) else: self.add_result = self.all_users() if 'membersearch' in form: self.add_result = self.search_members(form['membersearch']) if 'add_email' in form: # member checkboxes or email addresses selected for addition add_email_addresses = self._parse_email_input(form['add_email']) add_members = [k.split('/')[1] for k,v in form.items() if k.startswith('member/') and v] # catalog email addresses and members with 'invited' relatiomship for name, addr in add_email_addresses: if addr: sig = ('email', addr) if sig not in self._container: sig, sub = self._container.add( namespace=sig[0], email=sig[1],) else: sub = self._container[sig] if name: # always globally store the latest name; note: this # has the consequence of possibly overriding previous # values of name-to-email mappings affecting other # events, but this is the least-evil expedient way # to handle this without storing a name-to-email # mapping for every event object context. sub.name = name.decode('utf-8') self.invite(sub) for sig in [('member',m) for m in add_members]: if sig not in self._container: sig, sub = self._container.add( namespace=sig[0], user=sig[1] ) else: sub = self._container[sig] self.invite(sub) self._load_indexed() #reload fresh state for re-render self.request.form['debug'] = '1' #TODO: remove this if 'submatrix_modify' in form: for idx in self.indexes(): existing_subs = set([sub.signature() for sub in self.indexed[idx]]) checked_subs = set([tuple(k.split('/')[1:]) for k,v in form.items() if k.startswith('%s/' % idx)]) remove_subs = existing_subs - checked_subs add_subs = checked_subs - existing_subs for sub in remove_subs: self._catalog.unindex(sub, self.uid, names=(idx,)) for sub in add_subs: self._catalog.index(sub, self.uid, names=(idx,)) self._load_indexed() #reload fresh state for re-render def __call__(self, *args, **kwargs): self.update(*args, **kwargs) return self.index(*args, **kwargs)
def subscriptions_for(self, signature): adapted = IContentSubscribers(self.context) return adapted.subscriptions_for(signature)
def test_content_subscribers(self): """Content subscribers adapter search, index, unindex tests""" csubs = IContentSubscribers(self.content) relnames = ('invited', 'confirmed', 'attended') # test that there are no subscribers for any of the relnames for # content yet assert self.sub not in self.container #not yet, at least for name in relnames: assert len(csubs.find(name)) == 0 # index, and verify via find: csubs.index('invited', self.sub) assert self.sub in self.container # result of index through adapter assert self.sub in csubs.find('invited') assert 'invited' in csubs.subscriptions_for(self.sub) assert IItemSubscriber.providedBy(csubs.find('invited')[0]) assert len(csubs.find('confirmed')) == 0 assert len(csubs.find('attended')) == 0 assert len(csubs.find()) == 1 #unamed, one subscription so far # index another name: csubs.index('confirmed', self.sub) assert self.sub in csubs.find('invited') assert self.sub in csubs.find('confirmed') assert 'confirmed' in csubs.subscriptions_for(self.sub) assert self.sub in csubs.find() # unindex, make sure item is not found: csubs.unindex('invited', self.sub) assert self.sub not in csubs.find('invited') #removed assert 'invited' not in csubs.subscriptions_for(self.sub) assert self.sub in csubs.find('confirmed') #this still exists assert 'confirmed' in csubs.subscriptions_for(self.sub) # verify that we can look from the other direction at what we've done: subitems = ISubscriberItems(self.sub) assert self.content in subitems.find('confirmed') # finally, clean up: for name in relnames: csubs.unindex(name, self.sub) assert len(csubs.find(name)) == 0