Series: Building a Social Network with Flask & Stream – Part 9

This post is the ninth installment of a tutorial series focused on how to set up a full-stack application using Flask and Stream. This week, we’re going to be creating everything we need to make immersive social experiences for our app using follow relationships! Follow relationships includes the following of users, collections, as well as building a customized homepage timeline and notifications screen for each user. Be sure to check out the Github repo to follow along!

Getting Started

Our first step is to create our feeds. We are going to be using the aggregate feed type for our homepage, and a notifications feed (go figure) for our notifications page. Navigating to the Stream dashboard, click the add feed group button. Next, add the feeds:

Many-To-Many

Follow relationships, whether between users and collections or users and users, can be complicated structures to model. In the past, we have connected relationships between tables purely in the User, Collection, or Content tables. For this type, however, we will need to create an entirely new table to connect them. The nice part is that both of them will be remarkably similar, as what they are trying to accomplish is the same. We are going to start by creating those tables ( in app/models.py).

There is a lot of information there, so I will break it down. The Follow and CollectionFollow tables include the ID number for the follower, as well as the ID for the followed user, or collection (respectively). It also includes a timestamp so we can see when that association was formed. The additions to the User and Collection tables for following a collection should seem pretty familiar; we are just proxying that relationship to the new table. For the user-to-user follow, we are using a self-referential relationship. This means that the table is referencing itself for both the follower and the followee.

Methods to the Madness

Now that our models are created, we need ways to interact with it using methods. We will want to add a follow relationship, remove it, as well as check if one already exists, so we don't accidentally create multiple. We'll also make sure that these actions are updated to our Stream feeds as they happen. Once again, the actions will be similar between the two, so we will create them all together (in app/models.py).

The is_following, follow, and unfollow for both users and collections accomplish what we outlined above. Still, I added a new method that checks if the following relationship for users extends the other way. This will let us have a “follows you” tag in a user’s profile if the relationship exists.

Follow, Unfollow

Our next move is creating the endpoints for the follow and unfollow relationships (in app/main/views.py).

The functions themselves once again work in a very similar fashion to each other. They first check to see if the object to be followed exists (a sensible start), before moving into ensuring the relationship hasn’t already been made. After passing those checks, it creates the following and uploads the activity to Stream, and commits the entry. For unfollowing, it is almost the same, except that it checks to make sure the relationship exists before trying to delete it.

It’s All About Me

Most users will expect to see their posts along with their friends when scrolling through a timeline or even notification screen. For this, we are going to add some self follow functions to the User class as well as adding this function to the deploy CLI method. We’ll start by creating the functions (in app/models.py).

Our next step is to add it to the deploy CLI command ( in application.py).

Testing 1, 2

Now that we’ve created some new models and functionality, we should take some time to develop tests for them to ensure everything is working as it should. We’ll start with the collection follows (in tests/test_collection_model.py)

Here we create two new users with the first creating a collection. We then check to make sure the second user isn’t already following it, before creating, checking, and destroying that relationship. Cleaning up, we erase the users from Stream. Next, we will do the same thing for user follows ( in tests/test_user_model.py)

Before we move, be sure to give a quick flask test in the CLI to make sure that everything is working!

Next Steps

At this point, if a new user were to join, we wouldn't be able to find them, nor them us. We need to make a way to have users find each other on our app. Stream does not provide the functionality to retrieve a list of all the users in our app, but in my mind, that is a lot better than potentially having a malicious actor use a token to get all of our user info. Stream rightly assumes that we have our users stored on our system, where we can use permissioned access to that resource using login_required. Since we don't have access to Stream’s lightning-quick data retrieval, we’ll be switching the pagination method from infinite scroll to a button-based system. I find that if you can't do something correctly, don’t do it at all. If a user perceives an infinite scroll to be clunky and slow to load, that impression is quickly spread across the rest of the site. If at a certain point, you notice that it’s taking a long time to find someone, you can also quickly scale up and down the number of returned results (in config.py) to reduce the number of pages. We will start this section by creating a view for all users (in app/main/views.py)

