LoginSignup
4
2

More than 3 years have passed since last update.

【Python】【Django】お天気予報機能2(xmlから取得したデータをDBに登録)

Last updated at Posted at 2019-05-08

はじめに

前回の続き(【Python】【Django】お天気予報機能1(要件整理))
https://qiita.com/oshiro-pooh/items/4c48e3ca7a39e4f137bf

アプリケーション名は直球で「weather」です。

今回は市町村IDを管理しているxmlを取り込み、テーブルとして管理する為の画面(地域情報一括更新画面)をざっくり作りました。

modelについて

xmlから取得したデータを「都道府県テーブル(親)」、「市町村テーブル(子)」で管理するために以下のようなmodelを作成しました。

models.py
from django.db import models

class Pref(models.Model):
  """
  都道府県テーブル(親)のmodel
  """

  # 都道府県名
  name = models.CharField(max_length=255)

  def __str__(self):
    return self.name

class City(models.Model):
  """
  市町村テーブル(子)のmodel
  """

  # 都道府県テーブル(親)の主キー
  pref = models.ForeignKey(Pref, on_delete=models.CASCADE)
  # 市町村名
  name = models.CharField(max_length=255)
  # 市町村ID(この値をキーとして市町村ごとの天気を取得します)
  city_id = models.CharField(max_length=6)

  def __str__(self):
    return self.name

上記をマイグレーションした結果、以下のようなテーブルができました。

mysql> describe weather_pref;
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| id    | int(11)      | NO   | PRI | NULL    | auto_increment |
| name  | varchar(255) | NO   |     | NULL    |                |
+-------+--------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

mysql> describe weather_city;
+---------+--------------+------+-----+---------+----------------+
| Field   | Type         | Null | Key | Default | Extra          |
+---------+--------------+------+-----+---------+----------------+
| id      | int(11)      | NO   | PRI | NULL    | auto_increment |
| name    | varchar(255) | NO   |     | NULL    |                |
| city_id | varchar(6)   | NO   |     | NULL    |                |
| pref_id | int(11)      | NO   | MUL | NULL    |                |
+---------+--------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

いいですね。ちゃんと親子関係ができてます。

viewについて

updateボタン押下によりテーブルを全書き換えする仕様のため、update関数を定義しました。
(index関数、next関数はここでは触れません。)

views.py
from django.shortcuts import render
from django.http import HttpResponse
from django.contrib.auth.decorators import login_required
from .models import Pref,City
import urllib.request
import urllib.parse
import xml.etree.ElementTree as ET
from django.contrib import messages

@login_required
def index(request):
  return render(request, 'weather/index.html')

@login_required
def next(request):
  return render(request, 'weather/update.html')

@login_required
def update(request):
  # テーブルを初期化
  prefs = Pref.objects.all()
  prefs.delete();
  citys = City.objects.all()
  citys.delete()

  try:
    # livedoor提供のcityIDが定義されたxmlファイルを抽出
    url = 'http://weather.livedoor.com/forecast/rss/primary_area.xml'
    req = urllib.request.Request(url)
    with urllib.request.urlopen(req) as response:
      xml_string = response.read()
    root = ET.fromstring(xml_string)
  except urllib.error.HTTPError:
    messages.error(request, '元データを取得できませんでした。システム管理者へご連絡下さい。')
    return render(request, 'weather/update.html')

  # タグを抽出し、テーブルに登録する
  for pref in root.iter('pref'):
    pref_entity = Pref(name=pref.attrib['title'])
    pref_entity.save()
    for city in pref.iter('city'):
      ciyte_entity = City(pref_id=pref_entity.id ,name=city.attrib['title'], city_id=city.attrib['id'])
      ciyte_entity.save()

  messages.success(request, '更新処理に成功しました。')
  return render(request, 'weather/update.html')

xmlの構成は都道府県ごとにprefタグが定義されており、prefタグの子要素としてcityタグが定義されてます。

以下、印象に残った点。

  • iter()メソッド(指定されたタグ名をルートからまとめて取り出し)がとにかく便利

  • 保存済みの親テーブルの主キー(外部キーとして使用)取得の実装に悪戦苦闘

    • もはやview実装の9割の記憶はここにある。saveしたタイミングでテーブルインスタンスにidフィールド(自動追加される主キー)が自動追加されるのですね。。idカラム値の取得の実装方式が全く見えず、2時間弱くらいトライ&エラーを繰り返しました。。
  • 完了メッセージの実装が容易
    *messages(django.contrib)を利用することにより意図も簡単にメッセージを画面出力できる。このページを参考にさせていただきました。→https://qiita.com/sapuri/items/9ee0523af2a82f6de753

