FE - React Project

수현·2023년 2월 21일
1

IT (CS)

목록 보기
4/12

🔷 개요

  • What : climbing project
  • Who : 이민경, 양석준, 김수현
  • When : 2023.02.13 ~ 2023.03.03
  • Why : React 사용해서 웹페이지 제작
  • How : React 인강 수강 ➡ 주제 정해 실습 ➡ 줌미팅으로 상호 학습 ➡ velog 정리 / 코드 합쳐서 Git 저장

🔷 제작 배경

1. 서론

현대인들의 건강에 대한 관심이 올라가면서 운동에 대한 수요도 높아지고 있습니다. 혼자 운동을 하는 경우도 많지만, 운동하는 사람들이 늘어남에 따라서 같은 운동을 함께하고자 소모임이 많아진 것을 볼 수 있습니다. 하지만 운동 모임의 수요에 비해 모임 약속을 정하는 체제가 불편한 것을 알 수 있었습니다.

기존에는 카카오 톡방으로 모임의 출석 인원과 시간, 장소를 정하고 있지만, 가독성이 좋지 못할 뿐만 아니라, 모임 시간과 장소를 변경하기에 어려움이 있고, 더 많은 모임을 수용하기에 불편합니다.

그렇기에 운동 모임을 보다 쉽고 가독성 있게 확인할 수 있고, 시간별, 장소별로 한 눈에 볼 수 있도록 운동 체크 앱을 기획 및 설계하고자 합니다. 또한, 회원들의 출석일과 출석 시간을 저장하여 회원별로 출석률을 확인할 수 있고, 개인 페이지에 운동한 날짜, 장소를 기록하여 개인별로 운동 확인을 도울 수 있습니다.


<그림1> 카카오 톡방 모임 예시

2. 시장조사

1) 페어플레이

페어플레이는 아웃도어 운동 커뮤니티로 다양한 운동을 취향에 맞게 운동 클럽을 개설할 수 있습니다. 각자 운동 후 인증샷을 남기고, 운동에 관련한 이야기를 나눌 수 있는 커뮤니티 서비스를 제공하고 있습니다. 또한 자신만의 운동 스케줄을 작성하여 편리하게 관리할 수 있습니다.

2) 운동끼리

운동끼리는 근처 동네의 운동 친구를 찾는 앱으로 가까운 거리에 비슷한 관심사를 가진 맞춤형 운동 친구를 추천해줍니다. 원한다면 관심있는 종목에 대해 운동모집을 만들 수도 있는 기능을 제공하고 있습니다.

<그림2> 페어플레이, 운동끼리 어플리케이션 화면

🔷 기능 분석

1. 개발 스펙

  • 사용 라이브러리 : React
  • 사용 웹 프레임워크 : Django
  • 사용 DB : SQLite

2. 웹 기능 분석

1) 달력에 운동 일정 등록

  • 달력에 개인 운동 계획 저장
  • 팝업 형태로 확인 가능
  • 개인 운동 기록


<그림3> 달력별 정보 예시

2) 개인 페이지

  • 로그인을 통해 회원들 구분
  • 한달 누적 운동량 확인
  • 개인 ToDoList 작성

3) 시간별, 장소별 일정 확인

  • 출석 일자, 출석 장소 정보 기반으로 회원별 출석률, 장소별 참여인원 목록 확인하기


<그림4> 시간별, 장소별 정보 예시

4) 회원별 최근 출석일 확인

  • 회원 이름, 출석일, 출석횟수 기반으로 출석 신호등 표시하기


<그림5> 회원별 정보 예시

5) 암장별 정보

  • 암장 난이도 비교표
  • 암장 로고를 버튼 형식으로 표시


<그림7> 실내암장 난이도 비교표 예시

6) 추가 기능

  • 개인별 암장 난이도 추천
  • 개인별 목표 설정

📌 1주차 목표 (2.13 ~ 2.16)


React 개념 익히기
각자 프론트 만들어 보기

3. SWOT 분석을 통한 앱 분석

🔷 작업환경 준비

1) Node.js 설치
2) npm 설치
3) 에디터 설치 (Visual Studio Code 사용)
4) create-react-app 설치
(페이스북에서 만든 react project 생성 도구)

실행

📌 2주차 목표 (2.17 ~ 2.22)


(공통) 서버에서 자료 받기
(민경) 한달 운동량 요약 + 메뉴바 상단
(석준) 운동 일정 (팝업으로 일정 확인, 운동 기록 확인) + 단계별 레벨 업
(수현) To Do List (입력 + 토글) + 로그인 구현


+ 암장 난이도 비교표
+ 일자별/장소별 운동 약속 확인

🔷 웹/앱 설계 및 구현

🔶 Calendar

