サイドバーを設置したい
https://getbootstrap.com/docs/4.3/examples/dashboard
構造を理解する
参考先の構造は、以下のようなものだった。
<div class="container-fluid"> <nav サイドバーの役割></> <main メインの役割></> </div>
ここで新たにわかったことはメインタグはセクショニングコンテントではないので、ドキュメントのアウトラインに影響を与えない。
sqlite:DBに格納されたテーブルデータを確認する方法
djangoでは開発時にsqlite3が使われる。このDBに直接接続し、データを入れてみようと思った。そのときにテーブル名が必要になる。どのような名前のテーブルか
参考文献:https://crimnut.hateblo.jp/entry/2018/04/17/172709
import sqlite3 con = sqlite3.connect("mydb.sqlite") cursor = con.cursor() cursor.execute("select * from sqlite_master where type='table'") for x in cursor.fetchall(): print(x) con.close()
調べたいことはいろいろあるけど、とりあえずこのコードで 実行できた。
追記:SQLite3のテーブル確認だけしたいならDB Brouser(SQLite)を使えばコマンドを覚えておく必要はなくなるのでおすすめ。
補足情報:http://torajirousan.hatenadiary.jp/entry/2019/03/03/024103
ヘッダーとフッターのデザイン
ヘッダーとフッターのデザイン
わかったことがある。
スマホ等に画面を変更した場合、背景色があるヘッダーやフッターはフルで背景色が広がらない。
とても見栄えが悪い。ヘッダー、フッターは必ずフル画面に広がる設定でデザインすること。
bootstrap4では以下のクラスが役に立つ。
class="container-fluid"
sqlite3:エラー対処:sqlite3.OperationalError: near ")": syntax error
sqlite3.OperationalError: near ")": syntax error
このエラーがたまに出て、足止めをくらうのでメモにしておく。
コード cur.execute("INSERT INTO mydb VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,)", (url,title,description,price,None,None,None,None,None,None,None,None,None,None))
エラーの意味が最初理解出来なかった。 これは閉じ括弧周辺に構文エラーがあることを示す。 今回は閉じ括弧の前に,をおいていることが原因だった。修正すると以下になる。
コード cur.execute("INSERT INTO mydb VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)", (url,title,description,price,None,None,None,None,None,None,None,None,None,None))
sqlite3データベースの変更
sqlite3で既存のデータベースを修正したデータベースを作る必要が出てきた。
この作業は自分にとってヘビーだったので楽してできる方法を考る。今後同じケースにあたったときのために。
今回の方法
今回は別のファイルを作成した。拡張子がない”mydb”がファイル名だったが、これだとSQLAlchemyが読み込こまない問題が発覚した。
そこで"mydb.sqlite"と名付け、データをコピーすることになった。
工程
データベースファイルmydb.sqliteを作成
テーブルを作成
前データベースからデータ読み込み、リスト型データ化、新データベースに更新登録(カラムを分けて実施)
これが時間かかった。次回からはインサート一本で行う方法を用いる。
改善工程
データベースファイルmydb.sqliteを作成
前データベースからデータをcsv化する
テーブル作成
インサート
SQLAlchemy:エラー対処:sqlalchemy.exc.ArgumentError: Unknown arguments passed to Column: ['precision', 'asdecimal']
エラーコード sqlalchemy.exc.ArgumentError: Unknown arguments passed to Column: ['precision', 'asdecimal']
コード class Product(Base): __tablename__ = 'mydb_jp' description = Column(Text) price = Column(Float, precision=(10,0), asdecimal=True)
どうやらFloatからエラーが発生している。sqlalchemyの場合Floatを使ってdecimalのデータ型に変更するようだ。 でdecimalのデータ型に変更する際に引数がいる。そういうわけで引数を書いたら知らない引数が渡されています、とエラーが出てしまった。解消しだい更新するとする。
SQLAlchemy:エラー対処:sqlalchemy.exc.OperationalError: (OperationalError) unable to open database file None None
エラーメッセージ:
sqlalchemy.exc.OperationalError: (OperationalError) unable to open database file None None
エラーが出た背景
sqlite3のデータベースを読み込みいろいろ変更しようとする際に起きた。
エラー部分 engine = create_engine('sqlite:///mydb')
原因解消
どうやらsqlite3のデータベースを開くことが出来ないことに起因するエラーだった。sqlite3のデータベースを拡張子なしで作ってしまったことが原因だった。新たにmydb.sqliteというファイル名でデータベースを作成し、以下のコードを書いた結果エラーは出なかった。
engine = create_engine('sqlite:///mydb.sqlite')
したがってsqliteでデータベースを作成する際は拡張子をちゃんと書くのが教訓となった。
参考:sqlite3のデータベースの作り方 import sqlite3 connection = sqlite3.connect(dbpath) # 例えばconnectの引数として"mydb"とした場合でmydbがなければ、新たにmydbというファイルが作成される。 ''' 新たにdbファイルを作る場合、拡張子がなくても作ることができる。しかし他のモジュール利用のためにも.sqlite等の拡張子をつけるべき。'''
SQLAlchemy
参考文献:https://it-engineer-lab.com/archives/1183
sqlite3とdjangoのmodelsとSQLalchemyの共通点と差異についてメモできるとよい。
sqliteはそもそもすべてのデータをテキストのデータとして保存している。。。?
https://blog.ohgaki.net/sqlite-data-type-specification
データ型について
floatとdecimal
https://docs.sqlalchemy.org/en/latest/core/type_basics.html#sqlalchemy.types.Float
floatとdecimalはFloatを使って表現する。
Floatのasdecimal=Trueに設定した場合にDecimalとなる。
djangoの場合は
TextFieldがあるけどこれはどうなっているのか。
SQLAlchemyの場合でもTextがあるようだ。
djangoの場合はDecimalFieldで登録していたけど、
sqlite3は便宜的にIntegerにしてた。これは問題ないか?
sqlalchemyの場合は、Floatでasdecimal=Trueにすればdecimalが使える。
モデルの作成について
分かったこと。 データベースのテーブルデータを呼び出すためにモデルの作成がある。
例えばsqlite3のテーブルデータを呼び出すには、sqlite3のテーブルを自分で作成する必要がある。
その作り方はBaseモデルを継承して呼び出したいテーブルに似せたものを作る。この工程はdjangoのテーブルを作成する方法に似ている。
from sqlalchemy.ext.declarative import declarative_base # まずベースモデルを生成します Base = declarative_base() # 次にベースモデルを継承してモデルクラスを定義します class Product(Base): __tablename__ = 'mydb' title = Column(String(255)) description = Column(Text) price = Column(Integer)
わかったことは実際にテーブルに存在しないアトリビュートをモデルクラスとして定義すると、エラーが出てしまう。したがってテーブルのカラム名を正確に記述しなければいけない。
CRUDについて
SQL文の実行においてsqlite3でcur.execute()メソッドを使う。
SQLAlchemyの場合はsession.[sessionのメソッド名]()という形で行う。
例えば以下のようになる。
- add() -- INSERT
- query() -- SELECT
- filter() -- WHERE
ドキュメント(チュートリアル)
https://www.pythoncentral.io/migrate-sqlalchemy-databases-alembic/
sqlalchemyはAlembicを使って動かしているようだ。
psycopg2についてメモ
分からないことは、pysopg2とpsycopg2-binaryの違い。これはどうやって使い分けるのか。コンパイラや外部のライブラリ等を必要としないのがバイナリの方らしい。
そういう使い分け。
You can also obtain a stand-alone package, not requiring a compiler or external libraries, by installing the psycopg2-binary package from PyPI:
https://pypi.org/project/psycopg2/
コードを走らせる際に必要なライブラリがあるらしい。それがlibpqなるもののようだ。これをbinaryバージョンは別に準備しなくても実行できるってことのようだ。
psycopg2ドキュメントから得たい情報
http://initd.org/psycopg/docs/
自分が知る必要がある情報
一通りの流れのコマンド
csv読み込んでアップデートする方法
csv読み込んでインサートする方法
sqlite3,postresqlのsql文とどう違っているのか
一通りの流れのコマンド
http://initd.org/psycopg/docs/usage.html#basic-module-usage
>>> import psycopg2 # Connect to an existing database >>> conn = psycopg2.connect("dbname=test user=postgres") # Open a cursor to perform database operations >>> cur = conn.cursor() # Execute a command: this creates a new table >>> cur.execute("CREATE TABLE test (id serial PRIMARY KEY, num integer, data varchar);") # Pass data to fill a query placeholders and let Psycopg perform # the correct conversion (no more SQL injections!) >>> cur.execute("INSERT INTO test (num, data) VALUES (%s, %s)", ... (100, "abc'def")) # Query the database and obtain data as Python objects >>> cur.execute("SELECT * FROM test;") >>> cur.fetchone() (1, 100, "abc'def") # Make the changes to the database persistent >>> conn.commit() # Close communication with the database >>> cur.close() >>> conn.close()
postgresqlとpythonのデータ型の対応
http://initd.org/psycopg/docs/usage.html#adaptation-of-python-values-to-sql-types
postgresqlに接続する
ドキュメントではconn = psycopg2.connect("dbname=test user=postgres")と書いてあるけど、パスワードを渡す場合は引数にパスワードを使えばよい。
http://initd.org/psycopg/docs/module.html
また別の方法として、
DATABASE_URL = postgresql://{username}:{password}@{hostname}:{port}/{database}
これを使う方法もある。
conn = psycopg2.connect(DATABASE_URL)
補足:hostnameはlocalhostとすればローカルのpostgresqlにつなげる。
データベースにデータをインサートする
sqlite3の使い方とほぼ同じ。curオブジェクトをつくり、execute()メソッドを実行する。両者の違いは?を使うか%sを使うかだけだ。
#参考:pythonのsqlite3の使い方 import sqlite3 conn = sqlite3.connect("mydb.sqlite3") cur = conn.cursor() INSERT_SQL = """INSERT INTO test_teable (c1, c2, c3) VALUES (?,?,?)""" t = ("テスト", "インサート文", "やり方",) cur.execute(INSERT_SQL, t) conn.commit()
#参考:pythonのpsycopg2の使い方 import psycopg2 conn = psycopg2.connect(DATABASE_URL) cur = conn.cursor() INSERT_SQL = """INSERT INTO test_teable (c1, c2, c3) VALUES (%s,%s,%s)""" t = ("テスト", "インサート文", "やり方",) cur.execute(INSERT_SQL, t) conn.commit()
参考:
https://www.lewuathe.com/python/postgresql/remind-for-insert-into-with-psycopg2.html
https://algorithm.joho.info/programming/python/sqlite3-insert-into/
django-allauthのログインリンクのはり方
webアプリケーションのnavバーにログインリンクをはろうと試みた。
メモしておく。
試みたこと
いつものようにapp_nameをアプリurls.pyで定め、app.urls.pyでnameを定める計画をする。しかしそもそもこれは自分が作ったアプリではない。てことで少し悩む事になった。
django-allauthのurls.pyに直接app_name="accounts"を書き込んでみた。
app_nameは読み込めるようになったが、nameは読み込めない。
nameはそもそもどこで確認するか。
それはもちろんallauth内のurls.pyであるが、自分の場合は以下のパスとなっていた。
/anaconda3/envs/django36/lib/python3.6/site-packages/allauth/account/urls.py
githubの方がコードの確認はしやすい。
https://github.com/pennersr/django-allauth/blob/master/allauth/account/urls.py
urlpatterns = [ url(r"^signup/$", views.signup, name="account_signup"), url(r"^login/$", views.login, name="account_login"), url(r"^logout/$", views.logout, name="account_logout"), ....................
ログインのnameは"account_login"だと判明した。
結論
リンクが貼れた結果のみ書くと、app_nameやnamespaceを特段気にせず、むしろ無視してnameのみ書く
<a href="{% url 'account_login' %}">ログイン</a>"
django-allauth : テンプレートのカスタマイズ
django-allauthを使ってみた結果ユーザのログインページが味気ない感じだった。
ここを修正していこうと思う。
https://qiita.com/s-katsumata/items/b667c81a127223d2e868
こちらにテンプレートのカスタマイズ方法があった。これを参考にテンプレートをカスタマイズしてみようと思う。
django-allauthのテンプレートのコピー
コマンドを使ってファイルを操作することに苦手なのでここもメモしておく。
まずコピーのコマンドは以下のような形式である。
#windowsの場合 copy コピー元ファイル コピー先のディレクトリ #mac,linuxの場合 cp コピー元ファイル コピー先ディレクトリ
つぎにコピー元のファイルがどこに有るか?
django-allauthはpip でインストールした。pip でインストールした場合には、pip show モジュール名で情報参照できるようだ。
pip show django-allauth
Name: django-allauth Version: 0.38.0 Summary: Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication. Home-page: http://github.com/pennersr/django-allauth Author: Raymond Penners Author-email: raymond.penners@intenct.nl License: UNKNOWN Location: /anaconda3/envs/django36/lib/python3.6/site-packages Requires: python3-openid, requests-oauthlib, requests, Django Required-by:
locationがdjango-allauthのモジュールだ。ここからaccount,openid,socialaccount,base.htmlをディレクトリごとにコピーしておく。コピー先はテンプレートの優先順位を上げたい思惑からBASE_DIR/config/templates/allauth以下にコピーする。するとallauth以下にaccount,openid,socialaccountディレクトリが置かれる状態となる。
テンプレートはドキュメントで以下のように言及されている。
allauth ships many templates, viewable in the allauth/templates directory.
https://django-allauth.readthedocs.io/en/latest/templates.html#overridable-templates
#参考 #mac, linuxの場合 cp -r /Users/chiaki/opt/anaconda3/lib/python3.7/site-packages/allauth/templates コピー先ディレクトリ #-rオプションはディレクトリごとコピーすることができる
settings.pyのテンプレートの読み込み先を追加する
settings.pyのDIRSの欄にallauth部分を追加する。
djangoのテンプレートの読み込みは優先順序がある。それはtemplateのDIRSをまず読み込み、該当するテンプレートがなかった場合に各アプリのtemplatesディレクトリ(つまりAPP_DIRS)以下を読み込みに行く。そして言うまでもないが、各アプリはsettings.pyのINSTALLED_APPSに登録されて使われることが前提である。allauthの場合も同様、INSTALLED_APPSにallauthを登録するして初めてつかわれる。このことはテンプレートにおいてはallauthアプリ以下にあるテンプレートを読み込んでいるに過ぎない。したがってDIRSにカスタマイズしたallauthテンプレートのパスを記せば、カスタマイズのテンプレートを純正のテンプレートを無視して自己カスタマイズしたテンプレートを読みこむことになる。
https://docs.djangoproject.com/ja/2.2/topics/templates/#support-for-template-engines
具体的なコード例
config/settings.py にて TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, "config", "templates"), os.path.join(BASE_DIR, "config", 'templates', 'allauth')], # ←ココ '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', ], }, }, ] # 上記の追加はBASE_DIR/config/templates/allauthのテンプレ読込先を追加したことを意味する!
上記の追加がなければ、BASE_DIR/config/templates以下のテンプレートをまず探して、該当テンプレートがなければAPP_DIRSを探す。そしてallauth純正のテンプレートを探し当てる。上記を追加すればBASE_DIR/config/templates以下のテンプレートをまず探す。なければ、BASE_DIR/config/templates/allauth以下にテンプレートがないか探す。ここにカスタマイズしたテンプレートを置けば純正allauthテンプレートを探す前に見つけてもらえるのでカスタマイズテンプレートが実際使われる。
テンプレートのカスタマイズ
各テンプレートはextends base.htmlとされているので、それに対しbootstrapのコードを加える。
<!doctype html> <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <title>Hello, world!</title> </head> <body> <h1>Hello, world!</h1> <!-- Optional JavaScript --> <!-- jQuery first, then Popper.js, then Bootstrap JS --> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> </body> </html>
forms.pyのカスタマイズ
forms.pyのフィールドにclassのアトリビュートを足してbootstrap効果をつけたいと考える。この場合はどうするか。。。?
https://django-allauth.readthedocs.io/en/latest/forms.html#forms
https://www.reddit.com/r/django/comments/3uykx8/customizing_djangoall_auth/
結論から言うと、settings.pyにカスタムしたフォームを使う宣言と、allauthのloginformを継承した子フォームさえ作成すれば良い。
settings.pyにカスタムしたフォームを使う宣言
ACCOUNT_FORMS = {'login': 'myapp.forms.CustomLoginForm'}
上記のように宣言すると、settings.pyにてallauth純正ではなく自分がカスタムしたフォームを使う挙動に変更される。したがってmyapp/forms.pyにてLoginFormを継承したクラスを作成すればよい。
https://django-allauth.readthedocs.io/en/latest/forms.html#account-forms
デフォルトでは以下のような設定になっているカスタムする場合は下記を参考にkeyをsettings.pyに書き込む。。
ACCOUNT_FORMS = { 'login': 'allauth.account.forms.LoginForm', 'signup': 'allauth.account.forms.SignupForm', 'add_email': 'allauth.account.forms.AddEmailForm', 'change_password': 'allauth.account.forms.ChangePasswordForm', 'set_password': 'allauth.account.forms.SetPasswordForm', 'reset_password': 'allauth.account.forms.ResetPasswordForm', 'reset_password_from_key': 'allauth.account.forms.ResetPasswordKeyForm', 'disconnect': `allauth.socialaccount.forms.DisconnectForm`, }
allauthのloginformを継承した子フォームの作成
from allauth.account.forms import LoginForm class CustomLoginForm(LoginForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field in self.fields.values(): field.widget.attrs['class'] = "form-control"
django mediaについて
mediaの配信について分かっていないので、少し
調べてみることにする。
class Hoge(modelsModel): image = models.ImageField(upload_to="hoge/static/images", null=True, blank=True)
このように書くと、サーバー上のpostgresqlのテーブルでは一体どのようなデータの状態で情報が保存されるのか?
id | image -------+---------------------------------------------------------- 1 | hoge/static/images/my_image1.jpg 2 | hoge/static/images/manzana_1.jpg
こんな感じになっている。
postgresqlにはupload_toのパスとjpgファイルの組み合わせが保存されていた。
ここで仮説を立てる。 サーバーのmediaを置くディレクトリに画像を置く。そしてpostgresqlにupload_toのパスと画像の名前を保存する。これで画像を表示することができる。
この仮説は正しいか?
この仮説は正しかった。mediaファイルディレクトリに画像を置くと画像表示できることは正しい。しかしながらmediaファイルのディレクトリの構成については理解が正しくなかった。以下のような理解となった。
mediaファイルの格納先
djangoの構造が少しわかった。デプロイ環境でmediaファイルはどこに格納されるのか?当初はnginxのmediaファイル用のディレクトリだと思っていた。しかしこれは厳密には正しいと言えないことが分かった。
デプロイ時にはmediaのために以下のコマンドを入力した。
mkdir /usr/share/nginx/html/media
Imagefieldは以下のように規定した。
image = models.ImageField(upload_to="hoge/static/images", null=True, blank=True)
そしてmediaのパスは以下の通り。
/usr/share/nginx/html/media/hoge/static/images/my_image1.jpg
要するにmediaのパスは3要素で構成される。1つめはnginxのディレクトリ(/usr/share/nginx/html/media)。この次にupload_toで定めたhoge/static/imagesにつながる。そして最後にファイル名になる。
staticについて理解を深める
まず開発中の場合、django.views.static.serve() ビューを用いてstaticファイル、mediaファイルを配信するようだ。
https://docs.djangoproject.com/ja/2.1/howto/static-files/#serving-static-files-during-development https://docs.djangoproject.com/ja/2.1/howto/static-files/#serving-files-uploaded-by-a-user-during-development
collectstaticのこと
python manage.py collectstatic
このコマンドにより、各staticフォルダからSTATIC_ROOTのディレクトリにファイルがコピーされるようだ。
django.contrib.staticfilesのこと
https://docs.djangoproject.com/ja/2.1/ref/contrib/staticfiles/#module-django.contrib.staticfiles
3つのコマンドがあるらしい
collectstatic
python manage.py collectstatic
findstatic
python manage.py findstatic css/base.css
runserver
django-admin runserver [addrport]