如何在 Django 中构建天气应用程序

介绍

在本文中,您将构建一个Django应用程序,显示各个城市的当前天气。

包含伦敦、东京、拉斯维加斯和迈阿密天气的天气应用主页

当前天气数据将由Open Weather Map API 提供

您将使用数据库并创建一个表单。您在本教程中学到的内容以后可以应用于更复杂的项目。

先决条件

  • 该项目需要安装 Python,您应该可以参考本系列教程以获取更多信息。

本文中的代码是用 Python 3 和 Django 3.0 编写的,因此要学习本教程,您应该对两者都有些熟悉。

步骤 1 — 设置项目

安装 Django 就像安装任何其他 Python 库:

  • 您可以启动一个虚拟环境并运行pip以安装 Django。
  • 或者您可以创建一个项目目录,运行pipenv,然后激活pipenvshell。

这两种方法都有效,但对于本文,您将使用pipenv.

注意:对于替代的 Django 安装方法,您应该可以参考本教程系列以获取更多信息。

官方文档提供了安装说明pipenv自制或Linuxbrew。也可以pipenv使用pip.

在您的终端中,创建一个环境目录:

  • mkdir the_weather_env

接下来,导航到环境目录:

  • cd the_weather_env

然后,使用 pipenv 安装 Django:

  • pipenv install django

这将为您安装最新版本的 Django。在撰写本文时,Django 的版本为 3.0.5。

花点时间也使用 pipenv 安装您稍后将使用Requests库:

  • pipenv install requests

通过在终端中运行以下命令来激活项目的 virtualenv:

  • pipenv shell

这将产生一个新的 shell 子进程。

第 2 步 – 启动 Django 项目

安装 Django 后,如果还没有,请创建并导航到此项目的目录。

您可以运行startprojectDjango 给您命令来生成项目。

  • django-admin startproject the_weather

Django 应该在您的目录中创建了一些新文件。

让我们尝试启动您的开发服务器。为此,请导航到终端中的新目录:

  • cd the_weather

接下来,使用在终端中manage.py运行runserver命令:

  • python manage.py runserver

如果您检查终端,您应该会看到您的应用程序的 URL。默认情况下它应该是127.0.0.1:8000

描述开发服务器启动和运行的终端窗口

现在,打开您的 Web 浏览器并访问该 URL:

描绘 Django 开发服务器祝贺页面的浏览器窗口

如果您看到“祝贺”页面,您就知道您已经正确设置了 Django。

第 3 步 — 登录管理仪表板

接下来,您将登录到 Django 为您提供的管理仪表板。为此,首先,您必须迁移数据库,这意味着 Django 将创建默认应用程序所需的预定义表。

首先,您需要停止服务器。根据您的环境,这可以通过键盘命令CONTROL+CCTRL+C.

接下来,migrate在终端中运行命令:

  • python manage.py migrate

通过运行该命令,Django 为您创建了一个 SQLite 数据库,即设置中的默认数据库,并且已向该数据库添加了多个表。如果您db.sqlite3在项目目录中看到一个新文件,您就会知道数据库是否已创建

Django 为您提供的表之一是用户表,它将用于存储您的应用程序中的任何用户。您正在构建的应用程序不需要任何用户,但拥有管理员用户将允许您访问管理仪表板。

要创建管理员用户,您将createsuperuser在终端中运行以下命令:

  • python manage.py createsuperuser

按照说明为您的管理员用户提供用户名、电子邮件地址和密码。完成后,您需要在终端中再次启动服务器:

  • python manage.py runserver

在您的 Web 浏览器中,通过转至 访问管理仪表板127.0.0.1:8000/admin

您能够进入此页面的原因admin因为在您的urls.py.

如果您使用刚刚创建的用户名和密码登录,您将看到 Django 管理仪表板:

描述 Django 管理仪表板的浏览器窗口

用户代表 Django 允许您访问的两个模型。模型只是数据库中表的代码表示。即使 Django 创建了更多的表,也不需要直接访问其余的表,因此没有创建模型。

如果您单击“用户”,您应该会看到有关用户表的更多详细信息,并且您应该会看到您创建的用户。通过单击仪表板中的不同链接花点时间探索可用的内容。请注意不要删除您的用户,否则,您将不得不createsuperuser再次运行

让我们暂时离开管理仪表板并处理代码。你需要在你的项目中为你的天气应用程序创建一个应用程序。

第 4 步 – 创建应用程序

