티스토리 뷰

728x90


[ 세번째 프로젝트 ] 

 
 

AI를 이용한 소설 사이트를 만들어 보자.

 
 
 
 
++  팀 스로젝트로 팀과의 협업이 중요하다.
++  장고 공식 문서는 항상 확인하기 

https://docs.djangoproject.com/en/4.2/

 

++ 랭체인 공식 문서

https://www.langchain.com/

 

++ 리액트 공식문서

https://ko.legacy.reactjs.org/  # 한국어
https://ko.react.dev/


 
https://github.com/1489ehdghks/NOST.git

 

GitHub - 1489ehdghks/NOST

Contribute to 1489ehdghks/NOST development by creating an account on GitHub.

github.com

 



 

 

 

 

 

 

1. main booklist scss 정리하기 

이상태에서...ㅎㅎ...

예쁘게 한번 정리를 해보자.....ㅎㅎ...

 

 

댓글 부분 코드가 길어져서 분리가 필요함.

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useParams } from 'react-router-dom';
import useThemeStore from '../../shared/store/Themestore';
import BookComment from './BookComment';
import './BookDetail.scss';

const BookDetail = () => {
  const { id } = useParams();
  const { themes, currentSeason } = useThemeStore();
  const currentTheme = themes[currentSeason];
  const [bookData, setBookData] = useState(null);
  const [comments, setComments] = useState([]);

  useEffect(() => {
    axios.get(`http://127.0.0.1:8000/api/books/${id}/`)
      .then(response => {
        setBookData(response.data);
        setIsLiked(response.data.is_liked_by_user);  // 사용자가 좋아요를 눌렀는지 여부 설정
      })
      .catch(error => {
        console.error('Error fetching book data:', error);
      });

    axios.get(`http://127.0.0.1:8000/api/books/${id}/comments/`)
      .then(response => {
        setComments(response.data || []);
        console.log('comment :', response.data);
      })
      .catch(error => {
        console.error('Error fetching comments:', error);
      });
  }, [id]);

  return (
    <div className="bookdetail" style={{ color: currentTheme.buttonTextColor }}>
      {bookData && (
        <div className="summary" style={{ backgroundColor: currentTheme.buttonBackgroundColor }}>
          <h1>{bookData.title}</h1>
          {/* <img src={bookData.image} alt={bookData.header} /> */}
          {/* <p>{bookData.content}</p> */}
          <h3>Author : {bookData.user_nickname}</h3>
          <p>'❤️{bookData.is_liked.length} ⭐ {bookData.average_rating}/5</p>
        </div>
      )}
      <BookComment
        bookId={id}
        comments={comments}
        setComments={setComments}
        currentTheme={currentTheme}
      />
    </div>
  );
};

export default BookDetail;

 

.bookdetail {
  display: flex;
  justify-content: space-between;
  padding: 20px;
  margin-top: 50px;
  height: 100%; // 화면 전체 높이 설정
  overflow: auto; //스크롤 활성화
  scrollbar-width: none; /* 스크롤바 안보임 */
}

.summary {
  flex: 1.5;
  margin-right: 20px;
  padding: 20px;
  background-color: inherit;
  border-radius: 10px;
  margin-bottom: 100px;

  h1 {
    margin-bottom: 10px;
  }

  h3, p {
    margin-bottom: 10px;
    display: flex;
    justify-content: flex-end;
  }
  
  p {
    font-size: 16px;
    line-height: 1.5;
  }
}

 

import React, { useState } from 'react';
import axiosInstance from '../../features/auth/AuthInstance';
import './BookComment.scss';

