API
프로젝트 세팅
$ mkdir instagram-api
$ cd instagram-api
$ pyenv virtualenv 3.5.2 instagram_api
$ pyenv local instagram_api
$ pip install django
$ django-admin startproject instagram_api
$ mv instagram_api django_app
$ cp ~/deploy_review/.gitignore .
$ pip freeze > requirements.txt
$ git init
$ py .
# Interpreter 설정
# Source Root 설정
# config로 이름 변경
.conf-secret/settings_common.json
{
"django": {
"secret_key": "..."
}
}
.conf-secret/settings_local.json
{
"django": {
"allowed_hosts": [
"*"
]
}
}
settings.py
import json
# DEBUG = True
DEBUG = os.environ.get('MODE') == 'DEBUG'
ROOT_DIR = os.path.dirname(BASE_DIR)
CONF_DIR = os.path.join(ROOT_DIR, '.conf-secret')
CONFIG_FILE_COMMON = os.path.join(CONF_DIR, 'settings_common.json')
CONFIG_FILE = os.path.join(CONF_DIR, 'settings_local.json' if DEBUG else 'settings_deploy.json')
config_common = json.loads(open(CONFIG_FILE_COMMON).read())
config = json.loads(open(CONFIG_FILE).read())
for key, key_dict in config_common.items():
if not config.get(key):
config[key] = {}
for inner_key, inner_key_dict in key_dict.items():
config[key][inner_key] = inner_key_dict
TEMPLATES_DIR = os.path.join(BASE_DIR, 'templates')
SECRET_KEY = config['django']['secret_key']
ALLOWED_HOSTS = config['django']['allowed_hosts']
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
TEMPLATES_DIR,
],
'APP_DIRS': True,
'OPTIONS': {
# ...
},
},
]
LANGUAGE_CODE = 'ko-kr'
TIME_ZONE = 'Asia/Seoul'
$ MODE='DEBUG' ./manage.py runserver
유저 모델 생성
$ MODE='DEBUG' ./manage.py startapp member
member/models.py
from django.contrib.auth.models import AbstractUser
class MyUser(AbstractUser):
pass
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'
$ MODE='DEBUG' ./manage.py makemigrations
$ MODE='DEBUG' ./manage.py migrate
Post app 추가
1. 여러개 사진 올릴 수 있게 설게
- class Post
- class PostPhoto
$ MODE='DEBUG' ./manage.py startapp post
post/models.py
from django.conf import settings
from django.db import models
class Post(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL)
created_date = models.DateTimeField(auto_now_add=True)
class PostPhoto(models.Model):
post = models.ForeignKey(Post)
photo = models.ImageField(upload_to='post')
settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'member',
'post',
]
$ pip install pillow
$ MODE='DEBUG' ./manage.py makemigrations
$ MODE='DEBUG' ./manage.py migrate
order_with_respect_to 적용
class PostPhoto(models.Model):
post = models.ForeignKey(Post)
photo = models.ImageField(upload_to='post')
class Meta:
order_with_respect_to = 'post'
postgresql로 변경
.conf-secret/settings_local.json
"db": {
"engine": "django.db.backends.postgresql_psycopg2",
"name": "instagram_api",
"user": "hm",
"password": "...",
"host": "localhost",
"port": "5432"
}
데이터 베이스 생성
$ createdb instagram_api owner=hm
settings.py
DATABASES = {
'default': {
'ENGINE': config['db']['engine'],
'NAME': config['db']['name'],
'USER': config['db']['user'],
'PASSWORD': config['db']['password'],
'HOST': config['db']['host'],
'PORT': config['db']['port']
}
}
$ pip install psycopg2
$ MODE='DEBUG' ./manage.py makemigrations
$ MODE='DEBUG' ./manage.py migrate
확인을 위해 admin 등록
post/admin.py
from django.contrib import admin
from post.models import Post, PostPhoto
admin.site.register(Post)
admin.site.register(PostPhoto)
$ MODE='DEBUG' ./manage.py createsuperuser
$ MODE='DEBUG' ./manage.py runserver
shell script 작성
#!/bin/bash
while getopts "d" opt
do
case $opt in
d) mode='DEBUG' ;;
esac
done
manageCommand="MODE='$mode' ./manage.py ${@:OPTIND}"
echo $manageCommand
eval $manageCommand
$ chmod 755 ~/.scripts/manage
$ manage -d runserver
post_create API 작성
1. request.POST로 전달된 author_id를 받아 새 post를 생성
- 이후 생성된 post의 id 값을 Httpresponse로 반환
2. 받은 author_id에 해당하는 MyUser 객체를 가져옴
- 실패시 예외처리로 주어진 author_id에 해당하는 User는 없음을 리턴
3. url 연결
post/views.py
def post_create(request):
"""
request.POST로 전달된 author_id를 받아 새 post를 생성
이후 생성된 post의 id 값을 Httpresponse로 반환
받은 author_id에 해당하는 MyUser 객체를 가져옴
실패시 예외처리로 주어진 author_id에 해당하는 User는 없음을 리턴
"""
if request.method == 'POST':
try:
author_id = request.POST['author_id']
author = User.objects.get(id=author_id)
except KeyError:
return HttpResponse('key "author_id" is required field')
except User.DoesNotExist:
return HttpResponse('author_id {} is not exist'.format(
request.POST['author_id']
))
post = Post.objects.create(author=author)
return HttpResponse('{}'.format(post.pk))
else:
return HttpResponse('Post create view')
urls.py
from post import urls as post_urls
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^post/', include(post_urls)),
]
post/urls.py
app_name = 'post'
urlpatterns = [
url(r'^create/$', views.post_create, name='create'),
]
Postman
- Collections 생성
- Instagram API 이름으로 생성
127.0.0.1:8000/post/post-create
- Save
- Send
- Get 응답 확인 : Post create view
- Post 응답 확인 : CSRF 검증에 실패했습니다. 요청을 중단하였습니다
API로 통신하면 csrf를 사용하기 어려우므로 다음과 같이 처리한다. (크게 신경쓰지 않아도 된다.)
@csrf_exempt
def post_create(request):
그런 후 다시 실행하면 key "author_id" is required field
가 출력된다. body에 form-data
author_id를 넣어다시 요청한다.
post_photo_add API 추가
- post_id, photo를 받아서
- PostPhoto 객체를 생성
- 이후 post_id 와 post에 연결된 photo 들의 id 값을 리턴
post/views.py
@csrf_exempt
def post_photo_add(request):
if request.method == 'POST':
try:
post_id = request.POST['post_id']
photo = request.FILES['photo']
post = Post.objects.get(id=post_id)
except KeyError:
return HttpResponse('post_id and photo is required field')
except Post.DoesNotExist:
return HttpResponse('post_id {} is not exist'.format(
request.POST['post_id']
))
PostPhoto.objects.create(
post=post,
photo=photo
)
return HttpResponse('Post: {}, PhotoList: {}'.format(
post.id,
[photo.id for photo in post.photo_set.all()]
))
else:
return HttpResponse('Post photo add view')
post/urls.py
from django.conf.urls import url
from post import views
app_name = 'post'
urlpatterns = [
url(r'^post/create/$', views.post_create, name='create'),
url(r'^post/photo/add/$', views.post_photo_add, name='photo_add')
]
Media 파일 처리
settings.py
# Media path
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
TEMPLATES_DIR,
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
# MEDIA_URL 변수 사용
'django.template.context_processors.media',
],
},
},
]
Json 응답으로 처리하기
JsonResponse를 이용해서 Post.objects.all()에 해당하는 객체 리스트를 리턴
post/views.py
def post_list(request):
"""
JsonResponse를 이용해서 Post.objects.all()에 해당하는 객체 리스트를 리턴
"""
# post_list = Post.objects.all()
# 이 방법이 더 효율적임
post_list = Post.objects.select_related('author')
post_dict_list = []
# 전체 Post를 loop
for post in post_list:
cur_post_dict = {
'pk': post.pk,
'photo_list': [],
'created_date': post.created_date,
'author': {
'pk': post.author.pk,
'username': post.author.username,
}
}
photo_list = post.postphoto_set.all()
for post_photo in photo_list:
photo_dict = {
'pk': post_photo.pk,
'photo': post_photo.photo.url,
}
cur_post_dict['photo_list'].append(photo_dict)
post_dict_list.append(cur_post_dict)
context = {
'post_list': post_dict_list
}
return JsonResponse(data=context)
모델과 뷰 분리
post/views.py
def post_list(request):
context = {
# 'post_list': post_dict_list
'post_list': [post.to_dict() for post in Post.objects.select_related()]
}
return JsonResponse(data=context)
post/models.py
from django.conf import settings
from django.db import models
class Post(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL)
created_date = models.DateTimeField(auto_now_add=True)
def to_dict(self):
ret = {
'pk': self.pk,
'created_date': self.created_date,
'author': self.author.to_dict(),
'photo_list': [photo.to_dict() for photo in self.postphoto_set.all()]
}
return ret
class PostPhoto(models.Model):
post = models.ForeignKey(Post)
photo = models.ImageField(upload_to='post')
class Meta:
order_with_respect_to = 'post'
def to_dict(self):
ret = {
'pk': self.pk,
'photo': self.photo.url,
}
return ret
member/models.py
class MyUser(AbstractUser):
def to_dict(self):
ret = {
'pk': self.pk,
'username': self.username,
'first_name': self.first_name,
'last_name': self.last_name,
}
return ret
과제
- Django REST framework - 튜토리얼 실습
- 번역본