과제/팀과제

[Django] 팀 프로젝트 : 머신러닝 프로젝트(5) - 페이지네이션, 좋아요, 북마크

마이구미+ 2023. 5. 29. 20:27

<페이지네이션>

- 프론트엔드

// ariticlesList.js
// 페이지네이션
async function paginationView(articles) {
// html에 게시글 목록과 버튼을 js로 만들어서 붙여주기 위해 가져옴
    const contents = document.getElementById("article-list-kmj");
    const buttons = document.getElementById("buttons-kmj");

    const numOfContent = articles.length;	// 전체 게시글 수
    const maxContent = 10;	// 한 페이지당 게시글 수
    const maxButton = 5;	// 한 창에 보여질 버튼 수
    const maxPage = Math.ceil(numOfContent / maxContent);	// 최종 페이지 수
    let page = 1; // 페이지 초기화

    const makeContent = (id) => {
    // 이전에 게시글 목록 페이지 만들었을 때 그 HTML을 가져와서 페이지네이션으로 새로 만듦
        const content_kmj = document.createElement("tr");
        content_kmj.setAttribute("onclick", `articleDetail(${articles[id].pk})`);
        content_kmj.innerHTML = `
            <th>${articles[id].pk}</th>
            <td>${articles[id].title}</td>
            <td>${articles[id].nickname}</td>
            <td>${articles[id].created_at}</td>
        `;
        return content_kmj;
    };

    const makeButton = (id) => {
    // 버튼 생성하기
        const button_kmj = document.createElement("button");
        button_kmj.classList.add("button_kmj");
        button_kmj.dataset.num = id;
        button_kmj.innerText = id;
        button_kmj.addEventListener("click", (e) => {
            Array.prototype.forEach.call(buttons.children, (button) => {
                if (button.dataset.num) button.classList.remove("active");
            });
            e.target.classList.add("active");
            renderContent(parseInt(e.target.dataset.num));
        });
        return button_kmj;
    }

    const renderContent = (page) => {
        // 목록 리스트 초기화
        while (contents.hasChildNodes()) {
            contents.removeChild(contents.lastChild);
        }
        // 글의 최대 개수를 넘지 않는 선에서, 화면에 최대 10개의 글 생성
        for (let id = (page - 1) * maxContent + 1; id <= page * maxContent && id <= numOfContent; id++) {
            contents.appendChild(makeContent(id - 1));
        }
    };
	
    // 5페이지 넘어가는 경우 6페이지로 넘어가거나 6페이지 이상에서 5페이지 이전으로 넘어올 때 필요한 함수
    const goPrevPage = () => {
        page -= maxButton;
        render(page);
    };

    const goNextPage = () => {
        page += maxButton;
        render(page);
    };
	
    // 5페이지 넘어갈 때 6페이지쪽으로 넘어가는 버튼과 다시 이전 페이지로 넘어가는 버튼
    const prev = document.createElement("button");
    prev.classList.add("button_kmj", "prev");
    prev.innerHTML = `<ion-icon name="chevron-back-outline"></ion-icon>`;
    prev.addEventListener("click", goPrevPage);

    const next = document.createElement("button");
    next.classList.add("button_kmj", "next");
    next.innerHTML = `<ion-icon name="chevron-forward-outline"></ion-icon>`;
    next.addEventListener("click", goNextPage);

    const renderButton = (page) => {
        // 버튼 리스트 초기화
        while (buttons.hasChildNodes()) {
            buttons.removeChild(buttons.lastChild);
        }
        // 화면에 최대 10개의 페이지 버튼 생성
        for (let id = page; id < page + maxButton && id <= maxPage; id++) {
            buttons.appendChild(makeButton(id));
        }
        // 첫 버튼 활성화(class="active")
        buttons.children[0].classList.add("active");

        buttons.prepend(prev);
        buttons.appendChild(next);

        // 이전, 다음 페이지 버튼이 필요한지 체크
        if (page - maxButton < 1) buttons.removeChild(prev);
        if (page + maxButton > maxPage) buttons.removeChild(next);
    };

    const render = (page) => {
        renderContent(page);
        renderButton(page);
    };
    render(page);
}
  • 이건 사실 내가 짠 코드는 아니다
  • 하루종일 구글링 한 결과 내가 이해할 수 있는 코드를 가져와서 우리 프로젝트에 맞게 아주 살짝 고친 거다
  • 다음에는 백엔드에서 페이지네이션 하는 걸 공부해서 구현해보겠다....!

<좋아요>

