#pyconjp PyCon JP 2018に参加しました

9/17(日)・18(月・祝)に、大田区産業プラザ PiOで開催された「PyCon JP 2018」に参加しました。
トップ - PyCon JP 2018

去年と同様、無事に参加できてよかったです。
#pyconjp PyCon JP 2017に参加しました - メモ的な思考的な

動画も既に公開されているため、参加できなかったセッションはまたあとで振り返ろうと思います。
PyCon Japan 2018 - YouTube

以下、簡単な感想のメモです。

 
目次

 

参加したセッション

9/17 基調講演「Argentina in Python: community, dreams, travels and learning」 (Kaufmann Manuel 氏)

今年も会場入りが遅れたこともあり、メイン会場が満席で入れませんでした。サテライト会場で聞いていましたが、それでも情熱が伝わってくるセッションでした。

クルマで南米各地を走って6万km超、従来のメディア(TV、ラジオ、新聞)なども使ってPythonの輪を広げていく姿がとても印象に残りました。

何か情熱が出てきたら、それを大切にしようと思いました。

 

9/17 招待講演「東大松尾研流 実践的AI人材育成法」 (中山 浩太郎 氏)

AIまわりの情報と人材育成についての講演でした。

最近データ解析系をさわっているせいか、話についていけてよかったです。

 

Webアプリケーションの仕組み (Takayuki Shimizukawa 氏)

Webアプリケーションを低レイヤーから知りたいと思い、参加しました。

Webアプリケーションやそのまわりのある技術の紹介や、Webアプリケーションを低レイヤーから検証・実装していく内容でした。

個人的には

も分かってよかったです。

 

Building Maintainable Python Web App using Flask (Wonder Chang 氏)

メンテナンスしやすいコードはどんなふうに書けばよいのか気になったので参加しました。

Maintainability = readability (Hard) + extensibility (Hard) + testability (Easy) ということで、テストが重要と改めて感じました。

テストコードはpytestで書かれ、

  • Wrapper Pattern in Unit Testing
  • Integration Test of Wrapper

などのパターン集の紹介もありました。

あとでスライドを読み返すなどして、パターンを理解しようと思いました。

 

メルカリにおける AI 活用事例 (千葉 竜介 氏)

AIを実際に活用している現場を知りたくて参加しました。

メルカリのTeam AIでの

が印象に残りました。

 

Pythonで解く大学入試数学 (新井 正貴 氏)

Pythonでどうやって解いていくのか気になって参加しました。

Sympyを使うことで、分かりやすい数式へと変換する例を見ることができてよかったです。

また、いろいろ試すのにはJupyter Notebook便利と改めて感じました。

 

9/18 基調講演 「Pythonでやってみた」:広がるプログラミングの愉しみ (磯 蘭水 氏)

2日目の基調講演では、車輪の再実装をおそれないことを学びました。

また、作りたいものをどのような構成でシステムに落としてこんでいくかの説明があり、参考になりました。他の方が頭の中で考えているものを見る機会がなかなかないためです。

あとは、「学習する能力は、知識・経験量に比例する」とのことなので、今後何か気になったことがあれば、手を動かして実装・理解していきたいと感じました。

 

Migrating from Py2 application to Py3: first trial in MonotaRO (増田泰 氏)

仕事でPythonを使っているとPython2系に出会うこともあるため、移行について知りたいと思い参加しました。

Python2から3へ移行する時にトライしたこととやめたこと、どのように移行を進めようとしているのかの説明があり、参考になりました。

 

JVM上で動くPython3処理系cafebabepyの実装詳解 (澁谷 典明 氏)

Pythonのコードをどのように解析しているのか気になり、参加しました。

今まではPythonの標準モジュールastでの解析しかやったことがなかったので、Java + ANTRL v4でもできると知ったのが収穫でした。

また、難しくなりそうな話題をきちんと解説してくださり、処理系実装の理解を深められました。

 

Artisanal Async Adventures (Jonas Obrist 氏)

asnycモジュールを雰囲気で使っていたため、より具体的な内容を知りたいと思い、参加しました。

