📝 ORM

ORM이란, 객체 관계 매핑(Object Relational Mapping)으로, 데이터베이스 내의 테이블들을 객체화해서, 각 DBMS에 대해서 CRUD 등을 공통된 접근 기법으로 사용할 수 있다.

일반적으로 하나의 객체정보를 다루기 위해서 여러 SQL 쿼리를 사용하게 되고, 프로그램이 커질수록 작성해야 하는 SQL 쿼리가 많아져 복잡해진다.

반복되는 쿼리를 객체 단위로 생성하여, 이를 해결하고자 했고, 이런 작업을 도와주는 것을 바로 ORM이라고 한다. 

ORM을 사용하게 되면 따로 SQL문을 작성할 필요없이 객체를 통해 간접적으로 데이터베이스를 조작할 수 있다.

🔊 대표적인 python ORM = DjangoORM 과 SQLAlchemy 등이 있다.

💡 장점

  • 개발 생산성을 증가시킨다. 프로그래머는 DBMS에 대한 큰 고민없이, ORM에 대한 이해만으로
    대부분의 CRUD를 다룰 수 있다.
  • 유지보수성이 좋다. 데이터 구조 변경시, 객체에 대한 변경만 이루어지면 된다.
  • 코드 가독성이 좋다. 객체를 통해서 대부분의 데이터를 접근 및 수정을 진행한다.

💡 단점

  • 호출 방식에 따라 성능이 천차만별이다.
  • 복잡한 쿼리 작성시, ORM 사용에 대한 난이도가 증가한다.
  • DBMS(MySQL) 고유의 기능을 모두 사용하지 못한다.

 

📌 SQL 쿼리와 ORM의 차이점

다음과 같은 DB 테이블이 있고, 데이터를 삽입하려고 한다고 가정하자.

[ 출처 : elice ]

INSERT INTO 엘리스 (name, age) VALUES (‘여왕’, ‘18’);
member = Member(name=‘여왕’, age=‘18’)
db.session.add(member)

위에가 SQL, 아래가 ORM 방식이다.

 

📝 SQLAlchemy

파이썬 ORM 라이브러리에서 가장 많이 사용되는 SQLAlchemy파이썬 코드에서 Flask를 DB와 연결하기 위해 사용된다. SQLAlchemy는 ORM이고, DB 테이블을 프로그래밍 언어의 "클래스"로 표현하게 해주고, 테이블의 CRUD 등을 돕는다.

💡 SQLAlchemy 는 왜 사용하나 ?

  • SQLAlchemy를 사용하면, SQL쿼리를 사용하지 않고, 프로그램이 언어로 객체간의 관계를 표현할 수 있다.
  • SQLAlchemy로 Model을 정의하고, 정의한 모델을 테이블과 Mapping 할 수 있는 여러가지 방법을 쉽게 구현할 수 있다.
  • 이는, DB의 종류와 상관 없이 일관된 코드를 유지할 수 있어 프로그램 유지 및 보수가 편리하고 SQL 쿼리를 내부에서 자동으로 생성하기 때문에 오류 발생률이 줄어드는 장점이 있다.

 

📌 Model 구현

Member는 파이썬 클래스이다. 클래스는 DB의 테이블과 Mapping하여 사용되며 DB의 테이블과 매핑되는 이 클래스를 모델이라고 한다.

SQLAlchemy에 내장되어 있는 Mode 클래스를 상속해서, 이를 구현할 수  있으며, 쿼리를 사용할 수 있다.

from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Member(db.Model):
name = db.Column(db.String(20), primary_key = True)
age = db.Column(db.Integer, nullable=False)

💡 쿼리의 종류

일반적인 SQL 의 사용법과 비슷하다.
다만, 완전히 SQL이 아니라, 프로그래밍언어 적으로 사용해서, 사용방법이 조금씩은 다른 것 같다.

[ 출처 : elice ]

💡 사용 예시

  • equal ( == )
@app.route('/')
def list():
    member_list = Member.query.filter(Member.name == 'Elice')
    return " ".join(i.name for i in member_list)
  • not equal ( != )
@app.route('/')
def list():
    member_list = Member.query.filter(Member.name != 'Elice')
    return " ".join(i.name for i in member_list)
  • like ( like( ) )
@app.route('/')
def list():
    member_list = Member.query.filter(Member.name.like('Elice'))
    return " ".join(i.name for i in member_list)
  • in
@app.route('/')
def list():
    member_list = Member.query.filter(Member.name.in_(['Elice', 'Dodo']))
    return " ".join(i.name for i in member_list)
  • not in
@app.route('/')
def list():
    member_list = Member.query.filter(~Member.name.in_(['Elice', 'Dodo']))
    return " ".join(i.name for i in member_list)
  • is null
@app.route('/')
def list():
    member_list = Member.query.filter(Member.name == None)
    return " ".join(i.name for i in member_list)
  • is not null
@app.route('/')
def list():
    member_list = Member.query.filter(Member.name != None)
    return " ".join(i.name for i in member_list)
  • and
@app.route('/')
def list():
    member_list = Member.query.filter((Member.name == 'Elice') & (Member.age == '15'))
    return " ".join(i.name for i in member_list)
  • or
@app.route('/')
def list():
    member_list = Member.query.filter ((Member.name == 'Elice') | (Member.age == '15'))
    return " ".join(i.name for i in member_list)
  • order by
@app.route('/')
def list():
    member_list = Member.query.order_by(Member.age.desc())
    return " ".join(i.name for i in member_list)
  • limit
@app.route('/')
def list(limit_num = 5):
    if limit_num is None:
        limit_num = 5
    member_list = Member.query.order_by(Member.age.desc()).limit(limit_num)
    return " ".join(i.name for i in member_list)
  • offset
@app.route('/')
def list(off_set = 5):
    if off_set is None:
        off_set = 5
    member_list = Member.query.order_by(Member.age.desc()).offset(off_set)
    return " ".join(i.name for i in member_list)
  • count
@app.route('/')
def list():
    member_list = Member.query.order_by(Member.age.desc()).count()
    return str(member_list)

 

📝 Model 생성 예제

  • [ 참고 ] 저장된 DB를 확인할 때는 두 가지 방법으로 확인할 수 있다.
  • Member객체.query.all( ) = 리스트형으로 반환
  • Member객체.query.first( ) = 모델 하나의 객체로 반환하기 때문에 유의
# models.py

from main import db		# main.py에서 생성한 SQLAlchemy 객체인 db import

class Member(db.Model):		# db의 Model 클래스 상속, Member 모델 생성
	
    # Member 객체의 Atrribute 설정
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), nullable=False) 
    age = db.Column(db.Integer, nullable=False)
	
    # Member 객체 생성자
    def __init__(self,name,age):
        self.name = name
        self.age = age
    
# main.py

from flask import Flask, request, render_template, redirect, url_for
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
import config
db = SQLAlchemy()
migrate = Migrate()

app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)
migrate.init_app(app, db)

from models import Member	# main.py에서 Member 클래스를 import

@app.route('/')
def _list():
    name = ['Elice', 'Dodo', 'Checher', 'Queen']
    age = 15
	
    # 주어진 name 리스트를 사용하고, 나이와 함께, DB에 추가
    for data in name:
        member = Member(data,age)
        age += 1
        db.session.add(member)
        

    db.session.commit()		# DB를 commit()

    member_list = Member.query.all()	# member_list에 Member의 모든 튜플 저장

    if(type(member_list)!=type([])):
        member_list=[member_list]
    return render_template('member_list.html', member_list=member_list)

if __name__ == "__main__":
    app.run()
# config.py

import os
BASE_DIR = os.path.dirname(__file__)

SQLALCHEMY_DATABASE_URI = 'sqlite:///{}'.format(os.path.join(BASE_DIR, 'main.db'))
SQLALCHEMY_TRACK_MODIFICATIONS = False
<!-- ./templates/member_list.html -->

<!DOCTYPE html>
{% if member_list %}
    <ul>
        {% for member in member_list %}
             <li><p>{{ member.name }} {{ member.age }}</p></li>
        {% endfor %}
    </ul>
{% else %}
    <p>멤버가 없습니다.</p>
{% endif %}

[ 출처 : elice ]

 

📝 Model 사용해서 자료추가 예제

# models.py

from main import db

class Member(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(20), nullable=False)
    age = db.Column(db.Integer, nullable=False)
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
# main.py

from flask import Flask, request, render_template, redirect, url_for
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
import config

db = SQLAlchemy()
migrate = Migrate()

app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)
migrate.init_app(app, db)
from models import Member


@app.route('/list')
def _list():
    member_list = Member.query.all()
    return render_template('member_list.html', member_list=member_list)

@app.route("/", methods=["GET","POST"])
def _add():

    if request.method == 'POST':
        name = request.form['name']
   
        try:
            age = int(request.form["age"])
        except:
            return "나이는 숫자만 입력하세요."
            
        member = Member(name, age)
        db.session.add(member)
        db.session.commit()

        return redirect(url_for("_list"))
        
    else:
        return render_template('add.html')
if __name__ == "__main__":
    app.run()
# config.py

import os
BASE_DIR = os.path.dirname(__file__)

SQLALCHEMY_DATABASE_URI = 'sqlite:///{}'.format(os.path.join(BASE_DIR, 'main.db'))
SQLALCHEMY_TRACK_MODIFICATIONS = False
<!-- ./templates/add.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>HTML for python flask</title>
</head>

<body>
    <form action = "/" method="POST">
        <p>name : <input type="text" id = "name" name = "name"></p>
        <p>age : <input type="text" id = "age" name = "age"></p>
        <p>이름과 나이를 입력하고 제출버튼을 누르세요. <br><input type = "submit" value = "제출"/> </p>
    </form>
</body>
</html>


<!-- ./templates/member_list.html -->
<!DOCTYPE html>
{% if member_list %}
    <ul>
        {% for member in member_list %}
             <li><p>{{ member.name }} {{ member.age }}</p></li>
        {% endfor %}
    </ul>
{% else %}
    <p>멤버가 없습니다.</p>
{% endif %}

[ 출처 : elice ]

 

📝 Query 사용법 예제 ( 몇 가지만 )

  • equal, not equal, like
# main.py 에 추가
# config.py , models.py 는 동일

@app.route('/search', methods=['GET', 'POST'])
def _search():
    if request.method == 'POST':
        key = request.form['keyword']
        con = request.form['condition']
        if(con=='1'):
            member_list = Member.query.filter(Member.age == int(key))
        elif(con=='2'):
            member_list = Member.query.filter(Member.age != int(key))
        elif(con=='3'):
            member_list = Member.query.filter(Member.age.like(int(key)))
            
        return render_template('member_list.html', member_list=member_list)
    else:
        return render_template('search.html')
<!-- ./templates/search.html 추가 -->
<!-- add.html , member_list.html 은 model 생성 예제와 동일 -->

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>HTML for python flask</title>
</head>

<body>
    <form action = "/search" method="POST">
        <p><input type="text" id = "keyword" name = "keyword"></p>
        <p><input type="radio" name = "condition" value ="1" checked="checked">equal <input type="radio" name = "condition" value ="2">not equal <input type="radio" name = "condition" value ="3">like</p>
        <input type = "submit" value = "검색"/>
    </form>
</body>
</html>

[ 출처 : elice ]

  • in, not in, is null, is not null, and, or, order by, limit, offset 또한 위에 Member객체.query.filter( ) 안에 작성하는 것과 비슷해서 생략 ( 사용법만 필요할 때 숙지 )

 