🔶 To Do List

src/Todolist/TodoListTemplate.js

중앙에 박스와 타이틀이 보여지고, 하단에 폼과 리스트 배치

import React from 'react';
import './TodoListTemplate.css';

const TodoListTemplate = ({form, children}) => {
  return (
    <main className="todo-list-template">
      <div className="title">
        To Do List
      </div>
      <section className="form-wrapper">
        {form}
      </section>
      <section className="todos-wrapper">
        { children }
      </section>
    </main>
  );
};

export default TodoListTemplate;

src/Todolist/css

open-color 색상표

src/Todolist/Form.js

input과 button 추가

  • 컴포넌트 DOM 태그 작성 + CSS 스타일 작성
  • 상태 관리 및 props로 필요한 값 전달
    • value : input의 내용
    • onCreate : 버튼 클릭될 때 실행될 함수
    • onChange : input 내용이 변경될 때 실행되는 함수
    • onKeyPress : input에서 키를 입력할 때 실행되는 함수 (Enter가 눌렸을 때 onCreate한 것과 동일한 작업을 하기 위해 사용)
import React from 'react';
import './Form.css';

const Form = ({value, onChange, onCreate, onKeyPress}) => {
  return (
    <div className="form">
      <input value={value} onChange={onChange} onKeyPress={onKeyPress}/>
      <div className="create-button" onClick={onCreate}>
        추가
      </div>
    </div>
  );
};

export default Form;

src/Todolist/TodoItemList.js

  • TodoItem 컴포넌트 여러개 렌더링

  • 리스트 렌더링 시, 보여주는 리스트가 동적인 경우에는 함수형이 아닌 클래스형 컴포넌트로 작성하기 (컴포넌트 성능 최적화 가능)

    • todos : todo객체들이 들어있는 배열
    • onToggle : 체크박스 켜고끄는 함수
    • onRemove : 아이템 삭제시키는 함수
import React, { Component } from 'react';
import TodoItem from './TodoItem';

class TodoItemList extends Component {

  shouldComponentUpdate(nextProps, nextState) {
    return this.props.todos !== nextProps.todos;
  }
  
  render() {
    const { todos, onToggle, onRemove } = this.props;
    
    const todoList = todos.map(
      ({id, text, checked}) => (
        <TodoItem
          id={id}
          text={text}
          checked={checked}
          onToggle={onToggle}
          onRemove={onRemove}
          key={id}
        />
      )
    );

    return (
      <div>
        {todoList}    
      </div>
    );
  }
}

export default TodoItemList;

src/Todolist/TodoItem.js

  • 체크 값이 활성화 되어 있으면, 우측에 체크마크 보여주기

  • 마우스 위에 있을 때, 좌측에 x표시 보여주기 (x 클릭시 삭제)

  • 컴포넌트 영역이 클릭되면, 체크박스 활성화되며 중간줄이 그어지기

    • text : todo 내용
    • checked : 체크박스 상태
    • id : todo의 고유 아이디
    • onToggle : 체크박스를 켜고 끄는 함수
    • onRemove : 아이템을 삭제시키는 함수
import React, { Component } from 'react';
import './TodoItem.css';

class TodoItem extends Component {

  shouldComponentUpdate(nextProps, nextState) {
    return this.props.checked !== nextProps.checked;
  }
  
  render() {
    const { text, checked, id, onToggle, onRemove } = this.props;

    return (
      <div className="todo-item" onClick={() => onToggle(id)}>
        <div className="remove" onClick={(e) => {
          e.stopPropagation(); // onToggle 이 실행되지 않도록 함
          onRemove(id)}
        }>&times;</div>
        <div className={`todo-text ${checked && 'checked'}`}>
          <div>{text}</div>
        </div>
        {
          checked && (<div className="check-mark">&#x2713;</div>)
        }
      </div>
    );
  }
}

export default TodoItem;

src/Todolist/App.js

  • 상태가 필요한 컴포넌트 : Form, TodoItemList
    • Form : input값, 변경시키는 함수 전달
    • TodoItemList : todo목록, 변경시키는 함수 전달
  • Form 기능 구현
    • (1) 텍스트 내용 바뀌면 state 업데이트
    • (2) 버튼이 클릭되면 새로운 todo 생성 후 todos 업데이트
    • (3) input에서 Enter 누르면 버튼을 클릭한 것과 동일한 작업 진행
import React, { Component } from 'react';
import TodoListTemplate from './Todolist/TodoListTemplate';
import Form from './Todolist/Form';
import TodoItemList from './Todolist/TodoItemList';

class App extends Component {

  id = 3 // 이미 0,1,2 가 존재하므로 3으로 설정