const BookComment = ({ bookId, comments, setComments, currentTheme }) => {
  const [newComment, setNewComment] = useState('');
  const [editingCommentId, setEditingCommentId] = useState(null);
  const [updatedContent, setUpdatedContent] = useState('');
  const [visibleComments, setVisibleComments] = useState(5); // 현재 보이는 댓글 수를 관리하는 상태

  const handleAddComment = async () => {
    try {
      console.log('Adding comment:', newComment);
      const response = await axiosInstance.post(`http://127.0.0.1:8000/api/books/${bookId}/comments/`, {
        content: newComment,
      });
      console.log('Comment added successfully:', response.data);
      setComments([...comments, response.data]);
      setNewComment('');
    } catch (error) {
      console.error('Error adding comment:', error);
      alert('댓글을 추가하는 중에 오류가 발생했습니다.');
    }
  };

  const handleEditComment = async (commentId, updatedContent) => {
    try {
      const response = await axiosInstance.put(`http://127.0.0.1:8000/api/books/${bookId}/comments/${commentId}/`, {
        content: updatedContent,
      });

      setComments(comments.map(comment => comment.id === commentId ? response.data : comment));
      setEditingCommentId(null);
      setUpdatedContent('');
    } catch (error) {
      console.error('Error editing comment:', error);
      alert('댓글을 수정하는 중에 오류가 발생했습니다.');
    }
  };

  const handleDeleteComment = async (commentId) => {
    try {
      await axiosInstance.delete(`http://127.0.0.1:8000/api/books/${bookId}/comments/${commentId}/`);
      setComments(comments.filter(comment => comment.id !== commentId));
    } catch (error) {
      console.error('Error deleting comment:', error);
      alert('댓글을 삭제하는 중에 오류가 발생했습니다.');
    }
  };

  const buttonStyle = {
    backgroundColor: 'transparent',
    border: `1.5px solid ${currentTheme.buttonBackgroundColor}`,
    color: currentTheme.textColor,
    marginLeft: '5px'
  };

  return (
    <div className="comment-box" style={{ color: currentTheme.textColor }}>
      <h2>Comment Box</h2>
      <div className="comments">
        {comments.slice(0, visibleComments).map((comment) => (
          <div className="comment" key={comment.id}>
            <div className="comment-avatar" style={{ backgroundColor: currentTheme.buttonBackgroundColor, color: currentTheme.buttonTextColor }}>
              {comment.user_nickname.charAt(0).toUpperCase()}
            </div>
            <div className="comment-content">
              <p>{comment.content} </p>
              <p style={{ color: currentTheme.sidebarBg }}>{comment.user_nickname} <br />
                <small>on {new Date(comment.created_at).toLocaleDateString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit' })}</small>
                </p>
              <div>
                <button style={buttonStyle} onClick={() => { setEditingCommentId(comment.id); setUpdatedContent(comment.content); }}>Edit</button>
                <button style={buttonStyle} onClick={() => handleDeleteComment(comment.id)}>Delete</button>
                {editingCommentId === comment.id && (
                  <div>
                    <textarea value={updatedContent}
                      onChange={(e) => setUpdatedContent(e.target.value)}></textarea>
                    <button style={buttonStyle} onClick={() => handleEditComment(comment.id, updatedContent)}>Save</button>
                    <button style={buttonStyle} onClick={() => setEditingCommentId(null)}>Cancel</button>
                  </div>
                )}
              </div>
            </div>
          </div>
        ))}
        {visibleComments < comments.length && (
          <button style={buttonStyle} onClick={() => setVisibleComments(visibleComments + 5)}>더보기</button>
        )}
      </div>
      <textarea
          placeholder="Your comments"
          value={newComment}
          onChange={(e) => setNewComment(e.target.value)}>
        </textarea>
        <button style={{ backgroundColor: currentTheme.buttonBackgroundColor, color: currentTheme.buttonTextColor, marginBottom: '150px' }}
          onClick={handleAddComment}> Add </button>
    </div>
  );
};

export default BookComment;

 

.comment-box {
  flex: 1;
  margin-left: 50px;
  margin-bottom: 50px;
  
  h2 {
    margin-bottom: 10px;
  }

  textarea {
    width: 95%;
    color: inherit;
    height: 50px;
    padding: 10px;
    margin-bottom: 10px;
    border-radius: 5px;
    border: 1px solid #ccc;
    resize: none; /* 사용자가 크기를 조절하지 못하도록 설정합니다. */
    scrollbar-width: none; /* 스크롤바 안보임 */
  }
  
  button {
    padding: 10px 20px;
    color: #fff;
    border: none;
    border-radius: 5px;
    cursor: pointer;
  
    &:hover {
      background-color: #eceaea !important;
    }
  }
}

.comments {
  margin-bottom: 20px;
}

.comment {
  display: flex;
  align-items: flex-start;
  margin-bottom: 10px;
  
  p {
    font-size: 14px;
    line-height: 1.4;
  }
  
  n{
    margin-right: 10px;
  }

  button {
    padding: 5px 10px;
    color: inherit;
    border: none;
    border-radius: 5px;
    cursor: pointer;
  
    &:hover {
      background-color: #f6f5f5 !important;
    }
  }
}

.comment-avatar {
  width: 55px;
  height: 50px;
  min-width: 50px; /* 추가 */
  min-height: 50px; /* 추가 */
  background-color: inherit;
  border-radius: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 24px;
  font-weight: bold;
  color: inherit;
  margin-right: 10px;
}

.comment-content{
  textarea {
    width: 95%;
    height: 70px;
    color: inherit;
    padding: 10px;
    margin-top: 10px;
    margin-bottom: 5px;
    border-radius: 5px;
    border: 1px solid #ccc;
    resize: none; /* 사용자가 크기를 조절하지 못하도록 설정합니다. */
    scrollbar-width: none; /* 스크롤바 안보임 */
  }
}

 

 

 

5개씩 잘려서 댓글이 나오게 하고 더보기를 누르면 5개씩 더 보여주는 기능을 넣음.

동그란 부분은 사용자 닉네임의 첫글자를 가지고 와서 동그랗게 그림처럼 보여주는 기능을 넣음.

 

 

 

 

 

 

 

 

 

 

 

 

2. 이제 북 부분이 나오게 하자. 좋아요 기능 넣기

 

좋아요 참고 사이트 : https://heurm-tutorial.vlpt.us/12/04.html

 

좋아요 / 좋아요 취소 API 함수 및 액션 작성 · GitBook

12-4. 좋아요 / 좋아요 취소 API 함수 및 액션 작성 이제 좋아요 기능 구현을 위한 백엔드 작업은 끝났습니다. 프론트엔드 작업을 진행해볼까요? API 함수 만들기 우선, 방금 만든 API 를 요청 할 수

heurm-tutorial.vlpt.us

 

 

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useParams } from 'react-router-dom';
import useThemeStore from '../../shared/store/Themestore';
import axiosInstance from '../../features/auth/AuthInstance';
import BookComment from './BookComment';
import './BookDetail.scss';

