豆腐とコンソメ

豆腐とコンソメ

幸せと、主にラズパイとpythonについて書きます。

RaspBerryPiでラジコンをつくる(9):軽量化問題を解消するためのたったひとつの冴えたやり方

久々のラジコン記事です。
Djangoやら転職活動やらで書けてなかったのですが、ちょこちょこやってはいたので、成果を書きます!

前回のつづき

tohutokonsome.hatenablog.com

とりあえずですが、当初の目標は達成できました!

その過去の代償を払うためには私自身がラジコンを作る必要があるのではないのでしょうか。 そして完成したラジコンは今は亡き、お婆ちゃんに捧げます。

ラズパイ(RaspberyyPi3)を買ってきた - 豆腐とコンソメ

3ヶ月前の記事を見返したのですが、目標であるラジコンをつくる、という部分は改善点はあるものの、以下のとおりのものができました。

f:id:konoemario:20170619215609g:plain

これだけでもかなり嬉しいのですが、いざやってみると、カメラをつけたいとか、音楽も鳴らしたいとかいろいろでてきてしまうわけです。

しかし、ラジコンに最低限必要な部品を載せるだけで、以下の通りいっぱいいっぱいなのです。

f:id:konoemario:20170619215744j:plain:w500

軽量化にしても、ラズパイZeroをつかったり、バッテリーを小さなものを買ったりとしていて、自身のできる範囲ではこれ以上はないように思えます。


ではどうするか。


車を大きくすればいいじゃないか。

f:id:konoemario:20170619220133j:plain:w500

ということで「レゴ (LEGO) テクニック メルセデス・ベンツ アロクス 」を買ってきました。

レゴショップで購入したのですが、子供たちが小さなパッケージを、ご両親にねだっている隣で、特大のパッケージを買うのはなんとも言えない優越感を覚えました。「これが大人ってやつだよ」というのを見せつけちゃいましたね。 ただ、奥さんから「お前、来月から無職じゃねえかよ!」といろいろと罵倒されましたが、「結婚指輪はいらないので、レゴブロックを買ってください!と懇願することで、奥さんに買っていただけることになりました。

組み立て

勢いで買ってきたものの、こいつの大きさは半端ないです。 わかりにくい比較で、申し訳ないのですが前回のラジコンカーの箱と比べるとこんな感じです。

f:id:konoemario:20170619222512j:plain:w500

そして部品はこんな感じで、
f:id:konoemario:20170619222721j:plain:w500

説明書もPCゲームの「Age of Empires II 」を買ったとき並の分厚さです。

f:id:konoemario:20170619222702j:plain:w500

これの組み立てなのですが、平日の仕事終わりに2時間組み立てては片付け、週末は集中というサイクルでやっていったのですが15時間くらいはかかった気がします。

以降は、完全に個人の思い出なのですが、日々の成長の様子になります。

f:id:konoemario:20170619223234j:plain:w500f:id:konoemario:20170619223246j:plain:w500f:id:konoemario:20170619223327j:plain:w500f:id:konoemario:20170619223422j:plain:w500f:id:konoemario:20170619223516j:plain:w500f:id:konoemario:20170619223609j:plain:w500f:id:konoemario:20170619223737j:plain:w500

7日目にしてようやく完成しましたが、前回のラジコンカーと比べるとかなり大きいです。

解体する

完成してビールを飲みながら1時間程度堪能したら、ある程度、解体しました。

f:id:konoemario:20170619224126j:plain:w500

前回も前々回も、作り終わったら解体する、ということを経験してきたのですが、今回はかなりつらい作業になりました。

なんで解体するの?っていう点ですが、このダンプカーは、走る、曲がるという機能について考慮はされているのですが、デフォルトの状態では、走ったり曲がったりはしません。
標準のモーターの機能では、ダンプカーの荷台を持ち上げる、ショベルカー部分を動かす等になります。

興味のある方は、以下の動画が参考になるかと思います。

youtu.be

ある程度バラし終わったら、前回のラジコンカーと同様に、モーターやらサーボモーターやらをつけていきます。

f:id:konoemario:20170619225917j:plain:w500

以前はMモーターの中身を開けて、ジャンパー線をつけていたのですが、今回はモーターの付属のケーブルを切って、中の線にジャンパー線をつなぐというアプローチをとってみました。

XLモーターだけで3000円くらいするので、結構ドキドキしながら以下のように切っちゃいます。

f:id:konoemario:20170619225936j:plain:w500

電池ボックスと同じ要領で、以下のように、2本目と3本目を剥きます。

f:id:konoemario:20170619230042j:plain:w500

モータードライバー経由で動くことを確認をしました。 ※電池ボックスも前回使用したものを使ってます。

f:id:konoemario:20170619230011j:plain:w500

最後にジャンパー線をくっつけてモーター完成です。

f:id:konoemario:20170619230045j:plain:w500

とりあえず完成

いろいろと雑ですが、こんな感じで動くものができました! f:id:konoemario:20170619231211j:plain:w500

荷台の部品たちはかなり適当に詰め込んでいますが、ラズパイzeroや小型ブレッドボード等一切気にしない状態でも、問題なく収納できます。

f:id:konoemario:20170619231215j:plain:w500

また、地味にカメラも購入したので適当につけました。
本当は、真ん中のシャベルカー部分だった回転する箇所に取り付けたいのですが、カメラモジュールのケーブルが短くって、いまのところつけられません。今度長いケーブルを買う予定です。

f:id:konoemario:20170619231218j:plain:w500

また、曲がるためのサーボモーターも適当につけました。
ちょっとマイクロサーボだと車体が重いせいかタイヤの動きがよくないです。
もう一ランク上のサーボモーターを購入してみようかと思います。
f:id:konoemario:20170619231312j:plain:w500

動かす

動かすとこんな感じです!

