This is the seventh installment of a tutorial series focused on how to create a full-stack application using Flask and Stream. In this article, we will continue to explore creating, updating, and deleting activities by adding content to collections, a group of URLs with available previews. Be sure to check out the Github repo to follow along!
Getting Started
Similar to our user feeds from the last post, our first step will be to create a new “Feed Group” titled “Collections”:
The activities that we will be adding will be considered “content” which will be posted to a “collection”. We want users to be able to follow collections and see updates, so we will be using a flat feed. We can use the feed created from the collection to generate all of the content needed for the page. After the feed is created, a table in our database needs to be made to store the content.
Setting the Table
“Content” will include a URL, a title and description of the link, and fields for the ID, the Stream ID (reference previous article to decide if you need this field), timestamp, and a foreign key column to attach the associated collection.
app/models.py
https://gist.github.com/Porter97/be49a1ee28ad94c82b8a7d9b75bce1a6
Content is added to a collection by a user, so we must complete the relationship from the collection model. We also want to avoid adding duplicates, so we will create another class function to check for the existence of a URL within the collection.
app/models.py
https://gist.github.com/Porter97/53453a72eece66f8567d58590f23e526
To round out our model, we will construct class functions to add, update, and delete content.
app/models.py
https://gist.github.com/Porter97/2fefd30f34ed490e927e1d3bc119df72
As always, since we have changed our database, we will have to update and migrate.
flask db migrate flask db upgrade
Good Form
Now that our model is ready, we can start to build the form that will pass through to our template. This form will introduce a “select field” which we will populate with a user’s collections in the view. By default, data passed through a form is rendered as a string, and since our database expects an integer instead, we will coerce the collection field into an integer. The render_kw argument will allow us to pass a list of tuples, containing the collection ID and the name of the collection, to make the selection easier for the end user.
app/main/forms.py
https://gist.github.com/Porter97/084ff5141e7668d2a37e870117af426c
Template Time
Next, we will build templates for adding, editing, and viewing content. We must create the template for adding content first, using wtf.quick_form().
app/templates/new_content.html
https://gist.github.com/Porter97/4e299eaaf8272bedcf07635d55626ddc
To edit content, we will add a “permissions-check” to our original template so that the author or admin user can make changes. For your own projects, it is best to integrate a confirmation step when editing or deleting posts to ensure that it is done intentionally (we will touch on this in a future article).
app/templates/edit_content.html
https://gist.github.com/Porter97/51111118ca21e02a41993aeb855b8232
Lastly, we will set up the content view template, which will render the fields from the content, while using the permissions-check to allow editing.
app/templates/content.html
https://gist.github.com/Porter97/40b75eeba90b22eb42866e70b2b0f8e6
View From The Top
After we design our form, we can now build up the views. The select field element is the new addition to the process at this point. We will dynamically generate the select field box by creating a list of tuples. A select field requires two values: first, the value to return through the form and second, the text to represent the choice. To avoid confusion, the second value will be the name of the collection itself instead of an integer ID value.
app/main/views.py
https://gist.github.com/Porter97/7f721eb957661c6754e0430871558fe4
Sanity Check
We’re done! Now that we have built endpoints to create, read, upload and delete content for our app, we can finalize the process by ensuring that everything is functioning properly. Try it out by creating a new collection and adding your first piece of content!
Test Time
Now that our content and collections are up and running, I want to spend a little bit of time developing testing functions for the Stream components we have started to use.
The user model should test out adding and editing a user. When a user confirms their account, we add the user to Stream, so the test_valid_confirmation_token() function will need to be updated to use Stream. First, we initialize the Stream client and provide all of the fields Stream will need to create a user. Since the gravatar that we have associated with a user is dependent on their email address, we will have to make a similar change in test_valid_email_change_token(). To make certain that our test does not interfere with “real” data, delete the user after you are finished. If you were putting this testing into a production setting, it is good practice to create replica feeds to avoid any possible complications.
tests/test_user_model.py
https://gist.github.com/Porter97/52f7394ea5be2419d6127519ed7de5d5
To test all of the components of collections, we will be creating an entirely new file. In this test we will create a user, confirm and add them to Stream and the database, and build a collection for them. We will update the collection before deleting it, and then we will ultimately delete the user, too.
tests/test_collection_model.py
https://gist.github.com/Porter97/acd95d9fc8642d4f6e1801ca9c1d5062
Content will be another file, and we will test all of its components together. I find it helpful to perform tests that overlap to ensure that there are no issues in long chains of events. An example of a thorough test would include a user signing up, building a collection, editing it, creating a new piece of content in that collection and editing that as well. This will provide an end-to-end test of our application functionality.
tests/test_content_model.py
https://gist.github.com/Porter97/d6eaec6b9a90758d163da0df8eac18f3
Finishing Up
Now our web app core functionality is nearly completely up and running. We can create users and store them on Stream, and those users can create collections and add content to them, being able to edit and delete them. We can view individual content and collections, but we can’t view them as a feed- yet! The next article in this series will focus on showing you how to integrate and call Stream using JavaScript client-side while also using infinite scroll.
As always, thanks for reading and happy coding!
Note: The next post in this series can be found here.