SSAFY/Python문법 정리

[JS] 새로고침 없이 좋아요, Follow 하기

황성안 2021. 5. 6. 18:23
728x90

좋아요 새로고침없이하기!

가상환경만들기

python -m venv venv
activate
python manage.py migrate
python manage.py seed articcles --number=30
python manage.py runserver

브라우저에서 like 을 누르면 json으로 django로 이동하게?

 

articles > templates > index

https://github.com/axios/axios

CND 활성화 스크립터 넣어주기

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

https://developer.mozilla.org/ko/docs/Web/API/HTMLOrForeignElement/dataset

에서 script 에서 pk 넣는 방식? 을 확인할수있다

<form class="like-form" id="article-{{ article.pk }}">

index.html

{% extends 'base.html' %}

{% block content %}
  <h1>Articles</h1>
  {% if request.user.is_authenticated %}
    <a href="{% url 'articles:create' %}">[CREATE]</a>
  {% else %}
    <a href="{% url 'accounts:login' %}">[새 글을 작성하려면 로그인하세요.]</a>
  {% endif %}
  <hr>
  {% for article in articles %}
    <p>
      <b>작성자 : <a href="{% url 'accounts:profile' article.user.username %}">{{ article.user }}</a></b>
    </p>
    <p>글 번호 : {{ article.pk }}</p>
    <p>글 제목 : {{ article.title }}</p>
    <p>글 내용 : {{ article.content }}</p>
    <div>
      <form class="like-form" data-article-id="{{ article.pk }}">
        {% csrf_token %}
        {% if request.user in article.like_users.all %}
          <button id="like-{{ article.pk }}">좋아요 취소</button>
        {% else %}
          <button id="like-{{ article.pk }}">좋아요</button>
        {% endif %}
      </form>
    </div>
    <p>
      <span id="like-count-{{ article.pk }}">{{ article.like_users.all|length }}</span>명이 이 글을 좋아합니다.
    </p>
    <a href="{% url 'articles:detail' article.pk %}">[DETAIL]</a>
    <hr>
  {% endfor %}

  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script>
    
    const forms = document.querySelectorAll('.like-form')
    forms.forEach((form) => {
      form.addEventListener('submit', function(event) {

        event.preventDefault() //동작 취소
        //console.log(form.dataset)
        const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value
        const articleId = form.dataset.articleId
        // pk가져오는 방법 ????에는 pk가 들어가야한다.
        const requestUrl = `http://127.0.0.1:8000/articles/${articleId}/likes/`
        //console.log('test test!')// 잘 눌려지는지 확인
        const config = {
          headers: {'X-CSRFToken': csrftoken},
          }
        

        axios.post(requestUrl, {}, config)
        .then((res) => {        
          //console.log(res)
          //console.log(res.data)
          const liked = res.data.liked
          const likeBtn = document.querySelector(`#like-${articleId}`)
          const likeCount = document.querySelector(`#like-count-${articleId}`)
          
          likeBtn.innerText = liked? '좋아요 취소' : '좋아요'
          // 위와 아래는 상동합니다.
          //if (liked) {
          //  likeBtn.innerTest = '좋아요 취소'
          //}else {
          //  likeBtn.innerTest = '좋아요'
          //}
          likeCount.innerText = res.data.like_count
          

        })
        .catch((err) => {
          console.error(err)
        })
      })
    })



  </script>
{% endblock %}

views.py

@require_POST
def likes(request, article_pk):
    if request.user.is_authenticated:
        article = get_object_or_404(Article, pk=article_pk)

        if article.like_users.filter(pk=request.user.pk).exists():
        # if request.user in article.like_users.all():
            # 좋아요 취소
            article.like_users.remove(request.user)
            liked = False
        else:
            # 좋아요 누름
            article.like_users.add(request.user)
            liked = True

        data = {
            #'test': '잘 넘어가나요?',
            'liked':liked,
            'like_count':article.like_users.count(),
        }
        #더이상 html이아닌 json을 받을것이다
        return JsonResponse(data)
    return redirect('accounts:login')

아이콘만들기

https://fontawesome.com/icons?d=gallery&p=2&q=heart

https://icons.getbootstrap.com/

아이콘 사이트 둘중 아무거나사용

저는 부트스트랩 아이콘(아래링크)로가서

https://icons.getbootstrap.com/icons/hand-thumbs-up-fill/

엄지척 아이콘을 선택했습니다.

안에 색있는거