youtu.be

コントローラのUI部分と、ラジコンの見た目等、いろいろ改善点はありますが、ここまでこれてよかったです!

が、記事を見返して思ったのですが、このときと発想がかわっていないことに自分で驚きました。

f:id:konoemario:20170311000933j:plain:w500

以上。

スクラム講習に行ってきた

日記

転職に苦戦しています。
COBOLERでも入れる、素敵なスクラム開発を採用している企業様はありませんでしょうか。

本題

先日、知人の方に誘われて、スクラムの無料講習に行ってまいりました。

Odd-e Training

「ザ・ウォーターフォールみたいな職場なので流行りのアジャイルスクラム?、短期間でリリースしてくやつ?みたいな感覚で受講しました。

講習内容は自体は、スクラムとは?から始まり、スクラム開発を行う上での要素や全体の流れを学びました。

受講後は以下のような感想を受けました。

ここでは、雑記となってしまいますが、印象に残ったこと・思ったことをメモがてら書いていこうと思います。


アジャイルスクラム、その意味は?

そもそもアジャイルってなんだっけ、ってことなんですが、これは

よりよい開発をしていく状態

を指すみたいです。
(肝心の部分なんですが、メモが汚くって少し間違っているかもしれない!)

ですので、アジャイルをやるみたいな表現は間違っていて、正確にはアジャイルを目指す、アジャイルになるみたいな表現が正しいみたいです。

知っている人には常識なのかもしれませんが、知らなかった私にとっては、衝撃でした。

一方、スクラムですが、これは

現状を認識するためのフレームワーク

を指します。

なので、スクラムやると生産性や品質があがるとか、プロジェクトの炎上が防げるとかいろいろと思うことあったのですが、結果的にそうなるかも、というだけであって、スクラムの目的はそこではないということです。
あくまでも、現状を認識するため、の手法です。

これには、驚かされましたが、一方でなるほどなぁと考えさせられたりもしました。


私たちが抱える課題

システム開発の難しさって、目に見えない部分っていうのが非常に大きいと思います。

顧客が、本当に欲しかったものは動かしてみないとわからない。 開発に必要な工数も、現時点のチームメンバーの力量にもよるし、実装方法にもよるし、よくわからない。 性能要求も実装が全て終わってからでないと、本当に達成できるのかわからない。

といろいろとあるのですが、ようはやってみなきゃわからないんですね。

なので、現状を認識する、つまりやってみることによって、自分たちが今開発した場合にどれくらい工数がかかるのか、顧客が必要そうなものをいつ・どれくらいに実装できるのか、そもそも顧客がほしいものこれなのか、ということを把握していくわけです。

で、スクラム開発ってどうやるの

すみません、これに至っては、こちらの講習の事前資料として掲載されている、下記pdfファイルを読んでいただいた方が正確です。
興味のある方はぜひご一読を。

https://www.pastoraldog.com/THESCRUMPRIMER_ja.pdf


課題

ここでは、スクラムを導入するにあたって感じた課題を書きます。
そもそもスクラムの理解が誤っていたらご指摘いただければと思います。

チーム作り

スクラムの重要なエッセンスに、「透明性・検証・適合」というものがあります。
透明性は、前述のとおり「現状を認識する」という意味での透明性になります。
一方、検証・適合というものは、スクラムのスプリント(1〜4週間)を通して、都度振り返りを行い改善していこうぜ、という考え方です。
なので、スクラムにおけるチームというものは、ただの開発チームではなくRPGでいうパーティーみたいなものじゃないかという印象を受けました。
チームとしても成長していきますし、メンバーそれぞれを成長を促していきます。

とはいえ、現状のシステム開発の大半は、いろんなパードナー会社から人を集め、プロジェクトが終わったら解散し、残ったメンバーもジョブローテーションという名のもと、どこかに行ってしまいます。

短期間でチームをつくっていくという技術そのものは必要ですが、長期的にチームを組んでいくというのは業界構造そのものをかえないと難しかったりするのかなぁと思いました。

プロダクトオーナーの存在

私の理解では、スクラムにおける「現状を認識する」対象には顧客も含まれると思っています。 実際に動く製品を触ってみて、顧客が欲しいものを顧客自身が認識していくからです。

前述のpdfファイルを見ますと、スクラムにおいては、プロダクトオーナー = 顧客となることも当然あるみたいです。
プロダクトオーナーは、要求事項を出して、スプリント内で実装すべき機能の優先順位をつけたり、スプリントで開発された製品に対して検査を行ったりします。

このやり方は、プロダクトオーナー(=顧客自身)がこういった形でチームと一丸になって、開発をしていくという姿は理想的なのですが、なかなか難しいのかなって思ってしまいます。

スクラムどうこうではなく、要求もあいまいなまま丸投げしてくる発注側や、顧客が忙しくてシステム側と話す時間を取れない、もしくはそれを考えるのが君たちの仕事だ!みたいな方もたくさんいるのかな、と思います。

これに関しては、ウォーターフォールだろうが、なんだろうが顧客自身も開発に参加してもらわないといいものはできないっていうのが当たり前になってかないと、なかなか変わらないのかなって思っちゃいました。

まとめ

スクラムをやれば、(当たり前だけれども)なんでも解決するっていうものじゃない。
でも、スクラムの現状を認識する、という点にはシステム開発に関わらず、全てに領域に当てはまることだなぁと思う。

(これもシステム開発っていえばそうだけれども)ウォーターフォールの要件定義書を書く、というフェーズ部分だけを切り出しても、期間を短く切って成果物を都度出して、顧客を巻き込んで確認していく、って当たり前のことなんだけれどもできてないことって多いですよね。

今回学んだことを実践できる場が私にはあるのか微妙なんですが、ぜひやってみたいなと思いました。

なので私に仕事をください!

