diadia

興味があることをやってみる。独学で勉強しています。初心者から始めたので困ったことを書いて同じ境遇の方のヒントになればと思います。

django エラー:AttributeError: 'ManyToManyField' object has no attribute 'm2m_reverse_field_name'が出る

python manage.py makemigrations

をしたら以下のようなエラーが出た。

AttributeError: 'ManyToManyField' object has no attribute 'm2m_reverse_field_name'

エラーが出るまでの過程

models.pyをいじり、makemigrationsを終わったあとにmigrateした。

問題点はどのmodels.pyのどのクラスのManyToManyFieldからエラーが発生しているかエラー内容から判断する事ができない点だ。このエラー対処には以下の方法がある。

https://stackoverflow.com/questions/34698845/manytomanyfield-object-has-no-attribute-m2m-reverse-field-name

やり方はmigrationsディレクトリにある dependencies に係る内容を編集する手続きをとるようだ。自分の場合migration内のファイルを理解していないのでこの方法を採用しなかった。今となってはこの方法を採用して取り返しがつかなくなったら最終手段として自分の行なった方法を用いればより理解が深まる機会になったと思われる。

自分が行った方法

この方法は自分が客観的な根拠なくいじった結果エラーを解消した方法だから今後色々問題が生じる可能性がある。問題が生じたら追記することにする。

makemigration,migrateする結果各アプリのmigratinsに001...py,002...py,といったファイルが作られる。このファイルのdependencisの問題とのことだったのでこのファイルをすべて消す。
するとmigrateで新たに001_initial.pyがそれぞれのアプリ内に作られる。これでエラーが解消した。注意すべき点は__init__.pyは消さないことだ。このファイルはpython manage.py startapp *** で作られるファイルであり、migrateで作られるファイルではない。

JsonResponseについて

JsonResponseは今の所ajaxを利用するときに使うという認識だ。

 views.pyでは以下のような感じで関数ビューを書く。

views.py...
from django.http import JsonResponse

def ajax_post(request):
    if request.is_ajax == True :
        json_data = { "result" : "success"}
        return JsonResponse(json_data)
    else:
        print("Failed")
        json_data = { "result" : "failed"}
        return JsonResponse(json_data)

今回自分が分かったことは、returnでJsonResponseを返すということはどんな効果があるか?についてだ。

JsonResponseを使うのは言うまでもなくajaxに使うためのなのだけれども、JsonResponseを単独で使うとブラウザのデベロッパーツール上でjson_dataが表示されることがわかった。

jQueryのDOM操作

参考資料

http://www.koikikukan.com/archives/2013/07/16-003333.php
とてもわかりやすい。

DOM要素を新しく作る場合

新しく要素を作る場合以下のようにタグを書く。タグとして書かないとそれはセレクタとしての意味になるので注意する。

var p = ${"<p>"}; 
var p = ${"<p></p>"}; #又はこのように書いても良い。

新しく作ったDOM要素にコンテンツを入れる場合

html()メソッドを使う。

p.html("今日の出来事");

新たに作ったDOM要素を既存のDOMに追加する場合

メソッドは3種類ある。append(),prepend(),html()をつかう。使い方は追加したい要素をセレクタで指定し、メソッドを使う。

$("body").append(p) #bodyの子要素最下位末尾に追加できる。
$("body").prepend(p) #bodyの子要素先頭に追加する
$("body").html(p) #body要素を内容を新規作成としてpを加える。

 

DOM要素を新しく作り第二引数を取ることについて

新しく要素を作るとき同時に属性を定めることもできるようだ。
https://qiita.com/kazu56/items/8e92d08fd1bba0a2d771
その場合ブラケットで括る。以下のようなイメージ。

var a = $("<a>",{属性:値});
var a = $("<a>",
{ href:"asbua.jp/shu/page",
  "class" : "sample"});

そして第2引数は属性のみではなくjQueryのメソッドも扱える。例えばtext()メソッドを扱うことができる。

var p = $("<p>",{ text:"message"});

django TabularInline について

TabularInlineについて

tabularinlineをよくわかっていない。よく理解した頃に再編集するとするが、今立てている仮説とサンプルを残す。

