Skip to content

Instantly share code, notes, and snippets.

@tbarbugli
Last active September 30, 2021 09:28
Show Gist options
  • Select an option

  • Save tbarbugli/97bf26f400ecf1443ef6 to your computer and use it in GitHub Desktop.

Select an option

Save tbarbugli/97bf26f400ecf1443ef6 to your computer and use it in GitHub Desktop.

Revisions

  1. tbarbugli revised this gist Mar 29, 2018. 1 changed file with 164 additions and 71 deletions.
    235 changes: 164 additions & 71 deletions stream_twitter_tutorial.md
    Original file line number Diff line number Diff line change
    @@ -2,20 +2,23 @@ Build a scalable Twitter clone with Django and GetStream.io
    ---------------------------------------------------

    In this tutorial we are going to build a Twitter clone using Django and [GetStream.io](https://GetStream.io), a hosted API for newsfeed development.
    We will show you how easy is to power your newsfeeds with GetStream.io. At the end of this tutorial we will have a Django app with a profile feed, a timeline feed, support for following users, hashtags and mentions.

    I assume that you are familiar with Django. If you're new to Django the [official tutorial] (https://docs.Djangoproject.com/en/1.7/intro/install/) explains it very well.
    We will show you how easy is to power your newsfeeds with GetStream.io. For brevity we leave out some basic Django-specific code and recommend you refer you to the [Github project](https://github.com/GetStream/django_twitter/) for the complete runnable source code. At the end of this tutorial we will have a Django app with a profile feed, a timeline feed, support for following users, hashtags and mentions.

    I assume that you are familiar with Django. If you're new to Django the [official tutorial] (https://docs.djangoproject.com/en/2.0/intro/) explains it very well.


    Bootstrap the Django application
    --------------------------------

    We will use Python 2.7 and Django 1.7, which is the latest major release at the time of writing.
    We will use Python 3.6 and Django 2.0, which is the latest major release at the time of writing.

    Make sure you have a working Django project before you continue to the next part of the tutorial.


    Create the Django app
    ---------------------

    Let's start by creating a new Django app called stream_twitter

    ```sh
    @@ -29,7 +32,7 @@ Install stream_django
    [stream_django](https://github.com/getstream/stream-django) provides the GetStream integration for Django, it is built on top of the [stream_python](https://github.com/getstream/stream-python) client.

    ```sh
    pip install stream_django
    pip install stream-django
    ```

    To enable stream_django you need to add it to your INSTALLED_APPS:
    @@ -47,10 +50,11 @@ INSTALLED_APPS = (
    )
    ```


    GetStream.io setup
    ------------------

    First of all, we need to create an account on GetStream.io. You can signup with Github and it's free for usage below 3 million feed updates per month. Once you've signed up, get your api_key and api_secret from the dashboard and add them to Django's settings:
    First of all, we need to create an account on GetStream.io. You can signup with Github and it's free for usage below 3 million feed updates per month. Once you've signed up, get your API 'key' and 'secret' from the dashboard and add them to Django's settings:

    ```python
    STREAM_API_KEY = 'my_api_key'
    @@ -67,15 +71,18 @@ To keep it as simple as possible, we will use Django's contrib.auth User model.
    Have a look below at the initial version of the Tweet and Follow models.

    ```python
    from django.db import models


    class Tweet(models.Model):
    user = models.ForeignKey('auth.User')
    user = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    text = models.CharField(max_length=160)
    created_at = models.DateTimeField(auto_now_add=True)


    class Follow(models.Model):
    user = models.ForeignKey('auth.User', related_name='friends')
    target = models.ForeignKey('auth.User', related_name='followers')
    user = models.ForeignKey('auth.User', related_name='friends', on_delete=models.CASCADE)
    target = models.ForeignKey('auth.User', related_name='followers', on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
    @@ -93,7 +100,6 @@ Let's also setup the view to add tweets.

    ```python
    from django.views.generic.edit import CreateView
    from stream_twitter.models import Follow
    from stream_twitter.models import Tweet


    @@ -109,27 +115,32 @@ class TweetView(CreateView):
    And of course add it to urls.py

    ```
    from django.conf.urls import patterns, include, url
    from django.contrib import admin
    from django.contrib.auth.decorators import login_required
    from django.urls import path
    from stream_twitter import views
    urlpatterns = [
    path('admin/', admin.site.urls),
    path('timeline/', login_required(views.TimelineView.as_view()), name='timeline'),
    re_path(r'^user/(?P<username>.+)/', views.UserView.as_view(), name='user_feed')
    ]
    urlpatterns = patterns('',
    url(r'^admin/', include(admin.site.urls)),
    url(r'^tweet/', login_required(views.TweetView.as_view()))
    )
    ```

    Now that we have the view setup for creating tweets we can move on to setting up the newsfeed.


    Model integration
    -----------------

    We want the tweets to be stored in the authors feed. This is when we start using the stream_django integration. We can configure the Tweet model so that it will syncronise automatically with feeds.

    To do this we need to make Tweet a subclass of ```stream_django.activity.Activity```

    So let's do that by modifying the model we defined earlier in `stream_twitter.models`:

    ```python
    from stream_django import activity

    @@ -146,48 +157,39 @@ So, let's give it a try using Django's shell:
    python manage.py shell
    ```


    ```python
    from stream_twitter.models import Tweet
    from django.contrib.auth.models import User

    user = User.objects.all().first()
    user, _created = User.objects.get_or_create(username='tester')

    Tweet.objects.create(
    user=user,
    text='Go Cows!'
    )
    ```

    ```
    NotImplementedError: Tweet must implement activity_object_attr property
    ```

    Oh snap! Why is Django complaining about some missing property in the Tweet model?
    We've now created our first Tweet, and in turn added an Activity to a Feed via the Stream API. By default, *stream-django* creates and adds the Activity to a feed named after the 'actor' property. This can be customized by overriding the `_attr` functions inherited from the `stream_django.activity.Activity` mixin on the Django Model.

    The reason for this is quite simple and the problem is easy to fix. When the Tweet is created, stream_django converts it into an activity and sends that to GetStream APIs.

    Now, this is the first time we talk about activities, let's take a moment to define what an activity is.
    Now, this is the first time we talk about Activities and Feeds, let's take a moment to define what an activity is.

    An activity is an object that contains the information about an action that is performed by someone involving an object. When you write data to GetStream's feeds, you send this data in the form of activities. The simplest activity is made by these three fields: actor, object and verb. For example: Tommaso tweets: 'Go cows!'

    GetStream.io API's allow you to store additional fields in your feeds, as you can see from the documentation [here](https://GetStream.io/docs/#add-remove-activities).
    GetStream.io API's allow you to store additional fields in your feeds, as you can see from the documentation [here](https://getstream.io/docs/#adding-activities).

    In order to convert a model instance into an activity, the model class must implement `activity_object_attr`. In the case of Tweet, the *object* is the tweet itself, the *actor* is the user that created the tweet and the *verb* is 'tweet'.
    We can verify that the Activity was added by using the Data Browser in GetStream.io's Dashboard.

    ```python
    class Tweet(activity.Activity, models.Model):
    ...
    In this example you can determine the feed name by inspecting the `activity_actor` property:

    @property
    def activity_object_attr(self):
    return self
    ```python
    >>> t.activity_actor
    'auth.User:1'
    ```

    The same code that we used before will work now, meaning that the tweet gets stored on the author's feed. We can verify that by using the Data Browser in GetStream.io's Dashboard.
    ![Dashboard](https://gist.githubusercontent.com/dwightgunning/1e775a8a6d20b0f02ce02936789f1afa/raw/f69c029bf45c7a318f0750bc867e420d6c48734d/stream-dashboard.png)

    The tweet data will be stored in the feed with *slug* user and *id* 1

    ![Dashboard](http://oi60.tinypic.com/2rhmvtk.jpg)

    User feed
    ---------
    @@ -196,24 +198,37 @@ So now that every tweet gets stored in the author's feed, we are going to add a

    ```python
    from django.contrib.auth.models import User
    from django.shortcuts import render, get_object_or_404
    from django.views.generic import DetailView

    from stream_django.enrich import Enrich
    from stream_django.feed_manager import feed_manager

    enricher = Enrich()


    class UserView(DetailView):
    model = User
    template_name = 'stream_twitter/user.html'

    def get_object(self):
    return self.get_queryset().get(username=self.kwargs['username'])

    def profile_feed(request, username=None):
    enricher = Enrich()
    user = User.objects.get(username=username)
    feed = feed_manager.get_user_feed(user.id)
    activities = feed.get(limit=25)['results']
    enricher.enrich_activities(activities)
    context = {
    'activities': activities
    }
    return render(request, 'tweets.html', context)
    def get_context_data(self, object):
    user = self.object
    feeds = feed_manager.get_user_feed(user.id)
    activities = feeds.get()['results']
    activities = enricher.enrich_activities(activities)

    return {
    'activities': activities,
    'user': user,
    'login_user': self.request.user
    }
    ```


    There are two new things that I should explain: the *feed manager* and the *enricher*. As the name suggests, the feed manager takes care of managing the different feeds involved in your app. In this case we ask the feed manager to give us the user feed for current user.
    There are two new things that I should explain: the *feed manager* and the *enricher*. As the name suggests, the feed manager takes care of managing the different feeds involved in your app. In this case we ask the feed manager to give us the user feed for the current user.

    We learned before that data is stored in feeds in form of activities. This is what a tweet looks like when we read it from GetStream.io:

    @@ -228,10 +243,11 @@ We learned before that data is stored in feeds in form of activities. This is wh

    As you can see, 'object' field does not contain the tweet itself but a reference to that (the same applies to the 'actor' field). The enricher replaces these references with model instances.


    Templating feeds
    ----------------

    Django_stream comes with a templatetag that helps you to show the content from feeds in your templates. This can get quite complex as you add different kinds of activities in your feeds.
    *django_stream* comes with a templatetag that helps you to show the content from feeds in your templates. This can get quite complex as you add different kinds of activities in your feeds.

    Here is a very minimal tweets.html template:

    @@ -243,7 +259,7 @@ Here is a very minimal tweets.html template:
    {% endfor %}
    ```

    The first time your run this, Django will complain that 'activity/tweet.html' is missing. That's because the render_activity templatetag inspects the activity object and loads the template based on the verb.
    The first time you run this, Django will complain that 'activity/tweet.html' is missing. That's because the render_activity templatetag inspects the activity object and loads the template based on the verb.
    Because the verb in this case is 'tweet', it will look for tweet.html in activity path. The templatetag accepts extra options to make your templates as re-usable as possible, see [here](https://github.com/GetStream/stream-Django#templating) for the templatetag documentation.


    @@ -253,13 +269,28 @@ Feed Follow
    As a next step we're going to add the ability to follow users to the application. To do this we create a view that creates Follow objects.

    ```python
    from django.views.generic.edit import CreateView, DeleteView
    from django.urls import reverse_lazy
    from stream_twitter.forms import FollowForm
    from stream_twitter.models import Follow

    class FollowView(CreateView):
    form_class = FollowForm
    model = Follow
    fields = ['target']
    success_url = reverse_lazy('timeline_feed')

    def form_valid(self, form):
    form.instance.user = self.request.user
    return super(Tweet, self).form_valid(form)
    return super(FollowView, self).form_valid(form)


    class UnfollowView(DeleteView):
    model = Follow
    success_url = reverse_lazy('timeline_feed')

    def get_object(self):
    target_id = self.kwargs['target_id']
    return self.get_queryset().get(target__id=target_id)
    ```

    Now we can use Django's signals to perform follow/unfollow requests on GetStream APIs.
    @@ -278,21 +309,28 @@ post_save.connect(follow_feed, sender=Follow)
    post_delete.connect(unfollow_feed, sender=Follow)
    ```


    Timeline view (AKA flat feed)
    -----------------------------

    The hardest part for a scalable Twitter clone is the feed showing the tweets from people you follow. This is commonly called the timeline view or newsfeed. The code below shows the timeline.

    ```python
    def timeline(request):
    enricher = Enrich()
    feed = feed_manager.get_news_feeds(request.user.id)['flat']
    activities = feed.get(limit=25)['results']
    enricher.enrich_activities(activities)
    context = {
    'activities': activities
    }
    return render(request, 'timeline.html', context)
    from django.views.generic import TemplateView

    class TimelineView(TemplateView):
    template_name = 'stream_twitter/timeline.html'

    def get_context_data(self):
    context = super(TimelineView, self).get_context_data()

    feeds = feed_manager.get_news_feeds(self.request.user.id)
    activities = feeds.get('timeline').get()['results']
    enriched_activities = enricher.enrich_activities(activities)

    context['activities'] = enriched_activities

    return context
    ```

    The code looks very similar to the code of profile_feed. The main difference is that we use feed manager's get_news_feeds. By default, GetStream.io apps and stream_django come with two newsfeeds predefined: flat and aggregated feeds. When you use `feed_manager.get_news_feeds`, you get a dictionary with flat and aggregated feeds. Since we are not going to use aggregated feeds, we can adjust Django settings:
    @@ -301,14 +339,52 @@ The code looks very similar to the code of profile_feed. The main difference is
    STREAM_NEWS_FEEDS = dict(flat='flat')
    ```


    Adding activities
    -----------------

    Let's modify the `TimelineView` to include a form that will accept new tweets:

    ```python
    from django.views.generic.edit import CreateView


    class TimelineView(CreateView):
    fields= ['text']
    model = Tweet
    success_url = reverse_lazy('timeline_feed')
    template_name = 'stream_twitter/timeline.html'

    def form_valid(self, form):
    form.instance.user = self.request.user
    return super(TimelineView, self).form_valid(form)

    def get_context_data(self, form=None):
    context = super(TimelineView, self).get_context_data()

    feeds = feed_manager.get_news_feeds(self.request.user.id)
    activities = feeds.get('timeline').get()['results']
    enriched_activities = enricher.enrich_activities(activities)

    context['activities'] = enriched_activities
    context['login_user'] = self.request.user
    context['hashtags'] = Hashtag.objects.order_by('-occurrences')

    return context
    ```


    Hashtags feeds
    --------------

    We want Twitter style hashtags to work as well. Doing this is surprisingly easy. Let's first open GetStream.io dashboard and create the 'hashtag' feed type. (Note: By default getstream.io will setup user, flat, aggregated and notification feeds. If you more feeds you need ot configure them in the dashboard)

    ![hashtagfeedform](http://oi57.tinypic.com/68akah.jpg)
    ![hashtagfeedform](https://gist.githubusercontent.com/dwightgunning/1e775a8a6d20b0f02ce02936789f1afa/raw/60e22ea09a25c1c3da62d4a52bbc8867f2983dc6/stream_explorer.png)

    ```python
    from django.template.defaultfilters import slugify


    class Tweet(activity.Activity, models.Model):

    def parse_hashtags(self):
    @@ -317,9 +393,12 @@ class Tweet(activity.Activity, models.Model):

    Now that we have parsed the hashtags, we could loop over them and publish the same activity to every hashtag feed. Fortunately there's a shortcut though. GetStream allows you to send a copy of an activity to many feeds with a single request.

    To do this, we only need to implement the `activity_notify` method in Twitter model:
    To do this, we only need to implement the `activity_notify` method to the Twitter model we created previously:

    ```python
    from stream_django.feed_manager import feed_manager


    class Tweet(activity.Activity, models.Model):

    @property
    @@ -335,17 +414,29 @@ From now on, activities will be stored to hashtags feeds as well. For instance,
    Again the view code looks really similar to the other views.

    ```python
    def hashtag(request, hashtag):
    enricher = Enrich()
    feed = feed_manager.get_feed('hashtag', hashtag)
    activities = feed.get(limit=25)['results']
    enricher.enrich_activities(activities)
    context = {
    'activities': activities
    }
    return render(request, 'hashtag.html', context)
    from django.views.generic import TemplateView

    from stream_django.enrich import Enrich
    from stream_django.feed_manager import feed_manager


    class HashtagView(TemplateView):
    template_name = 'stream_twitter/hashtag.html'

    def get_context_data(self, hashtag):
    context = super(TemplateView, self).get_context_data()

    hashtag = hashtag.lower()
    feed = feed_manager.get_feed('user', f'hash_{hashtag}')
    activities = feed.get(limit=25)['results']

    context['hashtag'] = hashtag
    context['activities'] = enricher.enrich_activities(activities)

    return context
    ```


    Mentions
    --------

    @@ -369,10 +460,12 @@ class Tweet(activity.Activity, models.Model):
    return targets
    ```


    Conclusion
    ----------

    Congratulations, you've reached the end of this tutorial. This article showed you how easy it is to build scalable newsfeeds with Django and GetStream.io. It took us just 100 LoC and (I hope) less than one hour to get this far.

    You can find the code from this tutorial and the fully functional application on [GitHub](https://github.com/GetStream/django_twitter). The same application is also available online [here](http://tw.getstream.io/). I hope you found this interesting and useful and I'd be glad to answer all of your questions.
    You can find the code from this tutorial and the fully functional application on [GitHub](https://github.com/GetStream/django_twitter). The application is also running and can be tested [here](http://tw.getstream.io/). I hope you found this interesting and useful and I'd be glad to answer all of your questions.

    If you're new to Django or GetStream.io, I highly recommend the [official django tutorial](https://docs.Djangoproject.com/en/1.7/intro/install/) and the [getstream.io getting started](https://getstream.io/get_started/#intro).
    If you're new to Django or GetStream.io, I highly recommend the [official django tutorial](https://docs.djangoproject.com/en/2.0/intro/) and the [getstream.io getting started](https://getstream.io/get_started/#intro).
  2. tbarbugli revised this gist Jun 10, 2016. 1 changed file with 6 additions and 6 deletions.
    12 changes: 6 additions & 6 deletions stream_twitter_tutorial.md
    Original file line number Diff line number Diff line change
    @@ -36,12 +36,12 @@ To enable stream_django you need to add it to your INSTALLED_APPS:

    ```python
    INSTALLED_APPS = (
    'Django.contrib.admin',
    'Django.contrib.auth',
    'Django.contrib.contenttypes',
    'Django.contrib.sessions',
    'Django.contrib.messages',
    'Django.contrib.staticfiles',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'stream_twitter',
    'stream_django'
    )
  3. tbarbugli revised this gist Mar 24, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion stream_twitter_tutorial.md
    Original file line number Diff line number Diff line change
    @@ -373,6 +373,6 @@ Conclusion
    ----------
    Congratulations, you've reached the end of this tutorial. This article showed you how easy it is to build scalable newsfeeds with Django and GetStream.io. It took us just 100 LoC and (I hope) less than one hour to get this far.

    You can find the code from this tutorial and the fully functional application on [GitHub](https://github.com/GetStream/django_twitter). I hope you found this interesting and useful and I'd be glad to answer all of your questions.
    You can find the code from this tutorial and the fully functional application on [GitHub](https://github.com/GetStream/django_twitter). The same application is also available online [here](http://tw.getstream.io/). I hope you found this interesting and useful and I'd be glad to answer all of your questions.

    If you're new to Django or GetStream.io, I highly recommend the [official django tutorial](https://docs.Djangoproject.com/en/1.7/intro/install/) and the [getstream.io getting started](https://getstream.io/get_started/#intro).
  4. tbarbugli revised this gist Mar 13, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion stream_twitter_tutorial.md
    Original file line number Diff line number Diff line change
    @@ -373,6 +373,6 @@ Conclusion
    ----------
    Congratulations, you've reached the end of this tutorial. This article showed you how easy it is to build scalable newsfeeds with Django and GetStream.io. It took us just 100 LoC and (I hope) less than one hour to get this far.

    You can find the code from this tutorial on [GitHub](https://github.com/tbarbugli/stream_twitter). I hope you found this interesting and useful and I'd be glad to answer all of your questions.
    You can find the code from this tutorial and the fully functional application on [GitHub](https://github.com/GetStream/django_twitter). I hope you found this interesting and useful and I'd be glad to answer all of your questions.

    If you're new to Django or GetStream.io, I highly recommend the [official django tutorial](https://docs.Djangoproject.com/en/1.7/intro/install/) and the [getstream.io getting started](https://getstream.io/get_started/#intro).
  5. tbarbugli created this gist Dec 4, 2014.
    378 changes: 378 additions & 0 deletions stream_twitter_tutorial.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,378 @@
    Build a scalable Twitter clone with Django and GetStream.io
    ---------------------------------------------------

    In this tutorial we are going to build a Twitter clone using Django and [GetStream.io](https://GetStream.io), a hosted API for newsfeed development.
    We will show you how easy is to power your newsfeeds with GetStream.io. At the end of this tutorial we will have a Django app with a profile feed, a timeline feed, support for following users, hashtags and mentions.

    I assume that you are familiar with Django. If you're new to Django the [official tutorial] (https://docs.Djangoproject.com/en/1.7/intro/install/) explains it very well.


    Bootstrap the Django application
    --------------------------------

    We will use Python 2.7 and Django 1.7, which is the latest major release at the time of writing.
    Make sure you have a working Django project before you continue to the next part of the tutorial.


    Create the Django app
    ---------------------
    Let's start by creating a new Django app called stream_twitter

    ```sh
    python manage.py startapp stream_twitter
    ```


    Install stream_django
    ---------------------

    [stream_django](https://github.com/getstream/stream-django) provides the GetStream integration for Django, it is built on top of the [stream_python](https://github.com/getstream/stream-python) client.

    ```sh
    pip install stream_django
    ```

    To enable stream_django you need to add it to your INSTALLED_APPS:

    ```python
    INSTALLED_APPS = (
    'Django.contrib.admin',
    'Django.contrib.auth',
    'Django.contrib.contenttypes',
    'Django.contrib.sessions',
    'Django.contrib.messages',
    'Django.contrib.staticfiles',
    'stream_twitter',
    'stream_django'
    )
    ```

    GetStream.io setup
    ------------------

    First of all, we need to create an account on GetStream.io. You can signup with Github and it's free for usage below 3 million feed updates per month. Once you've signed up, get your api_key and api_secret from the dashboard and add them to Django's settings:

    ```python
    STREAM_API_KEY = 'my_api_key'
    STREAM_API_SECRET = 'my_api_secret'
    ```


    The models
    ----------

    In this application we will have three different models: users, tweets and follows.

    To keep it as simple as possible, we will use Django's contrib.auth User model.
    Have a look below at the initial version of the Tweet and Follow models.

    ```python
    class Tweet(models.Model):
    user = models.ForeignKey('auth.User')
    text = models.CharField(max_length=160)
    created_at = models.DateTimeField(auto_now_add=True)


    class Follow(models.Model):
    user = models.ForeignKey('auth.User', related_name='friends')
    target = models.ForeignKey('auth.User', related_name='followers')
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
    unique_together = ('user', 'target')
    ```

    Now, let's create the schema migrations and apply them.

    ```sh
    python manage.py makemigrations stream_twitter
    python manage.py migrate
    ```

    Let's also setup the view to add tweets.

    ```python
    from django.views.generic.edit import CreateView
    from stream_twitter.models import Follow
    from stream_twitter.models import Tweet


    class TweetView(CreateView):
    model = Tweet
    fields = ['text']

    def form_valid(self, form):
    form.instance.user = self.request.user
    return super(Tweet, self).form_valid(form)
    ```

    And of course add it to urls.py

    ```
    from django.conf.urls import patterns, include, url
    from django.contrib import admin
    from django.contrib.auth.decorators import login_required
    from stream_twitter import views
    urlpatterns = patterns('',
    url(r'^admin/', include(admin.site.urls)),
    url(r'^tweet/', login_required(views.TweetView.as_view()))
    )
    ```

    Now that we have the view setup for creating tweets we can move on to setting up the newsfeed.

    Model integration
    -----------------

    We want the tweets to be stored in the authors feed. This is when we start using the stream_django integration. We can configure the Tweet model so that it will syncronise automatically with feeds.

    To do this we need to make Tweet a subclass of ```stream_django.activity.Activity```

    ```python
    from stream_django import activity


    class Tweet(activity.Activity, models.Model):
    ...
    ```

    From now on, new tweets will be added to the user feed of the author *and* to the feeds of all his followers. The same applies to deleting a tweet.

    So, let's give it a try using Django's shell:

    ```sh
    python manage.py shell
    ```

    ```python
    from stream_twitter.models import Tweet
    from django.contrib.auth.models import User

    user = User.objects.all().first()

    Tweet.objects.create(
    user=user,
    text='Go Cows!'
    )
    ```

    ```
    NotImplementedError: Tweet must implement activity_object_attr property
    ```

    Oh snap! Why is Django complaining about some missing property in the Tweet model?

    The reason for this is quite simple and the problem is easy to fix. When the Tweet is created, stream_django converts it into an activity and sends that to GetStream APIs.

    Now, this is the first time we talk about activities, let's take a moment to define what an activity is.

    An activity is an object that contains the information about an action that is performed by someone involving an object. When you write data to GetStream's feeds, you send this data in the form of activities. The simplest activity is made by these three fields: actor, object and verb. For example: Tommaso tweets: 'Go cows!'

    GetStream.io API's allow you to store additional fields in your feeds, as you can see from the documentation [here](https://GetStream.io/docs/#add-remove-activities).

    In order to convert a model instance into an activity, the model class must implement `activity_object_attr`. In the case of Tweet, the *object* is the tweet itself, the *actor* is the user that created the tweet and the *verb* is 'tweet'.

    ```python
    class Tweet(activity.Activity, models.Model):
    ...

    @property
    def activity_object_attr(self):
    return self
    ```

    The same code that we used before will work now, meaning that the tweet gets stored on the author's feed. We can verify that by using the Data Browser in GetStream.io's Dashboard.

    The tweet data will be stored in the feed with *slug* user and *id* 1

    ![Dashboard](http://oi60.tinypic.com/2rhmvtk.jpg)

    User feed
    ---------

    So now that every tweet gets stored in the author's feed, we are going to add a view that reads them.

    ```python
    from django.contrib.auth.models import User
    from stream_django.enrich import Enrich
    from stream_django.feed_manager import feed_manager


    def profile_feed(request, username=None):
    enricher = Enrich()
    user = User.objects.get(username=username)
    feed = feed_manager.get_user_feed(user.id)
    activities = feed.get(limit=25)['results']
    enricher.enrich_activities(activities)
    context = {
    'activities': activities
    }
    return render(request, 'tweets.html', context)
    ```


    There are two new things that I should explain: the *feed manager* and the *enricher*. As the name suggests, the feed manager takes care of managing the different feeds involved in your app. In this case we ask the feed manager to give us the user feed for current user.

    We learned before that data is stored in feeds in form of activities. This is what a tweet looks like when we read it from GetStream.io:

    ```
    [{
    'actor': 'auth.User:1',
    'object': 'stream_twitter.Tweet:1',
    'verb': 'tweet',
    ... other fields ...
    }]
    ```

    As you can see, 'object' field does not contain the tweet itself but a reference to that (the same applies to the 'actor' field). The enricher replaces these references with model instances.

    Templating feeds
    ----------------

    Django_stream comes with a templatetag that helps you to show the content from feeds in your templates. This can get quite complex as you add different kinds of activities in your feeds.

    Here is a very minimal tweets.html template:

    ```htmlDjango
    {% load activity_tags %}
    {% for activity in activities %}
    {% render_activity activity %}
    {% endfor %}
    ```

    The first time your run this, Django will complain that 'activity/tweet.html' is missing. That's because the render_activity templatetag inspects the activity object and loads the template based on the verb.
    Because the verb in this case is 'tweet', it will look for tweet.html in activity path. The templatetag accepts extra options to make your templates as re-usable as possible, see [here](https://github.com/GetStream/stream-Django#templating) for the templatetag documentation.


    Feed Follow
    -----------

    As a next step we're going to add the ability to follow users to the application. To do this we create a view that creates Follow objects.

    ```python
    class FollowView(CreateView):
    model = Follow
    fields = ['target']

    def form_valid(self, form):
    form.instance.user = self.request.user
    return super(Tweet, self).form_valid(form)
    ```

    Now we can use Django's signals to perform follow/unfollow requests on GetStream APIs.

    ```python
    def unfollow_feed(sender, instance, **kwargs):
    feed_manager.unfollow_user(instance.user_id, instance.target_id)


    def follow_feed(sender, instance, created, **kwargs):
    if created:
    feed_manager.follow_user(instance.user_id, instance.target_id)


    post_save.connect(follow_feed, sender=Follow)
    post_delete.connect(unfollow_feed, sender=Follow)
    ```

    Timeline view (AKA flat feed)
    -----------------------------

    The hardest part for a scalable Twitter clone is the feed showing the tweets from people you follow. This is commonly called the timeline view or newsfeed. The code below shows the timeline.

    ```python
    def timeline(request):
    enricher = Enrich()
    feed = feed_manager.get_news_feeds(request.user.id)['flat']
    activities = feed.get(limit=25)['results']
    enricher.enrich_activities(activities)
    context = {
    'activities': activities
    }
    return render(request, 'timeline.html', context)
    ```

    The code looks very similar to the code of profile_feed. The main difference is that we use feed manager's get_news_feeds. By default, GetStream.io apps and stream_django come with two newsfeeds predefined: flat and aggregated feeds. When you use `feed_manager.get_news_feeds`, you get a dictionary with flat and aggregated feeds. Since we are not going to use aggregated feeds, we can adjust Django settings:

    ```python
    STREAM_NEWS_FEEDS = dict(flat='flat')
    ```

    Hashtags feeds
    --------------

    We want Twitter style hashtags to work as well. Doing this is surprisingly easy. Let's first open GetStream.io dashboard and create the 'hashtag' feed type. (Note: By default getstream.io will setup user, flat, aggregated and notification feeds. If you more feeds you need ot configure them in the dashboard)

    ![hashtagfeedform](http://oi57.tinypic.com/68akah.jpg)

    ```python
    class Tweet(activity.Activity, models.Model):

    def parse_hashtags(self):
    return [slugify(i) for i in self.text.split() if i.startswith("#")]
    ```

    Now that we have parsed the hashtags, we could loop over them and publish the same activity to every hashtag feed. Fortunately there's a shortcut though. GetStream allows you to send a copy of an activity to many feeds with a single request.

    To do this, we only need to implement the `activity_notify` method in Twitter model:

    ```python
    class Tweet(activity.Activity, models.Model):

    @property
    def activity_notify(self):
    targets = []
    for hashtag in self.parse_hashtags():
    targets.append(feed_manager.get_feed('hashtag', hashtag))
    return targets
    ```

    From now on, activities will be stored to hashtags feeds as well. For instance, the feed 'hashtag:Django' will contain all tweets with '#Django' in it.

    Again the view code looks really similar to the other views.

    ```python
    def hashtag(request, hashtag):
    enricher = Enrich()
    feed = feed_manager.get_feed('hashtag', hashtag)
    activities = feed.get(limit=25)['results']
    enricher.enrich_activities(activities)
    context = {
    'activities': activities
    }
    return render(request, 'hashtag.html', context)
    ```

    Mentions
    --------

    Now that we found out about the `activity_notify` property, it only takes a bunch of extra lines of code to add user mentions.

    ```python

    class Tweet(activity.Activity, models.Model):

    def parse_mentions(self):
    mentions = [slugify(i) for i in self.text.split() if i.startswith("@")]
    return User.objects.filter(username__in=mentions)

    @property
    def activity_notify(self):
    targets = []
    for hashtag in self.parse_hashtags():
    targets.append(feed_manager.get_feed('hashtag', hashtag))
    for user in self.parse_mentions():
    targets.append(feed_manager.get_news_feeds(user.id)['flat'])
    return targets
    ```

    Conclusion
    ----------
    Congratulations, you've reached the end of this tutorial. This article showed you how easy it is to build scalable newsfeeds with Django and GetStream.io. It took us just 100 LoC and (I hope) less than one hour to get this far.

    You can find the code from this tutorial on [GitHub](https://github.com/tbarbugli/stream_twitter). I hope you found this interesting and useful and I'd be glad to answer all of your questions.

    If you're new to Django or GetStream.io, I highly recommend the [official django tutorial](https://docs.Djangoproject.com/en/1.7/intro/install/) and the [getstream.io getting started](https://getstream.io/get_started/#intro).