Djangoを学ぶ(4):リンクをはる

前回の続き

tohutokonsome.hatenablog.com

今回は、以下のようにトップ画面から個別記事に飛ぶようにリンクを貼っていきます。

f:id:konoemario:20170613232718p:plain:w500

個別記事を返す関数をviews.pyに追加する。

まず、前回のhome.htmlを返すhome関数と同様に、個別記事を返す「post_details」関数を作成します。
ポイントは、Postオブジェクトの取得方法です。

views.pyを抜粋

from django.shortcuts import render
from .models import Post

def post_details(request, post_id):

    post = Post(pk=post_id)

    return render(request,'posts/post_details.html', {'post':post})

前回のhome.htmlでは、トップ画面に記事一覧を表示するために、Postオブジェクトをすべて取得していました。

  #Postオブジェクトを取得
    posts =  Post.objects.order_by('pub_date')

ですが、今回必要なのは、個別記事に表示するひとつのPostオブジェクトのみ必要になります。
ここでは、特定のPostオブジェクトを取得するための方法として、

 post = Post(pk=post_id)

という書き方をしています。

※参考
クエリを生成する — Django 1.4 documentation

pkは字のごとくプライマリキーを表しており、管理者画面から投稿した記事にはデフォルトでidなるプライマリキーが振られています。
ここでは、引数から与えられたpost_idをもとに、一意のPostオブジェクトを取得しています。
取得したPostオブジェクトは、「post_details.html」に渡します。

次に、post_details関数を呼び出すための定義をurls.pyに追加します。


urls.pyの編集

urls.pyを抜粋

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    #home
    url(r'^$',posts.views.home, name='home'),
    #detail
    url(r'^posts/(?P<post_id>[0-9]+)/$',posts.views.post_details, name="post_detail"),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

ちょっとわかりずらいのですが、「P<post_id>」の部分がポイントなのかなぁと思っています。
Djangoは、「http://localhost/posts/[0-9]+」にマッチするリクエストがあった場合に、さきほど作成した「post_details」関数を呼び出します。
このとき、「P<引数名>」書いておくと、Djangoは、post_details関数に名前付き引数として、引数を渡すみたいです。

これによって、前述のpost_details関数は、受け取ったpost_idをキーに、Postオブジェクトを取得することができます。

ちなみに、「P<post_id>」と書かないと、関数に引数を渡さないような印象を受けますが、特に書かなくても正規表現でキャプチャした値は、関数に渡す仕組みになっているようです。
ただ、「P<post_id>」としておくと、名前付き引数として扱えるので、引数の順番を意識する必要がなくなるというわけです。

※参考
URL ディスパッチャ — Django 1.4 documentation


home.htmlの編集

home.htmlでは、記事のタイトルにタグをつけて、記事へのリンクを貼ることにします。

home.htmlより抜粋

<!-- postオブジェクトは複数あるので、こんな感じでループさせる。-->
{% for post in posts.all %}
     <h1>{{ post.title }}</h1>
    <p> {{ post.pub_date }}</p>
    <img src="{{url 'post_dettail' post.id}}" style="max-height:300px"/>
    <p> {{ post.body }} </p>

{% endfor %}

「{{url ‘urls.pyに定義したname’}}」は前回同様なのですが、post.idをここに書くことで、引数として渡せるみたいです。


post_detail.htmlの作成

最後に、個別記事のhtmlを作成します。
といっても、ほぼhome.htmlをコピペしただけの内容になっています。

post_detail.htmlの抜粋

    <a href="{% url 'post_detail' post.id %}"><h1>{{ post.title }}</h1></a>
    <p> {{ post.pub_date }}</p>
    <img src="{{post.image.url}}" style="max-height:300px"/>
    <p> {{ post.body }} </p>

次回はしばらくないかも

とりえず、ここまでの内容でトップ画面から個別記事に遷移するブログちっくな機能の実装ができました。

画面のレイアウトについては、cssの話になってしまうので、メモが必要な局面がでてきたら更新していこうかと思います。

ラズパイ:100円のスピーカーレビューと注意事項

そろそろ、ラズパイで音でも出してみよっか!ということで、100均一(セリア)スピーカーを購入して参りました。

今回は、タイトルのスピーカーのレビューと注意事項を記載します。

USBタイプのものだと、電源をラズパイから供給しなきゃいけないのでは?みたいな不安があったので より手軽なステレオピンジャック用のスピーカーを選ぶことにしました。

そして、購入したものは、以下の二つになります。

f:id:konoemario:20170611191143j:plain:w500

「音を大きするための商品ではございません。」 の注意書きが気になりましたが、とりあえず購入しました。


キューブ型

まずは、キューブ型です。
こちら、モノラルスピーカーなのですが、二つ購入することでステレオスピーカーに進化することができます。
すごい!

f:id:konoemario:20170611192051j:plain:w500

早速、ラズパイのステレオジャックに接続して、再生してみたところ、

すごく音が小さいです。

静かな場所で聞く分にはいいのですが、うーん、という音量でした。

ただ、ラズパイではなく、スマホとかにつなげる分にはそれなりに音がでます。
でも、スマホ内臓の音量とそんなかわらないです。

説明文の「音を大きするための商品ではございません。」は謙遜でもなんでもなく真実でした。


マルチメディア型

こちらは、キューブ型に比べるとそこそこ音はでます。
マルチメディア型を最初に使っていたら、うーん、となっていたかもしれませんが、キューブ型の音量を知った私にとっては、いけるじゃない!という出来でした。


注意事項

今回、購入したスピーカーですが、使用用途としてはラジコンカーにつけて、なにかしらしたかったわけです。

ですが、ラジコンカーと、ステレオジャックピンの相性は(もしかしたら)非常に悪いです。

ラズパイのトピックにも上がっていますが、

