작정하고 Django 35강 - Article 모델 생성 오류 수정

_·2023년 12월 28일

작정하고 Django 강의

목록 보기
34/44

Article model 생성

on_delete=models.SET_NULL : 회원이 탈퇴했을 때 그 게시글이 사라지지 않고 주인 없는 게시글이 되는 것으로 설정

related_name='article' : User 객체에서 Article 이라는 모델에 접근할 때 쓰는 이름이므로 article이라는 이름이 더 직관적 → user.article 이런식으로 접근

그 외, title, image, content, created_at 컬럼 정의

created_at 컬럼이 모두 null로 저장되는 문제점
[해결] : auto_created=Trueauto_now_add=True로 수정

models.py를 작성 후, 그 변경 사항을 마이그레이션 작업을 통해 DB에 반영

# articleapp/models.py

from django.contrib.auth.models import User
from django.db import models


# Create your models here.

class Article(models.Model):
    writer = models.ForeignKey(User, on_delete=models.SET_NULL, related_name='article', null=True)

    title = models.CharField(max_length=200, null=True)
    image = models.ImageField(upload_to='article/', null=False)
    content = models.TextField(null=True)

    created_at = models.DateTimeField(auto_now_add=True, null=True)
# 마이그레이션
python manage.py make migrations
python manage.py migrate

Article model 기반으로 하는 form 만들기

# articleapp/forms.py 작성
from django.forms import ModelForm
from articleapp.models import Article


class ArticleCreationForm(ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'image', 'content'] # writer 는 서버 내에서 설정해줄 것이므로 제외

Create, Detail

ArticleCreateView, ArticleDetailView, ArticleUpdateView, ArticleDeleteView 작성

from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from django.urls import reverse, reverse_lazy
from django.utils.decorators import method_decorator
from django.views.generic import CreateView, DetailView, UpdateView, DeleteView

from articleapp.decorators import article_ownership_required
from articleapp.forms import ArticleCreationForm
from articleapp.models import Article


# Create your views here.
@method_decorator(login_required, 'get')
@method_decorator(login_required, 'post')
class ArticleCreateView(CreateView):
    model = Article
    form_class = ArticleCreationForm
    template_name = 'articleapp/create.html'

    def form_valid(self, form):
        temp_article = form.save(commit=False)
        temp_article.writer = self.request.user
        temp_article.save()
        return super().form_valid(form)

    def get_success_url(self):
        return reverse('articleapp:detail', kwargs={'pk': self.object.pk})


class ArticleDetailView(DetailView):
    model = Article
    context_object_name = 'target_article'
    template_name = 'articleapp/detail.html'


@method_decorator(article_ownership_required, 'get')
@method_decorator(article_ownership_required, 'post')
class ArticleUpdateView(UpdateView):
    model = Article
    context_object_name = 'target_article'
    form_class = ArticleCreationForm
    template_name = 'articleapp/update.html'

    def get_success_url(self):
        return reverse('articleapp:detail', kwargs={'pk': self.object.pk})


@method_decorator(article_ownership_required, 'get')
@method_decorator(article_ownership_required, 'post')
class ArticleDeleteView(DeleteView):
    model = Article
    context_object_name = 'target_article'
    success_url = reverse_lazy('articleapp:list')
    template_name = 'articleapp/delete.html'

create.html 작성

<!--articleapp/templates/articleapp/create.html-->
{% extends 'base.html' %}
{% load bootstrap4 %}

{% block content %}

  <div style="text-align : center; max-width: 500px; margin: 4rem auto;">
    <div class="mb-4"> <!-- margin bottom 해서 4배 -->
      <h4>Article Create</h4>
    </div>
    <form action="{% url 'articleapp:create' %}" method="post" enctype="multipart/form-data">  <!--url 일원화, post 방식 으로 전송 -->
      {% csrf_token %}
      {% bootstrap_form form %}
      <input type="submit" class="btn btn-dark rounded-pill col-6 mt-3">
    </form>
  </div>

{% endblock %}

header.html 수정

list view 페이지로 향하는 nav 설정

<!--templates/header.html 수정-->
<div class="pragmatic_header">
   <div>
      <h1 class="pragmatic_logo">Prgmatic</h1>
   </div>
   <div>
      <a href="{% url 'articleapp:list' %}">
         <span>Articles</span>
      </a>
     
     ...

list.html 수정

하단 글쓰기 버튼 만들기

<!--articleapp/templates/articleapp/list.html 수정-->
...

    <div class="item13">
        <img src="https://picsum.photos/200/240" alt="">
    </div>