- 백엔드

# articles/urls.py
from django.urls import path
from . import views

urlpatterns = [
    ...
    path("like/<int:comment_id>/", views.LikeView.as_view(), name="like_view"),
    ...
]
  • 먼저 url을 설정해주고
# articles/views.py
class LikeView(APIView):
    def post(self, request, comment_id):
        """댓글 좋아요 누르기"""
        comment = get_object_or_404(Comment, id=comment_id)
        if request.user in comment.like.all():
            comment.like.remove(request.user)
            return Response("dislike", status=status.HTTP_200_OK)
        else:
            comment.like.add(request.user)
            return Response("like", status=status.HTTP_200_OK)
  • 좋아요 기능을 함수를 구현했다
  • HTTP status가 200으로 똑같기 때문에 프론트에서는 반환되는 텍스트값으로 좋아요와 좋아요 취소를 구현할 것이다

- 프론트엔드

// api.js
...
// 좋아요 누르기
async function likeClick(comment_id) {
	const comment = await getComment(comment_id);

	let token = localStorage.getItem("access");
	let clickLike = document.getElementById(`like-${comment_id}`);
	let clickDislike = document.getElementById(`dislike-${comment_id}`);

	const response = await fetch(
		`${backend_base_url}/api/articles/like/${comment_id}/`,
		{
			method: "POST",
			headers: {
				Authorization: `Bearer ${token}`
			}
		}
	);
	if (response.status == 401) {
		alert("로그인한 사용자만 좋아요를 누를 수 있습니다");
	}
	const response_json = await response.json();

	//좋아요 하트 색 변경
	if (response_json == "like") {
		clickLike.setAttribute("style", "display:flex;");
		clickDislike.setAttribute("style", "display:none;");
	} else if (response_json == "dislike") {
		clickLike.setAttribute("style", "display:none;");
		clickDislike.setAttribute("style", "display:flex;");
	}
}
...
  • 이전 프로젝트에서 구현했던 코드를 거의 그대로 가져와서 다시 썼다
  • 다만 게시글이 아닌 댓글에 구현되는 것이기 때문에 comment_id를 받아서 백엔드로 보낸다
  • html에서 하트 아이콘이 한 번 클릭되면 빨간 하트로 변하고 두 번 클릭되면 빈 하트로 변한다
  • 로그인 하지 않은 사용자는 좋아요 누를 수 없기 때문에 alert창을 띄우게끔 했다
// articleDetail.js
...
//좋아요 하트색 세팅
if (token) {
    let like = document.getElementById(`like-${comment.id}`);
    let dislike = document.getElementById(`dislike-${comment.id}`);
    login_user.like_comments.forEach((obj) => {
        if (comment.id == obj.id) {
            like.setAttribute("style", "display:flex;");
            dislike.setAttribute("style", "display:none;");
        }
    });
}
...
  • 처음에 상세페이지를 들어왔을 때 본인이 하트 누른 댓글은 빨간 하트, 누르지 않은 댓글은 빈 하트로 보여져야 하기 때문에 로그인 한 사용자의 경우 serializer를 통해 들어오는 사용자 정보 중 like_comments 필드의 댓글 id를 해당 댓글 id와 비교해서 일치하면 빨간 하트가 보이게끔 했다
  • 로그인 하지 않으면 본인인지 아닌지 알 수 없기 때문에 다 빈 하트로 보인다
  • js파일에서 만들지 않고 html에서 만들었다면 로그인 하지 않은 사용자한테는 하트 조차 보이지 않게 할 수 있을 것 같아서 약간 UI적으로 아쉬운 부분이 있다
  • 하지만 난 백엔드 개발자가 될 것이기 때문에 크게 아쉽진 않다..!!!

<북마크>

- 백엔드

# articles/urls.py
from django.urls import path
from . import views

urlpatterns = [
    ...
    path(
        "bookmark/<int:article_id>/", views.BookmarkView.as_view(), name="bookmark_view"
    ),
   ...
]
  • 좋아요랑 똑같이 urls.py를 작성하고 view.py에서 해당 함수를 만든다
# articles/views.py
class BookmarkView(APIView):
    def post(self, request, article_id):
        """게시글 북마크 하기"""
        article = get_object_or_404(Article, id=article_id)
        if request.user in article.bookmark.all():
            article.bookmark.remove(request.user)
            return Response("unbookmark", status=status.HTTP_200_OK)
        else:
            article.bookmark.add(request.user)
            return Response("bookmark", status=status.HTTP_200_OK)