Raspberry Pi • View topic - ALSA not working properly: aplay: pcm_write:1939: write erro

ハードウェアPWM信号を使っていると、ステレオジャックピンが動作してくれないみたいです。

どうもステレオジャックピンもPWM信号を使っているみたいで、競合しちゃうみたいです。

私の場合は、ラジコンカーにサーボモーターを使用しており、そこでハードウェアPWM信号を使用しています。

なので、ラジコンカーの処理をセットアップする前は、以下のように再生できるのですが、

pi@raspberrypi:~ $ aplay test.wav 
再生中 WAVE 'test.wav' : Signed 16 bit Little Endian, レート 44100 Hz, ステレオ

ラジコンカーのためのGPIOの設定を行うと、以下のエラーがでてしまいます。

pi@raspberrypi:~ $ aplay test.wav 
再生中 WAVE 'denpa.wav' : Signed 16 bit Little Endian, レート 44100 Hz, ステレオ
aplay: pcm_write:1939: 書込エラー: 入力/出力エラーです

前述のラズパイのトピックでも、解決策はない?みたい。


結論

ハードウェアPWMを使用しているのであれば、USBスピーカーを購入したほうがよさそう。
そうでなく手軽に音楽を流したいなら、マルチメディアスピーカーでも事足りるかも。

Djangoを学ぶ(3):画像の表示

前回からの続き

今回は前回表示できていなかった、画像を表示していきたいと思います。

f:id:konoemario:20170610205858p:plain:w500

staticファイルの公開

画像とかjsとかcssとかのいわゆるstaticファイルと呼ばれる。
これをDjangoで扱うには、ちょっと作業がいる。
適当なディレクトリに置いて、そのディレクトリを公開すればいいんじゃない?って考えてしまうのですが、Djangoには以下の機能が備わっているみたいです。

静的ファイルの公開方法 — Django 1.4 documentation

上記より抜粋。

小さなプロジェクトではこのことは大きな問題になりません。 Web サーバが見つけら れる場所で静的ファイルを単に管理することができるからです。しかし、もっと大きな プロジェクトで、特に複数のアプリケーションからなる場合は、各アプリケーションが 持っている静的ファイルの集まりを複数扱うことになり、ややこしくなってきます。

django.contrib.staticfiles はまさにそのためにあります。これは静的なファイルを各アプリケーションから (さらに指定した別の場所からも) 一つの場所に集め、運用環境で公開しやすくするものです。

大きなプロジェクトを作成しないと、有り難みがわからないかもしれませんが、ざっと見ていきます。

ヘッダー画像を表示する

まず、管理者画面から投稿した画像を表示する前に、通常の画像を扱う方法を見ていきます。
ここでは、以下のようにTOP画面のヘッダー画像を表示していきます。

f:id:konoemario:20170610194010p:plain:w500

前述の「django.contrib.staticfiles」ですが、setting.pyのINSTLLED_APPSに標準搭載されているのが確認できるので、特になにもしなくても使えそうです。

setting.py

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
#↓これのこと
    'django.contrib.staticfiles',  
    'posts',
]

そして、「django.contrib.staticfiles」は「setting.py」の「STATIC_URL」配下を探しにいくみたいです。
デフォルトでは、「/static/」が既に記載されています。

setting.py

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

#staticlファイルの定義
STATIC_URL = '/static/'

このへんを確認したら、実際に表示したい画像を置きます。 アプリケーション「posts」配下にstaticディレクトリを新規に作成して、その配下にアプリケーション名「posts」のディレクトリを作成した上で、「home.jpg」を置きます。

└── posts
    ├── __init__.py
    ├── __pycache__
    ├── admin.py
    ├── apps.py
    ├── migrations
    ├── models.py
#staticディレクトリを作成した
    ├── static
    │   └── posts
    │       └── home.jpg

前回のtemplatesもそうだったのですが、

アプリケーションのディレクトリ–>templates とか static とか–>アプリケーション名–>ファイル

という構成になってます。
(このへんの仕組みを書いたドキュメントがあったのですが、わからなくなっちゃいました。)

次に、html側を修正していきます。
以下のようにします。

home.html

<!--staticファイルの参照方法-->
{% load static%}
<img src="{% static "posts/home.jpg"%}" style="max-height:500px"/>

「{% load static %}」と 「{% static “ディレクトリ名” %}」を追加しています。
実際に、クライアントに返されるhtmlは以下の通りとなります。

home.html

<img src="/static/posts/home.jpg" style="max-height:500px"/>

実際にTOP画面にアクセスすると、画像が表示されることが確認できるかと思います。

管理者画面で投稿した画像を表示する

次に、管理者画面で投稿した画像を表示していきます。

現在の状態ですが、html側のソースを見てみると、「media/pinky2.jpg」となっていることが確認できます。

home.html

 <h3> 彼女とデートなう</h3>
    <p> June 5, 2017, midnight</p>
    <img src="media/pinky2.jpg"/>
    <p> 彼女ともんじゃ焼きを食べに行きました </p>

クライアントに返す前の状態はこんな感じでした。

home.html

{% for post in posts.all %}
    <h3> {{ post.title }}</h3>
    <p> {{ post.pub_date }}</p>
    <img src="{{ post.image.url }}"/>
    <p> {{ post.body }} </p>

{% endfor %}

そもそもmediaディレクトリってなんだっけ?ってなったのですが、 Postクラスのimageにアップロード先のディレクトリを定義していました。

class Post(models.Model):
    #適当な画像
    image = models.ImageField(upload_to='media/')

そして、管理者画面から投稿した画像は、mediaディレクトリに置かれてますね。

├── db.sqlite3
├── manage.py
├── media
│   ├── django?\205??\226\200.png
│   ├── jingyi-wang-195381.jpg
│   ├── pinky2.jpg
│   └── remi-skatulski-88995.jpg
├── ohankyblog