スライドはほぼなく、ライブコーディングで進みました。async/awaitの利用前後のコードが分かり、ためになりました。

また、英語で分からない部分があっても、コードを見れば分かったのでよかったです。

 

C拡張と共に乗り切るPython 2→3移行術 (末田 卓巳 氏)

今のところC拡張に出会う機会はないのですが、Python2からの移行が気になったので参加しました。

C拡張を移行する時のつらさやその解決方法が説明されていてよかったです。

難しい内容になるのかなと思いましたが、リラックスした雰囲気を作り出していて、理解しやすかったです。

 

料理写真が美味しく撮れる! 開発現場から覗くAI料理カメラの裏側 (森永 雄也 氏)

AI料理カメラが気になって参加しました。

あらかじめタグ付けされたデータがあるとだいぶ楽になりそうな一方、SNS特有のノイズもあるのが大変そうでした。

あとは、学習した時のマシンの紹介もあり、金の弾丸を撃てれば個人でも試せそうでした。

 

その他

同僚のスピーカー

会社の同僚3名がスピーカーとして登壇していました。

 
いろいろ準備している姿を見ていたこともあり、無事にセッションを終えたようで何よりでした。

 

pytestを使ったライブコーディング

翔泳社のブースにて、書籍「テスト駆動Python」の訳者・安井さんによる、pytestを使ったライブコーディングがありました。

pytestの書き方も当然のことながら、

  • vimを使いこなしている姿
  • テストコードの書き方
    • テストコード間の依存性は少なくする
    • テストコードはシンプルにする
      • 後で見返した時に分かりづらくなるのを避ける
      • テストコード用共通ライブラリはなるべく作らない
      • テストで使う値とかも、ベタ書きする
  • 実装が分かりきっているところは、テストを後から書いても良い
  • 日本語話者のみでメンテナンスしていくのであれば、テストメソッド名が日本語だとわかりやすい

など、いろいろと参考になりました。

 

Python Boot Camp

2017年にTAとして2回参加した縁で、Python Boot CampのTシャツをいただきました。ありがたい & 嬉しかったです。

 

 

パーティ

いろいろな方とお話ができてよかったです。世界が狭いと感じる出来事もあり、楽しめました。

特に、Python Boot Campに「Pythonを学びたい」と言って参加された方が、その後いろいろと動かれて、現在はPythonメインで書いていると聞けたことが印象に残っています。

やる気をもって行動することの大事さを、改めて感じました。

 

WiFi環境が良好

会場は広かったのですが、1〜6階を往復しても、同じSSIDでネットワークが途切れることなく利用できました。

あの人数をさばくのにどのような構成になっていたのかが気になりました。

CONBUをはじめとしたNetwork Sponsorのみなさま、ありがとうございました。

 
最後になりましたが、PyCon JP 2018を運営してくださったみなさま、ありがとうございました。

「PythonユーザのためのJupyter実践入門」を写経しながら読みました

最近、Python機械学習やデータサイエンス方面で使う機会がありました。

これまでPythonをWebアプリで使うことが多かったこともあり、最初のうちは各種ツールを雰囲気で使っていました。

ただ、使っていくうちに、ツールを基本的なところから理解したくなりました。

そこで、知りたいツールが網羅されている「PythonユーザのためのJupyter実践入門」を写経しつつ読みました。

PythonユーザのためのJupyter[実践]入門

PythonユーザのためのJupyter[実践]入門

 

感想

本書では、自分の知りたかった

  • Anaconda
  • Jupyter Notebook
  • Pandas
  • Matplotlib

が基本的なところから書かれていました。

そのため、雰囲気で使っていた

  • condaコマンドで仮想環境を作る方法
  • Jupyter Notebookのメニューやボタンの意味
  • Matplotlibの日本語文字化けを解消する方法
  • Matplotlibで図を描画する方法
    • フィギュアとサブプロットの概念(p140)

などがおさえられました。

 
特に良かったのが、Matplotlibの書き方について

  • MatplotlibにはMATLAB-styleとOOP-Styleの2つの書き方がある
  • 本書ではOOP-Styleの書き方を推奨する

と書かれていたことです(p253)。