728x90
반응형

📝 RDB

데이터베이스(DataBase)의 종류는 크게 1. 관계형 데이터베이스(RDB, 2. NoSQL(Not Only SQL)로 나뉜다.

RDB(Relation Database)는 관계형 데이터 모델을 기반으로 한 데이터 베이스다.
다시 말해, 키(Key) - 값(Value)들의 간단한 관계를 테이블화한 데이터베이스다. RDB는 다음 특징을 가진다.

  • 데이터 독립성이 높다.
  • 고수준의 DML을 사용해서, 결합, 제양, 투영 등의 관계 조작에 의해 비약적으로 표현 능력을 높일 수 있다.
  • 이들의 관계 조작에 의해 자유롭게 구조를 변경할 수 있다.

💡 RDB의 종류

  • Oracle
  • MySQL
  • MS-SQL
  • DB2
  • Maria DB
  • Derby
  • SQLite

 

📝 RDB와 Flask의 상호작용

Flask에서 RDB를 연동하면 어떻게 될까 ? Flask에서 입력 받은 내용들을 DB에 저장할 수 있어야 한다.

= 효율적인 데이터 관리 기능 제공

파이썬은 오픈 소스와 상용 데이터베이스에 대한 대부분의 데이터베이스 엔진을 위한 패키지를 가지고 있다.
앞으로의 포스팅에서는 그 중, sqlite3 와 Flask 어플리케이션 안에 있는 SQLAlchemy를 사용해서 진행한다.

SQLAlchemy파이썬 코드에 DB와 연결하기 위해 사용되는 라이브러리다.

[ 출처 : elice ]

 

📝 Flask - RDB 예제 : 게시판 구현하기

💡 DB 사용자 추가

from flask import Flask, render_template, request, url_for, redirect
import sqlite3 # salite3

app = Flask(__name__)
conn = sqlite3.connect("database.db")   # splite3 db 연결
print("Opened database successfully")
conn.execute("CREATE TABLE IF NOT EXISTS Board(name TEXT, context TEXT)")   # Board 라는 DB생성
print("TABLE Created Successfully")
name = [
    ["Elice", 15],
    ["Dodo", 16],
    ["checher", 17],
    ["Queen", 18]
]
for i in range(len(name)):
    conn.execute(f"INSERT INTO Board(name,context) VALUES('{name[i][0]}', '{name[i][1]}')")  # Board DB에 데이터 삽입
conn.commit()   # 지금껏 작성한 SQL, DB에 반영 commit
conn.close()    # 작성 다한 DB는 닫아줘야함 close

# ================= 여기서부터는 다시 Flask 영역 ==========================

@app.route('/')
def board():
    con = sqlite3.connect("database.db")
    cur = con.cursor()
    cur.execute("SELECT * FROM Board")
    rows = cur.fetchall()

    print("DB: ")
    for i in range(len(rows)):
        print(rows[i][0] + ':' + rows[i][1])
    return render_template("board1.html", rows = rows)


@app.route("/search", methods=["GET","POST"])
def search():
    if request.method == "POST":
        name = request.form["name"] # search.html 가보면, form에 name만 받기로 함.
        con = sqlite3.connect("database.db")
        cur = con.cursor()
        cur.execute(f"SELECT * FROM Board WHERE name='{name}'")
        rows = cur.fetchall()
        print("DB : ")
        for i in range(len(rows)):
            print(rows[i][0] + ':' + rows[i][1])
        return render_template("search.html", rows=rows)
    else:
        return render_template("search.html")

@app.route("/add", methods=["GET","POST"])
def add():
    if request.method == "POST":
        try:
            name = request.form["name"]
            context = request.form["context"]

            # DB에 접근해서, 데이터를 삽입할때는, 직접 DB를 열어야되는데, 윗 과정처럼, close까지 하기 힘드니깐, 하는 방식, 결과는 같은 것 !
            with sqlite3.connect("database.db") as con:
                cur = con.cursor()
                cur.execute(f"INSERT INTO Board(name,context) VALUES('{name}','{context}')")
                con.commit()
        except:
            con.rollback()  # DB 롤백함수, SQL이 오류나면, 반영전, 이전 상태로 돌리는 것
        finally:
            return redirect(url_for("board"))
    else:
        return render_template("add.html")


if __name__ == '__main__':
    app.run()
<!-- ./templates/board1.html -->

<!doctype html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="Generator" content="EditPlus®">
    <meta name="Author" content="">
    <meta name="Keywords" content="">
    <meta name="Description" content="">
    <title>SQLite3 게시판 등록</title>

    <style type="text/css">
        body {
            text-align: center;
        }
    </style>
</head>

<body>
    <h3>게시판</h3>
    <h4><a href="{{url_for('add')}}">추가</a> <a href="{{url_for('search')}}">검색</a><br><br>목록</h4>
    <table border=1 width="600" align="center">
        <thead>
            <td>이름</td>
            <td>내용</td>
        </thead>
        {% for row in rows %}
        <tr>
            <td>{{ row[0] }}</td>
            <td>{{ row[1] }}</td>
        </tr>
        {% endfor %}
    </table>
</body>

</html>
<!-- ./templates/add.html -->

<!doctype html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="Generator" content="EditPlus®">
    <meta name="Author" content="">
    <meta name="Keywords" content="">
    <meta name="Description" content="">
    <title>SQLite3 게시판 등록</title>

    <style type="text/css">
        body {
            text-align: center;
        }
    </style>
</head>

<body>
    <h3>게시판</h3>
    <h4>추가</h4>
    <form action="/add" method="POST">
        이름<br>
        <input type="text" name="name" /><br>
        내용<br>
        <input type="text" name="context" style="text-align:center; width:400px; height:100px;" /><br><br>
        <input type="submit" value="게 시" /><br>
    </form>
</body>

</html>
<!-- ./templates/search.html -->

<!doctype html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="Generator" content="EditPlus®">
    <meta name="Author" content="">
    <meta name="Keywords" content="">
    <meta name="Description" content="">
    <title>SQLite3 게시판 등록</title>

    <style type="text/css">
        body {
            text-align: center;
        }
    </style>
</head>

<body>
    <form action="/search" method="POST">
        <input type="text" name="name" />
        <input type="submit" value="검 색" /><br>
    </form>
    <h4>검색결과</h4>
    {% if rows%}
    <table border=1 width="600" align="center">
        <thead>
            <td>이름</td>
            <td>내용</td>
        </thead>
        {% for row in rows %}
        <tr>
            <td>{{ row[0] }}</td>
            <td>{{ row[1] }}</td>
        </tr>
        {% endfor %}
        {% else %}
        <p> 검색결과가 없습니다. </p>
        {% endif %}
    </table>
</body>

</html>

[ 출처 : elice ]

 

💡 중복 사용자 제어 ( 위에, DB 사용자 추가에서 변한 코드는 몇 없음

from flask import Flask, render_template, request, url_for, redirect
import sqlite3 # salite3

app = Flask(__name__)
conn = sqlite3.connect("database.db")   # splite3 db 연결
print("Opened database successfully")
conn.execute("CREATE TABLE IF NOT EXISTS Board(name TEXT, context TEXT)")   # Board 라는 DB생성
print("TABLE Created Successfully")
name = [
    ["Elice", 15],
    ["Dodo", 16],
    ["checher", 17],
    ["Queen", 18]
]
for i in range(len(name)):
    conn.execute(f"INSERT INTO Board(name,context) VALUES('{name[i][0]}', '{name[i][1]}')")  # Board DB에 데이터 삽입
conn.commit()   # 지금껏 작성한 SQL, DB에 반영 commit
conn.close()    # 작성 다한 DB는 닫아줘야함 close

# ================= 여기서부터는 다시 Flask 영역 ==========================

@app.route('/')
def board():
    con = sqlite3.connect("database.db")
    cur = con.cursor()
    cur.execute("SELECT * FROM Board")
    rows = cur.fetchall()

    print("DB: ")
    for i in range(len(rows)):
        print(rows[i][0] + ':' + rows[i][1])
    return render_template("board1.html", rows = rows)


@app.route("/search", methods=["GET","POST"])
def search():
    if request.method == "POST":
        name = request.form["name"] # search.html 가보면, form에 name만 받기로 함.
        con = sqlite3.connect("database.db")
        cur = con.cursor()
        cur.execute(f"SELECT * FROM Board WHERE name='{name}'")
        rows = cur.fetchall()
        print("DB : ")
        for i in range(len(rows)):
            print(rows[i][0] + ':' + rows[i][1])
        return render_template("search.html", rows=rows)
    else:
        return render_template("search.html", msg ="검색어를 입력해주세요.")

@app.route("/add", methods=["GET","POST"])
def add():
    if request.method == "POST":

        name = request.form["name"]
        context = request.form["context"]

        # DB에 접근해서, 데이터를 삽입할때는, 직접 DB를 열어야되는데, 윗 과정처럼, close까지 하기 힘드니깐, 하는 방식, 결과는 같은 것 !
        with sqlite3.connect("database.db") as con:
            cur = con.cursor()
            cur.execute(f"SELECT count(*) FROM Board WHERE name='{name}'")
            
            # 회원 한명, 추가할라 했는데, 그전에 들어온, name값이랑 같은 이름이 DB에 있으면, 중복회원자이므로, 못하게 제어함
            if cur.fetchall()[0][0] == 0:   # 중복이름이 없으면
                cur.execute(f"INSERT INTO Board(name,context) VALUES('{name}','{context}')")
                con.commit()
                cur.execute("SELECT * FROM Board")
                rows = cur.fetchall()
                return render_template("board1.html",rows= rows)        
            else:   # 중복이름이 있으면
                return render_template("add.html",msg = "중복사용자가 있습니다.")
    else:
        return render_template("add.html")


if __name__ == '__main__':
    app.run()
<!-- ./templates/board1.html -->

<!doctype html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="Generator" content="EditPlus®">
    <meta name="Author" content="">
    <meta name="Keywords" content="">
    <meta name="Description" content="">
    <title>SQLite3 게시판 등록</title>

    <style type="text/css">
        body {
            text-align: center;
        }
    </style>
</head>

<body>
    <h3>게시판</h3>
    <h4><a href="{{url_for('add')}}">추가</a> <a href="{{url_for('search')}}">검색</a><br><br>목록</h4>
    <table border=1 width="600" align="center">
        <thead>
            <td>이름</td>
            <td>내용</td>
        </thead>
        {% for row in rows %}
        <tr>
            <td>{{ row[0] }}</td>
            <td>{{ row[1] }}</td>
        </tr>
        {% endfor %}
    </table>
</body>

</html>
<!-- ./templates/add.html -->

<!doctype html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="Generator" content="EditPlus®">
    <meta name="Author" content="">
    <meta name="Keywords" content="">
    <meta name="Description" content="">
    <title>SQLite3 게시판 등록</title>

    <style type="text/css">
        body {
            text-align: center;
        }
    </style>
</head>

<body>
    <h3>게시판</h3>
    <h4>추가</h4>
    <form action="/add" method="POST">
        이름<br>
        <input type="text" name="name" /><br>
        내용<br>
        <input type="text" name="context" style="text-align:center; width:400px; height:100px;" /><br><br>
        <input type="submit" value="게 시" /><br>
    </form>
    {% if msg %}
    <p> {{ msg }} </p>
    {% endif %}
</body>

</html>
<!-- ./templates/reaserch.html -->

<!doctype html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="Generator" content="EditPlus®">
    <meta name="Author" content="">
    <meta name="Keywords" content="">
    <meta name="Description" content="">
    <title>SQLite3 게시판 등록</title>

    <style type="text/css">
        body {
            text-align: center;
        }
    </style>
</head>

<body>
    <form action="/search" method="POST">
        <input type="text" name="name" />
        <input type="submit" value="검 색" /><br>
    </form>
    <h4>검색결과</h4>
    {% if rows%}
    <table border=1 width="600" align="center">
        <thead>
            <td>이름</td>
            <td>내용</td>
        </thead>
        {% for row in rows %}
        <tr>
            <td>{{ row[0] }}</td>
            <td>{{ row[1] }}</td>
        </tr>
        {% endfor %}
        {% elif msg %}
        <p> {{ msg }} </p>
        {% else %}
        <p> 검색결과가 없습니다. </p>
        {% endif %}
    </table>
</body>

</html>

[ 출처 : elice ]

 

💡 게시판 내용 생성 및 조회

위에서, DB 사용자 생성, 중복제거 내용과 동일

 

💡 게시판 내용 수정 및 삭제

#DATABASE
from flask import Flask, render_template, request, url_for, redirect
import sqlite3
app = Flask(__name__)
conn = sqlite3.connect('database.db')
print ("Opened database successfully")
conn.execute("DROP TABLE IF EXISTS Board")  # Board 테이블이 기존에 있다면 삭제 (매번, 동일한 파일에서 실행하면, 내용이 겹쳐서 만듦)
conn.execute('CREATE TABLE IF NOT EXISTS Board (name TEXT, context TEXT)')  # Board 테이블이 기존에 없다면 생성
print ("Table created successfully")
name = [['Elice', 15], ['Dodo', 16], ['Checher', 17], ['Queen', 18]]
for i in range(4):
    conn.execute(f"INSERT INTO Board(name, context) VALUES('{name[i][0]}', '{name[i][1]}')")
conn.commit()
conn.close()

# root = home
@app.route('/')
def board():
    con = sqlite3.connect("database.db")
    cur = con.cursor()
    cur.execute("select * from Board")
    rows = cur.fetchall()
    print("DB:")
    for i in range(len(rows)):
            print(rows[i][0] + ':' + rows[i][1])
    return render_template('board1.html', rows = rows)

# 게시물 조회 (Read)
@app.route('/search', methods = ['GET', 'POST'])
def search():
    if request.method == 'POST':
        name = request.form['name']
        con = sqlite3.connect("database.db")
        cur = con.cursor()
        cur.execute(f"SELECT * FROM Board WHERE name='{name}' or context='{name}'")
        rows = cur.fetchall()
        print("DB:")
        for i in range(len(rows)):
            print(rows[i][0] + ':' + rows[i][1])
        return render_template('search.html', rows = rows)
    else:
        return render_template('search.html')

# 게시물 생성 (Create)
@app.route('/add', methods = ['GET', 'POST'])
def add():
    if request.method == 'POST':
        try:
            name = request.form['name']
            context = request.form['context']
            with sqlite3.connect("database.db") as con:
                cur = con.cursor()
                cur.execute(f"INSERT INTO Board (name, context) VALUES ('{name}', '{context}')")
                con.commit()
        except:
            con.rollback()
        finally : 
            con.close()
            return redirect(url_for('board'))
    else:
        return render_template('add.html')
# 위에 조회, 생성은 이전과 동일 

# 게시물 내용 갱신(Update)
@app.route("/update/<uid>", methods=["GET","POST"])
def update(uid):
    if request.method == "POST":
        name = request.form["name"]
        context = request.form["context"]
        
        # 내용 갱신하고
        with sqlite3.connect("database.db") as con:
            cur = con.cursor()  # connection한 db에 접근하기 위해, cursor 객체 만들기
            cur.execute(f"UPDATE Board SET name='{name}', context='{context}' WHERE name='{uid}'")
            con.commit()

        return redirect(url_for("board"))   # 갱신되었는지, board함수 리다이렉트해서, / 페이지 렌더링
    else:
        con = sqlite3.connect("database.db")
        cur = con.cursor()
        cur.execute(f"SELECT * FROM Board WHERE name='{uid}'")
        row = cur.fetchall()
        return render_template("update.html",row=row)

@app.route("/delete/<uid>")
def delete(uid):
    # 들어온 uid 값이랑 name이랑 delete 연산하고 반영
    with sqlite3.connect("database.db") as con:
        cur = con.cursor()
        cur.execute(f"DELETE FROM Board WHERE name='{uid}'")
        con.commit()

    return redirect(url_for('board'))  # 삭제 반영하고, 반영됬는지, board함수 리다이렉트, / 페이지 렌더링

if __name__ == '__main__':
    app.run()
<!-- ./templates/board1.html -->

<!doctype html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="Generator" content="EditPlus®">
    <meta name="Author" content="">
    <meta name="Keywords" content="">
    <meta name="Description" content="">
    <title>SQLite3 게시판 등록</title>

    <style type="text/css">
        body {
            text-align: center;
        }
    </style>
</head>

<body>
    <h3>게시판</h3>
    <h4><a href="{{url_for('add')}}">추가</a> <a href="{{url_for('search')}}">검색</a><br><br>목록</h4>
    <table border=1 width="600" align="center">
        <thead>
            <td>이름</td>
            <td>내용</td>
            <td>수정/삭제</td>
        </thead>
        {% for row in rows %}
        <tr>
            <td>{{ row[0] }}</td>
            <td>{{ row[1] }}</td>
            <td><a href="{{url_for('update', uid = row[0])}}">수정</a> <a
                    href="{{url_for('delete', uid = row[0])}}">삭제</a></td>
        </tr>
        {% endfor %}
    </table>
</body>

</html>
<!-- ./templates/add.html -->

<!doctype html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="Generator" content="EditPlus®">
    <meta name="Author" content="">
    <meta name="Keywords" content="">
    <meta name="Description" content="">
    <title>SQLite3 게시판 등록</title>

    <style type="text/css">
        body {
            text-align: center;
        }
    </style>
</head>

<body>
    <h3>게시판</h3>
    <h4>추가</h4>
    <form action="/add" method="POST">
        이름<br>
        <input type="text" name="name" /><br>
        내용<br>
        <input type="text" name="context" style="text-align:center; width:400px; height:100px;" /><br><br>
        <input type="submit" value="게 시" /><br>
    </form>
</body>

</html>
<!-- ./templates/research.html -->

<!doctype html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="Generator" content="EditPlus®">
    <meta name="Author" content="">
    <meta name="Keywords" content="">
    <meta name="Description" content="">
    <title>SQLite3 게시판 등록</title>

    <style type="text/css">
        body {
            text-align: center;
        }
    </style>
</head>

<body>
    <form action="/search" method="POST">
        <input type="text" name="name" />
        <input type="submit" value="검 색" /><br>
    </form>
    <h4>검색결과</h4>
    {% if rows%}
    <table border=1 width="600" align="center">
        <thead>
            <td>이름</td>
            <td>내용</td>
        </thead>
        {% for row in rows %}
        <tr>
            <td>{{ row[0] }}</td>
            <td>{{ row[1] }}</td>
        </tr>
        {% endfor %}
        {% else %}
        <p> 검색결과가 없습니다. </p>
        {% endif %}
    </table>
</body>

</html>
<!-- ./templates/update.html -->

<!doctype html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="Generator" content="EditPlus®">
    <meta name="Author" content="">
    <meta name="Keywords" content="">
    <meta name="Description" content="">
    <title>SQLite3 게시판 등록</title>

    <style type="text/css">
        body {
            text-align: center;
        }
    </style>
</head>

<body>
    <h3>게시판</h3>
    <h4>수정</h4>
    <form action="" method="POST">
        이름<br>
        <input type="text" name="name" /><br>
        내용<br>
        <input type="text" name="context" style="text-align:center; width:400px; height:100px;" /><br><br>
        <input type="submit" value="수 정" /><br>
    </form>
    <h4>기존</h4>
    <table border=1 width="600" align="center">
        <thead>
            <td>이름</td>
            <td>내용</td>
        </thead>
        <tr>
            <td>{{ row[0][0] }}</td>
            <td>{{ row[0][1] }}</td>
        </tr>
    </table>
</body>

</html>

[ 출처 : elice ]

 

728x90
반응형

📝 인증(Authentication)

인증은 유저의 identification을 확인하는 절차이다. 한 마디로, 유저의 아이디와 비밀번호를 확인하는 절차다.

인증을 하기 위해선 먼저, 유저의 아이디와 비밀번호를 생성할 수 잇는 기능도 필요하다.

우리가 생각하는 로그인 절차는 생각해보자.

1. 회원가입 : 아이디와 비밀번호를 생성한다.

  • 이때, 비밀번호는 암호화 저장한다.

2. 로그인 : 아이디와 비밀번호를 입력한다.

  • 입력한 비밀번호를 암호화 한 후, DB에 저장된 암호화 비밀번호와 비교한다.

3. 일치하면 로그인 성공, 일치하지 않으면 에러가 발생한다.

4. 로그인이 성공되면 access_tocken을 클라이언트에게 전송한다.

5. 유저는 로그인 성고 후 다음부터는 access_tocken을 첨부해서, request를 서버에 전송한다.

 

💡 비밀번호 암호화

유저의 비밀번호는 절대 그대로 DB에 저장되지 않는다. 만일 DB가 해킹 당할 경우, 그대로 유출되기 때문이다.
따라서, 유저의 비밀번호는 반드시 암호화하여 저장해야 한다. 그럴 경우 DB가 해킹 당하더라도 그대로 노출되지 않기 때문이다.

💡 허가(Authorization)

허가는 유저가 요청하는 request를 실행할 수 있는 권한이 있는 유저인가를 확인하는 절차이다.
예를 들어 해당 유저는 고객 정보를 볼 수 있지만, 수정할 권한은 없는 경우이다.

 

📝 Flask 로그인 구현하기

# 1번을 해보세요!
from flask import Flask, request, render_template, session, url_for, redirect

app = Flask(__name__)
app.secret_key = 'super secret key'
app.config['SESSION_TYPE'] = 'filesystem'
userinfo = {'Elice': '1q2w3e4r!!'}


@app.route("/")
def home():
    if session.get('logged_in'):
        return render_template('loggedin.html')
    else:
        return render_template('index.html')


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        name = request.form['username']
        password = request.form['password']
        try:
            if (name in userinfo):
                #2번을 해보세요!
                session["logged_in"] = True
                    #3번을 해보세요!
                return render_template('loggedin.html')
                    #4번을 해보세요!
            else:
                return '비밀번호가 틀립니다.'
            return '아이디가 없습니다.'
        except:
            return 'Dont login'
    else:
        return render_template('login.html')


@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        #4번을 해보세요!
        name = request.form['username'] 
        password = request.form['password']
        userinfo[name] = password
        
        return redirect(url_for('login'))
    else:
        return render_template('register.html')


@app.route("/logout")
def logout():
    session['logged_in'] = False
    return render_template('index.html')
    
if __name__ == '__main__':
    app.run()
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>HTML for python flask</title>
</head>

<body>
        <p>로그인이 필요한 서비스입니다.</p>
        <a href= "login">로그인창으로</a><br>
        <a href= "register">회원가입</a>
</body>
</html>


<!-- loggedin.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>HTML for python flask</title>
</head>

<body>
        <form action = "" method="post">
        <p>로그인 성공 페이지</p>
        <a href= "/logout">로그아웃</a>
        </form>
</body>
</html>


<!-- login.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>HTML for python flask</title>
</head>

<body>
    <form action = "" method="post">
        <p>name : <input type="text" name = "username"></p>
        <p>password : <input type="password"  name = "password"></p>
        <p>이름과 비밀번호를 입력하고 로그인버튼을 누르세요.<br><input type = "submit" value = "로그인"/> </p>
    </form>
</body>
</html>


<!-- register.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>HTML for python flask</title>
</head>

<body>
    <form action = "" method="post">
        <p>name : <input type="text" name = "username"></p>
        <p>password : <input type="password" name = "password"></p>
        <p>이름과 비밀번호를 입력하고 생성버튼을 누르세요.<br> <input type = "submit" value = "생성"/> </p>
    </form>
</body>
</html>

[ 출처 : elice ]

 

📝 로깅

로깅은 프로그램이 작동할 때 발생하는 이벤트를 추적하는 행위이다.
프로그램의 문제들을 파악하고 유지보수하는데 사용되고, 로깅을 통해 발생한 에러를 추적 가능하다.

운영 중인 웹 서비스에서 문제가 발생했을 경우, 해당 문제의 원인을 파악하려면 문제가 발생했을 때의 정보가 필요하다.
이런 정보를 얻기 위해서 중요한 기능의 실행되는 부분에 적절한 로그(log)를 남겨야 한다.
일반적인 로그 기록의 이점은 다음과 같다.

  • 로그는 성능에 관한 통계와 정보를 제공할 수 있다.
  • 로그는 재현하기 힘든 버그에 대한 유용한 정보를 제공할 수 있다.
  • 설정이 가능할 때, 로그는 예기치 못한 특정 문제들을 디버그하기 위해 그 문제들을 처리하도록  코드를  수정하여 다시 적용하지 않아도, 일반적인 정보를 저장할 수 있다.

💡 로깅 레벨(Loggin Level)

다음 순서로 로깅이 된다.

DEBUG < INFO < WARNING < ERROR < CRITICAL

  • DEBUG : 상세한 정보
  • INFO : 일반적인 정보
  • WARNING : 예상치 못하거나, 가까운 미래에 발생할 문제
  • ERROR : 에러 로그, 심각한 문제
  • CRITICAL : 프로그램 자체가 실행되지 않을 수 있는 문제

파이썬 로거 레벨 설정에 따라, 하위 레벨은 출력되지 않는다.
기본 로거 레벨 세팅은 WARNING 이다. 때문에  설정 없이는 INFO, DEBUG를 출력할 수 없다.

💡 python logger

기본적으로 다음 로깅 이력은 남기는 것이 좋다.

  • 서버 시작 로그
  • 서버 포트 번호
  • 함수 호출
  • 데이터의 입출력
#예시 코드
import logging 

if __name__ : '__main__': 
    logging.info("hello elice!")

위 코드로 실행하면, "hello elice!" 가 출력되지 않는다. 위에서 언급했듯이, 로깅의 기본 세팅은 WARNING이기 때문
그렇다면 어떻게 하면 "hello elice!" 가 출력이 될까 ?  아래 코드와 같이 설정하면 출력된다.

import logging 

if __name__ : '__main__': 
    logger = logging.getLogger() 
    logger.setlevel(logging.DEBUG) 	# 로깅 기본세팅 WARNING -> DEBUG로 변경
    logger.info("hello elice!")

 

📝 Flask logger

플라스크는 0.3 버전부터 logger를 플라스크 내부에서 제공하기 시작했다. 😮
(플라스크에서 기본적으로 제공하는 로깅을 제외하고, 일반 python logging을 사용해도 무방하다. )

# 레벨에 따른 함수가 이미 있어서, 따로 로깅레벨 설정을 안해도 된다.

from flask import Flask 
app = Flask(__name__) 
if __name__ == '__main__': 

    app.logger.info("test") 
    app.logger.debug("debug test") 
    app.logger.error("error test") 
    app.run()

 

💡 Flask logger 구현

from flask import Flask, render_template

app = Flask(__name__)

# .errorhandler(에러코드) : flask 내부에 기본적으로 있는 에러 핸들러
# 특정 에러에 대하여 errorhandler를 사용하면, 해당 에러가 발생했을 때 매칭된다.
@app.errorhandler(404)
def page_not_found(error):
    app.logger.error(error)
    return "페이지를 찾을 수 없습니다."

@app.route("/")
def hello_elice():
    return "Hello world!"

if __name__ == '__main__':
    app.run()

[ 출처 : elice ]

 

728x90
반응형

📝 렌더링 템플릿

렌더링(Rendering) : 마크업 언어를 브라우저(Client)에 보여주기 위한 과정

플라스크 내에서 html 파일을 사용해 웹 사이트를 제작할 수 있다. 이 때 사용하는 것이 렌더링 템플릿이다.
html 코드들을 플라스크내에 사용하는 것보다 파일을 따로 만들어 라우팅하는 방법이 유지보수에서 이롭다.

플라스크는 render_template( )을 이용해 html 파일을 렌더링하여 브라우저에 보여줄 수 있습니다.

from flask import render_template

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
    return render_template('hello.html', name=name)

hello 함수의 URL에 접근할 때, hello.html을 반환하며 해당 html 파일의 매개변수 name에 함수의 매개변수인 name을 전달한다.

 

📝 Jinja2

렌더링 템플릿을 통해 화면을 출력할 때 html에서는 아래와 같은 Jinja2 문법으로 해당 내용을 띄울 수 있습니다.

{% for row in rows %}
<tr>
    <td>{{ loop.index }}</td>
    <td>{{ row[0] }}</td>
    <td>{{ row[1] }}</td>
</tr>
{% endfor %}


<!--
	일반적인 형식
    
	{% if문 or for문 %}
    
    {{ 변수나 표현식}}
    	...	....
    {% endif 또는 endfor %}
-->

Docs : jinja.palletsprojects.com/en/2.11.x/templates/

 

Template Designer Documentation — Jinja Documentation (2.11.x)

This document describes the syntax and semantics of the template engine and will be most useful as reference to those creating Jinja templates. As the template engine is very flexible, the configuration from the application can be slightly different from t

jinja.palletsprojects.com

 

📝 블루 프린트

Flask의 요청으로 URL을 생성 할 때 화면을 출력하는 함수를 블루 프린트와 연결한다.

블루 프린트는 웹 애플리케이션의 개체를 미리 요구하지 않고 기능을 정의할 수 있다.
또한, 파일을 여러개로 나누어 유지 보수 측면에서 매우 효과적으로 개발할 수 있게끔 할 수 있다.

블루 프린트 없이 flask 웹 애플리케이션을 만들 수 있다.
하지만 여러 기능이 포함된 웹 애플리케이션을 제작하고자 한다면 블루프린트를 사용하는 것을 권장한다.

from flask import Blueprint, render_template, abort
from jinja2 import TemplateNotFound

simple_page = Blueprint('simple_page', __name__,
                        template_folder='templates')

@simple_page.route('/', defaults={'page': 'index'})
@simple_page.route('/<page>')
def show(page):
    try:
        return render_template('pages/%s.html' % page)
    except TemplateNotFound:
        abort(404)

@simple_page.route 장식자를 이용해 함수를 바인딩하면 구현한 show() 함수를 통해 블루 프린트가 동작하게 된다.
만약 블루 프린트 등록하려면 아래와 같이 register_blueprint() 함수를 이용하면 된다.

from flask import Flask
from yourapplication.simple_page import simple_page

app = Flask(__name__)
app.register_blueprint(simple_page)

 

📝 예제 ) 간단한 게시판 ( CRUD )

from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)
board = []

