31年目のRealize

プログラミング、イラスト、家族の事を書いたりするブログにする予定

pythonでDB操作

DBMSに依存しないDB操作プログラミング

SQLiteMySQL, PostgreSQL, Oracle Databaseなど色々DBMSがあり、それぞれプログラムから使用する際はDBMS毎に異なったコーディングが必要。覚えきれない…。

そこで、オブジェクト指向マッピング(ORM)という技法を使えば、DBMS毎にコードを変える必要が無くなるのです!

pythonにはSQLAlchemyというパッケージがあり、それで実現できます。やったね!

ということで、これは是非身につけたい。

まずはSQLite用のコーディング

SQLite用に書くとこんな感じなります。 sqlite3pythonに標準でインストールされていて、軽くて使いやすい。らしい。

import sqlite3

# DBのコネクションを生成
# :memory:を指定すると、DBがメモリ上に保存され、接続が終了すると消える。DBファイルに出力するときはファイル名を指定する。
conn = sqlite3.connect(':memory:')

# DBを操作するためのカーソルを生成
curs = conn.cursor()

# executeでSQLを実行
curs.execute(
    'CREATE TABLE persons(id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING)')
curs.execute(
    'INSERT INTO persons(name) values("Mike")'
)
curs.execute(
    'INSERT INTO persons(name) values("Nancy")'
)
conn.commit()

curs.execute('SELECT * FROM persons')

# クエリ結果をコンソールに表示
print(curs.fetchall())

conn.close()

コンソールの表示結果は↓

[(1, 'Mike'), (2, 'Nancy')]

これがMySQLPostgreSQLになるとまた別のパッケージをインストールして、そのパッケージの使い方に合わせたコーディングが必要。

SQLAlchemyを使ってみる

SQLAlchemyを使ってSQLiteのDBを操作するコーディングはこうなる。

import sqlalchemy
import sqlalchemy.ext.declarative
import sqlalchemy.orm

# データベース接続のためのEngineオブジェクトを取得
engine = sqlalchemy.create_engine('sqlite:///:memory:')

# Baseにオブジェクトを生成
Base = sqlalchemy.ext.declarative.declarative_base()

# Baseに入ったオブジェクトのクラスを継承して、テーブルの定義クラスを作成
class Person(Base):
    __tablename__ = 'perons'
    
    # テーブルのカラムを定義
    id = sqlalchemy.Column(
        sqlalchemy.Integer, primary_key=True, autoincrement=True)
    name = sqlalchemy.Column(sqlalchemy.String(14))

# 使用するデータベースのEngineをBaseに設定
Base.metadata.create_all(engine)

# DBにアクセスするため、Engineのセッションを作成
Session = sqlalchemy.orm.sessionmaker(bind=engine)

session = Session()

# DBに追加するオブジェクトを作成し、セッションに追加
person1 = Person(name='Mike')
person2 = Person(name='Nancy')
session.add(person1)
session.add(person2)

session.commit()

# クエリの結果全てをリストで取得
persons = session.query(Person).all()

# リストの中身をコンソールに表示
for person in persons:
    print(person.id, person.name)

出力結果は↓

1 Mike
2 Nancy

UPDATEする時は次のように記述

# データを更新
# まずは更新対象のデータの1行目を取得
person3 = session.query(Person).filter_by(name='Mike').first()

# データを書き換え、セッションに追加
person3.name = 'Michel'
session.add(person3)
session.commit()

ここで色々と疑問

ひとまず、使い方の流れを理解した。…が、ちょっといくつか分からないコードがある。 基礎的な話です。

分からないポイント

pythonのクラスとかその辺がまだ身に付いていない感じ。

1つ目

import sqlalchemy
engine = sqlalchemy.create_engine('sqlite:///:memory:')

sqlalchemyをimportしてるんで、sqlalchemyていうモジュールがあって、その中でcreate_engine関数が定義されてるんですよね?

探してみた結果。

sqlalchemyパッケージ(フォルダ)があり、その配下のengineパッケージの__init__.pyの中に定義されていました。 つまり、普通に呼ぶときは

import sqlalchemy.engine
engine = sqlalchemy.engine.create_engine('sqlite:///:memory:')

となるはず?これで実行しても同じ結果でした。 ここで気付いたのが、「あれ、importする時って、パッケージ名.モジュール名だよね?」

あー、__init__.pyだとモジュール名が省略されるということか。

なので、import sqlalchemyの一文で、sqlalchemy配下の__init__.pyが呼ばれる。 そこには