今までMatplotlibにはいろいろな関数があって分かりづらいと感じていたことが、それが実はスタイルが2つあるせいだと分かりました。

そのため、今後は「Webにあるサンプルはどちらで書かれているか?」が判断でき、悩むことなくサンプルを読み進められそうでした。

 
また、本書の推奨するOOP-Styleであれば、どの図形を描くときも

# フィギュアを用意する
fig = plt.figure()

# サブプロットを用意する
ax = fig.add_subplot(111)

# 図形をプロット
ax.<描画メソッド>

# タイトルやラベルなどをつける
ax.set_title('foo')

# 描画
plt.show()

という流れで書いていけそうでした。

 
3章以降は写経しながら読み進めましたが、進めるうちにコード補完が欲しくなりました。そのため、PyCharm + Jupyter Notebookに切り替えました。
Using IPython/Jupyter Notebook with PyCharm - Help | PyCharm

見栄えは劣るものの、日頃PyCharmを使っていることもあり、切り替えて以降はスムーズに写経できました。

 
一方、Pandasについてはまだ理解しきれていないと感じたため、本書以外の書籍や自分好みのデータを探して、引き続き手を動かしていこうと思います。

 
最後になりましたが、著者のみなさま、良い本をありがとうございました。

Jupyter Notebookで「TypeError: __init__() got an unexpected keyword argument 'io_loop'」エラーのため、コードが実行できない

$ jupyter notebook でJupyter Notebookを起動したところ、

  • コードを入力し、Ctrl + Enter を押してもコードが実行されない
  • Jupyter Notebookの右側に connected が青点滅している

ことがありました。

 
その時の対応をメモします。

 
目次

 

環境

  • Mac OSX 10.11.6
  • Anaconda 3.5.1
    • pyenvでインストール

 

調査

Jupyter Notebookを起動したターミナルを見ると、以下のエラーが繰り返し出ていました。

[I 10:04:58.541 NotebookApp] Adapting to protocol v5.1 for kernel b8c41504-eb3c-4aa2-b879-c650ba061127
[E 10:04:58.542 NotebookApp] Uncaught exception in /api/kernels/b8c41504-eb3c-4aa2-b879-c650ba061127/channels
    Traceback (most recent call last):
      File "/Users/foo/.pyenv/versions/anaconda3-5.1.0/envs/jupyterbook-env/lib/python3.6/site-packages/tornado/websocket.py", line 498, in _run_callback
        result = callback(*args, **kwargs)
      File "/Users/foo/.pyenv/versions/anaconda3-5.1.0/envs/jupyterbook-env/lib/python3.6/site-packages/notebook/services/kernels/handlers.py", line 262, in open
        super(ZMQChannelsHandler, self).open()
      File "/Users/foo/.pyenv/versions/anaconda3-5.1.0/envs/jupyterbook-env/lib/python3.6/site-packages/notebook/base/zmqhandlers.py", line 176, in open
        self.send_ping, self.ping_interval, io_loop=loop,
    TypeError: __init__() got an unexpected keyword argument 'io_loop'

 
このエラーを調べたところ、GitHubのissueに情報がありました。
Unable to start Jupyter · Issue #3202 · jupyter/notebook

For now, you'll need to downgrade to tornado 4.x. We'll be making a new notebook release soon that avoids this error.

https://github.com/jupyter/notebook/issues/3202#issuecomment-357199274

とのことです。

 
そのため、 conda list で手元のtornadoのバージョンを調べてみました。

$ conda list
# packages in environment at /Users/foo/.pyenv/versions/anaconda3-5.1.0/envs/jupyterbook-env:
#
# Name                    Version                   Build  Channel
...
tornado                   5.0.2            py36h1de35cc_0  
...

tornadoは5系でした。

 

対応

tornadoを4系の最新版にします。
https://pypi.org/project/tornado/#history

$ conda install tornado=4.5.3

 
Jupyter Notebookを起動します。

$ jupyter notebook

 
コマンドを実行してみると、問題なく動作しました。ターミナルにもエラーが出ていません。

jQuery1.6.0より、属性がない時のattr()の戻り値が、空文字からundefinedへと変更されていた

