티스토리 뷰
24.06.03_TIL ( 팀 프로젝트 : AI NOST Django ) _ 8. My book list 좋아요, 평점 수정, 게시글에 북리스트 가져오기 (장고 시드)
티아(tia) 2024. 6. 3. 10:11
[ 세번째 프로젝트 ]
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
1. 마이 북 리스트에 보이는 책 화면을 바꾸어보자.
제목을 눌렀을 때 내용이 좋아요 몇개와 평점을 얼마 받았는지 보일 수 있게 바꾸어야 한다.
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import useThemeStore from '../../shared/store/Themestore';
import './Mybooklist.scss';
const Card = ({ id, image, header, likes, rating, onClick }) => {
return (
<div className="card" style={{ backgroundImage: `url(${image})` }} onClick={() => onClick(id)}>
<div className="card-header"><h1>{header}</h1></div>
<div className="card-content">
<p> ❤️ {likes}</p>
<p>
{'⭐'.repeat(Math.min(Math.floor(rating), 5))}
{rating % 1 !== 0 ? (rating % 1 >= 0.5 ? '⭐' : '☆') : ''}
{'☆'.repeat(Math.max(5 - Math.ceil(rating), 0))} {rating} / 5
</p>
</div>
</div>
);
};
...
const Mybooklist = () => {
const { themes, currentSeason } = useThemeStore();
const currentTheme = themes[currentSeason];
const cards = [
{
id: 1,
image: 'https://images.unsplash.com/photo-1479660656269-197ebb83b540?dpr=2&auto=compress,format&fit=crop&w=1199&h=798&q=80&cs=tinysrgb&crop=',
header: 'Canyons',
likes: 4,
rating: 4.5,
},
...
return (
<div className="container" style={{ color: currentTheme.textColor }}>
<h1 className="title">My Book List</h1>
<div className="cardlist">
{currentCards.map((card) => (
<Card
key={card.id}
id={card.id}
image={card.image}
header={card.header}
likes={card.likes}
rating={card.rating}
onClick={handleCardClick}
/>
))}
</div>
<div className="pagination">
...
</div>
</div>
);
};
export default Mybooklist;
.container {
text-align: center;
padding: 2rem;
padding-bottom: 10vh;
height: 100%; // 화면 전체 높이 설정
overflow: auto; //스크롤 활성화
scrollbar-width: none; /* 스크롤바 안보임 */
}
...
.card-header {
position: absolute;
bottom: 1rem;
width: 100%;
background: transparent;
color: #fff;
padding: 1rem;
text-align: left;
transition: transform 0.3s ease;
h1 {
margin: 0;
font-size: 1.5rem;
}
}
.card-content {
display: none;
color: #fff;
padding: 1rem;
box-sizing: border-box;
text-align: left;
position: absolute;
bottom: 1rem;
left: 0;
right: 0;
z-index: 2;
max-height: 6rem; // 2줄 높이
overflow: hidden;
p {
margin: 0;
}
}
&:hover .card-header {
transform: translateY(-100%);
}
&:hover .card-content {
display: block;
}
}
...
이렇게 별이 총 5개여야 하는데 4.2 면 ⭐⭐⭐⭐☆ 이고, 4.8이면 ⭐⭐⭐⭐⭐ 이고, 4 이면 ⭐⭐⭐⭐☆이고, 3이면 ⭐⭐⭐☆☆ 이렇게 나타나게 수정한다.
2. 게시글에 북 리스트 가져오기
db에 시드로 저장되어 있는 북 리스트랑 연결해서 가져와보자.
지금은 아직 프론트앤드에서 작성해서 화면을 보여주고 있다.
pip install django-seed
- settings.py 에서 app등록
이렇게 에러가 떠버렸다면?
ModuleNotFoundError: No module named 'psycopg2'
설치해주어야 한다.
pip install psycopg2
설치한 후 다시 seeding 을 해준다.
python manage.py seed books --number=10 //글 10개 생성
books 에 관련된 comment 나 rating, liked 가 10개씩 랜덤으로 생긴것을 볼 수 있다.
- django seed를 특정 article 에 댓글을 20개 생성해보자.
python manage.py seed books --number=5 --seeder "Comment.book_id" 1
음... 어디에 생성됐는지 모르겠다ㅜㅜ 해결이 안되네... 일단 연결을 해보자.
src/ pages/ main/ component/ Booklist.jsx 수정하기
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import useThemeStore from '../../../shared/store/Themestore';
import './BookList.scss';
const BookList = () => {
const { themes, currentSeason } = useThemeStore(); // 테마 설정 사용
const currentTheme = themes[currentSeason]; // 현재 시즌 테마 색상
const [sortOption, setSortOption] = useState('newest'); // 초기 정렬기준
const [books, setBooks] = useState([]);
const [loading, setLoading] = useState(true); // 로딩 상태
useEffect(() => {
fetchNovels(); // 컴포넌트 마운트 시 데이터 가져오기
}, []);
useEffect(() => {
sortNovels(sortOption); // 정렬 기준 변경 시 소설 목록 정렬
}, [sortOption]);
const fetchNovels = async () => {
try {
const response = await axios.get('http://127.0.0.1:8000/api/books/'); // 백엔드 API 호출
console.log('Fetched novels:', response.data); // 응답 데이터 콘솔에 출력
if (Array.isArray(response.data.results)) {
setBooks(response.data.results); // results 배열로 설정
} else {
console.error('Fetched data is not an array:', response.data);
setBooks([]); // 응답 데이터가 배열이 아닌 경우 빈 배열로 설정
}
setLoading(false);
} catch (error) {
console.error('Error fetching novels:', error);
setBooks([]); // 오류 발생 시 빈 배열로 설정
setLoading(false);
}
};
const handleSortChange = (e) => {
const { value } = e.target;
setSortOption(value); // 정렬 기준 변경
};
const sortNovels = (criteria) => {
const sortedNovels = [...books];
switch (criteria) {
case 'newest':
sortedNovels.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
break;
case 'popular':
sortedNovels.sort((a, b) => b.is_liked - a.is_liked);
break;
case 'rating':
sortedNovels.sort((a, b) => b.average_rating - a.average_rating);
break;
default:
break;
}
setBooks(sortedNovels); // 정렬된 목록 설정
};
if (loading) {
return <div>Loading...</div>; // 로딩 중일 때 표시할 컴포넌트
}
return (
<div className="book-list section" style={{ backgroundColor: currentTheme.mainpageBackgroundColor, color: currentTheme.textColor }}>
<div className="header">
<select value={sortOption} onChange={handleSortChange} style={{ backgroundColor: currentTheme.buttonBackgroundColor, color: currentTheme.buttonTextColor }}>
<option value="newest">Newest</option>
<option value="popular">Most Popular</option>
<option value="rating">Highest Rated</option>
</select>
</div>
<table>
<thead>
<tr>
<th>Novel</th>
<th>Author</th>
<th>Likes</th>
<th>Rating</th>
<th>Created At</th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<tr key={book.id}>
<td>{book.title}</td>
<td>{book.user_id}</td>
<td>{book.is_liked}</td>
<td>{book.average_rating}</td>
<td>{new Date(book.created_at).toLocaleDateString()}</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default BookList;
console.log 로 보면서 백엔드에 어떤 이름으로 연결되어 있는지 보고 이름을 넣어야 한다ㅜㅜ
다행히 정보가 잘 들어간것을 볼 수 있다.
++ like 를 누가 선택했는지가 아니라 몇명이나 선택했는지로 바꾸어야 한다.
배열의 길이로 is_liked: (3) [7, 13, 14] 몇명이 선택했는지 출력할 수 있다.
...
const sortNovels = (criteria) => {
const sortedNovels = [...books];
switch (criteria) {
case 'newest':
sortedNovels.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
break;
case 'popular':
sortedNovels.sort((a, b) => b.is_liked.length - a.is_liked.length);
break;
case 'rating':
sortedNovels.sort((a, b) => b.average_rating - a.average_rating);
break;
default:
break;
}
setBooks(sortedNovels); // 정렬된 목록 설정
};
...
<tbody>
{books.map((book) => (
<tr key={book.id}>
<td>{book.title}</td>
<td>{book.user_id}</td>
<td>{book.is_liked.length}</td> {/* 배열의 길이로 좋아요 수 출력 */}
<td>{book.average_rating}</td>
<td>{new Date(book.created_at).toLocaleDateString()}</td>
</tr>
))}
</tbody>
...
3. 게시판 페이지 네이션 만들기
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import useThemeStore from '../../../shared/store/Themestore';
import './BookList.scss';
const BookList = () => {
const { themes, currentSeason } = useThemeStore(); // 테마 설정 사용
const currentTheme = themes[currentSeason]; // 현재 시즌 테마 색상
const [sortOption, setSortOption] = useState('newest'); // 초기 정렬기준
const [books, setBooks] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
useEffect(() => {
fetchNovels(currentPage);
}, [currentPage]);
useEffect(() => {
sortNovels(sortOption); // 정렬 기준 변경 시 소설 목록 정렬
}, [sortOption]);
const fetchNovels = async (page = 1) => {
try {
const response = await axios.get(`http://127.0.0.1:8000/api/books/?page=${page}`); // 백엔드 API 호출
console.log('Fetched novels:', response.data); // 응답 데이터 콘솔에 출력
if (Array.isArray(response.data.results)) {
setBooks(response.data.results);
const totalItems = response.data.count;
const itemsPerPage = response.data.results.length;
setTotalPages(Math.ceil(totalItems / itemsPerPage));
} else {
console.error('Fetched data is not an array:', response.data);
setBooks([]); // 응답 데이터가 배열이 아닌 경우 빈 배열로 설정
}
} catch (error) {
console.error('Error fetching novels:', error);
setBooks([]); // 오류 발생 시 빈 배열로 설정
}
};
const handleSortChange = (e) => {
const { value } = e.target;
setSortOption(value); // 정렬 기준 변경
};
const sortNovels = (criteria) => {
const sortedNovels = [...books];
switch (criteria) {
case 'newest':
sortedNovels.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
break;
case 'popular':
sortedNovels.sort((a, b) => b.is_liked.length - a.is_liked.length);
break;
case 'rating':
sortedNovels.sort((a, b) => b.average_rating - a.average_rating);
break;
default:
break;
}
setBooks(sortedNovels); // 정렬된 목록 설정
};
const handleClick = (page) => {
if (page >= 1 && page <= totalPages) {
setCurrentPage(page);
}
};
const generatePagination = () => {
const paginationArray = [];
for (let i = 1; i <= totalPages; i++) {
paginationArray.push(i);
}
return paginationArray;
};
return (
<div className="book-list section" style={{ backgroundColor: currentTheme.mainpageBackgroundColor, color: currentTheme.textColor }}>
<div className="header">
<select value={sortOption} onChange={handleSortChange} style={{ backgroundColor: currentTheme.buttonBackgroundColor, color: currentTheme.buttonTextColor }}>
<option value="newest">Newest</option>
<option value="popular">Most Popular</option>
<option value="rating">Highest Rated</option>
</select>
</div>
<table>
<thead>
<tr>
<th>Novel</th>
<th>Author</th>
<th>Likes</th>
<th>Rating</th>
<th>Created At</th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<tr key={book.id}>
<td>{book.title}</td>
<td>{book.user_id}</td>
<td>{book.is_liked.length}</td> {/* 배열의 길이로 좋아요 수 출력 */}
<td>{book.average_rating}</td>
<td>{new Date(book.created_at).toLocaleDateString()}</td>
</tr>
))}
</tbody>
</table>
<div className="pagination">
<button onClick={() => handleClick(1)} disabled={currentPage === 1}> « </button>
<button onClick={() => handleClick(currentPage - 1)} disabled={currentPage === 1}> < </button>
{generatePagination().map((page) => (
<button
key={page}
onClick={() => handleClick(page)}
className={currentPage === page ? 'active' : ''}>
{page}
</button>
))}
<button onClick={() => handleClick(currentPage + 1)} disabled={currentPage === totalPages}> > </button>
<button onClick={() => handleClick(totalPages)} disabled={currentPage === totalPages}> » </button>
</div>
</div>
);
};
export default BookList;
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin-top: 20px;
margin-bottom: 50px;
button {
border: none;
background: none;
font-size: 16px;
padding: 10px;
cursor: pointer;
color: inherit;
&:hover:not(.active) {
background-color: transparent; // 배경색을 투명으로 설정
}
}
.active {
background-color: #d4d4d4;
color: white;
border-radius: 5px;
}
}
++ 페이지 네이션은 잘 되는데 24개 전체로 정렬을 하는게 아니라 페이지당 정렬을 해서 수정해야한다.
++ 백엔드에서 페이지네이션을 해서 8개로 쪼개서 보내서 안되는 것임....ㅠㅠ 백엔드에서 수정해서 넘겨주기로함
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import useThemeStore from '../../../shared/store/Themestore';
import './BookList.scss';
const BookList = () => {
const { themes, currentSeason } = useThemeStore(); // 테마 설정 사용
const currentTheme = themes[currentSeason]; // 현재 시즌 테마 색상
const [sortOption, setSortOption] = useState('newest'); // 초기 정렬기준
const [books, setBooks] = useState([]);
const [currentPage, setCurrentPage] = useState(1); // 현재 페이지
const booksPerPage = 8; // 페이지 당 보여질 책의 개수
useEffect(() => {
fetchNovels();
}, []);
useEffect(() => {
sortNovels(sortOption); // 정렬 기준 변경 시 소설 목록 정렬
}, [sortOption]);
const fetchNovels = async () => {
try {
const response = await axios.get('http://127.0.0.1:8000/api/books/'); // 백엔드 API 호출
console.log('Fetched novels:', response.data); // 응답 데이터 콘솔에 출력
if (Array.isArray(response.data)) {
setBooks(response.data);
} else {
console.error('Fetched data is not an array:', response.data);
setBooks([]); // 응답 데이터가 배열이 아닌 경우 빈 배열로 설정
}
} catch (error) {
console.error('Error fetching novels:', error);
setBooks([]); // 오류 발생 시 빈 배열로 설정
}
};
const handleSortChange = (e) => {
const { value } = e.target;
setSortOption(value); // 정렬 기준 변경
};
const sortNovels = (criteria) => {
const sortedNovels = [...books];
switch (criteria) {
case 'newest':
sortedNovels.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
break;
case 'popular':
sortedNovels.sort((a, b) => b.is_liked.length - a.is_liked.length);
break;
case 'rating':
sortedNovels.sort((a, b) => b.average_rating - a.average_rating);
break;
default:
break;
}
setBooks(sortedNovels); // 정렬된 목록 설정
};
const indexOfLastBook = currentPage * booksPerPage;
const indexOfFirstBook = indexOfLastBook - booksPerPage;
const currentBooks = books.slice(indexOfFirstBook, indexOfLastBook);
const totalPages = Math.ceil(books.length / booksPerPage);
const handleClick = (page) => {
setCurrentPage(page);
};
const generatePagination = () => {
const pageNumbers = [];
for (let i = 1; i <= totalPages; i++) {
pageNumbers.push(i);
}
return pageNumbers;
};
return (
<div className="book-list section" style={{ backgroundColor: currentTheme.mainpageBackgroundColor, color: currentTheme.textColor }}>
<div className="header">
<select value={sortOption} onChange={handleSortChange} style={{ backgroundColor: currentTheme.buttonBackgroundColor, color: currentTheme.buttonTextColor }}>
<option value="newest">Newest</option>
<option value="popular">Most Popular</option>
<option value="rating">Highest Rated</option>
</select>
</div>
<table>
<thead>
<tr>
<th>Novel</th>
<th>Author</th>
<th>Likes</th>
<th>Rating</th>
<th>Created At</th>
</tr>
</thead>
<tbody>
{currentBooks.map((book) => (
<tr key={book.id}>
<td>{book.title}</td>
<td>{book.user_id}</td>
<td>{book.is_liked.length}</td> {/* 배열의 길이로 좋아요 수 출력 */}
<td>{book.average_rating}</td>
<td>{new Date(book.created_at).toLocaleDateString()}</td>
</tr>
))}
</tbody>
</table>
<div className="pagination">
<button onClick={() => handleClick(1)} disabled={currentPage === 1}> « </button>
<button onClick={() => handleClick(currentPage - 1)} disabled={currentPage === 1}> < </button>
{generatePagination().map((page) => (
<button
key={page}
onClick={() => handleClick(page)}
className={currentPage === page ? 'active' : ''}>
{page}
</button>
))}
<button onClick={() => handleClick(currentPage + 1)} disabled={currentPage === totalPages}> > </button>
<button onClick={() => handleClick(totalPages)} disabled={currentPage === totalPages}> » </button>
</div>
</div>
);
};
export default BookList;
ㅜㅜㅜㅜ 해결 완료!