</div>
	<!-- 추가 -->
    <div style="text-align: center;">
        <a href="{% url 'articleapp:create' %}" class="btn btn-dark rounded-pill col-3 mt-3 mb-3">
            Create Article 
        </a>
    </div>

detail.html 작성

<!--articleapp/templates/articleapp/detail.html-->
{% extends 'base.html'%}

{% block content %}

  <div>
    <div style="text-align: center; max-width: 500px; margin: 4rem auto;">

      <!--게시글의 제목-->
      <h1>
        {{ target_article.title }}
      </h1>
      <!--게시글의 이미지-->
      <img src=" {{ target_article.image.url }}" alt="">

      <!-- 게시글의 내용 -->
      <p>
        {{ target_article.content }}
      </p>

      <!-- 게시글 수정하는 업데이트 링크-->
      <a href="{% url 'articleapp:update' pk=target_article.pk %}">
        <p>Update Article</p>
      </a>

      <!-- 게시글 삭제 링크-->
      <a href="{% url 'articleapp:delete' pk=target_article.pk %}">
        <p>Delete Article</p>
      </a>

    </div>
  </div>

{% endblock %}

decorators.py 작성

주인인지 아닌지를 체크하는 decorators.py 작성

from django.http import HttpResponseForbidden

from articleapp.models import Article


def article_ownership_required(func): # decorator 정의 (이 계정의 소유권이 필요하다는 이름)
    def decorated(request, *args, **kwargs): # request 를 받아
        # 우리가 원하는 작업 - 본인인지 확인하는 작업
        # get, post 등의 요청을 받으면서 pk 로 받은 값을 가지고 있는 User 객체가 user 가 되는 것
        article = Article.objects.get(pk=kwargs['pk'])
        # pk 를 확인해서 해당 pk 의 Article 객체가 실제로 request 를 보낸 article 과 같은지 아닌지 확인
        if not article.writer == request.user: # 게시글의 저자가 지금 요청을 보내는 user 와 다르다면
            return HttpResponseForbidden()   # 금지 되었다고 내보내기
        return func(request, *args, **kwargs) # 같다면 그냥 보내주기
    return decorated
    # article_ownership_required 라는 decorator 를 만들었으니까 views.py에 적용하기

update.html 작성

{% extends 'base.html' %}
{% load bootstrap4 %}

{% block content %}

  <div style="text-align : center; max-width: 500px; margin: 4rem auto;">
    <div class="mb-4"> <!-- margin bottom 해서 4배 -->
      <h4>Article Update</h4>
    </div>
    <form action="{% url 'articleapp:update' pk=target_article.pk %}" method="post" enctype="multipart/form-data"> 
      {% csrf_token %}  <!-- csrf_token 은 항상 들어 가야 하는 것 -->
      {% bootstrap_form form %}
      <input type="submit" class="btn btn-dark rounded-pill col-6 mt-3">
    </form>
  </div>

{% endblock %}

delete.html 작성

{% extends 'base.html' %}

{% block content %}

  <div style="text-align : center; max-width: 500px; margin: 4rem auto;">
    <div class="mb-4"> <!-- margin bottom 해서 4배 -->
      <h4>Delete Article : {{ target_article.title }}</h4> <!-- 이 제목을 가진 게시글 삭제 -->
    </div>
    <form action="{% url 'articleapp:delete' pk=target_article.pk %}" method="post">  <!--url 일원화, post 방식 으로 전송 -->
      {% csrf_token %}
      <input type="submit" class="btn btn-danger rounded-pill col-6 mt-3">
    </form>
  </div>

{% endblock %}

app_name 설정 및 전체 페이지 url 등록

# articleapp/urls.py
from django.urls import path
from django.views.generic import TemplateView

from articleapp.views import ArticleCreateView, ArticleDetailView, ArticleUpdateView, ArticleDeleteView

app_name = 'articleapp'

urlpatterns = [
    path('list/', TemplateView.as_view(template_name='articleapp/list.html'), name='list'),

    path('create/', ArticleCreateView.as_view(), name='create'),
    path('detail/<int:pk>', ArticleDetailView.as_view(), name='detail'),
    path('update/<int:pk>', ArticleUpdateView.as_view(), name='update'),
    path('delete/<int:pk>', ArticleDeleteView.as_view(), name='delete'),
]

ignore 설정

git ignore 수행 : media 파일 내부에 저장되는 파일들이 쌓이는데 → git ignore에 추가
.gitignore 파일 하단에 media/ 추가
media 밑의 하위 파일들을 ignore 하도록 설정

commit

0개의 댓글