なのでここでやりたいことはヘッダー画像のようにstaticディレクトリ配下に置いたものを見せる、というものではなく
管理者画面で投稿したmediaディレクトリ配下に置いたものを見せる、ということになります。

他のディレクトリを公開する。

あんまりしっくりきてないのですが、メモがてら書きます。
さらにいえば、こんへんのやり方は開発環境用のもので実際に運用する際にはいろいろと問題があるみたいです。
最終的にはインターネットの世界に公開していきたいので、どこかでちゃんとした運用を紹介できればと思います。

まず、「setting.py」に「MEDIA_URL」と「MEDIA_ROOT」を追加しています。

setting.py

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

STATIC_URL = '/static/'
MEDIA_URL = '/pics/'
MEDIA_ROOT = BASE_DIR

次に、urls.pyを以下のように編集しています。

urls.py

from django.conf.urls import url
from django.contrib import admin
import posts.views

#以下の定義を追加
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    #home
    url(r'^$',posts.views.home, name='home'),
#以下の定義を追加
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

こうすることで、あら不思議、管理者画面で投稿した画像たちが表示されます。

f:id:konoemario:20170610205637p:plain:w500

実際のhtmlソースは以下のように編集されています。

    <h3> 彼女とデートなう</h3>
    <p> June 5, 2017, midnight</p>
    
    <img src="/pics/media/pinky2.jpg" style="max-height:300px"/>
    <p> 彼女ともんじゃ焼きを食べに行きました </p>

setting.pyで設定した「MEDIA_URL」は画像が置かれているディレクリを指定しているわけではなく、URLでどう見せるのかという設定だということがわかります。

なんでDjangoはmediaディレクトリに画像があるって知ってるんだろうなぁ、とか考えていたのですが
Postsクラスのimageフィールドがあるのか、思ったら納得しました。

試しに、sqliteでテーブルの中身を確認したところ、imageフィールドには、画像のパスが定義されていました。
(media以外にも格納先を「tekitou」とかに変えてみたりしていました)

sqlite> select * from posts_post2;
      id = 1
   title = aaaa
pub_date = 2017-06-10 11:26:18
    body = aaaaaa
   image = tekitou/pinky2.jpg

ちょっと中途半端ですが、今回はここまでです。

Djangoを学ぶ(2):ブログサイトの作成

前回からの続き

tohutokonsome.hatenablog.com

前回は、Djangoで作成したプロジェクトに対して、手動でviews.pyを作成して、簡単なWebページを作成した。

今回はよりDjangoらしい機能を使っていきます。

今回作成するもの

以下のようなブログサイトを作成してみたいと思います。

TOP画面
f:id:konoemario:20170608151428j:plain:w500

詳細画面
   f:id:konoemario:20170608151432j:plain:w500

プロジェクトの作成とモデルの作成

前回同様に、まずプロジェクトを作成します。
ブログサイトの名前は「ohankyblog」としました。

#プロジェクトの名前はohankyblog
masao-3:myblog konoe_mario$ django-admin startproject ohankyblog

そして、前回と大きく異なる点として、「startapp」なるコマンドを用いてアプリケーションを作成します。
記事を管理するアプリケーションということで、「posts」とします。
アプリケーションがどういった単位で作成すべきなのか、という点がイメージできていませんが、とりあえず続行します。

#作成するアプリケーションはposts
masao-3:myblog konoe_mario$ python manage.py startapp posts

この「startapp posts」とすることで、新たにpostsディレクトリとその配下にいろいろなファイルが追加されているのが確認できます。

masao-3:myblog konoe_mario$ tree
.
├── manage.py
├── ohankyblog
│   ├── __init__.py
│   ├── __pycache__
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
#startapp potstsを行う事で作成されるファイルたち
└── posts
    ├── __init__.py
    ├── admin.py
    ├── apps.py
    ├── migrations
    │   └── __init__.py
    ├── models.py
    ├── tests.py
    └── views.py

その配下のファイルには、前回手動で作成した、「views.py」が作成されていることに気づきます。
きっと、作成されたviews.pyにHTTPリクエストから呼ばれる関数を書くんだな、と想像できますね。

templatesとhome.htmlの作成

とりあえず、前回と同様にトップ画面を作成していきます。
トップ画面である、home.htmlファイルを以下のようにtemplatesディレクトリ配下におきます。
templatesディレクトリの中にさらにpostsディレクトリを置く意図がいまいちわかりませんが、こちらもUdemyの動画にならいます。
home.htmlはこの段階では適当な内容なので、内容は割愛します。

└── posts
    ├── __init__.py
    ├── __pycache__
    ├── admin.py
    ├── apps.py
    ├── migrations
    ├── models.py
#posts配下にtemplatesとhome.htmlを作成した。
    ├── templates
    │   └── posts
    │       └── home.html
    ├── tests.py
    └── views.py

views.pyとurls.pyの編集

このへんは慣れてきました。
posts配下のviews.pyにhome.htmlを返す関数と、urls.pyにurlの情報を定義します。

views.py

from django.shortcuts import render

# Create your views here.
def home(request):
    return render(request, 'posts/home.html')

urls.py

from django.conf.urls import url
from django.contrib import admin
import posts.views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    #home
    url(r'^$',posts.views.home, name='home'),
]

前回は言われるがままに書いていましたが、「r'^$‘」って正規表現で、「^」は先頭一致、「$」は末尾一致で、組み合わせることで何も入力されていない(つまり、ルートディレクトリである)ことを表現している気がします。

setting.pyの編集

urls.pyとviews.pyを編集したらもう参照できるかな?と思いきや、前回定義したhome.htmlの場所を定義していません。
前回は、setting.pyのTEMPLATESに記載しましたが、startappでアプリケーションを作成する正規の方法では、setting.pyのINSTALL_APPSに作成したアプリケーション「posts」を追加すればいいみたいです。

