DEV Community

Cover image for Build A Weather App With Django.
Kwaku Duah
Kwaku Duah

Posted on

Build A Weather App With Django.

In this tutorial, I will build a weather web application using python, django web framework and Open Weather Map API. In order to do this, I will use python requests to call the Open Weather Map API.

Current weather data can be accessed freely by creating an account at OPen Weather Map API.

Table Of Contents

  • Installation

  • Admin Dashboard

  • Creating the project

  • Calling the Open Weather API in our code.

  • Creating a template and displaying data in it.

  • Creating a form

  • Conclusion

This is simple application that will fetch weather data for varying cities. However, we will make use of a database and a form element so knowledge from this project can be applied to complicated projects in the future.

Python 3.0 and Django 4 were used to code this project. It is a beginner friendly project but people with a tint of experience will find it much easier to follow. A preview of how the application will look like has been shown below:
A preview

A preview

Installation
The installation of python-django follows the same process of installing other frameworks of python.
Note: It is very important to create a virtual environment for this project. This allows the developer the flexibility of opting for any particular version of python,django without needing to cause a system wide change.
In this article, I will be using pipenv to create the virtual environment. You can create a directory, navigate into the directory and follow the instructions.

pipenv install django

This will install the latest version of Django.
Once Django has been installed successfully, in the next step you will generate a project in django.

django-admin startproject weatherproj .

New files will be generated in the directory akin to this:

Project Directory

This is the directory with the file 'manage.py' which is essential for deployment to the server.
You can run this command to check if so far your project deploys on the local server:

python3 manage.py runserver

Successful installation
If you see this page you have installed django correctly.

Admin Dashboard
Django comes with an admin dashboard that allows for Creating, Reading, Updating and Deleting(CRUD) as a super user.
Now in order to view our admin dashboard, we will migrate our database which means django created predefined tables for default apps. TO do this, you will use the migrate command. Stop the server with CTRL + C and run:

  • python3 manage.py makemigrations

  • python3 manage.py migrate

After running these commands, django created a SQLite database for this project. You can check your directory to confirm the presence of a db.sqlite3 file.
By default, several tables are created by django in the database. However, the cons of SQLite database such as insecurity will not hinder this project in anyway.This is a minimal project with emphasis on acquisition of knowledge for test projects. Secure databases such as MySQL may however be used for projects with otherwise sensitive data.

The database comes with a user table which will be used to store any users in our application. This table has an admin user which we will allow us to access the admin dashboard.
To create an admin user, we will run the createsuperuser command.

python3 manage.py createsuperuser

Follow the instructions to input name, email address and password for your admin user.
Once you are done with that, you have to restart the server again and access the admin dashboard.

python3 manage.py runserver

then goto the link 127.0.0.1:8000/admin
The reason why we can access this endpoint is that admin is defined in the urls.py at the project level (weatherproj).
Login to the admin dashboard with your credentials. You should see a page like this:

Admin Dashboard

Groups and Users represent two models in Django. Models are representations of tables in code form. I encourage you to click around in the dashboard and exercise in caution in the deletion of users else you will have to createsuperuser again.

Creating the App

Django allows for the separation of functionality in projects in smaller chunks called apps. App is a confusing term since it generally represents entire codebases, projects.However, in Django, app refers to a specific funtionality in your project.Furthermore, a quick look at settings.py file shows a section with heading INSTALLED_APPS list.

The first on the list, django.contrib.admin is what we used for the admin user. This further elaborates the fact that pieces of functionality called apps in django is what is collectively used to build a project.

In our weatherproj, we need to create an app to handle another functionality.This is the command to create an app.

python3 manage.py startapp weatherapp

After running the commmand, a Django app called weatherapp folder is generated in the directory.
Navigate to the repository with the command
cd weatherapp
and add a new file called urls.py.
weatherproj/weatherapp/urls.py

from django.urls import path

urlpatterns = [
]

There is a similar file in the weatherproj directory. The difference between the urls.py file at the app level(weatherapp) and urls.py file at the project level(weatherproj) is that the former contains all the URLS that are relevant to the app itself.This further buttresses the point of apps in Django representing separate functionalites in the project as each app has urls.py file with relevant URL links to itself.

