def get_success_url(self):
return reverse('accountapp:detail', kwargs={'pk': self.object.user.pk})
detail에서 누구나 edit을 할 수 있는 문제점 발생
get_success_url 함수를 만들어 이를 방지한다.
self.object는 Profile을 의미한다.
python manage.py startapp articleapp
setting.py
'articleapp'
urls.py
path('articles/', include('articleapp.urls')),
from django.urls import path
from django.views.generic import TemplateView
urlpatterns = [
path('list/', TemplateView.as_view(template_name='articleapp/list.html'), name='list')
]
var masonrys = document.getElementsByTagName('img');
//벽돌 형태로 엇비슷하기 올려가는 레이아웃 형태, 문서 내부에서 img 태그를 다 가져온다.
for (let i = 0; i < masonrys.length; i++) { //masonrys의 개수만큼 도는데
masonrys[i].addEventListener('load', function() {
//하나하나마다 addEventListener로 load가 되었을 때 function을 넣어주는데 이것이 magicgrid
magicGrid.positionItems();
//magicGrid의 postionItems을 보여주고 끝낼 것이다.
}, false);
//모든 html 안에 있는 이미지가 로드되었을 때, 매직그리드를 다시 포지셔닝하라는 이벤트리스너를 추가한 것이다.
}
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.DateField(auto_created=True, null=True)
class ArticleCreationForm(ModelForm):
class Meta:
model = Article
fields = ['title', 'image', 'content']
python manage.py makemigrations
python manage.py migrate
ArticleCreateView 작성
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})
ArticleDetailView 작성
class ArticleDetailView(DetailView):
model = Article
context_object_name = 'target_article'
template_name = 'articleapp/detail.html'
create 작성
기존의 create를 복사하고 일부만 수정한다.
<div class="mb-4">
<h4>Article Create</h4>
</div>
<form action="{% url 'articleapp:create' %}" method="post" enctype="multipart/form-data">
detail 작성
<h1>
{{target.article_title}}
</h1>
<img src="{{ target_article.image.url }}" alt="">
<p>
{{ target_article.content }}
</p>
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'),
]
decorator.py
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
article 작성자가 유저가 맞다면 request하고 그게 아니면 금지
UpdateView 작성
@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})
url 연결
path('update/<int:pk>', ArticleUpdateView.as_view(), name='update'),
Delete View 작성
@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'
url 연결
path('delete/<int:pk>', ArticleDeleteView.as_view(), name='delete'),
class ArticleListView(ListView):
model = Article
context_object_name = 'article_list'
template_name = 'articleapp/list.html'
paginate_by = 25
paginate_by: 한 페이지에 몇 개의 게시물까지 들어갈 것인지. 여기선 25개
path('list/', ArticleListView.as_view(), name='list'),
{% if article_list %}
<div class="container">
{% for article in article_list %}
<a href="{% url 'articleapp:detail' pk=article.pk %}">
{% include 'snippets/card.html' with article=article %}
</a>
{% endfor %}
</div>
article_list에 있는 게시물을 for문으로 돌려서 게시물 수만큼 반복한다.
pk가 게시물을 작성한 유저라면 detail로 연결한다.
snippets는 조각 코드가 있는 폴더이다. snippets 내의 card.html에서 조각 코드를 작성하고 article에 적용한다.
{% if article_list %}
<script src="{% static 'js/magicgrid.js' %}"></script>
{% else %}
<div style="text-align:center">
<h1>No Articles YET!</h1>
</div>
{% endif %}
{% include 'snippets/pagination.html' with page_obj=page_obj %}
만약 아티클 리스트가 있으면 매직그리드 형태로 보여주고 없다면, 아직 아티클이 없다는 문구를 띄운다.
게시물 하단에 페이지를 표시하는 기능의 페이지네이션 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>
<!-- 이 페이지가 이전 페이지를 가지고 있으면 이전 페이지로 향하는 링크를 생성한다.-->
<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>
Create View는 object 정보는 없는데 Form은 있고 반대로 Detail View는 object 정보는 있고 Form은 없다.
만약, Detail View에서 Form을 사용하고 싶다면 문제 발생
➡️ Mixin으로 해결 가능!
Mixin은 다중 상속을 의미한다. (Addon과 유사하다.)
Detail View에서 Form을 사용할 수 있다.
python manage.py startapp commentapp
사용한다고 말해주는 작업
INSTALLED_APPS = ['commentapp']
path('comments/', include('commentapp.urls')),
app_name = 'commentapp'
urlpatterns = [
path
]
class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.SET_NULL, null=True, related_name='comment')
writer = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='comment')
content = models.TextField(null=False)
created_at = models.DateTimeField(auto_now=True)
class CommentCreationForm(ModelForm):
class Meta:
model = Comment
fields = ['content']
python manage.py makemigrations
python manage.py migrate
DB에 반영
class CommentCreateView(CreateView):
model = Comment
form_class = CommentCreationForm
template_name = 'commentapp/create.html'
def get_success_url(self):
return reverse('articleapp:detail', kwargs={'pk':self.object.article.pk})
app_name = 'commentapp'
urlpatterns = [
path('create/', CommentCreateView.as_view(), name='create')
]
{% if user.is_authenticated %}
<input type="submit" class="btn btn-dark rounded-pill col-6 mt-3">
{% else %}
<a href="{% url 'accountapp:login' %}?next={{ request.path }}"
class="btn btn-dark rounded-pill col-6 mt-3">
Login
</a>
{% endif %}
인증받은 사용자라면 제출 버튼이 나오고 그게 아니라면 로그인 페이지로 연결한다.
def form_valid(self, form):
temp_comment = form.save(commit=False)
temp_comment.article = Article.object.get(pk=self.request.POST['article_pk'])
temp_comment.writer = self.request.user
temp_comment.save()
return super().form_valid(form)
여기서 object는 create.html의 hidden을 가져옴
{% for comment in target_article.comment.all %}
{% include 'commentapp/detail.html' with comment=comment %}
{% endfor %}
{% include 'commentapp/create.html' with article=target_article %}
comment에 target_article들의 모든 코멘트를 가져온다.
CommentDeleteView 작성
class CommentDeleteView(DeleteView):
model = Comment
context_object_name = 'target_comment'
template_name = 'commentapp/delete.html'
def get_success_url(self):
return reverse('articleapp:detail', kwargs={'pk': self.object.article.pk})
urls.py
path('delete/<int:pk>', CommentDeleteView.as_view(), name='delete'),
delete.html
만약 작성자가 유저가 맞다면 delete 버튼을 보이게 한다.
{% if comment.writer == user %}
<div style="text-align: right">
<a href="{% url 'commentapp:delete' pk=comment.pk %}" class="btn btn-danger rounded-pill">
Delete
</a>
</div>
{% endif %}
그리고 데코레이터를 작성하고 붙어주어 유저가 맞을 때만 댓글을 삭제할 수 있도록 한다.
@method_decorator(comment_ownership_required, 'get')
@method_decorator(comment_ownership_required, 'post')
class CommentDeleteView(DeleteView):
새로 알게된 점은 이번 포스팅 전부 새로 알게되었다. 시간날 때마다 봐야겠다.
app들을 만들면서 일련의 순서가 반복되니 슬슬 감이 오는 것 같다.
오류가 자주 발생했는데 전부 오타의 문제였어서 크게 어려움은 겪지 않고 금방 고쳤다.