Next is creating a pagination tool to render the pages at the bottom of the screen. This will be kept in a new file that will be imported to the pages that use it (in app\templates\_macros.html).

Now we will create a page to render the list of returned results on the page. As this template will only ever be imported and never be rendered as an independent page, we will use an underscore preceding its name (in app/templates/_users.html)

After that, we will create the user's page that is currently being returned by the view and import the _users template inside (in app/templates/users.html)

Finally, we need to create a link to the page on the navbar to allow us to navigate to the user's page (in app/templates/base.html)

F4F

Most social networks give you the ability to see who is following who, and who is followed by whom. We will implement this ourselves, as well as showing a count of those followers on the user/collection page.

Since it is the more basic one, we will start with creating the collection followers template (in app/templates/collection_followers.html).

User followers will be slightly more complicated, as unlike collections, users will both follow and be followed. We want to try and keep the number of pages to a minimum (DRY!), so we will need to use a little creativity with our templating (in app/templates/followers.html)

Now that the templates are finished, we create the views to return them. Since the many to many relationships separate the classes from each other, we will need to use a list comprehension to get the details on each user and pass them through as a variable in the template (in app/main/views.py)

Finally, we will need a way to access these pages, so we can update our user and collection pages to give a count as well as a link to see who is following or being followed by what. The collection page will be first (in app/templates/collection.html)

Now the same for the user page (in app/templates/user.html)

Act of Creation

Before we move on, create a new user and confirm them, and if you’re feeling particularly adventurous, create a new collection with some content for them. Remember before you do so to run flask deploy to migrate and update the database, as well as to add the self follows to both your profile and your collection!

Injections

Before we can follow a user or a collection, we will have to make sure that the user has the follow level permissions. We will be “injecting” permission to our views to make them available in our templates (in app/main/__init__.py)

On The Button

We will be updating the user page to add a dynamic follow/unfollow button dependent on the current follow status (in app/templates/user.html)

Next, we will do the same for our collection page (in app/templates/collections.html)

Finally, a little bit of housekeeping. Since there can be unauthenticated users who will be accessing our site, we will want to create an empty Stream user token for anonymous users so that a non-authenticated user won't cause the site to crash ( in app/models.py)

Following

If you haven't created a new user and some collections/content, do so now. It will help provide some validation after we are finished that everything is working properly. If/once you have, navigate to that user’s page with your original account (using that spiffy new all users endpoint) and follow both the account and it’s collection. You should see all of the new features that we’ve built so far.

Index and Notifications

Aggregate and Notification feeds have some important distinctions between flat feeds in the way that data is retrieved. As such, we will have to make some small changes to the script we used in our user and collection pages to construct our infinite scroll feed. Starting with the homepage timeline (in app/templates/index.html)

Next, we need to create our notification page (in app/templates/notifications.html)

While similar to flat feeds, there are some significant changes that I want to run through. First, the results now contain a nested list comprising “activities”. For the aggregated feed, it allows you to create custom logic to compress multiple similar activities (x added y new pieces of content to z collection). Notifications have their benefits, like seen and read receipts that can be updated in your JavaScript request. We will be taking advantage of this next week when we integrate a counter into the navbar to help users keep track of new activities.

After setting up the templates, we will update the view for the index as well as a notifications file (in app/main/views.py).

Last but not least, we will include the notifications link within the user options tab in the navbar (in app/templates/base.html)

Sanity Check

Running flask deploy and flask run, we can see our brand new homepage. This will show all of the newest content added to any collections the user is following. In the top right corner under an account, there is a notifications tab that will show you all of the newest collections created by users that you follow!

Final Thoughts

It’s taken a little bit of thought and time to get here, but the core elements of a fully-featured social web app are taking shape. We have customized feeds, a navigation system, and (hopefully not too) complex follow relationships between users. Users can create, edit, and delete content on the site, as well as have a customizable profile page. The one drawback we see is that the page isn’t particularly aesthetically pleasing. In our next article, we will change all that, as we start implementing link previews using Open Graph, CSS Styling, as well as some UX tweaks like displaying notification counts.

As always, thanks for reading, and happy coding!

Note: The next post in this series can be found here

TutorialsFeeds