python을 활용한 식물도감 앱 만들기 프로젝트

강사 소개

구일모

 

  • 매릴랜드 주립대 항공기계공학 전공
  • 현) 쿠스랩 대표
  • 전) 코드스테이츠 CPO
  • 전) 프리랜서 개발자
  • 전) 자버, 개발자
  • 전) 벡터코리아, 기술 영업
  • 전) 대한항공, 무인비행기 시스템 엔지니어
  • 전) 로토크, 하드웨어 테스트 연구원
  • 전) 매릴랜드 주립대, 무인기 센서 연구원

 

강사 소개

최민주

 

  • 현) Dencomm (덴탈 도메인 스타트업)
    • AI Lab, NLP Researcher
  • 전) BeringLab (기계번역 스타트업)
    • NLP Researcher
    • Tanalysis (특허 서비스 스타트업)
      • Backend Developer

 

교육 목표

  1. 컴퓨터 및 코드, 프로그래밍의 기본 이해
  2. 웹 어플리케이션의 구조 이해
  3. 웹 어플리케이션 제작 실습

 

진짜 목표
프로그래밍을 통한 문제 해결, 컴퓨터적 사고방식, 프로그래밍의 재미

계획

  1. 컴퓨터 및 프로그래밍 소개
  2. 웹, 어플리케이션 소개
  3. 식물도감 어플리케이션 소개
  4. HTML, javascript, CSS 기초
  5. python 프로그래밍 언어 기본 및 실습
  6. 데이터베이스 기본 및 실습 with SQLite3
  7. 서버 어플리케이션 기본 및 실습 with Flask
  8. 클라이언트 코드 with Flask, jinja2, HTML
  9. 어플리케이션 구동 및 배포
  10. 개인 산출물 발표

pre Q&A

컴퓨터의 동작 원리와 구성요소

컴퓨터가 정보를 저장하는 원리

  • 컴퓨터는 정보를 비트와 바이트 형태로 저장
    • 비트(bit): 정보의 최소단위 (0 또는 1)
    • 바이트(byte): 8비트로 구성된 단위 
  • 데이터 저장 매체:
    • RAM: 휘발성 메모리, 빠른 속도
    • 하드 드라이브 (HDD), SSD: 비휘발성 메모리, 대용량 저장

컴퓨터 HW 기본 구성 요소

  • CPU: 컴퓨터의 두뇌, 명령어를 처리
  • 메모리 (RAM): 데이터를 일시적으로 저장, 빠른 접근 속도
  • 입력 장치: 키보드, 마우스 등
  • 출력 장치: 모니터, 스피커, 프린터 등

컴퓨터 SW 기본 구성 요소

  • 운영체제 (OS): 하드웨어와 소프트웨어를 관리, 사용자와 컴퓨터 간의 인터페이스 제공 (예: Windows, macOS, Linux)
  • 응용 소프트웨어 (Application): 사용자가 특정 작업을 수행할 수 있도록 도와주는 프로그램 (예: 웹 브라우저, 워드 프로세서)

Q&A

웹사이트 작동 원리 이해

  • 클라이언트와 서버 간의 통신
    • 클라이언트: 웹사이트를 요청하는 어플리케이션 (컴퓨터 브라우저 혹은 스마트폰 브라우저, 스마트폰 앱 등)
    • 서버: 웹사이트를 호스팅하고 요청에 응답하는 장치

웹사이트 작동 원리 이해

  • 동작 순서
    1. 클라이언트가 웹 브라우저를 통해 url 입력
    2. 브라우저가 DNS 서버를 통해 해당 urldml IP 주소 찾기
    3. 브라우저가 서버에 HTTP/HTTPS 요청 보냄
    4. 서버가 요청된 웹페이지 찾아서 클라이언트에게 응답
    5. 브라우저가 응답 받은 웹페이지를 렌더링

