인스타그램 프로젝트

인스타그램의 주요 기능은 다음과 같다.

  1. 사진 등록
  2. 팔로우 하기
  3. 좋아요 하기
  4. 코멘트 남기기

프로젝트 설정

  • 프로젝트 명 : instagram
  • 어플리케이션 : member, post
1. 프로젝트로 사용할 폴더 생성
2. pyenv virtualenv 3.4.3 <환경명>
3. pyenv local <환경명>
4. pip install django
5. django-admin startproject <프로젝트명>
6. mv <프로젝트명> django_app
7. Pycharm interpreter세팅
8. django_app폴더를 Sources root로 설정

DB 모델 설계

인스타 그램 서비스를 구성하는 DB모델을 설계해본다

member app

- MyUser model
  - Attributes
    - username (유일한 값을 갖음)
    - last_name
    - first_name
    - nickname
    - email
    - date_joined
    - last_modified
    - following
  - Method
    - follow : 인자로 다른 Myuser 객체를 받아 해당 Myuser 객체를 팔로우 하도록 함
    - unfollow : 위와 반대로 동작
    - followers : 자신을 follow하고 있는 User목록, Property 처리
    - change_nickname

post app

- Post model
  - Attributes
    - author (ForeignKey, User)
    - photo
    - like_users (Intermediate모델로 PostLike를 사용)
    - content
    - created_date
  - Method
    - add_comment
    - like_count (property)
    - comment_count (property)

- Comment model
  - Attributes
    - author (ForeignKey, User)
    - post (ForeignKey, Post)
    - content
    - created_date

- PostLike model
  - Attributes
    - user (ForeignKey, User)
    - post (ForeignKey, Post)
    - created_date

member app

변수 선언

class MyUser(models.Model):
    username = models.CharField('유저네임', unique=True, max_length=30)
    last_name = models.CharField('성', max_length=20)
    first_name = models.CharField('이름', max_length=20)
    nickname = models.CharField('닉네임', max_length=20)
    email = models.EmailField('이메일', blank=True)
    date_joined = models.DateField('가입한 날짜', auto_now_add=True)
    last_modified = models.DateField('수정한 날짜', auto_now=True)

    def __str__(self):
        return self.username

유저를 자동으로 생성하는 메서드 추가

@staticmethod
def create_dummy_user(num):
    """
    num 개수만큼 User1 ~ User<num>까지 임의의 유저를 생성한다.
    :return: 생성된 유저 수
    """
    last_name_list = ['박', '이', '김', '서']
    first_name_list = ['민아', '혜리', '소진', '아영']
    nickname_list = ['빵', '리헬', '쏘지', '율곰']
    created_count = 0
    for i in range(num):
        try:
            MyUser.objects.create(
                username='User {}'.format(i + 1),
                last_name=random.choice(last_name_list),
                first_name=random.choice(first_name_list),
                nickname=random.choice(nickname_list)
            )
            created_count += 1
        except IntegrityError as e:
            print(e)
    return created_count
In [1]: from member.models import MyUser

In [2]: MyUser.create_dummy_user(10)
Out[2]: 10

In [3]: MyUser.objects.all()
Out[3]: <QuerySet [<MyUser: A user>, <MyUser: B user>, <MyUser: User 1>, <MyUser: User 2>, <MyUser: User 3>, <MyUser: User 4>, <MyUser: User 5>, <MyUser: User 6>, <MyUser: User 7>, <MyUser: User 8>, <MyUser: User 9>, <MyUser: User 10>]>

글로벌 변수 생성하여 유저를 할당하는 메서드 추가


@staticmethod
def assign_global_variables():
    # sys 모듈은 파이썬 인터프리터 관련 내장모듈
    #  __main__ 모듈을 module 변수에 할당
    module = sys.modules['__main__']
    users = MyUser.objects.filter(username__startswith='User')
    for index, user in enumerate(users):
        # 글로벌 변수를 할당하기 위해서 메인 모듈에서 하는 것
        # __main__ 모듈에 'u1, u2, u3, ...' 이름으로 각 MyUser객체를 할당
        setattr(module, 'u{}'.format(index + 1), user)


In [1]: from member.models import MyUser

In [2]: MyUser.assign_global_variables()