サンプル

 

admin.pyにて

class AnswerTabularInline(admin.TabularInline):
    model = Answer
    
class QuestionAdmin(admin.ModelAdmin):
    inlines = [AnswerTabularInline]
    
    class Meta:
        model = Question

こんな感じで書くとTabularInlineが使えるようになるらしい。

そもそもインラインって?

おそらくinline , TabularInlineってのはForeignKeyと関係があるのかもしれない。あるモデルとそのモデルに紐付けられたものを同時に表示させる役割なのではないのか?djnago adminでquestionにAnswereを表示させることができたからそういう役割を果たすのかもしれない。理解が進んだらまた書き直す。

sqlite3を使ってみる

使う経緯

apiスクレイピングで取得した情報をcsvモジュールを使ってデータの管理をしていたが、csvファイルやcsvファイルを作成するpyファイルが量産されてしまう。そこでdjangoのアプリケーションを作ってリレーショナルなデータベースを使って管理した。しかしデータのインポートやエクスポートが面倒だしdjangoのサーバーを起動しなければいけないの手軽じゃない。てことでdjangoのdbであるsqlite3を直接いじってみようと思った。

使うイメージ

まずurl,urlの一部分のデータをsqlに入力する。urlからapi経由で追加で得られる情報を取得し、それをsqlに保存していく。必要な際にそこからcsvファイルに変換する。できるようにならないといけない要素は、sqlにインサートすること、アップデートすること、csvファイルに吐き出すことになりそうだ。

使い方

ドキュメント:

https://docs.python.org/ja/3/library/sqlite3.html

まずsqlite3はpythonの標準モジュールらしいのでダウンロード、インストールの手続きは考えなくて良い。

import sqlite3
connection = sqlite3.connect("dbpath")

cursor = connection.cursor()

#create table
cursor.execute("CREATE TABLE mydb (url TEXT, part TEXT)")

#insert
cursor.execute("INSERT INTO mydb VALUES (?, ?)", (a,b))

INSERTに関しては記法が2つあるようだ。どちらもpostgresqlと同じ書き方ではない。

個人的にqmarkスタイルが書きやすかいと思った。

SQL実行メソッドであるexecuteの記法については以下を見ると良い。

https://docs.python.org/ja/3/library/sqlite3.html#sqlite3.Cursor.execute

データ型としてはデフォルトで以下のものに限られている。

SQLite はネイティブで TEXT、INTEGER、REAL、BLOB および NULL のみをサポートしています。その他のタイプを使用したい場合はあなた自身で追加しなければなりません。detect_types パラメータおよび、register_converter() 関数でモジュールレベルで登録できるカスタム 変換関数 を使用することで簡単に追加できます。

データ型については以下に説明があった。 https://www.dbonline.jp/sqlite/type/index1.html

 

sqlite3に格納されたデータを表示するには?

INSERTしたデータをどのように閲覧するのか?
一つはコンソールで表示させる方法、もう一つはcsvファイルとして出力し表示させる方法、これらはどうすればよいのか?

データ表示はsqlの操作どおり、SELECTを用いて処理する。
postgresqlではSELECT * FROM mydb; でデータベースからデータを取得したが、sqlite3ではそれを引数化すれば良いみたいだ。

import sqlite3
conn   = sqlite3.connect("mydb")
cursor = conn.cursor()

cursor.execute("SELECT * FROM mydb")
# 全件取得 一件の場合はfetchone()を使う
res = cursor.fetchall()
    

UPDATEについて

postgresqlでは以下のような感じでUPDATEしていた。

UPDATE mydb SET url="dauduia" WHERE part="today"

sqlite3の場合ではこのSQL文をそのまま引数として書けば良いみたいだ。

import sqlite3
conn   = sqlite3.connect("mydb")
cursor = conn.cursor()

cursor.execute('UPDATE mydb SET url="dauduia" WHERE part="today')

まとめ

