Tip

  • 마이그래이션시, fake 옵션

11. main page 연결하기

# instagram/views.py 생성 후 index 추가
from django.shortcuts import redirect


def index(request):
    return redirect('post:list')

# instagram/urls.py
from . import views

urlpatterns = [
    url(r'^$', views.index, name='index'),
]

12 . 포스트, 코멘트 삭제 기능 (과제)

def post_delete(request, post_id, db_delete=False):
    if request.method == 'POST':
        post = Post.objects.get(id=post_id)
        if post.author.id == request.user.id:
            if db_delete:
                post.delete()
            else:
                post.is_visible = False
                post.save()
        return redirect('post:list')
app_name = 'post'
urlpatterns = [
    url(r'^(?P<post_id>[0-9]+)/delete/$',
        views.post_delete, name='delete'),
]

13. 코드 분리

  • models 폴더 생성
    • init.py
    • comment.py
    • post.py
# post/views/post.py
# import * 했을 때, 가져올 것을 선언
__all__ = (
    'post_list',
    'post_detail',
    'post_like_toggle',
    'post_add',
)
# urls.py에 선언된 views로 이동해 메서드를 찾는데,
# 이 때, __init__으로 가서 메서드를 찾음
from .post import *
from .cooment import *

14 . user model 외래키 변경

User를 직접 참조하는 경우(예: 외래키), AUTH_USER_MODEL 설정이 다른 사용자 모델로 변경된 프로젝트에서 코드가 작동하지 않을 수 있다.

사용자 모델 참조

from django.conf import settings
from django.db import models

class Post(models.Model):
    author = models.ForeignKey(settings.AUTH_USER_MODEL)

15. 실제로 삭제하지 않고 게시물 보이지 않게만하기

# Post 모델에 필드 추가
# post/models/post.py
class Post(models.Model):
    # ...
    is_visible = models.BooleanField(default=True)
# post/views/post.py
def post_list(request):
    posts = Post.objects.filter(is_visible=True)
    context = {
        'posts': posts
    }
    return render(request, 'post/post_list.html', context)

def post_delete(request, post_id):
    if request.method == 'POST':
        post = Post.objects.get(id=post_id)
        if post.author.id == request.user.id:
            post.is_visible = False
            post.save()
        return redirect('post:list')

16. model manager 커스텀

유저에게 보여주는 리스트의 조건이 많아질 경우를 대비해 모델 manager에 설정을 추가한다.

Managers

  • Manager는 Django 모델에 데이터베이스 쿼리작업을 제공
  • Django 응용프로그램은 최소한 하나 이상의 Manager 존재
  • 기본적으로 Django는 모든 Django 모델 클래스에 objects라는 이름의 Manager를 추가

Custom Managers

커스텀 Manager를 사용하려는 두 가지 이유가 있습니다.

  • Mansager 메서드 추가
  • Manager가 반환하는 초기(initial) QuerySet을 수정
    • Manager의 기본 QuerySet은 모든 객체(all objects)를 반환 (예: Post.objects.all())
    • Manager.get_queryset() 메서드를 재정의하여 기본 QuerySet 변경 가능
# post/models/post.py
# 커스텀 Manager를 설정하는 방법에 두 가지 방식이 있다.

# 1. manager에 메서드 추가
class PostManager(models.Manager):
    def visible(self):
        return super().get_queryset().filter(is_visible=True)

# 2. get_queryset을 오버라이드
class PostUserVisibleManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(is_visible=True)

class Post(models.Model):
    # Default 모델 Manager 교체
    objects = PostManager()
    # Post.obejcts.all() : 모든 객체 반환
    # Post.objecst.visible() : 필터된 객체 반환

    # 커스텀 모델 매니저 추가
    visible = PostUserVisibleManager()
    # Post.visibles.all()
# 뷰에서 사용할 때
# post/views/post.py

def post_list(request):
    # posts = Post.objects.filter(is_visible=True)
    posts = Post.visible.all()
    context = {
        'posts': posts
    }
    return render(request, 'post/post_list.html', context)

