실용주의 프로그래머님의 인프런 강의를 듣고 작성하였습니다.
출처: 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 github : https://github.com/e-oj/Magic-Grid
먼저 articleapp을 만들어준다.
1. 생성
python manage.py startapp articleapp
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);
}
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)
forms.py
from django.forms import ModelForm
from articleapp.models import Article
class ArticleCreationForm(ModelForm):
class Meta:
model = Article
fields = ['title', 'image', 'content']
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 %}
path('create/', ArticleCreateView.as_view(), name='create'),
path('detail/<int:pk>', ArticleDetailView.as_view(), name='detail')
header.html
<a href="{% url 'articleapp:list' %}">
<span>Article</span>
</a>
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>
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'
decorator를 이용해 게시글을 수정하려는 자가 작성자인지 확인시키는 작업을 거친다.
@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
{% 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 %}
path('update/<int:pk>', ArticleUpdateView.as_view(), name='update'),
만든 update도 url에 넣어준다.
<a href="{% url 'articleapp:update' pk=target_article.pk %}">
<p>Update Article</p>
</a>
@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')
{% 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 %}
path('delete/<int:pk>', ArticleDeleteView.as_view(), name='delete'),
<a href="{% url 'articleapp:delete' pk=target_article.pk %}">
<p>Delete Article</p>
</a>
이제까지 account나 article은 단일 객체만 보면 됐었다.게시판을 만들 때에는 단일 객체가 아니라 멀디 객체를 봐야 한다. 여러 개의 게시글을 한번에 보여주는 List view를 사용해야 한다. + Pagination으로 페이징화를 시켜준다.
class ArticleListView(ListView):
model = Article
context_object_name = 'article_list'
template_name = 'articleapp/list.html'
paginate_by = 8
path('list/', ArticleListView.as_view(), name='list'),
<div>
<img src="{{ article.image.url }}" alt="">
</div>
{% 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 %}
<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>
{% include 'snippets/pagination.html' with page_obj=page_obj %}
detail.html
<h3>
{{ target_article.writer.profile.nickname }}
</h3>