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).

https://gist.github.com/Porter97/fd0cfae599ecbe81eef6804b29142552

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).

https://gist.github.com/Porter97/b147c168ce70a505d913a71415e52605

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).

https://gist.github.com/Porter97/0bbee6c5e833a8924793dd250ae0a178

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).

https://gist.github.com/Porter97/24955727bd79ed1bd6e72c7cfe6101b7

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

https://gist.github.com/Porter97/79558023b4df88f7b981985fb3cebfaf

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)

https://gist.github.com/Porter97/1e5b4e908df50047705366910b7fe36e

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)

https://gist.github.com/Porter97/e478f360fab4e3a4b285393089402da7

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)

https://gist.github.com/Porter97/02f606e11995bd8c7f31ee6082ac836a

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).

https://gist.github.com/Porter97/9bc7fd56d3d6eeda5d35b3980f3ef577

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)

https://gist.github.com/Porter97/9010fc08d6a108b2b84d0e1400019ae6

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)

https://gist.github.com/Porter97/d2f36c5d3743e9aa1836746546ad0d5a

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)

https://gist.github.com/Porter97/7785ffc7956f13ded030975fa77ff3f8

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).

https://gist.github.com/Porter97/ffc2d380266adfa20b97a72aef25d1f3

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)

https://gist.github.com/Porter97/63fac53690778a03c2ac37b9eae477c7

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)

https://gist.github.com/Porter97/c5dad2d0cff82c1cc891eea603cffc37

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)

https://gist.github.com/Porter97/68f4cb11a9bee61694efd34503bf6b62

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

https://gist.github.com/Porter97/eea5328c8f281f65c5e031987ba695be

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)

https://gist.github.com/Porter97/9b4995916e23038a98041dc3bc61eb87

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)

https://gist.github.com/Porter97/857d376f46682547af40dd3fb890c790

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

https://gist.github.com/Porter97/d73609ceafac9e228d995ce94e8eab6e

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)

https://gist.github.com/Porter97/f6434ac6dee95c31e4eedfb2880ff306

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)

https://gist.github.com/Porter97/fcb343751e44049e19a6512b1ebb9131

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

https://gist.github.com/Porter97/3c51a8129eda98fbd26c09e5d30e9422

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).

https://gist.github.com/Porter97/c9ec576b8a7935a08475325100f0b0ea

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

https://gist.github.com/Porter97/9779c9d392b81ace0f56a3b1ea0e0843

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

Tutorials

Feeds