반응형

Database에서 간단히 CRUD만 구성할 일이 있었는데, Backend 와 Frontend를 분리해서 구축하려니 조금 번거로워서 찾게되었습니다. Python 프레임워크들은 Jinja2 템플릿을 사용하여 Frontend도 쉽고 빠르게 구축할 수 있도록 지원해주기 때문에, 이런 용도로는 잘 맞는 것 같습니다. FastAPI도 일반적으로는 API 형태의 backend를 구성하겠지만, Jinja2를 이용하여 Frontend 연결까지 해보도록 하겠습니다.

1. 폴더구조

아래와 같이 폴더구조를 만들겠습니다. 이전에 만들었던 main.py를 업데이트하기 위해 mainlist.py 파일을 새로 작성하였습니다. 실행도 mainlist.py로 실행하겠습니다.

2. 라이브러리 설치

<기존 설치된 라이브러리>

pip install fastapi, uvicorn
pip install mysql, sqlalchemy
pip install starlette

<추가 라이브러리>

pip install jinja2 	#jinja2 실행
pip install python-multipart	#Form 기능을 위한 라이브러리

3. 코드

<mainlist.py>

route 처리부분을 별도의 파일로 분리하여 작성하고, 해당 router를 include_router로 연결합니다.

from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
from routes import index

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(index.router)

 

<db/db.py>

가장 많이 예제로 주어지는 mysql database를 연결하도록 하겠습니다. sqlalchemy를 사용하여 연결하게 되는데, DATABASE 정보를 이용하여 ENGINE을 생성하고, ENGINE을 이용하여 session을 생성합니다.

Session이란 트랜잭션이 커밋되기 전 동작들이 거쳐가는 일종의 버퍼 같은 역할을 합니다. 커밋이 안된 데이터가 다른 트랜잭션으로 흘러들어가거나, 같은 테이블의 인스턴스를 두번 불러왔을 때 다른 객체로 인식되는 등의 문제가 없도록 합니다.

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session

user_name = "root"
user_pwd = ""
db_host = "127.0.0.1"
db_name = "project01"

DATABASE = 'mysql://%s:%s@%s/%s?charset=utf8' % (
    user_name,
    user_pwd,
    db_host,
    db_name,
)

ENGINE = create_engine(
    DATABASE,
    encoding="utf-8",
    echo=True
)

session = scoped_session(
    sessionmaker(
        autocommit = False,
        autoflush = False,
        bind = ENGINE
    )
)

Base = declarative_base()
Base.query = session.query_property()

 

<model/model.py>

Database 테이블의 Mapping Class를 생성합니다.

from sqlalchemy import Column, Integer, String
# from pydantic import BaseModel
from db.db import Base

class UserTable(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50), nullable=False)
    age = Column(Integer)

 

<routes/index.py>

Jinja2Templates를 불러오고 html파일이 위치할 디렉토리를 지정해주면 해당 파일에서 Jinja2문법을 사용할 수 있습니다. Jinja2에서는 Post와 Get 메서드만 지원되며, put, delete 등의 다른 메서드는 지원되지 않습니다. Form 요소의 사용법에 대해서는 여기 문서를 참조하세요

from fastapi import APIRouter, Request, Form
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from starlette.responses import RedirectResponse

from db.db import session
from model.model import UserTable, User

router = APIRouter()
templates = Jinja2Templates(directory="templates")


# 전체 리스트
@router.get("/users", response_class=HTMLResponse)
async def read_users(request: Request):
    context={}
    users = session.query(UserTable).all()

    context["request"] = request
    context["users"] = users

    return templates.TemplateResponse("user_list.html", context)



# 특정 인원 보기
@router.get("/users/{user_id}", response_class=HTMLResponse)
async def read_user(request: Request, user_id: int):
    context={}
    user = session.query(UserTable).filter(UserTable.id == user_id).first()

    context["request"] = request
    context["name"] = user.name
    context["age"] = user.age

    return templates.TemplateResponse("user_list.html", context)

# 인원 생성
@router.post("/user")
async def create_user(
        name: str = Form(...),
        age:int = Form(...)):

    print(name, age)
    user = UserTable()
    user.name = name
    user.age = age

    session.add(user)
    session.commit()

    return RedirectResponse(url="/users", status_code=302)