from .engine import create_engine, engine_from_config

と記述がある。 ここでengineが出てきました。でもこれはパッケージなので、またモジュール名が無い。 そして__init__.pyの中にcreate_engineがいる。

つまり、import sqlalchemyだけでその配下のengineパッケージの__init__.pyまで読み込まれるってことかー…。慣れないorz

はい、次

2つ目

Base = sqlalchemy.ext.declarative.declarative_base()
…
Base.metadata.create_all(engine)

この流れ…。 あるクラスのオブジェクトを生成して、そのクラスのインスタンスメソッドcreate_allが呼ばれてるんだろうか…でも間の.metadata.は何者か。

Base.metadataBaseに入れたクラスオブジェクトで、そのインスタンス変数matadataにまたクラスのオブジェクトが入ってて そのクラスがcreate_allを定義しているってことかな。

sqlalchemyのドキュメントで調べると、そのクラスはclass sqlalchemy.schema.MetaDataということになっているが、いない。 pythonista3アプリだと宣言箇所に飛べないから 探すのが大変…。(修行にはなりそう)

そして、sqlalchemy.sql.schemaにいたーーー!

実態はsqlalchemy.sql.schema.MetaDataでした。

なんで違うかと考えたところ、ドキュメントには「MetaData API」という文字があったんで、これはあくまでAPIとしてのマニュアルってことか。

ドキュメント上は、実態とか関係なしにモジュールの呼び出し方だけ記載してあるってことなんですねー。知らなかった。

ドキュメント(英語)ちゃんと読めって話ですが、今はまだ時間がかかりすぎると思うので、もうちょいスキル上がってから読みます! 一旦モヤモヤは落ち着いたので良しとしよう。

最後

Session = sqlalchemy.orm.sessionmaker(bind=engine)
session = Session()

Sessionsessionの違いとは…。 Sessionはクラスのオブジェクトが入ってるとして、sessionはそれをインスタンス化したものだよねきっと。

ドキュメントに

A configurable Session factory.

The sessionmaker factory generates new Session objects when called, creating them given the configurational arguments established here.

て書いてある。訳すと

設定可能なセッションファクトリ。

sessionmakerファクトリは呼び出されると新しいSessionオブジェクトを生成し、ここで設定された構成引数を与えられてそれらを作成します。

うん、それっぽい!(疲れてきた)

まとめ

なるほど、こうやってDBMSの種類に合わせたクラスの構造自体を作っていって、操作する時はその出来上がったクラスをインスタンス化して使うってことなんだなぁ。きっと。

今はそう理解しておこう…。またスキルが上がった時にさらに深掘りしたいと思います

詳しい人いたら是非指摘して欲しい!

プログラミング修行中

何にしてもプログラミング

インフラからフロントエンドまで、色々できるようになりたいとは言っても、まずはプログラミングスキルが無いと転職もできない、ということで力を入れないといけない。

アウトプットしていく

年末から時間を見つけては競技プログラミングの問題を解いていました。 数をこなすのはいいけど、自分のコードを見直したり、レベル高い人のコードと比べたりして復習の時間も必要…ということでアウトプットしながら頭の中を整理していく習慣をつける!

今回のアウトプット

競技プログラミングとは別で、Udemyで購入したシリコンバレー流のpythonコードスタイルを学ぶ!という動画も視聴進めています。そちらもかなりインプットが溜まっているので、整理整理。

CSVファイルの読み込み、書き込み

よく使いそうな処理をピックアップして書いてみます。

configファイルでcsvファイルパスを指定

configparserパッケージを使ってみます。 まずは、config.iniファイルを作成。

import configparser

# ConfigParserクラスのオブジェクトを作成
config = configparser.ConfigParser()

# 辞書形式でカテゴリと各要素を指定
config['CSV_FILE'] = {
    'path': 'tmp/',
    'name': 'counter.csv',
}

# config.iniファイルを新規作成し、writeメソッドにオブジェクトを渡し書き込む
# ファイルのclose忘れを防止するため、withステートメントを使用している
with open('config.ini','w') as config_file:
    config.write(config_file)

これで出来上がったconfig.iniファイルの中身が以下。

[CSV_FILE]
path = tmp/
name = counter.csv

csvファイルの存在を確認、無ければ新規作成

CsvModelというクラスを作って、オブジェクト生成時に存在確認する。

