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에 설정을 추가한다.
- 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 변경 가능
- Manager의 기본 QuerySet은 모든 객체(all objects)를 반환 (예:
# 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 사이트 커스텀
- 관리자 사이트는 유저 그룹별로 권한을 달리 할 수 있다.
- 하지만 내부에서 사용하길 추천한다.
- 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. 여러개 사진 올리기
# 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 뷰로 이동
- 폼에 choice 상자 사용하기
- ModelForm
- ModelForm을 사용하면, 폼에 작성한 필드를 그대로 view에 작성하지 않아도 된다.
- Validation on a ModelForm
- 검사도 폼 안에서 할 수 있다.
- Form and field validation
clean_<fieldname>()
메서드는 폼의 서브 클래스이다.
# 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. 가입한 유저로 로그인 시키기
유저 생성 후 로그인 메서드를 통해 로그인을 수행한다.
# 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. 유저 비밀번호 유효성 검사
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',
},
]
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. 로그아웃
# member/views.py
def logout_fbv(request):
logout(request)
return redirect('member:login')
과제
- TDD 책 : 1부