Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
2
Help us understand the problem. What is going on with this article?
@oshiro-pooh

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

More than 1 year has passed since last update.

はじめに

前回の続き(【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で表現するか悩ましい。。

2
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
oshiro-pooh
・Java歴:約4年。 ・現在主流のスクリプト言語に全く触れたことがない状況に危機感を覚え、Pythonの勉強中。今後はDjangoを利用した簡単なWebアプリ制作に取り組む予定。 ・Python技術力(i-lLearning主催のPyhont初心者研修を受講。)

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
2
Help us understand the problem. What is going on with this article?