const BookDetail = () => {
  const { id } = useParams();
  const { themes, currentSeason } = useThemeStore();
  const currentTheme = themes[currentSeason];
  const [bookData, setBookData] = useState(null);
  const [comments, setComments] = useState([]);
  const [isLiked, setIsLiked] = useState(false);

  useEffect(() => {
    axios.get(`http://127.0.0.1:8000/api/books/${id}/`)
      .then(response => {
        setBookData(response.data);
        setIsLiked(response.data.is_liked_by_user);  // 사용자가 좋아요를 눌렀는지 여부 설정
      })
      .catch(error => {
        console.error('Error fetching book data:', error);
      });

    axios.get(`http://127.0.0.1:8000/api/books/${id}/comments/`)
      .then(response => {
        setComments(response.data || []);
        console.log('comment :', response.data);
      })
      .catch(error => {
        console.error('Error fetching comments:', error);
      });
  }, [id]);

  const toggleLike = async () => {
    try {
      const response = await axiosInstance.post(`http://127.0.0.1:8000/api/books/${id}/like/`);
      const { like_bool, total_likes, book } = response.data;
      setIsLiked(like_bool);
      setBookData(book);
    } catch (error) {
      console.error('Error toggling like:', error);
    }
  };
  

  return (
    <div className="bookdetail" style={{ color: currentTheme.buttonTextColor }}>
      {bookData && (
        <div className="summary" style={{ backgroundColor: currentTheme.buttonBackgroundColor }}>
          <h1>{bookData.title}</h1>
          {/* <img src={bookData.image} alt={bookData.header} /> */}
          {/* <p>{bookData.content}</p> */}
          <h3>Author : {bookData.user_nickname}</h3>
          <p><button className="like"
          onClick={toggleLike}> {isLiked ? '❤️' : '♡'} </button> {bookData.is_liked.length} 
          ⭐ {bookData.average_rating}/5</p>
          
        </div>
      )}
      <BookComment
        bookId={id}
        comments={comments}
        setComments={setComments}
        currentTheme={currentTheme}
      />
    </div>
  );
};

