해리의 데브로그

JavaScript 02 - 기본문법 (2) / 배열, 함수

|

JavaScript 기본 문법 (2) - 배열, 함수

5. 배열

1) length / reverse / push

  • .length : 배열의 길이
  • .reverse() : 배열 뒤집기
  • .push(data) : 배열의 끝에 데이터 추가, 배열의 길이를 반환함
const numbers = [1,2,3,4]

>console.log(numbers[0])
1

> const numbers = [1,2,3,4]
undefined
> numbers
[ 1, 2, 3, 4 ]
> numbers.length
4
> numbers.reverse()
[ 4, 3, 2, 1 ]
> numbers
[ 4, 3, 2, 1 ]
> numbers.reverse()
[ 1, 2, 3, 4 ]
> numbers.push('a')
5
> numbers
[ 1, 2, 3, 4, 'a' ]
> numbers.push(5)
6
> numbers
[ 1, 2, 3, 4, 'a', 5 ]

2) pop / unshift / shift

  • .pop() : 배열의 오른쪽 끝 값 제거. 배열의 길이를 반환함.
  • .unshift() : 배열의 왼쪽에 값 추가
  • .shift() : 배열의 왼쪽 값 제거. 제거되는 값을 반환함.
> numbers.pop()
5
> numbers.pop()
'a'
> numbers.unshift('a')
5
> numbers
[ 'a', 1, 2, 3, 4 ]
> numbers.shift()
'a'
> numbers
[ 1, 2, 3, 4 ]

3) includes / indexOf

  • .includes(data): 배열에 data가 들어있으면 true 반환, 없으면 false 반환
  • .indexOf(data): 배열에 data가 들어있으면 왼쪽에서 부터 인덱스 번지를 반환, 없으면 -1 반환
> numbers.includes(1)
true
> numbers.includes(0)
false
> numbers.indexOf('a')
-1
> numbers.push('a')
5
> numbers.push('a')
6
> numbers.indexOf('a')
4

4) join / slice

  • join(형식): 데이터를 형식으로 구분해서 나타냄
  • slice(2,4): 2번부터 4번 전까지 인덱스에 해당하는 값 출력
  • slice(2): 인덱스 2번부터 값 출력
  • slice(-2): 뒤에서부터 두번째부터 출력
> numbers.join()
'1,2,3,4,a,a'
> numbers.join('')
'1234aa'
> numbers.join('-')
'1-2-3-4-a-a'
> numbers.join()
'1,2,3,4,a,a'
> numbers.join('')
'1234aa'
> numbers.join('-')
'1-2-3-4-a-a'
> numbers.slice(2,4)
[ 3, 4 ]
> numbers.slice(2)
[ 3, 4, 'a', 'a' ]
> numbers.slice(-3)
[ 4, 'a', 'a' ]
> numbers.slice(-2)
[ 'a', 'a' ]

5) Object

  • 원시타입(primitive)를 제외한 나머지 값들(함수, 배열, 정규표현식 등)을 일컫으며, 객체는 key와 value로 구성된 Property들의 집합임.
  • 앞에 붙는 key값에 쌍따옴표를 해주지 않아도 됨.
    • object를 사용할 때는 []안에 키를 넣어 value를 불러올때 따옴표를 붙여줘야 함.
  • 중간에 띄어쓰기가 있다면 따옴표가 필요.
  • 오프젝트의 value로 배열, object 뭐든지 올 수 있음.
  • 오브젝트 사용시 me.name 과 같이 띄어쓰기가 없다면 . 으로 key를 불러 사용할 수 있음.
const me = {
... name: 'harry',
... 'phone number': '01012345678',
... appleProduct :{
..... ipad: true,
..... iphone: 'X'
..... }
... }

> me['name']
'harry'
> me.name
'harry'
> me.appleProduct
{ ipad: true, iphone: 'X' }
  • 아래와 같이 선언한 변수도 Object안에서 사용 가능하다.
const food = {
    fruits : ['apple', 'grapes'],
    vegetables : ['onion', 'lettuce']
};

const server = null;

const restaurant = {
    food,
    server
}

console.log(restaurant)
  • method
    • 파이썬과 달리 별도의 문법이 잇는 것이 아니라 오브젝트의 value에 함수를 할당함
    • 앞서 변수/object와 같이 함수도 ES6+부터 아래와 같이 선언도 가능하다.
    • 메서드 정의시, arrow function은 사용하지 않음.
const me2 = {
    name : 'LEE',
    greeting: function(message) {
        return `${this.name} : ${message}`
    }
}

console.log(me2.greeting('hi')) // LEE : hi
me2.name = 'PARK'
console.log(me2.greeting('hello')) // PARK : hello
const greeting = function(message) {
    return `${this.name} : ${message}`
}

const you = {
    name : 'you',
    greeting
}

console.log(you.greeting('hi'))
you.name = "KIM"
console.log(you.greeting('hello'))