jQueryのバージョンを上げる時に出くわしたのでメモを残します。

 
目次

 

環境

 

現象

jQueryattr() を使って属性の値を取得しているコードのうち、

<p>Hello World</p>

<script>
    // class属性がないのに attr() を使っている
    result = $('p').attr('class');
</script>

と、属性が定義されていない場合、後続の処理で予期しない挙動をしていました*1

 

原因

jQuery1.6.0より、 attr() の戻り値が変わっていました。

属性が定義されていない場合の attr() の戻り値が

  • 〜1.5.2:空文字
  • 1.6.0〜:undefined

へと変わっていました。

 
公式ドキュメントの attr() でも undefined が返ってくるとの記載がありました。
.attr() | jQuery API Documentation

 
これより、元のソースコードは空文字が返ってくると想定していたようなので、undefinedが返ってきて想定外の挙動になったようでした。

 
jQuery1.6〜では、 attr()prop() に関する変更も入っているので、合わせてこのあたりも修正されたのでしょうか。
jqueryのattrとprop(とdataとvalとhtmlとかつまり属性とかの取得)の使い分け一覧 - Qiita

 

実験

ためしに

result = $('p').attr('class');
console.log(result);
console.log(typeof result);

を試してみたところ、以下の結果となりました。

 
jQuery1.5.2

f:id:thinkAmi:20180703123106p:plain

 
jQuery1.6.0

f:id:thinkAmi:20180703123123p:plain

 

ソースコード

GitHubに上げました。 attr_undefined ディレクトリの中が今回のファイルです。
https://github.com/thinkAmi-sandbox/jQuery-misc

*1:クラス名をattr()で取得するのが良いのかは、ここでは置いておきます

DjangoのModelのFieldのオプション null と blank の違いについて

DjangoにてDBのNOT NULL 制約を外したい場合、nullとblankのどちらを使えばよいのか忘れることがあるため、メモしておきます。

 
目次

 

環境

 

違い

stackoverflowにありました。
python - differentiate null=True, blank=True in django - Stack Overflow

 
公式ドキュメントの場合はこのあたり。
https://docs.djangoproject.com/en/2.0/ref/models/fields/#null

  • null オプションは、NOT NULL 制約を外す
    • 基本、何も値を与えなければNULLをセット
    • ただし、CharField型などの文字型については、NULLではなく空文字をセット
      • このため、 blank=True かつ unique=True の場合は、 null=True にしないと空文字が複数回登場することになり、エラーとなる
    • BooleanFieldでNULLを扱いたい場合は、 NullBooleanField にする
  • blank オプションは、保存時に何もデータを渡さなくても、バリデーションでOKとする (値の任意入力化)

でした。

 

確認

stackoverflowではPostgreSQLMySQLで試していたので、今回はSQLiteで試してみます。

Model

今回は

  • CharField
  • IntegerField
  • DateTimeField
  • BooleanField
  • NullBooleanField

なModelを用意します。

class NullBlank(models.Model):
    charNull = models.CharField(max_length=10, null=True)
    charBlank = models.CharField(max_length=10, blank=True)
    charNullBlank = models.CharField(max_length=10, null=True, blank=True)
    charBlankUnique = models.CharField(
        max_length=10, blank=True, unique=True,
    )
    charNullBlankUnique = models.CharField(
        max_length=10, null=True, blank=True, unique=True,
    )

    intNull = models.IntegerField(null=True)
    intBlank = models.IntegerField(blank=True)
    intNullBlank = models.IntegerField(null=True, blank=True)

    dateNull = models.DateTimeField(null=True)
    dateBlank = models.DateTimeField(blank=True)
    dateNullBlank = models.DateTimeField(null=True, blank=True)

    boolNull = models.BooleanField(null=True)
    boolNullBlank = models.BooleanField(null=True, blank=True)
    boolBlank = models.BooleanField(blank=True)

    nullboolNull = models.NullBooleanField(null=True)
    nullboolBlank = models.NullBooleanField(blank=True)
    nullboolNullBlank = models.NullBooleanField(null=True, blank=True)

 

