def _unlike(self, request, data=None): (appchange, script, args, myId) = yield self._getBasicArgs(request) orgId = args['orgId'] itemId, item = data['id'] item = yield Item.unlike(itemId, item, myId, orgId) if not item: return args["items"] = {itemId: item} args["myLikes"] = {itemId: []} likesCount = int(item["meta"]["likesCount"]) convId = item["meta"].get('parent', itemId) if itemId != convId: t.renderScriptBlock(request, "item.mako", "item_footer", False, "#item-footer-%s" % (itemId), "set", args=[itemId], **args) else: relation = Relation(myId, []) yield relation.initSubscriptionsList() toFetchEntities = set() likes = [] subscriptions = list(relation.subscriptions) if subscriptions: likes = yield db.get_slice(convId, "itemLikes", subscriptions) likes = [x.column.name for x in likes] toFetchEntities = set(likes) feedItems = yield db.get_slice(myId, "feedItems", [convId]) feedItems = utils.supercolumnsToDict(feedItems) isFeed = (utils.getRequestArg(request, "_pg") != "/item") hasComments = False if not isFeed: hasComments = True else: feedItems = yield db.get_slice(myId, "feedItems", [convId]) feedItems = utils.supercolumnsToDict(feedItems) for tuuid in feedItems.get(convId, {}): val = feedItems[convId][tuuid] rtype = val.split(":")[0] if rtype == "C": hasComments = True entities = base.EntitySet(toFetchEntities) if toFetchEntities: yield entities.fetchData() args["entities"] = entities handler = {"onload": "(function(){$$.convs.showHideComponent('%s', 'likes', false)})();" % (convId)} if not likes else None t.renderScriptBlock(request, "item.mako", "conv_footer", False, "#item-footer-%s" % (itemId), "set", args=[itemId, hasComments, likes], **args) t.renderScriptBlock(request, "item.mako", 'conv_likes', False, '#conv-likes-wrapper-%s' % convId, 'set', True, args=[itemId, likesCount, False, likes], handlers=handler, **args)
def get_suggestions(request, count, mini=False): authinfo = request.getSession(IAuthInfo) myId = authinfo.username SUGGESTIONS_UPDATE_FREQUENCY = 3 * 86400 # 5days MAX_INVALID_SUGGESTIONS = 3 now = time.time() suggestions = [] relation = Relation(myId, []) yield defer.DeferredList([relation.initSubscriptionsList(), relation.initFollowersList()]) @defer.inlineCallbacks def _get_suggestions(myId, relation): validSuggestions = [] invalidCount = 0 FORCE_UPDATE = False cols = yield db.get_slice(myId, "suggestions", reverse=True) for col in cols: if now - col.column.timestamp/1e6 > SUGGESTIONS_UPDATE_FREQUENCY: FORCE_UPDATE = True for userId in col.column.value.split(): if isValidSuggestion(myId, userId, relation): validSuggestions.append(userId) else: invalidCount += 1 defer.returnValue((validSuggestions, invalidCount, FORCE_UPDATE)) validSuggestions, invalidCount, FORCE_UPDATE = yield _get_suggestions(myId, relation) if not validSuggestions: yield _update_suggestions(request, relation) validSuggestions, invalidCount, FORCE_UPDATE = yield _get_suggestions(myId, relation) no_of_samples = count*2 population = count*5 if mini and len(validSuggestions) >= no_of_samples: suggestions = random.sample(validSuggestions[:population], no_of_samples) else: suggestions = validSuggestions[:] if FORCE_UPDATE or invalidCount > MAX_INVALID_SUGGESTIONS: _update_suggestions(request, relation) entities = base.EntitySet(suggestions[:count]) if suggestions: suggestions = suggestions[:count] yield entities.fetchData() defer.returnValue((suggestions, entities))
def getPeople(myId, entityId, orgId, start='', count=PEOPLE_PER_PAGE, fn=None, fetchBlocked=True): blockedUsers = [] toFetchCount = count + 1 nextPageStart = None prevPageStart = None userIds = [] if fetchBlocked: cols = yield db.get_slice(orgId, "blockedUsers") blockedUsers = utils.columnsToDict(cols).keys() if not fn: d1 = db.get_slice(entityId, "displayNameIndex", start=start, count=toFetchCount) d2 = db.get_slice(entityId, "displayNameIndex", start=start, count=toFetchCount, reverse=True) if start else None # Get the list of users (sorted by displayName) cols = yield d1 userIds = [col.column.name.split(":")[1] for col in cols] if len(userIds) > count: nextPageStart = utils.encodeKey(cols[-1].column.name) userIds = userIds[0:count] toFetchUsers = userIds # Start of previous page if start and d2: prevCols = yield d2 if len(prevCols) > 1: prevPageStart = utils.encodeKey(prevCols[-1].column.name) else: userIds, nextPageStart, prevPageStart\ = yield fn(entityId, start, toFetchCount) toFetchUsers = userIds entities = base.EntitySet(toFetchUsers) usersDeferred = entities.fetchData() relation = Relation(myId, userIds) results = yield defer.DeferredList([usersDeferred, relation.initSubscriptionsList()]) defer.returnValue((entities, relation, userIds,\ blockedUsers, nextPageStart, prevPageStart))
def _render(self, request): (appchange, script, args, myId) = yield self._getBasicArgs(request) # We are setting an empty value to 'cu' here just to make sure that # any errors when looking validating the entity should not leave us # in a bad state. request.addCookie('cu', '', path="/ajax/profile") if request.args.get("id", None): userId, ign = yield utils.getValidEntityId(request, "id", "user") else: userId = myId # XXX: We should use getValidEntityId to fetch the entire user # info instead of another call to the database. request.addCookie('cu', userId, path="/ajax/profile") user = base.Entity(userId) yield user.fetchData([]) if user._data: args['user'] = user detail = utils.getRequestArg(request, "dt") or "activity" args["detail"] = detail args["userId"] = userId args["menuId"] = "people" args["entities"] = base.EntitySet({myId:args['me'], userId:user}) # When scripts are enabled, updates are sent to the page as # and when we get the required data from the database. # When we are the landing page, we also render the page header # and all updates are wrapped in <script> blocks. landing = not self._ajax # User entered the URL directly # Render the header. Other things will follow. if script and landing: t.render(request, "profile.mako", **args) # Start with displaying the template and navigation menu if script and appchange: t.renderScriptBlock(request, "profile.mako", "layout", landing, "#mainbar", "set", **args) # Prefetch some data about how I am related to the user. # This is required in order to reliably filter our profile details # that I don't have access to. relation = Relation(myId, [userId]) args["relations"] = relation yield defer.DeferredList([relation.initSubscriptionsList(), relation.initGroupsList()]) # Reload all user-depended blocks if the currently displayed user is # not the same as the user for which new data is being requested. if script: t.renderScriptBlock(request, "profile.mako", "summary", landing, "#profile-summary", "set", **args) t.renderScriptBlock(request, "profile.mako", "user_subactions", landing, "#user-subactions", "set", **args) fetchedEntities = set() start = utils.getRequestArg(request, "start") or '' fromFetchMore = ((not landing) and (not appchange) and start) if detail == "activity": userItems = yield self._getUserItems(request, userId, start=start) args.update(userItems) elif detail == 'files': end = utils.getRequestArg(request, "end") or '' end = utils.decodeKey(end) start = utils.decodeKey(start) userFiles = yield files.userFiles(myId, userId, args['orgId'], start, end, fromFeed=False) args['userfiles'] = userFiles args['fromProfile'] = True if script: t.renderScriptBlock(request, "profile.mako", "tabs", landing, "#profile-tabs", "set", **args) handlers = {} if detail != "activity" \ else {"onload": "(function(obj){$$.convs.load(obj);})(this);"} if fromFetchMore and detail == "activity": t.renderScriptBlock(request, "profile.mako", "content", landing, "#next-load-wrapper", "replace", True, handlers=handlers, **args) else: t.renderScriptBlock(request, "profile.mako", "content", landing, "#profile-content", "set", True, handlers=handlers, **args) # List the user's subscriptions cols = yield db.get_slice(userId, "subscriptions", count=11) subscriptions = set(utils.columnsToDict(cols).keys()) args["subscriptions"] = subscriptions # List the user's followers cols = yield db.get_slice(userId, "followers", count=11) followers = set(utils.columnsToDict(cols).keys()) args["followers"] = followers # Fetch item data (name and avatar) for subscriptions, followers, # user groups and common items. entitiesToFetch = followers.union(subscriptions)\ .difference(fetchedEntities) # List the user's groups (and look for groups common with me) cols = yield db.multiget_slice([myId, userId], "entityGroupsMap") myGroups = set([x.column.name.split(':', 1)[1] for x in cols[myId]]) userGroups = set([x.column.name.split(':', 1)[1] for x in cols[userId]]) commonGroups = myGroups.intersection(userGroups) if len(userGroups) > 10: userGroups = sample(userGroups, 10) args["userGroups"] = userGroups args["commonGroups"] = commonGroups groupsToFetch = commonGroups.union(userGroups) entitiesToFetch = entitiesToFetch.union(groupsToFetch) entities = base.EntitySet(entitiesToFetch) yield entities.fetchData() for entityId, entity in entities.items(): if not entity._data: del entities[entityId] args["entities"].update(entities) if script: t.renderScriptBlock(request, "profile.mako", "user_subscriptions", landing, "#user-subscriptions", "set", **args) t.renderScriptBlock(request, "profile.mako", "user_followers", landing, "#user-followers", "set", **args) t.renderScriptBlock(request, "profile.mako", "user_me", landing, "#user-me", "set", **args) t.renderScriptBlock(request, "profile.mako", "user_groups", landing, "#user-groups", "set", **args) if script and landing: request.write("</body></html>") if not script: t.render(request, "profile.mako", **args)
def _search(self, request): (appchange, script, args, myId) = yield self._getBasicArgs(request) landing = not self._ajax myOrgId = args['orgId'] filter_map = {'people':'itemType'} term = utils.getRequestArg(request, "q") start = utils.getRequestArg(request, "start") or 0 filters = utils.getRequestArg(request, 'filter', multiValued=True) or [] filters = dict([(filter_map[x], x) for x in filters if x in filter_map]) args["term"] = term nextPageStart = '' if not term: errors.MissingParams() try: start = int(start) if start < 0: raise ValueError except ValueError: errors.InvalidParamValue() if script and landing: t.render(request, "search.mako", **args) if script and appchange: t.renderScriptBlock(request, "search.mako", "layout", landing, "#mainbar", "set", **args) count = SEARCH_RESULTS_PER_PAGE items = {} convs = set() highlighting = {} toFetchItems = [] toFetchStart = start toFetchEntities = set() people = [] relation = Relation(myId, []) yield defer.DeferredList([relation.initGroupsList(), relation.initSubscriptionsList(), relation.initFollowersList()]) regex = re.compile("(.*?)([^\s]*\s*[^\s]*\s*[^\s]*\s*)(<em class='highlight'>.*<\/em>)(\s*[^\s]*\s*[^\s]*\s*[^\s]*)(.*)") res = yield solr.search(term, args['orgId'], count, toFetchStart, filters={'itemType': 'people'}) docs = res.data.get('response', {}).get('docs', []) for item in docs: entityId = item['id'] people.append(entityId) toFetchEntities.add(entityId) while 1: res = yield solr.search(term, args['orgId'], count, toFetchStart) messages = [] convItems = [] numMatched = res.data.get('response', {}).get('numFound', 0) docs = res.data.get('response', {}).get('docs', []) highlighting.update(res.data.get('highlighting', {})) for index, item in enumerate(docs): itemId = item['id'] parent = item.get('parent', None) position = toFetchStart + index if item.get('itemType', '') == "message": if (item.get('id'), parent) not in messages: messages.append((item.get('id'), parent)) elif item.get('itemType', '') == 'people': entityId = item.get('id') if entityId not in people: people.append(entityId) toFetchEntities.add(entityId) elif parent: convItems.append((itemId, parent, position)) convs.add(parent) else: convItems.append((itemId, itemId, position)) convs.add(item.get('id')) if convs: filteredConvs, deleted = yield utils.fetchAndFilterConvs(convs, relation, items, myId, myOrgId) for itemId, convId, position in convItems: if convId in filteredConvs and itemId not in toFetchItems: toFetchItems.append(itemId) if len(toFetchItems) == count: if position +1 < numMatched: nextPageStart = position + 1 break if len(toFetchItems) == count or len(docs) < count: break toFetchStart = toFetchStart + count _items = yield db.multiget_slice(toFetchItems, "items", ['meta', 'attachments', 'tags']) items.update(utils.multiSuperColumnsToDict(_items)) for itemId, item in items.iteritems(): toFetchEntities.add(item['meta']['owner']) if 'target' in item['meta']: toFetchEntities.update(item['meta']['target'].split(',')) if itemId in highlighting and 'comment' in highlighting[itemId]: match = re.match(regex, unquote(highlighting[itemId]['comment'][0])) if match: comment = "".join(match.groups()[1:4]) comment = comment + " …" if match.group(5) else comment items[itemId]['meta']['comment'] = comment entities = yield db.multiget_slice(toFetchEntities, "entities", ['basic']) entities = utils.multiSuperColumnsToDict(entities) for userId in people: if userId in highlighting and userId in entities: entities[userId]['basic']['reason'] = {} for key in highlighting[userId]: if key in entities[userId]['basic']: entities[userId]['basic'][key] = " ".join(highlighting[userId][key]) else: entities[userId]['basic']['reason'][key] = highlighting[userId][key] fromFetchMore = True if start else False args['term'] = term args['items'] = items args['people'] = people args['entities'] = entities args['relations'] = relation args["conversations"] = toFetchItems args["nextPageStart"] = nextPageStart args['fromFetchMore'] = fromFetchMore args['fromSidebar'] = 'people' in filters.values() if script: onload = "(function(obj){$$.convs.load(obj);})(this);" if fromFetchMore: t.renderScriptBlock(request, "search.mako", "results", landing, "#next-load-wrapper", "replace", True, handlers={"onload": onload}, **args) else: t.renderScriptBlock(request, "search.mako", "results", landing, "#search-results", "set", True, handlers={"onload": onload}, **args) if 'people' not in filters.values() and people: t.renderScriptBlock(request, "search.mako", "_displayUsersMini", landing, "#people-block", "set", True, **args) if script and landing: request.write("</body></html>") if not script: t.render(request, "search.mako", **args)
def _update_suggestions(request, relation=None): authinfo = request.getSession(IAuthInfo) myId = authinfo.username orgId = authinfo.organization weights = {'group': {'follower': 15, 'subscription': 40, 'group': 30}, 'follower': {'follower': 9, 'subscription': 24, 'group': 15}, 'subscription': {'follower': 24, 'subscription': 64, 'group': 40}} defaultWeight = 1 people = {} @defer.inlineCallbacks def _compute_weights(userIds, myGroups, type): followers = yield db.multiget_slice(userIds, "followers", count=50) subscriptions = yield db.multiget_slice(userIds, "subscriptions", count=50) groups = yield db.multiget_slice(userIds, "entityGroupsMap") followers = utils.multiColumnsToDict(followers) subscriptions = utils.multiColumnsToDict(subscriptions) groups = utils.multiColumnsToDict(groups) for userId in followers: for follower in followers[userId]: people[follower] = people.setdefault(follower, defaultWeight) + weights[type]['follower'] for userId in subscriptions: for subscription in subscriptions[userId]: people[subscription] = people.setdefault(subscription, defaultWeight) + weights[type]['subscription'] for userId in groups: groupIds = [x.split(':', 1)[1] for x in groups[userId]] for groupId in groupIds: if groupId in myGroups: people[userId] = people.setdefault(userId, defaultWeight) + weights[type]['group'] if not relation: relation = Relation(myId, []) yield defer.DeferredList([relation.initSubscriptionsList(), relation.initFollowersList(), relation.initGroupsList()]) if relation.followers: yield _compute_weights(relation.followers, relation.groups, 'follower') if relation.subscriptions: yield _compute_weights(relation.subscriptions, relation.groups, 'subscription') if relation.groups: groupMembers = yield db.multiget_slice(relation.groups, "groupMembers", count=20) groupMembers = utils.multiColumnsToDict(groupMembers) for groupId in groupMembers: yield _compute_weights(groupMembers[groupId], relation.groups, 'group') cols = yield db.get_slice(orgId, "orgUsers", count=100) for col in cols: userId = col.column.name if userId not in people: people[userId] = people.setdefault(userId, 0) + defaultWeight suggestions = {} for userId in people: if isValidSuggestion(myId, userId, relation): suggestions.setdefault(people[userId], []).append(userId) yield db.remove(myId, "suggestions") weights_userIds_map = {} format = '>l' for weight in suggestions: key = struct.pack(format, weight) weights_userIds_map[key] = ' '.join(suggestions[weight]) if weights_userIds_map: yield db.batch_insert(myId, "suggestions", weights_userIds_map)
def renderItem(self, request, toFeed=False): (appchange, script, args, myId) = yield self._getBasicArgs(request) landing = not self._ajax myOrgId = args["orgId"] convId, conv = yield utils.getValidItemId(request, "id", columns=['tags']) itemType = conv["meta"].get("type", None) if 'parent' in conv['meta']: raise errors.InvalidItem('conversation', convId) start = utils.getRequestArg(request, "start") or '' start = utils.decodeKey(start) args['convId'] = convId args['isItemView'] = True args['items'] = {convId: conv} meta = conv["meta"] owner = meta["owner"] relation = Relation(myId, []) yield defer.DeferredList([relation.initGroupsList(), relation.initSubscriptionsList()]) args["relations"] = relation if script and landing: t.render(request, "item.mako", **args) if script and appchange: t.renderScriptBlock(request, "item.mako", "layout", landing, "#mainbar", "set", **args) args["entities"] = {} toFetchEntities = set() toFetchTags = set(conv.get("tags", {}).keys()) plugin = plugins[itemType] if itemType in plugins else None if plugin: entityIds = yield plugin.fetchData(args) toFetchEntities.update(entityIds) toFetchEntities.add(conv['meta']['owner']) if "target" in conv["meta"]: toFetchEntities.update(conv['meta']['target'].split(',')) if conv['meta']['owner'] not in toFetchEntities: toFetchEntities.add(conv['meta']['owner']) entities = base.EntitySet(toFetchEntities) yield entities.fetchData() args["entities"] = entities renderers = [] if script: t.renderScriptBlock(request, "item.mako", "conv_root", landing, "#conv-root-%s > .conv-summary" % (convId), "set", **args) convOwner = args["items"][convId]["meta"]["owner"] args["ownerId"] = convOwner if script: if itemType != "feedback": t.renderScriptBlock(request, "item.mako", "conv_owner", landing, "#conv-avatar-%s" % convId, "set", **args) else: feedbackType = conv['meta']['subType'] t.renderScriptBlock(request, "item.mako", "feedback_icon", landing, "#conv-avatar-%s" % convId, "set", args=[feedbackType]) # A copy of this code for fetching comments is present in _responses # Most changes here may need to be done there too. itemResponses = yield db.get_slice(convId, "itemResponses", start=start, reverse=True, count=constants.COMMENTS_PER_PAGE + 1) nextPageStart = itemResponses[-1].column.name\ if len(itemResponses) > constants.COMMENTS_PER_PAGE\ else None itemResponses = itemResponses[:-1] \ if len(itemResponses) > constants.COMMENTS_PER_PAGE\ else itemResponses responseKeys = [] for response in itemResponses: userKey, responseKey = response.column.value.split(":") responseKeys.append(responseKey) toFetchEntities.add(userKey) responseKeys.reverse() subscriptions = list(relation.subscriptions) likes = yield db.get_slice(convId, "itemLikes", subscriptions) \ if subscriptions else defer.succeed([]) toFetchEntities.update([x.column.name for x in likes]) entities = base.EntitySet(toFetchEntities) d1 = entities.fetchData() d2 = db.multiget_slice(responseKeys, "items", ["meta", "attachments"]) d3 = db.multiget_slice(responseKeys + [convId], "itemLikes", [myId]) d4 = db.get_slice(myOrgId, "orgTags", toFetchTags)\ if toFetchTags else defer.succeed([]) yield d1 fetchedItems = yield d2 myLikes = yield d3 fetchedTags = yield d4 fetchedItems = utils.multiSuperColumnsToDict(fetchedItems) myLikes = utils.multiColumnsToDict(myLikes) fetchedTags = utils.supercolumnsToDict(fetchedTags) # Do some error correction/consistency checking to ensure that the # response items actually exist. I don't know of any reason why these # items may not exist. missingIds = [x for x, y in fetchedItems.items() if not y] if missingIds: yield self._cleanupMissingComments(convId, missingIds, itemResponses) args["items"].update(fetchedItems) args["entities"].update(entities) args["myLikes"] = myLikes args["tags"] = fetchedTags args["responses"] = {convId: responseKeys} if nextPageStart: args["oldest"] = utils.encodeKey(nextPageStart) if script: t.renderScriptBlock(request, "item.mako", 'conv_footer', landing, '#item-footer-%s' % convId, 'set', **args) t.renderScriptBlock(request, "item.mako", 'conv_tags', landing, '#conv-tags-wrapper-%s' % convId, 'set', handlers={"onload": "$('#conv-meta-wrapper-%s').removeClass('no-tags')" % convId} if toFetchTags else None, **args) t.renderScriptBlock(request, "item.mako", 'conv_comments', landing, '#conv-comments-wrapper-%s' % convId, 'set', **args) t.renderScriptBlock(request, "item.mako", 'conv_comment_form', landing, '#comment-form-wrapper-%s' % convId, 'set', True, handlers={"onload": "(function(obj){$$.convs.load(obj);})(this);"}, **args) numLikes = int(conv["meta"].get("likesCount", "0")) if numLikes: numLikes = int(conv["meta"].get("likesCount", "0")) iLike = myId in args["myLikes"].get(convId, []) t.renderScriptBlock(request, "item.mako", 'conv_likes', landing, '#conv-likes-wrapper-%s' % convId, 'set', args=[convId, numLikes, iLike, [x.column.name for x in likes]], entities=args['entities']) if plugin and hasattr(plugin, 'renderItemSideBlock'): plugin.renderItemSideBlock(request, landing, args) if script and landing: request.write("</body></html>") if not script: t.render(request, "item.mako", **args)