Navigate to the project directory(weatherproj) and in the settings.py file add the name of the app we just created(weatherapp) to the list of INSTALLED_APPS.

view

The app weatherapp will now be used by Django in the weatherproj project.

Navigate to the project level urls.py and alter it to point to our apps urls.py. To do that, we need to add a line below the existing path for admin dashboard. We need to import 'include' so we can properly point our apps urls.py.

from django.contrib import admin
from django.urls import path,include

urlpatterns = [
path('admin/', admin.site.urls),
path('', include('weatherapp.urls')),
]

The empty path string indicates we are not using any endpoint as an entry to the app. Instead, the app is allowed to handle the endpoints.Defining a string like 'weather' would have forced us to access that endpoint with ('weather/').
This would mean to access it, we would have to use that endpoint in 127.0.0.1:8000/weather/ to access anything related to the weatherapp. This is a simple project and we will not be using that approach.

Templating and Modifying the Views.py file
A template in Django is simply an HTML file that allows for extra syntax to make it dynamic than an ordinary HTML file. This dynamics include adding loops, if statements and variables.

Navigate to the weatherapp directory and enter these commands:
mkdir templates
cd templates
mkdir weatherapp

Becareful to note the addition of a new directory with same name as our app.Django combines template directories from the various apps we have and to prevent duplication by repeating the name of the app.
Navigate to the template folder, then to the weatherapp and create an index.html. This will the template and it is named index.html because it will be at root level and be the topmost element. This is the HTML to use for the template:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>What is the current weather?</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.2/css/bulma.css" />
</head>
<body>
<section class="hero is-primary">
<div class="hero-body">
<div class="container">
<h1 class="title">
What's the weather like?
</h1>
</div>
</div>
</section>
<section class="section">
<div class="container">
<div class="columns">
<div class="column is-offset-4 is-4">
<form method="POST">
{% csrf_token %}
<div class="field has-addons">
<div class="control is-expanded">
{{ form.name }}
</div>
<div class="control">
<button class="button is-info">
Add New City
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<div class="columns">
<div class="column is-offset-4 is-4">
{% for weather in weather_data %}
<div class="box">
<article class="media">
<div class="media-left">
<figure class="image is-50x50">
<img src="http://openweathermap.org/img/w/{{ weather.icon }}.png" alt="Image">
</figure>
</div>
<div class="media-content">
<div class="content">
<p>
<span class="title">{{ weather.city }}</span>
<br>
<span class="subtitle">{{ weather.temperature }}° F</span>
<br> {{ weather.description }}
</p>
</div>
</div>
</article>
</div>
{% endfor %}
</div>
</div>
</div>
</section>
<footer class="footer">
<h2>Made by KDUAH of <a href="kdwebdeveloper.vercel.app">PCkingpin INC</a></h2>
<p>Contact Developer through duah229@gmail.com</p>
</footer>
</body>
</html>

With the creation of the template successful, we will create a view and with URL combination render this to be displayed in the app.

Views in Django are either functions or classes. In this tutorial, we will be creating a simple function in our views.py file.

from django.shortcuts import render

def index(request):
return render(request, 'weatherapp/index.html')

Add the URL that will send the request to this view. At the app level, update the urlpatterns list.

from django.urls import path
from . import views
urlpatterns = [
path('',views.index),
]

This allows us to reference the view we just created.Django will route to the view function we just created.

Using the Open Weather API
Goto Open Weather Map API Open Weather Map API and sign up.
Confirm your email and click on your username, generate an API key.

openweather api

The endpoint we will use is written below and you can insert your generated API key to access the data. Note, it may take a few minutes for your API to become active.

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

After that, let us install python requests which is what we will use to access the data in our app with this command.

pipenv install requests

Let us update the views.py to send a request to the URL we have.

from django.shortcuts import render
import requests

