Using AJAX to send data to flask

rang-dev·2020년 3월 17일
0

Using AJAX to send data to flask

  • Data request는 동기(synchronous)이거나 비동기(asynchronous)이다.
  • Async data requests는 서버에게 보내졌다가 페이지 새로고침 없이 클라이언트에게 되돌아 온다.
  • Async requests(AJAX requests)는 다음 두 메소드 중 하나를 사용한다.
    • XMLHttpRequest
    • Fetch (modern way)
  • 더 자세한 내용 참고

Using XMLHttpRequest

Send a Request To a Server

var xhttp = new XMLHttpRequest();  // #1
description = document.getElementById("description").value;  // #2
xhttp.open("GET", "/todos/create?description="+description);  // #3
xhttp.send();  // #4
  • The XMLHttpRequest object can be used to exchange data with a web server behind the scenes.
  • open(method, url, async): Specifies the type of request
  • method: the type of request: GET or POST
  • url: the server (file) location
  • async: true (asynchronous) or false (synchronous)
  • send(): Sends the request to the server (used for GET)
  • send(string): Sends the request to the server (used for POST)

  • #1. XMLHttpRequest 객체 생성
  • #2. id가 description인 form에 입력된 value값을 description에 할당

    (DOM에서 request와 함께 보낼 data를 가져온다.)
  • #3. open a connection from client to server, specified where you're looking to send.
  • #4. 서버에 해당 요청을 전송하고 connection을 종료

서버에서 요청 처리가 끝나면..

  • 동기: the server dictates how the view should then uptake.
  • 비동기: It's on the client side that you reacts to the server and you figure out how to update the DOM that is already loaded on the client based on the response that you get.

Server Response - XMLHttpRequest on success

xhttp.onreadystagechange = function() {
  if (this.readyState === 4 && this.status === 200) {
  // on successful response
  console.log(xhttp.responseText);
  }
};

The readyState property holds the status of the XMLHttpRequest.

The onreadystatechange property defines a function to be executed when the readyState changes.

The status property and the statusText property holds the status of the XMLHttpRequest object.

The onreadystatechange function is called every time the readyState changes.

When readyState is 4 and status is 200, the response is ready.

Using Fetch

fetch('/my/request', {
  method: 'POST',
  body: JSON.stringity({
    'description': 'some description here'
  }),
  headers: {
    'Content-Type': 'application/json'    // To server knows to parse it as JSON
  }
});
  • fetch는 request를 더 쉽게 보낼 수 있도록 해준다.
  • request와 관련된 headers, body, method와 같은 파라메터를 가진다.
  • fetch(<url-route>, <object of request parametsers>)

todoapp

  • View
<html>
    <head>
        <title>Todo App</title>
        <style>
        .hidden{
            display:none;
        }
        </style>
    </head>

    <body>
        <form id="form">   <!-- since preventDefualt, we no longer need to those method and action-->
            <input type="text" id="description" name="description" /> 
            <input type="submit" value="Create" />
        </form>
        <div id="error" class = "hidden">Something went wrong!</div>    <!--error message-->
        <ul>
            {% for d in data %}
            <li>{{ d.description }}</li>
            {% endfor %}
        </ul>
        <script>
            document.getElementById('form').onsubmit = function(e){    // on subit handler: by dault wound up sending information to the server.
                e.preventDefault();                                    // use event object --> default behavior: full page refresh and submit it using the method and action attributes.
                fetch('/todos/create', {                               // send post request asynchronously using fetch
                    method:'POST',
                    body: JSON.stringify({
                        'description': document.getElementById('description').value
                    }),
                    headers: {
                        'Content-Type': 'application/json'
                    }

                })
                .then(function(response){                              //give back response
                    return response.json();
                })
                .then(function(jsonResponse){
                    console.log(jsonResponse);
                    const liItem = document.createElement('LI');       // append the item to our current list.
                    liItem.innerHTML = jsonResponse['description'];    // <li>안에 description의 내용을 넣는다.
                    document.getElementById('todos').appendChild(liItem);
                    document.getElementById('error').className = 'hidden'; // 성공하면 그대로 error message를 숨긴다.
                })
                .catch(function(){                                     // Just in case it doesn't work.
                    document.getElementById('error').className = '';   // 실패하면 error message를 나타낸다.

                })
            }
        </script>
    </body>