# 인원 수정
@router.post("/users", response_class=HTMLResponse)
async def update_user(
        user_id: int = Form(...),
        n_name: str = Form(...),
        n_age: int = Form(...)):

    user = session.query(UserTable).filter(UserTable.id == user_id).first()
    user.name = n_name
    user.age = n_age
    print(n_name, n_age)
    session.commit()

    return RedirectResponse(url="/users", status_code=302)


# 인원 삭제
@router.post("/delete/{user_id}", response_class=HTMLResponse)
async def delete_users(request: Request, user_id: int):

    print('delete', user_id)
    session.query(UserTable).filter(UserTable.id == user_id).delete()
    session.commit()

    return RedirectResponse(url="/users", status_code=302)

 

<templates/user_list.html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>MySQL연동</title>
</head>
<body>
    <div>
        <a href="/">HOME</a>
    </div>
    <form action="/user" method="post">
        <input type="text" id="username_" name="name" value="사용자" />
        <input type="text" id="userage_" name="age" value="1" />
        <input type="submit">등록</input>
    </form>
    <div id="usertable">
        <table width="100%" border="1" cellpadding="0" cellspacing="0">
            {% if users %}
                {% for user in users %}
                    <tr>
                        <form action="/users" method="post">
                            <td align="center"><input type="text"  name="user_id" value={{user.id}}></td>
                            <td align="center"><input type="text" id="username_{{user.id}}" name="n_name" value="{{user.name}}" /></td>
                            <td align="center"><input type="text" id="userage_{{user.id}}" name="n_age" value="{{user.age}}" /></td>
                            <td align="center"><input type="submit" value="수정"/></td>
                        </form>
                        <form action="/delete/{{user.id}}" method="post">
                            <td align="center">
                                <input type="submit" value="삭제"/>
                            </td>
                        </form>
                    </tr>
                {% endfor %}
            {% else %}
                <tr>
                    <td align="center" colspan="6">No user...</td>
                </tr>
            {% endif %}
        </table>
    </div>
</body>
</html>

 

<실행>

uvicorn mainlist:app --reload

<결과>

 

이상, FastAPI를 사용하여, 간단하게 Database와 연계, CRUD 실행, Frontend까지 작성해보았습니다.

 

~~끝~~

반응형
반응형

 

1. 폴더구조

2. 라이브러리 설치

pip install fastapi, uvicorn
pip install mysql, sqlalchemy
pip install starlette

 

3. 코드

<db.py>

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session

user_name = "root"
user_pwd = ""
db_host = "127.0.0.1"
db_name = "project01"

DATABASE = 'mysql://%s:%s@%s/%s?charset=utf8' % (
    user_name,
    user_pwd,
    db_host,
    db_name,
)

ENGINE = create_engine(
    DATABASE,
    encoding="utf-8",
    echo=True
)

session = scoped_session(
    sessionmaker(
        autocommit = False,
        autoflush = False,
        bind = ENGINE
    )
)

Base = declarative_base()
Base.query = session.query_property()

<model.py>

from sqlalchemy import Column, Integer, String
from pydantic import BaseModel
from db import Base
from db import ENGINE

class UserTable(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50), nullable=False)
    age = Column(Integer)

class User(BaseModel):
    id: int
    name: str
    age: int

 

<main.py>

from fastapi import FastAPI
from typing import List
from starlette.middleware.cors import CORSMiddleware

from db import session
from model import UserTable, User

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.get("/users")
async def read_users():
    users = session.query(UserTable).all()

    return users


@app.get("/users/{user_id}")
async def read_user(user_id: int):
    user = session.query(UserTable).filter(UserTable.id == user_id).first()
    return user


@app.post("/user")
async def create_users(name: str, age: int):
    user = UserTable()
    user.name = name
    user.age = age

    session.add(user)
    session.commit()

    return f"{name} created..."


@app.put("/users")
async def update_user(users: List[User]):
    for i in users:
        user = session.query(UserTable).filter(UserTable.id == i.id).first()
        user.name = i.name
        user.age = i.age
        session.commit()

    # users[0].name

    return f"{users[0].name} updated..."


@app.delete("/user")
async def delete_users(user_id: int):
    user = session.query(UserTable).filter(UserTable.id == user_id).delete()
    session.commit()

    return read_users