class CsvModel():
    def __init__(self):
        self.csv_file = self.get_csv_file_path()
        
        # csvファイルが存在しなかったら新規作成
        if not os.path.exists(self.csv_file):
            pathlib.Path(self.csv_file).touch()
        
        self.header = [CSV_HEADER_NAME, CSV_HEADER_COUNT]

        # csvデータの格納領域を作っておく。
        # (keyがない場合、int型のデフォルト値(0)が設定される、defaultdictオブジェクト)
        self.data = collections.defaultdict(int)
        self.load_data()

get_csv_file_pathメソッドでは、configparserを使ってconfigファイルの内容を読み込み、csvのパスを返すようにしている。

    def get_csv_file_path(self):
        config = configparser.ConfigParser()
        config.read(CONFIG_FILE)
        return config['CSV_FILE']['path'] + config['CSV_FILE']['name']

また、load_dataメソッドでは、既にcsvファイルが存在していた場合、オブジェクト生成時にcsvの内容を読み込む。

    def load_data(self):
        with open(self.csv_file, 'r+') as csv_file:
            reader = csv.DictReader(csv_file)
            for row in reader:
                self.data[row[CSV_HEADER_NAME]] = int(row[CSV_HEADER_COUNT])
        return self.data

csvファイルにデータを書き込む

csvパッケージのDictWriterを使うと簡単!

    def save(self):
        with open(self.csv_file,'w+') as csv_file:
            # DictWriterを使うと、辞書形式でcsvファイルに書き込める
            writer = csv.DictWriter(csv_file,fieldnames=self.header)
            writer.writeheader()
            for name, count in self.data.items():
                writer.writerow({
                    CSV_HEADER_NAME: name,
                    CSV_HEADER_COUNT: count
                })

csvファイルに書き込むデータを作成

csvのカラムNAMEを指定したら、対応するCOUNTカラムの値が増えていくメソッドを作成。

    def increment(self, name):
        self.data[name] += 1
        self.save()

ではこれを使ってcsvファイルに書き込もう!

import csvmodel

csv_file = csvmodel.CsvModel()
csv_file.increment('rabbit')
csv_file.increment('cat')
csv_file.increment('dog')
csv_file.increment('rabbit')

結果は…

NAME,COUNT
rabbit,2
cat,1
dog,1

できました!

動画の演習内容をもとに自分なりにカスタマイズしたけど、色々と歪な設計になってしまったような気がするorz

オブジェクト指向的なコーディングも鍛えなきゃ…。

一応、読み込みも確認のため、別のオブジェクト生成してみる。

csv_file2 = csvmodel.CsvModel()
csv_file2.increment('rabbit')
csv_file2.increment('cat')
csv_file2.increment('rabbit')
csv_file2.increment('rabbit')

NAME,COUNT
rabbit,5
cat,2
dog,1

あってます。

家族が増えました

全然関係ないですが、癒しのため。

新たな危機感

再起動中…

4ヶ月振りに更新です。 なんとなくモチベ下がってからそのままゲームにハマり、仕事も忙しくなり、なんとなく充実して危機感も薄れてしまったのかなーと分析…。

9月の成果物です

英雄伝説 閃の軌跡Ⅳより

そしてゲームクリアに60時間。

ゲーム楽しいけど時間が吸い取られます…。

目標を再設定

いつか仕事で使えるよう、プログラミングのスキルアップを目指し勉強してきたけど、自分と会社の方針のギャップが広がっているのを感じた11月末。

この会社ではエンジニアとしての仕事は出来ない!と判断し、次のキャリアに向けて どこに行っても戦えるようなスキルを身につける!というのが新たな目標です。

身につけたいスキル

フロントエンドからバックエンド、インフラ周りも触れるのが理想。 (フルスタックエンジニアと言うらしい)

vue.js

javascriptフレームワーク。最近のトレンドらしく、フロントエンジニアの求人を見ているとよく見かけるので、抑えておきたいなと思っています。…正直javascriptフレームワークの違いが良く分からないので、そこも勉強!

ここで勉強中。

https://www.udemy.com/learn-vuejs/

python

バックエンドのロジックは書けるようになっておきたい。Javaは実戦経験無いけどそれなりにできる(ハズ)なので、pythonを継続で。 ロジックの修行として競技プログラミングを勧められたので、↓のサイトでちまちまやってます。

AtCoder

上位プログラマーのコード見てると心が折れそうなほど理解できない…。

Docker + Kubernetes

仮想マシンとは違う、一つのマシン上にアプリ+インフラを仮想的な領域(コンテナ)で 実行することができる技術を便利に活用できるアプリケーションたち。 まだまだこいつが何者なのかは理解できてません。 ↓の書籍で勉強中。

