200824-200830_TIL

|

8월 24일 (월)

  • 오늘은 동국푸드를 손봐보았다. jquery를 이용해서 스크롤 이벤트로 스타일 제어도 해보았고, 저번에 못했던 ajax를 이용해 댓글기능도 구현해 보았다. 구현한 기능들은 정리해 봐야겠다.

8월 27일 (목)

  • 포트폴리오 사이트를 제작중이다. pdf이력서도 괜찮지만, 이렇게 웹 페이지로 보여주는것도 좋을거 같다. 깃허브 블로그는 무료 호스팅이라 이게 참 좋은거 같다.
  • 다만, 지금 쓰는 블로그에서 사이드바로 목차를 넣어보고 싶어 이것저것 테마 올려보다 망가져서 내려버렸다. 다음에 다시 도전해봐야겠다.
  • 친구와 하는 프로젝트 레이아웃 수정 및 일부 기능을 구현했다.

8월 28일 (금)

  • 에듀캐스트 강의를 마저 듣고있다. 너무 이것저것 하느라 중구난방이 되어서 다시 한 작업부터 마무리 해야겠다.

8월 29일 (토)

  • 에듀캐스트 강의의 챕터 10을 다 수강했다. 남은 11을 들으면서 Docker와 AWS를 사용해봐야겠다.

8월 30일 (일)

  • 리액트와 DRF 이해 안가는 부분들을 찾아보았다. 내것으로 습득하기란 먼 길인거 같다.

8월 31일 (월)

  • TIL 파일을 월별로 만들어서 관리하기로 했다.

Django 프로젝트02 - 리액트와 SPA 방식으로 인스타그램 만들기(8)

|

에듀캐스트 장고&리액트 강의를 듣고 정리하는 글이다.

장고에 JWT 토큰 발급 붙이기

import React, { useState, useEffect } from "react";
import { Card, Form, Input, Button, notification } from "antd";
import { SmileOutlined, FrownOutlined } from "@ant-design/icons";
import { useHistory } from "react-router-dom";
import Axios from "axios";
import useLocalStorage from "utils/useLocalStorage";

export default function Login() {
  const history = useHistory();
  const [jwtToken, setJwtToken] = useLocalStorage("jwtToken", "");
  const [fieldErrors, setFieldErrors] = useState({});

  const onFinish = (values) => {
    async function fn() {
      const { username, password } = values;

      setFieldErrors({});

      const data = { username, password };
      try {
        const response = await Axios.post(
          "http://localhost:8000/accounts/token/",
          data
        );
        const {
          data: { token: jwtToken },
        } = response;
        setJwtToken(jwtToken);
        console.log("jwtToken :", jwtToken);
        notification.open({
          message: "로그인 성공",
          icon: <SmileOutlined style= />,
        });

        // history.push("/accounts/login"); // TODO: 이동 주소
      } catch (error) {
        if (error.response) {
          notification.open({
            message: "로그인 실패",
            description: "아이디/암호를 확인해주세요.",
            icon: <FrownOutlined style= />,
          });
          const { data: fieldsErrorMessages } = error.response;
          // python: mydict.items()
          setFieldErrors(
            Object.entries(fieldsErrorMessages).reduce(
              (acc, [fieldName, errors]) => {
                /// errors : ["m1", "m2"].join(" ") => "m1 m2"
                acc[fieldName] = {
                  validateStatus: "error",
                  help: errors.join(" "),
                };
                return acc;
              },
              {}
            )
          );
        }
      }
    }
    fn();
  };
  return (
    <Card title="로그인">
      <Form
        {...layout}
        onFinish={onFinish}
        // onFinishFailed={onFinishFailed}
      >
        <Form.Item
          label="Username"
          name="username"
          rules={[
            { required: true, message: "Please input your username!" },
            { min: 5, message: "5글자 입력해주세요." },
          ]}
          hasFeedback
          {...fieldErrors.username}
          {...fieldErrors.non_field_errors}
        >
          <Input />
        </Form.Item>

        <Form.Item
          label="Password"
          name="password"
          rules={[{ required: true, message: "Please input your password!" }]}
          {...fieldErrors.password}
        >
          <Input.Password />
        </Form.Item>

        <Form.Item {...tailLayout}>
          <Button type="primary" htmlType="submit">
            Submit
          </Button>
        </Form.Item>
      </Form>
    </Card>
  );
}

const layout = {
  labelCol: { span: 8 },
  wrapperCol: { span: 16 },
};

const tailLayout = {
  wrapperCol: { offset: 8, span: 16 },
};

// https://usehooks.com/useLocalStorage/

import { useState } from "react";

export default function useLocalStorage(key, initialValue) {
  // State to store our value
  // Pass initial state function to useState so logic is only executed once
  const [storedValue, setStoredValue] = useState(() => {
    try {
      // Get from local storage by key
      const item = window.localStorage.getItem(key);
      // Parse stored json or if none return initialValue
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      // If error also return initialValue
      console.log(error);
      return initialValue;
    }
  });

  // Return a wrapped version of useState's setter function that ...

  // ... persists the new value to localStorage.

  const setValue = value => {
    try {
      // Allow value to be a function so we have same API as useState
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      // Save state
      setStoredValue(valueToStore);
      // Save to local storage
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      // A more advanced implementation would handle the error case
      console.log(error);
    }
  };

  return [storedValue, setValue];
}

Django 프로젝트02 - 리액트와 SPA 방식으로 인스타그램 만들기(8)

|

에듀캐스트 장고&리액트 강의를 듣고 정리하는 글이다.

장고에 JWT 토큰 발급 붙이기

JWT를 설치해 주자.

pip install djangorestframework-jwt