# 게시판 내용 조회 (Read)
@app.route('/')
def index():
    return render_template('list.html', rows=board)

# 게시판 내용 추가 (Create)
@app.route('/add',methods=["POST"])
def add():
    if request.method == "POST":
        name = request.form["name"]
        context = request.form["context"]
        board.append([name,context])
        return redirect(url_for("index"))
    else:
        return render_template("list.html", rows=board)

# 게시판 내용 갱신 (Update)
@app.route('/update/<int:uid>', methods=["GET","POST"])
def update(uid):
    if request.method == "POST":
        index = uid - 1
        name = request.form["name"]
        context = request.form["context"]
        
        board[index] = [name,context]   # 기존의 board내용에 덮어쓰기
        return redirect(url_for("index"))
    else:
        return render_template("update.html",index=uid,rows=board)

# 게시판 내용 삭제 (Delete)
@app.route('/delete/<int:uid>')
def delete(uid):
    index = uid - 1
    del board[index]

    return redirect(url_for("index"))


if __name__ == '__main__':
    app.run(debug=True)
<!-- ./template/list.html -->

<!doctype html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="Generator" content="EditPlus®">
    <meta name="Author" content="">
    <meta name="Keywords" content="">
    <meta name="Description" content="">
    <title>SQLite3 게시판 등록</title>

    <style type="text/css">
        body {
            text-align: center;
        }
    </style>
