티스토리 뷰

반응형

0. 들어가면서

  class table
회원관리/로그인/로그아웃 User 클래스를 통해 관리한다 auth_user
게시글 Article 클래스를 통해 관리한다 articles_article

 

'auth_user' table

id(PK=primary key) username email password article
1 Jone dken@ine.com efqewf! 1, 2, 3(X)
2 Nem djf@namer.com djek 여러개들어가면 안된다... 즉 불가.
3 요트맨 dfienf@kdnd.com qidjfnk!d

 

'articles_article' table

id title content created_at update_at user
1 냉무 23213 ~ ~ 1
2 222 가나다라 ~ ~ 1
3 LG전자 배고프다 ~ ~ 3

위부분에서 user가 FK로 외래키이다. (=사람을 찾기 위한 열쇠를 저장한다고 생각하면 도니다.)

 

 

1 : N으로 우리가 구현 할 것은 2가지 이다.

1 : N = report : articles_article                  - 유저 하나가 여러개의 게시글을 작성가능

1 : N = articles_article : articles_comment    -  게시글 하나당 여러개의 댓글이 달린다.

여기서 우리는 유저와 게시글의 관계를 1:N으로 나타낼 것이다.

 

1:N 중에 N 부분의 삭제는 그냥 삭제를 하면 되지만, 1 부분에서의 삭제는 N이 갈 곳을 잃어버리기 때문에 다른 설정이 필요하다. 그래서 나온 것이 on_delete 이다. 

 

on_delete

CASCADE 해당 객체(reporter)가 삭제 되었을 때 참조하는 객체(article)도 모두 삭제. ex) 아이디 삭제 시 게시글 삭제
PROTECT 참고하는 객체(article)가 존재하면, reporter 삭제 금지. ex) 게시글 있으면 계정 삭제 불가
SET_NULL NULL 값으로 치환, NOT NULL 옵션이 있는 경우는 활용 불가
SET_DEFAULT 다폴트 값(article)을 참조하게끔 한다. ex) 계정 삭제 시 원래 있던 게시글이나 댓글에 미리 지정해둔 임의의 값들이 들어간다. 지워진 id 대신 ghast 같은 것들을 넣고 댓글이나 게시글들은 유지한다.

 

 

1. User와 Article 구현 (1:N)

기본적으로 CRUD와 사용자 인증 정보까지 전부 포함 된 상태라고 가정하자. 그 이후에 코드다. 이해를 위해 하나씩 차근차근 아래의 방식대로 따라 하면 된다.

 

0. models에서 데이터가 있는 경우에 class의 column 추가하기

column을 추가하고 migration하면, 기존에 있는 것에 생긴 빈 값을 어떻게 할 것인지 물어본다. 이 때 blank=True를 쓴다. 예를 들면

new_column1 = models.CharField(max_length=100, blank=True)

이렇게 적어주면 빈값으로 바로 적용된다. string으로 나중에 바꿔주면 된다.

 

1. models.py의 class Article에 가서 "Foreignkey"를 추가하자

 

from django.db import models

from django.conf import settings

 

# Create your models here.

class Article(models.Model):

    title = models.CharField(max_length=100)

    content = models.TextField()

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

 


원래의 코드에서

from django.conf import settings

import 부분을 추가하고

 

user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

이부분을 추가로 한거다.

  • settings.AUTH_USER_MODEL어떤 클래스와 연결되고 어느 app의 model인자에 대한 정보를 가지는지에 대한 정보를 적는다. 여기서만, get_user_model() 을 쓰지 않는다.
  • on_delete 삭제시 어떤 행동을 하는지 적는다.

 

2. models.py 에 적은 내용을 DB에 반영

터미널창) python manage.py makemigrations

python manage.py migrate

DB 들어가서 articles_article의 table을 보면 column에 user_id가 추가 됨을 확인하자.

 

문제발생) 글쓰기를 해보니 유저를 선택할 수 있게 추가가 되어있다. 어떤 코드를 고쳐야하나.....

 

3. forms.py

forms.py를 수정해야한다. 내가 필요한 feild만 직접 정의한다.

feilds = '__all__'

feilds = ['title', 'content']

로 바꾼다. 그러면 글쓰기 시 추가 유저를 선택하는 부분이 사라진다.

(관련부분은 form부분이 적혀있는 아래의 게시물을 참고하자.)

모델폼을 이해 후에 Meta데이터부분을 보면 된다.

https://han-py.tistory.com/87

 

[django] Form으로 사용자의 입력 받기+ModelForm

Django에서 Form을 언제 쓸까? 사용자가 정보를 적을 수 있게 from양식을 사용자에 준다. 그 후에 Form을 통해 사용자가 보낸 정보를 사용한다. 일단 예시를 보고 흐름을 간단히 이해를 해보자.     �

han-py.tistory.com

https://han-py.tistory.com/142

 

문제발생) 글쓰기를 눌러서 쓰고 제출을 누르면 오류가 뜬다. IntegrigyError가 뜨고 오류 내용에는 NOT NULL constraint failed articles.article.user_id 이런식으로 적혀있다. 쉽게 말하면 articles 표에 article 컬럼안에 있는 user_id가 없다는 말이다.

 

4. views.py의 create 수정