<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-hand-thumbs-up-fill" viewBox="0 0 16 16">
              <path d="M6.956 1.745C7.021.81 7.908.087 8.864.325l.261.066c.463.116.874.456 1.012.965.22.816.533 2.511.062 4.51a9.84 9.84 0 0 1 .443-.051c.713-.065 1.669-.072 2.516.21.518.173.994.681 1.2 1.273.184.532.16 1.162-.234 1.733.058.119.103.242.138.363.077.27.113.567.113.856 0 .289-.036.586-.113.856-.039.135-.09.273-.16.404.169.387.107.819-.003 1.148a3.163 3.163 0 0 1-.488.901c.054.152.076.312.076.465 0 .305-.089.625-.253.912C13.1 15.522 12.437 16 11.5 16H8c-.605 0-1.07-.081-1.466-.218a4.82 4.82 0 0 1-.97-.484l-.048-.03c-.504-.307-.999-.609-2.068-.722C2.682 14.464 2 13.846 2 13V9c0-.85.685-1.432 1.357-1.615.849-.232 1.574-.787 2.132-1.41.56-.627.914-1.28 1.039-1.639.199-.575.356-1.539.428-2.59z"/>
            </svg>

없는거

<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-hand-thumbs-up" viewBox="0 0 16 16">
           <path d="M8.864.046C7.908-.193 7.02.53 6.956 1.466c-.072 1.051-.23 2.016-.428 2.59-.125.36-.479 1.013-1.04 1.639-.557.623-1.282 1.178-2.131 1.41C2.685 7.288 2 7.87 2 8.72v4.001c0 .845.682 1.464 1.448 1.545 1.07.114 1.564.415 2.068.723l.048.03c.272.165.578.348.97.484.397.136.861.217 1.466.217h3.5c.937 0 1.599-.477 1.934-1.064a1.86 1.86 0 0 0 .254-.912c0-.152-.023-.312-.077-.464.201-.263.38-.578.488-.901.11-.33.172-.762.004-1.149.069-.13.12-.269.159-.403.077-.27.113-.568.113-.857 0-.288-.036-.585-.113-.856a2.144 2.144 0 0 0-.138-.362 1.9 1.9 0 0 0 .234-1.734c-.206-.592-.682-1.1-1.2-1.272-.847-.282-1.803-.276-2.516-.211a9.84 9.84 0 0 0-.443.05 9.365 9.365 0 0 0-.062-4.509A1.38 1.38 0 0 0 9.125.111L8.864.046zM11.5 14.721H8c-.51 0-.863-.069-1.14-.164-.281-.097-.506-.228-.776-.393l-.04-.024c-.555-.339-1.198-.731-2.49-.868-.333-.036-.554-.29-.554-.55V8.72c0-.254.226-.543.62-.65 1.095-.3 1.977-.996 2.614-1.708.635-.71 1.064-1.475 1.238-1.978.243-.7.407-1.768.482-2.85.025-.362.36-.594.667-.518l.262.066c.16.04.258.143.288.255a8.34 8.34 0 0 1-.145 4.725.5.5 0 0 0 .595.644l.003-.001.014-.003.058-.014a8.908 8.908 0 0 1 1.036-.157c.663-.06 1.457-.054 2.11.164.175.058.45.3.57.65.107.308.087.67-.266 1.022l-.353.353.353.354c.043.043.105.141.154.315.048.167.075.37.075.581 0 .212-.027.414-.075.582-.05.174-.111.272-.154.315l-.353.353.353.354c.047.047.109.177.005.488a2.224 2.224 0 0 1-.505.805l-.353.353.353.354c.006.005.041.05.041.17a.866.866 0 0 1-.121.416c-.165.288-.503.56-1.066.56z"/>
         </svg>

 

위코드 넣어주기

 

팔로우도해봅시다!