Docker/Kubernetes 実践コンテナ開発入門

AmazonWebService

有名なクラウドサービスはいくつかありますが、ここに勝てるものはもういないのでは…。 簡単に仮想マシンやネットワーク、DBも1人で構築出来てしまう。 インフラでは必須のスキルだと思ってます。

動画見ながら、一年間の無料枠+一部有料機能使って勉強中。

https://www.udemy.com/aws-14days/learn/v4/content

3月末までにやること

インフラからフロントエンドまで、一通り作ってみようと思います。

…さすがに独学で一人だと厳しいので会社の先輩に協力もらいながら 何か便利なWebサービスを作って、公開、運用するのが目標です!

それを武器に、次のステップに進みたい。

リフレッシュからの復帰待ち

お盆休みで頭真っ白

先週は仕事も休みで、帰省などなどしていました。お勉強のモチベが戻るまでちょっと時間かかりそうですね。

どうやったら戻って来るのか?( ̄◇ ̄;)

いい感じの一枚

旅立ちの予感…!

時間があったので羽田空港国際線ターミナルの展望デッキに行きました。 美味しそうなご飯屋がたくさんあり、こっちで食べれば良かったと後悔。

血小板ちゃん

はたらく細胞より

赤血球や白血球など、細胞を擬人化した漫画のアニメ化。人間の身体の中で起こっていることが分かりやすく表現されている。

久々にイラストも描きました。 面白いのでオススメです。がん研究者もその表現の正確さを絶賛しているとかなんとか。

魚と格闘

妻が最近できた業務スーパーから送ってきた写真

三枚おろしに挑戦しようと思い、購入を依頼したところ、思ったより大きくてビックリ。

かなり苦戦したらしい。まさかこんなに大きいとは思わず…。

結構な大きさだったので、以前からやりたかった、一尾丸ごと使ってフルコースを実践!

妻と協力し、刺身、アラ汁、塩焼きを作成!

娘も塩焼きを美味しいと言っていたので大満足でした。

シメは漬け丼。

まとめ

次やるときまでに出刃包丁と刺身包丁を買おうと心に決めた、そんな休暇でした。

スクレイピングに挑戦

アウトプットが渋滞中

週一回の更新を目標にしていましたが、追い付かなくなってきました。

仕事の方も新しい試み(自分の中で)をやっていて、そちらも覚えるものが多く大変…。スクラム継続的インテグレーション(CI)などなど。

ただ、今までの仕事の仕方に比べると楽しいので苦ではないですけどね。仕事を楽しいと思う日が来るとは…!

自己学習では、自分が使いたいアプリを作るために必要なスクレイピングという技術について勉強中。 その経過について書きながら、頭の中を整理したい。

スクレイピングとは

WebページのHTML構造を解析し、任意の情報を抽出する技術です。 まず第一の目標として、市の施設予約システムからテニスコートの空き情報を取得するところまで作っていこうと思います。

そこで、pythonを使ったスクレイピングに使えるライブラリは次の2つ

urllib

URLを扱うライブラリ。 主に、URLを指定してリクエストを送信し、レスポンス情報を取得します。 その情報から、HTMLの要素を取り出したりするのに使います。 ただ、urllibよりはrequestsというライブラリの方がより高水準で使いやすく、お奨めらしい。

Beautiful Soup

面白い名前のライブラリ。「ふしぎの国のアリス」の中で出てくる詩が由来らしい。今度調べてみよう…。 HTMLを渡すと解析してくれます。

とりあえずやってみる

まずは小手調べに、自分が利用しているシステムのトップページのURLを指定して「title」要素を抽出してみる。

from bs4 import BeautifulSoup
import urllib.request as req

# URLを指定(一応マスキング)
url = "https://xxxxxxxxxxxxxxxxx"
# URLを開き、レスポンスを取得
res = req.urlopen(url)
# レスポンス情報からHTMLを解析
soup = BeautifulSoup(res, "html.parser")
# "title"要素を抽出
title = soup.select_one("title").string
print("title = ", title)

結果は↓

title =  施設予約システム

こうでました! あとは、select_one("title")で引数として渡している「title」の部分を他の要素で指定してあげれば割とすぐできるのでは?

…この結果を得られた時はそう思っていました…。

JavaScriptの壁

抽出したい対象の要素を知るために、ターゲットとなるシステムのHTML構造を見てみます。

