Building a Weather App With Masonite Framework - Basics Concepts
Tags: python, masonite, beginners, heroku
Published: 2019-12-11 21:14:41

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

Plans : 
<ol>
<li>A quick view on Masonite Frameowork</li>
<li>Environment setup</li>
<li>Adding Routing,Controllers and View</li>
<li>Using the Weather API</li>
<li>Display the data in our Template</li>
<li>Add "Add" Button and Delete Button</li>
<li>Conclusion</li>
</ol>

<h2>Quick View on Masonite Framework</h2>
<b>Masonite</b> 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.

<h2>Environment Setup</h2>
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.
```bash
mkdir Masonite-Weather 
virtualenv -p python3 env 
source env/bin/activate
```
The virtual environment ready, install the masonite-cli to enjoy the power of the craft command.
<code>pip3 install masonite-cli</code>
Great! Now, your craft command should be ready!
<code>craft</code>
And now, let's create our project.
<code>craft new Weather</code>

Application Created Successfully! 
Now, go into your project to install masonite dependencies.
```bash
cd Weather
craft install
```
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 <code>craft serve</code> to run the server and go the localhost/8000.
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/un1iowkbwhalvdd832cl.png)

Before start coding, I must explain to you the architecture of every app based on Masonite.
Masonite is called <a href="https://www.tutorialspoint.com/mvc_framework/mvc_framework_introduction.htm">MVC framework</a>. The <b>MVC architecture or Model, View & Controller</b> is popular because it isolates the application logic from the user interface and supports separation of concerns.
<ul> 
<li>The Model stores data in the database. It's the shape of the data and business logic.</li>
<li>The view is the UI. It basically consists of HTML/CSS/Javascript and it renders the data returned by the Controller.</li>
<li>The Controller connects View and Model adequately. It handles any requests coming from the view, treats it and involves the adequate model if necessary.</li>
</ul>

Here is the tree of the project.
```bash
.
├── 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

```
<h2>Adding Routes,Controllers and View</h2>

<h3>Routes</h3>

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 <code>routes/web.py</code>.
```python
#routes/web.py
"""Web Routes."""

from masonite.routes import Get, Post

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

    #Weather App
    Get('/', 'WeatherController@show'),
]
```
<h3>Controllers</h3>

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 <code>app/http/controllers/</code>.
Actually, we don't need to write code to create one. 
Just use the craft command.
<code>craft controller Weather</code>
```python
#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
```

<h3>Return the view</h3>

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

I provide an HTML template, we are going to use.
```html
<!--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>
```
Now, we will return this view in the <code>show()</code> method of WeatherController.

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

        return view.render('weather')
```
Great! Now hit http://localhost:8000 .
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/4w409lj2y1gdghpcli33.png)

<h3>Setup the .env</h3>
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

```
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
```
<h3>Create migrations for City</h3>
Migrations in Masonite helps us create tables.
Run : 
<code>craft migration create_cities_table --create cities</code>
Now go to <code>database/migrations/2019_12_09_232649_create_cities_table.py</code>.(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:
```python
#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')
```
Modify the file : 

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

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

<h3>Model</h3>
Not like others web framework in Python, Models in Masonite take shape of our tables.
<code>craft model City</code>
A file will be create in <code>app/</code>
```python 
#app/City.py
"""City Model."""

from config.database import Model

class City(Model):
    """City Model."""
    pass
```
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.
```python
#app/City.py
class City(Model):
    """City Model."""
    __table__ = "cities"

    __fillable__ = ['name'] 
```
<h2>Using the Weather API</h2>

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](https://thepracticaldev.s3.amazonaws.com/i/1sb4soj9kjfjp7r0wgla.png) 
The API KEY differs from one user to another. 

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

Now re-open <code>WeatherController.py</code> and import requests module and City Model.
```python
#app/http/controllers/WeatherController.py
import requests
from app.City import City
...
```
and in our show method, add your APY_KEY.
```python
#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}) 
```
Modify our template : 
```html
<!--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>
```
<blockquote>Masonite uses Jinja2 templating so if you don't understand this templating, be sure to read their documentation.</blockquote>
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](https://thepracticaldev.s3.amazonaws.com/i/03t83wrtwcqs9qq7aykj.png)


<h3>Use the Interactive Console</h3>
Masonite provides an interactive console to quickly test your app or make some diagnostics.
<code>craft tinker</>
We will use this console to add data in cities table.
```bash
from app.City import City
City.create(name='London')
City.create(name='Paris')
```
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.

<h3>Add a loop</h3>
We are going to modify the <code>show()</code> method.
```python
#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 })
```
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.

<h2>Add Button and Delete Button</h2>
To make the add button functional, we need first to create a URL for a <code>store()</code> 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.
<b>Step 1 :</b>
We create the URL in web.py : 
```python
#routes/web.py

#Weather App
    Get('/', 'WeatherController@show'),
    Post('/add','WeatherController@store'),
]
```
<b>Step 2 :</b>
```python
#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('/') 
```
Now you should modify the forms in the html.
```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>
```

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

<h3>Delete Button</h3>
It would be nice to delete a City if we want.
Add a new URL to our routes in web.py:
```python
#routes/web.py
    ...
    Get('/delete', 'WeatherController@delete'),
    ]
```
Now we add the delete method in WeatherController class :
```python
#app/http/controllers/Weather.py
     def delete(self, request: Request):
        city = City.find(request.param('id'))

        city.delete()

        return request.redirect('/')
```
Now in <code>weather.html</code> we add the delete button : 
```html
<!--resources/templates/weather.html-->
          </article>
          <a class="button is-rounded is-danger" href="/{{ weather.id }}/delete">Remove</a>
      </div>
  {% endfor %}
```
Congratulations! Our Weather App is now ready!

<h2>Conclusion</h2>
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 :
<ul>
<li>Add a Authentication System to the App</li>
<li>Schedule tasks (for our API calls) periodically to update the informations on the Page</li>
<li><a href="https://dev.to/masonite/deploying-a-masonite-application-to-heroku-45jp">Deploy the App to Heroku 🚀</a></li>
</ul>
<em>Thanks for reading 📖! If you have any questions, then you are welcome in the comment section</em>
<b>Some useful links :</b>
<ul>
<li>https://docs.masoniteproject.com</li>
<li>https://masonitecasts.com/</li>
<li><a href="https://github.com/Kolawole39/Masonite-Weather-App-tutorial/">Github Repo of the Weather App</a></li>
</ul>