マイグレーションファイルの作成

BooleanFieldがNULLを許可しないため、マイグレーションファイル作成時にエラーとなりました。

$ python manage.py makemigrations nullblank
nullblank.NullBlank.boolNull: (fields.E110) BooleanFields do not accept null values.
  HINT: Use a NullBooleanField instead.
nullblank.NullBlank.boolNullBlank: (fields.E110) BooleanFields do not accept null values.
  HINT: Use a NullBooleanField instead.

 
それらのFieldをコメントアウトして、再度実行すると、マイグレーションファイルができました。

$ python manage.py makemigrations nullblank
Migrations for 'nullblank':
  nullblank/migrations/0001_initial.py
    - Create model NullBlank

 

sqlmigrateで、発行されるSQLを確認

CREATE TABLEのところを整形して見やすくしてみます。

$ python manage.py sqlmigrate nullblank 0001
BEGIN;
--
-- Create model NullBlank
--
CREATE TABLE "nullblank_nullblank" (
  "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, 
  "charNull" varchar(10) NULL, 
  "charBlank" varchar(10) NOT NULL, 
  "charNullBlank" varchar(10) NULL, 
  "charBlankUnique" varchar(10) NOT NULL UNIQUE, 
  "charNullBlankUnique" varchar(10) NULL UNIQUE, 
  "intNull" integer NULL, 
  "intBlank" integer NOT NULL, 
  "intNullBlank" integer NULL, 
  "dateNull" datetime NULL, 
  "dateBlank" datetime NOT NULL, 
  "dateNullBlank" datetime NULL, 
  "boolBlank" bool NOT NULL, 
  "nullboolNull" bool NULL, 
  "nullboolBlank" bool NULL, 
  "nullboolNullBlank" bool NULL
);
COMMIT;

 
いい感じです。

 

マイグレーションファイルを適用
$ python manage.py migrate nullblank
Operations to perform:
  Apply all migrations: nullblank
Running migrations:
  Applying nullblank.0001_initial... OK

 

データの投入

実際にデータを投入してみます。

以下のようなDjangoコマンドを用意します。

# management/commands/null_blank.py

from django.core.management import BaseCommand
from django.utils import timezone

from nullblank.models import NullBlank

class Command(BaseCommand):
    def handle(self, *args, **options):
        NullBlank().save()

 
投入してみます。

$ python manage.py null_blank

sqlite3.IntegrityError:
NOT NULL constraint failed: nullblank_nullblank.intBlank
NOT NULL constraint failed: nullblank_nullblank.dateBlank
NOT NULL constraint failed: nullblank_nullblank.boolBlank

blank=True なカラムがエラーとなりました。

 
そのため、

NullBlank(
    intBlank=0,
    dateBlank=timezone.now(),
    boolBlank=True,
).save()

とすると、登録できました。

 
しかし、もう一度実行すると、

$ python manage.py null_blank

sqlite3.IntegrityError:
  UNIQUE constraint failed: nullblank_nullblank.charBlankUnique

と出ました。

UNIQUE制約のある列で、空文字が重複したようです。

 
そのため、最終的には以下となります。

NullBlank(
    intBlank=0,
    dateBlank=timezone.now(),
    boolBlank=True,
    charBlankUnique=timezone.now().strftime('%Y/%m/%d %H:%M:%S')
).save()

 

SQLiteCLIでデータを確認

SQLiteCLIを使い、実際のデータベースの中身を見てみます。
https://www.sqlite.org/cli.html

手元のMacではsqlite3コマンドがインストール済なので、それを利用します。

# SQLite3ファイルがある場所へと移動
$ ls -a db.sqlite3
db.sqlite3

# データベースファイルに対し、CLIを起動
$ sqlite3 db.sqlite3 
SQLite version 3.16.2 2017-01-06 16:32:41
Enter ".help" for usage hints.

# ヘッダを付けて表示
sqlite> .header on

# カラムモードで表示
sqlite> .mode column

# NULLの場合は、"NULL" という文字を出力
# こうしないと、空文字とNULLの違いが分からないため
sqlite> .nullvalue NULL