반응형
반응형

1. 폴더구조

2. 라이브러리 설치

 

pip install fastapi uvicorn pydantic jinja2

<main.py>

import uvicorn
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel

import requests

app = FastAPI()
db=[]
#----------------------------------------------------------------------------------------------------------
# data models

class City(BaseModel):
    name: str
    timezone: str

templates = Jinja2Templates(directory="templates")

#app.include_router(index.router);

@app.get("/")
async def root():
    return {"message": "Hello World"}

@app.get("/cities", response_class=HTMLResponse)
def get_cities(request: Request):
    context = {}
    rsCity = []

    cnt = 0
    for city in db:
        str=f"http://worldtimeapi.org/api/timezone/{city['timezone']}"
        print(str)
        r = requests.get(str)
        cur_time = r.json()['datetime']

        cnt += 1
        rsCity.append({'id': cnt, 'name':city['name'], 'timezone': city['timezone'], 'current_time': cur_time})

    context['request'] = request
    context['rsCity'] = rsCity

    return templates.TemplateResponse('city_list.html', context)

@app.get('/cities/{city_id}')
def get_city(city_id: int):
    city = db[city_id-1]
    r = requests.get(f"http://worldtimezpi.org/api/timezone/{city['timezone']}")
    cur_time = r.json()['datetime']
    return {'name': city['name'], 'timezone': city['timezone'], 'current_time': cur_time}

@app.post('/cities')
def create_city(city: City):
    db.append(city.dict())
    return db[-1]

@app.delete('/cities/{city_id}')
def delete_city(city_id: int):
    db.pop(city_id-1)
    return {}


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

 

<templates/city_list.py>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>City list</h1>
    {% if rsCity %}
        {% for city in rsCity %}
            <div>
                {{city.name}} - {{city.timezone}}
            </div>
        {% endfor %}
    {% else %}
            <div>
                No city...
            </div>
    {% endif %}
</body>
</html>
반응형
반응형


1. 설치

pip install fastapi uvicorn 

pip install aiofiles # CSS파일 로딩 

pip install jinja2


2. Project 구성

3. 시작명령어

python app.py


4. app.py

import uvicorn from fastapi 
import FastAPI, Request from fastapi.responses 
import HTMLResponse 
from fastapi.staticfiles import StaticFiles 
from fastapi.templating import Jinja2Templates 

app = FastAPI() 
templates = Jinja2Templates(directory="templates") 
app.mount("/static", StaticFiles(directory="static"), name="static") 

@app.get('/') 
def hello_world(): 
	return {'message':'hello'} 

@app.get("/items/{id}", response_class=HTMLResponse) 
async def read_item(request: Request, id: str): 
	return templates.TemplateResponse("item.html", {"request": request, "id":id}) 

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


5. /tcmplates/item.html

<!DOCTYPE html> 
<html lang="en"> 
<head> 
    <title>Item Details</title> 
    <link href="{{ url_for('static', path='/styles.css') }}" rel="stylesheet"> 
</head> 
<body> 
    <img src="{{ url_for('static', path='/fastapi.png') }}" alt="FastAPI"> 
    <h1>is awesome!</h1> 
    <p>Path for item ID: {{ id }}</p> 
</body> 
</html>


6./static/styles.css

h1{ 
	color: red; text-align: center; 
} 

img { 
  	display: block; 
  	margin-left: auto; 
  	margin-right: auto; 
  	width: 50%; 
}

 

반응형

'Programming > Python_Web' 카테고리의 다른 글

FastAPI - MySQL CRUD  (0) 2021.09.10
FastAPI 시작하기  (0) 2021.09.09
Flask강좌6 - 등록/로그인/로그아웃  (0) 2020.12.04
Flask강좌5 - Flask form입력  (0) 2020.12.03
Flask강좌4 - Flask_SQLAlchemy MySQL연동  (5) 2020.12.02
반응형
import folium as g

g_map = g.Map(location=[34.5, 128], zoom_start=7) # tiles='Stamen Terrain', 'Stamen Toner', 'Stamen Watercolor'
latlon=[
        [33.452278, 126.567803],#제주
        [37.56667, 126.97806], #서울
        [35.17944, 129.07556], #부산
       ]