6) JSON - JavaScript Object Notation (JS 객체 표기법)

  • 어떠한 요청을 보낼때는 문자열 타입으로 보내고 받는 쪽은 문자열을 object형태로 변환하는 과정이 웹에서 발생한다.
  • JSON은 stringify()parse() 를 이용하여 object와 string으로 변환이 가능하다.
JSON.stringify() // Object -> JSON String
JSON.parse() // JSON String -> Object
> const JsonData = JSON.stringify(me)
undefined
> JsonData
'{"name":"harry","phone number":"01012345678","appleProduct":{"ipad":true,"iphone":"X"}}'
> typeof JsonData
'string'
> const parseData = JSON.parse(JsonData)
> parseData
{ name: 'harry',
  'phone number': '01012345678',
  appleProduct: { ipad: true, iphone: 'X' } }
> typeof parseData
'object'

6. 함수

1) 함수 선언식

function add(num1, num2){
    return num1 + num2
}

console.log('add: ' + add(1,2))

2) 함수 표현식

const sub = function(num1, num2){
    return num1 - num2
}

console.log('add: ' + sub(5,3))

3) Arrow Function(ES6 +)

  • ES6+ 부터 Arrow Function을 사용하면 함수를 더욱 간소화 시킬 수 있음.
// 기존방법
const mul = function(num1, num2) {
    return num1 * num2
}

//Arrow: function을 제거하고, => 를 추가함.
const mul = (num1, num2) => {
    return num1* num2
}

let square = (num) => {
    return num ** 2
}

// return문 단 한줄이면 {} & return 생략 가능
square = (num) => num ** 2

// ()안의 인자가 하나뿐이면 () 생략 가능. 인자가 없는 경우는 생략 불가
// 0개인 경우는 아래와 같이 입력 요
square = num => num ** 2
let noArgs = () => 'No args'

// object를 return하는 경우
// 괄호가 없으면 {}를 함수의 {}로 인식하기 때문에 ()가 필요!
let returnObject = () => ({key:'value'})
  • 함수의 기본 인자 설정 가능
const sayHello = (name='noName') => `hi${name}`

console.log(sayHello('John'))
console.log(sayHello())

=> hiJohn
=> hinoName
  • 익명 함수; 이름이 없는 함수를 익명함수 라고 함.
function (num) {return num ** 3} // 세제곱
(num) => {return num ** 0.5} // 제곱근

// 익명 함수 즉시 호출
(function (num) {return num**3})(3) // 3의 3제곱근

JavaScript 01 - 기본문법 (1) / HTML 조작, 변수, 자료형, 조건 & 반복

|

JavaScript 기본 문법 (1)

1. JavaScript로 HTML 조작

1) 기본 작성 위치

  • JS는 html에서 사용된 제목 본문을 수정해야 하기 때문에 html로 작성된 구문 밑에서 작성됨.
  <!DOCTYPE html>
  <html lang="en">
  <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Document</title>
      <!-- JS 위치 -->
  </head>
  <body>
      
      <!-- JS 위치 -->
      <script>
          
      </script>
  </body>
  </html>

2) 주석달기

  • 한줄에 주석을 달때는 // 을 사용하고, 여러줄을 쓸때는 /* */ 를 사용함.
/*
This is Comment
*/

// This is Comment

3) 문서조작

  • JS로 문서 조작
    • 문서에 html태그가 적용되어서 글이 써짐
//문서에 출력하기
document.write('<h1>Hello, World!</h1>')
  • 브라우저 상에서 JS로 웹 문서 조작
    • 개발자 도구 -> Console 창에서 입력해서 태그를 선택하고 태그의 글을 수정할 수 있음.
document.querySelector('h1')
<h1>Hello, World!</h1>document.querySelector('h1').innerText
"Hello, World!"
document.querySelector('h1').innerText = 'Bye, World!'
"Bye, World!"

2. 변수

1) var, let, const

  • 파이썬과 달리 javascript에서는 기본적으로 변수를 선언해야함.
  • ES6 이전에는 var을 이용하였으나 이후에는 letconst를 사용함.
  • let은 변수를 선언하는 keyword로 재할당 가능함.
  • const는 상수를 선언하는 keyword로 재할당 불가능함.
    • console에서 재할당 하면 Uncaught TypeError: Assignment to constant variable. 에러 발생
// let: 재할당 가능
let word1 = '하이'
document.write(word) 

// const: 재할당 불가능
const word2 = '안녕?'
document.write(word2)
const word2 = '안녕2?' // Uncaught TypeError: Assignment to constant variable 에러발생

2) var vs let, const

  • var은 function 스코프이며, letconst는 block 스코프이다.
// let: 블록 스코프. {} 내에서만 선언된 변수를 사용할 수 있음. 
for (let j=0; j<1; j++){
    console.log(j) // 0 
}
// console.log(j) // Uncaught TypeError: Assignment to constant variable 에러발생

// var: 함수 스코프. 함수 내에서 선언된 변수를 사용할 수 있음. 
// 따라서 반복문 블록{} 밖에서도 함수 내라면 선언된 함수를 사용할 수 있음.
for (var i=0; i<1; i++){
    console.log(i)
}
console.log(i)