위와 같이 글쓰기 시 NOT NULL 제약 조건(표에 빈칸이 있는 경우에 발생하는 에러)이 걸렸다. 이런 경우는 create함수를 수정하여 저장 시 user를 같이 저장을 해야한다. 쉽게 말하면 article 오브젝트(article.user)에 로그인한 유저의 id값(request.user)을 저장해야한다.

 

기존의 create 함수 코드를 보자.

 

def create(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST)
        if form.is_valid():
            article = form.save()
            return redirect('articles:index')
    else:
        form = ArticleForm()
    context = {
    return render(request, 'articles/detail.html', context)

 

위부분에서 수정해야 할 부분은 article = form.save() 이다.

form.save()는 모델폼으로 한번에 저장을 한다. 그래서 설정 할 틈이 없다. 즉, save()를 작성하는 순간 바로 쿼리로 날라간다. 하지만, 현재 우리가 원하는 상황은 object 조작은 하는데 아직 쿼리를 날리지 않는 상황이다.

 

좀 더 쉽게 이야기해 보면  ModelForm의 save 매서드는 해당 연결된 class를 save한다. 위의 코드 같은 경우는 Article 클래스가 연결되어 있기 때문에 save를 호출한 순간에 SQL인 쿼리로 나가서 저장된다. 이 때 바로 저장을 하지 않도록 시간을 벌어서 다른 값을 추가 가능하게 하는 것이 commit=False이다.

 

commit=False의 역할

  • 해당 object는 반환한다.
  • SQL의 실제 실행은 하지 않게 한다.

수정된 create 함수

 

def create(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST)
        if form.is_valid():
            article = form.save(commit=False)
            article.user = request.user
            article.save()
            return redirect('articles:detail', article.pk)
    else:
        form = ArticleForm()
    context = {
    return render(request, 'articles/detail.html', context)

 

article = form.save(commit=False)

object를 받아서 (object는 리턴하는데 DB는 아직 저장을 안 함)

article.user = request.user

조작을 완료하고 (저장 안 된 상태로 받았으니 받아준다.)

article.save()

직접 저장되도록 쿼리를 호출한다. (save 호출)

 

 

 

create함수는 위와 같이 해주면 된다.

 

 

5, 글쓴이만 수정 삭제가 가능하게 수정하기

현재 누구나 게시글 수정과 삭제가 가능하다. 우리는 이제 글쓴이만 수정과 삭제가 가능하게 해보자. html 코드로 작성버튼을 수정하고 views.py의 코드를 수정하자.

 

개념

{{ article.user }} vs {{ article.user.username }}

html으로 보면 위의 출력 형태는 같다. 하지만 반드시 구별할 수 있어야한다.

{{ article.user }} : type는 클래스의 인스턴스다. 출력을 하면 username을 출력하게 끔만 만들어 놓은거다.

{{ article.user.username }} : 해당 인스턴스의 username으로 string 값이다.

 

detail.html에서 아래의 코드를 추가하자

{% article.user == request.user %}
   ## 수정 버튼
   ## 삭제 버튼
{% endif %}

article.user 는 게시글 유저를 나타낸다

request.user 는 로그인 유저를 나타낸다.

 

html을 위와같이 수정하면 버튼이 로그인 유저가 아니면 안 보인다. 그러나 이것만 하면 다른 사람이 url만 수정해도 수정 삭제가 된다. 따라서 views.py의 delete와 update의 코드도 수정이 필요하다. 아래와 같은 코드를 추가해 주자

 

if request.user == article.user:
    작성자만 가능하게 하된다.

else:
    return redirect('articles:index')

 

else부분은 사용자가 아닌 사람이 url로 '~/2/delete'로 접근을 하면 index 페이지로 넘어간다.

if request.user == article.user를 쓰면 @login_request를 달아주는 것이 좋다. request.user가 login 됐을 때만 활용을 할수 있기 때문이다. 로그인을 안 했을 시는 request.user가 익명의 사용자 객체가 되므로 혹시나 생길 문제를 사전에 방지 할 수 있다.

 

최종

@require_POST
@login_required
def delete(request, pk):
    article = get_object_or_404(Article, pk=pk)
    if request.user == article.user:
        article.delete()
    return redirect('articles:index')

 

 

def update(request, pk):
    article = get_object_or_404(Article, pk=pk)
    if request.user == article.user:
        if request.method == 'POST':
            form = ArticleForm(request.POST, instance=article)
            if form.is_valid():
                article = form.save(commit=False)
                article.user = request.user
                article.save()
                return redirect('articles:detail', article.pk)
        else:
            form = ArticleForm(instance=article)
        context = {
            'form': form
        }
        return render(request, 'articles/form.html', context)
    else:
        return redirect('articles:index')

 

{% block body %}
    <h1>{{ article.pk }}번 글</h1>
    <h2>{{ article.title }}</h2>
    <h3>글쓴이 : {{ article.user.username }}</h3>
    <p>생성 : {{ article.created_at }}</p>
    <p>수정 : {{ article.updated_at }}</p>
    <hr>
    <p>{{ article.content }}</p>
    <hr>
    {% if article.user == request.user  %}
    <form action="{% url 'articles:delete' article.pk %}" method="POST" class="d-inline">
        {% csrf_token %}
        <button class="btn btn-primary">삭제</button>
    </form>
    <a href="{% url 'articles:update' article.pk %}"><button class="btn btn-primary">수정</button></a>
    {% endif %}
{% endblock %}
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함