--- title: 【Python】【Django】お天気予報機能2(xmlから取得したデータをDBに登録) tags: Python Django WebAPI XML model author: oshiro-pooh slide: false --- ##はじめに 前回の続き(【Python】【Django】お天気予報機能1(要件整理)) https://qiita.com/oshiro-pooh/items/4c48e3ca7a39e4f137bf アプリケーション名は直球で「weather」です。 今回は市町村IDを管理しているxmlを取り込み、テーブルとして管理する為の画面(地域情報一括更新画面)をざっくり作りました。 ##modelについて xmlから取得したデータを「都道府県テーブル(親)」、「市町村テーブル(子)」で管理するために以下のようなmodelを作成しました。 ```python: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関数はここでは触れません。) ```python: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()メソッド(指定されたタグ名をルートからまとめて取り出し)がとにかく便利 * pythonはxmlの解析処理が直感的に実装できて良いですね。 実装時、参考にさせていただきました。→ https://www.sejuku.net/blog/74013#iter * 保存済みの親テーブルの主キー(外部キーとして使用)取得の実装に悪戦苦闘 * もはやview実装の9割の記憶はここにある。saveしたタイミングでテーブルインスタンスにidフィールド(自動追加される主キー)が自動追加されるのですね。。idカラム値の取得の実装方式が全く見えず、2時間弱くらいトライ&エラーを繰り返しました。。 * 完了メッセージの実装が容易 *messages(django.contrib)を利用することにより意図も簡単にメッセージを画面出力できる。このページを参考にさせていただきました。→https://qiita.com/sapuri/items/9ee0523af2a82f6de753 一つ納得いっていないのはfor文のネスト。もっとカッコよく効率良い形式で書けそうな。。 これはPythonの基礎を要学習ですね。。 ##templateについて 超シンプル。特に苦労してません。 (今回はサンプルアプリと平仄を合わせるためにmilligramフレームワークを利用してます。OSHARE。) こっちが継承元。 ```html:base.html {% load staticfiles %} Weather
{% block content %} {% endblock %}
``` こっちが本体。 ```html:update.html {% extends 'weather/base.html' %} {% block content %}

Update

{% if messages %} {% endif %}
{% csrf_token %}
{% csrf_token %}
{% endblock %} ``` ##URLConfについて 特筆点なし。(プロジェクト側本体のURLConfについては割愛。) ```python: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407364/d9dfce2a-1355-b37c-0a2b-3c72d030f710.png) UPDATEボタンを押下すると・・ ![スクリーンショット 2019-05-08 23.23.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407364/bb6adc35-0d07-5ec1-3ffc-e5295d35d223.png) テーブルにデータが登録されます!!良い感じ!! ![スクリーンショット 2019-05-08 23.24.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407364/a6a8f70f-a0c0-1836-393b-784e9a96eac0.png) ![スクリーンショット 2019-05-08 23.25.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407364/c43b6144-6786-80ee-0e55-76c36bdb27bf.png) ちなみに今回は例外処理も実装してみました。 views.pyを以下のように変更。 (参照するxmlのパスを嘘のものに変更。) ```python: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407364/04e1fe53-97da-1ed5-ff79-1960e14c24b5.png) ##NEXT データ取り込みの画面が完成したため、次回は肝心のお天気検索画面の実装に入ります。 プルダウンの変更処理をAjaxで表現するか悩ましい。。