使えるようになってきたので再び使い方を書く。 まずSQL文を実行するメソッドがexecute()である。execute()の引数としてSQL文を書く。
しかしながらこのSQL文はpostgresqlと同じものではなくsqlite3特有の書き方であるようだ。 その特有の書き方は2種類ある。qmark記法、named記法である。qmarkではSQL文の入力値は?を便宜的に書く。そして実際の入力値は第二引数タプルで渡す。
イメージでは以下のような感じだ。

cursor.execute("?を使ったSQL文",(実際の入力値))

executeメソッドで行えるのはSQL文なのでCREATE TABLE やSELECT, UPDATE INSERT である。

# CREATE 
cursor.execute("CREATE TABLE mydb (url TEXT, part1 TEXT, part2 TEXT)")

 

# INSERT
cursor.execute("INSERT INTO mydb VALUES (?, ?, ?)",("https://fdsahfufbsaui.com", "zxzxzx", None  ))
#postgresqlでは右のように書く。INSERT INTO mydb VALUES (a, b)

 

# UPDATE
cursor.execute("UPDATE mydb SET url=? WHERE part1=?",("urlの入力値", "part1の入力値"))

#複数値のUPDATEの場合
cursor.execute("UPDATE mydb SET url=?, author=? title=? WHERE part1=?", ("urlの入力値","authorの入力値","titleの入力値"))
#>postgresqlでは右のように書く。UPDATE mydb SET url="dauduia" WHERE part1="today"

INSERTやUPDATEは実際の入力値を伴うものだからSQL文の中に?を記述することになる。一方CREATE TABLEやSELECT文はデータベースに入力する値がないためSQL文そのままで書くことになる。

カラムを追加する方法

postgresqlでは以下のように書く。

ALTER TABLE mydb ADD COLUMNS column_a CHAR 

sqlite3は以下のように書く。

cur.execute("ALTER TABLE mydb ADD COLUMN column_a TEXT")

executeのメモ

例えばテーブルのあるカラムのデータ型をREALとして定義する。そしてフィールドとして-1(INTEGER)をUPDATEで渡したらどうなるか?
結果はエラーが出ることなくexecute()メソッドが実行され、結果が-1.0としてデータが格納された。

django csvを出力する

やりたいこと

とにかくファイルの出力の基礎を学びたい。今回はcsvファイルをdjangoに投げて、重複するデータは削除し、csvとして出力する仕組みを作ってみる。

 

未知な部分

  • 出力した結果をhtml上ではどう表現するのか?イメージではアンカーをクリックでダウンローする形にしたい
  • アンカーに設置するのはcsvファイルってことで良いのか

requestsでAPIを利用する際のJSONの取り扱いについて

APIを使うのイメージ

APIを使うイメージは、あるドメインに何かアイテムを投げつけて、返ってきたオブジェクトを加工修正して利用する。pythonでは例えばrequests.getを使ってオブジェクトを得る。データをいじるにはJSON形式のデータとして返されることが多い。JSON形式のデータについて対応できるとpythonSDKがなかったとしても対応できるようになる。

JSON形式のデータについてはrequestsのメソッドが用意されているので難しいことはない。

変換の仕方

response  = requests.get(hoge)
json_data = response.json()
print(type(json_data))

requests.getで返ってきたオブジェクトにjson()を使うだけで良い。あとは見やすくするためにpprintを使うとデータの構造を理解しやすくなる。

import pprint          #pprintは標準ライブラリなのでインストールする必要はない
pprint.pprint(json_data)

ドメイン設定について

全然やり方を知らない。

ドメインとipの関係はipをドメインに変換するサーバーがあり、ドメインを入力するとipが戻ってくるのでアクセスできるみたいなことは知っている。

ただ自分が設定するとなるとよくわからなくなる。

 

まずドメイン取得したサービス会社、さくらVPS、または両方から設定をするのか?またはネームサーバーに直接紐付けることを申請するのか?

 

普通に考えればネームサーバーにヒモ付お願いしますと言えば、良いようなものだけども実際はどうなっているのか。。。

 

 

さくらVPSとお名前.comの場合

ドメインを取得し、さくらVPSに設定してみる - Qiita

CSVファイルを受け取る機能(アップロード機能)について

csv関連の資料