def index(request):
url = 'http://api.openweathermap.org/data/2.5/weather?q={}&units=imperial&appid=YOUR_APP_KEY'
city = 'Las Vegas'
city_weather = requests.get(url.format(city)).json() #request the API data and convert the JSON to Python data types
return render(request, 'weather/index.html')

With those lines, we have added the URL that we will send requests to. We will make the part of the city a placholder for when users add their own cities.

Finally, we will send the request to the URL using the city and get the JSON representation of that city. If we print that to the console we can see the same data we saw when we put the URL in our address bar.

If you start your server again and reload the page, you’ll see the data get printed to your console.

Creating a Weather App in Django Using Python Requests

Displaying the Data in the Template
Next, we need to pass the data to the template so it can be displayed to the user.

Let us create a dictionary to hold all of the data we need. Of the data returned to us, we need temp, description, and icon.

def index(request):
weather = {
'city' : city,
'temperature' : city_weather['main']['temp'],
'description' : city_weather['weather'][0]['description'],
'icon' : city_weather['weather'][0]['icon']
}
return render(request, 'weather/index.html')

Now that we all the information we want, we can pass that to the template. To pass it to the template, we’ll create a variable called context. This will be a dictionary that allows us to use its values inside of the template.

def index(request):
context = {'weather' : weather}
return render(request, 'weather/index.html', context)

And then in render, we will add the context as the third argument.

With the weather data added inside of context, lets go to the template to add the data.

Inside of the template, all we need to do is modify the HTML to use variables instead of the values I typed in. Variables will use

{{ }}

tags, and they will reference anything inside of your context dictionary.

Note that Django converts dictionary keys so you can only access them using dot notation. For example, weather.city will give us the city name. We don’t use weather['city'] like we would in Python.

And then in render, we will add the context as the third argument.

With the weather data added inside of context, lets go to the template to add the data.

Inside of the template, all we need to do is modify the HTML to use variables instead of the values I typed in. Variables will use

{{ }}

tags, and they will reference anything inside of your context dictionary.

Note that Django converts dictionary keys so you can only access them using dot notation. For example, weather.city will give us the city name. We don’t use weather['city'] like we would in Python.

Find the box div, and update it to this:

<div class="box">
<article class="media">
<div class="media-left">
<figure class="image is-50x50">
<img src="http://openweathermap.org/img/w/{{ weather.icon }}.png" alt="Image">
</figure>
</div>
<div class="media-content">
<div class="content">
<p>
<span class="title">{{ weather.city }}</span>
<br>
<span class="subtitle">{{ weather.temperature }}° F</span>
<br> {{ weather.description }}
</p>
</div>
</div>
</article>
</div>
`

With all the variables replaced, we should now see the current weather for our city.

Creating a Weather App in Django Using Python Requests

Great! Now we can see the weather for one city, but we had to hard code the city. What we want to do now is pull from the database and show the cities that are in our database.

To do that, we will create a table in our database to hold the cities that we want to know the weather for. We will create a model for this.

Go to the models.py in your weather app, and add the following:

from django.db import models

class City(models.Model):
name = models.CharField(max_length=25)

```def _str_(self): #show the actual city name on the dashboard
    return self.name```

```class Meta: #show the plural of city as cities instead of citys
    verbose_name_plural = 'cities'```
Enter fullscreen mode Exit fullscreen mode

This will create a table in our database that will have a column called name, which is the name of the city. This city will be a charfield, which is just a string.

To get these changes in the database, we have to run makemigrations to generate the code to update the database and migrate to apply those changes. Here are the commands

python manage.py makemigrations
python manage.py migrate

We need to make it to where we can see this model on our admin dashboard. To do that, we need to register it in our admin.py file.

from django.contrib import admin
from .models import City

admin.site.register(City)

You’ll see the city as an option on the admin dashboard.

Creating a Weather App in Django Using Python Requests

We can then go into the admin dashboard and add some cities. I will start with three: London, Tokyo, and Las Vegas.

Creating a Weather App

With the entries in the database, we need to query these entries in our view. Start by importing the City model and then querying that model for all objects.

from django.shortcuts import render
import requests
from .models import City
`

