과제

  • 데이터베이스 설게 시, 중간자 모델을 사용하는게 좋다. 사용하지 않으면 이상하게 짜게 된다.
  • 날짜 형식이 pasing 되지 않을 때는 UTC로 변환해준다.
    • 일반적으로 서버는 무조건 UTC 표준시로 보내고, front-end에서 처리한다.
    • template - date

9. 북마크 기능 추가

  • 북마크한 동영상만 데이터베이스에 저장
  • 북마크 한 동영상만 볼 수 있는 페이지 추가
  • 검색 결과 북마크 추가/삭제 토글 버튼 추가

10. Migrate

이미 데이터베이스 테이블이 만들어진 상태이기 때문에 마이그레이션 시, 에러가 난다. 이럴때 이미 있는 데이터 베이스 모델을 보며 최대한 비슷하게 모델을 임시로 변경한다.

class MyUser(AbstractUser):
    bookmark_videos = models.ManyToManyField(
        'video.Video',
        blank=True,
        # through='BookmarkVideo',
    )

class BookmarkVideo(models.Model):
    myuser = models.ForeignKey(settings.AUTH_USER_MODEL)
    video = models.ForeignKey('video.Video')
    create_date = models.DateTimeField(auto_now_add=True)

    # migration 에러를 해결하기 위해 db_table 이름을 설정한 후
    # --fake 옵션을 주어 migration
    # $./manage.py migrate member --fake
    # db_table은 만들어져 있는 db_table 이름 필드이름과 동일하게 한다.
    class Meta:
         db_table = 'member_myuser_bookmark_videos'

11. Pagination

장고에서는 “Previous”/”Next” 링크를 제공하는 pagination 기능을 제공한다. 이 클래스는 django/core/paginator.py에 있다.

# 2는 한 페이지에 나타낼 개수
>>> p = Paginator(objects, 2)
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.shortcuts import render

def listing(request):
    contact_list = Contacts.objects.all()
    # 한 페이지에 25개 노출
    paginator = Paginator(contact_list, 25)

    page = request.GET.get('page')
    try:
        # 현재 페이지 number와 앞,뒤 페이지 정보를 가지고 있다.
        contacts = paginator.page(page)
    except PageNotAnInteger:
        # page가 integer가 아니거나 없을 경우에는 첫 번째 페이지로 이동
        contacts = paginator.page(1)
    except EmptyPage:
        # 범위를 넘는 큰 수를 입력 할 경우(예: 9999), 마지막 페이지로 이동
        contacts = paginator.page(paginator.num_pages)

    return render(request, 'list.html', {'contacts': contacts})

페이스북 프로젝트

프로젝트 환경 설정

$ mkdir facebook
$ cd facebook

$ pyenv virtualenv 3.4.3 facebook
$ pyenv local facebook

$ pip install django
$ pip freeze > requirements.txt

$ django-admin startproject facebook
$ mv facebook django_app

$ vi .gitignore
# .conf 추가
# .idea 추가

$ tree
.
├── django_app
│   ├── facebook
│   │   ├── __init__.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   └── manage.py
└── requirements.txt

프로젝트 구성

facebook <Repository directory>
├── django_app <Django project directory> # source directory 설정
│   ├── facebook <Settings directory>
│   ├── ft # python package
│   ├── static
│   └── templates
└── .conf
    └── settings_local.json

Settings.py

import json

ROOT_DIR = os.path.dirname(BASE_DIR)
CONF_DIR = os.path.join(ROOT_DIR, '.conf')

# Load config file
config = json.loads(open(os.path.join(CONF_DIR, 'settings_local.json')).read())

# Template path
TEMPLATE_DIR = os.path.join(BASE_DIR, 'templates')

# Static path
STATIC_URL = '/static/'
STATIC_DIR = os.path.join(BASE_DIR, 'static')
STATICFILES_DIRS = {
    STATIC_DIR,
}

# Media path
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

SECRET_KEY = config['django']['secret_key']

TEMPLATES = [
    {
        # ...
        'DIRS': [
            TEMPLATE_DIR
        ],
        'APP_DIRS': True,
        'OPTIONS': {
            # ...
        },
    },
]

LANGUAGE_CODE = 'ko-kr'
TIME_ZONE = 'Asia/Seoul'

Custom User Model

  1. member app 생성
  2. member/models.py 파일에 AbstractUser를 상속받는 MyUser를 구현
  3. 마이그레이션
  4. 세팅 파일에 설정 추가
# member/models.py
from django.contrib.auth.models import AbstractUser


class MyUser(AbstractUser):
    pass
# facebook/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'member',
]

AUTH_USER_MODEL = 'member.MyUser'

페이스북에 앱 등록

Facebook Developer 사이트에서 새 앱 추가한다. 추가한 후, 발급한 앱 ID앱 시크릿 코드는 API에 사용할 값 이다.

서버측에서 페이스북 OAuth 사용하기

facebook for developer

1. 로그인 기본 구조 구현

로그인 뷰 생성하기

  1. view 생성
  2. template 생성
  3. url 연결

2. 페이스북 로그인 창 띄우기

