티스토리 뷰
AI웹 개발자 과정 공부 (팀스파르타)/프로젝트
24.06.11_TIL ( 팀 프로젝트 : AI NOST Django ) _ 13. dall_e로 생성된 이미지 백엔드와 프론트엔드 연결, 글 삭제, 회원가입 탈퇴
티아(tia) 2024. 6. 11. 15:35반응형
[ 세번째 프로젝트 ]
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. 백엔드를 조금 수정해야 한다.
views.py
class DALL_EImageAPIView(APIView):
def post(self, request, book_id):
client = OpenAI()
book = get_object_or_404(Book, id=book_id)
response = client.images.generate(
model="dall-e-3",
prompt=f"{book.title}, {book.tone}",
size="1024x1024",
quality="standard",
n=1,
)
res = requests.get(response.data[0].url)
book.image = ContentFile(res.content, name = f'{book.title}.png')
book.save()
serializer = BookSerializer(instance=book, context={'request': request})
return Response(serializer.data, status = 200)
Serializer.py
class BookSerializer(serializers.ModelSerializer):
average_rating = serializers.SerializerMethodField()
user_nickname = serializers.SerializerMethodField()
chapters = ChapterSerializer(many=True, read_only=True)
class Meta:
model = Book
fields = "__all__"
def get_average_rating(self, book):
return Rating.objects.filter(book=book).aggregate(Avg("rating"))["rating__avg"]
def get_user_nickname(self, book):
return book.user_id.nickname
def get_chapters(self,book) :
return book.full_text.content
def get_image_url(self, obj):
request = self.context.get('request')
if obj.image:
return request.build_absolute_uri(obj.image.url)
return None
config/ urls.py
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path("admin/", admin.site.urls),
path("api/accounts/", include("accounts.urls")),
path("api/books/", include("books.urls")),
# spectacular
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
path(
"api/schema/swagger-ui/",
SpectacularSwaggerView.as_view(url_name="schema"),
name="swagger-ui",
),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
2. 포스트맨에서 이미지 생성 및 프론트엔드와 연결
이렇게 만들고 나면 url 주소로 되는 것이 아니라 백엔드 폴더에 저장되는 것을 볼 수 있다.
이렇게 폴더에서 이미지를 받는 것은 주소 모양이 다르다.
const Card = ({ id, image, header, likes, rating, onClick }) => {
const defaultImage = 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQT6uhVlGoDqJhKLfS9W_HQOoWJCf-_lsBZzw&s'; // 기본 이미지 URL을 설정합니다.
const backgroundImage = image ? `http://127.0.0.1:8000${image}` : defaultImage; // Update this line to use the full URL
return (
<div className="card" style={{ backgroundImage: `url(${backgroundImage})` }} 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>
);
};
프론트엔드 mybooklist 부분의 이미지 주소를 받는 곳을 바꾸어 주어야 한다.
이렇게 이미지를 보여주는 것을 볼 수 있다.
3. 글 삭제하기 만들기
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useParams, useNavigate } from 'react-router-dom';
import useThemeStore from '../../shared/store/Themestore';
import BookComment from './BookComment';
import BookLike from './BookLike';
import BookRating from './BookRating';
import axiosInstance from '../../features/auth/AuthInstance';
import './BookDetail.scss';
const BookDetail = () => {
const { id } = useParams();
const navigate = useNavigate();
...
const handleDeleteBook = async () => {
try {
await axiosInstance.delete(`http://127.0.0.1:8000/api/books/${id}/`);
// 삭제 성공 시, 메인 페이지로 이동
navigate('/main');
} catch (error) {
console.error('Error deleting book:', error);
}
};
const buttonStyle = {
backgroundColor: 'transparent',
border: `1.5px solid ${currentTheme.secondary}`,
color: currentTheme.buttonTextColor,
marginLeft: '5px'
};
...
return (
<div className="bookdetail" style={{ color: currentTheme.buttonTextColor }}>
...
<button
style={buttonStyle}>
Delete Book
</button>
...
export default BookDetail;
4. 회원탈퇴 백엔드와 연결하기
백엔드 부분을 수정할 것이 있어서 수정해주어야한다.
profile 로 연결되는 것이 맞고 delete 부분에 비밀번호와 토큰을 받아서 삭제해주어야 한다.
accounts / views.py
def delete(self, request, *args, **kwargs):
user = self.request.user
password = request.data.get("password")
refresh_token = request.data.get("refresh_token")
if not password:
return Response(
{"message": "비밀번호가 제공되지 않았습니다."},
status=status.HTTP_400_BAD_REQUEST,
)
if not user.check_password(password):
return Response(
{"message": "비밀번호가 일치하지 않습니다."},
status=status.HTTP_400_BAD_REQUEST,
)
try:
if refresh_token:
token = RefreshToken(refresh_token)
token.blacklist()
except Exception as e:
return Response({"message": "토큰 블랙리스트 처리 중 오류 발생"}, status=status.HTTP_400_BAD_REQUEST)
try:
# 사용자 비활성화
# user.is_active=False
# user.save()
user.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except Exception as e:
return Response(status=status.HTTP_400_BAD_REQUEST)
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
const useAuthStore = create(persist(
(set) => ({
token: null,
refreshToken: null,
isLoggedIn: false,
userId: null,
nickname: null,
email: null,
setToken: (token) => set({ token }),
setRefreshToken: (refreshToken) => set({ refreshToken }),
setIsLoggedIn: (isLoggedIn) => set({ isLoggedIn }),
setUserId: (userId) => set({ userId }),
setNickname: (nickname) => set({ nickname }),
setEmail: (email) => set({ email }),
logout: () => set({ token: null, refreshToken: null, isLoggedIn: false, userId: null, nickname: null, email: null }),
// 상태 초기화 함수 추가
reset: () => set({ token: null, refreshToken: null, isLoggedIn: false, userId: null, nickname: null, email: null })
}),
{
name: 'auth_store',
partialize: (state) => ({
token: state.token,
isLoggedIn: state.isLoggedIn,
userId: state.userId,
nickname: state.nickname,
email: state.email
}),
}
));
export default useAuthStore;
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import useThemeStore from '../../shared/store/Themestore';
import useAuthStore from '../../shared/store/AuthStore';
import axiosInstance from '../../features/auth/AuthInstance';
import EditProfileModal from './EditProfileModal';
import EditPasswordModal from './EditPasswordModal';
import LikeBookList from './LikeBookList';
import './Profile.scss';
const Profile = () => {
const { themes, currentSeason } = useThemeStore();
const currentTheme = themes[currentSeason];
const navigate = useNavigate();
const { userId, nickname, email, setNickname, reset } = useAuthStore((state) => ({
userId: state.userId,
nickname: state.nickname,
email: state.email,
setNickname: state.setNickname,
reset: state.reset, // reset 함수 추가 회원탈퇴시
}));
useEffect(() => {
if (userId) {
setUser((prevUser) => ({
...prevUser,
nickname: nickname,
email: email,
}));
}
}, [nickname, email, userId]);
const [user, setUser] = useState({
nickname: nickname || "네임",
email: email || "메일",
profilePicture: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQT6uhVlGoDqJhKLfS9W_HQOoWJCf-_lsBZzw&s',
likedPosts: [],
});
const handleDeleteAccount = async () => {
const password = prompt("회원 탈퇴를 진행하시려면 비밀번호를 입력하세요:");
if (password !== null) {
try {
const response = await axiosInstance.delete('/api/accounts/profile/', {
data: {
password: password,
refresh_token: useAuthStore.getState().refreshToken, // 추가
}
});
alert('회원 탈퇴가 완료되었습니다.');
reset(); // 상태 초기화 호출
navigate('/');
// 탈퇴 후 추가적인 로직을 여기에 추가할 수 있습니다.
} catch (error) {
console.error('회원 탈퇴 실패:', error);
alert('비밀번호가 일치하지 않습니다.');
}
}
};
...
..
회원 탈퇴로직만 넣었더니 토큰이 남아있어서 로그인한것처럼 화면을 볼 수 있어서 회원탈퇴를 하고 나면 토큰도 없애고 reset 하면서 메인 로그인화면으로 갈 수 있게 수정해주었다.
반응형