const myFunction = function() {
    for (var k=0; k<1; k++){
        console.log(k)
    }
    console.log(k)
}
myFunction() 
// 0 
// 1

console.log(k) // ReferenceError: k is not defined
  • 만약 변수 선언 키워드를 사용하지 않으면, 함수/블록 안에서 선언되어더라도 무조건 전역변수로 취급된다.
// 변수 선언 키워드 사용 X -> 무조건 전역변수로 취급됨
function myFunction1(){
    for (p=0; p<1; p++){
        console.log(p)
    }
    console.log(p)
}
myFunction1()
console.log(p) // 1

3) 문자열과 포맷팅

  • 아래의 형태로 formatting을 활용하여 입력할 수 있음.
  • `(backtick) 통해서 문자열을 안에 넣어서 작성할 수 있다.
const firstName = 'Harry'
const lastName = 'Potter'
const fullName = firstName + lastName
document.write('<h1>' + fullName + '!!' + '</h1>')
document.write(`<h1>${fullName}!!</h1>`)

// console에 출력하기
console.log(`Console: ${fullName}!!`)

4) 변수 네이밍

  • JavaScript는 userName 과 같은 띄워쓰기를 대문자로 구문하는 CamelCase를 사용함
    • python은 under bar (_) 로 변수명을 구분지음. 예) user_name (snake_case)

5) 사용자 입력받기

  • prompt 를 사용하면 브라우저 실행 시 팝업 입력창을 띄울 수 있음. 이를 활용하여, 브라우저에 변수의 값을 나타낼 수 있음.
<body>
    <script>
        const userName = prompt('Hello! Who are you?')
        let message = `<h1>hello ${userName}</h1>`
        document.write(message)
    </script>
</body>

3. 자료형

1) 기본 자료형 (primitve)

  • Boolean (true/false)
  • null - 어떤 값이 의도적으로 비어있음을 표현함.
  • undefined - 값을 할당하지 않은 변수를 undefined로 나타냄.
  • number(Infinty, 양수, 음수 등)
  • string - 텍스트 데이터를 나타낼 때 사용. immutable한 속성을 갖고 있음.

2) object

  • key와 value가 있는 형태로, key를 통해 value에 접근이 가능함. JSON과 유사한 형태임.

4. 조건/반복

1) 조건문

  • if / else if / else 로 사용하며 기본적인 구조는 아래의 예시를 참조하자.
  • 또한, 위에서 배운 내용을 활용하여 입력받은 변수로 조건문 분기를 사용할 수 도 있다.
const userName = prompt('Hello! Who are you?')
let message = ''

// if문
if (userName === 'admin') {
    message = '<h1>This is secret Admin page</h1>'
}
else if (userName === 'HarryLee'){
    message = '<h1> I am inevitable </h1>'
}
else {
    message = `<h1>Hello ${userName}</h1>`
}
  • ===== 비교
    • == 는 동등 연산자로, 피연산자가 서로 다른 타입일 경우, 타입을 강제로 형변환하여 비교함.
    • === 는 일치 연산자로, 두 피연산자를 더 정확하게 비교함.
// == vs. ===
// == : '값' 만 비교 (ex. 0=="0"는 true) 타입은 다르지만 값은 동일해서 true
// === : '값 & '타입' 비교 (ex. 0 === "0"는 false. 한쪽은 integer 다른 쪽은 string이기 때문) 

// 동등 연산자(==) 사용
> 1 == '1'
true
> 1 == '1 '
true
> 1 == true
true
> undefined == null
true

> null == true
false
> 'true' == true
false
> true == 2
false

// 일치 연산자(===) 사용
> 1 === '1'
false
> true === 1
false
> undefined === null
false

2) 삼항 연산자

  • 조건문이 참이면 콜론(:)앞의 구문실행하고 거짓이면 콜론(:)뒤의 구문을 실행함.
const number = 10
number === 10 ? console.log('number === 10') : console.log('number !== 10')

cf) 세미콜론 테스트

  • javascript는 세미콜론의 사용은 자유로움. 에러가 발생 할 수 있는 구문에 세미콜론을 사용할 것.
  • 세미콜론을 찍는 규칙은 C 언어와 동일함.
const a = 1
const b = 2
const c = a + b
(a+b).toString()

구문이
const a = 1
const b = 2
const c = a+ b(a+b).toString() 이렇게 연결되서 인식   있다.

3) 반복문

  • 방법 1, 2는 let 변수를 사용해서 연산자로 변수의 값을 수정해서 반복문을 돌리고
  • 방법 3을 const로 선언할 경우, 반복문이 돌면서, 기존에 선언된 값은 지워지고, number에 새로운 값으로 선언을 해서 반복문을 돌려짐.
// 방법 1 
let i = 0
while (i<10){
    console.log(i)
    i++
}
// 방법 2
for(let j=0; j<10;j++){
    console.log(j)
}
// 방법 3
for (let number of [1,2,3,4,5]) { // const로도 선언 가능
    console.log(number)
}

2019년 6월 5주차 TIL

|

2019-06-24

  • 드디어 자바스크립트를 시작, 기본문법부터 정리 및 복습 진행
  • 자바스크립트는 파이썬과 달리 기본적으로 변수를 선언해야하며, 변수선언의 종류는 미세하게 다름
    • let & const : 둘다 변수를 선언하는 keyword 이나, let 은 재할당이 가능하나 const는 불가능
    • var 은 function 스코프이며, letconst 는 block 스코프임
  • 자바스크립트에서는 key와 value가 있는 형태인, JSON과 유사한 Object라는 자료형이 존재함.
  • ===== 의 쓰임새는 다름 (==의 경우 서로다른 타입일 때, 타입을 강제로 형변환함)
  • 삼항연산자를 이용하면 조건문을 좀더 간결히 사용가능함!

2019-06-25

  • 자바스크립트 기본 문법 중 배열 & 함수에 대해 공부를 하였음.
    • length, reverse, push, pop, unshift, shift, includes, indexOf, join, slice
  • object는 파이썬의 딕셔너리처럼, key를 통해 value 값에 접근이 가능. 선언한 변수 또한 objet에서 사용이 가능함
  • 함수는 선언식, 표현식, arrow function 등 여러방법으로 표현이 가능한데, ES6+부터 사용가능한 arrow function 사용법이 매우 흥미롭다.
  • 따로 이름을 없는 함수를 익명함수라고 하는데, 콜백함수가 그대표적인 익명함수임.

2019-06-26

  • 자바스크립트의 Array Helper Methods에 대해서 정리를하였다.
  • 가장 기본적인 반복형태인 forEach 부터 map, filter, every 등 다양한 배열 조작 메서드가있었는데, 그중 특히 reduce 메서드가 많은 기억이 남는다. 수업시간에는 다루지 않았던 메서드인데, 강사님이 시간이 되면 한번 보면 좋다는게 기억이나서, 공식문서읽기 부터 시작해서 정리를 하였다. (이땐 몰랐다. 20분이면 끝날줄 알았던 정리가 2시간이 넘게걸릴 줄은..)
  • 배열의 각 요소에 대하여 주어진 콜백 함수를 실행시키고 결과 값을 반환하는데, 콜백의 반환값을 누적시킬 수도 있으며, 초기값을 새로 주는것도 가능했다. 반복문을 돌면서 콜백을 계속 호출하는 매커니즘이 특이 했다.
  • 잘 활용만 할 수 있다면 활용도가 무궁무진할 것 같은데, 사용법을 확실히 익힐 수 있도록 연습을 많이 해야겠다.( 실제로도 다른 배열 조작메서드(map , filter , find) 를 reduce로 구현이 가능했다)

2019-06-27

  • Event Listener 와 Axios를 통한 요청 보내기를 공부하였음
  • Event Listener는 사용자가 지정한 event가 발생하였을 때, 정의한 코드가 발생하게끔 작동하는 함수를 의미함. 마우스 버튼을 눌렀을 때, 글자가 뜨게한다거나, 키를 눌렀을 때, 창을 띄운다거나 등, 이벤트의 유형에 따라 많은 것을 할 수 있음.
  • Axios는 자바스크립트로 요청을 보내는 복잡하고 지저분한 코드를 손쉽게 요청을 보낼 수 있게 하는 HTTP Client 라이브러리임(결과로 promise 객체를 반환함)
  • 자바스크립트는 싱글 스레드 기반의 비동기적으로 동작하는 언어이다.
    • 싱글스레드: 스레드가 하나라는 의미로, 동시에 하나의 작업만을 철 할 수 있다는 의미
    • 비동기: 코드가 먼저 작성된 순서대로(동기적으로) 코드가 실행되는 것이 아니라, 앞에서 실행된 코드의 작업이 끝나기 전에 뒤의 코드를 읽고 작업이 끝날 수도 있다는 의미임
    • 비동기적으로 동작하지 않는다면, 시간이 매우 오래걸리는 코드가 중간에 있다고 가정할 경우 브라우저는 멈추어버리기 때문에 많은 많은 불편함을 야기할 것임.
  • Axios에서는 이처럼 코드가 비동기적으로 막기 위해서 .then 메서드로 코드를 이어줌.
  • 간단하게 스레드 & 비동기 & Promise 에 대한 개념을 정리했는데, 시간이 날 때 이 개념을 좀 더 상세히 공부하고 MarkDown으로 정리해야겠다.

Django 28 - Django-RestFramework(DRF)를 이용한 API 구현

|

Django-RestFramework를 이용한 RESTful API 구현

DRF(Django RestFramework)는 Django 안에서 RESTful API 서버를 쉽게 구축할 수 있도록 도와주는 오픈소스 라이브러리이다.

REST API에 대한 이전 포스팅에서 REST API에 대해 정리를 한 적이 있는데, REST란 간단히 URI는 자원을 표현하는데 집중하고, HTTP METHOD(POST / GET /PUT /DELETE)를 통해 행위(해당 자원을 제어하는) 에 대해 정의하는 방식의 아키텍쳐를 의미한다. 이와 같은 방식으로 적용된 API를 RESTful API 라고 한다.

따라서, DRF를 이용하여 RESTful API 백엔드 서버를 구축하는 작업을 해보도록 하자.

기본 설정

1. 기본 설정

  • python -m venv api_venv : api_venv 라는 가상환경 생성
  • source activate : api_venv/Scripts/ 폴더로 이동 후 가상환경 실행
  • pip install django==2.1.8 : 2.1.8 버젼의 장고 설치

  • django-amdin startproject api . : api 라는 신규 프로젝트 생성
  • python manage.py startapp musics : musics 이라는 어플리케이션 생성
  • pip install djangorestframework : DRF 라이브러리 설치
  • 어플리케이션 생성 후, settings.py 에 앱 추가
INSTALLED_APPS = [
    'rest_framework',
    'musics',
]

2. models.py

  • 가수(artist)에 대한 모델 생성
  • 곡(music)에 대한 모델을 생성하고, artist 모델과 1:N 관계 설정
  • 댓글(comment)에 대한 모델 생성 후, music 모델과 1:N 관계 설정
  • def __str__(self) 는 클래스가 생성되었을 때, 어떻게 보여줄건지를 오버라이딩하여 정의할 때 쓰는 함수
  • 마이그레이션 설정
from django.db import models

class Artist(models.Model):
    name = models.TextField()
    
    def __str__(self):
        return self.name

class Music(models.Model):
    artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
    title = models.TextField()
    
    def __str__(self):
        return self.title
    
class Comment(models.Model):
    music = models.ForeignKey(Music, on_delete=models.CASCADE)
    content = models.TextField()

3. admin.py

  • python manage.py createsuperuser 을 통해 admin페이지에 접속할 superuser 생성
  • 이 프로젝트에서는 따로 create에 대한 로직을 생성하지 않을 예정이므로, admin 페이지에 접속하여 각 모델에 대한 정보를 DB에 저장하도록 하자
from django.contrib import admin
from .models import Artist, Music, Comment

admin.site.register(Artist)
admin.site.register(Music)
admin.site.register(Comment)

4. urls.py

  • 프로젝트(api) / 어플리케이션(musics) 별로 urls.py 를 구분하고, 주소에서 API에 대한 것인것을 알 수 있게 네이밍 설정. v1은 version1을 의미
  • admin 페이지를 사용하기 위해 import
# 프로젝트 (api) urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('api/v1/', include('musics.urls')),
    path('admin/', admin.site.urls),
]

# 앱 (musics) urls.py
from django.urls import path
from . import views

urlpatterns = [
]

Music 리스트 API 구현

1. urls.py

urlpatterns = [
    path('musics/', views.music_list),
]

2. views.py

  • @api_view(): 어떠한 HTTP Methods를 허옹할건지에 대한 설정을 하는 데코레이터. 아래의 예시의 경우, GET 방식으로 music 리스트 함수를 호출하므로 @api_view(['GET']) 사용
  • serializer에 대한 코드 작성 필요.
from django.shortcuts import render
from .models import Music
from rest_framework.decorators import api_view

@api_view(['GET'])
def music_list(request):
    musics = Music.objects.all()
    
    #serializer

3. serializers.py

장고 MTV 패턴에 따라 HTML 문서를 넘기면 브라우저는 HTML 문서 내 코드를 해석하여 사용자에게 화면을 표시하였음. API는 브라우저를 통해 응답하는 것이 아니라, 요청을 주소에 담아 던지면 HTML 파일이 아니라 단순하게 텍스트 데이터의 형태로 응답을 보냄. 좀 더 밀도있는 문자열의 형태로 만들어주는 친구가 serializer임. serializer은 쿼리셋과 모델 인스턴스와 같은 복잡한 데이터를 JSON, XML 또는 다른 컨텐츠의 유형으로 쉽게 변환 할 수 있도록 해줌.

DJango에서 Client으로 복잡한 데이터(모델 인스턴스 등)를 보내려면 ‘string’으로 변환해야합니다. 이 변환을 serializer 라고 함.

  • forms.py 내 모델폼을 작성했던 것과 거의 동일한 형태임. ModelSerializer를 통해 우리가 얻고자 하는 정보(id, title, artist)를 받아옴.
  • serializer은 DRF에 내장된 기능이므로 import 할 것
  • Music 모델 import
  • 외래키인 artist의 id가 들어가는 필드명은 artist_id 이나 artist 중 어떤걸 사용해도 상관없음
from rest_framework import serializers
from .models import Music

class MusicSerializer(serializers.ModelSerializer):
    class Meta:
        model = Music
        fields = ['id', 'title', 'artist']

4. views.py

  • serializers.py 내 정의한 MusicSerializer import

  • views.py 함수에서는 최종적으로 response라는 객체를 반환해야함. (render 이나 redirectresponse 클래스를 상속받은 메소드임). imoprt 해오자.
  • 함수는 항상 응답으로 return 해야함 (Web은 항상 요청과 응답으로 이루어져있음을 기억하자)
  • serializer의 첫번째 인자에는 문자열의 형태를 변환시킬 데이터를 넣음.
    • musics 에는 Music 모델에 저장된 모든 데이터가 저장된 변수로, 여러개의 객체가 저장되어있으므로 many=True 인자를 넘겨줄 것.
from django.shortcuts import render
from .models import Music
form rest_framework.decorators import api_view
# 추가
from .serializers import MusicSerializer
from rest_framework.response import Response


@api_view(['GET'])
def music_list(request):
    musics = Music.objects.all()
    serializer = MusicSerializer(musics, many=True)
    return Response(serializer.data)

Music 디테일 API 구현

1. urls.py

urlpatterns = [
    path('musics/<int:music_id>/', views.music_detail),
]

2. views.py

  • 동일한 방법으로 함수 구현
  • get_object_or_404 메서드 사용을 위해 import
  • id=music_id 을 만족하는 Music 모델의 인스턴스 객체만을 갖고오므로 하나의 객체만 저장되어있음. 따라서 serializer에 many=True 인자는 필요 없음.
from django.shortcuts import get_object_or_404

@api_view(['GET'])
def music_detail(request, music_id);
	music = get_object_or_404(Music, id=music_id)
    serializer = MusicSerializer(music)
    return response(serializer.data)

API 자동 문서화 라이브러리 설치

1. settings.py

  • pip install django-rest-swagger 설치
  • INSTALLED_APPS에 추가
INSTALLED_APPS = [
    'rest_framework_swagger',
]

2. urls.py

  • swagger가 제공하는 view를 갖고와서 import
  • get_swagger_view 에는 키워드 인자 title 사용 시, 해당 페이지의 제목을 설정 할 수 있음.
from django.urls import path
from . import views
from rest_framework_swagger.views import get_swagger_view

urlpatterns = [
    path('docs/', get_swagger_view(title='API Docs')),
]

Artist 리스트 API 구현

1. urls.py

urlpatterns = [
    path('artists/', views.artist_list),
]

2. serializers.py

  • Artist 모델 import
from .models import Music, Artist

class ArtistSerializer(serializers.ModelSerializer):
    class Meta:
        model = Artist
        fields = ['id', 'name',]

3. views.py

  • ArtistSerializer & Artist import
  • music_list 함수와 동일한 방식으로 코드 작성
from .serializers import MusicSerializer, ArtistSerializer #ArtistSerializer import
from .models import Music, Artist #Artist 모델 import

@api_view(['GET'])
def artist_list(request):
    artists = Artist.objects.all()
    serializers = ArtistSerializer(artists, many=True)
    return response(serializer.data)

Artist 디테일 API 구현 & 관계설정

models.py에 구현한 모델에 따르면 Artist 모델은 아래와 같이 name 필드만 갖고 있음. 하지만 ArtistMusic 이 1:N 관계가 형성되어있기 때문에, Artist 디테일 API에서 관계가 형성되어 있는 music 정보도 함께 보여줄 수 있음. 이를 위한 코드를 작성해보자.

class Artist(models.Model):
    name = models.TextField()
	#...중략

class Music(models.Model):
    artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
    title = models.TextField()
	#...중략

1. serializers.py

  • ArtistDetailSerializer 클래스 정의.
  • MusicSerializer를 통해 갖고오는 music 정보는 music_set이라는 변수에 저장.
    • artist.music_set.all() 와 같이 1:N 관계에서 1로부터 N으로 접근할 경우 _set을 사용함. 이 방법을 사용하여 자식모델에 접근하기 위해 변수명에 _set을 넣어서 정의
    • MusicSerializer의 인자에 변환할 기존의 데이터를 첫번째 인자에 넣지 않는 이유는 views.py 내에서 Artist 모델의 인스턴스 객체인 artist를 ArtistDetailSerializer의 인자로 넘기는데, 해당 객체는 artist_id를 갖고 있으므로, ArtistDetailSerializerMusicSerializer 의 인자로 변수를 따로 넘길 필요가 없으며, 관계설정에 따라 해당 가수의 음악 목록을 다 갖고 올 수 있음.
class ArtistDetailSerializer(serializers.ModelSerializer):
    music_set = MusicSerializer(many=True)
    class Meta:
        model = Artist
        fields = ['id', 'name','music_set',]

2. views.py

@api_view(['GET'])
def artist_detail(Request, artist_id):
    artist = get_object_or_404(Artist, id=artist_id)
    serializer = ArtistDetailSerializer(artist)
    return Response(serializer.data)

Comment API 설정

1. urls.py

path('musics/<int:music_id>/comments/', views.comment_create),

2. serializers.py

  • Comment 모델의 외래키인 music_idCommentSerializer 내 구현할 필요가 없음. serializer 는 오롯이 데이터를 가공하는 역할을 할 뿐임.
from .models import Music, Artist, Comment

class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = ['id', 'content',]

3. views.py

  • 댓글 생성은 POST 방식이 적합
  • 유저가 입력하는 댓글의 정보는 request.data에 저장되어있으므로 serializer의 키워드인자 data에 저장
  • 잘못된 요청이 들어왔을 때, 사용자에게 적절한 응답을 보내기 위해 .is_valid() 의 인자에 raise_exception=True 입력
  • 어떠한 music에 대한 대한 댓글인지를 알려줘야함. serializer의 기능으로 save 메서드를 사용가능하며, 이 메서드의 인자로, Comment 모델의 music_id 필드의 값으로 variable routing으로 들어오는 music_id를 입력해주자. 이를 통해 들어온 데이터를 DB에 바로 저장할 수 있음.
  • POST 방식으로 요청을 보낼 경우, form 태그를 사용해야하나, template은 따로 구현하지 않았으므로 POSTMAN 을 활용하자.
from .models import Comment
from .serializers import CommentSerializer

@api_view(['POST'])
def comment_create(request, music_id):
    serializer = CommentSerializer(data=request.data)
    if serializer.is_valid(raise_exception=True):
        serializer.save(music_id=music_id)
        return Response(serializer.data)

Comment 수정/삭제 API 구현

1. urls.py

path('musics/<int:music_id>/comments/<int:comment_id>/', views.comment_update_and_delete),

2. views.py

  • HTTP Methods에 따라 분기문을 이용해서 수정 & 삭제 구분하여 구현
    • PUT : 데이터 수정할 때 사용
    • DELETE : 데이터 삭제 할 때 사용
  • 어떠한 댓글인지에 대한 정보를 get_object_or_404 를 통해 갖고 왔기 때문에, serializer.save() 의 인자에 따로 추가적인 값을 넣을 필요는 없음.
from .models import Comment
form .serializers import CommentSerializer

@api_view(['PUT','DELETE'])
def comment_update_and_delete(request, music_id, comment_id):
    comment = get_object_or_404(Comment, id=comment_id)
    if request.method == 'PUT':
        serializer = CommentSerializer(data=request.data, instance=comment)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response({'message':'Comment has been updated!'})
    else:
        comment.delete()
    	return Response({'message':'Comment has been deleted!'})

Django 27 - 팔로우 기능, 유저모델(AbstractUser) 상속 (2)

|

팔로우 기능, 유저모델(AbstractUser) 상속 (2)

accounts/views.py - follow 함수 작성

urlpattern을 통해 people.html (개인페이지)에서 팔로우를 누를 경우, follow 함수가 실행되게 코드 작성. 이때, 팔로우를 누르는 사람은 로그인한 유저이며, people은 follow를 하려는 유저의 정보가 담긴 User의 인스턴스 객체임.

  • people.followers은 people을 팔로우 하고 있는 팔로워들이 저장되어있는 리스트임.
  • 조건문을 적용하여, 동일한 urlpattern / a태그에 대해 팔로우 & 언팔로우 가능을 구현할 수 있음. 만약 request.user (로그인한 유저)가
    • 리스트에 있는 경우 리스트에 제거 => 언팔로우 기능
    • 리스트에 없는 경우 리스트에 추가 => 팔로우 기능
def follow(request, user_id):
    people = get_object_or_404(get_user_model(), id=user_id)
    if request.user in people.followers.all():
        # people을 unfollow 하기
        people.followers.remove(request.user)
    else:
        # 1. people을 follow 하기
        people.followers.add(request.user)
    
    return redirect('accounts:people', people.username)

people.html

  • 개인 페이지(people.html)에 팔로우/언팔로우 기능을 하는 follow 함수를 연결시키는 링크를 연결
    • 지금 로그인된 유저와 people 페이지의 people와 같지 않은 경우에만 해당 링크가 보이게 설정
    • 리스트의 길이를 출력하면 팔로워/팔로잉의 수를 뽑아낼 수 있음.
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block container %}

<div class="container">
    <div class="row">
        <div class="col-3">
            <h1> 
            {% if people.profile.image %}
            <img src="{{ people.profile.image.url }}" width= 70, 
                 alt="{{ people.profile.image}}">
            {% endif %}
            {{ people.username }}
            </h1>
        </div>
        <div class="col-9">
            <div>
                <strong>{{ people.profile.nickname }}</strong>
                {{ people.followers.all }}
                {% if user != people %}
                    {% if user in people.followers.all %}
                    <a href="{% url 'accounts:follow' people.id %}">UnFollow</a>
                    {% else %}
                    <a href="{% url 'accounts:follow' people.id %}">Follow</a>
                    {% endif %}
                {% endif %}
            </div>
            <div>
                <strong> Followers: </strong> {{ people.followers.count }}
                <strong> Followings: </strong> {{ people.followings.count }}

리스트 페이지에 팔로우한 사람에 대한 게시글만 보이게 하기

  • request.user (로그인된 유저)가 팔로우한 사람(request.user.followings)들의 전체 리스트를 뽑아와 followings 에 저장
  • user 필드를 기준으로 filter 조건을 적용하려하나, user 는 필드에는 단 하나의 유저정보(유저의 id값)가 담겨있으므로, 필드값에 followings 을 넣는 것은 부적합하다 (followings에는 여러 유저 id값이 리스트의 형태로 담겨있음)
  • 이때, user__in 을 사용하여, 다수의 유저정보를 토대로 filter를 사용하면 됨.
@login_required
def list(request):
    #posts = Post.objects.order_by('-id').all()
    
    # 1. 내가 follow하고 있는 사람들의 리스트
    followings = request.user.followings.all()
    # 2. follow하고 있는 사람들이 작성한 Posts만 뽑아옴.
    posts = Post.objects.filter(user__in=followings).order_by('id')
    comment_form = CommentForm()
    
    return render(request, 'posts/list.html', {'posts':posts, 'comment_form':comment_form})

리스트 페이지에 본인글 포함하기 (1)

이 경우, followings 변수(본인이 팔로우하고 있는 유저정보가 담긴 리스트)에 나 자신도 넣어야함. 이 리스트는 쿼리셋이라는 데이터 타입인데, 여기에서는 데이터 수정을 하는 것이 어려움. 따라서 데이터 타입을 수정할 필요가 있음

  • values_list : 쿼리셋을 튜플의 형태로 변경시킴. 인자로 넣는 필드명 순서에 맞게 튜플에 저장이 됨

    • 하나의 필드만 넣을 경우, flat 인자를 입력할 수 있음. flat=True 인 경우, 결과값을 리스트의 형태처럼 single values로 반환함
    >>> Entry.objects.values_list('id', 'headline')
    <QuerySet [(1, 'First entry'), ...]>
    >>> Entry.objects.values_list('id').order_by('id')
    <QuerySet[(1,), (2,), (3,), ...]>
    >>> Entry.objects.values_list('id', flat=True).order_by('id')
    <QuerySet [1, 2, 3, ...]>
    
  • 공식문서 참조

  • request.user (로그인된 유저)가 팔로우 하는 유저의 정보들을 values_list를 이용. 이때, id 하나를 단일 필드로 입력하고, flat=True 값을 입력하여 로그인된 유저가 팔로우하는 유저의 id값을 리스트의 형태로 나타낸 후, followings 라는 변수에 저장

  • 이후, 변수에 본인의 유저 id 값 또한 append를 사용하여 저장.

@login_required
def list(request):
    
    followings = request.user.followings.values_list('id', flat=True)
    followings.append(request.user.id)
    
    posts = Post.objects.filter(user__in=followings).order_by('id')
    comment_form = CommentForm()
    
    return render(request, 'posts/list.html',
                  {'posts':posts,'comment_form':comment_form})

위의 방법이 권장되나, 게시글 목록을 보여주는 함수의 이름을 list로 정의하는 바람에 values_list를 사용하지 못하는 상황이 발생함. (values_list의 list를 함수명의 list로 인식해버림). 따라서, 대안으로 아래의 방법을 사용할 수 있음.

리스트 페이지에 본인글 포함하기 (2)

  • itertools.chain() 을 사용하여 여러개의 쿼리셋을 하나의 리스트로 합칠 수 있음.
    • chain의 인자에는 iterable가능한 데이터 타입이 들어가야함. request.user은 iterable 하지않으므로 리스트의 형태로 타입을 변경하기 위해 []로 묶어줌.
  • 사용을 위해 import
from itertools import chain

@login_required
def list(request):
    
    followings = request.user.followings.all()
    followings = chain(followings, [request.user])
    
    posts = Post.objects.filter(user__in=followings).order_by('-id')
    comment_form = CommentForm()
    
    return render(request, 'posts/list.html', 
                  {'posts':posts, 'comment_form': comment_form})

전체 리스트 페이지 구현

views.py 내 list 함수를 통해 본인이 팔로우한 사람들의 게시글 + 자신의 게시글 목록을 보여주는 페이지를 구현하였음. 팔로우를 하지 않은 사람의 경우, id값을 알지 못하는 이상 팔로우를 할 수 없으므로, 팔로우 유무와 관계 없이 모든 글이 뜨는 또다른 리스트 페이지를 작성해보자.

  • 기본 MTV 패턴 구현
  • explore 함수가 실행되는 링크를 base.html 삽입
  • html은 list.html을 그대로 활용
#views.py
def explore(request):
    posts = Post.objects.order_by('id').all()
    comment_form = CommentForm()
    return render(request, 'posts/list.html', 
                  {'posts':posts, 'comment_form':comment_form})

#urls.py
urlpatterns = [
    path('explore/', views.explore, name='explore'),
]
  • base.html
<a href="{% url 'posts:explore' %}">Explore</a>