在 Django 中,您可以使用apps来分离项目中的功能对于 Django, app 指的是项目中的特定功能。

例如,如果您查看settings.py文件,您将看到INSTALLED_APPS列表。

第一个安装的应用程序 – django.contrib.admin– 是您刚刚使用的。它处理所有管理功能,仅此而已。默认情况下,您项目中的另一个应用程序是django.contrib.auth,它允许您登录到您的管理仪表板。

就您而言,您需要创建一个新应用程序来处理与显示天气相关的所有事情。

首先,您需要停止服务器。

接下来,startapp在终端中运行命令:

  • python manage.py startapp weather

通过运行startapp,Django 为您的项目添加了一个新目录和更多文件。

生成最新的文件后,让我们urls.py在您的weather应用程序目录中创建一个名为的新文件

the_weather/weather/urls.py
from django.urls import path

urlpatterns = [
]

此文件类似于urls.pythe_weather目录中的 。不同之处在于该urls.py文件包含与应用程序本身相关的所有 URL。

您尚未指定 URL,但您可以设置项目以识别您的应用程序并将特定于您的应用程序的任何 URL 路由到应用程序urls.py文件。

首先,转到INSTALLED_APPS列表中settings.py并添加weather到列表中:

the_weather/the_weather/settings.py
...

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'weather',
]

...

这让 Django 知道您想weather在您的项目中使用该应用程序。通过这样做,Django 将知道在哪里寻找迁移和 URL。

接下来,您需要修改原始文件urls.py以指向您的应用程序urls.py文件。为此,您在管理仪表板的现有路径下添加一行。您还需要导入,include以便您可以指向您的应用程序urls.py文件。

the_weather/the_weather/urls.py
from django.contrib import admin
from django.urls import path, include

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

空字符串意味着您不需要使用端点作为应用程序的入口点。相反,您将让应用程序处理任何特定端点。您可以输入类似path('weather/', ...),这意味着您必须输入127.0.0.1:8000/weather/才能获取与您的天气应用程序相关的任何内容。但由于您的项目很简单,因此您不会在这里这样做。

第 5 步 – 添加模板和视图

现在,您需要将模板添加到您的项目中。

一个模板在Django是一个HTML文件,允许额外的语法,使模板动态。您将能够处理添加变量、if语句和循环等功能。

在您的终端中,导航到weather应用程序目录:

  • cd weather

接下来,制作templates目录:

  • mkdir templates

并导航到它:

  • cd templates

您还将创建另一个与您的应用同名的目录。这是因为 Django 结合了您拥有的各种应用程序中的所有模板目录。为了防止文件名重复,您可以使用应用程序的名称来防止重复:

  • mkdir weather

在此weather目录中,创建一个名为index.html. 这将是您的主要模板。

这是您将用于模板的 HTML:

the_weather/weather/templates/weather/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>What's the weather like?</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">
                        <div class="field has-addons">
                            <div class="control is-expanded">
                                <input class="input" type="text" placeholder="City Name">
                            </div>
                            <div class="control">
                                <button class="button is-info">
                                    Add 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">
                    <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">Las Vegas</span>
                                        <br>
                                        <span class="subtitle">29° F</span>
                                        <br> thunderstorm with heavy rain
                                    </p>
                                </div>
                            </div>
                        </article>
                    </div>
                </div>
            </div>
        </div>
    </section>
    <footer class="footer">
    </footer>
</body>
</html>

注意:在幕后,我们使用Bulma来处理样式和布局。要更深入地了解 Bulma 和 CSS 框架,请考虑阅读了解 Bulma:我目前最喜欢的 CSS 框架

现在您已经创建了模板,让我们创建一个视图和 URL 组合,以便您可以在您的应用程序中实际看到它。

Django 中的视图是函数或类。在这种情况下,由于您正在创建一个简单的视图,因此您将创建一个函数。将此函数添加到您的views.py文件中:

the_weather/weather/views.py
from django.shortcuts import render

def index(request):
    return render(request, 'weather/index.html') #returns the index.html template

您正在命名您的视图,index因为它将位于您的应用程序的索引处,即根 URL。要渲染模板,您返回request,这是render函数所必需的,以及要渲染的模板文件的名称,在本例中为weather/index.html.

让我们添加将请求发送到此视图的 URL。urls.py应用程序文件中,更新urlpatterns列表。

the_weather/weather/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index),  #the path for our index view
]