一つ納得いっていないのはfor文のネスト。もっとカッコよく効率良い形式で書けそうな。。
これはPythonの基礎を要学習ですね。。

templateについて

超シンプル。特に苦労してません。
(今回はサンプルアプリと平仄を合わせるためにmilligramフレームワークを利用してます。OSHARE。)

こっちが継承元。

base.html
{% load staticfiles %}
<!DOCTYPE html>
<html>
    <head>
        <title>Weather</title>
        <!-- CSS And JavaScript -->
        <link rel="stylesheet"
            href="//fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
        <link rel="stylesheet"
            href="//cdn.rawgit.com/necolas/normalize.css/master/normalize.css">
        <link rel="stylesheet"
            href="//cdn.rawgit.com/milligram/milligram/master/dist/milligram.min.css">
        <link rel="stylesheet"
            href="{% static 'css/todo.css' %}">
    </head>
    <body>
        <div class="container">
            {% block content %}
            {% endblock %}
        </div>
    </body>
</html>

こっちが本体。

update.html
{% extends 'weather/base.html' %}

{% block content %}
<h1>Update</h1>
{% if messages %}
  <ul class="messages">
    {% for message in messages %}
      <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
    {% endfor %}
  </ul>
{% endif %}

<form action="{% url 'weather:update' %}" method="post">
    {% csrf_token %}
    <button>UPDATE</button>
</form>

<form action="{% url 'weather:index' %}" method="post">
    {% csrf_token %}
    <button>◀︎Back</button>
</form>

{% endblock %}

URLConfについて

特筆点なし。(プロジェクト側本体のURLConfについては割愛。)

urls.py
from django.urls import path
from . import views

app_name = 'weather'

urlpatterns = [
    path('index', views.index, name='index'),
    path('next', views.next, name='next'),
    path('update', views.update, name='update'), # ◀︎今回の実装に関連する制御文
    path('', views.index, name='index'),
]

完成!!!

こんな感じの画面イメージです。
スクリーンショット 2019-05-08 23.22.04.png

UPDATEボタンを押下すると・・
スクリーンショット 2019-05-08 23.23.28.png

テーブルにデータが登録されます!!良い感じ!!
スクリーンショット 2019-05-08 23.24.24.png
スクリーンショット 2019-05-08 23.25.01.png

ちなみに今回は例外処理も実装してみました。

views.pyを以下のように変更。
(参照するxmlのパスを嘘のものに変更。)

views.py
from django.shortcuts import render
from django.http import HttpResponse
from django.contrib.auth.decorators import login_required
from .models import Pref,City
import urllib.request
import urllib.parse
import xml.etree.ElementTree as ET
from django.contrib import messages

@login_required
def index(request):
  return render(request, 'weather/index.html')

@login_required
def next(request):
  return render(request, 'weather/update.html')

@login_required
def update(request):
  # テーブルを初期化
  prefs = Pref.objects.all()
  prefs.delete();
  citys = City.objects.all()
  citys.delete()

  try:
    # livedoor提供のcityIDが定義されたxmlファイルを抽出
    url = 'http://weather.livedoor.com/test/primary_area.xml' # ここを変更。
    req = urllib.request.Request(url)
    with urllib.request.urlopen(req) as response:
      xml_string = response.read()
    root = ET.fromstring(xml_string)
  except urllib.error.HTTPError:
    messages.error(request, '元データを取得できませんでした。システム管理者へご連絡下さい。')
    return render(request, 'weather/update.html')

  # タグを抽出し、テーブルに登録する
  for pref in root.iter('pref'):
    pref_entity = Pref(name=pref.attrib['title'])
    pref_entity.save()
    for city in pref.iter('city'):
      ciyte_entity = City(pref_id=pref_entity.id ,name=city.attrib['title'], city_id=city.attrib['id'])
      ciyte_entity.save()

  messages.success(request, '更新処理に成功しました。')
  return render(request, 'weather/update.html')

この状態でupdateボタンを押下するとエラーメッセージが表示される。
(エラーの場合、フォントを変更したいのですが、それはまたおいおい。)
スクリーンショット 2019-05-08 23.34.19.png

NEXT

データ取り込みの画面が完成したため、次回は肝心のお天気検索画面の実装に入ります。
プルダウンの変更処理をAjaxで表現するか悩ましい。。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2