# 마커
marker = g.Marker([37.509671, 127.055517], # 마커를 찍는 과정이다. 해당 위치의  마커를 찍고
                 popup='campus seven', # 해당 마커의 이름을 넣어준다.
                 icon = g.Icon(color='blue'))# 해당 아이콘의 색깔을 지정해준다.
marker.add_to(g_map) # 마지막으로 위에 만들었던 맵에다가 marker를 add 해준다.

# 서클
for i in range(len(latlon)):
    g.Circle(
        location = latlon[i],
        radius = 50,
        color = '#000000',
        fill ='crimson',
    ).add_to(g_map)

# 서클마커    
for j in range(len(latlon)):
    g.CircleMarker(
        latlon[j], # CircleMarker를 통해 원형으로 보이게 한다.
        radius=70,		# 범위
        color='skyblue',	# 선 색깔
        popup='campus seven', # 원의 의름
        fill_color = 'skyblue' # 채워질 원의 색깔
     ).add_to(g_map)


g_map

반응형
반응형

1. vue/cli 설치

npm i -g @vue/cli

 

2. 프로젝트 생성

vue create . //해당 폴더에 생성

 

3. 라이브러리 설치

npm i -g reset-css
npm i vuex@next

4. App.js

<template>
  <div id="app">
    <h1 id="app-title">Fruits List</h1>
    <div id="fruits-table">
      <FruitsList></FruitsList>
      <FruitsPrice></FruitsPrice>
    </div>
  </div>
</template>

<script>
// import HelloWorld from './components/HelloWorld.vue'
// import Mycom from './components/Mycom.vue';
import FruitsList from './components/FruitList.vue'
import FruitsPrice from './components/FruitPrice.vue'

export default {
  name: 'App',
  components: {
    FruitsList,
    FruitsPrice,
  }
}
</script>

<style>
  @import url("css/app.css");
</style>

 

5. /components/FruitList.vue

<!--FruitsList.vue-->
<template>
  <div id="fruits-list">
    <h1>Fruits Name</h1>
    <ul>
      <li v-for="fruit in fruits" :key=fruit>
        {{ fruit.name }}
      </li>
    </ul>
  </div>
</template>

<script>
  export default {
    // Removed Props
    computed: {
      fruits() {
        return this.$store.state.fruits
      }
    }
  }
</script>

<style></style>

 

6. /components/FruitPrice.vue

<!--FruitsPrice.vue-->
<template>
  <div id="fruits-price">
    <h1>Fruits Price</h1>
    <ul>
      <li v-for="fruit in fruits" :key="fruit">
        $ {{ fruit.price }}
      </li>
    </ul>
  </div>
</template>

<script>
  export default {
    // Removed props
    computed: {
      fruits() {
        return this.$store.state.fruits
      }
    }
  }
</script>

<style></style>

 

7. store/store.js

// store.js

import  {createStore} from 'vuex';


//export const store = createStore({
export default createStore({
  // 상태
  state: {
    fruits: [
      { name: 'Apple', price: 30 },
      { name: 'Banana', price: 40 },
      { name: 'Mango', price: 50 },
      { name: 'Orange', price: 60 },
      { name: 'Tomato', price: 70 },
      { name: 'Pineapple', price: 80 }
    ]
  }
});

8. main.js

import { createApp } from 'vue'
import App from './App.vue'
import { store } from './store/store.js'

createApp(App)
.use(store)
.mount('#app')

9. App.css

reset.css가 잘 안불러와질 때가 있네요. 설정 고치는 일이 어려워 일단 생략하고 진행합니다.

/*app.css*/
/* @import url("~reset-css/reset.css"); */
#app-title {
  color: orangered;
  font-size: 40px;
  font-weight: bold;
  text-align: center;
  padding: 40px 0 20px 0;
}
.fruits-table {
  max-width: 700px;
  margin: 0 auto;
  display: flex;
  text-align: center;
  padding: 0 20px;
}
.fruits-table li {
  padding: 20px;
  border-bottom: 1px dashed grey;
  color: white;
  list-style: none;
}
.fruits-table li:last-child { border-bottom: none; }
.fruits-table li:hover { background: rgba(255,255,255,.2); }