setting.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'posts',    <----これ
]

これを行うことで、Djangoはpostsアプリケーションがあるんだなって認識してくれるみたいです。
この状態でrunserverして、ルートディレクトリにアクセスすれば、home.htmlが表示されるかと思います。

モデルの作成

さて、モデルというものがここででてきます。

いまいちよくわかってませんが、データをもってるクラスって思っていればいいのかもしれません。
MVCモデルのMの部分だよってことなのかな。

※このへんをみるとモデルとは、データベースの定義と追加のメタデータの定義とある。
はじめての Django アプリ作成、その 1 — Django v1.0 documentation

さっそく、posts配下にあるmodels.pyを編集していきます。

└── posts
    ├── __init__.py
    ├── __pycache__
    ├── admin.py
    ├── apps.py
    ├── migrations
    ├── models.py   <---これ!
    ├── templates
    │   └── posts
    │       └── home.html
    ├── tests.py
    └── views.py

編集した内容は以下の通りです。

models.py

from django.db import models

# Create your models here.

class Post(models.Model):
    '''
    記事の内容を管理するクラスと思われる。  

    modelの定義はここに詳しく書いてある。
    https://docs.djangoproject.com/ja/1.11/ref/models/fields/
    '''
    #タイトル
    title = models.CharField(max_length=250)
    #発行日
    pub_date = models.DateTimeField()
    #適当な画像
    image = models.ImageField(upload_to='media/')
    #記事本文
    body = models.TextField()

いろいろとでていますが、 記事の内容を構成する要素を定義しています。
気になる、models.CharField()あたりですが、これがデータの型を表しているみたいです。
title要素は、Char型の要素ですよ、っていうのと、引数にmax_lengthを与えることで最大250文字ってことを定義しているみたいです。

詳細は以下に書いてあります。
モデルフィールドリファレンス | Django documentation | Django

さてさて、ここで「title」は「Char型」だよと定義する意味なんですが、冒頭の「モデルとはデータベースの定義」ということを思い出します。

つまり、ここで定義したデータ型をもつPostsテーブルをDjangoが作成してくれるみたいです。
テーブルっていっても、データベースなんかどこにあるんだっけ、と思ったのですが、setting.pyをみたりすると、sqlliteなる記述を発見できるかと思います。
つまり、デフォルトでsqlliteというデータベースが存在しているみたいです。

makemigrationとmigration

モデルに情報を定義したら、その次は、「makemigration」と「migration」コマンドを実行します。

一旦、テスト用のWebサーバーを起動していたら、落としときます。
※必須じゃないかも。

「makemigration」を実行することで、さきほどのモデルに定義した情報をもとにファイル「0001_initial.py」が作成されます。

masao-3:myblog konoe_mario$ python3 manage.py makemigrations
Migrations for 'posts':
  posts/migrations/0001_initial.py
    - Create model Posts

これは、DBのスキーマを作成するためのpythonコードが書かれているみたい。
sqlmigrateコマンドを実行すことで、DDL文の形式で確認することができます。

masao-3:ohankyblog konoe_mario$  python manage.py sqlmigrate posts 0001
BEGIN;
--
-- Create model Post
--
CREATE TABLE "posts_post" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(250) NOT NULL, "pub_date" datetime NOT NULL, "image" varchar(100) NOT NULL, "body" text NOT NULL);
COMMIT;

そして、このDDL文を実行するコマンドが、「migrate」になります。

masao-3:ohankyblog konoe_mario$  python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, posts, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying posts.0001_initial... OK
  Applying sessions.0001_initial... OK

初回のmigrateなんで、いろいろでてる。
postsも表示されているので、うまくいったみたい。



Django Admin

さきほど、モデルを編集して、migrateして〜みたいなことをしたので、sqlliteにPostsモデルの定義をもつテーブルが作成されたはずです。

では、実際に作成したテーブルに値を入れるにはどうしたらいいんだろう、ということで、前回スルーした以下のDjango管理者画面について触れていきます。

from django.conf.urls import url
from django.contrib import admin
#views.pyのohanky関数を呼ぶためにimport
from . import views

urlpatterns = [
    #Djangoの管理者画面。今回触れないんだ。  
    url(r'^admin/', admin.site.urls),
    #ohankyworld/でアクセスすると関数ohankyworldが呼び出される
    url(r'^ohankyworld/', views.ohankyworld),
]

http://localhost:8000/admin」にアクセスすると、以下のような画面が開きます。

f:id:konoemario:20170606222553p:plain:w500

Djangoはこの管理画面からさきほど作成したモデルのデータを作成することができます。

管理ユーザーの作成

まず、管理画面を使用するユーザーを作成する。
「createsuperuser」でuser名とアドレス(適当)、パスワードを設定して、サクッとつくります。

masao-3:ohankyblog konoe_mario$ python manage.py createsuperuser
Username (leave blank to use 'konoe_mario'): admin
Email address: admin@admin.jp
Password: 
Password (again): 
Superuser created successfully.

管理画面をさわってみる

ログインすると、以下の画面が表示されます。

f:id:konoemario:20170606223719p:plain:w500

が、このままでは作成したPostモデルを触ることができません。

編集できるようにするには、「admin.py」に以下の定義を追加する必要があります。

admin.py

from django.contrib import admin

# Register your models here.
from .models import Post

#admin管理画面でさわれるようにする。
admin.site.register(Post)

定義を追加したあとに、管理者画面をリロードすると、以下のように作成したモデル(というよりアプリケーション?)が追加されます。

f:id:konoemario:20170606223721p:plain:w500

追加されたPostをクリックすると、まだなんもない状態であることがわかります。

そこから画面右上のADD POSTを押下します。

f:id:konoemario:20170606224614p:plain:w500

