new

Building a Flask Blog: Part1



In this Flask Tutorial we are going to build a Blog from the scratch we’ll create an authentication system and create few articles. The tools used are Flask-SQLAlchemy, WTForms, Flask-WTF, Flask-Migrate, WebHelpers to bootstrap the basic application. The Database we will be using is PostgreSQL. Before starting make sure all the above mentioned dependencies are installed and working properly.

Setting the Stage

Assuming that everything is setup correctly, lets start by bootstrapping the Flask app. If you are not sure how to create a Flask app for larger applications, please refer the previous article Creating a Music Streaming App in Flask and then come back here. Now, we are assuming you have the directory setup correctly with the same set of CSS Files.

Configuring the Database

We are going to configure the PostgreSQL database. Open up the __init__.py file and import SQLAlchemy and configure it like this

We have configured the database URI with the app object and created an instance of the SQLAlchemy and initialized it with the app object. Refer the supported databases in the Flask-SQLAlchemy to initialize your database.

You need to download the supported drivers to connect to the respective database. Windows users can download the binaries and install it, and the others can install it with the simple pip command

PostgreSQL users can use this package.

Now, we are going to use Flask-Migrate to create migrations for our database.

Open __init__.py , import the various migrate commands into it.

rename the run.py to manage.py and include all these imports there. This is completely optional and you can name it whatever you like.

What does that code do ?

We have imported the MigrateCommand and manager from the __init__.py and configured it along the app, you might have noticed the manage.run() command which is little different from the tradional command of app.run(), we are giving the manager object to run our application and to do other kitchen-sink activities like running the migrations and server.

Now if you run the command in the terminal, you will notice that we got several options to play with.

Next to create out first migration, we will run

When the init command completes you will have a migrations folder with the configuration files ready to be used.

Shaping Models

Next we we will create our models to hold records in database, create models.py inside your app package.

Below the imports, we have Person class inheriting from the db.model class.

Next, we are declaring the tablename to be used to store all the Person objects and then we are declaring various SQLAlchemy Columns to hold the data. Inside of our Person class, we create attributes for the table’s name, primary key, and the user’s first name, last name, email, and password. We then write a constructor which sets the class attributes. We save names in title case and email addresses in lowercase to ensure a match regardless of how a user types in his credentials on subsequent sign ins.

We use the set_password function to set a salted hash of the password, instead of using the plain text password itself. Lastly, we have a function named check_password that uses check_password_hash, to check a user’s credentials on any subsequent sign ins. check_password_hash and generate_password_hash are the werkzeug security functions.

Next import the models into the __init__.py to link our models with the app .

Now __init__.py looks like this

Since we have created our First Model, we will migrate the models into the database. To do so, run this command in the terminal.

Terminal window will look something like this

Once the command is successful, you can see the revision file inside your versions folder, you can change the model later as per your need in this file. Now run the below command to ship the models into our database. 

Successful migration will create two tables inside your PostgreSQL database, one is alembic_version and person table.

Continuing our Person Model, create another class called Article to hold all our articles, import some helper modules to generate and print nice dates.

The Columns needed for our article model are self explanatory.

This @classmethod will return query object that can return whole dataset to us when needed.The query object will be sorting the rows by date in descending order and the created_in_words function will return the time of creation in words.
Now our models.py will look like this.

Now again run python manage.py db migrate and python manage.py db upgrade command to ship our models into database. You will have 3 tables in database now.

Creating Forms

Next, we will create our forms through which we can signup and create the articles. create forms.py inside app package and import Flask-WTF and WTForms imports.

Next we have created the ArticleCreateForm class and it inherits from Flask-WTF’s Form class, since we have title and body fields in our database, we need those fields in our form to migrate the data we get from the user and to store the values into the respective fields in database so we have created the title and body and have chosen the respective Fields based upon our Article Model’s structure. There’s a presence validator on each field to ensure it’s filled in and then we have used the strip filter function to strip the extra spaces before saving articles into the database to save our memory.

Similarly we will create SignupForm and SigninForm.

