One place for hosting & domains

      Build a RESTful JSON API With Rails 5 – Part One


      Rails is popularly known for building web applications. Chances are if you’re reading this you’ve built a traditional server-rendered web application with Rails before. If not, I’d highly recommend going through the Getting Started with Rails page to familiarize yourself with the Rails framework before proceeding with this tutorial.

      As of version 5, Rails core now supports API only applications! In previous versions, we relied on an external gem: rails-api which has since been merged to core rails.

      API only applications are slimmed down compared to traditional Rails web applications. According to Rails 5 release notes, generating an API only application will:

      • Start the application with a limited set of middleware
      • Make the ApplicationController inherit from ActionController::API instead of ActionController::Base
      • Skip generation of view files

      This works to generate an API-centric framework excluding functionality that would otherwise be unused and unnecessary.

      In this three-part tutorial, we’ll build a todo list API where users can manage their to-do lists and todo items.

      Prerequisites

      Before we begin, make sure you have ruby version >=2.2.2 and rails version 5.

      $ ruby -v # ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin16]
      $ rails -v # Rails 5.0.1
      

      If your ruby version is not up to date, you can update it with a ruby version manager like rvm or rbenv.

      # when using rbenv
      $ rbenv install 2.3.1
      # set 2.3.1 as the global version
      $ rbenv global 2.3.1
      
      # when using rvm
      $ rvm install 2.3.1
      # set 2.3.1 as the global version
      $ rvm use 2.3.1
      

      If your rails version is not up to date, update to the latest version by running:

      $ gem update rails
      

      All good? Let’s get started!

      API Endpoints

      Our API will expose the following RESTful endpoints.

      Endpoint Functionality
      POST /signup Signup
      POST /auth/login Login
      GET /auth/logout Logout
      GET /todos List all todos
      POST /todos Create a new todo
      GET /todos/:id Get a todo
      PUT /todos/:id Update a todo
      DELETE /todos/:id Delete a todo and its items
      GET /todos/:id/items Get a todo item
      PUT /todos/:id/items Update a todo item
      DELETE /todos/:id/items Delete a todo item

      Part One will Cover:

      • Project setup
      • Todos API
      • TodoItems API

      Project Setup

      Generate a new project todos-api by running:

      $ rails new todos-api --api -T
      

      Note that we’re using the --api argument to tell Rails that we want an API application and -T to exclude Minitest the default
      testing framework. Don’t freak out, we’re going to write tests. We’ll be using RSpec instead to test our API. I find RSpec to be more expressive
      and easier to start with as compared to Minitest.

      Dependencies

      Let’s take a moment to review the gems that we’ll be using.

      • rspec-rails – Testing framework.
      • factorybotrails – A fixtures replacement with a more straightforward syntax. You’ll see.
      • shoulda_matchers – Provides RSpec with additional matchers.
      • database_cleaner – You guessed it! It literally cleans our test database to ensure
        a clean state in each test suite.
      • faker – A library for generating fake data. We’ll use this to generate test data.

      All good? Great! Let’s set them up. In your Gemfile:

      Add rspec-rails to both the :development and :test groups.

      # Gemfile
      group :development, :test do
        gem 'rspec-rails', '~> 3.5'
      end
      

      This is a handy shorthand to include a gem in multiple environments.

      Add factory_bot_rails, shoulda_matchers, faker and database_cleaner to the :test group.

      # Gemfile
      group :test do
        gem 'factory_bot_rails', '~> 4.0'
        gem 'shoulda-matchers', '~> 3.1'
        gem 'faker'
        gem 'database_cleaner'
      end
      

      Install the gems by running:

      $ bundle install
      

      Initialize the spec directory (where our tests will reside).

      $ rails generate rspec:install
      

      This adds the following files which are used for configuration:

      • .rspec
      • spec/spec_helper.rb
      • spec/rails_helper.rb

      Create a factories directory (factory bot uses this as the default directory). This is where we’ll define the model factories.

      $ mkdir spec/factories
      

      Configuration

      In spec/rails_helper.rb

      # require database cleaner at the top level
      require 'database_cleaner'
      
      # [...]
      # configure shoulda matchers to use rspec as the test framework and full matcher libraries for rails
      Shoulda::Matchers.configure do |config|
        config.integrate do |with|
          with.test_framework :rspec
          with.library :rails
        end
      end
      
      # [...]
      RSpec.configure do |config|
        # [...]
        # add `FactoryBot` methods
        config.include FactoryBot::Syntax::Methods
      
        # start by truncating all the tables but then use the faster transaction strategy the rest of the time.
        config.before(:suite) do
          DatabaseCleaner.clean_with(:truncation)
          DatabaseCleaner.strategy = :transaction
        end
      
        # start the transaction strategy as examples are run
        config.around(:each) do |example|
          DatabaseCleaner.cleaning do
            example.run
          end
        end
        # [...]
      end
      

      Phew! That was a rather long. Good thing is, it’s a smooth ride from here on out.


      Models

      Let’s start by generating the Todo model

      $ rails g model Todo title:string created_by:string
      

      Notice that we’ve included the model attributes in the model generation command. This way we don’t have to edit the migration file.
      The generator invokes active record and rspec to generate the migration, model, and spec respectively.

      # db/migrate/[timestamp]_create_todos.rb
      class CreateTodos < ActiveRecord::Migration[5.0]
        def change
          create_table :todos do |t|
            t.string :title
            t.string :created_by
      
            t.timestamps
          end
        end
      end
      

      And now the Item model

      $ rails g model Item name:string done:boolean todo:references
      

      By adding todo:references we’re telling the generator to set up an association with the Todo model.
      This will do the following:

      • Add a foreign key column todo_id to the items table
      • Setup a belongs_to association in the Item model
      # db/migrate/[timestamp]_create_items.rb
      class CreateItems < ActiveRecord::Migration[5.0]
        def change
          create_table :items do |t|
            t.string :name
            t.boolean :done
            t.references :todo, foreign_key: true
      
            t.timestamps
          end
        end
      end
      

      Looks good? Let’s run the migrations.

      $ rails db:migrate
      

      We’re Test Driven, let’s write the model specs first.

      # spec/models/todo_spec.rb
      require 'rails_helper'
      
      # Test suite for the Todo model
      RSpec.describe Todo, type: :model do
        # Association test
        # ensure Todo model has a 1:m relationship with the Item model
        it { should have_many(:items).dependent(:destroy) }
        # Validation tests
        # ensure columns title and created_by are present before saving
        it { should validate_presence_of(:title) }
        it { should validate_presence_of(:created_by) }
      end
      

      RSpec has a very expressive DSL (Domain Specific Language). You can almost read the tests like a paragraph.
      Remember our shoulda matchers gem? It provides RSpec with the nifty association and validation matchers above.

      # spec/models/item_spec.rb
      require 'rails_helper'
      
      # Test suite for the Item model
      RSpec.describe Item, type: :model do
        # Association test
        # ensure an item record belongs to a single todo record
        it { should belong_to(:todo) }
        # Validation test
        # ensure column name is present before saving
        it { should validate_presence_of(:name) }
      end
      

      Let’s execute the specs by running:

      $ bundle exec rspec
      

      And to no surprise, we have only one test passing and four failures. Let’s go ahead and fix the failures.

      # app/models/todo.rb
      class Todo < ApplicationRecord
        # model association
        has_many :items, dependent: :destroy
      
        # validations
        validates_presence_of :title, :created_by
      end
      
      # app/models/item.rb
      class Item < ApplicationRecord
        # model association
        belongs_to :todo
      
        # validation
        validates_presence_of :name
      end
      

      At this point run the tests again and…

      voila! All green.


      Controllers

      Now that our models are all setup, let’s generate the controllers.

      $ rails g controller Todos
      $ rails g controller Items
      

      You guessed it! Tests first… with a slight twist. Generating controllers by default generates controller specs.
      However, we won’t be writing any controller specs. We’re going to write request specs instead.

      Request specs are designed to drive behavior through the full stack, including routing. This means they can hit the applications’
      HTTP endpoints as opposed to controller specs which call methods directly. Since we’re building an API application, this is exactly the kind of behavior we want from our tests.

      According to RSpec, the official recommendation of the Rails team and the RSpec core team is to write request specs instead.

      Add a requests folder to the spec directory with the corresponding spec files.

      $ mkdir spec/requests && touch spec/requests/{todos_spec.rb,items_spec.rb} 
      

      Before we define the request specs, Let’s add the model factories which will provide the test data.

      Add the factory files:

      $ touch spec/factories/{todos.rb,items.rb}
      

      Define the factories.

      # spec/factories/todos.rb
      FactoryBot.define do
        factory :todo do
          title { Faker::Lorem.word }
          created_by { Faker::Number.number(10) }
        end
      end
      

      By wrapping faker methods in a block, we ensure that faker generates dynamic data every time the factory is invoked.
      This way, we always have unique data.

      # spec/factories/items.rb
      FactoryBot.define do
        factory :item do
          name { Faker::StarWars.character }
          done false
          todo_id nil
        end
      end
      

      Todo API

      # spec/requests/todos_spec.rb
      require 'rails_helper'
      
      RSpec.describe 'Todos API', type: :request do
        # initialize test data 
        let!(:todos) { create_list(:todo, 10) }
        let(:todo_id) { todos.first.id }
      
        # Test suite for GET /todos
        describe 'GET /todos' do
          # make HTTP get request before each example
          before { get '/todos' }
      
          it 'returns todos' do
            # Note `json` is a custom helper to parse JSON responses
            expect(json).not_to be_empty
            expect(json.size).to eq(10)
          end
      
          it 'returns status code 200' do
            expect(response).to have_http_status(200)
          end
        end
      
        # Test suite for GET /todos/:id
        describe 'GET /todos/:id' do
          before { get "/todos/#{todo_id}" }
      
          context 'when the record exists' do
            it 'returns the todo' do
              expect(json).not_to be_empty
              expect(json['id']).to eq(todo_id)
            end
      
            it 'returns status code 200' do
              expect(response).to have_http_status(200)
            end
          end
      
          context 'when the record does not exist' do
            let(:todo_id) { 100 }
      
            it 'returns status code 404' do
              expect(response).to have_http_status(404)
            end
      
            it 'returns a not found message' do
              expect(response.body).to match(/Couldn't find Todo/)
            end
          end
        end
      
        # Test suite for POST /todos
        describe 'POST /todos' do
          # valid payload
          let(:valid_attributes) { { title: 'Learn Elm', created_by: '1' } }
      
          context 'when the request is valid' do
            before { post '/todos', params: valid_attributes }
      
            it 'creates a todo' do
              expect(json['title']).to eq('Learn Elm')
            end
      
            it 'returns status code 201' do
              expect(response).to have_http_status(201)
            end
          end
      
          context 'when the request is invalid' do
            before { post '/todos', params: { title: 'Foobar' } }
      
            it 'returns status code 422' do
              expect(response).to have_http_status(422)
            end
      
            it 'returns a validation failure message' do
              expect(response.body)
                .to match(/Validation failed: Created by can't be blank/)
            end
          end
        end
      
        # Test suite for PUT /todos/:id
        describe 'PUT /todos/:id' do
          let(:valid_attributes) { { title: 'Shopping' } }
      
          context 'when the record exists' do
            before { put "/todos/#{todo_id}", params: valid_attributes }
      
            it 'updates the record' do
              expect(response.body).to be_empty
            end
      
            it 'returns status code 204' do
              expect(response).to have_http_status(204)
            end
          end
        end
      
        # Test suite for DELETE /todos/:id
        describe 'DELETE /todos/:id' do
          before { delete "/todos/#{todo_id}" }
      
          it 'returns status code 204' do
            expect(response).to have_http_status(204)
          end
        end
      end
      

      We start by populating the database with a list of 10 todo records (thanks to factory bot).
      We also have a custom helper method json which parses the JSON response to a Ruby Hash which is easier to work with in our tests.
      Let’s define it in spec/support/request_spec_helper.

      Add the directory and file:

      $ mkdir spec/support && touch spec/support/request_spec_helper.rb
      
      # spec/support/request_spec_helper
      module RequestSpecHelper
        # Parse JSON response to ruby hash
        def json
          JSON.parse(response.body)
        end
      end
      

      The support directory is not autoloaded by default. To enable this, open the rails helper and comment out the support directory auto-loading and then
      include it as shared module for all request specs in the RSpec configuration block.

      # spec/rails_helper.rb
      # [...]
      Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
      # [...]
      RSpec.configuration do |config|
        # [...]
        config.include RequestSpecHelper, type: :request
        # [...]
      end
      

      Run the tests.

      We get failing routing errors. This is because we haven’t defined the routes yet. Go ahead and define them in config/routes.rb.

      # config/routes.rb
      Rails.application.routes.draw do
        resources :todos do
          resources :items
        end
      end
      

      In our route definition, we’re creating todo resource with a nested items resource. This enforces the 1:m (one to many) associations at the routing level.
      To view the routes, you can run:

      $ rails routes
      

      When we run the tests we see that the routing error is gone. As expected we have controller failures. Let’s go ahead and define the controller methods.

      # app/controllers/todos_controller.rb
      class TodosController < ApplicationController
        before_action :set_todo, only: [:show, :update, :destroy]
      
        # GET /todos
        def index
          @todos = Todo.all
          json_response(@todos)
        end
      
        # POST /todos
        def create
          @todo = Todo.create!(todo_params)
          json_response(@todo, :created)
        end
      
        # GET /todos/:id
        def show
          json_response(@todo)
        end
      
        # PUT /todos/:id
        def update
          @todo.update(todo_params)
          head :no_content
        end
      
        # DELETE /todos/:id
        def destroy
          @todo.destroy
          head :no_content
        end
      
        private
      
        def todo_params
          # whitelist params
          params.permit(:title, :created_by)
        end
      
        def set_todo
          @todo = Todo.find(params[:id])
        end
      end
      

      More helpers. Yay! This time we have:

      • json_response which does… yes, responds with JSON and an HTTP status code (200 by default).
        We can define this method in concerns folder.
      # app/controllers/concerns/response.rb
      module Response
        def json_response(object, status = :ok)
          render json: object, status: status
        end
      end
      
      • set_todo – callback method to find a todo by id. In the case where the record does not exist, ActiveRecord
        will throw an exception ActiveRecord::RecordNotFound. We’ll rescue from this exception and return a 404 message.
      # app/controllers/concerns/exception_handler.rb
      module ExceptionHandler
        # provides the more graceful `included` method
        extend ActiveSupport::Concern
      
        included do
          rescue_from ActiveRecord::RecordNotFound do |e|
            json_response({ message: e.message }, :not_found)
          end
      
          rescue_from ActiveRecord::RecordInvalid do |e|
            json_response({ message: e.message }, :unprocessable_entity)
          end
        end
      end
      

      In our create method in the TodosController, note that we’re using create! instead of create. This way, the model will raise
      an exception ActiveRecord::RecordInvalid. This way, we can avoid deep nested if statements in the controller. Thus, we rescue from this exception
      in the ExceptionHandler module.

      However, our controller classes don’t know about these helpers yet. Let’s fix that by including these modules in the
      application controller.

      # app/controllers/application_controller.rb
      class ApplicationController < ActionController::API
        include Response
        include ExceptionHandler
      end
      

      Run the tests and everything’s all green!

      Let’s fire up the server for some good old manual testing.

      $ rails s
      

      Now let’s go ahead and make requests to the API. I’ll be using httpie as my HTTP client.

      # GET /todos
      $ http :3000/todos
      # POST /todos
      $ http POST :3000/todos title=Mozart created_by=1
      # PUT /todos/:id
      $ http PUT :3000/todos/1 title=Beethoven
      # DELETE /todos/:id
      $ http DELETE :3000/todos/1
      

      You should see similar output.


      TodoItems API

      # spec/requests/items_spec.rb
      require 'rails_helper'
      
      RSpec.describe 'Items API' do
        # Initialize the test data
        let!(:todo) { create(:todo) }
        let!(:items) { create_list(:item, 20, todo_id: todo.id) }
        let(:todo_id) { todo.id }
        let(:id) { items.first.id }
      
        # Test suite for GET /todos/:todo_id/items
        describe 'GET /todos/:todo_id/items' do
          before { get "/todos/#{todo_id}/items" }
      
          context 'when todo exists' do
            it 'returns status code 200' do
              expect(response).to have_http_status(200)
            end
      
            it 'returns all todo items' do
              expect(json.size).to eq(20)
            end
          end
      
          context 'when todo does not exist' do
            let(:todo_id) { 0 }
      
            it 'returns status code 404' do
              expect(response).to have_http_status(404)
            end
      
            it 'returns a not found message' do
              expect(response.body).to match(/Couldn't find Todo/)
            end
          end
        end
      
        # Test suite for GET /todos/:todo_id/items/:id
        describe 'GET /todos/:todo_id/items/:id' do
          before { get "/todos/#{todo_id}/items/#{id}" }
      
          context 'when todo item exists' do
            it 'returns status code 200' do
              expect(response).to have_http_status(200)
            end
      
            it 'returns the item' do
              expect(json['id']).to eq(id)
            end
          end
      
          context 'when todo item does not exist' do
            let(:id) { 0 }
      
            it 'returns status code 404' do
              expect(response).to have_http_status(404)
            end
      
            it 'returns a not found message' do
              expect(response.body).to match(/Couldn't find Item/)
            end
          end
        end
      
        # Test suite for PUT /todos/:todo_id/items
        describe 'POST /todos/:todo_id/items' do
          let(:valid_attributes) { { name: 'Visit Narnia', done: false } }
      
          context 'when request attributes are valid' do
            before { post "/todos/#{todo_id}/items", params: valid_attributes }
      
            it 'returns status code 201' do
              expect(response).to have_http_status(201)
            end
          end
      
          context 'when an invalid request' do
            before { post "/todos/#{todo_id}/items", params: {} }
      
            it 'returns status code 422' do
              expect(response).to have_http_status(422)
            end
      
            it 'returns a failure message' do
              expect(response.body).to match(/Validation failed: Name can't be blank/)
            end
          end
        end
      
        # Test suite for PUT /todos/:todo_id/items/:id
        describe 'PUT /todos/:todo_id/items/:id' do
          let(:valid_attributes) { { name: 'Mozart' } }
      
          before { put "/todos/#{todo_id}/items/#{id}", params: valid_attributes }
      
          context 'when item exists' do
            it 'returns status code 204' do
              expect(response).to have_http_status(204)
            end
      
            it 'updates the item' do
              updated_item = Item.find(id)
              expect(updated_item.name).to match(/Mozart/)
            end
          end
      
          context 'when the item does not exist' do
            let(:id) { 0 }
      
            it 'returns status code 404' do
              expect(response).to have_http_status(404)
            end
      
            it 'returns a not found message' do
              expect(response.body).to match(/Couldn't find Item/)
            end
          end
        end
      
        # Test suite for DELETE /todos/:id
        describe 'DELETE /todos/:id' do
          before { delete "/todos/#{todo_id}/items/#{id}" }
      
          it 'returns status code 204' do
            expect(response).to have_http_status(204)
          end
        end
      end
      

      As expected, running the tests at this point should output failing todo item tests. Let’s define the todo items controller.

      # app/controllers/items_controller.rb
      class ItemsController < ApplicationController
        before_action :set_todo
        before_action :set_todo_item, only: [:show, :update, :destroy]
      
        # GET /todos/:todo_id/items
        def index
          json_response(@todo.items)
        end
      
        # GET /todos/:todo_id/items/:id
        def show
          json_response(@item)
        end
      
        # POST /todos/:todo_id/items
        def create
          @todo.items.create!(item_params)
          json_response(@todo, :created)
        end
      
        # PUT /todos/:todo_id/items/:id
        def update
          @item.update(item_params)
          head :no_content
        end
      
        # DELETE /todos/:todo_id/items/:id
        def destroy
          @item.destroy
          head :no_content
        end
      
        private
      
        def item_params
          params.permit(:name, :done)
        end
      
        def set_todo
          @todo = Todo.find(params[:todo_id])
        end
      
        def set_todo_item
          @item = @todo.items.find_by!(id: params[:id]) if @todo
        end
      end
      

      Run the tests.

      Run some manual tests for the todo items API:

      # GET /todos/:todo_id/items
      $ http :3000/todos/2/items
      # POST /todos/:todo_id/items
      $ http POST :3000/todos/2/items name="Listen to 5th Symphony" done=false
      # PUT /todos/:todo_id/items/:id
      $ http PUT :3000/todos/2/items/1 done=true
      # DELETE /todos/:todo_id/items/1
      $ http DELETE :3000/todos/2/items/1
      


      Conclusion

      That’s it for part one! At this point you should have learned how to:

      • Generate an API application with Rails 5
      • Setup RSpec testing framework with Factory Bot, Database Cleaner, Shoulda Matchers and Faker.
      • Build models and controllers with TDD (Test Driven Development).
      • Make HTTP requests to an API with httpie.

      In the next part, we’ll cover authentication with JWT, pagination, and API versioning. Hope to see you there. Cheers!



      Source link

      Build a CRUD Web App With Python and Flask – Part One


      In this three-part tutorial, we’ll build a CRUD (Create, Read, Update, Delete) employee management web app using Flask, a microframework for Python. I’ve named the app Project Dream Team, and it will have the following features:

      1. Users will be able to register and login as employees
      2. The administrator will be able to create, update, and delete departments and roles
      3. The administrator will be able to assign employees to a department and assign them roles
      4. The administrator will be able to view all employees and their details

      Part One will cover:

      1. Database setup
      2. Models
      3. Migration
      4. Homepage
      5. Authentication

      Ready? Here we go!

      Prerequisites

      This tutorial builds on my introductory tutorial, Getting Started With Flask, picking up where it left off. It assumes you have, to begin with, the following dependencies installed:

      1. Python 2.7
      2. Flask
      3. virtualenv (and, optionally, virtualenvwrapper)

      You should have a virtual environment set up and activated. You should also have the following file and directory structure:

      ├── dream-team
             ├── app
             │   ├── __init__.py
             │   ├── templates
             │   ├── models.py
             │   └── views.py
             ├── config.py
             ├── requirements.txt
             └── run.py
      

      This project structure groups the similar components of the application together. The dream-team directory houses all the project files. The app directory is the application package, and houses different but interlinked modules of the application. All templates are stored in the templates directory, all models are in the models.py file, and all routes are in the views.py file. The run.py file is the application’s entry point, the config.py file contains the application configurations, and the requirements.txt file contains the software dependencies for the application.

      If you don’t have these set up, please visit the introductory tutorial and catch up!

      Database Setup

      Flask has support for several relational database management systems, including SQLite, MySQL, and PostgreSQL. For this tutorial, we will be using MySQL. It’s popular and therefore has a lot of support, in addition to being scalable, secure, and rich in features.

      We will install the following (remember to activate your virtual environment):

      1. Flask-SQLAlchemy: This will allow us to use SQLAlchemy, a useful tool for SQL use with Python. SQLAlchemy is an Object Relational Mapper (ORM), which means that it connects the objects of an application to tables in a relational database management system. These objects can be stored in the database and accessed without the need to write raw SQL. This is convenient because it simplifies queries that may have been complex if written in raw SQL. Additionally, it reduces the risk of SQL injection attacks since we are not dealing with the input of raw SQL.

      2. MySQL-Python: This is a Python interface to MySQL. It will help us connect the MySQL database to the app.

      $ pip install flask-sqlalchemy mysql-python
      

      We’ll then create the MySQL database. Ensure you have MySQL installed and running, and then log in as the root user:

      $ mysql -u root
      
      mysql> CREATE USER 'dt_admin'@'localhost' IDENTIFIED BY 'dt2016';
      Query OK, 0 rows affected (0.00 sec)
      
      mysql> CREATE DATABASE dreamteam_db;
      Query OK, 1 row affected (0.00 sec)
      
      mysql> GRANT ALL PRIVILEGES ON dreamteam_db . * TO 'dt_admin'@'localhost';
      Query OK, 0 rows affected (0.00 sec)
      

      We have now created a new user dt_admin with the password dt2016, created a new database dreamteam_db, and granted the new user all database privileges.

      Next, let’s edit the config.py. Remove any exisiting code and add the following:

      # config.py
      
      class Config(object):
          """
          Common configurations
          """
      
          # Put any configurations here that are common across all environments
      
      
      class DevelopmentConfig(Config):
          """
          Development configurations
          """
      
          DEBUG = True
          SQLALCHEMY_ECHO = True
      
      
      class ProductionConfig(Config):
          """
          Production configurations
          """
      
          DEBUG = False
      
      app_config = {
          'development': DevelopmentConfig,
          'production': ProductionConfig
      }
      

      It is good practice to specify configurations for different environments. In the file above, we have specifed configurations for development, which we will use while building the app and running it locally, as well as production, which we will use when the app is deployed.

      Some useful configuration variables are:

      1. TESTING: setting this to True activates the testing mode of Flask extensions. This allows us to use testing properties that could for instance have an increased runtime cost, such as unittest helpers. It should be set to True in the configurations for testing. It defaults to False.
      2. DEBUG: setting this to True activates the debug mode on the app. This allows us to use the Flask debugger in case of an unhandled exception, and also automatically reloads the application when it is updated. It should however always be set to False in production. It defaults to False.
      3. SQLALCHEMY_ECHO: setting this to True helps us with debugging by allowing SQLAlchemy to log errors.

      You can find more Flask configuration variables here and SQLAlchemy configuration variables here.

      Next, create an instance directory in the dream-team directory, and then create a config.py file inside it. We will put configuration variables here that will not be pushed to version control due to their sensitive nature. In this case, we put the secret key as well as the database URI which contains the database user password.

      # instance/config.py
      
      SECRET_KEY = 'p9Bv<3Eid9%$i01'
      SQLALCHEMY_DATABASE_URI = 'mysql://dt_admin:dt2016@localhost/dreamteam_db'
      

      Now, let’s edit the app/__init__.py file. Remove any existing code and add the following:

      # app/__init__.py
      
      # third-party imports
      from flask import Flask
      from flask_sqlalchemy import SQLAlchemy
      
      # local imports
      from config import app_config
      
      # db variable initialization
      db = SQLAlchemy()
      
      
      def create_app(config_name):
          app = Flask(__name__, instance_relative_config=True)
          app.config.from_object(app_config[config_name])
          app.config.from_pyfile('config.py')
          db.init_app(app)
      
          return app
      

      We’ve created a function, create_app that, given a configuration name, loads the correct configuration from the config.py file, as well as the configurations from the instance/config.py file. We have also created a db object which we will use to interact with the database.

      Next, let’s edit the run.py file:

      # run.py
      
      import os
      
      from app import create_app
      
      config_name = os.getenv('FLASK_CONFIG')
      app = create_app(config_name)
      
      
      if __name__ == '__main__':
          app.run()
      

      We create the app by running the create_app function and passing in the configuration name. We get this from the OS environment variable FLASK_CONFIG. Because we are in development, we should set the environment variable to development.

      Let’s run the app to ensure everything is working as expected. First, delete the app/views.py file as well as the app/templates directory as we will not be needing them going forward. Next, add a temporary route to the app/__init__.py file as follows:

      # app/__init__.py
      
      # existing code remains
      
      
      def create_app(config_name):
          # existing code remains
      
          # temporary route
          @app.route('/')
          def hello_world():
              return 'Hello, World!'
      
          return app
      

      Make sure you set the FLASK_CONFIG and FLASK_APP environment variables before running the app:

      $ export FLASK_CONFIG=development
      $ export FLASK_APP=run.py
      $ flask run
       * Serving Flask app "run"
       * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
      

      We can see the “Hello, World” string we set in the route. The app is working well so far.

      Models

      Now to work on the models. Remember that a model is a representation of a database table in code. We’ll need three models: Employee, Department, and Role.

      But first, let’s install Flask-Login, which will help us with user management and handle logging in, logging out, and user sessions. The Employee model will inherit from Flask-Login’s UserMixin class which will make it easier for us to make use of its properties and methods.

      $ pip install flask-login
      

      To use Flask-Login, we need to create a LoginManager object and initialize it in the app/__init__.py file. First, remove the route we added earlier, and then add the following:

      # app/__init__.py
      
      # after existing third-party imports
      from flask_login import LoginManager
      
      # after the db variable initialization
      login_manager = LoginManager()
      
      
      def create_app(config_name):
          # existing code remains
      
          login_manager.init_app(app)
          login_manager.login_message = "You must be logged in to access this page."
          login_manager.login_view = "auth.login"
      
          return app
      

      In addition to initializing the LoginManager object, we’ve also added a login_view and login_message to it. This way, if a user tries to access a page that they are not authorized to, it will redirect to the specified view and display the specified message. We haven’t created the auth.login view yet, but we will when we get to authentication.

      Now add the following code to the app/models.py file:

      # app/models.py
      
      from flask_login import UserMixin
      from werkzeug.security import generate_password_hash, check_password_hash
      
      from app import db, login_manager
      
      
      class Employee(UserMixin, db.Model):
          """
          Create an Employee table
          """
      
          # Ensures table will be named in plural and not in singular
          # as is the name of the model
          __tablename__ = 'employees'
      
          id = db.Column(db.Integer, primary_key=True)
          email = db.Column(db.String(60), index=True, unique=True)
          username = db.Column(db.String(60), index=True, unique=True)
          first_name = db.Column(db.String(60), index=True)
          last_name = db.Column(db.String(60), index=True)
          password_hash = db.Column(db.String(128))
          department_id = db.Column(db.Integer, db.ForeignKey('departments.id'))
          role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
          is_admin = db.Column(db.Boolean, default=False)
      
          @property
          def password(self):
              """
              Prevent pasword from being accessed
              """
              raise AttributeError('password is not a readable attribute.')
      
          @password.setter
          def password(self, password):
              """
              Set password to a hashed password
              """
              self.password_hash = generate_password_hash(password)
      
          def verify_password(self, password):
              """
              Check if hashed password matches actual password
              """
              return check_password_hash(self.password_hash, password)
      
          def __repr__(self):
              return '<Employee: {}>'.format(self.username)
      
      
      # Set up user_loader
      @login_manager.user_loader
      def load_user(user_id):
          return Employee.query.get(int(user_id))
      
      
      class Department(db.Model):
          """
          Create a Department table
          """
      
          __tablename__ = 'departments'
      
          id = db.Column(db.Integer, primary_key=True)
          name = db.Column(db.String(60), unique=True)
          description = db.Column(db.String(200))
          employees = db.relationship('Employee', backref="department",
                                      lazy='dynamic')
      
          def __repr__(self):
              return '<Department: {}>'.format(self.name)
      
      
      class Role(db.Model):
          """
          Create a Role table
          """
      
          __tablename__ = 'roles'
      
          id = db.Column(db.Integer, primary_key=True)
          name = db.Column(db.String(60), unique=True)
          description = db.Column(db.String(200))
          employees = db.relationship('Employee', backref="role",
                                      lazy='dynamic')
      
          def __repr__(self):
              return '<Role: {}>'.format(self.name)
      

      In the Employee model, we make use of some of Werkzeug’s handy security helper methods, generate_password_hash, which allows us to hash passwords, and check_password_hash, which allows us ensure the hashed password matches the password. To enhance security, we have a password method which ensures that the password can never be accessed; instead an error will be raised. We also have two foreign key fields, department_id and role_id, which refer to the ID’s of the department and role assigned to the employee.

      Note that we have an is_admin field which is set to False by default. We will override this when creating the admin user. Just after the Employee model, we have a user_loader callback, which Flask-Login uses to reload the user object from the user ID stored in the session.

      The Department and Role models are quite similar. Both have name and description fields. Additionally, both have a one-to-many relationship with the Employee model (one department or role can have many employees). We define this in both models using the employees field. backref allows us to create a new property on the Employee model such that we can use employee.department or employee.role to get the department or role assigned to that employee. lazy defines how the data will be loaded from the database; in this case it will be loaded dynamically, which is ideal for managing large collections.

      Migration

      Migrations allow us to manage changes we make to the models, and propagate these changes in the database. For example, if later on we make a change to a field in one of the models, all we will need to do is create and apply a migration, and the database will reflect the change.

      We’ll begin by installing Flask-Migrate, which will handle the database migrations using Alembic, a lightweight database migration tool. Alembic emits ALTER statements to a database thus implememting changes made to the models. It also auto-generates minimalistic migration scripts, which may be complex to write.

      $ pip install flask-migrate
      

      We’ll need to edit the app/__init__.py file:

      # app/__init__.py
      
      # after existing third-party imports
      from flask_migrate import Migrate
      
      # existing code remains
      
      
      def create_app(config_name):
          # existing code remains
      
          migrate = Migrate(app, db)
      
          from app import models
      
          return app
      

      We have created a migrate object which will allow us to run migrations using Flask-Migrate. We have also imported the models from the app package. Next, we’ll run the following command to create a migration repository:

      $ flask db init
      

      This creates a migrations directory in the dream-team directory:

      └── migrations
          ├── README
          ├── alembic.ini
          ├── env.py
          ├── script.py.mako
          └── versions
      

      Next, we will create the first migration:

      $ flask db migrate
      

      Finally, we’ll apply the migration:

      $ flask db upgrade
      

      We’ve sucessfully created tables based on the models we wrote! Let’s check the MySQL database to confirm this:

      $ mysql -u root
      
      mysql> use dreamteam_db;
      
      mysql> show tables;
      +------------------------+
      | Tables_in_dreamteam_db |
      +------------------------+
      | alembic_version        |
      | departments            |
      | employees              |
      | roles                  |
      +------------------------+
      4 rows in set (0.00 sec)
      

      Blueprints

      Blueprints are great for organising a flask app into components, each with its own views and forms. I find that blueprints make for a cleaner and more organised project structure because each blueprint is a distinct component that addresses a specific functionality of the app. Each blueprint can even have its own cutsom URL prefix or subdomain. Blueprints are particularly convenient for large applications.

      We’re going to have three blueprints in this app:

      1. Home – this will have the homepage and dashboard views
      2. Admin – this will have all administrator (department and role) forms and views
      3. Auth – this will have all authentication (registration and login) forms and views

      Create the relevant files and directories so that your directory structure resembles this:

      └── dream-team
          ├── app
          │   ├── __init__.py
          │   ├── admin
          │   │   ├── __init__.py
          │   │   ├── forms.py
          │   │   └── views.py
          │   ├── auth
          │   │   ├── __init__.py
          │   │   ├── forms.py
          │   │   └── views.py
          │   ├── home
          │   │   ├── __init__.py
          │   │   └── views.py
          │   ├── models.py
          │   ├── static
          │   └── templates
          ├── config.py
          ├── instance
          │   └── config.py
          ├── migrations
          │   ├── README
          │   ├── alembic.ini
          │   ├── env.py
          │   ├── script.py.mako
          │   └── versions
          │       └── a1a1d8b30202_.py
          ├── requirements.txt
          └── run.py
      

      I chose not to have static and templates directories for each blueprint, because all the application templates will inherit from the same base template and use the same CSS file. Instead, the templates directory will have sub-directories for each blueprint so that blueprint templates can be grouped together.

      In each blueprint’s __init__.py file, we need to create a Blueprint object and initialize it with a name. We also need to import the views.

      # app/admin/__init__.py
      
      from flask import Blueprint
      
      admin = Blueprint('admin', __name__)
      
      from . import views
      
      # app/auth/__init__.py
      
      from flask import Blueprint
      
      auth = Blueprint('auth', __name__)
      
      from . import views
      
      # app/home/__init__.py
      
      from flask import Blueprint
      
      home = Blueprint('home', __name__)
      
      from . import views
      

      Then, we can register the blueprints on the app in the app/__init__.py file, like so:

      # app/__init__.py
      
      # existing code remains
      
      
      def create_app(config_name):
          # existing code remains
      
          from app import models
      
          from .admin import admin as admin_blueprint
          app.register_blueprint(admin_blueprint, url_prefix='/admin')
      
          from .auth import auth as auth_blueprint
          app.register_blueprint(auth_blueprint)
      
          from .home import home as home_blueprint
          app.register_blueprint(home_blueprint)
      
          return app
      

      We have imported each blueprint object and registered it. For the admin blueprint, we have added a url prefix, /admin. This means that all the views for this blueprint will be accessed in the browser with the url prefix admin.

      Home Blueprint

      Time to work on fleshing out the blueprints! We’ll start with the home blueprint, which will have the homepage as well as the dashboard.

      # app/home/views.py
      
      from flask import render_template
      from flask_login import login_required
      
      from . import home
      
      
      @home.route('/')
      def homepage():
          """
          Render the homepage template on the / route
          """
          return render_template('home/index.html', title="Welcome")
      
      
      @home.route('/dashboard')
      @login_required
      def dashboard():
          """
          Render the dashboard template on the /dashboard route
          """
          return render_template('home/dashboard.html', title="Dashboard")
      

      Each view function has a decorator, home.route, which has a URL route as a parameter (remember that home is the name of the blueprint as specified in the app/home/__init__.py file). Each view handles requests to the specified URL.

      The homepage view renders the home template, while the dashboard view renders the dashboard template. Note that the dashboard view has a login_required decorator, meaning that users must be logged in to access it.

      Now to work on the base template, which all other templates will inherit from. Create a base.html file in the app/templates directory and add the following code:

      <!-- app/templates/base.html -->
      
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <title>{{ title }} | Project Dream Team</title>
          <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
          <link href="https://www.digitalocean.com/community/tutorials/{{ url_for("static', filename="css/style.css") }}" rel="stylesheet">
          <link rel="shortcut icon" href="https://www.digitalocean.com/community/tutorials/{{ url_for("static', filename="img/favicon.ico") }}">
      </head>
      <body>
          <nav class="navbar navbar-default navbar-fixed-top topnav" role="navigation">
              <div class="container topnav">
                <div class="navbar-header">
                    <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
                        <span class="sr-only">Toggle navigation</span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                    </button>
                    <a class="navbar-brand topnav" href="https://www.digitalocean.com/community/tutorials/{{ url_for("home.homepage') }}">Project Dream Team</a>
                </div>
                <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                    <ul class="nav navbar-nav navbar-right">
                        <li><a href="https://www.digitalocean.com/community/tutorials/{{ url_for("home.homepage') }}">Home</a></li>
                        <li><a href="#">Register</a></li>
                        <li><a href="#">Login</a></li>
                    </ul>
                </div>
              </div>
          </nav>
          <div class="wrapper">
            {% block body %}
            {% endblock %}
            <div class="push"></div>
          </div>
          <footer>
              <div class="container">
                  <div class="row">
                      <div class="col-lg-12">
                          <ul class="list-inline">
                              <li><a href="https://www.digitalocean.com/community/tutorials/{{ url_for("home.homepage') }}">Home</a></li>
                              <li class="footer-menu-divider">⋅</li>
                              <li><a href="#">Register</a></li>
                              <li class="footer-menu-divider">⋅</li>
                              <li><a href="#">Login</a></li>
                          </ul>
                          <p class="copyright text-muted small">Copyright © 2016. All Rights Reserved</p>
                      </div>
                  </div>
              </div>
          </footer>
      </body>
      </html>
      

      Note that we use # for the Register and Login links. We will update this when we are working on the auth blueprint.

      Next, create a home directory inside the app/templates directory. The homepage template, index.html, will go inside it:

      <!-- app/templates/home/index.html -->
      
      {% extends "base.html" %}
      {% block title %}Home{% endblock %}
      {% block body %}
      <div class="intro-header">
          <div class="container">
              <div class="row">
                  <div class="col-lg-12">
                      <div class="intro-message">
                          <h1>Project Dream Team</h1>
                          <h3>The best company in the world!</h3>
                          <hr class="intro-divider">
                          </ul>
                      </div>
                  </div>
              </div>
          </div>
      </div>
      {% endblock %}
      

      Inside the static directory, add css and img directories. Add the following CSS file, style.css, to your static/css directory (note that you will need a background image, intro-bg.jpg, as well as a favicon in your static/img directory):

      /* app/static/css/style.css */
      
      body, html {
          width: 100%;
          height: 100%;
      }
      
      body, h1, h2, h3 {
          font-family: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif;
          font-weight: 700;
      }
      
      a, .navbar-default .navbar-brand, .navbar-default .navbar-nav>li>a {
        color: #aec251;
      }
      
      a:hover, .navbar-default .navbar-brand:hover, .navbar-default .navbar-nav>li>a:hover {
        color: #687430;
      }
      
      footer {
          padding: 50px 0;
          background-color: #f8f8f8;
      }
      
      p.copyright {
          margin: 15px 0 0;
      }
      
      .alert-info {
          width: 50%;
          margin: auto;
          color: #687430;
          background-color: #e6ecca;
          border-color: #aec251;
      }
      
      .btn-default {
          border-color: #aec251;
          color: #aec251;
      }
      
      .btn-default:hover {
          background-color: #aec251;
      }
      
      .center {
          margin: auto;
          width: 50%;
          padding: 10px;
      }
      
      .content-section {
          padding: 50px 0;
          border-top: 1px solid #e7e7e7;
      }
      
      .footer, .push {
        clear: both;
        height: 4em;
      }
      
      .intro-divider {
          width: 400px;
          border-top: 1px solid #f8f8f8;
          border-bottom: 1px solid rgba(0,0,0,0.2);
      }
      
      .intro-header {
          padding-top: 50px;
          padding-bottom: 50px;
          text-align: center;
          color: #f8f8f8;
          background: url(../img/intro-bg.jpg) no-repeat center center;
          background-size: cover;
          height: 100%;
      }
      
      .intro-message {
          position: relative;
          padding-top: 20%;
          padding-bottom: 20%;
      }
      
      .intro-message > h1 {
          margin: 0;
          text-shadow: 2px 2px 3px rgba(0,0,0,0.6);
          font-size: 5em;
      }
      
      .intro-message > h3 {
          text-shadow: 2px 2px 3px rgba(0,0,0,0.6);
      }
      
      .lead {
          font-size: 18px;
          font-weight: 400;
      }
      
      .topnav {
          font-size: 14px;
      }
      
      .wrapper {
        min-height: 100%;
        height: auto !important;
        height: 100%;
        margin: 0 auto -4em;
      }
      

      Run the app; you should be able to see the homepage now.

      Auth Blueprint

      For the auth blueprint, we’ll begin by creating the registration and login forms. We’ll use Flask-WTF, which will allow us to create forms that are secure (thanks to CSRF protection and reCAPTCHA support).

      pip install Flask-WTF
      

      Now to write the code for the forms:

      # app/auth/forms.py
      
      from flask_wtf import FlaskForm
      from wtforms import PasswordField, StringField, SubmitField, ValidationError
      from wtforms.validators import DataRequired, Email, EqualTo
      
      from ..models import Employee
      
      
      class RegistrationForm(FlaskForm):
          """
          Form for users to create new account
          """
          email = StringField('Email', validators=[DataRequired(), Email()])
          username = StringField('Username', validators=[DataRequired()])
          first_name = StringField('First Name', validators=[DataRequired()])
          last_name = StringField('Last Name', validators=[DataRequired()])
          password = PasswordField('Password', validators=[
                                              DataRequired(),
                                              EqualTo('confirm_password')
                                              ])
          confirm_password = PasswordField('Confirm Password')
          submit = SubmitField('Register')
      
          def validate_email(self, field):
              if Employee.query.filter_by(email=field.data).first():
                  raise ValidationError('Email is already in use.')
      
          def validate_username(self, field):
              if Employee.query.filter_by(username=field.data).first():
                  raise ValidationError('Username is already in use.')
      
      
      class LoginForm(FlaskForm):
          """
          Form for users to login
          """
          email = StringField('Email', validators=[DataRequired(), Email()])
          password = PasswordField('Password', validators=[DataRequired()])
          submit = SubmitField('Login')
      

      Flask-WTF has a number of validators that make writing forms much easier. All the fields in the models have the DataRequired validator, which means that users will be required to fill all of them in order to register or login.

      For the registration form, we require users to fill in their email address, username, first name, last name, and their password twice. We use the Email validator to ensure valid email formats are used (e.g some-name@some-domain.com.) We use the EqualTo validator to confirm that the password and confirm_password fields in the RegistrationForm match. We also create methods (validate_email and validate_username) to ensure that the email and username entered have not been used before.

      The submit field in both forms will be represented as a button that users will be able to click to register and login respectively.

      With the forms in place, we can write the views:

      # app/auth/views.py
      
      from flask import flash, redirect, render_template, url_for
      from flask_login import login_required, login_user, logout_user
      
      from . import auth
      from forms import LoginForm, RegistrationForm
      from .. import db
      from ..models import Employee
      
      
      @auth.route('/register', methods=['GET', 'POST'])
      def register():
          """
          Handle requests to the /register route
          Add an employee to the database through the registration form
          """
          form = RegistrationForm()
          if form.validate_on_submit():
              employee = Employee(email=form.email.data,
                                  username=form.username.data,
                                  first_name=form.first_name.data,
                                  last_name=form.last_name.data,
                                  password=form.password.data)
      
              # add employee to the database
              db.session.add(employee)
              db.session.commit()
              flash('You have successfully registered! You may now login.')
      
              # redirect to the login page
              return redirect(url_for('auth.login'))
      
          # load registration template
          return render_template('auth/register.html', form=form, title="Register")
      
      
      @auth.route('/login', methods=['GET', 'POST'])
      def login():
          """
          Handle requests to the /login route
          Log an employee in through the login form
          """
          form = LoginForm()
          if form.validate_on_submit():
      
              # check whether employee exists in the database and whether
              # the password entered matches the password in the database
              employee = Employee.query.filter_by(email=form.email.data).first()
              if employee is not None and employee.verify_password(
                      form.password.data):
                  # log employee in
                  login_user(employee)
      
                  # redirect to the dashboard page after login
                  return redirect(url_for('home.dashboard'))
      
              # when login details are incorrect
              else:
                  flash('Invalid email or password.')
      
          # load login template
          return render_template('auth/login.html', form=form, title="Login")
      
      
      @auth.route('/logout')
      @login_required
      def logout():
          """
          Handle requests to the /logout route
          Log an employee out through the logout link
          """
          logout_user()
          flash('You have successfully been logged out.')
      
          # redirect to the login page
          return redirect(url_for('auth.login'))
      

      Just like in the home blueprint, each view here handles requests to the specified URL. The register view creates an instance of the Employee model class using the registration form data to populate the fields, and then adds it to the database. This esentially registers a new employee.

      The login view queries the database to check whether an employee exists with an email address that matches the email provided in the login form data. It then uses the verify_password method to check that the password in the database for the employee matches the password provided in the login form data. If both of these are true, it proceeds to log the user in using the login_user method provided by Flask-Login.

      The logout view has the login_required decorator, which means that a user must be logged in to access it. It calles the logout_user method provided by Flask-Login to log the user out.

      Note the use of flash method, which allows us to use Flask’s message flashing feature. This allows us to communicate feedback to the user, such as informing them of successful registration or unsuccessful login.

      Finally, let’s work on the templates. First, we’ll install Flask-Bootstrap so we can use its wtf and utils libraries. The wtf library will allow us to quickly generate forms in the templates based on the forms in the forms.py file. The utils library will allow us to display the flash messages we set earlier to give feedback to the user.

      pip install flask-bootstrap
      

      We need to edit the app/__init__.py file to use Flask-Bootstrap:

      # app/__init__.py
      
      # after existing third-party imports
      from flask_bootstrap import Bootstrap
      
      # existing code remains
      
      
      def create_app(config_name):
          # existing code remains
      
          Bootstrap(app)
      
          from app import models
      
          # blueprint registration remains here
      
          return app
      

      We’ve made quite a number of edits to the app/__init__.py file. This is the final version of the file and how it should look at this point (note that I have re-arranged the imports and variables in alphabetical order):

      # app/__init__.py
      
      # third-party imports
      from flask import Flask
      from flask_bootstrap import Bootstrap
      from flask_login import LoginManager
      from flask_migrate import Migrate
      from flask_sqlalchemy import SQLAlchemy
      
      # local imports
      from config import app_config
      
      db = SQLAlchemy()
      login_manager = LoginManager()
      
      
      def create_app(config_name):
          app = Flask(__name__, instance_relative_config=True)
          app.config.from_object(app_config[config_name])
          app.config.from_pyfile('config.py')
      
          Bootstrap(app)
          db.init_app(app)
          login_manager.init_app(app)
          login_manager.login_message = "You must be logged in to access this page."
          login_manager.login_view = "auth.login"
          migrate = Migrate(app, db)
      
          from app import models
      
          from .admin import admin as admin_blueprint
          app.register_blueprint(admin_blueprint, url_prefix='/admin')
      
          from .auth import auth as auth_blueprint
          app.register_blueprint(auth_blueprint)
      
          from .home import home as home_blueprint
          app.register_blueprint(home_blueprint)
      
          return app
      

      We need two templates for the auth blueprint: register.html and login.html, which we’ll create in an auth directory inside the templates directory.

      <!-- app/templates/auth/register.html -->
      
      {% import "bootstrap/wtf.html" as wtf %}
      {% extends "base.html" %}
      {% block title %}Register{% endblock %}
      {% block body %}
      <div class="content-section">
        <div class="center">
          <h1>Register for an account</h1>
          <br/>
          {{ wtf.quick_form(form) }}
        </div>
      </div>
      {% endblock %}
      
      <!-- app/templates/auth/login.html -->
      
      {% import "bootstrap/utils.html" as utils %}
      {% import "bootstrap/wtf.html" as wtf %}
      {% extends "base.html" %}
      {% block title %}Login{% endblock %}
      {% block body %}
      <div class="content-section">
        <br/>
        {{ utils.flashed_messages() }}
        <br/>
        <div class="center">
          <h1>Login to your account</h1>
          <br/>
          {{ wtf.quick_form(form) }}
        </div>
      </div>
      {% endblock %}
      

      The forms are loaded from the app/auth/views.py file, where we specified which template files to display for each view. Remember the Register and Login links in the base template? Let’s update them now so we can access the pages from the menus:

      <!-- app/templates/base.html -->
      
      <!-- Modify nav bar menu -->
      <ul class="nav navbar-nav navbar-right">
          <li><a href="https://www.digitalocean.com/community/tutorials/{{ url_for("home.homepage') }}">Home</a></li>
          <li><a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.register') }}">Register</a></li>
          <li><a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.login') }}">Login</a></li>
      </ul>
      
      <!-- Modify footer menu -->
      <ul class="list-inline">
          <li><a href="https://www.digitalocean.com/community/tutorials/{{ url_for("home.homepage') }}">Home</a></li>
          <li class="footer-menu-divider">⋅</li>
          <li><a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.register') }}">Register</a></li>
          <li class="footer-menu-divider">⋅</li>
          <li><a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.login') }}">Login</a></li>
      </ul>
      

      Run the app again and click on the Register and Login menu links. You should see the templates loaded with the appropriate form.

      Try to fill out the registration form; you should be able to register a new employee. After registration, you should be redirected to the login page, where you will see the flash message we configured in the app/auth/views.py file, inviting you to login.

      Logging in should be successful; however you should get a Template Not Found error after logging in, because the dashboard.html template has not been created yet. Let’s do that now:

      <!-- app/templates/home/dashboard.html -->
      
      {% extends "base.html" %}
      {% block title %}Dashboard{% endblock %}
      {% block body %}
      <div class="intro-header">
          <div class="container">
              <div class="row">
                  <div class="col-lg-12">
                      <div class="intro-message">
                          <h1>The Dashboard</h1>
                          <h3>We made it here!</h3>
                          <hr class="intro-divider">
                          </ul>
                      </div>
                  </div>
              </div>
          </div>
      </div>
      {% endblock %}
      

      Refresh the page. You’ll notice that the navigation menu still has the register and login links, even though we are already logged in. We’ll need to modify it to show a logout link when a user is already authenticated. We will also include a Hi, username! message in the nav bar:

      <!-- app/templates/base.html -->
      
      <!-- In the head tag, include link to Font Awesome CSS so we can use icons -->
      <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
      
      <!-- Modify nav bar menu -->
      <ul class="nav navbar-nav navbar-right">
          {% if current_user.is_authenticated %}
            <li><a href="https://www.digitalocean.com/community/tutorials/{{ url_for("home.dashboard') }}">Dashboard</a></li>
            <li><a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.logout') }}">Logout</a></li>
            <li><a><i class="fa fa-user"></i>  Hi, {{ current_user.username }}!</a></li>
          {% else %}
            <li><a href="https://www.digitalocean.com/community/tutorials/{{ url_for("home.homepage') }}">Home</a></li>
            <li><a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.register') }}">Register</a></li>
            <li><a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.login') }}">Login</a></li>
          {% endif %}
      </ul>
      
      <!-- Modify footer menu -->
      <ul class="list-inline">
          <li><a href="https://www.digitalocean.com/community/tutorials/{{ url_for("home.homepage') }}">Home</a></li>
          <li class="footer-menu-divider">⋅</li>
          {% if current_user.is_authenticated %}
            <li><a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.logout') }}">Logout</a></li>
          {% else %}
            <li><a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.register') }}">Register</a></li>
            <li class="footer-menu-divider">⋅</li>
            <li><a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.login') }}">Login</a></li>
          {% endif %}
      </ul>
      

      Note how we use if-else statements in the templates. Also, take note of the current_user proxy provided by Flask-Login, which allows us to check whether the user is authenticated and to get the user’s username.

      Logging out will take you back to the login page:

      Attempting to access the dashboard page without logging in will redirect you to the login page and display the message we set in the app/__init__.py file:

      Notice that the URL is configured such that once you log in, you will be redirected to the page you initially attempted to access, which in this case is the dashboard.

      Conclusion

      That’s it for Part One! We’ve covered quite a lot: setting up a MySQL database, creating models, migrating the database, and handling registration, login, and logout. Good job for making it this far!

      Watch this space for Part Two, which will cover the CRUD functionality of the app, allowing admin users to add, list, edit, and delete departments and roles, as well as assign them to employees.



      Source link

      Testing Angular with Jasmine and Karma (Part 1)


      Our goal

      In this tutorial we will be building and testing an employee directory for a fictional company. This directory will have a view to show all of our users along with another view to serve as a profile page for individual users. Within this part of the tutorial we’ll focus on building the service and its tests that will be used for these users.

      In following tutorials, we’ll populate the user profile page with an image of the user’s favorite Pokemon using the Pokeapi and learn how to test services that make HTTP requests.

      What you should know

      The primary focus for this tutorial is testing so my assumption is that you’re comfortable working with TypeScript and Angular applications. As a result of this I won’t be taking the time to explain what a service is and how it’s used. Instead, I’ll provide you with code as we work through our tests.

      Why Test?

      From personal experience, tests are the best way to prevent software defects. I’ve been on many teams in the past where a small piece of code is updated and the developer manually opens their browser or Postman to verify that it still works. This approach (manual QA) is begging for a disaster.

      Tests are the best way to prevent software defects.

      As features and codebases grow, manual QA becomes more expensive, time consuming, and error prone. If a feature or function is removed does every developer remember all of its potential side-effects? Are all developers manually testing in the same way? Probably not.

      The reason we test our code is to verify that it behaves as we expect it to. As a result of this process you’ll find you have better feature documentation for yourself and other developers as well as a design aid for your APIs.

      Why Karma?

      Karma is a direct product of the AngularJS team from struggling to test their own framework features with existing tools. As a result of this, they made Karma and have transitioned it to Angular as the default test runner for applications created with the Angular CLI.

      In addition to playing nicely with Angular, it also provides flexibility for you to tailor Karma to your workflow. This includes the option to test your code on various browsers and devices such as phones, tablets, and even a PS3 like the YouTube team.

      Karma also provides you options to replace Jasmine with other testing frameworks such as Mocha and QUnit or integrate with various continuous integration services like Jenkins, TravisCI, or CircleCI.

      Unless you add some additional configuration your typical interaction with Karma will be to run ng test in a terminal window.

      Why Jasmine?

      Jasmine is a behavior-driven development framework for testing JavaScript code that plays very well with Karma. Similar to Karma, it’s also the recommended testing framework within the Angular documentation as it’s setup for you with the Angular CLI. Jasmine is also dependency free and doesn’t require a DOM.

      As far as features go, I love that Jasmine has almost everything I need for testing built into it. The most notable example would be spies. A spy allows us to “spy” on a function and track attributes about it such as whether or not it was called, how many times it was called, and with which arguments it was called. With a framework like Mocha, spies are not built-in and would require pairing it with a separate library like Sinon.js.

      The good news is that the switching costs between testing frameworks is relatively low with differences in syntax as small as Jasmine’s toEqual() and Mocha’s to.equal().

      A Simple Test Example

      Imagine you had an alien servant named Adder who follows you everywhere you go. Other than being a cute alien companion Adder can really only do one thing, add two numbers together.

      To verify Adder’s ability to add two numbers we could generate a set of test cases to see if Adder provides us the correct answer.

      Within Jasmine, this would begin with what’s referred to as a “suite” which groups a related set of tests by calling the function describe.

      // A Jasmine suite
      describe('Adder', () => {
      
      });
      

      From here we could provide Adder with a set of test cases such as two positive numbers (2, 4), a positive number and a zero (3, 0), a positive number and a negative number (5, -2), and so on.

      Within Jasmine, these are referred to as “specs” which we create by calling the function it, passing it a string to describe the functionality that’s being tested.

      describe('Adder', () => {
        // A jasmine spec
        it('should be able to add two whole numbers', () => {
          expect(Adder.add(2, 2)).toEqual(4);
        });
      
        it('should be able to add a whole number and a negative number', () => {
          expect(Adder.add(2, -1)).toEqual(1);
        });
      
        it('should be able to add a whole number and a zero', () => {
          expect(Adder.add(2, 0)).toEqual(2);
        });
      });
      

      Within each spec we call expect and provide it what is referred to as an “actual”—the call site of our actual code. After the expectation, or expect, is the chained “matcher” function, such as toEqual, which the testing developer provides with the expected output of the code being tested.

      There are many other matchers available to us other than toEqual. You can see a full list within Jasmine’s documentation.

      Our tests aren’t concerned with how Adder arrives at the answer. We only care about the answer Adder provides us. For all we know, this may be Adder’s implementation of add.

      function add(first, second) {
        if (true) { // why?
          if (true) { // why??
            if (1 === 1) { // why?!?1
              return first + second;
            }
          }
        }
      }
      

      In other words, we only care that Adder behaves as expected—we have no concern for Adder’s implementation.

      This is what makes a practice like test-driven development (TDD) so powerful. You can first write a test for a function and its expected behavior and get it to pass. Then, once it’s passing, you can refactor your function to a different implementation and if the test is still passing, you know your function is still behaving as specified within your tests even with a different implementation. Adder’s add function would be a good example!

      Angular setup

      We’ll begin by creating our new application using the Angular CLI.

      ng new angular-testing --routing
      

      Since we’ll have multiple views in this application we use the --routing flag so the CLI automatically generates a routing module for us.

      From here we can verify everything is working correctly by moving into the new angular-testing directory and running the application.

      cd angular-testing
      ng serve -o
      

      You can also verify the application’s tests are currently in a passing state.

      ng test
      

      Adding a home page

      Before creating a service to populate our home page with users, we’ll start by creating the home page.

      ng g component home
      

      Now that our component has been created, we can update our routing module’s (app-routing.module.ts) root path to HomeComponent.

      import { NgModule } from '@angular/core';
      import { RouterModule, Routes } from '@angular/router';
      import { HomeComponent } from './home/home.component';
      
      const routes: Routes = [
        { path: '', component: HomeComponent }
      ];
      
      @NgModule({
        imports: [RouterModule.forRoot(routes)],
        exports: [RouterModule]
      })
      export class AppRoutingModule { }
      

      Run the application if it isn’t already and you should now see “home works!” below the default template in app.component.html which was created by the CLI.

      Removing AppComponent tests

      Since we no longer need the default contents of AppComponent, let’s update it by removing some unnecessary code.

      First, remove everything in app.component.html so that only the router-outlet directive remains.

      <router-outlet></router-outlet>
      

      Within app.component.ts, you can also remove the title property.

      import { Component } from '@angular/core';
      
      @Component({
        selector: 'app-root',
        templateUrl: './app.component.html',
        styleUrls: ['./app.component.css']
      })
      export class AppComponent { }
      

      Finally, you can update the tests in app.component.spec.ts by removing the two tests for some of the contents that were previously in app.component.html.

      import { async, TestBed } from '@angular/core/testing';
      import { RouterTestingModule } from '@angular/router/testing';
      import { AppComponent } from './app.component';
      describe('AppComponent', () => {
        beforeEach(async(() => {
          TestBed.configureTestingModule({
            imports: [
              RouterTestingModule
            ],
            declarations: [
              AppComponent
            ],
          }).compileComponents();
        }));
        it('should create the app', async(() => {
          const fixture = TestBed.createComponent(AppComponent);
          const app = fixture.debugElement.componentInstance;
          expect(app).toBeTruthy();
        }));
      });
      

      Testing an Angular service

      Now that our home page is set up we can work on creating a service to populate this page with our directory of employees.

      ng g service services/users/users
      

      Here we’ve created our users service within a new services/users directory to keep our services away from the default app directory which can get messy quick.

      Now that our service is created, we can make a few small changes to the test file services/users/users.service.spec.ts.

      I personally find injecting dependencies within it() to be a bit repetitive and harder to read as it’s done in the default scaffolding for our test file as shown below:

      it('should be created', inject([TestService], (service: TestService) => {
        expect(service).toBeTruthy();
      }));
      

      With a few minor changes, we can move this into the beforeEach removing the duplication from each it.

      import { TestBed } from '@angular/core/testing';
      import { UsersService } from './users.service';
      
      describe('UsersService', () => {
        let usersService: UsersService; // Add this
      
        beforeEach(() => {
          TestBed.configureTestingModule({
            providers: [UsersService]
          });
      
          usersService = TestBed.get(UsersService); // Add this
        });
      
        it('should be created', () => { // Remove inject()
          expect(usersService).toBeTruthy();
        });
      });
      

      In the code above, TestBed.configureTestingModule({}) sets up the service we want to test with UsersService set in providers. We then inject the service into our test suite using TestBed.get() with the service we want to test as the argument. We set the return value to our local usersService variable which will allow us to interact with this service within our tests just as we would within a component.

      Now that our test setup is restructured, we can add a test for an all method which will return a collection of users.

      import { of } from 'rxjs'; // Add import
      
      describe('UsersService', () => {
        ...
      
        it('should be created', () => {
          expect(usersService).toBeTruthy();
        });
      
        // Add tests for all() method
        describe('all', () => {
          it('should return a collection of users', () => {
            const userResponse = [
              {
                id: '1',
                name: 'Jane',
                role: 'Designer',
                pokemon: 'Blastoise'
              },
              {
                id: '2',
                name: 'Bob',
                role: 'Developer',
                pokemon: 'Charizard'
              }
            ];
            let response;
            spyOn(usersService, 'all').and.returnValue(of(userResponse));
      
            usersService.all().subscribe(res => {
              response = res;
            });
      
            expect(response).toEqual(userResponse);
          });
        });
      });
      

      Here we add a test for the expectation that all will return a collection of users. We declare a userResponse variable set to a mocked response of our service method. Then we use the spyOn() method to spy on usersService.all and chain .returnValue() to return our mocked userResponse variable wrapping it with of() to return this value as an observable.

      With our spy set, we call our service method as we would within a component, subscribe to the observable, and set its return value to response.

      Finally, we add our expectation that response will be set to the return value of the service call, userResponse.

      Why mock?

      At this point many people ask, “Why are we mocking the response?” Why did we provide our test a return value userResponse that we created ourselves, to manually set what’s being returned from our service? Shouldn’t the service call return the real response from the service, whether it’s a hard-coded set of users or a response from an HTTP request?

      This is a perfectly reasonable question to ask and one that can be hard to wrap your head around when you first begin testing. I find this concept is easiest to illustrate with a real world example.

      Imagine you own a restaurant and it’s the night before opening day. You gather everyone you’ve hired for a “test run” of the restaurant. You invite a few friends to come in and pretend they’re customers who will sit down and order a meal.

      No dishes will actually be served in your test run. You’ve already worked with your cooks and are satisfied they can make the dishes correctly. In this test run you want to test the transition from the customer ordering their dish, to the waiter sending that to the kitchen, and then the waiters fulfilling the kitchen’s response to the customer. This response from the kitchen may be one of a few options.

      1. The meal is ready.
      2. The meal is delayed.
      3. The meal cannot be made. We ran out of ingredients for the dish.

      If the meal is ready, the waiter delivers the meal to the customer. However, in the event that a meal is late or cannot be made, the waiter will have to go back to the customer, apologize, and potentially ask for a second dish.

      In our test run, it wouldn’t make sense to actually create the meals when we want to test the front-end’s (waiter’s) ability to fulfill the requests received from the backend (kitchen). More importantly, if we wanted to test our waiters could actually apologize to customers in the cases where a meal is delayed or cannot be made we would literally be waiting until our cooks were too slow or we ran out of ingredients before our tests for those cases could be confirmed. For this reason, we would “mock” the backend (kitchen) and give the waiters whatever scenario it is that we want to test.

      Similarly in code, we don’t actually hit the API when we’re testing various scenarios. We mock the response we may expect to receive and verify that our application can handle that response accordingly. Just like our kitchen example, if we were testing our application’s ability to handle an API call that failed we would literally have to wait for our API to fail to verify it could handle that case—a scenario that hopefully won’t be happening that often!

      Adding users

      To get this test to pass, we need to implement the service method in users.service.ts.

      First, we’ll start by adding our imports and a collection of employees to the service.

      import { Injectable } from '@angular/core';
      import { Observable, of } from 'rxjs'; // Add imports
      
      @Injectable({
        providedIn: 'root'
      })
      export class UsersService {
        users: Array<object> = [  // Add employee object
          {
            id: '1',
            name: 'Jane',
            role: 'Designer',
            pokemon: 'Blastoise'
          },
          {
            id: '2',
            name: 'Bob',
            role: 'Developer',
            pokemon: 'Charizard'
          },
          {
            id: '3',
            name: 'Jim',
            role: 'Developer',
            pokemon: 'Venusaur'
          },
          {
            id: '4',
            name: 'Adam',
            role: 'Designer',
            pokemon: 'Yoshi'
          }
        ];
      
        constructor() { }
      }
      

      Then, just below our constructor, we can implement all.

      all(): Observable<Array<object>> {
        return of(this.users);
      }
      

      Run ng test again and you should now have passing tests including the new tests for our service method.

      Add users to the home page

      Now that our service method is ready to use, we can work towards populating our home page with these users.

      First, we’ll update index.html with Bulma to help us with some styling.

      <!doctype html>
      <html lang="en">
      <head>
        <meta charset="utf-8">
        <title>AngularTesting</title>
        <base href="https://www.digitalocean.com/">
      
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="icon" type="image/x-icon" href="favicon.ico">
        <!--Add these-->
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css">
        <script defer src="https://use.fontawesome.com/releases/v5.1.0/js/all.js"></script>
      </head>
      <body>
        <app-root></app-root>
      </body>
      </html>
      

      Then within home/home.component.ts we can add a call to our new service.

      import { Component, OnInit } from '@angular/core';
      import { UsersService } from '../services/users/users.service';
      
      @Component({
        selector: 'app-home',
        templateUrl: './home.component.html',
        styleUrls: ['./home.component.css']
      })
      export class HomeComponent implements OnInit {
        users;
      
        constructor(private usersService: UsersService) { }
      
        ngOnInit() {
          this.usersService.all().subscribe(res => {
            this.users = res;
          });
        }
      
      }
      

      First we import our service and inject it into our component’s constructor. Then we add a call to the service method within ngOnInit and set the return value to our component’s users property.

      To display these users to the view, update the template in home/home.component.html.

      <section class="section is-small">
        <div class="container">
          <div class="columns">
            <div class="column" *ngFor="let user of users">
              <div class="box">
                <div class="content">
                  <p class="has-text-centered is-size-5">{% raw %}{{user.name}}{% endraw %}</p>
                  <ul>
                    <li><strong>Role:</strong> {% raw %}{{user.role}}{% endraw %}</li>
                    <li><strong>Pokemon:</strong> {% raw %}{{user.pokemon}}{% endraw %}</li>
                  </ul>
                </div>
              </div>
            </div>
          </div>
        </div>
      </section>
      

      Now when you run ng serve and view the home page, you should see the users displayed within Bulma boxes.

      Finding a single user

      Now that our users are being populated into our home page, we’ll add one more service method for finding a single user that will be used for the user profile pages.

      First we’ll add the tests for our new service method.

      describe('all', () => {
        ...
      });
      
      describe('findOne', () => {
        it('should return a single user', () => {
          const userResponse = {
            id: '2',
            name: 'Bob',
            role: 'Developer',
            pokemon: 'Charizard'
          };
          let response;
          spyOn(usersService, 'findOne').and.returnValue(of(userResponse));
      
          usersService.findOne('2').subscribe(res => {
            response = res;
          });
      
          expect(response).toEqual(userResponse);
        });
      });
      

      Here we add a test for the expectation that findOne will return a single user. We declare a userResponse variable set to a mocked response of our service method, a single object from the collection of users.

      We then create a spy for usersService.findOne and return our mocked userResponse variable. With our spy set, we call our service method and set its return value to response.

      Finally, we add our assertion that response will be set to the return value of the service call, userResponse.

      To get this test to pass, we can add the following implementation to users.service.ts.

      all(): Observable<Array<object>> {
        return of(this.users);
      }
      
      findOne(id: string): Observable<object> {
        const user = this.users.find((u: any) => {
          return u.id === id;
        });
        return of(user);
      }
      

      Now when you run ng test you should see all of the tests in a passing state.

      Conclusion

      At this point I hope testing, both its benefits and the reason for writing them, is starting to become a bit more clear. Personally, I pushed off testing for the longest time and my reasons were primarily because I didn’t understand the why behind them and resources for testing were limited.

      What we’ve created in this tutorial isn’t the most visually impressive application but it’s a step in the right direction.

      In the next tutorial, we’ll create the user profile page and a service to retrieve a Pokemon image using the Pokeapi. We’ll learn how to test service methods that make HTTP requests and how to test components.

      If you want the tests to display in a more readable format within your terminal, there’s an npm package for this.

      First, install the package.

      npm install karma-spec-reporter --save-dev
      

      Once that’s finished, open src/karma.conf.js, add the new package to plugins, and update the progress value within reporters to spec.

      module.exports = function (config) {
        config.set({
          basePath: '',
          frameworks: ['jasmine', '@angular-devkit/build-angular'],
          plugins: [
            require('karma-jasmine'),
            require('karma-chrome-launcher'),
            require('karma-jasmine-html-reporter'),
            require('karma-coverage-istanbul-reporter'),
            require('@angular-devkit/build-angular/plugins/karma'),
            require('karma-spec-reporter') // Add this
          ],
          client: {
            clearContext: false // leave Jasmine Spec Runner output visible in browser
          },
          coverageIstanbulReporter: {
            dir: require('path').join(__dirname, '../coverage'),
            reports: ['html', 'lcovonly'],
            fixWebpackSourcePaths: true
          },
          reporters: ['spec', 'kjhtml'], // Update progress to spec
          port: 9876,
          colors: true,
          logLevel: config.LOG_INFO,
          autoWatch: true,
          browsers: ['Chrome'],
          singleRun: false
        });
      };
      

      Now when you run ng test you should have a more visually appealing output for your test suite.



      Source link