settings 파일에 아래와 같이 설정해 준다.

REST_FRAMEWORK = {
    'DEFAULT_PERMission_CLASSES': ["rest_framework.permissions.IsAuthenticated",],
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework_jwt.authentication.JSONWebTokenAuthentication",
        "rest_framework.authentication.SessionAuthentication",
    ],
}

JWT_AUTH = {
    'JWT_SECRET_KEY': SECRET_KEY,
    'JWT_ALGORITHM' : "HS256",
    'JWT_ALLOW_REFRESH': True,
    'JWT_EXPIRATION_DELTA': timedelta(days=7),
    'JWT_REFRESH_EXPIRATION_DELTA': timedelta(days=28),
}

urls 파일 설정

from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token, verify_jwt_token
from .views import SignupView

urlpatterns = [
    path('signup/', SignupView.as_view(), name='login'),
    path('token/', obtain_jwt_token),
    path('token/refresh/', refresh_jwt_token),
    path('token/verify/', verify_jwt_token),
]

Django 프로젝트02 - 리액트와 SPA 방식으로 인스타그램 만들기(7)

|

에듀캐스트 장고&리액트 강의를 듣고 정리하는 글이다.

Ant Design Form을 활용한 회원가입 폼 만들기

import React, { useState, useEffect } from "react";
import { Form, Input, Button, notification } from "antd";
import { SmileOutlined, FrownOutlined } from "@ant-design/icons";
import { useHistory } from "react-router-dom";
import Axios from "axios";

export default function Signup() {
  const history = useHistory();
  const [fieldErrors, setFieldErrors] = useState({});

  const onFinish = (values) => {
    async function fn() {
      const { username, password } = values;

      setFieldErrors({});

      const data = { username, password };
      try {
        await Axios.post("http://localhost:8000/accounts/signup/", data);
        notification.open({
          message: "회원가입 성공",
          description: "로그인 페이지로 이동합니다.",
          icon: <SmileOutlined style=/>,
        });

        history.push("/accounts/login")
      } catch (error) {
        if (error.response) {
          notification.open({
            message: "회원가입 실패",
            description: "아이디/암호를 확인해주세요.",
            icon: <FrownOutlined style=/>,
          });
          const { data: fieldsErrorMessages } = error.response;
          // python: mydict.items()
          setFieldErrors(
            Object.entries(fieldsErrorMessages).reduce(
              (acc, [fieldName, errors]) => {
                /// errors : ["m1", "m2"].join(" ") => "m1 m2"
                acc[fieldName] = {
                  validateStatus: "error",
                  help: errors.join(" "),
                };
                return acc;
              },
              {}
            )
          );
        }
      }
    }
    fn();
  };

  return (
    <Form
      {...layout}
      onFinish={onFinish}
      // onFinishFailed={onFinishFailed}
    >
      <Form.Item
        label="Username"
        name="username"
        rules={[
          { required: true, message: "Please input your username!" },
          { min: 5, message: "5글자 입력해주세요." },
        ]}
        hasFeedback
        {...fieldErrors.username}
      >
        <Input />
      </Form.Item>

      <Form.Item
        label="Password"
        name="password"
        rules={[{ required: true, message: "Please input your password!" }]}
        {...fieldErrors.password}
      >
        <Input.Password />
      </Form.Item>

      <Form.Item {...tailLayout}>
        <Button type="primary" htmlType="submit">
          Submit
        </Button>
      </Form.Item>
    </Form>
  );
}

const layout = {
  labelCol: { span: 8 },
  wrapperCol: { span: 16 },
};

const tailLayout = {
  wrapperCol: { offset: 8, span: 16 },
};

Django 프로젝트02 - 리액트와 SPA 방식으로 인스타그램 만들기(6)

|

에듀캐스트 장고&리액트 강의를 듣고 정리하는 글이다.

리액트 기본 기능으로 회원가입 폼 만들기

serializers

import React, { useState, useEffect } from "react";
import { Alert } from "antd";
import { useHistory } from "react-router-dom";
import Axios from "axios";

export default function Signup() {
  const history = useHistory();
  const [inputs, setInputs] = useState({ username: "", password: "" });
  const [loading, setLoading] = useState(false);
  const [errors, setErrors] = useState({});
  const [formDisabled, setFormDisabled] = useState(true);
  //   const [username, setUsername] = useState("");
  //   const [password, setPassword] = useState("");

  const onSubmit = (e) => {
    e.preventDefault();
    setLoading(true);
    setErrors({});
    Axios.post("http://localhost:8000/accounts/signup/", inputs)
      .then((response) => {
        console.log("response :", response);
        history.push("/accounts/login");
      })
      .catch((error) => {
        console.log("error:", error);
        if (error.response) {
          setErrors({
            username: (error.response.data.username || []).join(" "),
            password: (error.response.data.password || []).join(" "),
          });
        }
      })
      .finally(() => {
        setLoading(false);
      });
  };

  useEffect(() => {
    const isEnabled = Object.values(inputs).every((s) => s.length > 0);
    setFormDisabled(!isEnabled);
  }, [inputs]);

  const onChange = (e) => {
    const { name, value } = e.target;
    setInputs((prev) => ({
      ...prev,
      [name]: value,
    }));\
  };
  return (
    <div>
      <form onSubmit={onSubmit}>
        <div>
          <input type="text" name="username" onChange={onChange} />
          {errors.username && <Alert type="error" message={errors.username} />}
        </div>
        <div>
          <input type="password" name="password" onChange={onChange} />
          {errors.password && <Alert type="error" message={errors.password} />}
        </div>
        <input
          type="submit"
          value="회원가입"
          disabled={loading || formDisabled}
        />
      </form>
    </div>
  );
}