https://docs.djangoproject.com/ja/2.1/topics/http/file-uploads/ https://qiita.com/otera05/items/25d5bad759d39d61bc40

 

html上でどのような記述が必要か(試行錯誤過程)

文字列においてはformタグ内にinputやbuttonを設置することで、サーバーサイドで当該文字列を受け取ることが出来た。音声ファイルや映像ファイルそしてcsvファイルに対応するformやinput等は何なのか?ここが分かっていない。
調べたところinputタグの属性としてtype="file"なるものがあるらしい。これを試してみようと思う。

html上でどのような記述が必要か(結論)

こちらの情報通りでうまくいった。
http://www.htmq.com/html5/input_type_file.shtml
ファイルのやり取りにおいてformタグではenctype="multipart/form-data"が必要。またinputタグではtype="file"が必要。これが知らなかった情報だった。

file受取り時の挙動をコントロールしたい

文字列の情報はrequest.POSTに当該情報が辞書型のようなデータとして格納された。だから辞書型データのように取り出せば文字列を扱うことが出来た。csvファイルのようなデータはどのように扱えばよいのか?ここが知らない。csvモジュールを使えばよいのか?またcsvファイルを受け取ったら、そのデータをもとにクラスインスタンスを作成したい。おそらくcsvファイルの受け取りとその中身の読み取りさえできれば、問題は解決するだろう。

file受取り時の挙動をコントロールする

こちらの情報が参考になった。
https://docs.djangoproject.com/ja/2.1/topics/http/file-uploads/#basic-file-uploads
https://docs.djangoproject.com/ja/2.1/ref/request-response/#django.http.HttpRequest.FILES
以下のような感じでコードを書いてみた。

hoge/views.py ...

class ReceiveFileView(View):


    def get(self, request, *args, **kwargs):
		return render(request, 'hoge/receiving_csv.html')

    def post(self, request, *args, **kwargs):
		print(request.FILES)
		print(dir(request.FILES))
		return render(request, 'hoge/receiving_csv.html')
hoge/receiving_csv.html ...

<form action="{% url 'hoge:receive_file' %}" enctype="multipart/form-data" method="POST">
{% csrf_token %}
<input type="file" />     # name="csv"をinputタグの属性として入れること
<button type="submit">ファイルを送信する</button>
</form>

inputタグにname属性を入れなかったせいでrequest.FILESにファイルが格納されず、時間を費やしてしまった。辞書型データとして格納するためにkeyの代わりにnameが必要だとわかった。また、辞書型データとして格納するためには他にも条件がドキュメントに書いてある。ファイルはリクエストポストで送信すること。その他にformタグにenctype="multipart/form-data"を記述していることだった。

今作りたいアプリケーションについて

今作りたいもの

djangoECサイトはそれらしいものが出来てきた。JAVAではECサイト作成は登竜門である、とどこかで聞いたのでやっとそれなりにdjango使い見習いとして言える身分になったと思う。
pythondjangoを通してよく学べたと思う。クラスの継承方法だったり、インスタンスとは何なのかとか知る必要が出てきて結果的にpythonが少し分かるようになってきた。まだまだwebアプリケーションの作成能力がしょぼいのでもっといろいろなものづくりにチャレンジしたい。他にも機械学習バイオインフォマティクスみたいな分野もあるみたいで結構興味がある。まあでもwebアプリケーション作りを続ける。

現状ではクライアントから情報を入力する観点で考えると、formタグからinputフォームを使うような方法でしかシステムを構築できない。音声ファイルや映像ファイルの受け取りや、CSVファイルの受け取りができる機能を持ったアプリケーションが作ってみたい。まずは一番簡単そうなCSVファイルを受け取るものを作ってみたい。

それが出来たらdjangorest APIなるものを載せたものができるらしいのでそれを挑戦してみたい。

検索(サーチ)機能を実装する

検索機能を実装したい

データベースからブログの記事内容だったり、商品を検索して結果を表示したい。

それは検索キーワードに合致する結果を表示すれば良い。 キーワードに合致するものだけ抽出する方法は、objects.filter()を使えば解決できる。 結果を表示するにはListViewを使うかViewを使う。もちろんview関数で定めてあげても良い。
ListViewを使った例はこちら。ポイントはクラスインスタンスメソッドを別途定めてあげることだ。