views.py 함수

  1.  

  2. form tag 변경해주기

    id로하는 이유는 버튼이 유일하기때문에!

    여러개면 class 가 편합니당

    <form id=
    

     

  3. script 적어주기

    <script> const form = document.querySelector('#follow') form.addEventListener('submit', function(e) {    e.preventDefault()        }</script>
    
  4. url 이 무엇인지봐야한다

     path('<int:user_pk>/follow/', views.follow, name='follow'),
    
  5. form 수정

    <form id="follow" data-user-id="{{ person.id }}"
    
  6. 다시 form 으로 옮겨서

    <script> const form = document.querySelector('#follow') form.addEventListener('submit', function(e) {    e.preventDefault()        console.log(form.dataset) // data-*    }</script>
    

    서버로가서 개발자도그를통해 log 를 확인해보고

    <script> const form = document.querySelector('#follow') form.addEventListener('submit', function(e) {    e.preventDefault()        console.log(form.dataset.userId) // data-*    }</script>
    
    <script> const form = document.querySelector('#follow') form.addEventListener('submit', function(e) {    e.preventDefault()        form.dataset.userId // data-*	const userId = e.target.dataset.userId		const requestUrl = `/accounts/&{userId}/follow/`		axios.post(requestUrl)    }</script>
    

    기억이안나면 console log 로 보내서 확인해보자.

<script> const form = document.querySelector('#follow') form.addEventListener('submit', function(e) {    e.preventDefault()        form.dataset.userId // data-*	const userId = e.target.dataset.userId		const requestUrl = `/accounts/&{userId}/follow/`		axios.post(requestUrl)	.then((response) => {		console.log(response)		})	.catch((err) => {		console.error(err)		})    }</script>

다시 돌아와서 공식문서를 참고하여 적어주자

<script> const form = document.querySelector('#follow') form.addEventListener('submit', function(e) {    e.preventDefault()        form.dataset.userId // data-*	const userId = e.target.dataset.userId		const requestUrl = `/accounts/&{userId}/follow/`	const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;				const config = {          headers: {'X-CSRFToken': csrftoken},    }				axios.post(requestUrl, {}, config)    }</script>

조금더 아름다운 방법

https://github.com/axios/axios 에서 axios API 를 보자

 

 

 

views.py

맨위에서

from django.http import JsonResponse

    @require_POSTdef follow(request, user_pk):    if request.user.is_authenticated:        # 팔로우 받는 사람        you = get_object_or_404(get_user_model(), pk=user_pk)        me = request.user        # 나 자신은 팔로우 할 수 없다.        if you != me:            if you.followers.filter(pk=me.pk).exists():            # if request.user in person.followers.all():                # 팔로우 끊음                you.followers.remove(me)                followed = False            else:                # 팔로우 신청                you.followers.add(me)                followed = True            data = {                'followed':followed,                # 'follower_count':you.followers.count(),            }        return JsonResponse(data)    return redirect('accounts:login')

followed 값에 따라 바꿔주면된다.

return JsonResopnse() 에서 json 으로 변환 .then에 바꿔준다.

 

html

<script> const form = document.querySelector('#follow') form.addEventListener('submit', function(e) {    e.preventDefault()        form.dataset.userId // data-*	const userId = e.target.dataset.userId		const requestUrl = `/accounts/&{userId}/follow/`	const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;				const config = {          headers: {'X-CSRFToken': csrftoken},    }				axios.post(requestUrl, {}, config)    }</script>

 

button에다가 id를 추가해줘야합니다

버튼이 1개이니까 id 주는거

<form class="follow" data-follower-id = "{{ person.pk }}">          {% csrf_token %}          {% if request.user in followers %}            <button id="follow-btn">언팔로우</button>          {% else %}            <button id="follow-btn">팔로우</button>          {% endif %}        </form>

 

 

<script> const form = document.querySelector('#follow') form.addEventListener('submit', function(e) {    e.preventDefault()        form.dataset.userId // data-*	const userId = e.target.dataset.userId		const requestUrl = `/accounts/&{userId}/follow/`	const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;				const config = {          headers: {'X-CSRFToken': csrftoken},    }				axios.post(requestUrl, {}, config)		.then((response) => {		const followed		const folloBtn-		})	.catch((err) => {		console.error(err)		})   })</script>

 

views.py 로 돌아가 팔로잉/팔로워 인원수 표시

    @require_POSTdef follow(request, user_pk):    if request.user.is_authenticated:        # 팔로우 받는 사람        you = get_object_or_404(get_user_model(), pk=user_pk)        me = request.user        # 나 자신은 팔로우 할 수 없다.        if you != me:            if you.followers.filter(pk=me.pk).exists():            # if request.user in person.followers.all():                # 팔로우 끊음                you.followers.remove(me)                follow = False            else:                # 팔로우 신청                you.followers.add(me)                follow = True            data = {                'followed':followed,                'followers_count':you.followers.count(),                'followings_count':you.followings.count(),            }        return JsonResponse(data)    return redirect('accounts:login')

html

<script>
 const form = document.querySelector('#follow')
 form.addEventListener('submit', function(e) {
    e.preventDefault()
    
    form.dataset.userId // data-*
	const userId = e.target.dataset.userId
	
	const requestUrl = `/accounts/&{userId}/follow/`
	
	
	const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
	const config = {
          headers: {'X-CSRFToken': csrftoken},
    }
	
	
	
	axios.post(requestUrl, {}, config)
		.then((response) => {
		const followed = response.data.followed
		const folloBtn = document.queselector('#follow-btn')
		
		const followersCnt = response.data.followers_count
		
		//const followingsCnt = response.data.followings_count
		//const followingsCntBox = document.querySelector('#followings-cnt') 
		//followingsCntBox.innerText = followingsCnt
				
		const followersCntBox = document.querySelector('#followers-cnt')
		
		followBtn.innerText = followed ? '팔로우 취소' : '팔로우'

		followersCntBox.innerText = followersCnt
	})
	.catch((err) => {
		console.error(err)
	
	})
   
   }
   const form = document.querySelector('#follow')
   if (form) {
   	form.addEventListener('submit', onFollowSubmit)
   }
</script>

 

팔로윙 인원수 html 변경해주기

 <div>
      팔로잉 : <span id="followings-cnt">{{ followings|length }}</span>
      / 
      팔로워 : <span id="followers-cnt">{{ followers|length }}</span>
 </div>

 

 

 

html

폼이 별로다 삭제 ㄱ 버튼만 남겨둬라

{% extends 'base.html' %}

{% block content %}
<h1>{{ person.username }}님의 프로필</h1>

{% with followings=person.followings.all followers=person.followers.all %}
  <div>
    <div>
      팔로잉 : {{ followings|length }} / 팔로워 : {{ followers|length }}
    </div>
    {% if request.user != person %}
      <div>
        
          {% csrf_token %}
          {% if request.user in followers %}
            <button id="follow-btn">언팔로우</button>
          {% else %}
            <button id="follow-btn">팔로우</button>
          {% endif %}
       
      </div>
    {% endif %}
  </div>
{% endwith %}


<hr>

<h2>{{ person.username }}'s 게시글</h2>
{% for article in person.article_set.all %}
  <div>{{ article.title }}</div>
{% endfor %}

<hr>

<h2>{{ person.username }}'s 댓글</h2>
{% for comment in person.comment_set.all %}
  <div>{{ comment.content }}</div>
{% endfor %}

<hr>

<h2>{{ person.username }}'s likes</h2>
{% for article in person.like_articles.all %}
  <div>{{ article.title }}</div>
{% endfor %}

<hr>

<a href="{% url 'articles:index' %}">[back]</a>


<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
 const form = document.querySelector('#follow')
 form.addEventListener('submit', function(e) {
    e.preventDefault()
    
    form.dataset.userId // data-*
	const userId = e.target.dataset.userId
	
	const requestUrl = `/accounts/&{userId}/follow/`
	
	
	const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
	const config = {
          headers: {'X-CSRFToken': csrftoken},
    }
	
	
	
	axios.post(requestUrl, {}, config)
		.then((response) => {
		const followed = response.data.followed
		const folloBtn = document.queselector('#follow-btn')
		
		const followersCnt = response.data.followers_count
		
		//const followingsCnt = response.data.followings_count
		//const followingsCntBox = document.querySelector('#followings-cnt') 
		//followingsCntBox.innerText = followingsCnt
				
		const followersCntBox = document.querySelector('#followers-cnt')
		
		followBtn.innerText = followed ? '팔로우 취소' : '팔로우'

		followersCntBox.innerText = followersCnt
	})
	.catch((err) => {
		console.error(err)
	
	})
   
   }
   const form = document.querySelector('#follow')
   if (form) { // null check 
   	form.addEventListener('submit', onFollowSubmit)
   }
</script>

{% endblock %}

 

 

 

 

 

 

like 로그인 관련구현

views.py

from django.http import JsonResponse, HttpResponse 불러오고

@require_POST
def likes(request, article_pk):
    if request.user.is_authenticated:
        article = get_object_or_404(Article, pk=article_pk)

        if article.like_users.filter(pk=request.user.pk).exists():
        # if request.user in article.like_users.all():
            # 좋아요 취소
            article.like_users.remove(request.user)
            liked = False
        else:
            # 좋아요 누름
            article.like_users.add(request.user)
            liked = True

        data = {
            #'test': '잘 넘어가나요?',
            'liked':liked,
            'like_count':article.like_users.count(),
        }
        #더이상 html이아닌 json을 받을것이다
        return JsonResponse(data)
    return HttpResponse(status=401)

 

 

728x90

'SSAFY > Python문법 정리' 카테고리의 다른 글

[Vue + Django] Client+ Server  (0) 2021.05.17
[JS]Vue router  (0) 2021.05.10
[Python] Prim 알고리즘  (0) 2021.04.21
[Python] 큐의 구조  (0) 2021.03.03
[Python] 스택 2  (0) 2021.02.24