`def index(request):
url = 'http://api.openweathermap.org/data/2.5/weather?q={}&units=imperial&appid=YOUR_APP_KEY'
cities = City.objects.all()`

Since we have the list of cities, we want to loop over them and get the weather for each one and add it to a list that will eventually be passed to the template.

This will just a variation of what we did in the first case. The other difference is we are looping and appending each dictionary to a list. We will remove the original city variable in favor a city variable in the loop.

`def index(request):
weather_data = []`

`for city in cities:`

    ```city_weather = requests.get(url.format(city)).json() #request the API data and convert the JSON to Python data types```

    ```weather = {
        'city' : city,
        'temperature' : city_weather['main']['temp'],
        'description' : city_weather['weather'][0]['description'],
        'icon' : city_weather['weather'][0]['icon']
    }```

    ```weather_data.append(weather)```

```context = {'weather_data' : weather_data}```
Enter fullscreen mode Exit fullscreen mode

Cool, so we have the data. Now let’s update the context to pass this list instead of a single dictionary.

`context = {'weather_data' : weather_data}`
Enter fullscreen mode Exit fullscreen mode

Next, inside of the template, we need to loop over this list and generate the HTML for each city in the list. To do this, we can put a for loop around the HTML that generates a single box for the city.

`<div class="column is-offset-4 is-4">
{% for weather in weather_data %}
<div class="box">
<article class="media">
<div class="media-left">
<figure class="image is-50x50">
<img src="http://openweathermap.org/img/w/{{ weather.icon }}.png" alt="Image">
</figure>
</div>
<div class="media-content">
<div class="content">
<p>
<span class="title">{{ weather.city }}</span>
<br>
<span class="subtitle">{{ weather.temperature }}° F</span>
<br> {{ weather.description }}
</p>
</div>
</div>
</article>
</div>
{% endfor %}
</div>`

Awesome! Now we can see the data for all the cities we have in the database.

Creating the Form
The last thing we want to do is allow the user to add a city directly in the form.

To do that, we need to create a form. We could create the form directly, but since our form will have exactly the same field as our model, we can use a ModelForm.

Create a new file called forms.py.

`from django.forms import ModelForm, TextInput
from . models import City`

`class CityForm(ModelForm):
class Meta:
model = City
fields = ['name']
widgets = {
'name': TextInput(attrs={'class' : 'input', 'placeholder' : 'City Name'}),
}`

To do that, lets update the index video to create the form. We will replace the old city variable at the same time since we no longer need it. We also need to update the context so the form gets passed to the template.

`def index(request):
form = CityForm()`

```weather_data = []```
```context = {'weather_data' : weather_data, 'form' : form}```
Enter fullscreen mode Exit fullscreen mode

Now in the template, let’s update the form section to use the form from our view and a csrf_token, which is necessary for POST requests in Django.

`<form method="POST">
{% csrf_token %}
<div class="field has-addons">
<div class="control is-expanded">
{{ form.name }}
</div>
<div class="control">
<button class="button is-info">
Add City
</button>
</div>
</div>
</form>`

With the form in our HTML working, we now need to handle the form data as it comes in. For that, we’ll create an if block checking for a POST request. We need to add the check for the type of request before we start grabbing the weather data so we immediately get the data for the city we add.

`def index(request):
cities = City.objects.all() #return all the cities in the database`

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

```if request.method == 'POST': # only true if form is submitted
    form = CityForm(request.POST) # add actual request data to form for processing
    form.save()```

```form = CityForm()```
Enter fullscreen mode Exit fullscreen mode

By passing request.POST, we will be able to validate the form data.

Now you should be able to type in the name of a city, click add, and see it show up. I’ll add Miami as the next city.

Creating a Weather App in Django

When we drop out of the if block, the form will be recreated so we can add another city if we choose. The rest of the code will behave in the same way.

VSCODE DEMO

Conclusion

In this article, we had to work with various parts of Django to get this working: views, models, forms, and templates. We also had to use the Python library requests to get the actual weather data. So even though the app is simple, you will use many of the same concepts in apps with more complexity.

Here is a link to the project in github:
Github
Feel free to clone and modify.

Top comments (0)