MySite에서 User가 페이스북 로그인을 클릭하면, MySite에서 페이스북으로 redirect

  • request : https://www.facebook.com/v2.8/dialog/oauth?client_id={app-id}&redirect_uri={redirect-uri}

페이스북은 User와 통신하여 로그인이 완료되면,

  • response : redirect_uri에 code 파라미터를 포함해 전달
def login_fbv(request):
    facebook_app_id = settings.config['facebook']['app_id']
    context = {
        'facebook_app_id': facebook_app_id
    }
    return render(request, 'member/login.html', context)


def login_facebook(request):
    pass
<!-- member/login.html -->
<div>
  <a href="https://www.facebook.com/v2.8/dialog/oauth?client_id=
  &redirect_uri=http://localhost:8000/member/login/facebook">페이스북으로 로그인</a>
</div>

링크를 클릭하면 다음과 같이 응답이 온 것을 확인 할 수 있다.

[23/Feb/2017 22:46:33] "GET /member/login/facebook?code=AQBSR1b3pztG2P1cstilFj8HtT6x-HVOA9SzMC3VN-4MjPGpP1QD3 ... vDJEy HTTP/1.1" 404 2611

이 때, 요청이 정상적으로 되지 않을 경우 Facebook Developer 사이트에서 설정을 해줘야한다. 모든 앱보기에서 해당 앱을 클릭 한 후 ‘설정 - 기본설정’에 앱 도메인, 플랫폼 추가 - 웹 사이트 - 사이트 URL 등록 후 다시 시도한다.

3. 사용자 Access Token 요청

code(사용자 정보)와 app_secret(앱 정보)를 보내 사용자 Access Token을 요청

  • request : `https://graph.facebook.com/v2.8/oauth/access_token?client_id={app-id}&redirect_uri={redirect-uri}&client_secret={app-secret}&code={code-parameter}
  • response : access_token, token_type, expires_in

def login_facebook(request):
    APP_ID = settings.config['facebook']['app_id']
    SECRET_CODE = settings.config['facebook']['secret_code']
    REDIRECT_URI = 'http://localhost:8000/member/login/facebook'

    if request.GET.get('code'):
        code = request.GET.get('code')

        # 전달받은 code 값을 이용해 access_token 요청
        url_request_access_token = 'https://graph.facebook.com/v2.8/oauth/access_token'
        params = {
            'client_id': APP_ID,
            'redirect_uri': REDIRECT_URI,
            'client_secret': SECRET_CODE,
            'code': code
        }
        r = requests.get(url_request_access_token, params=params)
        pprint(r.text)
        dict_access_token = r.json()
        USER_ACCESS_TOKEN = dict_access_token['access_token']
        print('USER_ACCESS_TOKEN : {}'.format(USER_ACCESS_TOKEN))
# pprint(r.text) 결과
'{"access_token":"EAAaZA35ZAAH...ZBSrXpluViQo4KIZD",
"token_type":"bearer","expires_in":5164067}'

# print('USER_ACCESS_TOKEN : {}'.format(USER_ACCESS_TOKEN)) 결과
USER_ACCESS_TOKEN : EAAaZA35ZAAHt4BAHKZBIZ...ZCPZAeJEZD

사용자 액세스 토큰은 단기 실행 토큰과 장기 실행 토큰의 두 양식으로 제공된다. 일반적으로 단기 실행 토큰의 사용 시간은 약 1~2시간인 반면, 장기 실행 토큰의 사용 시간은 약 60일이다.

4. 앱 Access Token 생성

앱 Access Token을 발급받지 않고 생성하는 방법으로 만든다.

  • access_token=app_id|app_secret
APP_ACCESS_TOKEN = '{app_id}|{secret_code}'.format(
        app_id=APP_ID,
        secret_code=SECRET_CODE
    )

5. Access Token 검사, User 정보 접근

User Access Token과 App Access Token을 전달해 검증한다.

  • request : graph.facebook.com/debug_token?input_token={token-to-inspect}&access_token={app-token-or-admin-token}
  • response : is_valid, user_id
# user_access_token, app_access_token 사용해 토큰 검증
        url_debug_token = 'https://graph.facebook.com/debug_token'
        params = {
            'input_token': USER_ACCESS_TOKEN,
            'access_token': APP_ACCESS_TOKEN,
        }
        r = requests.get(url_debug_token, params=params)
        dict_debug_token = r.json()
        pprint(dict_debug_token)
        USER_ID = dict_debug_token['data']['user_id']
        print('USER_ID : {}'.format(USER_ID))
# pprint(dict_debug_token) 결과
{'data': {'app_id': '1...8',
          'application': 'wps-oauth',
          'expires_at': 1493022658,
          'is_valid': True,
          'issued_at': 1487838658,
          'scopes': ['public_profile'],
          'user_id': '1...4'}}
# print('USER_ID : {}'.format(USER_ID)) 결과
USER_ID : 1...4

QnA

앱 Access Token 생성법이 2가지인데 이유는,

  • 사용자 측에서 요청하는 경우
  • 서버에서 직접 생성하는 경우 (파이프라인 사용하는 것)