</head>

<body>
    <h3>게시판</h3>
    <h4>추가</h4>
    <form action="/add" method="POST">
        이름<br>
        <input type="text" name="name" /><br>
        내용<br>
        <input type="text" name="context" style="text-align:center; width:400px; height:100px;" /><br><br>
        <input type="submit" value="게 시" /><br>
    </form>
    <h4>목록보기</h4>
    <table border=1 width="600" align="center">
        <thead>
            <td>목차</td>
            <td>이름</td>
            <td>내용</td>
            <td>수정, 삭제</td>
        </thead>
        {% for row in rows %}
        <tr>
            <td>{{ loop.index }}</td>
            <td>{{ row[0] }}</td>
            <td>{{ row[1] }}</td>
            <td><a href="{{url_for('update', uid = loop.index)}}">수정</a> <a
                    href="{{url_for('delete', uid = loop.index)}}">삭제</a></td>
        </tr>
        {% endfor %}
    </table>
</body>

</html>
<!-- ./templates/update.html -->

<!doctype html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="Generator" content="EditPlus®">
    <meta name="Author" content="">
    <meta name="Keywords" content="">
    <meta name="Description" content="">
    <title>SQLite3 게시판 등록</title>

    <style type="text/css">
        body {
            text-align: center;
        }
    </style>
</head>

<body>
    <h3>게시판</h3>
    <h4>수정</h4>
    <form action="/update/{{index}}" method="POST">
        이름<br>
        <input type="text" name="name" /><br>
        내용<br>
        <input type="text" name="context" style="text-align:center; width:400px; height:100px;" /><br><br>
        <input type="submit" value="수 정" /><br>
    </form>
    <h4>기존</h4>
    <table border=1 width="600" align="center">
        <thead>
            <td>목차</td>
            <td>이름</td>
            <td>내용</td>
        </thead>
        <tr>
            <td>{{ index}}</td>
            <td>{{ rows[index-1][0] }}</td>
            <td>{{ rows[index-1][1] }}</td>
        </tr>
    </table>
</body>

</html>

[ 출처 : elice ]

 

728x90
반응형

📝 REST란 무엇인가 ?

REST : Representational State Transfer

