Building a Weather App With Masonite Framework - Basics Concepts

Mangabo Kolawole - Dec 11 '19 - - Dev Community

Through this guide, you will build a weather app to help you understand basics web app creation with Masonite.

Plans :

  1. A quick view on Masonite Frameowork
  2. Environment setup
  3. Adding Routing,Controllers and View
  4. Using the Weather API
  5. Display the data in our Template
  6. Add "Add" Button and Delete Button
  7. Conclusion

Quick View on Masonite Framework

Masonite is an increasingly popular Python web framework that is generating a lot of attention in the Python community. Described as a modern and developer-centric Python web framework, it is really suited for beginners as well for experts who want to create powerful web apps and deploy it quickly.
And if you are coming from Django like me, you will quickly fall in love with the craft command.🙃
So now, Let's set up our environment.

Environment Setup

This step is optional but I recommend it because it isolates your working environment to avoid packages conflict.
Use your preferred tool, I am using virtualenv.

mkdir Masonite-Weather 
virtualenv -p python3 env 
source env/bin/activate
Enter fullscreen mode Exit fullscreen mode

The virtual environment ready, install the masonite-cli to enjoy the power of the craft command.
pip3 install masonite-cli
Great! Now, your craft command should be ready!
craft
And now, let's create our project.
craft new Weather

Application Created Successfully!
Now, go into your project to install masonite dependencies.

cd Weather
craft install
Enter fullscreen mode Exit fullscreen mode

This will install all the required dependencies of Masonite, creates a .env file for us, generates a new secret key, and put that secret key in our .env file.
Now run craft serve to run the server and go the localhost/8000.
Alt Text

Before start coding, I must explain to you the architecture of every app based on Masonite.
Masonite is called MVC framework. The MVC architecture or Model, View & Controller is popular because it isolates the application logic from the user interface and supports separation of concerns.

  • The Model stores data in the database. It's the shape of the data and business logic.
  • The view is the UI. It basically consists of HTML/CSS/Javascript and it renders the data returned by the Controller.
  • The Controller connects View and Model adequately. It handles any requests coming from the view, treats it and involves the adequate model if necessary.

Here is the tree of the project.

.
├── app
│   ├── City.py
│   ├── http
│   │   ├── controllers
│   │   │   ├── __pycache__
│   │   │   │   ├── WeatherController.cpython-37.pyc
│   │   │   │   └── WelcomeController.cpython-37.pyc
│   │   │   ├── WeatherController.py
│   │   │   └── WelcomeController.py
│   │   └── middleware
│   │       ├── AuthenticationMiddleware.py
│   │       ├── CsrfMiddleware.py
│   │       ├── LoadUserMiddleware.py
│   │       ├── __pycache__
│   │       │   ├── AuthenticationMiddleware.cpython-37.pyc
│   │       │   ├── CsrfMiddleware.cpython-37.pyc
│   │       │   ├── LoadUserMiddleware.cpython-37.pyc
│   │       │   └── VerifyEmailMiddleware.cpython-37.pyc
│   │       └── VerifyEmailMiddleware.py
│   ├── providers
│   ├── __pycache__
│   │   ├── City.cpython-37.pyc
│   │   └── User.cpython-37.pyc
│   └── User.py
├── bootstrap
│   ├── cache
│   ├── __pycache__
│   │   └── start.cpython-37.pyc
│   └── start.py
├── config
│   ├── application.py
│   ├── auth.py
│   ├── broadcast.py
│   ├── cache.py
│   ├── database.py
│   ├── factories.py
│   ├── __init__.py
│   ├── mail.py
│   ├── middleware.py
│   ├── packages.py
│   ├── providers.py
│   ├── __pycache__
│   │   ├── application.cpython-37.pyc
│   │   ├── broadcast.cpython-37.pyc
│   │   ├── cache.cpython-37.pyc
│   │   ├── database.cpython-37.pyc
│   │   ├── __init__.cpython-37.pyc
│   │   ├── mail.cpython-37.pyc
│   │   ├── middleware.cpython-37.pyc
│   │   ├── packages.cpython-37.pyc
│   │   ├── providers.cpython-37.pyc
│   │   ├── queue.cpython-37.pyc
│   │   ├── session.cpython-37.pyc
│   │   └── storage.cpython-37.pyc
│   ├── queue.py
│   ├── session.py
│   └── storage.py
├── craft
├── databases
│   ├── migrations
│   │   ├── 2018_01_09_043202_create_users_table.py
│   │   ├── 2019_12_09_232649_create_cities_table.py
│   │   ├── __init__.py
│   │   └── __pycache__
│   │       ├── 2018_01_09_043202_create_users_table.cpython-37.pyc
│   │       ├── 2019_12_09_232649_create_cities_table.cpython-37.pyc
│   │       └── __init__.cpython-37.pyc
│   └── seeds
│       ├── database_seeder.py
│       ├── __init__.py
│       └── user_table_seeder.py
├── LICENSE
├── __pycache__
│   └── wsgi.cpython-37.pyc
├── README.md
├── requirements.txt
├── resources
│   ├── __init__.py
│   └── templates
│       ├── base.html
│       ├── __init__.py
│       ├── redirect.html
│       ├── weather.html
│       └── welcome.html
├── routes
│   ├── __pycache__
│   │   └── web.cpython-37.pyc
│   └── web.py
├── storage
│   ├── compiled
│   │   └── style.css
│   ├── public
│   │   ├── favicon.ico
│   │   └── robots.txt
│   ├── static
│   │   ├── __init__.py
│   │   └── sass
│   │       └── style.scss
│   └── uploads
│       └── __init__.py
├── tests
│   ├── database
│   │   └── test_user.py
│   └── unit
│       └── test_works.py
├── tree.txt
└── wsgi.py