export default BookDetail;

 

 .like {
    background-color: transparent; /* 배경 투명 */
    border: none; /* 버튼 라인 투명 */
    cursor: pointer; /* 마우스 포인터 모양 변경 */
  }

 

백엔드 books 의 views.py 도 수정해야해서 해준다.

class BookLikeAPIView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request, book_id):
        book = get_object_or_404(Book, id=book_id)
        like_bool = False
        # 좋아요 삭제
        if request.user in book.is_liked.all():
            book.is_liked.remove(request.user)
        # 좋아요 추가
        else:
            book.is_liked.add(request.user)
            like_bool = True
        serializer = BookLikeSerializer(book)
        return Response(
            {
                "like_bool": like_bool,
                "total_likes": book.total_likes(),
                "book": serializer.data,
            },
            status=200,
        )

 

이렇게 수정하면 하트를 눌렀을 때 디비에 저장되고 취소하면 없어진다.

내가 좋아요한 숫자만 올라간다.

 

 

 

 

 

 

 

 

 

 

 

 

 

3.  별점 기능 넣기 백엔드랑 연결

 

 

 


별점 참고 사이트 : https://velog.io/@io4408/React-%EB%A6%AC%EB%B7%B0-%EB%B3%84%EC%A0%90-%EA%B8%B0%EB%8A%A5%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0

 

[React] 리뷰, 별점 기능구현하기

리뷰, 별점기능 구현하기

velog.io

 

 

 

rating 관련해서 백엔드 변경

models.py

class Rating(models.Model) :
    RATING_CHOICES = [
        (1,"1"),
        (2,"2"),
        (3,"3"),
        (4,"4"),
        (5,"5"),
    ]
    book = models.ForeignKey(Book, related_name='ratings', on_delete=models.CASCADE)
    user_id = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="rating_user", on_delete=models.CASCADE)
    rating = models.PositiveIntegerField(blank=True)

    class Meta :
        constraints = [
            models.UniqueConstraint(fields=['book', 'user_id'], name = 'unique_book_user_rating')
        ]

------------------------------
views.py



class RatingAPIView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request, book_id):
        book = get_object_or_404(Book, id=book_id)
        rating = request.data.get("rating")

        if rating not in [1, 2, 3, 4, 5]:
            return Response("Rating must be between 1 and 5", status=400)

        existing_rating = Rating.objects.filter(book=book, user_id=request.user).exists()
        if existing_rating:
            return Response("You have already rated this book.", status=400)

        serializer = RatingSerializer(data={"rating": rating})
        if serializer.is_valid(raise_exception=True):
            serializer.save(user_id=request.user, book=book)
            return Response(serializer.data, status=200)
        return Response(status=400)

 

 

별점 5개 아이콘 넣기 위해서 리엑트의 아이콘을 다운받아준다.

npm install react-icons

 

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useParams } from 'react-router-dom';
import useThemeStore from '../../shared/store/Themestore';
import axiosInstance from '../../features/auth/AuthInstance';
import BookComment from './BookComment';
import './BookDetail.scss';
import { FaStar } from 'react-icons/fa';


