티스토리 뷰

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.  마이 북 리스트에 보이는 책 화면을 바꾸어보자.

 

제목을 눌렀을 때 내용이 좋아요 몇개와 평점을 얼마 받았는지 보일 수 있게 바꾸어야 한다.

 

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}> &laquo; </button>
                <button onClick={() => handleClick(currentPage - 1)} disabled={currentPage === 1}> &lt; </button>
                {generatePagination().map((page) => (
                    <button 
                        key={page} 
                        onClick={() => handleClick(page)}
                        className={currentPage === page ? 'active' : ''}>
                        {page}
                    </button>
                ))}
                <button onClick={() => handleClick(currentPage + 1)} disabled={currentPage === totalPages}> &gt; </button>
                <button onClick={() => handleClick(totalPages)} disabled={currentPage === totalPages}> &raquo; </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}> &laquo; </button>
                <button onClick={() => handleClick(currentPage - 1)} disabled={currentPage === 1}> &lt; </button>
                {generatePagination().map((page) => (
                    <button
                        key={page}
                        onClick={() => handleClick(page)}
                        className={currentPage === page ? 'active' : ''}>
                        {page}
                    </button>
                ))}
                <button onClick={() => handleClick(currentPage + 1)} disabled={currentPage === totalPages}> &gt; </button>
                <button onClick={() => handleClick(totalPages)} disabled={currentPage === totalPages}> &raquo; </button>
            </div>
        </div>
    );
};

export default BookList;

 

 

ㅜㅜㅜㅜ 해결 완료!

 

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