•11 months ago
This is the sixth installment of a tutorial series focusing on how to create a full-stack application using Flask and Stream. In this post, we will be exploring how to create, update, and delete activities on Stream, as well as enrichment of feed activities. We are going to dive into creating our first user-created objects, Collections, which will be a place to store URL links for all of their favorite content. Be sure to check out the Github repo to help you follow along!
This week we are going to be starting to integrate Stream into our app. Some of the concepts may be new and feel a little strange at first, but I will be taking some time to make sure you understand the ideas behind them so you will feel comfortable using them in your projects.
Put simply; Stream is a datastore with a bit of magic under the hood. It helps in the storage and retrieval of feed and chat objects to reduce latency in your application. This particular tutorial is also opinionated, abstracting a lot of the components in development to the server. Stream, however, is very robust, allowing you to use many different strategies in constructing your project architecture. Flat feeds, what we are going to be using in constructing our Collections and User activities, are a straightforward but extensible chronological store.
Before we can start, we will have to install the Stream Python package in our app using ‘pip install stream-python’. After that, get your project keys from the Stream Dashboard, and save them in config.py to retrieve them later.
All This Room For Activities
At the end of last week, I started with a brief tutorial on adding activities to a feed. Now we are going to look into how this will work in the context of our web app. As before, we are going to start with users. While users and activities should be stored on the app itself, part of the big draws of Stream is its low-latency retrieval, and being to offload some of our data retrieval operations to the Stream API. We will have to do a little more work to ensure that these capabilities are available on the client-side rather than the server to take full advantage.
An Enriching Experience
When adding our activity in the demo, we added a random ID number as the actor, as well as to the object. Now that we are using it in our app, the process becomes a little more involved. As we don’t want to have to call our API to retrieve information pertaining to a user for each feed object, we will be using the built-in enrichment capabilities. Enrichment is the process where we create a user on Stream, adding information to the object that, in subsequent calls will be joined when referenced. When retrieving our activities, the two things we will need for a user will be the username and their gravatar image. This means that we will have to make a fundamental change to the user registration flow. Unfortunately, if you have already created an account on your version of the app, we will have to delete and replace your account. However, it will give us an excellent opportunity to learn a valuable way to remove information from the database in the command line. Using the Flask Shell, type in the following command:
Now that the database is wiped clean, we can now make the changes we need. To keep things a little more secure, we will leave adding users to the Stream service until a user correctly confirms their account. We will also have to update the user's gravatar when they change their email. It is always a good practice to keep third party API requests as a class function instead of a view to be able to handle exceptions in the request as well.
Now that we have added our users to Stream when we request activities that reference them, their username and gravatar will be returned in the response, saving a trip to our backend. Next up is to start creating our first of those activities, which are Collections. As I explained at the beginning of this tutorial, Collections will be groups of URL links created by users and able to be followed by other users. They could contain all of your favorite Khan Academy videos or Medium articles that you want to share, or simply a place to store all those tabs open on your computer that you never want to close.
The first thing we have to do is to create the Collection model locally to store it.
For this model, we have created fields for the name of the collection, a brief description, a timestamp, and author_id, as well as its Stream ID. Finally, there is an author ID field that uses a foreign key to associate it with the Users table. To complete the relationship, we will have to add another field to the User table as well.
Migrate And Upgrade
Once again, we have made changes to the database, so we will have to upgrade and update it with
db flask migrate and
db flask upgrade in the CLI.
Forms, Forms, Forms
Now that our database model is ready, we can make our form for users to input their collections. We will only need to collect two fields in order to construct it, so this will snap together quickly.
Feed The Beast
To add the Collection creation activity to Stream, we will have to make a feed for Users. We will be using a Flat Feed because we will want to allow other users to follow it.
Out Of Sight, Out Of Mind
Once again, we will be abstracting our API calls from the view and into a class function to be able to handle exceptions better. The logical place to do this is in the Collection class within models.py. There are some options for you, as well. To make changes or delete a feed entry after it has been created, you need either the foreign_id and timestamp, or you need the Stream generated ID. I have personally chosen the former, as it reduces the number of commits I have to perform. I have also included the stream_id as a field in the Collection model and will continue to do so so that you can choose for yourself.
The Views From Up Here
Now that the feed is running, we can begin to construct the views. To start, we will make two endpoints, one to create a new Collection, the other to retrieve it.
For a new Collection, we import the
forms.py, and perform permissions checks with form validation before adding the object to the database. After committing, we then initialize the Stream client and add the activity to the User feed. You’ll notice for the actor; we used the
users.create_reference() function from the Stream client. This makes sure that when we request the feed from Stream later that our data can be enriched with the user information established during the user sign-up and confirmation. When we are calling the feed from JS, this means one less trip to our backend and will speed up our load times.
You might have also noticed that we seem to be repeating ourselves with the foreign-id and object fields by using the same value twice. For objects you can perform a very similar function to what we did with the user's field, storing and joining information on Stream, however since the information we will be storing for the Collection is slightly basic (just the name and description), this isn’t entirely necessary for our purposes. I would recommend looking into it here if you do have more complex objects that you’re hoping to create. To finish off these functions, we just have to wrap up our templates.
Changing Your Mind
As our final step, we will need to create methods to edit and delete Collections.
Initially, we will make sure that the person trying to edit or delete the collection is its original author or an administrator, or else rerouting them to a 403 page. After that, we update/delete the object both on Stream and in our local database. When performing these changes with Stream, you must include either the
foreign id and
time, or the unique Stream-ID that is returned when the activity is created. Finally, create the template:
In this post, you have seen how to create users with enriched data and references on Stream, as well as how to add activities in the form of Collections to a feed. You also constructed endpoints to update and delete those activities. In our next article, we will be doing the same thing for Content, which will be added to the Collections, followed by creating some testing functions to ensure everything is working correctly.
Thanks for reading and happy coding!
Note: The next post in this series can be found here.