Enter fullscreen mode Exit fullscreen mode

Adding Routes,Controllers and View

Routes

We need to add routes first. A route in Masonite consists of a URL and a controller. Masonite automatically matches the two elements according to the type of HTTP request(Get, Post etc).
In masonite, routes are in routes/web.py.

#routes/web.py
"""Web Routes."""

from masonite.routes import Get, Post

ROUTES = [
    #Get('/', 'WelcomeController@show').name('welcome'),

    #Weather App
    Get('/', 'WeatherController@show'),
]
Enter fullscreen mode Exit fullscreen mode

Controllers

After the routes, we need to configure controllers for the weather app.
A Controller is a Class with methods.
If you are coming from Django, you can see controllers methods as "function-based views".
The controllers are located in app/http/controllers/.
Actually, we don't need to write code to create one.
Just use the craft command.
craft controller Weather

#app/http/controller/WeatherController.py

"""A WeatherController Module."""

from masonite.request import Request
from masonite.view import View
from masonite.controllers import Controller


class WeatherController(Controller):
    """WeatherController Controller Class."""

    def __init__(self, request: Request):
        """WeatherController Initializer
        Arguments:
            request {masonite.request.Request} -- The Masonite Request class.
        """
        self.request = request

    def show(self, view: View):
        pass
Enter fullscreen mode Exit fullscreen mode

Return the view

As I said earlier, View handles the UI. They are HTML templates.They are located in resources/templates.
craft view Weather
This will generate an empty html file named weather.html.

I provide an HTML template, we are going to use.

<!--resources/templates/weather.html-->

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Weather App</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css">
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
    <style>
        .has-item-centered {
            display: flex;
            justify-content: center;
        }
    </style>
</head>

<body>
    <nav class="navbar is-info" role="navigation" aria-label="main navigation">
        <div class="navbar-brand">
            <a class="navbar-item">
                <h1 class="title is-1 has-text-white">
                    Masonite Weather
                </h1>
            </a>
        </div>
    </nav>

    <section class="section hero is-white is-fullheight">
        <div class="container has-text-centered">
            <span class="content is-medium">
                This application lets you check for weather in differents cities in the world. <br> If you want weather
                about
                a
                specific city,
                just enter it below and add it.
            </span>
            <div class="columns">
                <div class="column">

                </div>
                <div class="column is-4">
                    <div class="field">
                        <div class="control">
                            <input class="input is-info is-rounded is-focused" type="text" placeholder="Add city">
                        </div>
                    </div>
                    <div class="field has-item-centered">
                        <div class="control">
                            <button class="button is-rounded is-outlined is-info">Add City</button>
                        </div>
                    </div>
                    <div class="box">
                        <article class="media">
                            <div class="media-left">
                                <figure class="image is-50x50">
                                    <img src="http://openweathermap.org/img/w/10d.png" alt="Image">
                                </figure>
                            </div>
                            <div class="media-content">
                                <div class="content">
                                    <p>
                                        <span class="title">Tokyo</span>
                                        <br>
                                        <span class="subtitle">20 C</span>
                                        <br> Sun at this time
                                    </p>
                                </div>
                            </div>
                        </article>
                    </div>
                </div>
                <div class="column">

                </div>
            </div>
        </div>
    </section>

    <footer class="hero is-info">
        <div class="content has-text-centered">
            <p>
                Made with <i class="fas fa-heart"></i> by <a href="https://github.com/Kolawole39/Masonite-Weather-App-tutorial/">Kolawole</a>.
            </p>
            <p>
                This little application is made with Masonite.
            </p>
        </div>
    </footer>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode

Now, we will return this view in the show() method of WeatherController.

#app/http/controller/WeatherController.py
    ...
    def show(self, view: View):

        return view.render('weather')
Enter fullscreen mode Exit fullscreen mode

Great! Now hit http://localhost:8000 .
Alt Text

Setup the .env

The .env file is where the configurations about Masonite are defined. It also contains a SECRET_KEY that must not be shared.
Because we'll start modifying the database and create a model, we must set some variables.
Go to the .env file and modify like this :

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=masonite
DB_USERNAME=root
DB_PASSWORD=root
DB_LOG=True

Enter fullscreen mode Exit fullscreen mode

to

DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=weather.db
DB_USERNAME=root
DB_PASSWORD=root
DB_LOG=True
Enter fullscreen mode Exit fullscreen mode

Create migrations for City

Migrations in Masonite helps us create tables.
Run :
craft migration create_cities_table --create cities
Now go to database/migrations/2019_12_09_232649_create_cities_table.py.(The name of the file depends on the date. Don't worry if we have not the same file name.)
You will see something like:

#databases/migration/2019_12_09_232649_create_cities_table.py
from orator.migrations import Migration


class CreateCitiesTable(Migration):

    def up(self):
        """
        Run the migrations.
        """
        with self.schema.create('cities') as table:
            table.increments('id')
            table.timestamps()

    def down(self):
        """
        Revert the migrations.
        """
        self.schema.drop('cities')
Enter fullscreen mode Exit fullscreen mode

Modify the file :

     def up(self):
         """
         Run the migrations.
         """
         with self.schema.create('cities') as table:
             table.increments('id')
             table.string('name')
             table.timestamps()
Enter fullscreen mode Exit fullscreen mode

And now run :
craft migrate
This will run migrations and create a table for cities.

Model

Not like others web framework in Python, Models in Masonite take shape of our tables.
craft model City
A file will be create in app/

#app/City.py
"""City Model."""

from config.database import Model

class City(Model):
    """City Model."""
    pass
Enter fullscreen mode Exit fullscreen mode

We will need to specify the table we want to attach to the model. And then we specify the columns we would like to be fillable.

#app/City.py
class City(Model):
    """City Model."""
    __table__ = "cities"

    __fillable__ = ['name'] 
Enter fullscreen mode Exit fullscreen mode

Using the Weather API

We will use OpenWeather API to make requests and get data about the weather in different cities.
Go to https://home.openweathermap.org/users/sign_up to create your account.
After validation, sign in and go to your dashboard in the API KEYS menu.

Alt Text
The API KEY differs from one user to another.

Import Model and test the API

To make our API call, we need two parameters: The name of the city and the API KEY.
'https://api.openweathermap.org/data/2.5/weather?q={}&units=imperial&appid={}'

Now re-open WeatherController.py and import requests module and City Model.

#app/http/controllers/WeatherController.py
import requests
from app.City import City
...
Enter fullscreen mode Exit fullscreen mode

and in our show method, add your APY_KEY.

#app/http/controllers/WeatherController.py
    def show(self, view: View):
        city = 'london'
        API_KEY = 'YOUR_API_KEY' #Don't forget to replace by your API KEY

        url = 'https://api.openweathermap.org/data/2.5/weather?q={}&units=imperial&appid={}'
        city_weather = requests.get(url.format(city,APY_KEY)).json()
        #We get the data and then parse it in python object
        weather_data = {
                    'city' : city.name,
                    'temperature' : city_weather['main']['temp'],
                    'description' : city_weather['weather'][0]['description'],
                    'icon' : city_weather['weather'][0]['icon']
                }
        return view.render('weather',{'weather_data':weather_data}) 
Enter fullscreen mode Exit fullscreen mode

Modify our template :

<!--resources/templates/weather.html-->
                  <div class="box">
                        <article class="media">
                            <div class="media-left">
                                <figure class="image is-50x50">
                                    <img src="http://openweathermap.org/img/w/{{ weather_data.icon }}.png" alt="Image">
                                </figure>
                            </div>
                            <div class="media-content">
                                <div class="content">
                                    <p>
                                        <span class="title">{{ weather_data.city }}</span>
                                        <br>
                                        <span class="subtitle">{{ weather_data.temperature }}</span>
                                        <br> {{ weather_data.description }}
                                    </p>
                                </div>
                            </div>
                        </article>
                    </div>
Enter fullscreen mode Exit fullscreen mode
Masonite uses Jinja2 templating so if you don't understand this templating, be sure to read their documentation.

Now save and open your browser to http://localhost:8000. It may take a few minutes for your API key to become active, so if it doesn't work at first, try again in after a few minutes.
Alt Text

Use the Interactive Console

Masonite provides an interactive console to quickly test your app or make some diagnostics.
craft tinker</>
We will use this console to add data in cities table.

from app.City import City
City.create(name='London')
City.create(name='Paris')
Enter fullscreen mode Exit fullscreen mode

As City is an object, it directly inherits methods of the Model class. So the create method helps us add an entry according to the fillable columns.
If you remember, in our migrations we have two columns, the id and the name. The id is auto-incremented.

Add a loop

We are going to modify the show() method.

#app/http/controllers/WeatherController.py
    def show(self, view: View):
        cities = City.all() #1

        API_KEY = 'YOUR_API_KEY' #Don't forget to replace by your API KEY

        url = 'https://api.openweathermap.org/data/2.5/weather?q={}&units=imperial&appid={}' 

        weather_data = [] #2 
        for city in cities:
            city_weather = requests.get(url.format(city.name,API_KEY)).json()
        #request data in json and then convert it in python data
            weather = {
                    'id':city.id,
                    'city' : city.name,
                    'temperature' : city_weather['main']['temp'],
                    'description' : city_weather['weather'][0]['description'],
                    'icon' : city_weather['weather'][0]['icon']
                }

            weather_data.append(weather) #3
        return view.render('weather', {'weather_data':weather_data })
Enter fullscreen mode Exit fullscreen mode

Let me explain. First, we gather all the entries available in our table cities at #1.
In #2, a list is appropriate to save our object.
In #3, the append method allows adding our new object to the list.
Now hit http://localhost:8000 and see the results.

Add Button and Delete Button

To make the add button functional, we need first to create a URL for a store() method in WeatherController class.
If a user wants to add a city, we must make a call to the API to make sure the city exists before save it.
So, we are sure our app won't crash.
Step 1 :
We create the URL in web.py :

#routes/web.py

#Weather App
    Get('/', 'WeatherController@show'),
    Post('/add','WeatherController@store'),
]
Enter fullscreen mode Exit fullscreen mode