Then we create a new class named SignupForm containing a field for each piece of user information we wish to collect . There’s a presence validator on each field to ensure it’s filled in, and a format validator which requires that email addresses match the pattern: user@example.com.

Next, we write a simple constructor for the class that just calls the base class’ constructor.So we’ve added some presence and format validators to our form fields, but we need an additional validator that ensures an account does not already exist with the user’s email address. To do this we hook into Flask-WTF’s validation process.

Now inside of the validate() function, we first ensure the presence and format validators run by calling the base class’ validate() method; if the form is not filled in properly, validate() returns False.

Next we define the custom validator. We start by querying the database with the email that the user submitted (line 18). If you remember from our models.py file, the email address is converted to lowercase to ensure a match regardless of how it was typed in.

If a user record already exists with the submitted email, validation fails giving the following error message: “That email is already taken”.

The SigninForm class is similar to the SignupForm class. To sign a user in, we need to capture their email and password, so we create those two fields with presence and format validators. Then we define our custom validator inside the validate() function. This time the validator needs to make sure the user exists in the database and has the correct password. If a record does exist with the supplied information, we check to see if the password matches. If it does, the validation check passes, otherwise they get an error message.

So we have all the stubs in place now, so lets start working on views.

Views are Controllers

Create views.py inside the app package and then import our app object, Article model from our models.py and render_template function from flask.

Lets create our index action to display all the articles from the database.

We have called our Article model and then applied the all() method on it which we created inside our model as the @classmethod. Here we are getting all the articles and then returning them as a dict in the index template.
Lets create index.html inside template directory now, before that lets create the layout.html.

Now we can create index.html

Here we are iterating through the articles and serving them to the browser. Here we have used post.created_in_words which is the @property we defined inside our Article model, and we are using the property as a method inside our views.

Now we can register our view in the __init__ file.

So lets try to boot our server and see what we get, but wait we dont have app.run() method, then how we can run our server. We can run our server just like running the server in Django. This command provided to us by themanager function can boot our server.

Now, our server is running at port 5000.

3a

 

Lets do the signup and signin part now.
Import the SignupForm we created in forms.py

Our imports look like this now

Now, lets create the signup route, which can take both GET and POST request.

We have created an object of the SignupForm and then we check if the request is POST and our form validates means we are creating a new object of our Person class with the values we will get from the form when the user enters in the form, then we are creating the session for that object and then committing to the database. We are setting the session by the email address provided by the user and since email address will be unique for each person, session will also will be unique, after setting the session, we are redirecting to a special page called profile route. If the request is GET, then just render the signup page to the user. it looks complicated but its very easy if you look a few times at it.
Next we will create the Signup page now,
Create signup.html inside template directory

We are echoing out the errors if the form doesn’t validate, here the fields which we created inside our forms will be used to get the data from the users and validate it and save into the database.

Next profile.html

We are echoing out the email address which is in session at the moment with the pleasant welcome message.
Now, lets see our signup page in action, visit http://localhost:5000/signup
Errr, we got an exception

CSRF is the Cross-site request forgery and to avoid it, Flask-WTF provide an solution to this by adding the secret key to our app and then passing it as the hidden value to avoid the attack.
To include the secret key, lets create a config.py file in the main root of the project along with manage.py and include this lines there

Include this line inside __init__.py to activate the CSRF Protection for our app.

Now, run the server again and this time it worked.
3a

Now, if we signup, we get another exception, since the profile route we declared is not yet been created but the data gets stored into the database, you can verify it by seeing at the Person table.

3a

Lets create our profile route now
Import the ArticleCreateForm and then check if the email is in session i.e check if the user is logged in or not, if the user is logged in present him the form to create the article and then store the values from the form into database and redirect the user to the index page.

We are checking whether the user is logged in or not and then querying the user from the database and then presenting him the form to create the article and and if the request is post and form validates, add the article object into session and commit it into database, After successfully adding the article redirect the user to index page or render the create.html page again.
Now create create.html

Now again try the signup process and you will be redirected to the profile page with the form to create article.

3a