</html>
  • Controller
from flask import Flask, render_template, request, redirect, url_for, jsonify
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__) ## file의 이름으로 application의 이름을 설정함
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:password@localhost:5432/todoapp' # db와 flask연결: createdb는 flask에서 X, 직접 해줘야함
db = SQLAlchemy(app) # db와 flask연결

class Todo(db.Model):
    __tablename__ = 'todos'
    id = db.Column(db.Integer, primary_key=True)
    description = db.Column(db.String(), nullable=False)

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

db.create_all()


@app.route('/todos/create', methods=['POST'])
def create_todo():
    description = request.get_json()['description']   # get the JSON that comes back from our AJAX request.
    todo = Todo(description=description)
    db.session.add(todo)
    db.session.commit()
    return jsonify({                                  # .jsonify will return JSON data to the client.
        'description': todo.description               # JSON object
    })

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


if __name__ == '__main__':
    app.run(debug=True)
  • 비동기였을 때는 페이지를 redirect하면서 전체가 새로고침이 되어야했는데 동기에서는 새로운 부분(response)만 기존의 화면에서 추가되는 것을 볼 수 있다.
  • 또한 이전에는 컨트롤러가 response를 받아서 view를 결정했지만 여기서는 client side(view)에서 request를 전송하고 response를 받아서 화면에 출력하는 것까지 결정한다. 컨트롤러는 model의 수행만 명령하고 JSON data를 client로 return한다.

Using sessions in controllers

  • Commits은 성공할수도 있고 실패할 수도 있다. 우리는 연결을 종료할 때 potential implicit commits가 발생하는 것을 방지하기 위해서 rollback을 수행해야 한다.
    • connection을 닫을 때 의도하지 않았더라도 데이터베이스에 pending된 transactions이 commit되기 때문이다.
  • 가장 좋은 방법은 컨트롤러에서 사용되는 모든 새션의 끝에서 connections을 종료하여 그 connection이 connection pool로 돌아가서 다시 사용될 수 있도록 하는 것이다.

Example

import sys

try:
  todo = Todo(description=description)
  db.session.add(todo)
  db.session.commit()
except:
  db.session.rollback()
  error=True
  print(sys.exc_info())
finally:
  db.session.close()

Implement try...except...finally in the app

from flask import abort
import sys
...
@app.route('/todos/create', methods=['POST'])
def create_todo():
  error = False
  try:
    description = request.get_json()['description']
    todo = Todo(description=description)
    db.session.add(todo)
    db.session.commit()
  except:
    error = True
    eb.session.rollback()
    print(sys.esc_info())         # 구체적인 error의 내용을 나타낸다.
  finally:
    db.session.close()
  if not error:
    return jsonify({
      'description': todo.description
    })
...

expire_on_commit: Defaults to True. When True, all instances will be fully expired after each commit(), so that all attribute/object access subsequent to a completed transaction will load from the most recent database state.

db에 commit후 종료된 세션에 있던 instance들은 모두 expired되었기 때문에 SQLAlchemy는 위와 같은 error를 발생시킨다.
db = SQLAlchemy(app, session_options={"expire_on_commit": False})로 변경하면 에러를 없앨 수 있지만 의도하지 않은 부작용이 생길 수 있기 때문에 사용하지 않을 것이다.

Let's remove the error

...
@app.route('/todos/create', methods=['POST'])
def create_todo():
  error = False
  body = {}
  try:
    description = request.get_json()['description']
    todo = Todo(description=description)
    db.session.add(todo)
    db.session.commit()
    body['description'] = todo.description        ### 해결방법: session을 종료하기전에 body를 return해두기
  except:
    error = True
    eb.session.rollback()
    print(sys.esc_info())   
  finally:
    db.session.close()
  if not error:
    return jsonify({body})
...
profile
지금 있는 곳에서, 내가 가진 것으로, 할 수 있는 일을 하기 🐢

0개의 댓글