import os import requests from PIL import Image from io import BytesIO from django.http import JsonResponse from django.shortcuts import render, redirect from django.contrib import messages from django.contrib.auth.decorators import login_required from django.contrib.auth import authenticate, login, logout from django.views.decorators.http import require_POST from django.utils.timezone import now from django.core.files.base import ContentFile from django.db import transaction from datetime import datetime, date from bs4 import BeautifulSoup from .forms import SignUpForm ,UserProfileForm, UpdateInfoForm, AIDiaryForm from .models import CustomUser, NewsArticle, Diary, UserProfile, DiaryInput from .services_lotto import get_latest_recommendations, generate_recommendations from .scripts.ml_lotto_recommend import get_ml_recommendations from django.shortcuts import get_object_or_404 # 유틸: 로그인 + 스킨 설정 여부 체크 def redirect_logged_user(user): if not user.skin_image: return redirect('/skin-setup/') return redirect('/main/') @login_required def main_view(request): if not request.user.skin_image: return redirect('/skin-setup/') # 스킨 설정 안 되어 있으면 차단 return render(request, 'myworld_app/main.html') def skin_setup_view(request): if not request.user.is_authenticated: return redirect('/login') return render(request, 'myworld_app/skinsetup.html', { 'icon_range': range(1, 25), 'skin_images': ['bg_00.webp', 'bg_11.webp', 'bg_22.webp', 'bg_33.webp', 'bg_44.webp'], 'is_main': False }) def index(request): if request.user.is_authenticated: return redirect_logged_user(request.user) return render(request, 'myworld_app/index.html', {'is_main': True}) def login_view(request): if request.user.is_authenticated: return redirect_logged_user(request.user) if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') user = authenticate(request, username=username, password=password) if user is not None: login(request, user) request.session['skin_image'] = os.path.basename(user.skin_image.name) if user.skin_image else '' request.session['character_image'] = os.path.basename(user.character_image.name) if user.character_image else '' request.session['birthdate'] = str(user.birthdate) if user.birthdate else '' request.session['nickname'] = user.nickname or '' request.session['username'] = user.username or '' request.session['is_staff'] = user.is_staff # Boolean 값 그대로 저장 request.session['email'] = user.email or '' request.session['gender'] = user.gender or '' request.session['phone'] = user.phone or '' request.session['address'] = user.address or '' request.session['oid'] = user.id or '' # ✅ 스킨 설정 여부 확인 if not user.skin_image: return redirect('/skin-setup/') # 스킨 설정 페이지로 이동 return redirect('/main/') # 스킨 설정 완료 시 메인 페이지로 이동 else: messages.error(request, '아이디 또는 비밀번호가 올바르지 않습니다.') return redirect('/login') return render(request, 'myworld_app/login.html') def logout_view(request): logout(request) request.session.flush() # messages.success(request, "로그아웃되었습니다.") return redirect('/') def signup_view(request): if request.user.is_authenticated: return redirect_logged_user(request.user) if request.method == 'POST': form = SignUpForm(request.POST) password = request.POST.get('password1') print("비밀번호 값:", password) if form.is_valid() and password and len(password) == 6: try: user = form.save(commit=False) user.set_password(password) user.save() messages.success(request, "회원가입이 완료되었습니다. 로그인해주세요.") return redirect('/login') except Exception as e: print("회원가입 오류:", e) messages.error(request, "회원가입 중 오류가 발생했습니다. 다시 시도해주세요.") return redirect('/signup') else: print("폼 유효성 실패:", form.errors) messages.error(request, "입력값이 올바르지 않거나 비밀번호가 6자리가 아닙니다.") return redirect('/signup') else: form = SignUpForm() return render(request, 'myworld_app/signup.html', {'form': form}) @login_required def update_info_view(request): user = request.user if request.method == 'POST': form = UpdateInfoForm(request.POST, instance=user) if form.is_valid(): user = form.save(commit=False) password = request.POST.get('password') if password: user.set_password(password) update_session_auth_hash(request, user) # 세션 유지 user.save() request.session['nickname'] = user.nickname or '' request.session['email'] = user.email or '' request.session['birthdate'] = str(user.birthdate) if user.birthdate else '' request.session['gender'] = user.gender or '' request.session['phone'] = user.phone or '' request.session['address'] = user.address or '' messages.success(request, "정보가 저장되었습니다.") return redirect('/main/') else: messages.error(request, "입력값을 확인해주세요.") else: form = UpdateInfoForm(instance=user) return render(request, 'myworld_app/update_info.html', {'user': request.user}) @require_POST @login_required def save_skin_icon(request): skin = request.POST.get('skin_image') # ex: bg_11.webp icon = request.POST.get('character_icon') # ex: pig-17 if not skin or not icon: messages.error(request, '스킨 또는 캐릭터를 선택하지 않았습니다.') return redirect('/skin-setup/') user = request.user user.skin_image.name = f'{skin}' user.character_image.name = f'{icon}' # 확장자 맞춰줘야 함 user.save() request.session['skin_image'] = os.path.basename(user.skin_image.name) request.session['character_image'] = os.path.basename(user.character_image.name) # messages.success(request, '내 월드 설정이 저장되었습니다.') return redirect('/main/') def check_duplicate(request): field = request.GET.get('field') value = request.GET.get('value') mode = request.GET.get('mode', 'signup') # 기본은 회원가입 user = request.user if request.user.is_authenticated else None exists = False queryset = CustomUser.objects.none() if field == 'username': queryset = CustomUser.objects.filter(username=value) elif field == 'nickname': queryset = CustomUser.objects.filter(nickname=value) elif field == 'email': queryset = CustomUser.objects.filter(email=value) if mode == 'update' and user: queryset = queryset.exclude(id=user.id) exists = queryset.exists() return JsonResponse({'exists': exists}) @login_required def lotto_view(request): existing = get_latest_recommendations() if existing.exists(): recommendations = existing.order_by('created_at') else: recommendations = generate_recommendations() return render(request, 'myworld_app/lotto.html', {'recommendations': recommendations}) @login_required def lotto_ml_view(request): ml_recommendations = get_ml_recommendations() # 머신러닝 기반 추천 조합 5개 return render(request, 'myworld_app/lotto_ml.html', { 'ml_recommendations': ml_recommendations }) @login_required def news_list_view(request): articles = NewsArticle.objects.order_by('-pub_date')[:30] return render(request, 'myworld_app/news.html', {'news_list': articles}) #뉴스 상세 페이지 뷰 import logging logger = logging.getLogger(__name__) def news_view(request): Aid = request.GET.get("Aid") article = NewsArticle.objects.filter(aid=Aid).first() if not article: return render(request, 'myworld_app/news_view.html', {'error': '뉴스를 찾을 수 없습니다.'}) return render(request, 'myworld_app/news_view.html', { 'title': article.title, 'content_html': article.content_html, 'content_txt': force_paragraphs(article.content_txt), 'content_image': article.content_image, 'author': article.author, 'section': article.section, 'main_image': article.main_image, 'pub_date': article.pub_date, }) def force_paragraphs(text): """ 모든 줄바꿈(\n)을 두 줄바꿈(\n\n)으로 바꿔서 Django의 linebreaks 필터가