f:id:altrlz:20180814001116p:plain

さっきは「施設予約システム」が抽出されたので、上の「title」が取れていたようです。 frame内の「title」を取るには、frame要素のsrc属性に指定してあるURIをくっつけたら良い…?

title =  表示できません

うーむ、URLをブラウザで直打ちするとタイトルが「処理選択」になるから、title要素もそうなってるハズなのに違う文字列が抽出されるのは何故だ…。

Beautifulsoupで解析した結果を以下のコードで表示して、HTML構造がどうなっているか見てみよう。

print(soup.prettify())

その結果を抜粋

<html>
 <head>
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
  <meta content="no-cache" http-equiv="Cache-Control"/>
  <meta content="0" http-equiv="Expires"/>
  <meta content="ja" http-equiv="Content-Language"/>
  <title>
   表示できません
  </title>
  <link href="../style/yoyacky_1_3.css" rel="stylesheet" type="text/css"/>
  <script language="javascript">
   function Window_OnUnload(){
}
  </script>
 </head>
 <body bgcolor="#FFCC99" link="#000000" onunload="Window_OnUnload()" text="#000000" vlink="#000000">
 </body>
</html>
<!--<font style="font-size:13pt;">-->
<font class="SerchMiddleBold_font">
 接続が制限時間切れです。
 <br/>
 ブラウザを閉じて処理をやり直してください。
 <br/>
 <br/>
 閉じるボタンが使用できない場合は、
 <br/>
 ブラウザの閉じるボタンを使用して画面を閉じてください。
 <br/>
 <input alt="この画面を閉じる" name="btn_close" onclick="window.top.close();return false;" src="../image/window_close.jpg" type="image" value="閉じる"/>
</font>

表示できなかったパターンのHTMLが返ってきている。 body要素の属性にonloadでJavaScript指定してたりするから、画面操作しないとダメなんだろうか…。

JavaScriptも慣れてないから時間かけないとと厳しいなぁ。

次のアプローチ

このような場合のアプローチとして、Webブラウザ経由でのスクレイピング方法もあります。 そこで使えるのが、Webブラウザを自動操作させるSeleniumと、画面無しでコマンドラインから利用できるWebブラウザのPhantomJS

Seleniumは今仕事でも使おうとしているし、一石二鳥! という訳でまたインプットのお時間が始まります。先は長そうだ。

RedisとBootstrapとGitHub

役立つオープンソースソフトウェアたち

Djangoフレームワークを使ってWebアプリを作ってきましたが、これで捗るのはWebアプリの基本的な部分のみ。ただそれだけでも自分がコーディングする量は大幅に少なくなり大助かりですが!

今回のアプリでは、その他にもRedisを使ってデータを保存したり、Bootstrapを使ってページのデザインを綺麗にしたりと…世の中にはこんなに便利なソフトウェアやフレームワークがあったのかとビックリ。

プログラミングを勉強する!と言いながら始めましたが、実際、基本的な文法や標準ライブラリの使い方を覚えた後、こういったものを使い始めてからが本番なんだなーと感じているところです。

今回使った便利なツールたち

ライブラリやフレームワークOSSオープンソースソフトウェア)やWebサービスなどなど色々な言葉はありますが、ひっくるめてツールと呼ぶことにします。ちょっと脳のメモリが不足気味…。

Redis

インメモリDBという種類のデータベースで、メモリ上にデータを格納するためRDBよりも高速にアクセス可能。永続性もあるらしい。 キー・バリュー型で、キー(key)を指定してバリュー(value)を取り出すシンプルな構造。

↓のように、手札の情報や所持金、ベット額を保存するのに使っています。 f:id:altrlz:20180729233513p:plain

Bootstrap

Twitter社が開発した、CSSデザインのフレームワークらしい。 CSSのクラスが準備されていて、HTMLファイル内でクラスを指定すると、自分でCSSをコーディングすることなく見栄えがいい画面を作ることが可能!

↑に貼った質素な画面が、少しのコーディングで↓のようになります!これはすごい。(カードは画像ファイルです)

f:id:altrlz:20180730234735p:plain

GitHubとGist

GitHub

GitHubは開発者向けのWebサービスで、無償でソースコードを格納するリポジトリを作成し、公開できるものです。OSSもこのサービス上にリポジトリが公開されていて、世界中の開発者達が一緒に開発したりできるという素晴らしいサービス。開発者達のSNSという表現をよく聞きます。

