Through this guide, you will build a weather app to help you understand basics web app creation with Masonite.
Plans :
- A quick view on Masonite Frameowork
- Environment setup
- Adding Routing,Controllers and View
- Using the Weather API
- Display the data in our Template
- Add "Add" Button and Delete Button
- 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
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
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.
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
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'),
]
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
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>
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')
Great! Now hit http://localhost:8000 .
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
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
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')
Modify the file :
def up(self):
"""
Run the migrations.
"""
with self.schema.create('cities') as table:
table.increments('id')
table.string('name')
table.timestamps()
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
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']
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.
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
...
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})
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>
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.
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')
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 })
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'),
]
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('/')
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>
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'),
]
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('/')
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 %}
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 :
- https://docs.masoniteproject.com
- https://masonitecasts.com/
- Github Repo of the Weather App