17. Admin 사이트 커스텀

The Django admin site

  • 관리자 사이트는 유저 그룹별로 권한을 달리 할 수 있다.
  • 하지만 내부에서 사용하길 추천한다.
  • end user가 사용해서는 안된다.
  • 커스텀하여 사용할 수 있는 기능이 많지만, 너무 많은 기능을 커스텀하려면 새로 만드는 것이 좋다.
# instagram/settings.py
# admin 사이트를 이용하기 위한 필수 설정
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',

    # 정의한 모든 모델을 추적
    'django.contrib.contenttypes',
    'django.contrib.sessions',

    # 웹 어플리케이션에서 사용자 입력을 처리 후,
    # 사용자에게 일회성 알림 메시지(aka "플래시 메시지")를 표시
    'django.contrib.messages',
]

Customizing the AdminSite class

# post/admin.py
class PostAdmin(admin.ModelAdmin):
    list_display = ('id', 'author', 'is_visible')

admin.site.register(Post, PostAdmin)
admin.site.register(Comment)

어드민 페이지 추가된 필드를 확인할 수 있다.

18. 포스트의 content가 아니라 comment에 등록하게 변경

1. Post 모델에서 content 필드를 없애고 db migration
2. post_add view의 동작을 변경 (입력받은 content는 새 comment 객체를 생성하도록)

19. 여러개 사진 올리기

Uploading multiple files

# post/forms.py
class PostForm(forms.Form):
    content = forms.CharField(required=False)
    photo = forms.ImageField(
        widget=forms.ClearableFileInput(
            attrs={
                'multiple': True
            }
        )
    )
# post/views/post.py
def post_add(request):
    if request.method == 'POST':
        form = PostForm(request.POST, request.FILES)
        if form.is_valid():
            files = request.FILES.getlist('photo')

            for file in files:
                create_post_comment(file, comment_content)

            return redirect('post:list')

20. 회원가입

1. member/signup.html 파일 생성
2. SignupForm 클래스 구현
3. 해당 Form을 사용해서 signup.html 템플릿 구성
4. POST 요청을 받아 MyUser 객체를 생성 후 로그인
5. 로그인 완료되면 post_list 뷰로 이동
# member/forms.py
class SignupForm(forms.Form):
    username = forms.CharField(max_length=30)
    password1 = forms.CharField(widget=forms.PasswordInput)
    password2 = forms.CharField(widget=forms.PasswordInput)
    # ...

    def clean_username(self):
        username = self.cleaned_data['username']
        if MyUser.objects.filter(username=username).exists():
            raise forms.ValidationError('username already exists!')
        return username

21. 가입한 유저로 로그인 시키기

유저 생성 후 로그인 메서드를 통해 로그인을 수행한다.

How to log a user in

# member/views.py
def signup_fbv(request):
    if request.method == 'POST':
        form = SignupForm(request.POST)
        if form.is_valid():
            user = form.create_user()
            # authenticate() 하지 않아도 됨
            login(request, user)
            return redirect('post:list')

22. 유저 비밀번호 유효성 검사

Included validators

settings 파일에서 확인할 수 있다.

AUTH_USER_MODEL = 'member.MyUser'
AUTH_PASSWORD_VALIDATORS = [
    {
        # 다른 속성들과 동일한지 검사
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        # 비밀번호 최소 길이를 충족하는지 검사
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        # 흔한 암호목록을 사용하는지 검사
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        # 숫자로만 이루어졌는지 검사
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

Integrating validation

def clean_password2(self):
    password1 = self.cleaned_data['password1']
    password2 = self.cleaned_data['password2']

    validate_password(password1)
    if password1 != password2:
        raise forms.ValidationError('password1 and password2 not equal!')
    return password2

23. 로그아웃

How to log a user out

# member/views.py
def logout_fbv(request):
    logout(request)
    return redirect('member:login')

과제