= 자원을 이름(자원의 표현)으로 구분하여 해당 자원의 상태를 주고 받는 것을 의미한다.
= 즉, 자원(resourece) 의 표현(representation)에 의한 "상태 전달"

  • 자원의 표현
    • 자원 : 해당 소프트웨어가 관리하는 모든 것 ( ex. 그림, 문서, 데이터 등 )
    • 자원의 표현 : 그 자원을 표현하기 위한 이름
  • 상태(정보) 전달
    • 데이터가 요청되는 시점에서 자원의 상태(정보)를 전달한다.
    • JSON 혹은 XML을 통해 데이터를 주고 받는 것이 보통의 전달 방식이다.

WWW(World Wide Web) 와 같은 시스템을 위한 소프트웨어 개발 아키텍처의 한 형식이다.

REST는 기본적으로 웹의 기존 기술과 HTTP 프로토콜을 그대로 사용하기 때문에, 웹의 장점을 최대한 활용할 수 있는
아키텍처 스타일이다. REST는 네트워크 상에서 클라이언트와 서버 사이의 통신 방식 중 하나이다.

 

💡 REST의 구체적인 개념

HTTP URI(Uniform Resource Identifier)을 통해 자원을 명시하고, HTTP Method(POST, GET, PUT, DELETE)를 통해 해당 자원에 대한 CRUD Operation을 적용하는 것을 의미한다.

  • REST는 자원 기반의 구조(ROA) 설계의 중심에 자원이 있고, HTTP Method를 통해 자원을 처리하도록 설계된 아키텍쳐를 의미한다.

💡 CRUD

  • Create : 생성(POST)
  • Read : 조회(GET)
  • Update : 갱신(PUT)
  • Delete : 삭제(DELETE)
  • HEAD : header 정보 조회(HEAD)

💡 REST 장단점

장점

  • 서버와 클라이언트의 역할을 명확하게 분리한다.
  • 여러가지 서비스 디자인에서 생길 수 있는 문제를 최소화한다.
  • REST API 메시지가 의도하는 바를 명확하게 나타내므로, 의도하는 바를 쉽게 파악할 수 있다.
  • HTTP 표준 프로토콜에 따르는 모든 플랫폼에서 사용이 가능하다.
  • HTTP 표준 프로토콜의 표준을 최대한 활용하여 여러 추가적인 장점을 함께 가져갈 수 있게 해준다.

단점

  • 사용할 수 있는 메소드가 4가지 밖에 없다. (POST,GET,PUT,DELETE)
  • 표준이 존재하지 않는다.

💡 그럼에도, REST가 왜 필요한가 ?

  • 다향한 클라이언트의 등장
  • 애플리케이션 분리 및 통합

최근 서버 프로그램은 다양한 브라우저(chrome, Explorer, safari 등)와 안드로이드폰, 아이폰과 같은 모바일 디바이스에서도 통신할 수 있어야 하기 때문이다.

💡 REST 구성요소

  • 자원(Resource) : URI
    • 웹 서버의 자원은 각자 이름을 가지고 있다.
    • 따라서, 클라이언트는 이러한 이름을 통해 원하는 정보를 찾을 수 있다.
    • 이때 서버 자원의 이름을 URI라고 한다.
  • 행위(Verb) : HTTP Method
    • HTTP 프로토콜은 GET,POST,PUT,DELETE와 같은 메서드를 제공한다.
  • 표현(Representation of Resource)
    • 클라이언트가 자원의 상태(정보)에 대해 요청하면, 서버는 이에 적절한 응답을 보내야한다.
    • REST의 하나의 자원은 JSON, XML, TEXT, RSS 등 여러 형태의 응답으로 나타낼 수 있다.
    • 하지만, 보통 JSON 이나 XML을 통해 데이터를 주고 받는 것이 일반적이다.

 

📝 HTTP Method : [ POST ]

HTTP 메소드 중 POST는 요청을 처리하기 위해 사용자(클라이언트)로부터 특정 양식(form)의 "데이터를 암호화"하여 서버로 전송하는 방법이다.

서버는 POST 방식으로 전달 받은 데이터를 통해 특정 동작을 수행할 수 있다.
예로 로그인 예시를 보자.

<!-- login.html -->
<html>  
   <body>  
      <form action = "/login" method = "post">  
         <table>  
        <tr><td>아이디</td>  
        <td><input type ="text" name ="id"></td></tr>  
        <tr><td>비밀번호</td>  
        <td><input type ="password" name ="pass"></td></tr>  
        <tr><td><input type = "submit"></td></tr>  
    </table>  
      </form>  
   </body>  
</html>
# login.py

from flask import *  
# Flask 인스턴스 생성
app = Flask(__name__) 


@app.route('/')
def hello():
    return render_template('login.html')


# login 주소에서 POST 방식의 요청을 받았을 때
@app.route('/login',methods = ['POST'])  
def login():  
    id = request.form['id']  
    passwrd = request.form['pass'] 

    if id=="Elice" and passwrd=="Awesome!": 
        return "Welcome %s" % id  


if __name__ == '__main__':  
    app.run()

[ 출처 : elice ]

이는, login.html 에서 "아이디"와 "비밀번호"를 입력하고, "버튼을 누르면" login.py 에서 이를 인식해서
"Elice" 이고, 비밀번호가 "Awesome!" 이면, "Welcome Elice" 라는 문구가 나타난다.

 

📝 HTTP Method : [ GET ]

GET 요청 또한 사용자(클라이언트)로부터 특정 양식(form)의 데이터를 서버로 전송하는 방법이다.

가장 큰 차이는 정보를 URL에 붙여서 보내게 되고, 즉, "데이터가 암호화되지 않는다는 점"이다.

앞선 로그인 예시를 통해 보자.

<!-- login.html -->
<html>  
   <body>  
      <form action = "/login" method = "get">  
         <table>  
        <tr><td>아이디</td>  
        <td><input type ="text" name ="id"></td></tr>  
        <tr><td>비밀번호</td>  
        <td><input type ="password" name ="pass"></td></tr>  
        <tr><td><input type = "submit"></td></tr>  
    </table>  
      </form>  
   </body>  
</html>
# login.py

from flask import *  
# Flask 인스턴스 생성
app = Flask(__name__)  


# login 주소에서 GET 방식의 요청을 받았을 때
# @app.route()에서 methods 인자의 기본 값은 GET으로 생략해서 작성해도 됩니다.
@app.route('/login',methods = ['GET'])  
def login():  
      id = request.args.get['id']  
      passwrd = request.args.get['pass']  
      if id=="Elice" and passwrd=="Awesome!": 
          return "Welcome %s" % id  


if __name__ == '__main__':  
   app.run()

[ 출처 : elice ]

 

GET이 POST 방식과 가장 큰 차이점은 URL에 "https://localhost:8080/login?id=elice&pass=awesome" 으로 표현된다.

즉, 로그인 할 때, URL에 아이디와 비밀번호가 표기된다. ( = 데이터가 암호화가 되지 않는다. )
이는, 한편으로 정보보안을 유지할 수 없다는 것을 의미한다.

따라서, "로그인"과 같이 정보를 가려야할 때는 GET이 아닌 POST 방식을 사용하는 것이 맞다.

 

📝 HTTP - POST 예제

POST 요청은 눈에 파라미터가 보이는 GET요청과 달리 전달하려는 정보가 HTTP body에 포함되어 전달된다.

전달하려는 정보는 form data, json strings 등이 있다.

render_templates( ) 메소드 = Flask 에서 html 파일을 "랜더링" 하기 위해서 사용한다.
( 랜더링(Rendering) = 웹 브라우저 상의 화면을 표현하는 과정 )

POST로 전달받은 "값"은 request.form[ '변수명' ]을 사용하면 html의 form에서 해당 변수명에 만족 값을 사용가능

from flask import Flask
from flask import request
from flask import render_template

app = Flask(__name__)

@app.route('/')
def hello():
    return render_template('index.html')


@app.route("/post", methods=['POST'])
def post():
    #1번을 해보세요!
    value = request.form["input"]
    msg = "%s 님 환영합니다." % value
    return msg


if __name__ == '__main__':
    app.run()
<!-- 사용하는 html 파일들은, 반드시 templates 디렉터리 안에 있어야한다. !! -->
<!-- /templates/index.html -->


<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>HTML for python flask</title>
</head>

<body>
    <form action = "/post" method="post">
        <p>
            이름 : 
            <input type="text" name = "input">
        </p>
        <p>
            이름을 입력하고 제출버튼을 누르세요.<br>
            <input type = "submit" value = "제출" onclick = "alert('제출 완료!')" />
        </p>
    </form>
</body>
</html>

아이디 입력란에 아이디를 입력하고 제출하면, 제출완료 ! 와 동시에
post 방식으로, input에 "id님 환영합니다." 출력 

 

📝 HTTP - GET 예제

Flask 에서 GET 또는 POST로 전달받은 정보를 request 모듈을 사용하여 활용할 수 있다.

GET 방식의 경우 모든 파라미터를 URL로 보내 요청하는 방식이라고 했다.

