Articleapp Implementation

jurin·2021년 4월 16일
0

Django Project - LeeBook

목록 보기
5/11
post-thumbnail

실용주의 프로그래머님의 인프런 강의를 듣고 작성하였습니다.
출처: https://www.inflearn.com/course/%EC%9E%A5%EA%B3%A0-%ED%95%80%ED%84%B0%EB%A0%88%EC%8A%A4%ED%8A%B8/lecture/62871?tab=note&speed=1.25

MagicGrid

핀터레스트의 카드형 레이아웃을 구현하기 위한 라이브러리

MagicGrid github : https://github.com/e-oj/Magic-Grid

먼저 articleapp을 만들어준다.
1. 생성

python manage.py startapp articleapp
  1. setting에 추가
  2. urls에 추가
path('articles/', include('articleapp.urls')),

이제 MagicGrid를 적용하기 위해 articleapp/urls.py를 만들고 articleapp/templates/articleapp/list.html를 만들어서 아까 사이트에서 html과 css코드들을 복붙해 적용시킨다.

articleapp/urls.py

from django.urls import path
from django.views.generic import TemplateView

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

articleapp/templates/articleapp/list.html

{% extends 'base.html' %}

{% block content %}
<style>
    .container div {
      width: 250px;
      height: 500px;
      background-color: antiquewhite;
      display: flex;
      justify-content: center;
      align-items: center;
      border-radius: 1rem;
    }

</style>

    <div class="container">
      <div class="item1">1</div>
      <div class="item2">2</div>
      <div class="item3">3</div>
      <div class="item4">4</div>
      <div class="item5">5</div>
      <div class="item6">6</div>
      <div class="item7">7</div>
      <div class="item8">8</div>
      <div class="item9">9</div>
      <div class="item10">10</div>
      <div class="item11">11</div>
      <div class="item12">12</div>
      <div class="item13">13</div>
    </div>
{% endblock %}

js 코드를 적용시키기 위해선 아까 사이트에서 Magic-Grid/dist/magic-grid.cjs.js의 코드를 복사해rid.cjs.js의 코드를 복사해 static/js/magicgrid.js에 붙여 넣는데 그 뒤에 magicgrid의 js 코드를 붙여넣는다.

그 후 articleapp/templates/articleapp/list.html에서 js 파일을 연결시켜준다.

articleapp/templates/articleapp/list.html

    {% load static %}
    <script src="{% static 'js/magicgrid.js %}"></script>

1번째 article에 사진을 넣어본다.

image의 src는 Lorem Picsum : https://picsum.photos/ 사이트를 통해 랜덤 이미지를 불러온다.

list.html

<style>
    .container img {
      width: 100%;
      border-radius: 1rem;
    }
</style>

<div class="item1">
          <img src="https://picsum.photos/200/300" alt="">
      </div>

그런데 문제가 생긴다. 자바스크립트 코드들이 다 실행된 후에 사진이 로드가 돼서 레이아웃이 깨진다.

올바르게 재배치 될 수 있도록 다음과 같이 수정한다.

var masonrys = document.getElementByTagName("Img");

for (let i = 0; i < masonrys.length; i++) {
    masonrys[i].addEventListener('load', function() {
        magicGrid.positionItems();
    }, false);
}

Articleapp

  1. model 작성
from django.contrib.auth.models import User
from django.db import models

# Create your models here.

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

    title = models.CharField(max_length=200, null=True)
    # image는 profile 밑의 article 밑에 업로드한다.
    image = models.ImageField(upload_to='article/', null=False)
    content = models.TextField(null=True)

    # 자동으로 날짜 입력되도록 설정
    created_at = models.DateField(auto_now_add=True, null=True)
  1. form 작성

forms.py

from django.forms import ModelForm

from articleapp.models import Article


class ArticleCreationForm(ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'image', 'content']
  1. html 작성

create.html

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


{% block content %}

    <div style="text-align: center; max-width: 500px; margin: 4rem auto">
        <div class="mb-4">
            <h4>
                Article Create
            </h4>
        </div>
        # article에는 image가 들어가기 때문에 enctype을 설정해준다.
        <form action="{% url 'articleapp:create' %}" method="post" enctype="multipart/form-data">
            {% csrf_token %}
            {% bootstrap_form form %}

            <input type="submit" class="btn btn-dark rounded-pill col-6 mt-3">
        </form>
    </div>

{% endblock %}

detail.html

{% extends 'base.html' %}

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

            <h1>
                {{ target_article.title }}
            </h1>

            <img src="{{ target_article.image.url}}" alt="">

            <p>
                {{ target_article.content}}
            </p>
        </div>
    </div>

{% endblock %}
  1. urls.py에 create.html, detail.html path 추가
    path('create/', ArticleCreateView.as_view(), name='create'),
    path('detail/<int:pk>', ArticleDetailView.as_view(), name='detail')
  1. 네이게이션 바에서 list.html로 가는 버튼 생성