#fruits-list {
  background: orange;
  flex: 1;
}
#fruits-price {
  background: tomato;
  flex: 1;
}
#fruits-list h1,
#fruits-price h1 {
  font-weight: bold;
  font-size: 20px;
  padding: 20px;
  border-bottom: 1px solid;
}

반응형
반응형

1. 라이브러리 설치

yarn add redux react-redux @reduxjs/tookit

 

2. index.js

Reducer 연결 및 store 생성

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";

import { configureStore } from "@reduxjs/toolkit";
import rootReducer from "./reducers";
import { Provider } from "react-redux";

const store = configureStore({ 
    reducer: rootReducer,
    devTools: process.env.NODE_ENV !== "production",
});

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

3. App.js

import React from "react";
import "./App.css";
import Counter from './components/Counter';

const App = () => {
  return (
    <div>
      <Counter />
    </div>
  );
};
export default App;

4. reducers/counter.js

import { createAction, createReducer } from "@reduxjs/toolkit";

const INCREASE = "counter/INCREASE";
const DECREASE = "counter/DECREASE";

export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);

const initialState = {
  number: 0,
};

const counter = createReducer(initialState, {
  [INCREASE]: (state, action) => ({ number: state.number + 1 }),
  [DECREASE]: (state, action) => ({ number: state.number - 1 }),
});

export default counter;

 

5.reducers/index.js

import { combineReducers } from "redux";
import counter from './counter';

const rootReducer = combineReducers({
  counter,
})

export default rootReducer;

6. components/Counter.jsx

import React from "react";
import { increase, decrease } from "../reducers/counter";
import { useSelector, useDispatch } from "react-redux";

const Counter = () => {
  
  const number = useSelector((state) => state.counter.number);
  const dispatch = useDispatch();
  return (
    <div>
      <h1>{number}</h1>
      <div>
        <button onClick={() => dispatch(increase())}>+1</button>
        <button onClick={() => dispatch(decrease())}>-1</button>
      </div>
    </div>
  );
};

export default Counter;
반응형
반응형

1. 지도에서 좌표-주소 변환

geocoder 라는 것을 사용해야하는데, services라이브러리를 써야합니다. 라이브러리는 api 요청할 때 url에 추가해서 보내면 됩니다. (kakao 예제에서 제공하는 cluster, drawing도 함께 추가해 넣었습니다.)

<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=내api키&libraries=services,clusterer,drawing"></script>

 

2. 우선 화면에 무언가 표시를 해봅시다.

예제는 kakao에서 제공되는 kakao 제주 사옥을 찾는 것 같습니다.

</src/container/MapContainer.js>

import React, { useEffect } from 'react';

const { kakao } = window;

const MapContainer = () => {

  useEffect(() => {
    const container = document.getElementById('myMap');
    const options = {
      center: new kakao.maps.LatLng(35.12, 129.1),
      level: 3
    };
    // 지도를 생성합니다.
    const map = new kakao.maps.Map(container, options);
    // 주소-좌표 변환 객체를 생성합니다.
    const geocoder = new kakao.maps.services.Geocoder();
    // 주소로 좌표를 검색합니다..
    geocoder.addressSearch('제주특별자치도 제주시 첨단로 242', function (result, status) {

      // 정상적으로 검색이 완료됐으면 
      if (status === kakao.maps.services.Status.OK) {
  
        var coords = new kakao.maps.LatLng(result[0].y, result[0].x);
  
        // 결과값으로 받은 위치를 마커로 표시합니다
        var marker = new kakao.maps.Marker({
          map: map,
          position: coords
        });
  
        // 인포윈도우로 장소에 대한 설명을 표시합니다
        var infowindow = new kakao.maps.InfoWindow({
          content: '<div style="width:150px;color:red;text-align:center;padding:6px 0;">내가 썼지롱</div>'
        });
        infowindow.open(map, marker);
  
        // 지도의 중심을 결과값으로 받은 위치로 이동시킵니다
        map.setCenter(coords);
      }
    })
  }, []);

  return (
    <div id='myMap' style={{
      width: '800px', 
      height: '800px'
    }}></div>
  );
}

export default MapContainer;

<결과>

아직 기능들을 알 수는 없지만, 뭔가 써집니다.

반응형

+ Recent posts