僕もアカウントを作ってみました。 メニューが全部英語なので、慣れるまで時間かかりそう…。

github.com

Gist

GitHubのサービスの一つで、ソースコードを1ファイル単位で管理できる。

これが地味に役に立ちました。

今回、ブラックジャックのアプリ部分はiOSpythonistaというアプリでコーディングしていて、WebアプリはPCで仮想OS上のエディタでコーディングしていました。

Pythonista 3

Pythonista 3

  • omz:software
  • 仕事効率化
  • ¥1,200

そこで、iphone→PC間のソースの受け渡しがめんどくさいなーと思ってpythonistaをポチポチしていたところ、GitHubのキャラクターっぽいシルエットを発見!

そこからGistにアップロードして、仮想OSからgit cloneしてソースをゲットするという技を身に付けることができました! (↓のリンクから見れます)

main.py · GitHub

広がっていく風呂敷

pythonでアプリを作る、というところから様々なツールを知ることができましたが、まだまだ使えるとは言えないレベル。自分が使いたいアプリを作る段階で、こういうツールを選定し、実装していくことで自分のものにしていきたい!と思った1週間でした。

Web画面の作り方

フレームワークの流れ

覚えたこと。 DjangoでのWebアプリ画面の作り方。

  1. templatesディレクトリ配下にWeb画面の基礎部分となるhtmlファイルを作成する。

  2. htmlファイルに対するhttpリクエストの対応をview.pyに記述する。

  3. 対象のhtmlファイルが呼ばれる際のURLパターンをurls.pyに記述する。

htmlファイル

記述としては↓のような感じ。(body部を抜粋)

<body>
  <form action="" method="POST" >
    {% csrf_token %}
    <p>記号<br>
      <select name="suit">
    <option value="H">Heart</option>
    <option value="D">Dia</option>
    <option value="S">Spade</option>
    <option value="H">Clov</option>
      </select>
    </p>
    <p>数字<br>
    <select name="rank">
      {% for i in rank_list %}
      <option value={{i}}>{{i}}</option>
      {% endfor %}
    </select>
    </p>
    <p>
      <input type="submit" value="送信">
    </p>
  </form>

画面はこうなる。

f:id:altrlz:20180722232058p:plain

通常のhtmlの記述に加えて、{% %}で囲まれた部分にDjangoが用意したpythonっぽいロジックを記述できたり、view.pyとの変数のやり取りができたりします。

なお、templatesディレクトリ自体をDjangoが認識するために、setting.pyに今回作成したアプリの情報を記載する必要がある。

INSTALLED_APPS = [
    'firstApp', #←これ#
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

view.py

記述はこんな感じ。

def form_card(request):
        if request.method =='GET':
                rank_list=[]
                for i in range(1,14):
                        rank_list.append(str(i))
                dictionary = {'rank_list':rank_list}

                dictionary.update(csrf(request))
                return render(request, 'select_card.html', dictionary)
        
        if request.method == 'POST':
                suit = request.POST['suit']
                rank = request.POST['rank'].zfill(2)
                dictionary = {'suit':suit, 'rank':rank}
                print(suit,rank)
                return render(request, 'display_card2.html', dictionary)

form_card関数が呼ばれたら、この処理が動く。 最終的にはrender関数で遷移先のhtmlファイルを決定し、dictionaryの部分にhtml内で使用する変数などなどを格納して受け渡しする。

ちなみに、dictionary.update(csrf(request))Djangoが用意してくれているセキュリティ対策で、CSRFクロスサイトリクエストフォージェリ)という攻撃を防ぐ機能を準備してくれている!

フレームワークって…いいね…。

urls.py

正規表現を使って、URLのパターンと、呼び出す関数の紐づけを行う。

urlpatterns = [
    url(r'^form_card/$',views.form_card),
]

ここで記述する関数名と、view.pyに定義した関数名が違って何度もDjango先生に怒られました…。そして削られていく時間。

ちなみに送信ボタン押すと

対応するトランプのカードが表示されます。

f:id:altrlz:20180722234236p:plain

画面作成の基本的な流れはこんなところですかね。 次はDjangoでデータを保管したりする動作を覚えて、最後にブラックジャックを動かす!

…もうちょっと進めるペース上げたいけど、8月、9月は誘惑が多くて更に時間が削られることが想定されます。

  • 8月~9月末までの誘惑

www.fantasylife.jp

  • 9月末~の誘惑

www.falcom.co.jp

これらの壁をどう乗り切って行くかが鍵。 目標を見失わないようにせねば。