Django 7일차 TIL

김민지·2023년 9월 11일
0

Django

목록 보기
7/11

get_success_url 함수 그리고 리팩토링

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

detail에서 누구나 edit을 할 수 있는 문제점 발생
get_success_url 함수를 만들어 이를 방지한다.
self.object는 Profile을 의미한다.


MagicGrid 소개 및 Articleapp 시작

Articleapp 생성

1. articleapp 생성

python manage.py startapp articleapp

2. practice_django/setting.py와 urls.py에 url 연결

setting.py

'articleapp'

urls.py

path('articles/', include('articleapp.urls')),

3. articleapp의 urls.py 생성 후 연결

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

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

4. list.html 파일 작성

MagicGrid.js

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 안에 있는 이미지가 로드되었을 때, 매직그리드를 다시 포지셔닝하라는 이벤트리스너를 추가한 것이다.
}

Article 모델 생성 오류 수정

모델 생성

1. articleapp에서 models.py에 모델 내용을 작성한다.

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)
  • on_delete=models.SET_NULL: 회원탈퇴를 했을 때 게시글이 사라지지 않고 알수없음 등으로 설정하겠다는 의미
  • related_name: 유저 객체에서 article을 접근할 때 직관적이기 위함
  • created_at: 언제 만들어졌는지 확인, 생성되었을 때 자동으로 생성시간이 저장된다.

2. forms.py 생성

class ArticleCreationForm(ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'image', 'content']

3. migration 작업

python manage.py makemigrations
python manage.py migrate

Articleapp 구현

4. View 작성

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'

5. Template 작성

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>

6. url 연결

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'),
]

7. decorator 연결

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하고 그게 아니면 금지

8. Update View 작성 후 url 연결

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'),

9. Delete View 작성 후 url 연결

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'),

ListView, Pagination 소개 및 적용

List View 만들기

1. views.py 작성

class ArticleListView(ListView):
    model = Article
    context_object_name = 'article_list'
    template_name = 'articleapp/list.html'
    paginate_by = 25

paginate_by: 한 페이지에 몇 개의 게시물까지 들어갈 것인지. 여기선 25개

2. urls.py 연결

path('list/', ArticleListView.as_view(), name='list'),

3. list.html 작성

  • 게시물을 보여주기
{% 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>

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

Mixin 소개 및 Commentapp 구현

Mixin?

Create View는 object 정보는 없는데 Form은 있고 반대로 Detail View는 object 정보는 있고 Form은 없다.
만약, Detail View에서 Form을 사용하고 싶다면 문제 발생
➡️ Mixin으로 해결 가능!

Mixin은 다중 상속을 의미한다. (Addon과 유사하다.)
Detail View에서 Form을 사용할 수 있다.

Commentapp 구현

1. 터미널에서 commentapp 생성

python manage.py startapp commentapp

2. setting.py

사용한다고 말해주는 작업

INSTALLED_APPS = ['commentapp']

3. urls.py

path('comments/', include('commentapp.urls')),

4. commentapp/urls.py

app_name = 'commentapp'

urlpatterns = [
    path
]

5. models.py 모델 생성

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)

6. Form 작성

class CommentCreationForm(ModelForm):
    class Meta:
        model = Comment
        fields = ['content']

7. migration

python manage.py makemigrations
python manage.py migrate

DB에 반영

8. View 작성

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

9. url 설정

app_name = 'commentapp'

urlpatterns = [
    path('create/', CommentCreateView.as_view(), name='create')
]

10. template 작성

{% 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 %}

인증받은 사용자라면 제출 버튼이 나오고 그게 아니라면 로그인 페이지로 연결한다.

11. Mixin 다중 상속

12. Form Valid

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을 가져옴


Commentapp 마무리

댓글 업그레이드

articleapp/detail.html

{% 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들의 모든 코멘트를 가져온다.

detail.html 작성

CommentDeleteView 작성과 url 연결

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

Commentapp 결과 화면


🤓 느낀 점

새로 알게된 점은 이번 포스팅 전부 새로 알게되었다. 시간날 때마다 봐야겠다.
app들을 만들면서 일련의 순서가 반복되니 슬슬 감이 오는 것 같다.
오류가 자주 발생했는데 전부 오타의 문제였어서 크게 어려움은 겪지 않고 금방 고쳤다.

profile
안녕하세요

0개의 댓글