  state = {
    input: '',
    todos: [
      { id: 0, text: '클라이밍 서초 19시', checked: false },
      { id: 1, text: '아침 러닝 하기', checked: true },
      { id: 2, text: '사이클 30분 타기', checked: false },
    ]
  }

  handleChange = (e) => {
    this.setState({
      input: e.target.value // input 의 다음 바뀔 값
    });
  }

  handleCreate = () => {
    const { input, todos } = this.state;
    this.setState({
      input: '', // 인풋 비우고
      // concat 을 사용하여 배열에 추가
      todos: todos.concat({
        id: this.id++,
        text: input,
        checked: false
      })
    });
  }

  handleKeyPress = (e) => {
    // 눌려진 키가 Enter 면 handleCreate 호출
    if(e.key === 'Enter') {
      this.handleCreate();
    }
  }

  handleToggle = (id) => {
    const { todos } = this.state;
    
    // 파라미터로 받은 id 를 가지고 몇번째 아이템인지 찾습니다.
    const index = todos.findIndex(todo => todo.id === id);
    const selected = todos[index]; // 선택한 객체

    const nextTodos = [...todos]; // 배열을 복사
    
    // 기존의 값들을 복사하고, checked 값을 덮어쓰기
    nextTodos[index] = { 
      ...selected, 
      checked: !selected.checked
    };

    this.setState({
      todos: nextTodos
    });
  }

  handleRemove = (id) => {
    const { todos } = this.state;
    this.setState({
      todos: todos.filter(todo => todo.id !== id)
    });
  }

  render() {
    const { input, todos } = this.state;
    const {
      handleChange,
      handleCreate,
      handleKeyPress,
      handleToggle,
      handleRemove
    } = this;

    return (
      <TodoListTemplate form={(
        <Form 
          value={input}
          onKeyPress={handleKeyPress}
          onChange={handleChange}
          onCreate={handleCreate}
        />
      )}>
        <TodoItemList todos={todos} onToggle={handleToggle} onRemove={handleRemove}/>
      </TodoListTemplate>
    );
  }
}

export default App;

📖 참고 📖

🔶 Google Login

구글 로그인을 활용하기 위해서는 구글 클라우드 플랫폼을 이용해 OAuth2.0 클라이언트 ID를 발급받아야 함

1. Goolge OAuth2 연동용 클라이언트 아이디 발급

OAuth : 인터넷 사용자들이 비밀번호를 제공하지 않고 구글, 카카오, 네이버, 페이스북 등에 저장되어 있는 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로써 사용되는 접근 위임 개방형 표준 프로토콜

1) 구글 클라우드 플랫폼 접속

애플리케이션으로서 구글 API를 호출하려면, 구글에 자신의 애플리케이션을 클라이언트로 등록하고 클라이언트 ID를 발급받아야함

2) 프로젝트 생성

프로젝트 선택 → 프로젝트 이름 → 만들기

3) OAuth 동의

API 및 서비스 → OAuth 동의 화면 → 외부 → 만들기

앱 이름 및 이메일 입력 → 저장

4) 사용자 인증 정보

사용자 인증 정보 만들기 → OAuth 클라이언트 ID →
어플리케이션 유형 (웹 애플리케이션) → 승인된 자바스크립트 원본 (http://localhost:3000)

  • 로컬 환경에서 테스트 용도로 최소한의 내용만 입력하여 발급받는 것이므로, 필요한 내용은 추가 입력해야함
  • 발급받은 클라이언트 ID는 리액트의 구글 ID 로그인 동작과 함께 정보 요청시 발송됨

📖 참고 - 클라이언트 사이드 / 서버 사이드 구현 📖

5) OAuth2.0 프로토콜

  • SPA (Single Oage Application)에는 implicit grant 방식 많이 사용

  • 사용자 : 구글에 로그인 후에 애플리케이션에서 요청하는 권한 확인하고, 해당 권한을 허용함
  • 구글 인가 서버 : 사용자를 사전에 등록된 redirect url로 리다이렉트 시키면서 access token을 애플리케이션에 보내줌 - - 애플리케이션 : access token을 이용해서 구글 API호출함

2. 로그인 페이지

$ npm install react-google-login

하단에 있는 원인으로 다른 라이브러리 모색

웹용 Google 로그인 자바스크립트 플랫폼 라이브러리가 지원 중단됩니다. 지원 중단 날짜인 2023년 3월 31일 이후에는 이 라이브러리를 다운로드할 수 없습니다. 대신 새로운 웹용 Google ID 서비스를 사용하세요.
새로 생성된 클라이언트 ID는 기본적으로 이전 플랫폼 라이브러리를 사용하지 못하도록 차단되며, 기존 클라이언트 ID는 영향을 받지 않습니다. 2022년 7월 29일 이전에 생성된 새 클라이언트 ID는 Google 플랫폼 라이브러리를 사용하도록 plugin_name을 설정할 수 있습니다.