const BookDetail = () => {
  const { id } = useParams();
  const { themes, currentSeason } = useThemeStore();
  const currentTheme = themes[currentSeason];
  const [bookData, setBookData] = useState(null);
  const [comments, setComments] = useState([]);
  const [isLiked, setIsLiked] = useState(false);
  const [rating, setRating] = useState(0);


  useEffect(() => {
    axios.get(`http://127.0.0.1:8000/api/books/${id}/`)
      .then(response => {
        setBookData(response.data);
        setIsLiked(response.data.is_liked);  // 사용자가 좋아요를 눌렀는지 여부 설정
        console.log('data : ', response.data);
      })
      .catch(error => {
        console.error('Error fetching book data:', error);
      });

    axios.get(`http://127.0.0.1:8000/api/books/${id}/comments/`)
      .then(response => {
        setComments(response.data || []);
      })
      .catch(error => {
        console.error('Error fetching comments:', error);
      });
  }, [id]);

  const toggleLike = async () => {
    try {
      const response = await axiosInstance.post(`http://127.0.0.1:8000/api/books/${id}/like/`);
      const { like_bool, total_likes, book } = response.data;
      setIsLiked(like_bool);
      setBookData(book);
    } catch (error) {
      console.error('Error toggling like:', error);
    }
  };

  const rateBook = async (newRating) => {
    try {
      const response = await axiosInstance.post(`http://127.0.0.1:8000/api/books/${id}/rating/`, { rating: newRating });
      const { rating, book } = response.data;
      console.log('ragingssssss : ', response.data);
      setBookData(book);
      setRating(rating); // 서버에서 업데이트된 평균 별점 설정
    } catch (error) {
      console.error('Error rating book:', error);
    }
  };

  const handleStarClick = (index) => {
    const newRating = index + 1; // 클릭된 별의 인덱스에 1을 더하여 새로운 별점을 설정합니다.
    rateBook(newRating); // 새로운 별점을 서버로 전송합니다.
    console.log('raging : ', newRating);
  };  

  return (
    <div className="bookdetail" style={{ color: currentTheme.buttonTextColor }}>
      {bookData && (
        <div className="summary" style={{ backgroundColor: currentTheme.buttonBackgroundColor }}>
          <h1>{bookData.title}</h1>
          {/* <img src={bookData.image} alt={bookData.header} /> */}
          {/* <p>{bookData.content}</p> */}
          <h3>Author : {bookData.user_nickname}</h3>
          <p>
            <button className="like" onClick={toggleLike}>
              {isLiked ? '❤️' : '♡'}
            </button>
            {bookData.total_likes} {bookData.is_liked ? 'Liked' : 'Likes'}
          </p>
          <div>
            {[...Array(5)].map((_, index) => (
              <FaStar
                key={index}
                onClick={() => handleStarClick(index)} // 클릭된 별의 인덱스를 handleStarClick 함수에 전달합니다.
                color={index < rating ? '#ffc107' : '#e4e5e9'}
                size={24}
                style={{ cursor: 'pointer' }}
              />
            ))}
            <p>Average Rating: {bookData.average_rating}/5</p>
          </div>

        </div>
      )}
      <BookComment
        bookId={id}
        comments={comments}
        setComments={setComments}
        currentTheme={currentTheme}
      />
    </div>
  );
};

export default BookDetail;

 

 

 

 

 

 

별점은 백엔드에 잘 저장된 것을 볼 수 있다.

그러나 프론트엔드에서 문제가 발생한다ㅜㅜ

 

++ 별점을 주자마자 제목 없어짐...ㅎㅎ

++ 별점을 한번만 줄 수 있는건 좋지만 총점이 바로 반영이 안됨

++ likes 도 바로 반영안되고 토탈을 눌러야 뜸... 바꿔야지ㅜㅜ

 

 

로컬 브렌치 삭제

git branch -D <브렌치 이름>

 

깃허브 원격 브렌치 삭제

git push origin -d <브렌치 이름>

 

 

 

 

 

 

 

 

 

 

 

4. 별점, 좋아요 오류 기능 수정 

 

 

  const rateBook = async (newRating) => {
    try {
      const response = await axiosInstance.post(`http://127.0.0.1:8000/api/books/${id}/rating/`, { rating: newRating });
      const { rating, book } = response.data;
      setRating(rating); // 서버에서 업데이트된 평균 별점 설정
    } catch (error) {
      console.error('Error rating book:', error);
  
      // 서버에서 받은 에러 메시지가 '이미 처리되었습니다'인지 확인하여 알림을 표시합니다.
      if (error.response && error.response.data === 'You have already rated this book.') {
        alert('이미 처리되었습니다');
      }
    }
  };

 