Step 2 :

#app/http/controllers/WeatherController.py
...
    def store(self, request: Request):
        #Before saving the city in our database, we must ask our API if the city exists
        API_KEY = 'YOUR_API_KEY' #Don't forget to replace by your API KEY
        url = 'https://api.openweathermap.org/data/2.5/weather?q={}&units=imperial&appid={}' 

        city = request.input('name')

        city_weather = requests.get(url.format(city,API_KEY)).json()

        if city_weather['cod'] == '404':
            return requests.redirect('/')
        else:
            City.create(
            name = request.input("name")
            )
        return request.redirect('/') 
Enter fullscreen mode Exit fullscreen mode

Now you should modify the forms in the html.

<!--resources/templates/weather.html-->
...
                    <form action="/add" method="POST">
                        {{ csrf_field }}
                        <div class="field">
                            <div class="control">
                                <input class="input is-info is-rounded is-focused" name="name" type="text" placeholder="Add city">
                            </div>
                        </div>
                        <div class="field has-item-centered">
                            <div class="control">
                                <button class="button is-rounded is-outlined is-info">Add City</button>
                            </div>
                        </div>
                    </form>
Enter fullscreen mode Exit fullscreen mode

Now add 'New York' and then try something like "New Work".

Delete Button

It would be nice to delete a City if we want.
Add a new URL to our routes in web.py:

#routes/web.py
    ...
    Get('/delete', 'WeatherController@delete'),
    ]
Enter fullscreen mode Exit fullscreen mode

Now we add the delete method in WeatherController class :

#app/http/controllers/Weather.py
     def delete(self, request: Request):
        city = City.find(request.param('id'))

        city.delete()

        return request.redirect('/')
Enter fullscreen mode Exit fullscreen mode

Now in weather.html we add the delete button :

<!--resources/templates/weather.html-->
          </article>
          <a class="button is-rounded is-danger" href="/{{ weather.id }}/delete">Remove</a>
      </div>
  {% endfor %}
Enter fullscreen mode Exit fullscreen mode

Congratulations! Our Weather App is now ready!

Conclusion

In this article, we learn how to set up the Masonite environment, and create: Controllers, Views, Models, Routes & Migrations through a simple Weather App. These are some of the basic concepts of Masonite framework that you will use with more complexity.
If you want to go further, you can :

  • Add a Authentication System to the App
  • Schedule tasks (for our API calls) periodically to update the informations on the Page
  • Deploy the App to Heroku 🚀

Thanks for reading 📖! If you have any questions, then you are welcome in the comment section
Some useful links :

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player