브라우저의 역할과 기능 이해

  • 브라우저는 웹사이트를 표시하는 데스크톱/모바일 어플리케이션 SW
  • 주요 역할
    • URL 통해 웹사이트/웹어플리케이션 요청
    • HTML, CSS, JS 등의 웹페이지 코드를 해석하여 화면에 표시
    • 쿠키 저장 및 관리
    • 브라우징 히스토리 저장
    • 보안 기능 제공 (e.g. SSL/TLS 암호화)

어플리케이션이란

  • 어플리케이션(Application)은 특정 작업을 수행하기 위해 설계된 소프트웨어 프로그램
    • OS 위에서 돌아감
    • 사용자는 애플리케이션을 통해 다양한 기능을 수행할 수 있습니다 (예: 문서 작성, 웹 탐색, 게임 등)

서버와 클라이언트의 개념 이해

  • 클라이언트(Client): 서비스를 요청하는 장치 또는 프로그램 (예: 웹 브라우저, 모바일 앱)
  • 서버(Server): 요청된 서비스를 제공하는 장치 또는 프로그램 (예: 웹 서버, 데이터베이스 서버)
  • 서버-클라이언트 모델:
    • 클라이언트는 요청을 보내고, 서버는 요청을 처리하여 응답을 반환
    • 이 모델은 웹 애플리케이션, 이메일, 파일 공유 등에서 사용

서버와 클라이언트 예시

  • 웹 어플리케이션
    • 클라이언트: 사용자의 웹 브라우저
    • 서버: 웹 페이지와 데이터를 제공하는 웹 서버
  • 이미지

모바일 vs 데스크톱 어플리케이션 차이점 이해

  • 모바일 앱
    • 스마트폰 및 태블릿에서 실행
    • 터치 인터페이스 기반
    • GPS, 카메라, 가속도계 등 하드웨어와 통합된 기능 제공
  • 데스크톱 앱
    • PC 및 노트북에서 실행
    • 키보드와 마우스 인터페이스 기반
    • 더 큰 화면과 강력한 하드웨어 성능 활용

vs 웹 어플리케이션 차이점 이해

  • 웹 앱
    • 데스크톱, 태블릿, 모바일에 있는 브라우저 위에서 돌아감
    • 웹 사이트 -> 웹 어플리케이션으로 발전
    • 최근 브라우저에서 방문하는 대부분의 사이트는 사실 웹 어플리케이션일 수 있음
      • 웹사이트 vs 웹 어플리케이션 차이

모바일 어플리케이션 예시

  • 카카오톡, 틱톡, 유투브, 인스타그램, 네이버맵 등
  • 특징
    • 간편한 터치 조작
    • 푸시 알림 기능
    • 위치 기반 서비스

데스크톱 어플리케이션 예시

  • MS Word, 한컴 한글, 어도비 포토샵, v3백신, 알집, 브라우저, 어도비 프리미어 등
  • 특징
    • 강력한 기능과 성능 (게임 전용 PC 등)
    • 파일 관리 및 저장 용이
    • 입출력 장치가 보통 분리되어있음

웹 어플리케이션

  • 웹 애플리케이션(Web Application)은 웹 브라우저를 통해 접근할 수 있는 소프트웨어 프로그램
  • 인터넷을 통해 서버에 호스팅되며, 사용자는 웹 브라우저를 통해 서비스를 이용
  • e.g. youtube, facebook, gmail

웹 어플리케이션 구조

  • 프론트엔드(Front-End): 사용자 인터페이스를 담당 (HTML, CSS, JavaScript)
  • 백엔드(Back-End): 서버 로직과 데이터 처리를 담당 (서버, 애플리케이션 로직)
  • 데이터베이스(Database): 데이터 저장소 (SQL, NoSQL 데이터베이스)