별점을 중복으로 줄 때 에러메세지 발생하게 만들어주자.

 

 

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useParams } from 'react-router-dom';
import useThemeStore from '../../shared/store/Themestore';
import axiosInstance from '../../features/auth/AuthInstance';
import BookComment from './BookComment';
import './BookDetail.scss';
import { FaStar } from 'react-icons/fa';
import { FaHeart } from 'react-icons/fa';

const BookDetail = () => {
  const { id } = useParams();
  const { themes, currentSeason } = useThemeStore();
  const currentTheme = themes[currentSeason];
  const [bookData, setBookData] = useState(null);
  const [comments, setComments] = useState([]);
  const [rating, setRating] = useState(0); // 별점 상태 추가
  const [books, setBooks] = useState([]);
  const [like_bool, setIsLiked] = useState(false);

  useEffect(() => {
    axios.get(`http://127.0.0.1:8000/api/books/${id}/`)
      .then(response => {
        setBookData(response.data);
        setIsLiked(response.data.is_liked);
        console.log('data : ', response.data);
      })
      .catch(error => {
        console.error('Error fetching book data:', error);
      });

    axios.get(`http://127.0.0.1:8000/api/books/${id}/comments/`)
      .then(response => {
        setComments(response.data || []);
      })
      .catch(error => {
        console.error('Error fetching comments:', error);
      });
  }, [id]);

  const toggleLike = async () => {
    try {
      const response = await axiosInstance.post(`http://127.0.0.1:8000/api/books/${id}/like/`);
      const { like_bool, total_likes } = response.data;
      setIsLiked(like_bool);
      setBookData(prevBookData => ({
        ...prevBookData,
        is_liked: like_bool,
        total_likes: total_likes
      }));
      const updatedBooks = books.map(book => {
        if (book.id === id) {
          return {...book, is_liked: like_bool};
        }
        return book;
      });
      setBooks(updatedBooks);
    } catch (error) {
      console.error('Error toggling like:', error);
    }
  };

  const rateBook = async (newRating) => {
    try {
      const response = await axiosInstance.post(`http://127.0.0.1:8000/api/books/${id}/rating/`, { rating: newRating });
      const { rating } = response.data;
      setBookData(prevBookData => ({
        ...prevBookData,
        user_rating: rating
      }));
      setRating(rating); // 사용자가 별점을 준 경우 별점 상태 업데이트
  
      // 사용자의 최신 별점을 가져와서 업데이트
      userRate();
    } catch (error) {
      console.error('Error rating book:', error);
      if (error.response && error.response.data === 'You have already rated this book.') {
        alert('이미 처리되었습니다');
      }
    }
  };

  const userRate = async () => {
    try {
      const response = await axiosInstance.get(`http://127.0.0.1:8000/api/books/${id}/rating/`);
      const { rating } = response.data;
      setRating(rating); // 사용자의 별점 업데이트
    } catch(error) {
      if (error.response && error.response.status === 404) {
        // 만약 책에 대한 평가가 없는 경우 평점을 0으로 설정
        setRating(0);
      } else {
        console.error('Error fetching user rating:', error);
      }
    }
  };

  useEffect(() => {
    // 페이지 로드 시 사용자의 별점을 가져오기 위해 호출
    userRate();
  }, []);

  const handleStarClick = (index) => {
    const newRating = index + 1;
    rateBook(newRating);
    console.log('raging : ', newRating);
  };  

  return (
    <div className="bookdetail" style={{ color: currentTheme.buttonTextColor }}>
      {bookData && (
        <div className="summary" style={{ backgroundColor: currentTheme.buttonBackgroundColor }}>
          <h1>{bookData.title}</h1>
          <h3>Author : {bookData.user_nickname}</h3>
          <div>
            <p> like {bookData.total_likes} 
              <span onClick={toggleLike}>
                <FaHeart
                  color={bookData.is_liked ? '#ff0707' : '#ffffff'}
                  size={20}
                  style={{ marginLeft: '10px', cursor: 'pointer' }}
                />
              </span>
            </p>
            <p> {rating ? `Your Rating: ${rating}/5` : `Please Rate This Book`}
              <span style={{ marginLeft: '10px' }}>
                {[...Array(5)].map((_, index) => (
                  <FaStar
                    key={index}
                    onClick={() => handleStarClick(index)}
                    color={index < rating ? '#fce146' : '#ffffff'}
                    size={24}
                    style={{ cursor: 'pointer' }}
                  />
                ))}</span>
            </p>
          </div>
        </div>
      )}
      <BookComment
        bookId={id}
        comments={comments}
        setComments={setComments}
        currentTheme={currentTheme}
      />
    </div>
  );
};