Next, if you add the article, you will see that it redirects to the index page with the latest article ordered first. But we dont have the link to logout the user or signin the user again
Lets create the signin and signout routes.

Import the SigninForm and then again repeat the same steps as we did for signing up the user. You will notice that, session.pop() method is used in signout which removes the user session and redirects to the index page.

Lets create SigninForm now.

With that our views are completed fully.
Completed views.py file looks like this

Lets change our layout.html file to include the navigation links.

3a

Final Directory structure looks like this
3a

Custom.css File Updated

 Conclusion

With that we have completed the Part 1 of the Flask-Blog, see you soon with the Part 2.

Feel free to ask your doubts.

 

 


  • Kenneth Kinyanjui

    Really awesome Post and I will be looking at it and trying flask out….

  • Yuanle

    Thanks!

  • Paul Aphivantrakul

    Hello. I am getting Error: No module named psychopg2 when I try to run my application at this point of the tutorial:

    …So lets try to boot our server and see what we get, but wait we dont have app.run() method, then how we can run our server. We can run our server just like running the server in Django. This command provided to us by themanager function can boot our server.

    1python manage.py runserver

    I have have checked my code and I don’t think it is a coding error. Do you know what may be causing this? Thank you.

    • ajkumar25

      REASON
      psycopg is not installed properly.

      SOLUTION
      if you are using Ubuntu, install it by this
      sudo apt-get install libpq-dev python-dev
      If Windows, download binary from http://www.lfd.uci.edu/~gohlke/pythonlibs/#psycopg and install it, then check if its installed correctly by going in the python shell and type this
      >> import psycopg2, if its installed correctly, no traceback will be returned.

      Cheers!!!

      • Paul Aphivantrakul

        Thank you for your help. I am running linux and ran sudo apt-get install libpq-dev python-dev as well as pip install psycopg2 and the error went away. Happy Thanksgiving.

  • Paul Aphivantrakul

    Hello again. I am getting an error –> TypeError: can’t compare datetime.datetime to unicode
    I found that the error disappears if I remove the following line from index.html:

    Created {{ post.created_in_words }} ago

    I double checked the code in created_in_words(self) method in models.py and it seems to be typo free. Do you know why this error is popping up? Thank you.

  • Daniel Correia

    I had some trouble with the second migration (adding the Article).
    Solved it by removing these lines in the migration script “op.drop_index(‘person_email_key’, ‘person’)” in the upgrade function and “op.create_index(‘person_email_key’, ‘person’, [u'email'], unique=True)” in the downgrade function.

    Does someone have a clue why this line was added to the migration script?

    • ajkumar25

      I dont think i understood you properly but the email id field is mandatory and it SHOULD be unique in the person models. you cannot remove email-id field from models, because sessions are built upon the email key.

      • Daniel Correia

        It is unique, I’ve set it in the first migration and didn’t change it later. I think the problem is related to Alembic. Anyway I followed Alembic’s suggestion “### commands auto generated by Alembic – please adjust! ###” :)

  • nek4life

    What’s the reason for using a classmethod and then hardcoding the class to Article in the Article model?

    Couldn’t you just do this instead?

    • ajkumar25

      Hmmmm Thank You for this :)

  • Miao Zhen

    I use sqlite instead, the database file was created, but when I sign up I got:

    (OperationalError) no such table: person u’SELECT person.id AS
    person_id, person.firstname AS person_firstname, person.lastname AS
    person_lastname, person.email AS person_email, person.pwdhash AS
    person_pwdhash nFROM person nWHERE person.email = ?n LIMIT ? OFFSET
    ?’ (u’demo@gmail.com’, 1, 0)

    and when I open the ‘/’ route I just got:

    OperationalError: (OperationalError) no such table: articles u’SELECT
    articles.id AS articles_id, articles.title AS articles_title,
    articles.body AS articles_body, articles.created AS articles_created
    nFROM articles ORDER BY articles.created DESC’ ()

  • kiran kumar

    When you create the second migration you say there are three tables. I see Person and article, what is the third one?