http://torajirousan.hatenadiary.jp/entry/2018/09/25/025035

 

Viewを使った方法(検索フォームの実装含む。)

流れとしては、テンプレートに検索用のフォームタグを設置する。このフォームタグから入力されたキーワードをfilter()メソッドを使って対象のデータオブジェクトに限定し、テンプレートにレンダリングして表示する。

キーワード入力テンプレートの作成

"products/search.html"...

<form action="{% url 'products:product_search' %}" method="GET">
<input name="q" type="search" placeholder="Search Product by keyword" />
<button type="submit">Search</button>
</form>

半年前はmethodGETの存在を知らなかった。検索用のフォームを作るときはmethod="GET"として、検索キーワードを取得する足がかりを作る。action={***}の役割は以下のものです。フォームになんらかのキーワードが入力される。Searchボタンが押されたときに、views.pyの特定のviewが発動します。
{% url "hoge:foo" %}の中身の部分はviews.pyに記述されているviewのうち、どのviewを発動させるかを指定しています。この場合はhogeアプリケーションおviews.pyのfooが発動します。このview名はurls.pyで名前をつけてあげることが必要になります。

views.pyの作成

"products/views.py"...

from django.shortcut import render
from django.views.generic import View


class ProductSearchListView(View):

    def get(self, request, *args, **kwargs():
        
        q = request.GET["q"]
        print(q)             # ちゃんと入力した文字がqに返されているかコンソールで確認する
      object_list = Product.objects.filter(title__icontains=q)
      context = {}
      context["object_list"] = object_list
      return render(request, "products/product_list.html", context)
    

自分が苦労したひとつにdjangoのrequest.POSTやrequest.GETにまず何らかの情報が格納されているという事実を知らなかった。その事実を知ってからもどんな種類の情報が格納されているか分からなかった。調べる術を持たなかった。どんな情報があるか調べるためにrequest.GET,POSTをいじってみるしかない。
具体的には以下の方法が役に立つ。

class ProductSearchListView(view):
    def get(self, request, *args, **kwargs):
        print(request.GET)
        print("=======================")
        print(dir(request.GET))
        print("=======================")
        print(self.request.GET)

dir()はrequest.GET,POSTに含まれている属性?メソッド?が何であるか表示してくれるので、dir()を足がかりに表示された属性?メソッド?を新たに使ってみてどんな情報があるか探索する方法で自分は取り組んできた。

urls.pyの作成

"products/urls.py"...
from django.urls import path

app_name= "products"
urlpatterns = [
    path('search', ProductSearchListView.as_view(), name="product_search"),
        ]

検索結果を表示するhtmlを作成

"product_list.html"...

{% for object in object_list %}

{{ object.title }}}
{{ object.description }}

{% endfor %}

OR検索を使いたい

Qオブジェクトを使う

and検索はfilter()メソッドで連結させて書くことで挙動を実現できる。一方or検索ではQオブジェクトを使うことで実現できることがわかった。

ドキュメント: https://docs.djangoproject.com/ja/2.1/topics/db/queries/#complex-lookups-with-q-objects
https://docs.djangoproject.com/ja/2.1/ref/models/querysets/#q-objects

Qのインスタンスを作成して運用する方法

参考url:
https://code.i-harness.com/ja-jp/q/d01be

TypeError: unhashable type: 'set' エラー対処

エラー内容

TypeError: unhashable type: 'set'

上記のエラーが出てきた。unhashable typeって出てきた。

 

エラー発生箇所

以下のコードがエラーの発生源だった。

print(type({{ cart_obj.subtotal }}))

他にもこのコードも同様なエラーが出た。

charge = stripe.Charge.create(
    amount      = {{ cart_obj.subtotal }},
    currency    = "usd",
    description ='Example charge',
    source=token,
		)

原因

テンプレート上では、{{ hoge }}って書くけど、サーバーサイドのコードに同じことを書くとこのようなエラーが発生する。unhashable typeが出たときはテンプレートで使う記法で書かれている可能性がある。