In [3]: globals()
Out[3]:
{'In': ['',
  'from member.models import MyUser',
  'MyUser.assign_global_variables()',
  'globals()'],
 'MyUser': member.models.MyUser,
 'Out': {},
 '_': '',
 '__': '',
# ...
 'u1': <MyUser: User 1>,
 'u10': <MyUser: User 10>,
 'u2': <MyUser: User 2>,
 'u3': <MyUser: User 3>,
 'u4': <MyUser: User 4>,
 'u5': <MyUser: User 5>,
 'u6': <MyUser: User 6>,
 'u7': <MyUser: User 7>,
 'u8': <MyUser: User 8>,
 'u9': <MyUser: User 9>}

follow, unfollow, followers, change_nickname 메서드 추가

def follow(self, user):
    self.following.add(user)

def unfollow(self, myuser):
    self.following.remove(user)

# follower는 수정할 수 없으므로,
# property를 이용해 읽기 전용으로 쓰는게 좋음
@property
def followers(self):
    return self.follower_set.all()

# 위에 3개 메서드는 Many to Many를 다루기 때문에 save가 필요 없지만,
# 이 메서드는 필요함
def change_nickname(self, new_nickname):
    self.nickname = new_nickname
    self.save()

post app

from django.db import models
from member.models import MyUser


class Post(models.Model):
    author = models.ForeignKey(MyUser)
    photo = models.ImageField(upload_to='post', blank=True)
    content = models.TextField(blank=True)
    created_date = models.DateTimeField(auto_now_add=True)
    like_users = models.ManyToManyField(
        MyUser,
        through='PostLike',
        related_name='like_post_set',
    )

    def __str__(self):
        return 'Post[{]]'.format(self.id)

    def toggle_like(self, user):
        # 중간자 모델을 사용하기 때문에 PostLike 중간자 모델 매니저를 사용
        # 핵심은 중간자 모델을 거쳐서 해야한다는 것이다.

        # PostLike 중간자 모델에서 인자로 전달된 Post, MyUser 객체를 가진 row를 조회
        # pl_list = PostLike.objects.filter(post=self, user=user)
        pl_list = self.postlike_set.filter(user=user)

        # 현재 인자로 전달된 user가 해당 Post(self)를 좋아여 한 적이 있는지 검사
        # 만약에 이미 좋아요를 했을 경우 해당 내역을 삭제
        # 아직 내역이 없을 경우 생성
        if pl_list.exists():
            pl_list.delete()
        else:
            PostLike.objects.create(post=self, user=user)

        # 파이썬 삼항연산자
        # [True일 경우 실행할 구문] if 조건문 else [False일 경우 실행할 구문]
        # return PostLike.objects.create(post=self, user=user) if not pl_list.exists() else pl_list.delete()

    def add_comment(self, user, content):
        # 자신에게 연결된 Comment 객체의 역참조 매니저(comment_set)로 부터
        # create 메서드를 이용해 Comment 객체를 생성
        return self.comment_set.create(
            user=user,
            content=content
        )

    @property
    def like_count(self):
        return self.like_users.count()

    @property
    def comment_count(self):
        return self.comment_set.count()


class Comment(models.Model):
    author = models.ForeignKey(MyUser)
    post = models.ForeignKey(Post)
    content = models.TextField()
    created_date = models.DateField(auto_now=True)

    def __str__(self):
        return 'Post[{}]\'s Comment[{}], Author[{}]'.format(
            self.post_id,
            self.id,
            self.author_id,
        )


class PostLike(models.Model):
    user = models.ForeignKey(MyUser)
    post = models.ForeignKey(Post)
    created_date = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = (
            ('user', 'post'),
        )

    def __str__(self):
        return 'Post[{}]\'s Like[{}]'.format(
            self.post_id,
            self.id,
        )

로그인 페이지 추가

1. def login 뷰를 생성
2. member app을 include를 이용해 'member' namespace를 지정
    instagram/urls.py와 member/ulrs.py 모두 사용
3. login 뷰는 member/login URL과 연결되도록 member/urls.py 구현
4. login 뷰에서는 member/login.html 파일을 렌더함
5. settings.py에 TEMPLATE_DIR 변수를 할당하고 추가
# instagram/urls.py
from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^member/', include('member.urls')),
]
# member/views.py
from django.http import HttpResponse
from django.shortcuts import render
from django.contrib.auth import authenticate, login