そうすると、以下の画面のように、モデルに定義したフィールドを持つ入力画面が表示されるではありませんか!

さっそくデータを適当に入力してみました。

f:id:konoemario:20170606224757p:plain:w500

さらにさらに、合計4件ほど追加しました。
「Post object」なるものが4件ほどあるのがわかるかと思います。

f:id:konoemario:20170606224905p:plain:w500



管理者画面で作成したデータをpythonでさわる

さきほど管理者画面で作成したデータをviews.pyからさわります。

さわるといっても、すごく簡単です。

from django.shortcuts import render
from .models import Post

# Create your views here.
def home(request):

    #Postオブジェクトを取得
    posts =  Post.objects.order_by('pub_date')

    #home.htmlにはディクショナリ形式の引数を与えることでデータを渡すことができる!
    return render(request, 'posts/home.html',{'posts':posts})

postモジュールをimportするだけです。
取得したモデルは、前回同様、ディクショナリ形式でrender関数に渡すだけで、html側で参照できます。

home.html側はこんな感じです。

home.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!-- postオブジェクトは複数あるので、こんな感じでループさせる。-->
{% for post in posts.all %}
    <h3> {{ post.title }}</h3>
    <p> {{ post.pub_date }}</p>
    <img src="{{ post.image.url }}"/>
    <p> {{ post.body }} </p>

{% endfor %}
</body>
</html>

上記、定義を追加したあとに実際に「http://localhost:8000/」にアクセスすると、
管理者画面から追加した内容が表示されていることが確認できます。
※イメージが表示できてないのは個別で設定が必要。
f:id:konoemario:20170608150639p:plain:w500

一旦、ここまでをまとめ

関係性をかんたんに書きました。

f:id:konoemario:20170608162649p:plain:w500

長くなったので、次回に続くかもです。

次回 tohutokonsome.hatenablog.com

Djangoを学ぶ(1)

せっかくだしpythonを使って、Webアプリケーションも作りたいな!と思ってDjangoにたどり着きました。
そして都合がよい事にオンライン学習の「Udemy」でセールがあった際に「Djangoの入門編」を見つけて購入していたので、始めることにしました。

www.udemy.com

当初は、オンライン学習のみをこつこつとやっていたのですが、動画ベースの講座なので時間が開くとさっぱり忘れちゃっていて、思い出すにも動画を見返さないといけなくって結構しんどいです。

なので、ここでは学習サイトでやった部分のうち、重要だと思われる部分を抜粋して記載していこうかと思います。

ただ、英語がぜんぜんわからないのに、英語の動画を見ているので、作業内容はともかく、口頭でいっているかもしれない重要なエッセンスは結構な頻度で漏らしている可能性があります。

そういった部分があれば、コメント等でご指摘いただければ幸いです。

Djangoのインストー

まずは、導入作業から。
pipでとってくるだけ。

masao-3:~ konoe_mario$ sudo pip3 install django

プロジェクトの作成

Djangoには、プロジェクトという概念があるみたい。
プロジェクト単位にテスト用のWebサーバを立ち上げたりできる。

今回は、「sample」という名前でプロジェクトを作った。

#django-admin startproject プロジェクト名
django-admin startproject sample

プロジェクトを作成すると、以下のファイルが作成される。
「db.sqllite3」は後述のrunserverを行うことで作成されるみたいなので 、この時点でなくても大丈夫。

masao-3:sample konoe_mario$ tree
.
├── db.sqlite3
├── manage.py
└── sample
    ├── __init__.py
    ├── __pycache__
    ├── settings.py
    ├── urls.py
    └── wsgi.py

Webサーバーの立ち上げ

プロジェクトを作成した際に、作成される「manage.py」でWebサーバーを立ち上げることができる。

masao-3:sample konoe_mario$ python manage.py runserver

上記、コマンドを実行すると、「http://127.0.0.1:8000/」でテスト用のWebサーバが起動しているよ!とのメッセージが表示される。

Performing system checks...

System check identified no issues (0 silenced).

You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

June 03, 2017 - 09:45:20
Django version 1.11, using settings 'sample.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

さっそく、ブラウザでアクセスしてみると、以下の画面が表示され、無事起動できていることが確認できた。
f:id:konoemario:20170603185035p:plain:w500

ポート番号をデフォルトの8000以外にしたいときは、runserver時にポート番号を指定することで変更できるみたい。

masao-3:sample konoe_mario$ python manage.py runserver 8080

終了したいときはctrl+cで終了する。


HelloWorld

とりあえず、ブラウザから特定のURLにアクセスして、pythonコードで「Hello World」ちっくなことをやってみる。

views.pyの作成

プロジェクトのsample配下に「views.py」を作成する。
この中に、HTTPリクエストから呼ばれる関数を書く。
名前に関しては「views.py」じゃなきゃいけないということではなさそうだけれども、動画の通りに作成した。

masao-3:sample konoe_mario$ tree
.
├── db.sqlite3
├── manage.py
├── sample
   ├── __init__.py
   ├── settings.py
   ├── urls.py
   ├── views.py ←----------これ
   └── wsgi.py


views.pyの内容は以下の通り。
クライアントからのHTTPリクエストでohankywolrd関数が呼ばれ、レスポンスとして「OhankyWorld」を返すだけ。

views.py

from django.http import HttpResponse

def ohankyworld(request):
    return HttpResponse("Ohanky World")

url.pyの編集

さきほど作成した「ohankyworld」関数をURLと紐づける。 プロジェクト作成時に作成されるurl.pyファイルを開くと、urlと関数の紐付けを定義する箇所がある。

ここに作成した、「ohankyworld」を追加する。
view.pyをimportしないと、ohankyworldはurls.pyからは見えないので追加する。

urls.py

from django.conf.urls import url
from django.contrib import admin
#views.pyのohanky関数を呼ぶためにimport
from . import views