src/Login/LoginForm.js

authenticated 변수와 login 함수를 prop으로 받아서 사용하는 로그인 폼

  • 첫번째는 사용자가 자발적으로 직접 로그인 버튼을 클릭한 경우
  • 두번째는 로그인하지 않고 인증이 필요한 페이지를 접근하려다가 강제로 보내진 경우
    ( 컴포넌트로 부터 인증 여부를 받기 위해서 authenticated prop을 사용함)

이메일과 패스워드 입력 후 버튼을 클릭 시, 컴포넌트부터 prop으로 내려받은 login 함수를 호출해줍니다.
그러면 컴포넌트의 login 함수는 user state에 로그인된 사용자를 저장하거나, 예외를 발생킬 것입니다.
정상적으로 로그인이 되었다면 컴포넌트로 부터 넘어온 authenticated prop값은 true가 될 것입니다.
그러면 로그인 폼이 렌더링되는 대신에 React Router의 컴포넌트를 통해 로그인 이전에 접근하려고 했었던 페이지로 리다이렉션 됩니다.

import React, { useState } from "react";
import { Redirect } from "react-router-dom";

function LoginForm({ authenticated, login, location }) {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const handleClick = () => {
    try {
      login({ email, password });
    } catch (e) {
      alert("Failed to login");
      setEmail("");
      setPassword("");
    }
  };

  const { from } = location.state || { from: { pathname: "/" } };
  if (authenticated) return <Redirect to={from} />;

  return (
    <>
      <h1>Login</h1>
      <input
        value={email}
        onChange={({ target: { value } }) => setEmail(value)}
        type="text"
        placeholder="email"
      />
      <input
        value={password}
        onChange={({ target: { value } }) => setPassword(value)}
        type="password"
        placeholder="password"
      />
      <button onClick={handleClick}>Login</button>
    </>
  );
}

export default LoginForm;

src/Login/LogoutButton.js

로그아웃 버튼은 컴포넌트로 부터 logout 함수를 prop으로 내려받습니다.
버튼 클릭 시 이 logout 함수를 호출하고, 사용자를 홈페이지로 이동시킵니다.

import React from "react";
import { withRouter } from "react-router-dom";

function LogoutButton({ logout, history }) {
  const handleClick = () => {
    logout();
    history.push("/");
  };
  return <button onClick={handleClick}>Logout</button>;
}

export default withRouter(LogoutButton);

📖 로그인 구현 📖

📖 참고 📖

📌 3주차 목표 (2.23 ~ 3.3)


(공통) Django와 React 연동, React Router
(민경) 한달 운동량 요약
(석준) 단계별 레벨 표시
(수현) 로그인 구현


+ 암장 난이도 비교표
+ 일자별/장소별 운동 약속 확인

🔶 Django Web framework

1. 설치

1) 터미널에서 설치
: python -m pip install Django

2) 서브 명령들 확인
: django-admin

3) 프로젝트 생성
: django-admin startproject (프로젝트 이름) (프로젝트 위치)

  • settings.py : 각종 설정을 지정하는 파일
  • urls.py : 사용자가 접속하는 path에 따라서 접속 요청을 어떻게 처리할지 지정 (route 기능)
  • manage.py : 프로젝트를 진행하는데 있어서 여러가지 기능이 들어가있는 유틸리티 파일

4) 실행
: python manege.py runserver (원하는 장고 서버)

  • 장고 기본 서버 8000번
  • 서버 종료 : ctrl + c

2. app 생성

1) 사용자가 다양한 경로로 접속하면 project의 urls.py에서 각각의 경로를 어떤 app에게 위임할건지 지정

2) app의 urls.py에서 적합한 view의 알맞은 함수로 위임됨

3) db에 직접 접속하지 않고 django에 model이라는 수단을 이용하여 db를 사용

4) db 정보를 받아서 client에게 html, json, xml 형태의 데이터를 만들어서 응답해줌

  • app 생성
    : django-admin startapp (app 이름)

3. Routing URLConf

1) urls.py가 큰 틀의 routing 역할 수행

  • urlpattern 반드시 정의
from django.contrib import admin
from django.urls import path

urlpatterns = [ # urlpatterns 반드시 정의 
    path('admin/', admin.site.urls), # routing과 관련된 정보 포함 
    # (admin/ - 장고가 기본적으로 가지고 있는 관리자 화면으로 이동하기 위한 routing 설정)
]

2) http://127.0.0.1에 접속했을 때 app의 views.py로 위임

  • http://127.0.0.1/app 이라는 경로로 접속했을 때 myapp으로 위임하고 싶을 때 : path('app/', include('myapp.urls'))