这允许您引用刚刚创建的视图。

Django 将匹配任何没有端点的 URL,并将其路由到您创建的视图函数。

现在,使用您的终端返回到您的项目根目录 ( the_weather)。

接下来,启动服务器:

  • python manage.py runserver

然后,打开您的网络浏览器并127.0.0.1:8000再次访问

使用硬编码值描绘拉斯维加斯的浏览器窗口

您将观察到index.html文件呈现的 HTML 有一个输入来添加一个城市。并且显示了拉斯维加斯的硬编码天气。但是,此时的表单不起作用,天气只是一个占位符。接下来让我们继续努力。

第 6 步 – 使用天气 API

您现在要做的是注册Open Weather Map API这将允许您获取添加到应用程序的任何城市的实时天气。

转到该站点,创建一个帐户,然后转到其仪表板上的 API 密钥。您可以使用他们提供的默认密钥,也可以创建新的 API 密钥。此密钥将允许您使用 API 来获取天气。

打开天气地图仪表板

注意:对 API 密钥保密以防止它们被其他方使用很重要。您将希望避免将 API 密钥提交到 GitHub 等远程存储库。

您将使用的一个端点如下所示,因此您可以通过使用 API 密钥修改以下 URL 并导航到浏览器中的 URL 来查看返回的数据:

http://api.openweathermap.org/data/2.5/weather?q=las%20vegas&units=imperial&appid=YOUR_APP_KEY

您的 API 密钥可能需要几分钟才能变为活动状态,因此如果一开始不起作用,请在几分钟后重试。

您应该会看到 JSON 格式的响应,其中包含坐标、温度和天气条件。

有了这个,让我们添加一个请求以将数据导入您的应用程序。

让我们更新您的index视图以向您拥有的 URL 发送请求。

the_weather/weather/views.py
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') #returns the index.html template

添加import requestsurlcity,和city_weather

使用这些新行,您将添加要向其发送请求的 URL。

请注意,此 URL 与您之前在浏览器中测试的 URL 略有不同。城市不是 URL 的一部分,它已移出到一个变量中。此模式将允许您将来替换其他城市名称。

现在,您将城市设置为“拉斯维加斯”,但稍后将设置为数据库中的城市。

最后,您将使用城市向 URL 发送请求并获取该城市的 JSON 表示。

如果你print在控制台上看到你在地址栏中输入 URL 时看到的相同数据:

the_weather/weather/views.py
...
def index(request):
    ...
    print(city_weather) #temporarily view output

    return render(request, 'weather/index.html') #returns the index.html template

如果您在 Web 浏览器中重新加载页面,您将看到数据打印到您的控制台。

在验证为真后,您可以print从代码中删除该语句。

步骤 7 — 在模板中显示数据

接下来,您需要将数据传递给模板,以便向用户显示。

让我们创建一个字典来保存您需要的所有数据。返回的数据,您需要tempdescription以及icon

the_weather/weather/views.py
...
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') #returns the index.html template

现在您拥有了所需的所有信息,您可以将其传递给模板。要将其传递给模板,您将创建一个名为context. 这将是一个字典,允许您在模板中使用其值。

然后在 中render,您将添加context作为第三个参数:

the_weather/weather/views.py
...
def index(request):
    ...
    context = {'weather' : weather}

    return render(request, 'weather/index.html', context) #returns the index.html template

里面添加了天气数据context,接下来我们到模板中添加数据。

index.html模板内部,您需要做的就是修改 HTML 以使用变量而不是硬编码值。变量将使用{{ }}标签,它们将引用上下文字典中的任何内容。

请注意,Django 会转换字典键,因此您只能使用点表示法访问它们例如,weather.city会给你城市名称。weather['city']不像在 Python 中那样使用

找到“box” <div>,并更新它以使用变量:

the_weather/weather/templates/weather/index.html
...
<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>
...

替换所有变量后,您现在将看到您所在城市的当前天气。

描绘拉斯维加斯动态天气的浏览器窗口

但是,该城市目前仍然是硬编码的。接下来您要做的是从数据库中提取并显示数据库中的城市。

为此,您将在数据库中创建一个表来保存您想要了解天气的城市。您将为此创建一个模型。

转到应用程序中models.py文件weather,并添加以下内容:

the_weather/weather/models.py
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'

这将在您的数据库中创建一个表name,其中有一个名为 的列,它是城市的名称。这个城市将是一个charfield,它只是一个字符串。