- 프론트엔드

// api.js
...
// 북마크 누르기
async function bookmarkClick(article_id) {
	const article = await getArticle(article_id);

	let token = localStorage.getItem("access");
	let clickBookmark = document.getElementById(`bookmark-${article_id}`);
	let clickUnbookmark = document.getElementById(`unbookmark-${article_id}`);

	const response = await fetch(
		`${backend_base_url}/api/articles/bookmark/${article_id}/`,
		{
			method: "POST",
			headers: {
				Authorization: `Bearer ${token}`
			}
		}
	);
	if (response.status == 401) {
		alert("로그인한 사용자만 북마크 할 수 있습니다");
	}
	const response_json = await response.json();

	// 북마크 버튼 변경
	if (response_json == "bookmark") {
		clickUnbookmark.setAttribute("style", "display:flex;");
		clickBookmark.setAttribute("style", "display:none;");
	} else if (response_json == "unbookmark") {
		clickUnbookmark.setAttribute("style", "display:none;");
		clickBookmark.setAttribute("style", "display:flex;");
	}
}
...
  • 좋아요 기능과 거의 유사하다
  • 좋아요는 요소 안에 요소 안에 요소 안에 요소 막 이런 식으로 태그가 타고타고타고타야 돼서 그냥 백틱으로 js에서 html처럼 구현했지만 북마크는 비교적 간단해서 js에서 html 요소를 불러와서 set 해주는 걸로 했다
// ariticleDetail.js
...
// 북마크 버튼 세팅
async function setBookmarkBtn(article) {
	const login_user = await getLoginUser();
	if (login_user.id === article.owner.id) {
		const articleButtons = document.getElementById("btns");
		const updateButton = document.createElement("button");
		const deleteButton = document.createElement("button");

		updateButton.setAttribute("class", "article-btn btn");
		updateButton.setAttribute("type", "button");
		updateButton.innerText = "수정하기";
		updateButton.setAttribute("onclick", `articleUpdate(${article_id})`);

		deleteButton.setAttribute("class", "article-btn btn del-btn");
		deleteButton.setAttribute("type", "button");
		deleteButton.innerText = "삭제하기";
		deleteButton.setAttribute("onclick", `articleDelete(${article_id})`);

		articleButtons.appendChild(updateButton);
		articleButtons.appendChild(deleteButton);
	} else if (token) {
		const articleButtons = document.getElementById("btns");
		const bookmarkButton = document.createElement("img");
		const unbookmarkButton = document.createElement("img");

		bookmarkButton.setAttribute("src", "../static/image/bookmark.svg");
		bookmarkButton.setAttribute("class", "btn p-0 bookmarkbtn");
		bookmarkButton.setAttribute("type", "button");
		bookmarkButton.setAttribute("id", `bookmark-${article_id}`);
		bookmarkButton.innerText = "북마크 하기!";
		bookmarkButton.setAttribute("onclick", `bookmarkClick(${article_id})`);

		unbookmarkButton.setAttribute("src", "../static/image/bookmarked.svg");
		unbookmarkButton.setAttribute("class", "btn p-0 bookmarkbtn");
		unbookmarkButton.setAttribute("type", "button");
		unbookmarkButton.setAttribute("id", `unbookmark-${article_id}`);
		unbookmarkButton.setAttribute("style", "display:none;");
		unbookmarkButton.innerText = "북마크 취소..";
		unbookmarkButton.setAttribute("onclick", `bookmarkClick(${article_id})`);

		articleButtons.appendChild(bookmarkButton);
		articleButtons.appendChild(unbookmarkButton);
		let bookmark = document.getElementById(`bookmark-${article_id}`)
		let unbookmark = document.getElementById(`unbookmark-${article_id}`)
		login_user.bookmarks.forEach((obj) => {
			if (article_id == obj.id) {
				unbookmark.setAttribute("style", "display:flex;")
				bookmark.setAttribute("style", "display:none;")
			}
		});
	}
}
...
  • 이게 뭐야 싶겠지만 위에는 로그인 한 사용자와 게시글 작성자가 일치하는 경우에 수정, 삭제 버튼을 넣어주는 코드고 else if문이 북마크와 관련된 코드다
  • 위에 수정, 삭제 버튼과 유사한데 좋아요 버튼처럼 북마크 버튼은 누르면 색깔 칠한 북마크 아이콘과 흑백 아이콘을 왔다갔다 한다

예시 이미지를 넣으면 참 좋겠지만....TIL이 밀려서 기력이 딸린다...