웹 애플리케이션의 작동 과정

  1. 사용자가 웹 브라우저를 통해 웹 애플리케이션에 접근
  2. 브라우저가 서버에 HTTP/HTTPS 요청을 전송
  3. 서버가 요청을 처리하고 데이터베이스와 상호작용
  4. 서버가 처리된 데이터를 포함한 응답을 브라우저에 전송
  5. 브라우저가 응답을 받아 사용자에게 웹페이지를 렌더링

클라이언트-서버-데이터베이스 상호작용 이해

  • 클라이언트(Client):
    • 사용자 인터페이스 제공
    • 서버에 HTTP/HTTPS 요청 전송
  • 서버(Server):
    • 비즈니스 로직 처리
    • 데이터베이스와 상호작용하여 데이터 요청 및 저장
    • 클라이언트에 HTTP/HTTPS 응답 전송
  • 데이터베이스(Database):
    • 데이터 저장 및 관리
    • 서버의 데이터 요청 처리

클라이언트와 서버 간의 예시 상호작용

  • 예시: 사용자가 로그인하는 과정
    1. 사용자가 로그인 폼에 정보를 입력하고 '로그인' 버튼 클릭
    2. 브라우저가 서버에 로그인 요청 전송
    3. 서버가 데이터베이스에서 사용자 정보 확인
    4. 서버가 인증 결과를 브라우저에 응답
    5. 브라우저가 사용자에게 로그인 결과 표시

데이터베이스와 서버 간의 예시 상호작용

  • 예시: 사용자가 게시글을 작성하는 과정
    1. 사용자가 게시글 작성 폼에 내용을 입력하고 '작성' 버튼 클릭
    2. 브라우저가 서버에 게시글 작성 요청 전송
    3. 서버가 데이터베이스에 게시글 데이터 저장
    4. 서버가 게시글 작성 완료 응답을 브라우저에 전송
    5. 브라우저가 사용자에게 게시글 작성 완료 메시지 표시

식물도감 웹 어플리케이션 구조

Codespace 에서 빌린 컴퓨터

여러분들 컴퓨터

산출물 시연

HTML 개요

  • HTML (HyperText Markup Language)
    • 웹페이지의 구조를 정의함
    • 요소와 태그를 사용하여 콘텐츠를 구성함
  • HTML 문서의 기본 구조
    • <!DOCTYPE html>
    • <html>, <head>, <body> 태그 포함

HTML의 주요 태그

  • 주요 태그 소개
  • <h1> ~ <h6>: 헤딩 태그
  • <p>: 문단 태그
  • <a>: 링크 태그
  • <img>: 이미지 태그
<!DOCTYPE html>
<html>
<head>
  <title>Sample HTML</title>
</head>
<body>
  <h1>This is a Heading</h1>
  <p>This is a paragraph.</p>
  <a href="https://www.example.com">This is a link</a>
  <img src="image.jpg" alt="Sample Image">
</body>
</html>

HTML 리스트 및 테이블

  • 리스트 태그
    • <ul>, <ol>, <li>
  • 테이블 태그
    • <table>, <tr>, <td>, <th>
<ul>
  <li>Item 1</li>
  <li>Item 2</li>
</ul>
<ol>
  <li>First</li>
  <li>Second</li>
</ol>
<table>
  <tr>
    <th>Name</th>
    <th>Age</th>
  </tr>
  <tr>
    <td>John</td>
    <td>30</td>
  </tr>
</table>

CSS 개요

  • CSS (Cascading Style Sheets)
    • 웹페이지의 스타일을 정의함
    • HTML 요소의 레이아웃, 색상, 폰트 등을 지정함
  • CSS의 기본 문법
    • 선택자, 속성, 값으로 구성

CSS 선택자와 속성

  • 주요 선택자
    • 태그 선택자, 클래스 선택자, ID 선택자
  • 주요 속성
    • color, font-size, margin, padding
/* 태그 선택자 */
p {
  color: blue;
}

/* 클래스 선택자 */
.container {
  margin: 0 auto;
  width: 80%;
}

/* ID 선택자 */
#header {
  background-color: #f8f8f8;
  padding: 10px;
}