단락을 제대로 생성하게 유도하는 전처리 함수 """ if not text: return "" # 중복 줄바꿈은 유지하고, 단일 줄바꿈만 \n\n으로 lines = text.strip().splitlines() return "\n\n".join(line.strip() for line in lines if line.strip()) @login_required def diary_list_view(request): user = request.user month_str = request.GET.get("month") if not month_str: # 💡 기본값: 현재 연-월 today = now().date() month_str = today.strftime("%Y-%m") try: year, month = map(int, month_str.split('-')) diaries = Diary.objects.filter( user=user, date__year=year, date__month=month ).order_by('-date') except: diaries = Diary.objects.filter(user=user).order_by('-date') return render(request, 'myworld_app/diary_list.html', { 'diaries': diaries, 'selected_month': month_str, }) @login_required def user_profile_view(request): user = request.user profile, created = UserProfile.objects.get_or_create(user=user) if request.method == 'POST': form = UserProfileForm(request.POST, instance=profile) if form.is_valid(): form.save() messages.success(request, "프로필이 저장되었습니다.") return redirect('/diary/list/') # 또는 일기 작성 페이지 else: messages.error(request, "입력값을 확인해주세요.") else: form = UserProfileForm(instance=profile) return render(request, 'myworld_app/user_profile.html', { 'form': form, 'is_main': False }) def calculate_age(birthdate): today = date.today() return today.year - birthdate.year - ((today.month, today.day) < (birthdate.month, birthdate.day)) def build_prompt(request, form_data): user = request.user # 🔥 여기서 바로 user 객체 profile, created = UserProfile.objects.get_or_create(user=user) def calculate_age(birthdate): if isinstance(birthdate, str): birthdate = datetime.strptime(birthdate, "%Y-%m-%d").date() # 문자열 → 날짜로 변환 today = date.today() return today.year - birthdate.year - ((today.month, today.day) < (birthdate.month, birthdate.day)) name = request.session.get("nickname") or "이름없음" gender = request.session.get("gender") or "비공개" age = calculate_age(request.session.get("birthdate")) or "?" mbti = profile.mbti or "알 수 없음" mood_baseline = profile.mood_baseline or "기본 기분 없음" keywords = profile.favorite_keywords or "없음" writing_style = profile.writing_style or "없음" tone = profile.tone or "없음" dream_type = profile.dream_type or "없음" preferred_time = profile.preferred_time or "없음" personality = profile.personality or "성향 설명 없음" # ✅ 작성자 소개 텍스트 만들기 author_description = f""" [작성자 정보] 이름은 {name}이며, {gender}이고 {age}세입니다. MBTI는 {mbti}로, 평소 {mood_baseline}한 성향을 가지고 있습니다. 선호하는 키워드는 '{keywords}'이며, 글을 쓸 때는 '{writing_style}' 스타일을 선호하고, '{tone}'를 자주 사용합니다. 꿈은 '{dream_type}' 분위기를 좋아하고, 주로 '{preferred_time}'에 글을 쓰는 것을 즐깁니다. 이 사용자의 성향을 한마디로 표현하자면: "{personality}"입니다. 아래에 오늘 하루에 대한 정보가 있습니다. 이를 바탕으로 감성적이고 1인칭 시점의 AI 일기를 작성해주세요. """ today_context = f""" [오늘의 정보] - 기분: {form_data['mood']} - 내상태: {form_data['state']} - 위치: {form_data['location']} - 날씨: {form_data['weather']} - 시간대: {form_data['time_of_day']} - 함께한 사람: {form_data['with_whom']} - 특별한 사건: {form_data['event']} - 오늘을 한 단어로 표현: {form_data['one_word']} - 오늘의 키워드: {form_data['keywords']} - 가장 중요한 순간: {form_data['most_important']} - 요약 문장: {form_data.get('summary') or '(없음)'} 위의 정보를 바탕으로 1인칭 감성적인 일기를 작성해 주세요. 전체 분량은 약 500자 이내로 해주세요. """ return (author_description.strip() + "\n\n" + today_context.strip()) def save_diary_input(user, form_data, prompt): DiaryInput.objects.create( user=user, mood=form_data['mood'], state=form_data['state'], location=form_data['location'], weather=form_data['weather'], time_of_day=form_data['time_of_day'], with_whom=form_data['with_whom'], event=form_data['event'], one_word=form_data['one_word'], most_important=form_data['most_important'], keywords=form_data['keywords'], summary=form_data.get('summary', ''), prompt=prompt ) @login_required def diary_write_view(request): if not request.session.get("birthdate") or not request.session.get("gender") or not request.session.get("nickname"): messages.warning(request, "생년월일, 성별이 있어야 AI 일기 작성이 가능합니다. 생년월일, 성별을 선택하세요.") return redirect("/update-info/") if request.method == "POST": form = AIDiaryForm(request.POST, request.FILES) if form.is_valid(): form_data = form.cleaned_data prompt = build_prompt(request, form_data) image_file = form.cleaned_data.get("image") try: with transaction.atomic(): diary_input = DiaryInput( user=request.user, mood=form_data['mood'], state=form_data['state'], location=form_data['location'], weather=form_data['weather'], time_of_day=form_data['time_of_day'], with_whom=form_data['with_whom'], event=form_data['event'], one_word=form_data['one_word'], most_important=form_data['most_important'], keywords=form_data['keywords'], summary=form_data.get('summary', ''), prompt=prompt ) if image_file: resized = resize_image(image_file) diary_input.image.save(image_file.name, resized, save=False) diary_input.save() diary = generate_diary_from_openai(prompt, diary_input) return redirect("diary_view", pk=diary.id) except Exception as e: print("❌ GPT 오류:", e) form = AIDiaryForm(request.POST, request.FILES) messages.error(request, "AI 일기 생성 중 오류가 발생했습니다. 다시 시도해 주세요.") return render(request, "myworld_app/diary_write.html", { "form": form, "prompt": prompt }) else: form = AIDiaryForm() return render(request, "myworld_app/diary_write.html", {"form": form}) @login_required def diary_view(request, pk): diary = get_object_or_404(Diary, pk=pk, user=request.user) return render(request, "myworld_app/diary_preview.html", { "diary": diary }) def resize_image(image_file, max_size=(800, 800)): image = Image.open(image_file) if image.mode == "RGBA": image = image.convert("RGB") # ✅ RGBA → RGB 변환 elif image.mode == "P": # 팔레트 기반 이미지도 변환 필요할 수 있음 image = image.convert("RGB") image.thumbnail(max_size) # 비율 유지하며 리사이즈 buffer = BytesIO() image.save(buffer, format="JPEG", quality=85) return ContentFile(buffer.getvalue()) from openai import OpenAI client = OpenAI(api_key="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") from datetime import date from .models import Diary def generate_diary_from_openai(prompt, diary_input): # 🔹 OpenAI 호출 response = client.chat.completions.create( model="gpt-4", messages=[ {"role": "user", "content": prompt} ], temperature=0.9, max_tokens=2048, ) ai_text = response.choices[0].message.content.strip() # 🔹 키워드 추출: 첫 줄 또는 적당한 길이 keywords = ai_text.splitlines()[0][:40] if ai_text else "일기" # 🔹 Diary 저장 및 반환 diary = Diary.objects.create( user=diary_input.user, input=diary_input, # ForeignKey로 연결되어 있어야 함 date=date.today(), mood=diary_input.mood, weather=diary_input.weather, keywords=keywords, diary_text=ai_text ) return diary # ✅ Diary 인스턴스를 그대로 리턴