# urls.py 

from django.urls import path
from myapp import views

urlpatterns = [ 
    path('', views.index), # 사용자가 home으로 들어옴 (아무것도 없는 경로)
    path('create/', views.create),
    path('read/<id>/', views.read)
]

# views.py

from django.shortcuts import render, HttpResponse

# index : client에게 정보를 전달하기 위한 함수 
# request : 첫 번째 파라미터의 인자로 요청과 관련된 여러가지 정보가 담긴 객체 전달 
def index(request): 
    return HttpResponse('Welcome!') # 처리한 결과를 return값으로 보내줌

def create(request): 
    return HttpResponse('Create!')

def read(request, id): # id를 이용하여 read함수의 파라미터를 이용해 변경 가능 
    return HttpResponse('Read'+id)

3) 접속이 들어올 때마다 랜덤한 정보를 동적으로 생성하기

from django.shortcuts import render, HttpResponse
import random 

def index(request): 
    return HttpResponse('<h1>Random</h1>'+str(random.random())) # 앞은 문자열, 뒤는 숫자라서 오류 발생 -> str로 형변환   

4. CRUD 기능

1) Read

  • hompage
    • /
  • article (상세보기)
    • /read//
# (1) views.py 기본 틀 

def index(request): # ''' 이용해 여러 줄 작성 
    return HttpResponse(''' 
    <html>
    <body>
        <h1>Django</h1>
        <ol>
            <li>routing</li>
            <li>view</li>
            <li>model</li>
        </ol>
        <h2> Welcome</h2>
        Hello, Django
    </body>
    </html>
    ''')  
  
# (2) 각각의 data를 dictionary에 담고, 모아서 grouping 하기 위해 list에 묶기 

topics = [ # dictionary에 data 담은 후 list로 그룹화 
    {'id':1, 'title':'routing', 'body':'Routing is ..'},
    {'id':2, 'title':'view', 'body':'View is ..'},
    {'id':3, 'title':'model', 'body':'Model is ..'},
]

def index(request): # ''' 이용해 여러 줄 작성 
    global topics # 해당 변수를 함수에서 사용하기 위해 전역변수로 지정 

    ol = ''
    for topic in topics:
        ol += f'<li><a href="/read/{topic["id"]}">{topic["title"]}</a></li>' # <a>태그로 상세페이지 링크 걸기 

    # f 붙이고 중괄호 사용시 변수 바로 사용 가능
    return HttpResponse(f''' 
    <html>
    <body>
        <<h1><a href="/">Django</a></h1>
        <ol>
            {ol}
        </ol>
        <h2> Welcome</h2>
        Hello, Django
    </body>
    </html>
    ''')  
  
# (3) html코드 함수화 시키기 

def HTMLTemplate(): # HTML 코드 함수화 시키기 
    global topics 

    ol = ''
    for topic in topics:
        ol += f'<li><a href="/read/{topic["id"]}">{topic["title"]}</a></li>' 

    return f''' 
    <html>
    <body>
        <h1><a href="/">Django</a></h1>
        <ol>
            {ol}
        </ol>
        <h2> Welcome</h2>
        Hello, Django
    </body>
    </html>
    '''

def index(request): 
    return HttpResponse(HTMLTemplate())  
  
# (4) HTML태그 변수화 시키기 (index와 read는 본문의 내용이 달라져야해서 변수화)
  
def HTMLTemplate(article): # HTML 코드 함수화 시키기 
    global topics 

    ol = ''
    for topic in topics:
        ol += f'<li><a href="/read/{topic["id"]}">{topic["title"]}</a></li>' 

    return f''' 
    <html>
    <body>
        <h1><a href="/">Django</a></h1>
        <ol>
            {ol}
        </ol>
        {article}
    </body>
    </html>
    '''

def index(request): # index와 read 본문 내용 달라야해서 변수화 
    article = ''' 
    <h2> Welcome</h2>
    Hello, Django
    '''
    return HttpResponse(HTMLTemplate(article)) # 본문의 내용을 인자로 전달  
  
# (5) read 함수 구현
  
def read(request, id):  
    global topics

    article = ''
    for topic in topics:
        if topic['id'] == int(id): # topic의 id는 정수, 파라미터 값은 문자열로 들어와 오류 발생  
            article = f'<h2>{topic["title"]}</h2>{topic["body"]}'
    return HttpResponse(HTMLTemplate(article))

2) Create

(1) create, delete, update 만들어야 하니까 리스트 형태로 변경

# views.py - HTMLTemplate 함수 
  