JavaScript 개요

  • JavaScript
    • 동적이고 인터랙티브한 웹페이지를 구현함
    • 클라이언트 사이드 스크립트 언어
// 변수 선언
let name = "John";
const age = 30;

// 함수 선언
function greet() {
  console.log("Hello, " + name);
}

// 함수 호출
greet();

예시 javascript 코드

  • Bullet One
  • Bullet Two
  • Bullet Three
// 요소 선택
const button = document.getElementById('myButton');

// 이벤트 핸들러 추가
button.addEventListener('click', function() {
  alert('Button clicked!');
});

// 요소 생성
const newElement = document.createElement('div');
newElement.textContent = "Hello, World!";
document.body.appendChild(newElement);

기타

  • Java와 javascript는 다른 언어
  • javascript 는 최근 모던 웹 개발 쪽에서 가장 hot 한 언어
  • 과거 javascript는 개발 프로그래밍 언어 취급 받지 못함
    • nodejs 등장 및 브라우저 웹앱 고도화로 굉장히 핫해짐

파이썬이란

  • 파이썬(Python)은 1990년 암스테르담의 귀도 반 로섬(Guido van rossum)이 개발한 인터프리터 언어이다. 귀도는 파이썬이라는 이름을 자신이 좋아하는 코미디 쇼인 ‘몬티 파이썬의 날아다니는 서커스(Monty python's flying circus)’에서 따왔다고 한다.

    인터프리터 언어란 소스 코드를 한 줄씩 해석한 후 그때그때 실행해 결과를 바로 확인할 수 있는 언어를 말한다.

    출처: 점프투파이썬 위키독스, 박응용

파이썬의 특징

  • 인간다운 언어
  • 문법이 쉬워 빠르게 배울 수 있음
  • 무료 (오픈소스)
  • 개발 속도가 빠름
if 4 in [1,2,3,4]: print("4가 있습니다")
languages = ['python', 'perl', 'c', 'java']

for lang in languages:
    if lang in ['python', 'perl']:
        print("%6s need interpreter" % lang)
    elif lang in ['c', 'java']:
        print("%6s need compiler" % lang)
    else:
        print("should not reach here")

파이썬을 돌려보자

  • 파이썬을 실행하는 방법은 2가지
    • 내 컴퓨터에서 돌리기
    • 남의 컴퓨터에서 돌리기

윈도우에서 파이썬 설치

  • Bullet One
  • Bullet Two
  • Bullet Three

github codespace

1. github 가입

2. vscode 설치
3. codespace 실행/연결

일단 아래 코드를

실행시켜봅시다

print('hello world')

python 기초를

배워봅시다

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three
# 정수형 변수 선언 및 출력
int_var = ?

# 부동 소수점형 변수 선언 및 출력
float_var = ?

# 문자열형 변수 선언 및 출력
str_var = ?

# 불리언형 변수 선언 및 출력
bool_var = ?

# 각 변수의 타입 출력
?

1.    변수를 선언하고 각 데이터 타입 (정수, 부동 소수점, 문자열, 불리언)의 값을 할당하세요.

 2.    각 변수의 타입을 출력하세요.

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three
# 사용자로부터 숫자 입력 받기
num = int(input("숫자를 입력하세요: "))

# if를 사용하여 사용자로부터 받은 숫자
# 입력값에 대해 양수, 음수, 0 판별

1.    사용자로부터 입력받은 숫자가 양수, 음수 또는 0인지 판별하는 프로그램을 작성하세요.

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three
# 문자열 값을 가지는 튜플 선언
str_list = ["apple", "banana", "cherry"]

# 모든 요소를 반복문을 사용하여 출력

# 출력:
# apple
# banana
# cherry

1.    문자열 값을 가지는 list를 선언하고, 모든 요소를 반복문을 사용하여 출력하세요.

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three
# 과제 1: 1부터 10까지의 숫자 출력 (while 문 사용)
i = 1
while...


# 과제 2: 사용자로부터 비밀번호를 입력받아 1234를
# 받을 때까지 출력
while True:
    num = int(input("비밀번호를 입력하세요: "))
    if...
    else...

1.  1부터 10까지의 숫자를 while 문을 사용하여 출력하세요.

 2.  사용자로부터 숫자를 입력받아, 그 숫자가 비번 1234가 될 때까지 계속해서 숫자를 출력하고, 숫자가 1234가 되면 “종료”를 출력하세요.

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three
# 곱셈 함수 만들기
def multiple(a, b):


# 덧셈 결과값 짝수 판별기
def check_sum_even(a, b):

1.    a, b를 입력받아 a x b 곱셈 결과값을 리턴하는 함수를 만드세요

 2.    a, b를 입력받아 더한 값이 짝수인지 판별하는 함수를 만드세요

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three
# get dict values
x = thisdict.values()

# get dict keys
x = thisdict.keys()

# get dict (key, value)s
x = this dict.items()

car = {
"brand": "Ford",
"model": "Mustang",
"year": 1964
}

dictionary 의 몇 가지 유용한 method 함수들

# 주어진 문자열
text = "hello world hello python hello code"
# 각 단어의 빈도수를 계산하여 딕셔너리에 저장
word_count = {}

# 문자열을 공백으로 분리하여 단어 리스트를 생성
words = text.split()

# 각 단어의 빈도수 계산
for word in words:
    if..
    else..

# 결과 출력
print(word_count)

1. 주어진 text 문자열에서 각 단어가 얼마나 자주 등장하는지 python dict 를 활용하여 풀어보세요

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

Flask Applicatoin

식물도감 앱을 만들어 봅시다

개발 환경과 배포 환경 소개

  1. 내 컴퓨터에서 개발하기 위해선
    1. 파이썬 설치
    2. 더 나은 개발 환경 설치 (e.g. windows 10 with WSL)
    3. 여러가지 개발 환경 추가 세팅
    4. 복잡하고 머리 아픈 일
  2. 남의 컴퓨터에서 개발하기 위해선
    1. 남의 것 빌려서 쓴다

데이터베이스

  • (basic) 그냥 python dictionary 자료 구조 사용
  • (advanced) SQLite3 라는 관계형 데이터베이스 교체 사용 
data = [
    {
        "id": 1,
        "plant_nm": "깻잎",
        "family_kor_nm": "깻잎과",
        "family_nm": "Brassica",
        "genus_kor_nm": "깻잎속",
        "genus_nm": "Brassica",
        "img_url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSr8Uh-buvRfMZZXw4LYjN5OZthg6mmEIFsWA&s",
        "desc": "깻잎은 맛있다"
    },
    {
        "id": 2,
        "plant_nm": "코코아잎",
        "family_kor_nm": "코코아잎과",
        "family_nm": "Coccaceae",
        "genus_kor_nm": "코코아속",
        "genus_nm": "Coccus",
        "img_url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ2GJiBJWcs7UlQQDc5ceHK1li4x22AboerzA&s",
        "desc": "코코아 하하"
        }
]

py dict vs SQLite3

fake_data = [
    {
        "id": 1,
        "plant_nm": "깻잎",
        "family_kor_nm": "깻잎과",
        "family_nm": "Brassica",
        "genus_kor_nm": "깻잎속",
        "genus_nm": "Brassica",
        "img_url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSr8Uh-buvRfMZZXw4LYjN5OZthg6mmEIFsWA&s",
        "desc": "깻잎은 맛있다"
    }
]
import sqlite3

conn = sqlite3.connect('plants.db')
cursor = conn.cursor()
cursor.execute('''
    CREATE TABLE plants (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        img_url TEXT,
        desc TEXT
    )
''')
conn.commit()
conn.close()

Flask Hello World

  • Flask 설치: pip install flask
  • Bullet Three
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

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

Flask App 실행

  • Flask 앱 실행: python app.py / flask run --debug
  • 브라우저에서 http://localhost:5000 접속하여 결과 확인

 

5000 은 개발환경을 위한 컴퓨터 포트 번호이며, 3000 등으로 변경 가능, 중복 불가

Bootstrap을 사용한 HTML 템플릿 작성

  • Bullet One
  • Bullet Two
  • Bullet Three
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
    <title>Plant Encyclopedia</title>
</head>
<body>
    <div class="container">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

jinja2 템플릿 엔진 base HTML 

  • Bullet One
  • Bullet Two
  • Bullet Three
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link
      rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    />
    <link rel="stylesheet" href="/static/main.css" />
    <title>Plant Encyclopedia</title>
  </head>
  <header>
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
      <a class="navbar-brand" href="/">홍길동의 식물도감</a>
      <button
        class="navbar-toggler"
        type="button"
        data-toggle="collapse"
        data-target="#navbarNav"
        aria-controls="navbarNav"
        aria-expanded="false"
        aria-label="Toggle navigation"
      >
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarNav">
        <ul class="navbar-nav">
          <li class="nav-item active">
            <a class="nav-link" href="/"
              >Home <span class="sr-only">(current)</span></a
            >
          </li>
          <li class="nav-item">
            <a class="nav-link" href="/about">About</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="/contact">Contact</a>
          </li>
        </ul>
      </div>
    </nav>
  </header>
  <body>
    <div class="container">{% block content %}{% endblock %}</div>
  </body>
  <footer class="bg-light text-center text-lg-start">
    <div class="text-center p-3" style="background-color: rgba(0, 0, 0, 0.05)">
      © 2024 산림과학고. All rights reserved.
    </div>
  </footer>
</html>

조회 페이지 템플릿

  • Bullet One
  • Bullet Two
  • Bullet Three
{% extends "base.html" %} {% block content %}
<div class="container">
  <div class="row mb-3">
    <div class="col-12 text-right">
      <a
        href="/add"
        class="btn btn-success"
        style="color: white; margin-top: 2em"
        >Add New Plant</a
      >
    </div>
  </div>
  <div class="row">
    {% for plant in plants %}
    <div class="col-12">
      <!-- This ensures each plant takes a full row -->
      <div class="card mb-3">
        <div class="row g-0">
          <a href="/{{ plant.id }}" class="col-md-3 card-box data-plant-id="{{ plant.id }}">
              <img
              src="{{ plant.img_url }}"
              class="img-fluid rounded-start card-img-top"
              alt="{{ plant.family_kor_nm }}"
              />
            </a>
          <div class="col-md-9">
            <div class="card-body">
              <h5 class="card-title">과: {{ plant.family_kor_nm }}</h5>
              <h5 class="card-title">속: {{ plant.genus_kor_nm }}</h5>
              <p class="card-title">이름: {{ plant.plant_nm }}</p>
              <button class="btn btn-primary" onclick="window.location.href='/edit/{{ plant.id }}'">Edit</button>
              <button class="btn btn-danger" onclick="confirmDelete('{{ plant.id }}', '{{ plant.plant_nm }}')">Delete</button>
            </div>
          </div>
        </div>
      </div>
    </div>
    {% else %}
    <div class="col-12">
      <p>No plants found.</p>
    </div>
    {% endfor %}
  </div>
</div>

<script>
function confirmDelete(id, name) {
  if (confirm('Do you really want to delete ' + name + '?')) {
    fetch('/delete/' + id, { method: 'POST' })
      .then(response => {
        if (response.ok) {
          window.location.reload(); // Reload the page to reflect the changes
        } else {
          alert('Failed to delete the plant.');
        }
      })
      .catch(error => {
        console.error('Error:', error);
        alert('Error deleting the plant.');
      });
  }
}
</script>
{% endblock %}

데이터 추가 form 작성

  • Bullet One
  • Bullet Two
  • Bullet Three
<!-- templates/add_plant.html -->
{% extends "base.html" %} {% block content %}
<h1>Add New Plant</h1>
<form method="POST">
  <div class="form-group">
    <label for="family_kor_nm">과 이름</label>
    <input
      type="text"
      class="form-control"
      id="family_kor_nm"
      name="family_kor_nm"
    />
  </div>
  <div class="form-group">
    <label for="genus_kor_nm">속 이름</label>
    <input
      type="text"
      class="form-control"
      id="genus_kor_nm"
      name="genus_kor_nm"
    />
  </div>
  <div class="form-group">
    <label for="plant_nm">이름</label>
    <input
      type="text"
      class="form-control"
      id="plant_nm"
      name="plant_nm"
      required
    />
  </div>
  <div class="form-group">
    <label for="img_url">사진 저장 주소</label>
    <input type="text" class="form-control" id="img_url" name="img_url" />
  </div>
  <div class="form-group">
    <label for="desc">Description</label>
    <textarea class="form-control" id="desc" name="desc" rows="3"></textarea>
  </div>
  <button type="submit" class="btn btn-primary">Add Plant</button>
</form>
{% endblock %}

데이터 수정 form 작성

  • Bullet One
  • Bullet Two
  • Bullet Three
<!-- templates/edit_plant.html -->
{% extends "base.html" %} {% block content %}
<h1>Edit Plant</h1>
<form method="POST">
  <div class="form-group">
    <label for="family_kor_nm">과 이름</label>
    <input
      type="text"
      class="form-control"
      id="family_kor_nm"
      name="family_kor_nm"
      placeholder="{{ plant.family_kor_nm }}"
    />
  </div>
  <div class="form-group">
    <label for="genus_kor_nm">속 이름</label>
    <input
      type="text"
      class="form-control"
      id="genus_kor_nm"
      name="genus_kor_nm"
      placeholder="{{ plant.genus_kor_nm }}"
    />
  </div>
  <div class="form-group">
    <label for="plant_nm">이름</label>
    <input
      type="text"
      class="form-control"
      id="plant_nm"
      name="plant_nm"
      placeholder="{{ plant.plant_nm }}"
    />
  </div>
  <div class="form-group">
    <label for="img_url">사진 저장 주소</label>
    <input
      type="text"
      class="form-control"
      id="img_url"
      name="img_url"
      placeholder="{{ plant.img_url }}"
    />
  </div>
  <div class="form-group">
    <label for="desc">Description</label>
    <textarea
      class="form-control"
      id="desc"
      name="desc"
      rows="3"
      placeholder="{{ plant.desc }}"
    ></textarea>
  </div>
  <button type="submit" class="btn btn-primary">수정하기</button>
</form>
{% endblock %}

가짜 데이터베이스 생성

fake_data = {
    1: {'name': '소나무', 'img_url': 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSRc378mgNcg7LzXbaxUYNd9mHFPQCAvCG_SA&s', 'desc': '늘 푸른 소나무'},
    2: {'name': '갈대풀', 'img_url': 'https://newsteacher.chosun.com/site/data/img_dir/2017/10/16/2017101600044_0.jpg', 'desc': '누구 마음은 갈대 바람'}
}

식물 조회 - 식물 모두

  • Bullet One
  • Bullet Two
  • Bullet Three
@app.route('/plants')
def get_plants():
	# get some plants data
    return render_template('plants.html', plants=fake_data)

식물 조회 - 1개 식물

  • Bullet One
  • Bullet Two
  • Bullet Three
@app.route('/plant/<int:plant_id>')
def get_plant(plant_id):
    # get one plant data
    if plant:
        return render_template('plant.html', plant=plant)
    else:
        return 'Plant not found', 404

식물 추가

  • Bullet One
  • Bullet Two
  • Bullet Three
@app.route('/add_plant', methods=['POST'])
def add_plant():
    new_id = max(fake_data.keys()) + 1
    fake_data[new_id] = {
        'name': request.form['name'],
        'img_url': request.form['img_url'],
        'desc': request.form['desc']
    }
    return redirect(url_for('get_plants'))

식물 수정

  • Bullet One
  • Bullet Two
  • Bullet Three
@app.route('/edit_plant/<int:plant_id>', methods=['POST'])
def edit_plant(plant_id):
    plant = fake_data.get(plant_id)
    if plant:
		fake_data[plant_id] = {
     	   'name': request.form['name'],
     	   'img_url': request.form['img_url'],
        	'desc': request.form['desc']
    	}
        return redirect(url_for('get_plants'))
    else:
        return 'Plant not found', 404

식물 삭제

  • Bullet One
  • Bullet Two
  • Bullet Three
@app.route('/delete_plant/<int:plant_id>', methods=['POST'])
def delete_plant(plant_id):
    if plant_id in fake_data:
        del fake_data[plant_id]
        return redirect(url_for('get_plants'))
    else:
        return 'Plant not found', 404

SQLite3 로 DB 변경

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:////Users/johnnykoo/repos/codespaces-flask/data.db"
db.init_app(app)

데이터 조회

  • Bullet One
  • Bullet Two
  • Bullet Three
@app.route('/plants')
def get_plants():
    plants = DataItem.query.all()
    
    
@app.route("/<int:id>")
def get_plant(id):
	plant = DataItem.query.get(id)

데이터 쓰기 - 추가

  • Bullet One
  • Bullet Two
  • Bullet Three
@app.route('/add_plant', methods=['POST'])
def add_plant():
    new_plant = DataItem(
                family_kor_nm=request.form['family_kor_nm'],
                genus_kor_nm=request.form['genus_kor_nm'],
                img_url=request.form['img_url'],
                plant_nm=request.form['plant_nm'],
                desc=request.form['desc'],
            )
    db.session.add(new_plant)
    db.session.commit()
   

데이터 쓰기 - 수정

  • Bullet One
  • Bullet Two
  • Bullet Three
@app.route("/edit/<int:id>", methods=['GET', 'POST'])
def edit_plant(id):
    plant_to_edit = DataItem.query.get(id)
    
	# do some edit work here
    
	db.session.commit()

데이터 삭제 route 작성

  • Bullet One
  • Bullet Two
  • Bullet Three
@app.route("/delete/<int:id>", methods=['POST'])
def delete_plant(id):
    plant_to_delete = DataItem.query.get(id)
    if plant_to_delete:
    db.session.delete(plant_to_delete)
    db.session.commit()

전체 코드 구조 정리

  • Bullet One
  • Bullet Two
  • Bullet Three
/project
│
├── /templates
│   ├── base.html
│   ├── plants.html
│   ├── plant.html
│   ├── add_plant.html
│   └── edit_plant.html
│
├── app.py
└── data.db

Advanced: 검색 기능

검색 route

  • Bullet One
  • Bullet Two
  • Bullet Three
@app.route('/search', methods=['GET', 'POST'])
def search():
    if request.method == 'POST':
        keyword = request.form['keyword']
        db = get_db()
        cursor = db.execute('SELECT * FROM plants WHERE name LIKE ? OR description LIKE ?', 
                            ('%' + keyword + '%', '%' + keyword + '%'))
        plants = cursor.fetchall()
        return render_template('plants.html', plants=plants)
    return render_template('search.html')

검색 form

  • Bullet One
  • Bullet Two
  • Bullet Three
<!-- templates/search.html -->
{% extends "base.html" %}
{% block content %}
<h1>Search Plants</h1>
<form method="POST">
    <div class="form-group">
        <input type="text" class="form-control" name="keyword" placeholder="Enter plant name or description" required>
    </div>
    <button type="submit" class="btn btn-primary">Search</button>
</form>
{% endblock %}

Q&A

Made with Slides.com