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