header.html

<a href="{% url 'articleapp:list' %}">
                <span>Article</span>
            </a>
  1. 글 목록 아래에 글쓰기 버튼 추가

list.html

    <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>

Article View

create view와 detail view

from django.contrib.auth.decorators import login_required
from django.shortcuts import render

# Create your views here.
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.views.generic import CreateView, DetailView

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

@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'

update view

decorator를 이용해 게시글을 수정하려는 자가 작성자인지 확인시키는 작업을 거친다.

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

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

decorator.py

from django.contrib.auth.models import User
from django.http import HttpResponseForbidden

from articleapp.models import Article


def article_ownership_required(func):
    def decorated(request, *args, **kwargs):
        article = Article.objects.get(pk=kwargs['pk'])
        if not article.writer == request.user:
            return HttpResponseForbidden()
        return func(request, *args, **kwargs)
    return decorated
  1. update.html
{% extends 'base.html' %}
{% load bootstrap4 %}


{% block content %}

    <div style="text-align: center; max-width: 500px; margin: 4rem auto">
        <div class="mb-4">
            <h4>
                Article Update
            </h4>
        </div>
        <form action="{% url 'articleapp:update' pk=target_article.pk %}" method="post" enctype="multipart/form-data">
            {% csrf_token %}
            {% bootstrap_form form %}

            <input type="submit" class="btn btn-dark rounded-pill col-6 mt-3">
        </form>
    </div>

{% endblock %}
  1. urls.py
    path('update/<int:pk>', ArticleUpdateView.as_view(), name='update'),

만든 update도 url에 넣어준다.

  • Update 버튼
    detail.html
<a href="{% url 'articleapp:update' pk=target_article.pk %}">
	<p>Update Article</p>
</a>

Delete View

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

    def get_success_url(self):
        return reverse('articleapp:list')
  1. delete.html
{% extends 'base.html' %}

{% block content %}

    <div style="text-align: center; max-width: 500px; margin: 4rem auto">
        <div class="mb-4">
            <h4>
                Delete Article : {{ target_article.title }}
            </h4>
        </div>
        <form action="{% url 'articleapp:delete' pk=target_article.pk %}" method="post">
            {% csrf_token %}
            <input type="submit" class="btn btn-danger rounded-pill col-6 mt-3">
        </form>
    </div>

{% endblock %}
  1. urls.py
    path('delete/<int:pk>', ArticleDeleteView.as_view(), name='delete'),
  1. delete 버튼
    detail.html
            <a href="{% url 'articleapp:delete' pk=target_article.pk %}">
                <p>Delete Article</p>
            </a>

ListView

이제까지 account나 article은 단일 객체만 보면 됐었다.게시판을 만들 때에는 단일 객체가 아니라 멀디 객체를 봐야 한다. 여러 개의 게시글을 한번에 보여주는 List view를 사용해야 한다. + Pagination으로 페이징화를 시켜준다.

  1. views
class ArticleListView(ListView):
    model = Article
    context_object_name = 'article_list'
    template_name = 'articleapp/list.html'
    paginate_by = 8
  1. urls
    path('list/', ArticleListView.as_view(), name='list'),
  1. templates/snippets/card.html
<div>
    <img src="{{ article.image.url }}" alt="">
</div>
  1. list.html (card)
    {% if article_list %}
    <div class="container">
        {% for article in article_list %}
        <a href="{% url 'articleapp:detail' pk=article.pk %}">
<!--            article을 전달해주어야 하므로 연결해준다.-->
            {% include 'snippets/card.html' with article=article %}
        </a>
        {% endfor %}
    </div>
    <script src="{% static 'js/magicgrid.js' %}"></script>

<!--        게시글이 없을 경우-->
    {% else %}
    <div class="text-center">
        <h1>
            No Articles YET!
        </h1>
    </div>
    {% endif %}
  1. templates/snippets/pagination.html
<div style="text-align: center; margin: 1rem 0;">
    {% if page_obj.has_previous %}
    <a href="{% url 'articleapp:list' %}?page={{ page_obj.previous_page_number }}" class="btn btn-secondary rounded-pill">
        {{ page_obj.previous_page_number }}
    </a>
    {% endif %}
    <a href="{% url 'articleapp:list' %}?page={{ page_obj.number }}" class="btn btn-secondary rounded-pill active">
        {{ page_obj.number }}
    </a>
    {% if page_obj.has_next %}
    <a href="{% url 'articleapp:list' %}?page={{ page_obj.next_page_number }}" class="btn btn-secondary rounded-pill">
        {{ page_obj.next_page_number }}
    </a>
    {% endif %}
</div>
  1. list.html (pagination)
    {% include 'snippets/pagination.html' with page_obj=page_obj %}

게시글 작성자 보이기

detail.html

<h3>
	{{ target_article.writer.profile.nickname }}
</h3>
profile
anaooauc1236@naver.com

0개의 댓글