def login(request):
    """
    request.method == 'POST'일 때와 아닐 때의 동작을 구분
    POST일 때는 authenticate, login을 거치는 로직을 실행
    GET일 때는 member/login.html을 render하여 return 한다.
    """
    if request.method == 'POST':
        username = request.POST['username']
        password = request.POST['password']
        # authenticate 인자로 POST로 전달받은 username, password 사용
        user = authenticate(username=username, password=password)
        if user is not None:
            # 장고의 인증관리 시스템을 이용하여 세션을 관리해주기 위해 login() 함수 사용
            login(request, user)
            return HttpResponse('Login Success')
        else:
            return HttpResponse('Login Failed')
    # GET method 요청이 온 경우
    else:
        return render(request, 'member/login.html')

# member/ursl.py
from django.conf.urls import url
from . import views

# 네임스페이스 지정
app_name = 'member'
urlpatterns = [
    url(r'^login/$', views.login, name='login'),
]
<!-- templates/member/login.html -->
<h1>Login!</h1>

Request and Response object

HttpRequest objects

Attributes

  • HttpRequest.path : 어떤 URL을 통해 왔는지
  • HttpRequest.method : request.method == 'POST'
  • HttpRequest.content_type : 리소스 타입, request에 넘어온 데이터가 어떤 형식인지 (Json인지, 자바스크립트인지, …)
  • HttpRequest.GET
  • HttpRequest.POST

Writing views

Customizing error views

Django의 기본 오류 views는 대부분의 웹 응용 프로그램에서 충분하지만, 필요하면 쉽게 재정의 할 수 있습니다. 아래에 표시된 것처럼 URLconf에서 핸들러를 지정하기만 하면됩니다.

# 이렇게 지정해주면 해당 뷰로 이동
handler404 = 'mysite.views.my_custom_page_not_found_view'
  • 404 : 페이지를 찾을 수 없다.
  • 500 : 서버에 오류가 있다.
  • 400 : 잘못된 요청을 보냈다.

User authentication in Django

cookie-based user sessions (쿠키기반 사용자 세션)

  • 유저가 새로고침을 할 때, 연결의 연속성이 없는데,
  • 그것을 연결해주는 것이 세션
  • 세션을 유지하게 해주는 것이 쿠키
  • 유저는 고유한 쿠키 값을 가진다.
  • 쿠키를 가지고 서버는 세션을 유지한다.

Overview

인증(authentication)과 권한(authorization)

auth system

  • Users
  • Permissions
  • Groups
  • A configurable password hashing system (패스워드 해싱시스템) : 해시를 했을때, 다시 평문으로 복원되면 안된다.
  • Forms and view tools for logging in users, or restricting content
  • pluggable backend system : 3rd 로그인방식, id/pwd 방식과 같이 여러가지 방식을 갈아끼워 사용할 수 있다.

third-party package(로 제공)

  • Password strength checking
  • Throttling of login attempts : 로그인 시도 제한
  • Authentication against third-parties (OAuth, for example)

Installation

MIDDLEWARE : request와 response사이에서 일어나는 과정을 미들웨어라고 한다. 모든 request에 대한 로그를 남고기 싶을 때, 미들웨어를 사용하면 된다.

Using the Django authentication system

User objects

기본 사용자의 primary 속성

  • username : 장고와 장고관련 3rd party 툴에서도 기본적으로 사용하기 때문에 변경하지 않는 것이 좋다.
  • password
  • email
  • first_name
  • last_name

Creating users

In [2]: from django.contrib.auth.models import User
In [3]: user = User.objects.create_user('jhon', 'test@test.com', 'johnpassword')


In [4]: user.username
Out[4]: 'jhon'

In [5]: user.password
Out[5]: '*****hased_password*****'

# 아래와 같이 해도 비밀번호가 변경되지만,
# 해시한 값이랑 다르기 때문에 나중에 로그인 할 수 없다.
user.password = '1234'
user.save()

# 패스워드 변경을 위해 set_password 메서드를 사용한다.
user.set_password('123')

Authentication in Web requests

Django는 request 객체를 이용해 세션과 미들웨어를 사용하여 인증 시스템을 연결합니다.

과제