Open yookore opened 9 years ago
What about feed[:1]?
Il sabato 14 marzo 2015, yookore notiions@github.com ha scritto:
@tbarbugli https://github.com/tbarbugli , I've been making greta progress with Stream Framework and its honestly a great piece of code. I've run into a little blocker which I think should be obvious but cant seem to wrap my head around it. How do i filter activities to only return the last action on an object in a user's feed. Here is the scenario User x has a feed that contains several actions carried out on a object (several posts, comments, likes etc) and when displaying the feed timeline, I want to be able to do two things,
- display only the last action in the stream and optionally
- aggregate actions perfomed on the that object.
I have a feeling that the Aggregated feed might be a good fit for this but I'd like to have some concrete examples on how to achieve this. Is there any portion of the code you could point me to?
— Reply to this email directly or view it on GitHub https://github.com/tschellenbach/Stream-Framework/issues/119.
sent from iphone (sorry for the typos)
@tbarbugli maybe I did not clarify what I am asking. Lets say I have a feed that contains several activities carried out on the same object, say a post. One person creates the post, someone else likes the post and later someone else comments on the post. What would happen if i pull the feed would be three separate activity items for the same object. What I would like to do is only display the most recent activity on the object which would be the comment and hide previous activity on that same object. I hope this makes sense?
I understand completely what you are asking for. I have devloped the same system and It is not that easy. I will try to explain it here as easy as I can. First we need to understand how activities are created based on Activity Streams Spec.
Action events are categorized by four main components.
Actor
. The object that performed the activity.Verb
. The verb phrase that identifies the action of the activity.Action Object
. (Optional) The object linked to the action itself.Target
. (Optional) The object to which the activity was performed.So for example we have a user John
and he posted something Object
called it as Article
John (actor)
posted (verb)
the (preposition)
Article (object)
12 hours ago
Now if any action is performed on the object, e.g. A comment on Article (A new object), that should create a new activity puting Article
object as target:
John (actor)
commented (verb)
Article Comment (object)
on (preposition)
Article (target)
1 hour ago
Now based on above spec, what we want (In your case) is that we want to aggregate (group) activities based on main activity, e.g. object
in first case and target
in other case. So we need to write our own aggregator extending the BaseAggregator to write our own method get_group and rank, get_group
is basically returns a unique key
under which all activities are grouped for same object.
newsfeed/aggregators.py
from stream_framework.aggregators.base import BaseAggregator
from newsfeed.activity import NewsFeedAggregatedActivity # we will discuss this later
class NewsFeedAggregator(BaseAggregator):
'''
Aggregates which should aggregate as "User A, B and C commented on post"
'''
aggregated_activity_class = NewsFeedAggregatedActivity
def rank(self, aggregated_activities):
'''
The ranking logic, for sorting aggregated activities
'''
aggregated_activities.sort(key=lambda a: a.updated_at, reverse=True)
return aggregated_activities
def get_group(self, activity):
'''
Returns a group based on the model and action object_id or target object_id (if present)
'''
activity_id = activity.target_id if activity.target_id else activity.object_id
# Make sure to store model_name or database table name in extra_context of activity
model = activity.extra_context['model_name']
group = '%s-%s' % (model, id) #left part should always be the model name followed by dash
return group
So far so good, now we will write NewsFeedAggregatedActivity
which we have used in the above aggregator:
newsfeed/activity.py
from stream_framework.activity import AggregatedActivity
from stream_framework.utils import make_list_unique
from newsfeed.utils import proper_verb_display
class NewsFeedAggregatedActivity(AggregatedActivity):
max_aggregated_activities_length = 500
@property
def main_activity(self):
'''
Returns the very first main activity object or target on which the future activities are performed and now being aggregated
'''
activity = self.recent_verb_activities[0]
# I am assuming here that you are storing the objects intead of object references for simplicity
# Storing the object references is prefered, then you need to query here to your database to fetch the object or target from database based on activity.object_id or activity.target_id
# Always prefer target over object
return activity.target if activity.target else activity.object
@property
def aggregated_verb_display(self, threshold=5):
'''
Returns verb display
e.g. A, B and C commented on this
e.g. A, B, C, D, E and 15 others commented on this
'''
activity = self.recent_verb_activities[-1]
verb = self.recent_verb
display = proper_verb_display(users=self.recent_verb_actors)
display += ' %s %s this' % (verb.past_tense, verb.preposition)
return display
def verb_display(self):
'''
Returns verb display of the main activity
e.g. User A posted the Article
'''
activity = self.main_activity
verb = activity.verb
display = proper_verb_display(users=[activity.actor])
model = activity.extra_context['model_name']
display += ' %s %s %s' % (verb.past_tense, verb.preposition, model.title())
return display
@property
def recent_actor(self):
return self.last_activity.actor
@property
def recent_verb(self):
return self.last_activity.verb
@property
def recent_verb_activities(self):
'''
Creating groups for similar activities based on object or target if present
'''
last_activity = self.last_activity
id = last_activity.target_id if last_activity.target_id else last_activity.object_id
activities = [a for a in self.activities if a.verb.past_tense == self.recent_verb.past_tense]
groups = {}
for a in activities:
key = a.target_id if a.target_id else a.object_id
if not groups.get(key):
groups[key] = []
groups[key].append(a)
return groups[id]
@property
def recent_verb_activities_count(self):
return len(self.recent_verb_activities)
@property
def recent_verb_actors(self):
activities = self.recent_verb_activities
activities.reverse()
return make_list_unique([a.actor for a in activities if a.verb.past_tense == self.recent_verb.past_tense])
Now we will write our own custom serializer to use our NewsFeedAggregatedActivity
newsfeed/serializers.py
from stream_framework.serializers.aggregated_activity_serializer import AggregatedActivitySerializer
from newsfeed.activity import NewsFeedAggregatedActivity
class NewsFeedAggregatedActivitySerializer(AggregatedActivitySerializer):
aggregated_class = NewsFeedAggregatedActivity
We will write our own verbs
to add the preposition
, use these verbs when generating activities:
newsfeed/verbs.py
from stream_framework.verbs import register
from stream_framework.verbs.base import Verb
class PostVerb(Verb):
id = 5 # 1-4 ids are already used in stream_framework.verbs.base
infinitive = 'post'
past_tense = 'posted'
preposition = 'the'
class CommentVerb(Verb):
id = 6
infinitive = 'comment'
past_tense = 'commented'
preposition = 'on'
register(PostVerb)
register(CommentVerb)
Now we will create feeds which will use above custom classes:
newsfeed/feeds.py
from stream_framework.feeds.redis import RedisFeed
from stream_framework.feeds.aggregated_feed.redis import RedisAggregatedFeed
from stream_framework.activity import Activity
from newsfeed.aggregators import NewsFeedAggregator
from newsfeed.serializers import NewsFeedAggregatedActivitySerializer
from newsfeed.activity import NewsFeedAggregatedActivity
class AggregatedUserFeed(RedisAggregatedFeed):
key_format = 'feed:aggregated:%(user_id)s'
aggregated_activity_class = NewsFeedAggregatedActivity
aggregator_class = NewsFeedAggregator
timeline_serializer = NewsFeedAggregatedActivitySerializer
max_length = 1000
merge_max_length = 1000
class UserFeed(RedisFeed):
key_format = 'feed:user:%(user_id)s'
activity_class = Activity
max_length = 1000
merge_max_length = 1000
Now we will write our own manager to use feeds.py
feeds:
newsfeed/managers.py
from stream_framework.feed_managers.base import Manager
from newsfeed.feeds import AggregatedUserFeed, UserFeed
class NewsFeedManager(Manager):
# customize the feed classes we write to
feed_classes = dict(
aggregated=AggregatedUserFeed
)
# customize the user feed class
user_feed_class = UserFeed
# next add yourself the add_activity logic remove_activity logic
newsfeed_stream = NewsFeedManager()
Some helpers:
newsfeed/utils.py
def proper_verb_display(users=[], threshold=5):
if users:
count = len(users)
if count <= 2:
display = " and ".join([u.username for u in users])
elif count < threshold:
display = ", ".join([u.username for u in users[:-1]]) + " and " + users[-1].username)
elif count == threshold:
display = ", ".join([u.username for u in users[:-1]]) + " and 1 other"
else:
display = ", ".join([u.username for u in users[:threshold-1]]) + " and +{} others".format(count - (threshold - 1))
return display
return ''
Now to show user feed, you do this:
from newsfeed.managers import newsfeed_stream
newsfeed = newsfeed_stream.get_feeds(user.id)['aggregated']
And in html:
@TODO: figure out how to display aggregated acitivites, Everything you need to display aggregated activities is up there. I am leaving this intentionaly so that you can show some effort by yourself :p
@intellisense thanks for your response. I will dig in to this and see what gives....
@tbarbugli , I've been making greta progress with Stream Framework and its honestly a great piece of code. I've run into a little blocker which I think should be obvious but cant seem to wrap my head around it. How do i filter activities to only return the last action on an object in a user's feed. Here is the scenario User x has a feed that contains several actions carried out on a object (several posts, comments, likes etc) and when displaying the feed timeline, I want to be able to do two things,
I have a feeling that the Aggregated feed might be a good fit for this but I'd like to have some concrete examples on how to achieve this. Is there any portion of the code you could point me to?