質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.60%

WebアプリでLIKE句を用いた検索がうまくできない

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 465

harunouta

score 144

前提・実現したいこと

FlaskとPostgres, SQLAlchemnyで作ったWebアプリで
データの入力と検索を行い、検索は部分一致で結果を出してページに表示しようとしています。
SQLAlchemyのLIKE句を使った記述は以下の記事を参考にしました。  
参考記事  

発生している問題・エラーメッセージ

データの入力はフォームに入力したものがデータベースに正しく格納されていますが、
検索で結果をWebアプリで取得できない状態です。

エラーメッセージは出ていないので、以下の状況を  
Postgresデータベース内でLIKE句を使ってSELECTで求めた結果と同じ出力にするにはどうすればいいでしょうか。  

htmlファイルの  

   {% for name in results %}  
   <li>  
       <h5>{{ todo.name }}</h5> <!-- 検索結果をページにプリント -->  
   </li>  
   {% endfor %}  

  
が機能しておらず、Searchをクリックすると以下が表示されます。  

{  
 "city": "",  
 "name": ""  
}  

  

該当のソースコード  

Postgres データベース内  

# \dt  
      List of relations  
Schema | Name | Type | Owner  
--------+-------+-------+-------  
public | todos | table | username  

# select * from todos;  
id | name | city   
----+--------+-------  
 1 | Hina  | Tokyo  
 2 | Shun  | Tokyo  
 3 | Taro  | Fukuoka  
 4 | Rika  | Nagano  
 5 | Hanako | Tokyo and Yokohama  
 6 | Haruka | Kyoto  

  
app.py  

from flask import Flask, render_template, request, redirect, url_for, abort, jsonify
from flask_sqlalchemy import SQLAlchemy
import sys

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgres://username@localhost:5432/simplewr'
db = SQLAlchemy(app)

class Todo(db.Model):
    __tablename__ = 'todos'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    city = db.Column(db.String(120))

    def __repr__(self):
      return f'<Todo {self.id} {self.name} {self.city}>'

db.create_all()

@app.route('/todos/create', methods=['POST'])
def create_todo():
  error = False
  body = {}

  try:
    name = request.form['name']
    city = request.form['city']
    todo = Todo(name=name, city=city)
    db.session.add(todo)
    db.session.commit()
    body['name'] = todo.name
    body['city'] = todo.city
  except:
    error = True
    db.session.rollback()
    print(sys.exc_info())
  finally:
    db.session.close()
  if error:
    abort (400)
  else:
    return jsonify(body)

@app.route('/todos/search', methods=['POST'])
def search_venues():
  #HTMLのフォームから検索に用いられた単語を取得
  search_term = request.form.get('search_term');
  results = []
  # 部分一致 LIKE
  # WHERE LIKE '%...%'
  if search_term == "":
    print("enter a keyword")

  for student in session.query(Todo).filter(todo.city.like('%search_term%')):
    print(todo.name)
    results.append(todo.name)

  """
  Tokyoで検索された時
  Todo.cityがTokyo and Yokohamaでも、Tokyoでも部分一致で該当するデータのnameを出力

 出力
  Hanako
  Taro

  """
  return render_template('index.html', results=results, search_term=search_term)
  #htmlのrenderにはjinja2というものが使われており、中でPythonのコードを実行することができる

@app.route('/')
def index():
    return render_template('index.html', data=Todo.query.all()) 

  