要在数据库中获取这些更改,您必须运行makemigrations以生成代码以更新数据库并迁移以应用这些更改。

让我们停止服务器,然后在终端中执行迁移:

  • python manage.py makemigrations

并迁移:

  • python manage.py migrate

您需要在管理仪表板上看到此模型。为此,您需要将其注册到您的admin.py文件中。

the_weather/weather/admin.py
from django.contrib import admin
from .models import City

admin.site.register(City)

接下来,重新启动服务器并在 Web 浏览器中查看管理仪表板。

在管理仪表板上描绘城市的浏览器窗口

City 现在是一个选择。

然后您可以进入管理仪表板并添加一些城市。例如:“伦敦”、“东京”和“拉斯维加斯”。

浏览器窗口描绘了添加到数据库的三个城市

对于数据库中的条目,您需要在视图中查询这些条目。首先导入City模型,然后查询所有对象的模型:

the_weather/weather/views.py
from django.shortcuts import render
import requests
from .models import City

接着,更新requestcities

the_weather/weather/views.py
...
def index(request):
    url = 'http://api.openweathermap.org/data/2.5/weather?q={}&units=imperial&appid=YOUR_APP_KEY'

    cities = City.objects.all() #return all the cities in the database
    ...

由于您拥有城市列表,您将希望遍历它们并获取每个城市的天气并将其添加到最终将传递给模板的列表中。

这只是您在前面步骤中所做的事情的变体。不同之处在于您正在循环并将每个字典附加到列表中。

首先,您将创建一个weather_data列表来保存weather每个city.

然后,替换原始city变量并在 上循环cities

接下来,应将weather每个响应city附加到weather_data.

您还需要更新context以传递此列表而不是单个字典。

此时,您views.py应该类似于:

the_weather/weather/views.py
...
def index(request):
    ...
    cities = City.objects.all() #return all the cities in the database

    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) #add the data for the current city into our list

    context = {'weather_data' : weather_data}

    return render(request, 'weather/index.html', context) #returns the index.html template

接下来,在index.html模板内部,您需要遍历此列表并为city列表中的每个生成 HTML 为此,您可以for在 HTML 周围放置一个循环,<div>city.

the_weather/weather/templates/weather/index.html
...
<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>
...

现在,您可以检查数据库中所有城市的数据。

第 8 步 – 创建表单

最后一步是允许用户直接通过表单添加城市。

为此,您需要创建一个表单。您可以手动创建表单,但由于您的表单将具有与模型完全相同的字段,因此您可以使用ModelForm.

创建一个forms.py在您的weather应用中调用的新文件

the_weather/weather/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'}),
        } #updates the input class to have the correct Bulma class and placeholder

要显示表单,您需要在视图中创建它并将其传递给模板。

为此,让我们更新index.html以创建表单。您还需要更新 ,context以便将表单传递给模板。

the_weather/weather/views.py
...
from .forms import CityForm

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

    weather_data = []
    ...
    context = {'weather_data' : weather_data, 'form' : form}

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

the_weather/weather/templates/weather/index.html
...
<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>
...

Note: CSRF stands for Cross-Site Request Forgery. This is a security measure to ensure form data is being submitted from an expected trusted source.

With the form in your HTML working, you will need to handle the form data as it comes in. For that, you will create an if block checking for a POST request. You need to add the check for the type of request before you start grabbing the weather data so you immediately get the data for the city you add.

the_weather/weather/views.py
...
def index(request):
    url = 'http://api.openweathermap.org/data/2.5/weather?q={}&units=imperial&appid=YOUR_APP_KEY'

    cities = City.objects.all() #return all the cities in the database

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

    form = CityForm()
    ...

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

现在,您应该能够输入城市名称,单击添加,然后看到它显示出来。

例如,添加“迈阿密”作为下一个城市:

描绘伦敦、东京、拉斯维加斯和迈阿密天气的浏览器窗口

当您退出if街区时,表格将重新创建,以便您可以选择添加另一个城市。其余代码的行为方式相同。

您现在可以在您的应用程序中跟踪多个城市的天气。

结论

在本文中,您必须使用 Django 的各个部分才能使其正常工作:视图、模型、表单和模板。您还必须使用 Python 库requests来获取实际的天气数据。因此,即使应用程序很简单,您也会在更复杂的应用程序中使用许多相同的概念。

觉得文章有用?

点个广告表达一下你的爱意吧 !😁