def HTMLTemplate(article): # HTML 코드 함수화 시키기 
    global topics 

    ol = ''
    for topic in topics:
        ol += f'<li><a href="/read/{topic["id"]}">{topic["title"]}</a></li>' 

    return f''' 
    <html>
    <body>
        <h1><a href="/">Django</a></h1>
        <ul>
            {ol}
        </ul>
        {article}
        <ul> 
            <li><a href="/create/">create</a></li>
        </ul>
    </body>
    </html>
    '''

(2) title, body 입력 및 제출 버튼 생성

# views.py - create 함수
  
def create(request): 
    # 입력값 이름 지정 : name, 도움말 : placeholder 
    # 줄바꿈을 위해 p태그 작성 (p : 단락 태그)
    # 여러 줄 입력 : textarea
    # title과 body에 담긴 type을 원하는 패스로 전달하기 위해 form 태그 사용 
    # from태그의 action 속성을 이용하여 원하는 패스로 전달 (/create/ : 현재 페이지로 전달)

    article = '''
        <form action="/create/">
            <p><input type="text" name="title" placeholder="title"></p>
            <p><textarea name="body" placeholder="body"></textarea></p>
            <p><input type="submit"></p>
        </form>
    '''

브라우저가 서버에게 데이터 요청 시 아래 2가지 사용 (GET 방식)

  • http:://127.0.0.1:8000/read/1
  • http:://127.0.0.1:8000/read/?id=1

?id=1 : query string (서버에게 정보를 질의할 때 사용)

브라우저가 서버에게 데이터 변경 시 GET 방식일 경우

  • http:://127.0.0.1:8000/create/?title=crud&body=crud+is+...

(URL안에 입력값이 포함된 상태가 되면, 주소를 copy해서 공유시 사용자가 클릭할 때마다 글이 추가됨)

브라우저가 서버에 있는 데이터를 변경하려고 할 때 URL에 query string을 넣으면 안됨 (POST 방식)

(3) GET → POST 방식

# views.py - create 함수 
  
    # 브라우저가 서버에 있는 데이터를 변경시 POST 방식 사용
    article = '''
        <form action="/create/" method="post">
            <p><input type="text" name="title" placeholder="title"></p>
            <p><textarea name="body" placeholder="body"></textarea></p>
            <p><input type="submit"></p>
        </form>
    '''

(4) GET 방식과 POST 방식 구분해서 실행

# views.py - create 함수
  
def create(request): 
    global nextId

    if request.method == 'GET':
        article = '''
            <form action="/create/" method="post">
                <p><input type="text" name="title" placeholder="title"></p>
                <p><textarea name="body" placeholder="body"></textarea></p>
                <p><input type="submit"></p>
            </form>
        '''
        return HttpResponse(HTMLTemplate(article))
  
    elif request.method == 'POST':
        title = request.POST['title']
        body = request.POST['body']
        newTopic = {"id":nextId, "title":title, "body":body} # newTopic에 id값이 없어서 nextId 사용 
        topics.append(newTopic)
        url = '/read/' + str(nextId)
        nextId = nextId + 1
        return redirect(url) # 누르면 상세보기 페이지 넘어가는 redirect 기능 

3) Delete

(1) 해당 data 삭제하고 홈으로 이동

delete 페이지가 따로 작성되어 있지 않고, 서버의 데이터를 바로 수정해야 해서 POST 방식 사용

  • GET 방식 : a 태그로 이동
  • POST 방식 : form 태그로 이동
# views.py - HTMLTemplate 함수 

def HTMLTemplate(article, id=None): # delete 함수에 어떤 id 삭제할지 필요한데, 인자를 사용하지 않으면 에러 발생 → id 인자 기본값 = None 지정
    global topics 

    ol = ''
    for topic in topics:
        ol += f'<li><a href="/read/{topic["id"]}">{topic["title"]}</a></li>' 

    return f''' 
    <html>
    <body>
        <h1><a href="/">Django</a></h1>
        <ul>
            {ol}
        </ul>
        {article}
        <ul> 
            <li><a href="/create/">create</a></li> 
            <li>
                <form action="/delete/" method="post">
                    <input type="hidden" name="id" value={id}> // hidden : 눈에 보이지 않지만 서버에 데이터 전송 
                    <input type="submit" value="delete">
                </form>
            </li>
        </ul>
    </body>
    </html>
    '''
# (2) id값 확인하여 일치한 것 삭제
  
# views.py - delete 함수
def delete(request):
    global topics

    if request.method == 'POST': # POST 방식이 맞는지 확인 필요
        id = request.POST['id']  # id 값 가져오기 
        newTopics = [] # id가 일치하지 않는 것 추가 
        for topic in topics:
            if topic['id'] != int(id):
                newTopics.append(topic)
        topics = newTopics
        return redirect('/') # 삭제 후 홈으로 이동