request 모듈에는 GET 방식으로 URL의 인자를 'key = value' 형태로 전달했을 때, 다음 방식으로 활용한다.

  • 다음과 예시와 같은 주소를 입력했을 시, number의 값은 = 1 (https://주소.com/?key=1)
number = request.args.get('key`, 초기값)

초기값은 key 값으로 아무 값도 넘겨받지 못했을 때 활용되는 값이다. 이후에는 URL을 통해 '/?key=value' 형태로
key값을 전달받으면 해당 값을 사용한다.

여러개의 key와 값을 전달받으려면 다음 예시와 같이 '&' 기호를 입력하여 추가할 수 있다. 

  • key의 값은 1, value의 값은 2 (https://주소.com/?key=1&value=2)
from flask import Flask
from flask import request

app = Flask(__name__)

@app.route('/')     # Default는  HTTP methods = ['GET ']
def user_juso():
    temp1 = request.args.get("word1","Elice")   # GET 방식으로, request.args.get() 할때, 
    temp2 = request.args.get("word2","Hello")   # 주소에 값이 지정안되면 기본값은, 다음과 같음을 의미
    return temp1 + "<br>" + temp2

if __name__ == '__main__':
    app.run()

GET 방식 -> URL에 값을 전달할 경우
      GET -> URL 에 값이 전달되지 않을 경우        = default 값으로 대체

 

📝 HTTP - GET & POST 예제

GET과 POST를 동시에 사용해서 웹 페이지를 동작 시킬 수 있다.

method가 GET이면 이름을 입력받는 웹 페이지를 동작시키고, POST라면 입력 같은 값을 포함하는 문장을 출력한다.

<!-- templates/index2.html -->

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>HTML for python flask</title>
</head>

<body>
    <form action = "/" method="POST">
        <p>
            이름 :
            <input type="text" id = "input" name = "input">
        </p>
        <p>
            이름을 입력하고 제출버튼을 누르세요.
            <input type = "submit" value = "제출" onclick = "alert('제출 완료!')" />
        </p>
    </form>
</body>
from flask import Flask, render_template, request

app = Flask(__name__)

@app.route('/', methods=["GET","POST"]) # GET 또는 POST가 호출될 수 있다는 것
def post():
    if request.method == "POST":
        value = request.form["input"]   # index2.html가서 form태크를 보면 요소에 method = "POST" 일것이다.
        return f"{value}님 환영합니다."
    
    if request.method == "GET":
        return render_template("index2.html")

if __name__ == '__main__':
    app.run()

GET 
form 입력, 즉 POST로 데이터를 보내겠다.
POST

 

📝 API 와 End point가 무엇인가 ?

API프로그램들이 서로 상호작용하는 것을 도와주는 매체이다.

우리가 음식점에 갔을 때, 아래와 같이 행동한다. 점원은 손님에게 메뉴를 알려주고, 주방에 주문받은 요리를 요청한다.
그 다음 주방에서 완성된 요리를 손님에게 다시 전달한다.

  • 손님과 요리사는 "End Point"
  • 점원은 이 둘 사이에서 "API"

[ 출처 : elice ]

API는 손님(프로그램)이 주문할 수 있게 메뉴(명령 목록)를 정리하고, 주문(명령)을 받으면 요리사(응용프로그램)와 상호작용하여 요청된 메뉴(명령에 대한 값)를 전달한다.

 

💡 API의 역할은 무엇인가 ?

1. API는 서버와 데이터베이스에 대한 출입구 역할을 한다.

  • 데이터베이스에는 정보들이 저장된다. 
  • 따라서, 모든 사람들이 이 데이터베이스에 접근할 수 있으면 안된다.
  • API는 이를 방지하기 위해 우리가 가진 서버와 데이터베이스에 대한 출입구 역할을 하며, 허용된 사람들에게만 접근성을 부여해준다.

2. API는 프로그램과 기기가 원활하게 통신할 수 있도록 한다.

  • 우리가 흔히 알고 사용하고 있는 스마트폰 어플이나 프로그램과 기기가 데이터를 원활히 주고 받을 수 있도록 돕는 역할을 한다.

3. API는 모든 접속을 표준화한다.

  • API는 모든 접속을 표준화하기 때문에 기기나 운영체제 등과 상관없이 누구나 동일한 권한을 얻을 수 있다.

 

💡 API Testing

API Testing 은 API를 테스트하여 기능, 성능, 신뢰성, 보안 측면에서 기대를 충족하는지 확인하는 테스팅의 한 유형

완벽하게 작동하는 API만이 실제 애플리케이션에서 사용할 수 있다.
따라서, 정상적으로 완벽하게 작동하는지 테스트 해야한다.
API 테스트를 진행하게 되면 향후 특정 시점에서 발생할 수 있는 애플리케이션의 많은 문제점들을 해결할 수 있다.

[ 출처 : elice ]

728x90
반응형

📝 Flask

플라스크는 많은 사람이 "Micro Web Framework" 라고 부른다.
여기서 "Micro" = "프레임워크를 간결하게 유지하고 확장할 수 있도록 만들었다" 라는 의미

  • Micro : 가벼운 기능 제공, 가볍게 배우고 가볍게 사용할 수 있으며, 확장성이 넓다.
  • Framework : 이미 작성된 코드의 모임인 "라이브러리" 그 이상의 의미, 어플리케이션을 개발하기 위한 일정한 "뼈대"를 제공해주는 기술

간단한 웹 사이트, 간단한 API 서버를 만드는 데에 특화 되어있는 Python Web Framework 이다.
요즘은 클라우드 컴퓨팅의 발달로 Docker, Kubernetes와 접목해서, 소규모 컨테이너 단위로 기능 별 개발 후, 한꺼번에 배포하는 방식이나 배포 후 기능을 추가하는 식으로 자주 사용하고 있다.

💡 플라스크 장점

  • 가볍게 배울 수 있다 : HTML/CSS/JS 알고, python을 어느정도 알고있다면, 빠르게 배울 수 있다.
  • 가볍게 사용 가능하다. : 코드 몇 줄이면, 만들 수 있다.
  • 가볍게 배포할 수 있다. : pip을 사용하여 Flask를 다운받고 배포하면 끝이다.

💡 플라스크의 단점

  • Django에 비해서 자유도가 높으나, 제공해주는 기능이 덜 하다.
  • 복잡한 어플리케이션을 만들려고 할 때, 해야 할 것들이 많다.

Flask는 소규모의 어플리케이션을 빠르게 만들 수 있고, 배포 환경에 따라 대규모 어플리케이션의 기능 확장의 역할을 하기 쉬운 장점이 있다.
반면, Django는 대규모 어플리케이션을 빠르게 만들 수 있으며, 기본으로 제공해 주는 기능이 많다.

Flask Django
ORM 기능이 제공되지 않음 ORM 기능이 내장
짧은 코드로 웹 서버 구동가능 자동으로 관리자 화면 구성

ORM(Object Relational Mapping) : ORM은 DB와 OOP 언어 간의 호환되지 않는 데이터를 변환하는 프로그래밍 기법

 

📝 로컬에서 Simple Web Server띄우기 : Hello World !!

환경 : VS code, Python.exe 20.3.3 version 이상
(오류 발생시, 보통, 파이썬 실행파일 버젼이, 이전 버젼이어서 발생할 수 있으니 그럴 경우, 버젼 업그레이드 필요)

c:/python39/python.exe -m pip install --upgrade pip
c:/python39/python.exe -m pip install --upgrade pip --user (액세스 거부시)

성공 시, 아래와 같은 "Hello world!"를 보여주는 간단한, 플라스크 코드 작성

from flask import Flask  # 플라스크 import

app = Flask(__name__)   # app 이라는 이름의, flask앱을 하나 만들꺼야.

@app.route('/')             # 라우팅, '/'는 접속시, root 경로
def hello_World():          # '/'과 매칭되는 함수(root 경로일 시), 페이지에서 작동할 메소드
    return "Hello World!"

if __name__ == '__main__':  # 모듈 명이 "main"일 때만 실행하도록 조건문
    app.run()

127.0.0.1:5000(로컬) 에 출력된 Hello World

 

📝 Flask  HTTP 통신 : HTTP Methods

먼저 HTTP 개념을 모른다면 아래, 포스팅을 잠깐 읽고 오자. 

 

[Network] HTTP란 ?

📝 개요 우리 모두, 대부분 정보를 인터넷으로 확인한다. 모든 "웹 브라우저"에 있는 정보에 접근할 때는, "URL"을 통하여 접근한다. 반대로 생각하면, "URL을 모르는 정보에는 접근을 할 수 없다"

youngminieo1005.tistory.com

Flask는 HTTP 통신을 위해 다음과 같은 Methods를 제공한다.

Method 설명
GET 암호화되지 않은 형태의 데이터를 서버로 전송하는데 사용되는 가장 일반적인 방법
HEAD GET과 유사한 방법으로 Response Body를 포함지 않고 사용
POST 특정 양식의 데이터를 암호화하여 서버로 전송하는데 사용
PUT 특정 대상의 데이터를 갱신(Update)하는데 사용
DELETE URL에 지정된 대상을 삭제(DELETE)하는데 사용

 

📝 URL Routing 이란 ?

URL Routing : 서버에서 특정 URL에 접속했을 시, 보여줄 페이지를 정해주는 것

즉, 특정 URL을 일부 작업을 수행하기 위한 관련된 기능과 "Mapping"하는 데 사용된다.

https://page.com/home  # 홈화면을 보여주고
https://page.com/login # 로그인화면을 보여준다.

위에 예시같은 것이, URL Routing 이다.

💡 Flask에서 URL Mapping을 어떻게 할까 ?

Flask에서 URL Routing은 @app.route(' ')를 통해 수행한다.
또한, 여기서 @를 통해 선언하는 방식 = 데코레이터(decorator)라고 부른다.

from flask import Flask  # 플라스크 import

app = Flask(__name__)   # app 이라는 이름의, flask앱을 하나 만들꺼야.

@app.route('/')             # 라우팅, '/'는 접속시, root 경로
def home():          # '/'과 매칭되는 함수(root 경로일 시), 페이지에서 작동할 메소드
    return "This is Home page !!"

@app.route('/hello')        # "/hello" URL 접속 시 
def hello():
    return "Hello world !!"
if __name__ == '__main__':  # 모듈 명이 "main"일 때만 실행하도록 조건문
    app.run()

localhost 페이지
localhost/hello 페이지

 

📝 Variable Rules

플라스크에서는 route( ) 를 사용해서 url path의 값을 활용할 수 있다.
url path의 문자열, 정수, 서브경로를 활용할 수 있다.

  • 문자열은 사용할 변수명을 <변수명>으로 감싸주면 함수에서 전달받은 값을 활용할 수 있다.
  • 정수는 <int:변수명> 형태로 사용이 가능하다.
  • 서브경로는 <path:subpath> 형태로 사용이 가능하다.
from flask import Flask

app = Flask(__name__)

@app.route('/user/<username>')
def show_user_profile(username):
    return f"User {username}"

@app.route('/post/<int:post_id>')
def show_post(post_id):
    return f"Post {post_id}"

@app.route('/path/<path:subpath>')
def show_subpath(subpath):
    return f"Subpath {subpath}"

if __name__ == '__main__':
    app.run()

문자열  = <변수명>

 

정수 = <int:변수명>
서브경로 = <path:서브경로>

 

📝 데이터 반환하기 - jsonify( )

Flask 에서는 데이터를 json 파일 형식으로 데이터를 교환한다.

json 파일 형식은 웹 사이트 상에서 정보를 주고 받는 형식이다. Dictionary형태와 유사한 구조를 가진다.

Flask 에는 데이터를 json 형식으로 바꿔주는 jsonify( ) 메소드가 있다.

from flask import Flask, jsonify    # jsonify import

app = Flask(__name__)

@app.route('/')
def json():
    people = [
        {'name' : 'natto' , 'birth-year' : 2020},
        {'name' : 'apple' , 'birth-year' : 2021},
        {'name' : 'banana' , 'birth-year' : 2022}
    ]
    return jsonify(people)

if __name__ == '__main__':
    app.run()

jsonify( ) 반환 = json형식

 

📝 URL 설계하기 - URL Building

웹 사이트의 기본 구조를 설계하고 사용자들이 웹 사이트에 접속하기 위해 URL 구조를 설계할 필요성이 있다.

만약 한 페이지에서 다른 페이지로 이동할 수 있도록 "링크"를 생성하려면 어떻게 해야 할까 ?

Flask 에서는 url_for( ) 메소드를 통해 이를 수행한다.

url_for( ) 메소드 : 특정 함수에 대한 url을 "동적"으로 구축하는 데 사용한다.
일반적으로 url 끝점에 이동할 함수 이름과 점(.)을 접두사로 붙이는 것처럼 사용할 수 있다.

from flask import Flask, redirect, url_for

app = Flask(__name__)

# 홈 페이지로 이동
@app.route('/')
def home():
    return "주소창에 /user/admin과 /admin <br> /user/student 와 /student를 입력해보세요."

# 관리자 페이지로 이동
@app.route('/admin')
def admin():
    return "관리자 페이지 입니다."

@app.route('/student')
def student():
    return "학생용 페이지 입니다."

# redirect() 는 "페이지에 다시 연결한다"는 뜻으로 마치 페이지를 "새로고침" 한 것과 같은 동작한다.
@app.route('/user/<name>')
def user(name):
    # 전달 받은 name이 'admin'이라면 ?
    if name == 'admin':
        return redirect(url_for('admin'))
    
    # 전달 받은 name이 'student' 라면 ?
    if name == 'student':
        return redirect(url_for('student'))


if __name__ == '__main__':
    app.run()

/admin = /user/admin 같은 페이지를 보여준다.
/student = /user/studnet 같은 페이지를 보여준다.

다만, 차이는 터미널에서 볼 수 있듯이, "HTTP status code" 인 것 같다.

위에, HTTP 포스팅에서 설명했듯이

300 = "리다이렉트" , 200 = "성공" 을 의미한다.
두 경우 모두, GET Method를 사용한것은 공통점이다.

728x90
반응형

📢 개요

정규표현식(Regular Expressions) : 복잡한 문자열을 처리할 때 사용하는 도구

  • 특정 프로그래밍 언어에 종속된 문법을 가진 것이 아니라, 문자열을 처리하는 곳이라면 폭넓게 사용 가능
  • 파이썬의 기본 모듈 중 re 모듈이 정규표현식을 지원

👇 왜 배워야 할까 ?

더보기

다음과 같은 문제가 주어졌다고 생각해보자.
[ 보안을 위해 고객 정보 중 전화번호 가운데 자리의 숫자는 * 문자로 변경하세요. ]

고객 정보는 이름, 주민번호, 전화번호가 문자열 데이터로 주어진다고 가정해봅시다.

text = '''
Elice 123456-1234567 010-1234-5678
Cheshire 345678-678901 01098765432
'''

이 문제를 정규식을 사용하지 않고 풀려면 매우 복잡하게 풀어야 합니다.

  1. 전체 텍스트를 공백 문자를 기준으로 나눈다.
  2. 나누어진 문자열이 전화번호 형식인지 점검한다.
  3. 전화번호를 다시 나누어 가운데 자리의 숫자를 *로 변환한다.
  4. 나눈 문자열을 다시 합쳐 전화번호를 완성한다.

그런데 나누어진 문자열을 전화번호 형식인지 어떻게 점검할까요? 그리고 가운데 자리의 숫자는 어떻게 나누어 *로 변환하고 다시 합칠까요?

설상가상으로 전화번호의 구분문자인 -도 있기 때문에 문제 해결은 더욱 쉽지 않습니다.
그러나 정규표현식을 이용하면 매우 간편하게 코드를 작성할 수 있습니다.

앞서 예로 든 전화번호같이, 원하는 형식의 문자열을 검색할 때 메타문자와 수량자 등 다양한 패턴을 사용하여 매치하고, 그룹핑을 이용하여 원하는 부분만 골라내고 re모듈의 메서드로 문자열을 수정할 수도 있습니다.

 

📢 re 모듈

import re	
 

re — Regular expression operations — Python 3.9.1 documentation

This module provides regular expression matching operations similar to those found in Perl. Both patterns and strings to be searched can be Unicode strings (str) as well as 8-bit strings (bytes). However, Unicode strings and 8-bit strings cannot be mixed:

docs.python.org

 

📢 정규식 검사 함수

문자열에 대해 정규식으로 검사하는 함수는 대표적으로 4가지가 있다.

함수 이름 기능
re.match(pattern, string) string 시작 부분부터 패턴이 존재하는지 검사하여 MatchObject를 반환함.
re.search(pattern, string) string 전체에서 pattern이 존재하는지 검사하여 MatchObject를 반환함.
re.findall(pattern, string) string 전체에서 패턴과 매치되는 모든 경우를 찾아 list로 반환함.
re.finditer(pattern, string) string 전체에서 패턴과 일치하는 결과에 대한 iterater 객체를 반환함.

 

📢 문자열 수정 함수

re 모듈에는 패턴과 매치된 무자열을 찾아줄 뿐만 아니라, 편집할 수 있는 함수들도 존재한다.

함수 이름 기능
re.sub(pattern, repl, string) string 에서 pattern과 매칭되는 부분을 repl로 수정한 문자열을 반환함.
re.subn(pattern, repl, string) re.sub()과 동일하지만, 함수의 결과를 (결과 문자열, 교체 횟수)꼴의 튜플로 반환함.

 

📢 메타 문자

메타 문자특정한 문자 혹은 계열을 표현하는 약속된 "기호" 이다.
메타 문자를 이용하면 특정한 규칙을 가진 여러 단어를 짧게 압출할 수 있어 편리하다.

주요 메타문자

  • [^ ] 와 같이 문자클래스 [ ] 안에 ^이 쓰이면, 문자열의 시작이 아닌, not 에 의미로 사용
  • [ - ]문자클래스 [ ] 안에 - 를 기준으로, 왼쪽 오른쪽에 범위로 쓸 문자를 쓰면, 그 사이에 범위에 해당하는 문자 전부를 의미한다. 반드시 왼쪽의 유니코드(또는 아스키코드)값이 오른쪽보다 작아야한다.
  • ★ "(?i)" = "(?i) 플래그" 라고 부른다. 특정 패턴 위치에 상관없이, 이 플래그를 넣어주면, 대소문자를 구분하지 않겠다는 의미

 

📢 수량자(Quantifier)

동일한 글자나 패턴이 반복될 때, 그대로 정규표현식을 만들고자 하면 상당히 불편하다.
\d 와 \w를 이용하면 각각 숫자와 문자를 "한 글자"씩 매칭해준다.
이는, 이어지는 문자를 패턴으로 만들어, "단어 단위"로 매칭해야 할 때 매우 불편하다.

이런 상황에서 유용한게 수량자(Quantifier) 이다.

주요 수량자(Quantifier)

  • ★ ? = "수량자 탐욕성 억제" 연산자

    표에, "0개 또는 1개"를 의미하는 수량연산자 ? 와는 다른 표현이다 !! (주의)
  • 수량자의 원래 특성은 "탐욕적"이라서, 매칭되는 "최대한 긴 문자열을 반환"하는데, 그게 아니라 가능한 "최소한"의 수량과 매칭하고 싶을 때 사용한다.

    ex) text = "<html><head><title>제목</head></html>" 일 때


    1. pattern = "<.*>" 일 때

    👉 ["<html><head><title>제목</head></html>"]  전체 하나가 반환 될것이다. <의 시작과 >의 맨끝을 기준

    2. pattern = <.*?>" 일 때 ( 수량자 탐욕 억제 사용 )

    👉 [ "<html>" , "<head>" , "<title>" , "</head>", "</html>" ] 반환. < >의 최소한의 기준으로 나눈다.

 

📢 그룹

( )그룹을 나타낸다. 그룹은 전체 패턴 내에서 "하나로 묶여지는 패턴"을 말한다.
그룹과 | 를 결합한 형태, 또는 그룹 뒤에 수량자를 붙이는 패턴으로 자주 사용된다.

예시

  • (e|a)lice  elice, alice와 매칭됩니다.
  • (tom|pot)ato는 tomato, potato와 매칭됩니다.
  • (base|kick){2}  basebase, basekick, kickkick, kickbase 와 매칭됩니다.

그룹 참조 예시

  • 특정문자열 = "(그룹1)쏼라쏼라~(그룹2)"  << 예시가 있다치자.
  • ★ 이 그룹을 "참조"할 때, "\g<그룹번호>" 를 사용하여, 내가 정의했던, 정규식의 그룹을 참조할 수 있다.

그룹의 재사용

한 번 만든 그룹은 재사용할 수도 있습니다. 만들어진 순서부터 1번부터 시작하는 그룹으로 참조할 수 있는데, 매치한 그룹을 패턴 내에서 재사용하려면 \\1과 같이 그룹 번호를 이스케이프하여 나타내야 합니다.

예시

  • (to)ma\\1은 tomato와 매칭됩니다. 괄호를 사용하여 앞에서 만든 그룹 (to)를 뒤에서 재사용하는 모습입니다.

외 ( group( ) 메소드 )

이외에도 그룹에는 re 모듈의 match 객체에 속해있는 group 메서드를 이용하여 매칭된 결과 중 일부 내용만을 추출할 수 있는 등 다양한 사용법이 있습니다.

text = "목표텍스트"
pattern = "정규표현식"
match_object = re.search(pattern, text)		# 매칭된 "객체"를 반환
match_object.group()	# 매칭된 객체 기반으로, 그루핑

 

📢 비캡쳐링그룹

예를들어, "tomato potato" 라는 문자열이 있다.  여기서, tomato 와 potato를 뽑아내기위해 앞선, "그룹"으로는"(tom|pot)ato"를 패턴으로 넣을 것이다. 

👉 그러나, 결과는 ["tom", "pot"] 으로 나올 것이다.

★ 이유는, 파이썬 정규표현식에서 그룹으로 "캡쳐" 한 부분이 있다면, "이외에 부분들은 출력하지 않기 때문"이다.
위 패턴에서는, "ato"는 그룹에 속해있지 않기 때문에, findall 함수에서 누락(해당되지 X)된 것이다.

해결법

👉 그룹 ( ) 앞에 ?: 을 붙인다.  ( ex. "(?:tom|pot)ato)" )

단, 비캡쳐링 그룹은 캡쳐가 되지 않는 그룹이기 때문에, "참조하여 사용할 수 없다."

비캡쳐링그룹은 패턴 문자들을 묶되, 그룹 단위로 매칭되지는 않게끔 한다.
그룹으로 묶은 것들을 "최종 결과"에서 따로 구분하여 사용하지 않는 경우에 적용한다.
 

 

📝 Reference : Elice Academy

728x90
반응형

📢 개요

강력한 MVC 웹 프레임워크들이 등장하고 발전하면서 우리는 빠르고 편리하게 웹사이트를 개발할 수 있게 되었습니다.

하지만 기술이 발달해서 어떤 필요를 충족하고 나면, 항상 또 다른 수요와 해결과제가 생기게 되죠 ?

기존 MVC 웹이 가졌던 아쉬움들 중에서 2가지 예시를 보면

1. 오래된 웹사이트일수록, 이것저것 많이 있는 웹사이트에서 한 게시글을 읽고, 내용이 마음에 들어 "좋아요"를 눌렀다.

👉 이거 하나 눌러서, 전체 좋아요 수가 증가하는 것을 업데이트하려고, 화면이 "깜빡"하면서 페이지가 새로 로딩된다.

게시물 페이지 접근시

 

게시물 페이지에 좋아요를 반영한 경우

좋아요를 눌렀을 경우, 위에 두 그림같은 과정이 매번 좋아요 버튼을 누를경우 반복될 것입니다. 

바뀐건 좋아요 하나뿐인데, 위에 모든 작업을 매번 새로하다보니, 굉장히 비효율적이지요.

사이트에 로드할 정보들이나 이미지가 많으면, 데이터 낭비도 심할 것입니다.

결과적으로, 이건 옛날 방식 !

2. 옛날에는, PC용 웹사이트 하나만 만들면 되었지만, 요즘같은 모바일 시대에서는, 앱이나 스마트폰 브라우저로 서비스들을 이용하면서, 이제는 어느정도 갖춰진 사업을 하는 회사에서는, PC용 웹사이트, 모바일용 사이트,안드로이드,ios 서버까지 개발하게 되었습니다.

👉 반응형 웹사이트를 만든것이 아니라는 전제하에, PC나 모바일 페이지에 접속하는 방식은 위 그림과 비슷한 과정을 보이며, 안드로이드나 ios에서는 자체적인 OS환경에서 페이지가 로딩되도록, 따로 프로그래밍을 해야합니다.

사이트 하나만 만들면 되던 예전보다, 개발 인력이 배로 들것입니다.

여기서, 겹치는 부분을 최소화하고, 업무를 좀 더 깔끔하게 분할할 수는 없을까요 ?

예를들어, 안드로이드와 ios에는 자체적인 SW와 시스템이 있어서, 서버로부터 데이터만 전송받으면 화면이 보여질 수 있으니, 웹에서도 그게 가능하다면, 즉 서버에서 데이터만 보내줘도, 그걸 서버가 아닌 "사용자의 브라우저"에서 HTML/CSS/JS로 렌더링해낼 수 있다면, 서버는 어디서 정보를 요청하든, 동일한 작업을 수행해서 데이터만 전송하면 될 것 입니다.

사용자 컴퓨터의 브라우저에서 돌아가는 👉 프론트엔트(Front_End)
서버에서 돌아가는 👉 백앤드(Back_End)

철저하게 분리하는 것이지요.

 

📢 Angular, React, Vue 는 SPA Framework

브라우저에서도 동작하는 "JavaScript" 로 강력한 라이브러리나 프레임워크를 만든 것이 바로

👉 Angular, React, Vue 같은 "SPA 프레임워크" 입니다.

SPA = Single Page Application 이라고 하는데요.

비유를 하면, 종업원들이 고기와 쌈재료를 가져다주면, 주방장이 아닌, 손님이 직접 불판에 구워먹는 고기집이라고 생각하면 됩니다.

게시물 페이지 SPA 방식

이렇게 되면 사이트에서 뭘 할 때마다 새로 접속하지 않고, 한 번 로드된 화면에서 많은 기능을 수행 할 수 있게 됩니다.

그래서 Single Page Application, SPA 라고 불리는 것입니다.

 

📢 마무리

왼쪽에서부터 React / Angular / Vue

Angular, React, Vue 에 대한 자세한  설명까지하는것은 이 글의 취지는 아니어서 생략하겠습니다.

다만, 대표적인 이 3가지의 SPA 프레임워크는 방식들은 조금씩 다르지만, 기본 원리와 아이디어는 유사합니다.

특징만 간단히 말하자면

  • Vue : 코드가 깔끔하고 배우기 쉽다. Angular, React에 비해 후발주자지만 빠르게 성장하고 있습니다.
  • React : Facebook에서 만들었는데, 막강한 커뮤니티와 자료들을 갖추고 있습니다. 다른 서비스에 잘 녹아들어 유연함을 요하는 서비스들에 권장됩니다.
  • Angular : Google에서 만들었고, TypeScript라는, JavaScript를 보완한 언어로, 매우 안정적이고 탄탄한 프론트엔드 앱을 만들 수 있다고 합니다. 대신 상대적으로 무겁고 배우기 어렵다고 하네요.

 

 

문제나 잘못되었거나 미흡한것이 있다면 알려주세요.🙏


 

728x90
반응형

📢 개요

네이버 같은 딱봐도 복잡한 웹사이트는, 요소와 기능들이 많아지고, 구조가 이것저것 얽혀있다는 것이 느껴집니다.

뭔가 거대해지고 복잡해질수록, 특정 기준으로 분리,모듈화해서 접근을 해야합니다.

국가기관을 입법,사법,행정으로 나누듯이, 웹사이트를 비롯한 소프트웨어는

  • Model
  • View
  • Controller

라는 접근법이 많이 사용됩니다.

 

📢 MVC Web Framework

MVC 패턴

MVC 패턴

  • Model : "데이터"에 관련된 것
  • Veiw : 눈에 보이는것, 웹의 경우 HTML/CSS 나타내는 요소들
  • Controller : 뭔가를 제어하는것, 프로그래밍의 대부분을 차지

게시판을 예시로 봅시다.

Model

👉 게시판에 쓰이고 읽히고 수정되는 글들은, DB에 데이터로 저장될 것입니다.
데이터의 형식을 지정하고, 저장하고 불러오는 작업들에 관한 코드들이 Model 

Veiw

👉 이걸, 사용자들이 목록과 글, 입력화면 등으로 시각적으로 볼 수 있도록 해주는 HTML/CSS 등의 요소가 View

Controller

👉 Model에 데이터를 View에 연결시켜서 사용자가 GUI화면을 통해 데이터를 읽고,쓰고,지우고 할 수 있도록 전반적 제이를 하는 파트가 Controller 입니다.

앞선, "정적 웹 vs 동적 웹" 포스팅에서 동적 웹을 "식당"에 비유했습니다.

 

[알쓸 IT] Static Web ? Dynamic Web ?

📢 개요 편의점과 식당이 있습니다. 편의점은 미리 조리되어있는 음식들을 내가 사다 먹을 수 있습니다. 식당은 내가 주문 넣으면, 그 자리에서 음식을 조리해서 만들어 제공을 해주면 먹을 수

youngminieo1005.tistory.com

Model은 이 식당의 식료품창고를 관리하고, 음식을 요리하는 "주방장"
View는 주방장이 내온 음식을 플레이팅하는 "직원"
Controller는 주문도 받고 서빙도 하는 매니저라고 생각하면 적당합니다.

이렇게 Model, View, Controller를 갖춘 웹사이트를 코드로 기본 골격을 한 번 짜고 나면, 그 코드를 기반으로 내 사이트를 개발하는 일이 수월해지겠지만, 이런 구조 자체를 개발하는 것은 꽤나 시간이 걸릴 것입니다.

다행이도 여러 회사나 비영리 단체, 혹은 개인들이 이 MVC 구조의 기본 설계가 갖춰진 상태인
"MVC 웹 프레임워크" 란 걸 제공합니다.

마치, 건물의 기초 골격과 수도,전기,난방이 설치된 채로 사용자가 원하는대로 집을 개조하고 꾸밀 수 있게 제공하는 것입니다.

"프레임워크"는 남이 이미 짜놓은 코드란 점에서 라이브러리와 비슷하지만, 이 둘을 간단히 비교하자면

라이브러리(Library) 프레임워크(Framework)
각각 개별적인 기능들을..
비유하자면, 문짝이나 욕조 등의 부속품
"가져다 쓰는 것"
부족품들이 연결되어서..
기초적인 제품 골격을 갖춘 상태
"기본 틀에서 덧붙여 만드는 것"

웹 프로그램언어에는 다양한 언어가 있고, 또 언어마다 다른 MVC 웹 프레임워크가 있습니다.

예로 들면, 한국에서, Java 언어로 동작하는 Spring 프레임워크, php 의 Laravel 등이 있지요.

특이하게 MVC라고 안하고, MTV(T,Template) 라고하는 python의 Django, Ruby 의 Ruby on Rails, 함수형 언어 Scala의 Play 등등 굉장히 많다고 합니다.

웹 프로그램언어 - 웹 프레임워크 

후에, 본인이 어느 회사에서 일을 하는가, 또는 어떤 웹사이트를 만드는가에 따라서, 무슨 언어와 프레임워크를 사용할지 결정하면 됩니다.

대략적인 구조나 사용법은 다 비슷해서, 한 두가지 써보면 새로운 걸 접해도 쉽게 터득할 수 있을 것입니다.

 

문제나 잘못되었거나 미흡한 설명 있으면 알려주세요. 🙏


 

728x90
반응형

📢 개요

편의점과 식당이 있습니다.

편의점은 미리 조리되어있는 음식들을 내가 사다 먹을 수 있습니다.
식당은 내가 주문 넣으면, 그 자리에서 음식을  조리해서 만들어 제공을 해주면 먹을 수 있죠.

이 둘의 근본적인 차이는, 업소에서 그때그때 음식을 만들어 주는가 입니다.

정적 웹(Static Web) = 편의점
동적 웹(Dynamic Web) = 식당에 비유할 수 있겠습니다.

 

📢 Static Web vs Dynamic Web

정적 웹(Static Web)

  • 말 그대로, "움직이지 않는, 언제 접속해도 같은 리소스를 보여주는 웹사이트"
  • 프로그래머가 서버에 미리 작성해놓은 HTML/CSS/JS 코드들 그대로 클라이언트의 브라우저에 건내준다.
  • 하지만, 무조건 매번, 동일한 페이지를 보여주는 것은 아닙니다.
    현재 시간을 보여주는 페이지나, 매번 랜덤숫자를 보여주는 페이지, 서버에 직접요청을해서 받아온 내용을
    보여주기도 하는 페이지도 존재합니다.
  • 결국, 정접웹의 기준은, 접속할 때마다 받게 되는 HTML,CSS,JS 코드들, 그리고 동봉된 이미지,영상 파일들이 같은지에 따라 판단이 됩니다.
  • 좀 더 정확하게는, 서버에서 프로그래머가 개발해놓은 HTML/CSS/JS 코드들을, 서버에서 직접 가공해서 제공하는 것이 아니라, 미리 개발해놓은 제품들을 그대로 가져가게 하냐 ? 입니다.
  • 예를들어, 접속시마다 내용이 바뀔필요가 없는 사이트. 회사의 소개페이지, 댓글기능이 없는 블로그 페이지 등에 사용 될 수 있겠죠 ?

동적 웹(Dynamic Web)

  • 하지만, 실시간으로 댓글들이 게시판 페이지같은 경우는 어떤가요 ?
  • 매번 새로운 댓글들이 추가된, 새로운 웹페이지를 제공하기엔 무리가 있을 것입니다.
  • SNS 웹페이지 같은 곳은 말할 것도 없겠죠 ?
  • 이런 페이지는, 식당같이, 서버가 그때그때 요리를 하도록 해야합니다.

두 웹 페이지의 제공 방식을 비교해보죠.

정적 웹은 개발자가 HTML/CSS/JS를 서버에 미리 사용자에게 보여질 모습, 최종모습으로 코딩해서 서버에 올려놓는다.

동적 웹은 DB에서 정보를 읽어서 서버에서 가공된 모습을, 접속할 때마다 최신 정보로 보여주는 방식이 대표적입니다.

 

📢 마무리

음....그러면 Dynamic Web이 좋은 것 같네요 ?

👉 모든 경우가 그렇지는 않습니다 : 오히러 동적이었던 방식에서 정적으로 많이 넘어가는 추세이기도 합니다.

옛날에 네이버같은 포털사이트에서 제공하는 방식인, 작성자,제목,내용,작성일,댓글 등의 정보가, DB에 따로 저장이되고 이를 접속시마다 페이지로 가공해서 보여주는 방식의 동적 블로그들을 다들 이용했습니다.

일단 글들이 DB 따로 저장이 되는 것이다보니, 따로 보관하거나 관리하기 까다로울 수 있고, 내가 직접 블로그사이트를 운영한다해도, 고작 같은 글 보여주느라 매번 서버가 일을 하도록 코딩하고 관리하는 것도 일 입니다.

차라리 글 하나하나를 HTML 파일 단위로, 잡지처럼 진열해놓는게 훨씬 운영하기 편할 것입니다.
보관도 그 HTML파일들을 그대로 복사해서 어딘가 저장해놓으면 됩니다.

그래서 요즘 개발자들은 Jekyll 이나 Hugo 같은, 정적 웹 생성 툴로 블로그를 많이 만듭니다.
특정 형식으로 블로그 글을 작성하면, 그걸 HTML,CSS 등등의 파일들로 출력해내고, 그 프로젝트를 Github이나 Netlify 같은 곳에 올리면, 자동으로 진열돼서 블로그 페이지로 보여지는 것입니다.

Jekyll 과 Hugo 정적 웹 생성 툴

개발자 블로그 중에, https:// ~ . github.io 로 끝나는 것들이, 바로 정적 블로그들의 예시 입니다.

Jekyll로 블로그를 작성해서 github에 게시하는 건 비용도 들지 않고, 배우기 크게 어렵지 않으니, 프로그래밍을 공부하면서 본인이 공부한 내용들을 이렇게 블로그에 정리하면, 공부하는데 많은 도움이 될 것입니다.

 

 

문제가 되거나, 잘못된 정보가 있으면 알려주세요. 🙏


 

728x90
반응형

+ Recent posts