# CharField関係
sqlite> select id, charNull, charBlank, charNullBlank, charBlankUnique, charNullBlankUnique from nullblank_nullblank;
id          charNull    charBlank   charNullBlank  charBlankUnique  charNullBlankUnique
----------  ----------  ----------  -------------  ---------------  -------------------
1           NULL                    NULL                            NULL               
2           NULL                    NULL           2018/06/20 08:4  NULL  

# IntegerField関係
sqlite> select intNull, intBlank, intNullBlank from nullblank_nullblank;
intNull     intBlank    intNullBlank
----------  ----------  ------------
NULL        0           NULL        
NULL        0           NULL  


# DatetimeField関係
sqlite> select dateNull, dateBlank, dateNullBlank from nullblank_nullblank;
dateNull    dateBlank                   dateNullBlank
----------  --------------------------  -------------
NULL        2018-06-20 08:39:24.738832  NULL         
NULL        2018-06-20 08:48:50.256008  NULL 


# BooleanField関係
sqlite> select boolBlank, nullboolNull, nullboolBlank, nullboolNullBlank from nullblank_nullblank;
boolBlank   nullboolNull  nullboolBlank  nullboolNullBlank
----------  ------------  -------------  -----------------
1           NULL          NULL           NULL             
1           NULL          NULL           NULL   

 

ソースコード

GitHubに上げました。 nullblank アプリケーション以下が、今回のファイルになります。
https://github.com/thinkAmi-sandbox/Django20-sample

Django2.0のプロジェクトのurls.pyにおける、include()での引数namespaceについて調べてみた

Django 2.0にて、プロジェクトの urls.py

from django.urls import path, include

urlpatterns = [
    path('old/', include('myapp.urls', namespace='old')),
    ...
]

アプリの urls.py

from django.urls import path
from django.views.generic import TemplateView

urlpatterns = [
    path('namespace/',
         TemplateView.as_view(template_name='myapp/url_with_include.html'),
         name='without_app'),
]

としたところ、以下のエラーが発生しました。

'Specifying a namespace in include() without providing an app_name '
django.core.exceptions.ImproperlyConfigured: Specifying a namespace in include() without providing an app_name is not supported. 

Set the app_name attribute in the included module, or pass a 2-tuple containing the list of patterns and app_name instead.

 
この時に調査・対応したことをメモしておきます。

 
目次

 

環境

 

調査

エラーメッセージより、

  • includeで呼ばれるモジュールに、 app_name を設定
  • (URLパターン, 名前空間) のタプルをinclude()に渡す

のどちらかを実装すれば良さそうでした。

 
また、 django.urls.include() の公式ドキュメントを見たところ、引数 namespace を使いたい場合の記載もありました。
include() | django.urls functions for use in URLconfs | Django documentation | Django

ただ、エラーメッセージで示されたどちらかの実装をしていないと、引数 namespace が使えないようです。

 
次に、いつから app_name が導入されたのかを調べてみたところ、 1.9のようでした。
https://docs.djangoproject.com/en/1.9/topics/http/urls/#url-namespaces-and-included-urlconfs

1.8以前は、include()の引数に namespace を使う方法が書かれていました。
https://docs.djangoproject.com/en/1.8/topics/http/urls/#url-namespaces-and-included-urlconfs

 
チュートリアルも、1.9から書き直されていました。

 
最後に、いつ頃からエラーが発生する用になったのかと思い、挙動を確認したところ、

でした。

Django 2.0のリリースノートにある

Support for setting a URL instance namespace without an application namespace is removed.

https://docs.djangoproject.com/en/2.0/releases/2.0/#features-removed-in-2-0

のあたりが関係していそうでした。

 
以上より、冒頭のソースコードはDjango2.0で1.8時代の実装をしていたために発生していたようでした。

 

対応

調査方法でも書きましたが、主な対応方法として以下の3つが考えられました。

  • プロジェクトのurls.pyのinclude()では namespace 引数を使用せず、アプリのurls.pyに app_name を追加
  • プロジェクトのurls.pyのinclude()の引数として、 (アプリのurls.py, 名前空間) となるタプルを渡す
  • プロジェクトのurls.pyのinclude()に namespace 引数を使用し、アプリのurls.pyでも app_name を追加

 