(3) 홈페이지가 아닌 상세페이지에 있을 때만 삭제 버튼 활성화

# views.py - HTMLTemplate 함수 
  
def HTMLTemplate(article, id=None): 
    global topics 
    contextUI = '' # 맥락에 따라서 UI가 만들어지고 안만들어지고 정해짐
    
    if id != None: # id값을 가지고 있다면 상세페이지에 위치, 없다면 홈에 위치 
        contextUI = f'''
            <li>
                <form action="/delete/" method="post">
                    <input type="hidden" name="id" value={id}>
                    <input type="submit" value="delete">
                </form>
            </li>
        '''

    ol = ''
    for topic in topics:
        ol += f'<li><a href="/read/{topic["id"]}">{topic["title"]}</a></li>' 

    return f''' 
    <html>
    <body>
        <h1><a href="/">Django</a></h1>
        <ul>
            {ol}
        </ul>
        {article}
        <ul> 
            <li><a href="/create/">create</a></li> 
            {contextUI}
        </ul>
    </body>
    </html>
    '''

4) Update

(1) Create와 유사하지만, 차이점은 form 안에 데이터가 표시됨

  • 상세페이지에서만 update 링크가 생김
  • title과 body가 form에 들어가 있음
    • title은 input의 value 속성으로 지정
    • body는 textarea 안에 있는 content로 지정
  • 제출 버튼을 누르면 서버로 전송되어 데이터 변경되고, 상세페이지로 이동
# views.py - HTMLTemplate 함수 
    
    if id != None: # id값을 가지고 있다면 상세페이지에 위치, 없다면 홈에 위치 
        contextUI = f'''
            <li>
                <form action="/delete/" method="post">
                    <input type="hidden" name="id" value={id}>
                    <input type="submit" value="delete">
                </form>
            </li>
            <li><a href="/update/{id}">update</a></li>
        '''

# views.py - update 함수 
def update(request, id):
    global topics

    if request.method == 'GET': # GET으로 접속시 update 텍스트 출력 
        for topic in topics:
            if topic['id'] == int(id): # 조회 성공시 selectedTopic 딕셔너리에 담기
                selectedTopic = {
                    "title":topic['title'],
                    "body":topic['body']
                }
        article = f'''
            <form action="/update/{id}/" method="post">
                <p><input type="text" name="title" placeholder="title" value={selectedTopic['title']}></p>
                <p><textarea name="body" placeholder="body">{selectedTopic['body']}</textarea></p>
                <p><input type="submit"></p>
            </form>
        '''
        return HttpResponse(HTMLTemplate(article, id))

    elif request.method == 'POST': # POST로 데이터 수정시 상세보기페이지로 이동 
        title = request.POST['title']
        body = request.POST['body']
        for topic in topics:
            if topic['id'] == int(id): # id 같을 경우 값 수정
                topic['title'] = title
                topic['body'] = body
        return redirect(f'/read/{id}')

📁 Web Server vs WAS

Web Server

  • 종류 : apache, nginix, IIS
  • 특징
    • 정적 (static)
    • 필요한 페이지를 미리 만들어야함
    • 속도가 빠르고 성능이 좋음

Web Application Server

  • 종류 : django, flask, php, jsp, ROL
  • 특징
    • 동적 (dynamic)
    • 웹페이지 생성하는 파이썬 스크립트만 만듦 (요청이 들어올 때마다 필요한 페이지를 만들어서 전달)
    • 유지보수 좋음, 개인화된 정보 생성 가능
    • python, Web framework, DB 등 학습할 양이 많음

📖 참고 📖
📖 django 설치 📖

📁 Django vs Spring vs Node.js

Django (Python)

  • MVT 패턴
    • Model : 데이터 관련 DB
    • View : Model과 View 연결 (함수 정의)
    • Template : 실제로 화면을 보여주는 인터페이스
  • ORM 기반 프레임워크
    • 객체지향 프로그래밍에서 객체라는 개념을 구현한 클래스와 RDB에 쓰이는 데이터인 테이블을 자동으로 연결하는 것 (SQL문 필요X)
  • MVT 패턴과 파일 구조
    • urls.py에서 넘어온 요청을 view.py의 함수와 연결
    • model.py에서 데이터를 읽어와 함수를 수행
    • render 함수를 통해서 template에 딕셔너리 형태로 데이터 넘겨주고, 받은 데이터를 가지고 html 수행

