본문 바로가기

개인 스터디/정리

[Django] Do it 장고+부트스트랩 13~15주차 정리

댓글 기능부터 배포전까지..드디어 스터디가 끝이 보인다.


#13

 

댓글 기능을 만들었다.

댓글 모델을 만들고, 이번에는 댓글 폼을 이용하여 기능을 구현하였다.

 

#models.py

class Comment(models.Model):
    post=models.ForeignKey(Post, on_delete=models.CASCADE)
    author=models.ForeignKey(User, on_delete=models.CASCADE)
    content=models.TextField()
    created_at=models.DateTimeField(auto_now_add=True)
    modified_at=models.DateTimeField(auto_now=True)

    def __str__(self):
        return f'{self.author}::{self.content}'

    def get_absolute_url(self):
        return f'{self.post.get_absolute_url()}#comment-{self.pk}' # #은 html요소의 id의미

 

#forms.py

from .models import Comment
from django import forms

class CommentForm(forms.ModelForm):
    class Meta:
        model=Comment
        fields=('content',)

직접 CommentForm을 만들었다. form에서 우리가 직접 작성할 내용은 content뿐이여서 이렇게 작성했으나, fields 대신 exclude=('post','author,...)와 같이 작성해주어도 된다. 만든 CommentForm은 PostDetail 클래스로 넘겨주어 post_detail.html에서 comment도 함께 가져올 수 있도록 한다.

 

#views.py

def new_comment(request, pk):
    if request.user.is_authenticated:
        post=get_object_or_404(Post, pk=pk)

        if request.method =='POST':
            comment_form=CommentForm(request.POST)
            if comment_form.is_valid():
                comment=comment_form.save(commit=False)
                comment.post=post
                comment.author=request.user
                comment.save()
                return redirect(comment.get_absolute_url())
        else:
            return redirect(comment.get_absolute_url())
    else:
        raise PermissionDenied

comment 또한 staff 유저만 작성이 가능하므로, post data에 if 조건을 걸어준다.

POST요청이 들어오면 form은 만들어두었던 CommentForm을 가져오고, form이 유효하면 post와 author 필드 등 적절하게 comment의 데이터를 작성 후 저장한다.

 

 

CommentUpdate 클래스는 PostUpdate 클래스와 비슷하게 UpdateView를 상속받아 만들고,

CommentDelete는 DeleteView를 상속받는 대신 새롭게 함수를 구현하였다.

 

 

#views.py

def delete_comment(request,pk):
    comment=get_object_or_404(Comment, pk=pk)
    post=comment.post
    if request.user.is_authenticated and request.user == comment.author:
        comment.delete()
        return redirect(post.get_absolute_url())
    else:
        raise PermissionDenied

마찬가지로 user가 권한이 있고 author와 같을 때만 작동한다.

중간에 reqeust.user==comment.author 부분은 꼭 띄어쓰기를 해주어야 작동하더라. 예상치 못한 부분에서 에러가 났다.

 

 

 


#14

 

pagination, search, 사용자 아바타 가져오기 등의 기능을 구현하였다.

 

 

pagination은 쉬웠다. ListView에서 paginated_by=(보여줄 포스트 갯수)만 작성하면 알아서 기능을 제공해 준다.

ListView의 page_obj를 이용하여 html을 작성하면 된다.

 

 

검색 기능은 검색 창 내용을 서버로 전달하기 위해 javascript를 사용했다.

#base.html

<div class="input-group">
           <input class="form-control" type="text" placeholder="Search for..." id="search-input">
           <button class="btn btn-primary" id="button-search" type="button" onclick="searchPost()">Go!</button>
</div>





<script>
            function searchPost(){
                let searchValue=document.getElementById('search-input').value.trim();
                if (searchValue.length > 1){
                    location.href="/blog/search/"+searchValue+"/";
                }
                else{
                    alert('검색어('+ searchValue +')가 너무 짧습니다.');
                }
            };

            searchValue=document.getElementById('search-input').addEventListener('keyup', function(event)
            {
                if(event.key === 'Enter'){
                    searchPost();
                }
            });
          </script>

 

trim()함수를 이용해 검색어의 앞뒤 공백을 제거하여 가져오고 url로 전달한다.

또, enter키 이벤트 리스너와 alert 창으로 검색어의 길이 제한을 두었다.

 

 

 

#views.py

from django.db.models import Q

class PostSearch(PostList):
    paginate_by=None # 검색 결과는 한 페이지에 모두 보이게
    
    def get_queryset(self):
        q=self.kwargs['q']
        post_list=Post.objects.filter(
            Q(title__contains=q) | Q(tags__name__contains=q)
        ).distinct()
        return post_list
    
    def get_context_data(self, **kwargs):
        context=super(PostSearch, self).get_context_data()
        q=self.kwargs['q']
        context['search_info']=f'Search: {q} ({self.get_queryset().count()})'

        return context

ListView의 get_queryset() 메서드를 오버라이딩 한다.

self.kwargs['q']로 url로 넘어온 검색어를 변수 q에 담고, title 혹은 tag와 일치하는지 검색한다. 쿼리 조건에서는 title__contains로 밑줄은 2개로 꼭 표현한다. 또, '|' == or, & == and의 의미임을 명심한다. 조건에 content와 일치하는 지 추가해도 좋을 것 같다.

 

 

사용자 아바타 가져오기는 여러 사이트가 있지만, 책에서 제공하는 사이트를 이용하였다.

 

 

#models.py

class Comment(models.Model):
	#생략
    def get_avatar_url(self):
        if self.author.socialaccount_set.exists():
            return self.author.socialaccount_set.first().get_avatar_url()
        else:
            return f'https://doitdjango.com/avatar/id/1461/f136db71fc1aec42/svg/{self.author.email}'

user의 email을 이용하여 아바타 이미지를 가져온다.

 

 

 


#15

대문 페이지와 자기소개 페이지도 마지막으로 만들었다.

특별한 건 없고, single_pages app에다가 새롭게 html과 static 폴더를 만들어줬다.

 

크게 어려운 점은 없었는데, setting.py에 STATIC_ROOT = os.path.join(BASE_DIR, '_static')가 없으면 static 파일이 적용이 안된다는 점??

css 적용이 안돼서 조금 당황했었다. MEDIA_ROOT는 작성했는데 STATIC_ROOT를 작성안했나보다.

 

완성!

이제 배포만 하면 끝!

 

 

 


아직 crispy문제를 해결하지 못해 form이 예쁘지 않은 점만 제외하면, 쉽게 성공했다.

또, 제공하는 html 코드가 달라져서 책의 코드를 그대로 작성해도 안예쁘게 나온다...최대한 고쳐보려고 했는데 그냥 class 문제라 책에서 제공하는 github사이트 참고해가면서 수정했다.

수정 전...댓글 사진
수정 후 댓글 사진

ㅎㅎ..배포는 따로 글 작성해야지. 배포하기 전에 crispy문제도 좀 해결하고 싶다.

 

 


참고 사이트

- 아바타 가져오기 사이트(이용함)

py-avatar :: doitdjango.com

 

py-avatar :: doitdjango.com

Random Avatar Get random avatar for your website. This avatar service is for reader of my book "Do It Django" Learn more

doitdjango.com

- 또 다른 아바타 사이트

DiceBear | Open Source Avatar Library

 

DiceBear | Open Source Avatar Library

DiceBear The avatar library you've always been looking for. Create avatars for your profiles, designs, websites or apps. Piece by piece or based on a seed.

dicebear.com