どの方法が良いのかと思い、Djangoの公式チュートリアルを見たところ、アプリのurls.pyに app_name を追加する書き方をしていました。
https://docs.djangoproject.com/ja/2.0/intro/tutorial03/#namespacing-url-names

そのため、特に制限がなければ、アプリのurls.pyに app_name を書くのが良いのかなと思いました。

 

動作確認

今回は、url テンプレートタグを使った時にいずれの方法でもURLリバースできるか にて確認してみます。

 

実装

まず、プロジェクトのurls.pyは

urlpatterns = [
    # アプリのurls.pyに app_name 設定があるパターン
    path('with/', include('myapp.urls_with_app_name')),

    # アプリのurls.pyには app_name 設定がなく、includeの引数をタプルにして名前空間を渡すパターン
    # (urlpatternsのあるモジュール, 名前空間)
    path('without/', include(('myapp.urls_without_app_name', 'without'))),

    # アプリのurls.pyに app_name 設定があるが、名前空間を別に用意するパターン
    path('over/', include('myapp.urls_overwrite_app_name', namespace='replaced')),
    # アプリのurls.pyの app_name 設定をそのまま使うパターン(再掲)
    path('not_over/', include('myapp.urls_overwrite_app_name')),
]

とします。

 
次に、app_nameのあるアプリのurls.pyはそれぞれ

myapp.urls_with_app_name.py

app_name = 'with'

# urlpatternsは、他も同じなので、以下省略
urlpatterns = [
    path('namespace/',
         TemplateView.as_view(template_name='myapp/url_with_include.html'),
         name='with_app'),
]

 
myapp.urls_overwrite_app_name.py

app_name = 'overwrite'

とします。

 
あとは、テンプレートを

<ul>
    <li><a href="{% url 'with:with_app' %}">app_name がある場合</a></li>
    <li><a href="{% url 'without:without_app' %}">app_name がない場合</a></li>
    <li><a href="{% url 'replaced:over_app' %}">app_name が上書きされた場合</a></li>
    <li><a href="{% url 'overwrite:over_app' %}">app_name がそのままの場合</a></li>
</ul>

として用意します。

 

動作確認

開発サーバを起動し、curlでアクセスしてみると、いずれの方法でもURLリバースできていました。

$ curl -L http://localhost:8000/with/namespace
<!DOCTYPE html>
...
<ul>
    <li><a href="/with/namespace/">app_name がある場合</a></li>
    <li><a href="/without/namespace/">app_name がない場合</a></li>
    <li><a href="/over/namespace/">app_name が上書きされた場合</a></li>
    <li><a href="/not_over/namespace/">app_name がそのままの場合</a></li>
</ul>

 

ソースコード

GitHubに上げました。
https://github.com/thinkAmi-sandbox/Django20-sample

Mac + Polipoで、SSHの動的フォワード(SOCKS) に HTTP Proxy をつなげてみた

よく分からないタイトルですが...

同一LAN内で、以下の論理構成とした時に、AndroidでアクセスしたときもRaspberry Piからのアクセスに見えるかどうかを試した時のメモです。

Android
|
(HTTP Proxy、ポート 23000)
|
Mac + polipo
|
(ssh -N -D 0.0.0.0:16000)
|
Raspberry Pi
|
Windows (Web Server)

 
物理的な構成は、

Android
|
(WiFi)
|
WiFiアクセスポイント
|
(有線LAN)
|
スイッチングハブ
|
(有線LAN)
|
Raspberry Pi, Windows, Mac

です。

 
目次

 

環境

  • Android 7.0系
    • 192.168.10.100
  • Mac OS X 10.11.6
    • LUA4-U3-AGTにて有線化
    • 192.168.10.99
    • Polipo 1.1.1
  • Raspberry Pi 2 Model B
    • Rasbpian
    • Macからssh可能
    • 192.168.10.201
  • Windows10
    • Python 3.6系
    • Webサーバは、Pythonpython -m http.server 9000 で実行中
    • 192.168.10.105

 