export default BookDetail;

 

 

이 코드는 일단 좋아요를 누를때 하나씩 올랐다가 한번 더 누르면 취소가 되어서 숫자가 줄어든다.

(++ 처음 들어갔을 때는 총 개수가 먼저 안뜨고 하트만 있어서 나중에 수정해야할것같다.)

 

별점은 별점 평균이 안보이고 내가 준 별점만 보이도록 수정하였다.

이때 포스트만으로는 개인 사용자의 별점을 가져올 수 없어서 백엔드도 수정해야했다.

 

class RatingAPIView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request, book_id):
        book = get_object_or_404(Book, id=book_id)
        rating = request.data.get("rating")

        if rating not in [1, 2, 3, 4, 5]:
            return Response("Rating must be between 1 and 5", status=400)

        existing_rating = Rating.objects.filter(book=book, user_id=request.user).exists()
        if existing_rating:
            return Response("You have already rated this book.", status=400)

        serializer = RatingSerializer(data={"rating": rating})
        if serializer.is_valid(raise_exception=True):
            serializer.save(user_id=request.user, book=book)
            return Response(serializer.data, status=200)
        return Response(status=400)

    def get(self, request, book_id):
        book = get_object_or_404(Book, id=book_id)
        user_rating = Rating.objects.filter(book=book, user_id=request.user.id).first()
        if user_rating:
            serializer = RatingSerializer(user_rating)
            return Response(serializer.data, status=200)
        return Response("User has not rated this book yet.", status=404)


views.py 에서 rating 겟방식으로 가져올수있게 수정

 

내가 준 별점으로 잘 보이지만 별점을 주지 않은 북으로 갔을 때는 에러가 발생하였다.

 

 

<< 이러한 문제가 발생하는 이유는 프론트엔드에서 컴포넌트가 마운트될 때 자동으로 실행되는 useEffect 훅에서 userRate 함수가 호출되기 때문입니다. useEffect 훅은 컴포넌트가 마운트될 때, 그리고 id가 변경될 때마다 호출되는데, 이 때 userRate 함수가 실행되어 서버에 사용자의 평점을 가져오려고 시도합니다.

그러나 사용자가 별점을 주지 않은 경우에도 userRate 함수가 호출되어 서버에 요청을 보내게 되는데, 이 때 백엔드에서는 해당 엔드포인트를 찾을 수 없어 404 에러가 발생합니다.

이 문제를 해결하기 위해서는 userRate 함수가 실행되는 조건을 변경해야 합니다. 사용자가 별점을 준 후에만 userRate 함수가 실행되도록 수정해야 합니다. 이를 위해서는 사용자가 별점을 준 경우에만 userRate 함수를 호출하도록 코드를 수정해야 합니다. >>

 

이런 문제가 발생한다고 하여 코드를 수정해주어야한다.

 

점수를 안줘서 먼저 404에러가 뜨는 것 같은데...

  const userRate = async () => {
    try {
      const response = await axiosInstance.get(`http://127.0.0.1:8000/api/books/${id}/rating/`);
      const { rating } = response.data;
      setRating(rating); // 사용자의 별점 업데이트
    } catch(error) {
      if (error.response && error.response.status === 404) {
        // 만약 책에 대한 평가가 없는 경우 평점을 0으로 설정
        setRating(0);
      } else {
        console.error('Error fetching user rating:', error);
      }
    }
  };

 

일단 console 창에는 이렇게 뜨게 수정을 해두었다.

값이 없는 경우의 에러메세지는 크게 영향을 미치지 않는다고 하는데... 글쎄..ㅠㅠ 그건 배포해봐야 알듯... 일단 이렇게 두고 넘어가겠다...ㅠㅠ

 

 

반응형
반응형
TAG
more
최근에 올라온 글