Spring (Java)

  • MVC 패턴
    • DAO (Data Access Object) : 실제로 DB에 접근하는 객체 (DB 전용 객체)
    • DTO (Data Transfer Object) : 데이터 교환을 위한 자바 객체 (데이터를 레이어 간 전달하는 것이 목적, 속성으로 Setter, Getter 가짐)
    • Service : 비즈니스 로직이 들어가는 부분 (Controller가 적절한 비즈니스 로직을 가지고 있는 Service 선택해서 선택된 Service가 적절한 처리를 한 후 반환)
  • 관점 지향 프로그래밍 (AOP)
    • 관심사 분리
    • 코드를 깔끔하게 작성 가능, 재사용성 ↑
  • 의존성 주입 (DI)
    • 각각의 계층이나 서비스들 간에 의존성이 존재할 경우 프레임워크가 서로 연결시켜줌
    • 설정 파일 부분에서 객체를 가져오니까 다양한 DB 사용 (DB마다 메서드 바꿀 필요x)
  • 제어역행 (IOC)
    • 컨트롤의 제어권이 사용자가 아니라 프레임워크에 있어서 스프링에서 사용자 코드를 호출
  • 경량 컨테이너
    • 크기의 부하 측면에서 경량
    • 애플리케이션 객체의 생명 주기와 설정을 포함하고 관리측면에서 컨테이너
  • Spring Framework 실행 흐름도
    • (1) 클라이언트가 Request 요청시 DispatcherServlet이 web.xml에 등록된 내요을 가로챔 (최초의 web.xml에서 '/'가 default라서 범위를 바꿔주면 특정 URL만 적용 가능함)
    • (2) DispatcherServlet이 가로챈 요청을 HandlerMapping에게 보내 해당 요청을 처리할 수 있는 Controller가 있는지 확인
    • (3) 실제 로직 처리
    • (4) 실제 로직 처리 후 ViewResolver를 통해 ViewResolver가 view 화면을 찾음
    • (5) 찾은 View 화면은 View에 보내면 이 결과를 다시 DispatcherServlet에게 보내고 DispatcherServlet는 최종 클라이언트에게 전송함

Node.js Express (js)

  • Node.js : 런타임 환경
  • 싱글 스레드 모델
  • 빠른 속도, CPU 대기시간 최소화, CPU 부하 적음
  • 하나의 작업에 시간이 많이 걸리면 성능 낮아짐
  • 비동기 IO 처리(프로그래밍) 모델
  • Async & Await 모듈
  • Chrome의 V8 자바스크립트 엔진 기반
  • 성능 뛰어남

🔶 Django와 React 연동

1. 방법

1) django template에서 react가 작동할 수 있게 static 파일에 react.js를 넣어 라이브러리로 사용

→ 서버 구조 간단하지만 react 기능 사용에 제한이 많아 채택x

2) 프론트엔드를 react로 작성하고, 데이터는 내부통신망의 django-rest-framework를 이용하여 가져오는 방식

  • 외부에는 front로 작성된 부분만 노출, 동작이 이뤄질 때마다 내부에 개설된 django-rest-framework 네트워크를 통해 가져옴
    • 비즈니스 로직과 프론트엔드 동작 완전히 분리
  • django와 react 사이의 내부 네트워크 구성한 후 react와 client의 외부 네트워크 구성시켜야함
    • 인증과정과 CRUD 동작에 대한 rest api 루트가 제대로 설정되어있다면 내부 네트워크 없이 완전 분리 가능
  • 검색엔진최적화(Search Engine Optimization, SEO)를 위해서 서버사이드렌더링(Server Side Rendering)이 되야함
    • 검색엔진은 http 요청을 보낸 순간에 그려져있지 않은 비동기데이터를 종합하지 못함 → SSR 기술 필요
    • SSR : http요청을 받았을 때 이미 html태그로 페이지를 그려놓아 검색엔진이 정보 수집해갈 수 있게해줌

2. django 구축

1) backend 디렉터리

  • django 설치 : django-admin startproject django_react_api .
  • api로 호출시킬 app 생성 : python manage.py startapp post
  • migrate 실행 : python manage.py mirate (sqlite 파일 생성)
  • 내부 웹서버 실행 : python manage.py runserver
# settings.py
  
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'post', # api로 호출할 app 이름 추가 
]
  
# models.py
from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()

    def __str__(self):
        """A string representation of the model."""
        return self.title
  
# admin.py
from django.contrib import admin

from .models import Post

admin.site.register(Post)

2) migrate으로 변경된 부분 db에 적용

  • python manage.py makemigrations
  • python manage.py migrate
  • python manage.py createsuperuser
  • python manage.py runserver
    (http://127.0.0.1:8000/admin 접속)

3) post 추가

3. django-rest-framework 구성 (DRF)

  • 설치 : pip install djangorestframework
# settings.py
  
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'post', # api로 호출할 app
    'rest_framework', # django-rest-framework 추가 
]

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ]
}

📌 목표

profile
Notion으로 이동

0개의 댓글