polipoを選んだ理由ですが、SOCKSをHTTPに変換できるProxyを探してみたらヒットした、というだけです。

ただ、今はメンテナンスも終了しているため、他に同じようなのがあれば、教えていただけるとありがたいです。
https://github.com/jech/polipo

 

準備

MacからRaspberry PiSSH可能にする

以前と同じようにセットアップします。
Raspberry Pi 2 Model B + docker-compose上に、Django + PostgreSQLなアプリをデプロイしてみた - メモ的な思考的な

 
内容としては

です。

 

Polipo

Macの場合、Homebrewでインストールします。

$ brew install polipo

 

Polipoの設定ファイルを作成

今回は

  • SOCKS5をHTTPにする、HTTP Proxyを作成
    • HTTP Proxyのポートは、 23000
  • キャッシュしない
  • 設定ファイルは ~/polipo_config/config として保存

とします。

# IPv4のみ接続可能
proxyAddress = "0.0.0.0"

# HTTP Proxyのポート
proxyPort = 23000

proxyName = "localhost"

# SOCKS Proxyの設定:SOCKSのポートは16000
socksParentProxy = "127.0.0.1:16000"
socksProxyType = socks5

# キャッシュをしない
maxDiskCacheEntrySize = 0 
diskCacheUnlinkTime = 0 

# 名前解決にシステムデフォルトのDNSを使う
dnsUseGethostbyname = true

# リダイレクトキャッシュをしない
dontCacheRedirects = true

# Cookieキャッシュをしない
dontCacheCookies = true

 
設定ファイルの詳細は、以下が参考になりました。

 

各機器の起動

Windows上のWebサーバ起動
python -m http.server 9000

 
Webサーバが動作しているか、Macからcurlでアクセスしてみます。

$ curl http://192.168.10.105:9000
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
...

 
Windowsコマンドプロンプト上にも、アクセスした時のMacIPアドレスが記録されました。

Serving HTTP on 0.0.0.0 port 9000 (http://0.0.0.0:9000/) ...
...
192.168.10.99 - - [12/Jun/2018 21:30:53] "GET / HTTP/1.1" 200 -

 
Androidもブラウザでアクセスしてみると、Windowsコマンドプロンプト上にログが残ります。

192.168.10.100 - - [12/Jun/2018 21:32:54] "GET / HTTP/1.1" 200 -
192.168.10.100 - - [12/Jun/2018 21:32:54] code 404, message File not found
192.168.10.100 - - [12/Jun/2018 21:32:54] "GET /favicon.ico HTTP/1.1" 404 -

 

MacからRaspberry PiSSH + 動的フォワード(SOCKS)

動的フォワード(SOCKS)となるように、SSHします。念のため v オプションを付けて進行状況が分かるようにしておきます。

ssh -N -D 0.0.0.0:16000 pi@192.168.10.201 -v

 
今回はパスワード認証のため、途中でパスワードを入力します。

pi@192.168.10.201's password: 
debug1: Authentication succeeded (password).

 

Mac上のPolipoを起動

SOCKSのポートにHTTP Proxyをつなぎ、ポート 23000 でHTTPアクセスを受け付けるようにします。

$ polipo -c ./polipo_config/config 
Established listening socket on port 23000.

なお、今回はPolipoを起動したときのみ、HTTP Proxyとして動作させるようにしました。停止は Ctrl + C です。

 

AndroidのProxy設定を変更

WiFi設定にて、Proxy設定を行います。

なお、手元のAndroidではHTTP Proxyしか存在しなかったので、実験に好都合です。

 
AndroidのProxy設定は、

  • プロキシ:手動
  • プロキシのホスト名:MacIPアドレス
    • 192.168.10.101
  • プロキシポート:polipoで設定したHTTP Proxyのポート
    • 23000

とします。

 
HTTP Proxyに変更後、AndroidのブラウザでWindows上のWebサーバへアクセスします。

コマンドプロンプトのログを見てみると、Raspberry Piのものになっていました。

192.168.10.201 - - [12/Jun/2018 21:49:44] "GET / HTTP/1.1" 200 -

 
期待通りの環境ができました。