index.html
(app.pyと同じフォルダにtemplatesというフォルダを作成し、index.htmlを置いています。

<html>
<head>
    <title>Todo App</title>
<style>
    .hidden{
        display:  none;
    }
</style>
</head>
<body>
    <form method="post" action="/todos/create">
    <h4>name</h4>
        <input type= "text" name="name" />
    <h4>city</h4>
    <input type= "text" name="city" />
    <input type= "submit" value="Create" />
    <h4>search box</h4>
    <input type="text" name="search_term" />
    <input type= "submit" value="Search" />
    </form>
    <div id= "error" class="hidden">Something went wrong!</div>
    <ul>
        {% for d in data %}
    <li>{{d.name}}</li>
        <li>{{d.city}}</li>
    <li>{{d.search_term}}</li>
        {% endfor %}
    {% for name in results %}
    <li>
        <h5>{{ todo.name }}</h5> <!-- 検索結果をページにプリント -->
    </li>
    {% endfor %}
    </ul>
    <script>
      const nameInput = document.getElementById('name');
      const cityInput = document.getElementById('city');
      const sechInput = document.getElementById('search_term');
      document.getElementById('form').onsubmit = function(e) {
        e.preventDefault();
        const name = nameInput.value;
        const city = cityInput.value;
        const search_term = sechInput.value;
        descInput.value = '';
        fetch('/todos/create', {
          method: 'POST',
          body: JSON.stringify({
            'name': name,
            'city': city,
          }),
          headers: {
            'Content-Type': 'application/json',
          }
        })

        //return the serched data
        SQL 
        SELECT city from table where LIKE search_term;

        .then(response => response.json())
        .then(jsonResponse => {
          console.log('response', jsonResponse);
          li = document.createElement('li');
          li.innerText = name;
          li.innerText = city;
          document.getElementById('todos').appendChild(li);
          document.getElementById('error').className = 'hidden';
        })
        .catch(function() {
          document.getElementById('error').className = '';
        })
      }
    </script>
</body>
</html>


上記のプログラムを

$ FLASK_APP=app.py FLASK_DEBUG=true flask run


で実行します。

試したこと

SQL文で取得できることは確認しました。

# select name from todos where city LIKE 'Tokyo';
name 
-------
Hina
Shun
Hanako

ご回答を受けての追記

不明瞭な点があったため明白にします。

  • 実現したいこと
    検索フォームに入力されたら、大文字小文字問わず部分一致で該当するcityを持つデータのnameを取得し、画面遷移で(POSTして)表示を変えたいです。

Tokyoの場合、画面遷移をして

  Hanako
  Taro


です。

  • search_venuesの戻り値がrender_template
    に関して、javascriptとpythonのjinja2で表示処理のちがいがよくわかっていないのですが
    検索しても該当するページを見つけられていない状況です。

def create_todo():に倣って以下のように変更しましたが依然として、

  1. 部分一致検索が機能しているのか
  2. javascriptとpythonのjinja2のちがいによる表示処理

がうまく理解できていないので、最新版のコードでもエラーが出ています。

@app.route('/todos/search', methods=['GET'])
def search_venues():
  #HTMLのフォームから検索に用いられた単語を取得
  search_term = db.session.query(Todo).get(request.form["search_term"].strip())
  #search_term = request.form.get('search_term');
  results = []

  if search_term == "":
    print("enter a keyword")

  users = session.query(Todo).filter(Todo.city.like('%'+ search_term + '%')).all()
  for user in users:
    print(user)
    results.append(user)

    return jsonify(results)

補足情報(FW/ツールのバージョンなど)

Python 3.6.0
Flask 1.1.1
Sqlalchemy 1.3.10
psql (PostgreSQL) 11.5
ブラウザ Google Chrome

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • FiroProchainezo

    2019/10/24 14:23

    > Searchをクリックすると以下が表示されます。
    Searchが見当たらないのですが、どこにあるのでしょうか?
    ボタンですか?関数呼び出しかなんかですか???

    他の質問にも書きましたが、このプログラムは最新版ですか?

    キャンセル

  • harunouta

    2019/10/24 18:06

    すみませんでした。Searchはボタンです。最新版に直しました。

    キャンセル

回答 1

checkベストアンサー

+1

確認しましたが、このアプリ、動いているのか疑問を感じるレベルでおかしいです。

以下を確認いただけますか?

app.py

  • sessionがimportされていないのに、sessionを使っている(search_venues)

DBにアクセスしたいなら、db.session.query()としてください。

  • search_venuesのtodoが小文字

Todoは大文字ですよね?

  • for student in session.query(Todo).filter(todo.city.like('%search_term%')):

以下のようにしないとずっと固定値なのでは?

Todo.city.like('%'+ search_term + '%')


containsを使っても良いかもしれません。

Todo.city.contains(search_term)
  • search_venuesの戻り値がrender_template

fetchで値を取得したいならrender_templateを使うのは変です。
jsonとかのデータを返し、fetch(javascript)の処理で画面に反映しましょう。
その場合、python側の処理ではなくなるので、jinja2での表示はできません。

javascriptで表示処理したいのか、pythonのjinja2で表示処理をしたいのかを決めて、それにあった作り方をしましょう。

index.html

  • create, searchボタン、どちらも送信先がcreate_todoになっている。
    <form>のactionを別にしないと処理は別になりません。

  • fetchが動いているように見えない。
    たぶん、普通にPOSTしているだけになっています。(fetchしていたら、どちらのボタン(search, create)を押してもcreate_todoに行くはずですが、formを分けたら送信先が変わったので。

何がしたいのかよくわからなかったのでこのくらいしか回答できませんが、index.html(/)をfetchを使って遷移せずに書き換えたいのか、画面遷移で(POSTして)表示を変えたいのかを明確にすればもう少し変わると思います。

追記(2019/10/25 20:21)

最初に、すべてに指摘は入れているわけではないので、指摘されていないところが問題無いとは思わないでください。

検索フォームに入力されたら、大文字小文字問わず部分一致で該当するcityを持つデータのnameを取得し、画面遷移で(POSTして)表示を変えたいです。 

画面遷移してで良いなら、formのpostにsearch_venuesを指定(<form method="post" action="{{ url_for('search_venues') }}">)し(フォームを2つに分けました)、
index.htmlではなく、適当にsearch.html等を作成し、
app.pyのsearch_venuesの処理を変更して、render_templateを返すように
すればOKと思います。

index.html

    <form method="post" action="/todos/create">
        <h4>name</h4>
            <input type= "text" name="name" />
        <h4>city</h4>
        <input type= "text" name="city" />
        <input type= "submit" value="Create" />
    </form>
    <form method="post" action="{{ url_for('search_venues') }}">
        <h4>search box</h4>
        <input type="text" name="search_term" />
        <input type= "submit" value="Search" />
    </form>

search.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div>
        <ul>
            {% for d in ret %}
                <li>{{d.name}} {{ d.city }}</li>
            {% endfor %}
        </ul>
    </div>
</body>
</html>

search_venues

@app.route('/todos/search', methods=['POST'])
def search_venues():
    search_term = request.form.get('search_term')
    ret = db.session.query(Todo).filter(Todo.city.contains(search_term)).all()
    return render_template('search.html', ret=ret)

jinja2とjavascriptについて

search_venuesの戻り値がrender_template
に関して、javascriptとpythonのjinja2で表示処理のちがいがよくわかっていないのですが
検索しても該当するページを見つけられていない状況です。 

pythonとjinja2はサーバ側で動作します。

javascriptは、サーバ側が処理し終わって、ブラウザにすべてが表示されたあとに動作します。(必ずではありませんが)

ブラウザでアクセス(localhost:5000)したら以下のような流れだと思ってください。

  1. ブラウザでhttp://localhost:5000/を開く
  2. app.pyのindexが動作する。
  3. render_templateでindex.htmlが呼ばれる。
  4. index.htmlのマスタッシュ記法{{}}部分をpython(jinja2)が処理する。
  5. index.htmlがブラウザに表示される。({{}}は残っていない)
  6. index.htmlのscriptに記載されている部分が動作する。(このソースでは動作する場所がありませんが)

fetchの結果を{{}}に反映しようとしているのは、処理している部分が違うので無理です。
Chromeでlocalhost:5000を表示し、開発者ツール(F12)を表示してみてください。(Sourcesタブの左側top->localhost:5000->)(index)
{{}}で囲まれたものはSorucesに見当たらないはずです。

python(flask, jinja2)はpython, javascriptはjavascriptで処理する必要があります。

# これは無理です。
        {% for name in results %}
            <li>
                <h5>{{ todo.name }}</h5> <!-- 検索結果をページにプリント -->
            </li>
        {% endfor %}

余談

index.htmlに以下の記述がありますが、Javascirptの記述として正しく無いので削除した方が良いです。

        //return the serched data
        SQL
        SELECT city from table where LIKE search_term;

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/10/25 12:42

    ご回答いただきありがとうございます。
    ご回答に関して詳細なレスポンスは質問に追記いたしました。
    また、質問中のコードを最新版のコードに書き換えてもいいでしょうか。

    キャンセル

  • 2019/10/25 21:53

    ご丁寧にありがとうございます。コメントアウトが抜けており申し訳ございませんでした。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.60%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る