urlpatterns = [
    #Djangoの管理者画面。今回触れないんだ。  
    url(r'^admin/', admin.site.urls),
    #ohankyworld/でアクセスすると関数ohankyworldが呼び出される
    url(r'^ohankyworld/', views.ohankyworld),
]

上記設定後に、「http://localhost:8000/ohankyworld」にアクセスしてみれば、「Ohanky World!」と表示される。



簡単なWebページを作成してみる

次にDjangoの基本を理解するために、簡単なWebページを作成してみる。
作成するWebサイトの構成は以下のとおり。

f:id:konoemario:20170603213320p:plain:w500

home.htmlの作成

まず、新規に「templates」ディレクトリを作成して、その配下に「home.html」を置く。

masao-3:sample konoe_mario$ tree
.
├── db.sqlite3
├── manage.py
├── sample
│   ├── __init__.py
│   ├── __pycache__
│   ├── settings.py
│   ├── urls.py
│   ├── views.py
│   └── wsgi.py
└── templates    ←---------------これ
    ├── home.html


home.htmlを抜粋すると以下の通り。

home.html

<h1>おはんきーてすと</h1>
<form action="xxxxx">
        <input type="text"  name="myText1"/>
        <br>
        <input type="text"  name="myText2"/>
        <br>
        <input type="submit" value="Button"/>
    </form>

formタグのAcitonがポイントになるのだけれども、ちょっと話がそれるので一旦xxxxとしています。


templatesの作成

さきほど作成したディレクトリのtemplatesのこと書く。
HelloWorldを試したときは、HttpResoponseとして文字列「Ohanky World!」を返した。

views.py

from django.http import HttpResponse

def onankyworld(request):
    return HttpResponse("Ohanky World")

文字列ではなく、htmlファイルをレスポンスとして返したい場合、templatesの機能を使うことで簡単にできる。

views.py

from django.http import HttpResponse
#templatesの機能を使うのに必要
from django.shortcuts import render

def home(request):
   #render関数に返却するhtmlファイルを指定
   return render(request, 'home.html')

ただし、このままではうまくいかない。
home.htmlがどこにあるを定義する必要がある。


setting.pyの編集

render関数に指定したhtmlファイルがどこにあるかは、プロジェクトを作成した際に自動で作成される「setting.py」で指定する。
「setting.py」のTEMPLATESに、home.htmlを置いたディレクトリ「templates」を指定する。
こうすることで、render関数はtemplates配下のファイルを探しに行ってくれるみたい。

setting.py

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['templates'],     ←-------ここ
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

また忘れずに、「url.py」にもhome.htmlの定義を追加しておく。

urls.py

from django.conf.urls import url
from django.contrib import admin
from . import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    #home.htmlを返す
    url(r'^home/', views.home),
]

ここまでやったら、「http://localhost:8000/home」でhomeにアクセスしてみる。

無事、表示されることが確認できた。

f:id:konoemario:20170603205643p:plain:w500


ohanky.htmlを一旦作成

次にhome.htmlの入力内容を表示するohanky.htmlを作成する。
home.htmlと同じtemplatesディレクトリ配下に作成する。

ohanky.html

<h1>ここに、home.htmlで入力した内容を表示したい</h1>

また、views.py、urls.pyにもohanky.htmlが参照できるように追記しておく。

views.py

from django.http import HttpResponse
#setting.pyでtemplatesの設定をした
from django.shortcuts import render

def home(request):
   return render(request, 'home.html')

def ohanky(request):
   return render(request, 'ohanky.html')

urls.py

from django.conf.urls import url
from django.contrib import admin
from . import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    #home.htmlを返す
    url(r'^home/', views.home),
    #ohanky.htmlを返す
    url(r'^ohanky/', views.ohanky),
]

http://localhost:8000/ohanky」にアクセスするとhtmlの内容が表示される。

f:id:konoemario:20170603215348p:plain:w500


home.htmlからohanky.htmlへの遷移

ここで、さきほど端折ったButtonを押したときのacitonについて書く。 action=“xxxx"だった部分について、以下のように変更する。 昔さわったphpを思い出した。

home.html

<h1>おはんきーてすと</h1>
<form action="{% url  'ohanky' %}">
        <input type="text"  name="myText1"/>
        <br>
        <input type="text"  name="myText2"/>
        <br>
        <input type="submit" value="Button"/>
    </form>

さらに、urls.pyのohaky.html部分のurl関数に引数nameを与える。
このnameが前述のform actionで指定する名前になる。

urls.py

from django.conf.urls import url
from django.contrib import admin
from . import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    #home.htmlを返す
    url(r'^home/', views.home),
    #ohanky.htmlを返す
    url(r'^ohanky/', views.ohanky, name='ohanky'),
]

この状態でButtonを押すと、「ohanky.html」に遷移してくれると思う。


home.htmlの入力内容をohanky.htmlに表示する。

さきほど作成した、ohanky関数にrequest.GETメソッドを追加することでhome.htmlの入力フォームの内容を取得することができる。
これを、render関数の引数にディクショナリ形式で渡す。

views.py

from django.http import HttpResponse
#setting.pyでtemplatesの設定をした
from django.shortcuts import render

def home(request):
   return render(request, 'home.html')

def ohanky(request):
    #request.GETメソッドtextを取得する。
    mytext1 = request.GET['myText1']
    mytext2 = request.GET['myText2']

    #ディクショナリ形式の引数を渡す
    return render(request, 'ohanky.html',{'mytext1':mytext1,'mytext2':mytext2})

ohanky.html側には、「{{ディクショナリのキー}}」を記載することで表示することができる。

ohanky.html

    <h1>
        {{ mytext1}}
        {{ mytext2}}
    </h1>

次回に続きます。
tohutokonsome.hatenablog.com