2025/11/10 Django - 4

김기훈·2025년 11월 10일

TIL

목록 보기
51/191

오늘 학습 내용

[블로그] 상세페이지 만들기

블로그에 작성자 컬럼 만들기 및 오류 해결법

  • author = models.ForeignKey(User, on_delete=models.CASCADE)
    • models.CASCADE => 같이 삭제
    • models.PROTECT => 삭제가 불가능함
      • 유저를 삭제하려고할때 블로그가 있으면 유저 삭제가 불가능
    • models.SET_NULL => null값을 넣습니다.
      • 유저 삭제시 블로그의 author가 null이 됨, 이 때 null=True 옵션도 함께 설정 필요
  • User
      1. from django.contrib.auth.models import User
      1. from django.contrib.auth import get_user_model
    • 2-1. User = get_user_model()
      • 항상 django.contrib.auth.models 여기에 있는 User을 사용하지 않기 때문에
      • 2번을 사용하면 User을 새롭게 만들어야 할 경우 연결된 User이 어떤것인지 파악 후 가져옴
        • 즉, 장고에 설정되있는 유저를 찾아서 가져오는 함수

  • 모델의 수정사항이 발생하면, python manage.py makemigrations
  • null=True / degault 둘 중 한개가 필요한데 없어서 오류 발생
    • 해결방법
        1. 1회성 디폴트 값을 기입 / 2. 추후 수동 처리
      • 현재 실습 상황: 1번을 선택하고 디폴트 갑승로 1을 기입
  • 즉, User테이블을 만들때 만들지 않았던 컬럼을 만들고 이 컬럼이 null이 불가능 할 경우
    • 기본값을 임의로 넣어주는 기능


Base.html로 템플릿 extends 시키기

  • Template Engine extends
    • 전체페이지, 네비게이션 같은 공통된 템플릿을 공유
    • {% block content %}{% endblock %}
      • 공통적으로 갖는 상단 네비바를 제외하고 자유롭게 변형할 수 있도록 설정


블로그 Form

  • Django Forms의 기능
      1. HTML에 input 그려주기
      1. validation (검증)
      1. 저장 및 업데이트
from django import forms
from blog.models import Blog

class BlogForm(forms.ModelForm):
    class Meta:
        model = Blog
        fields = ('title', 'content', )
  • class BlogForm(forms.ModelForm):
    • django의 forms를 가져와서 그 forms안에 있는 ModelForm을 상속받음
  • fields
    • 어떤 컬럼을 forms에 적용할지 지정
      • 전체 적용: __all__ / 특정 컬럼: list , dic

코드 설명

from blog.forms import BlogForm

def blog_create(request):

    form = BlogForm(request.POST or None)
    if form.is_valid():
        blog = form.save(commit=False) 
        blog.author = request.user 
        blog.save()
        return redirect(reverse('blog_detail', kwargs={'pk': blog.pk}))

    context = {'form': form}
    return render(request, 'blog_create.html', context)
  • form = BlogForm(request.POST or None)
    • BlogForm: Django의 ModelForm을 기반으로 한 폼 클래스
    • 사용자가 POST 요청(글 작성 폼 제출)을 하면 request.POST 데이터를 이용해 폼을 채움
    • GET 요청(페이지 처음 접속)은 request.POST가 없으므로 None이 들어가 빈 폼이 표시
  • if form.is_valid():
    • 사용자가 입력한 데이터가 폼의 유효성 검사(예: 빈칸, 글자 수 제한 등)를 통과했는지 확인
    • True면 아래 저장 로직이 실행, 아니면 마지막의 return render(...)로 다시 폼을 보여줌
  • blog = form.save(commit=False)
    • 폼의 데이터를 기반으로 Blog 모델 인스턴스를 만들지만 DB에는 아직 저장하지 않음을 의미
    • commit=False 덕분에 DB 저장 전에 추가로 author를 지정할 수 있게 됨
  • blog.author = request.user
    • 현재 로그인한 사용자를 author 필드에 연결
    • 즉, 새로 작성된 글의 작성자를 현재 로그인한 사용자로 지정
  • blog.save()
    • 실제로 DB에 저장, 이제 새 블로그 글이 생성되고 blog.pk(기본키, ID)가 생김
  • return redirect(reverse('blog_detail', kwargs={'pk': blog.pk}))
    • 저장 후, 새로 작성된 글의 상세 페이지로 리다이렉트
    • reverse('blog_detail', kwargs={'pk': blog.pk})
      • 'blog_detail'이라는 URL 패턴에 pk 값을 넣어서 URL 문자열을 만들어 줌(ex. /blog/5/)
context = {'form': form}
return render(request, 'blog_create.html', context)
  • 폼이 유효하지 않거나 처음 접속한 경우,
    • blog_create.html 템플릿을 렌더링해서 사용자에게 폼을 보여줌
  • context에 form을 담아 템플릿에서 {{ form.as_p }}처럼 사용 가능

Form 2

로그인 하지 않은 유저가 작성 할 경우 로그인 페이지로 보내버리기

  • 똑같은 기능을 데코레이터로 이용해보기(@login_required())
    • 로그인 페이지를 지정해주지 않아도 자동으로 지정됨
      • settings의 LOGIN_URL = '/accounts/login/'
        • http://127.0.0.1:8000/accounts/login/?next=/create/
        • 다음페이지가 어딘지도 나옴 next=/create/

로그인 해도 next뒤에 있는 페이지로 안가짐

# member/views.py

def login(request):
    form = AuthenticationForm(request, request.POST or None)
    if form.is_valid():
        django_login(request, form.get_user())

        next = request.GET.get('next')
        if next:
            return redirect(next)

        return redirect(reverse('blog_list'))

    else:
        form = AuthenticationForm(request)

    context = {
        'form': form
    }

    return render(request, 'registration/login.html', context)
  • next = request.GET.get('next')
    • request.GET['next'] 도 가능하지만 이것 없으면 오류발생
    • http://127.0.0.1:8000/login/?next=/create/ | '?' 뒤에있는 next를 받는 것

최근 글이 가장 위로 올라오도록 코드를 수정

  • -created_at : DESC / created_at : ASC

블로그 목록 페이지에서 바로 글을 작성 연결


블로그 수정페이지 만들기

  • 수정버튼은 로그인 유저와 글을 작성한 유저가 동일할 때만 보여야 함
    • 작성자와 로그인한 유저가 동일해야 수정 버튼이 활성화

블로그 상세페이지에도 수정 버튼을 추가

  • if request.user == blog.author
    • 이 조건 없으면 아무한테나 다 수정버튼이 보이게 됨
  • instance=blog
    • 블로그 인스턴스를 넣어주면 폼의 항목에 맞게 타이틀,컨텐츠 등등 구분이 자동으로 들어감

블로그 페이지네이션

  • 이미 있던 글을 여러번 반복해서 만듬

10개씩 나누기

  • request.GET: 쿼리스트링을 가져옴
  • http://127.0.0.1:8000/?page=2 와 같이 물음표 뒤의 쿼리문으로 페이지를 이동
    • page=0은 마지막 페이지로 이동

페이지 하단에 페이지번호 링크를 추가하는 코드

특정 페이지로 이동하는 코드를 추가

  • page_object.number = 현재 페이지

    1. 각 페이지 번호에 대한 링크를 출력, 현재 페이지는 (현재페이지)로 표시
    • for i in page_object.paginator.page_range
      • 0에서 쵀대페이지 까지 range에 들어가 있음
    • elif i > page_object.number|add:-3 and i < page_object.number|add:3
      • 현재 페이지 -3 보다 크고 현재 페이지 +3 보다 큰
        • ex. 현재 페이지: 2 => -1 ~ 5
    1. 현재 페이지 번호 근처의 페이지 번호 링크를 출력(현재 페이지 -2 > 1보다 크면)
    • ... 누르면 현재페이지 + 3 페이지로 가짐
    • <a href="?page={{ page_object.number|add:-3 }}">&hellip;</a>
    1. 마지막 페이지로 이동하는 링크와 현재 페이지 기준으로 앞뒤의 ... 표시를 추가
    • (최대페이지 > 현재페이지 + 2)
    • <a href="?page={{ page_object.number|add:3 }}">&hellip;</a>

  • if page_object.has_previous
    • 이전 페이지가 있는 경우, 첫 번째 페이지와 이전 페이지로 이동할 수 있는 링크를 보여줌
  • if page_object.has_next
    • 다음 페이지가 있는 경우, 다음 페이지와 마지막 페이지로 이동할 수 있는 링크를 보여줌

블로그 검색

    q = request.GET.get('q')
	  if q:
		    blogs = blogs.filter(
		        Q(title__icontains=q) |
		        Q(content__icontains=q)
		    )
  • 제목과 본문 모두 검색 대상으로 설정

  • 다른 페이지 번호를 눌렀을 때 전체 블로그 글이 보여지는 부분을 수정


블로그 삭제하기

# 1
if request.method == 'POST':
        raise Http404
# 2        
@require_http_methods(['POST'])
  • @require_http_methods(['POST'])
    • 특정 요청만 허락하는 데코레이터. 삭제나 수정은 POST 요청으로 받아야함
    • 삭제 요청은 꼭 POST요청으로 받아야 함 / GET는 안됨
      • POST요청 = csrf_token 필요

어려운 내용

  • 페이지 네이션
  • 검색

새로 배운 내용

  • django에서 post를 보내기 위해서는 csrf_token 꼭 필요
  • 같은 의미의 코드지만 단순화

오늘 발생한 문제(발생 했다면)

  • 문제: NOT NULL constraint failed: blog_blog.author_id
  • 원인: blog.author 필드가 null이면 안 되는데 null이 들어가서 DB 저장에 실패했다는 뜻
    • 이유: blog = form.save() (Blog 모델의 author 필드가 NOT NULL)
      • commit=False 없이 바로 저장하고 있음 → 이 경우 BlogForm에 포함된 필드만 저장
      • author는 form에 포함되어 있지 않음
        • 즉, author_id=None 상태가 되어 DB에 저장하려다가 에러
blog = form.save(commit=False) 
blog.author = request.user 
blog.save()
  • 해결
      1. form으로 객체는 만듬 / 2. DB에 저장하기 전에(author 지정 전) 잠시 멈추고
      1. blog.author = request.user 로 author 값을 채운 뒤 / 4. blog.save() 로 저장
      • author_id=None → DB 저장 에러가 안 생김
      • 즉, commit=False 덕분에 DB저장을 미루고 author 값을 넣고 save() 했기 때문

문제 2. 번호를 누르는 페이지네이션은 잘 되는데 다음 이전 버튼이 안됨

  • 문제 3. 검색창까지는 잘 나오지만 검색이 안됨
  • 원인: blogs = blogs.filter 를 설정 안했음
profile
안녕하세요.

0개의 댓글