first commit

This commit is contained in:
miniPc Ubuntu 2025-05-27 11:35:23 +09:00
commit 7416b88891
333 changed files with 61967 additions and 0 deletions

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

3
blog_project/blog_app/admin.py Executable file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
blog_project/blog_app/apps.py Executable file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class BlogAppConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'blog_app'

View File

View File

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
/*!
* Start Bootstrap - Clean Blog v6.0.9 (https://startbootstrap.com/theme/clean-blog)
* Copyright 2013-2023 Start Bootstrap
* Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-clean-blog/blob/master/LICENSE)
*/
window.addEventListener('DOMContentLoaded', () => {
let scrollPos = 0;
const mainNav = document.getElementById('mainNav');
const headerHeight = mainNav.clientHeight;
window.addEventListener('scroll', function() {
const currentTop = document.body.getBoundingClientRect().top * -1;
if ( currentTop < scrollPos) {
// Scrolling Up
if (currentTop > 0 && mainNav.classList.contains('is-fixed')) {
mainNav.classList.add('is-visible');
} else {
console.log(123);
mainNav.classList.remove('is-visible', 'is-fixed');
}
} else {
// Scrolling Down
mainNav.classList.remove(['is-visible']);
if (currentTop > headerHeight && !mainNav.classList.contains('is-fixed')) {
mainNav.classList.add('is-fixed');
}
}
scrollPos = currentTop;
});
})
/*테스트 www 폴더*/

View File

@ -0,0 +1,32 @@
{% extends 'blog_app/base.html' %}
{% load static %}
{% block title %}Blog{% endblock %}
{% block content %}
<!-- Page Header-->
<header class="masthead" style="background-image: url('{% static "assets/img/about-bg.jpg" %}')">
<div class="container position-relative px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<div class="page-heading">
<h1>About Me</h1>
<span class="subheading">This is what I do.</span>
</div>
</div>
</div>
</div>
</header>
<!-- Main Content-->
<main class="mb-4">
<div class="container px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Saepe nostrum ullam eveniet pariatur voluptates odit, fuga atque ea nobis sit soluta odio, adipisci quas excepturi maxime quae totam ducimus consectetur?</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius praesentium recusandae illo eaque architecto error, repellendus iusto reprehenderit, doloribus, minus sunt. Numquam at quae voluptatum in officia voluptas voluptatibus, minus!</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aut consequuntur magnam, excepturi aliquid ex itaque esse est vero natus quae optio aperiam soluta voluptatibus corporis atque iste neque sit tempora!</p>
</div>
</div>
</div>
</main>
{% endblock %}

View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>블로그 메인페이지</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">블로그</a>
<div class="collapse navbar-collapse">
<ul class="navbar-nav ml-auto">
{% if user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="#">{{ user.username }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'logout' %}">로그아웃</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{% url 'login' %}">로그인</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'signup' %}">회원가입</a>
</li>
{% endif %}
</ul>
</div>
</nav>
<div class="container mt-4">
<h1>블로그 메인페이지</h1>
<p>여기에 블로그 포스트 목록이 표시됩니다.</p>
</div>
</body>
</html>

View File

@ -0,0 +1,124 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" />
<meta name="author" content="" />
<title>{% block title %}Blog{% endblock %}</title>
<!--<link rel="icon" type="image/x-icon" href="{% static 'assets/favicon.ico' %}" />-->
<!-- Font Awesome icons (free version)-->
<script src="https://use.fontawesome.com/releases/v6.3.0/js/all.js" crossorigin="anonymous"></script>
<!-- Google fonts-->
<link href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic" rel="stylesheet" type="text/css" />
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css" />
<!-- Core theme CSS (includes Bootstrap)-->
<link href="{% static 'css/styles.css' %}" rel="stylesheet">
</head>
<body>
<!-- Navigation-->
<!-- 네비게이션 영역 -->
<nav class="navbar navbar-expand-lg navbar-light" id="mainNav">
<div class="container px-4 px-lg-5">
<a class="navbar-brand" href="{% url 'index' %}">Start Bootstrap</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive"
aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
Menu
<i class="fas fa-bars"></i>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ms-auto py-4 py-lg-0">
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'index' %}">Home</a></li>
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'about' %}">About</a></li>
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'post' %}">Sample Post</a></li>
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'contact' %}">Contact</a></li>
</ul>
</div>
</div>
</nav>
<!-- 🔽 날씨 정보 영역 (nav 바깥에 위치) -->
<!--
-->
{% block content %}{% endblock %}
<div class="container" style="margin-top: 80px;">
<div class="d-flex align-items-center justify-content-center gap-3 small py-2" id="weather-box">
<img id="icon" src="" alt="날씨 아이콘" width="40">
<span><strong id="city"></strong></span>
<span id="description" class="text-muted"></span>
<span id="temperature" class="ms-1"></span><span></span>
</div>
</div>
<!-- Footer-->
<footer class="border-top">
<div class="container px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<ul class="list-inline text-center">
<li class="list-inline-item">
<a href="#!">
<span class="fa-stack fa-lg">
<i class="fas fa-circle fa-stack-2x"></i>
<i class="fab fa-twitter fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
<li class="list-inline-item">
<a href="#!">
<span class="fa-stack fa-lg">
<i class="fas fa-circle fa-stack-2x"></i>
<i class="fab fa-facebook-f fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
<li class="list-inline-item">
<a href="#!">
<span class="fa-stack fa-lg">
<i class="fas fa-circle fa-stack-2x"></i>
<i class="fab fa-github fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
</ul>
<div class="small text-center text-muted fst-italic">Copyright &copy; Your Website 2023</div>
</div>
</div>
</div>
</footer>
<!-- Bootstrap core JS-->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Core theme JS-->
<script src="{% static 'js/scripts.js' %}"></script>
</body>
</html>
<script>
function updateWeather() {
fetch('/blog/weather/')
.then(response => response.json())
.then(data => {
if (data.error) {
console.error("날씨 오류:", data.error);
} else {
document.getElementById('city').textContent = data.city;
document.getElementById('description').textContent = data.description;
document.getElementById('temperature').textContent = data.temperature;
document.getElementById('icon').src = `https://openweathermap.org/img/wn/${data.icon}@2x.png`;
}
})
.catch(error => {
console.error("날씨 가져오기 실패:", error);
});
}
// 최초 실행
updateWeather();
// 5분마다 갱신 (300,000ms)
setInterval(updateWeather, 300000);
</script>

View File

@ -0,0 +1,82 @@
{% extends 'blog_app/base.html' %}
{% load static %}
{% block title %}Blog{% endblock %}
{% block content %}
<!-- Page Header-->
<header class="masthead" style="background-image: url('{% static "assets/img/contact-bg.jpg" %}')">
<div class="container position-relative px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<div class="page-heading">
<h1>Contact Me</h1>
<span class="subheading">Have questions? I have answers.</span>
</div>
</div>
</div>
</div>
</header>
<!-- Main Content-->
<main class="mb-4">
<div class="container px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<p>Want to get in touch? Fill out the form below to send me a message and I will get back to you as soon as possible!</p>
<div class="my-5">
<!-- * * * * * * * * * * * * * * *-->
<!-- * * SB Forms Contact Form * *-->
<!-- * * * * * * * * * * * * * * *-->
<!-- This form is pre-integrated with SB Forms.-->
<!-- To make this form functional, sign up at-->
<!-- https://startbootstrap.com/solution/contact-forms-->
<!-- to get an API token!-->
<form id="contactForm" data-sb-form-api-token="API_TOKEN">
<div class="form-floating">
<input class="form-control" id="name" type="text" placeholder="Enter your name..." data-sb-validations="required" />
<label for="name">Name</label>
<div class="invalid-feedback" data-sb-feedback="name:required">A name is required.</div>
</div>
<div class="form-floating">
<input class="form-control" id="email" type="email" placeholder="Enter your email..." data-sb-validations="required,email" />
<label for="email">Email address</label>
<div class="invalid-feedback" data-sb-feedback="email:required">An email is required.</div>
<div class="invalid-feedback" data-sb-feedback="email:email">Email is not valid.</div>
</div>
<div class="form-floating">
<input class="form-control" id="phone" type="tel" placeholder="Enter your phone number..." data-sb-validations="required" />
<label for="phone">Phone Number</label>
<div class="invalid-feedback" data-sb-feedback="phone:required">A phone number is required.</div>
</div>
<div class="form-floating">
<textarea class="form-control" id="message" placeholder="Enter your message here..." style="height: 12rem" data-sb-validations="required"></textarea>
<label for="message">Message</label>
<div class="invalid-feedback" data-sb-feedback="message:required">A message is required.</div>
</div>
<br />
<!-- Submit success message-->
<!---->
<!-- This is what your users will see when the form-->
<!-- has successfully submitted-->
<div class="d-none" id="submitSuccessMessage">
<div class="text-center mb-3">
<div class="fw-bolder">Form submission successful!</div>
To activate this form, sign up at
<br />
<a href="https://startbootstrap.com/solution/contact-forms">https://startbootstrap.com/solution/contact-forms</a>
</div>
</div>
<!-- Submit error message-->
<!---->
<!-- This is what your users will see when there is-->
<!-- an error submitting the form-->
<div class="d-none" id="submitErrorMessage"><div class="text-center text-danger mb-3">Error sending message!</div></div>
<!-- Submit Button-->
<button class="btn btn-primary text-uppercase disabled" id="submitButton" type="submit">Send</button>
</form>
</div>
</div>
</div>
</div>
</main>
{% endblock %}

View File

@ -0,0 +1,84 @@
{% extends 'blog_app/base.html' %}
{% load static %}
{% block title %}Blog{% endblock %}
{% block content %}
<!-- Page Header-->
<header class="masthead" style="background-image: url('{% static "assets/img/home-bg.jpg" %}')">
<div class="container position-relative px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<div class="site-heading">
<h1>Clean Blog</h1>
<span class="subheading">A Blog Theme by Start Bootstrap</span>
</div>
</div>
</div>
</div>
</header>
<!-- Main Content-->
<div class="container px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<!-- Post preview-->
<div class="post-preview">
<a href="post.html">
<h2 class="post-title">Man must explore, and this is exploration at its greatest</h2>
<h3 class="post-subtitle">Problems look mighty small from 150 miles up</h3>
</a>
<p class="post-meta">
Posted by
<a href="#!">Start Bootstrap</a>
on September 24, 2023
</p>
</div>
<!-- Divider-->
<hr class="my-4" />
<!-- Post preview-->
<div class="post-preview">
<a href="post.html"><h2 class="post-title">I believe every human has a finite number of heartbeats. I don't intend to waste any of mine.</h2></a>
<p class="post-meta">
Posted by
<a href="#!">Start Bootstrap</a>
on September 18, 2023
</p>
</div>
<!-- Divider-->
<hr class="my-4" />
<!-- Post preview-->
<div class="post-preview">
<a href="post.html">
<h2 class="post-title">Science has not yet mastered prophecy</h2>
<h3 class="post-subtitle">We predict too much for the next year and yet far too little for the next ten.</h3>
</a>
<p class="post-meta">
Posted by
<a href="#!">Start Bootstrap</a>
on August 24, 2023
</p>
</div>
<!-- Divider-->
<hr class="my-4" />
<!-- Post preview-->
<div class="post-preview">
<a href="post.html">
<h2 class="post-title">Failure is not an option</h2>
<h3 class="post-subtitle">Many say exploration is part of our destiny, but its actually our duty to future generations.</h3>
</a>
<p class="post-meta">
Posted by
<a href="#!">Start Bootstrap</a>
on July 8, 2023
</p>
</div>
<!-- Divider-->
<hr class="my-4" />
<!-- Pager-->
<div class="d-flex justify-content-end mb-4"><a class="btn btn-primary text-uppercase" href="#!">Older Posts →</a></div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,59 @@
{% extends 'blog_app/base.html' %}
{% load static %}
{% load widget_tweaks %}
{% block title %}Blog{% endblock %}
{% block content %}
<div class="row justify-content-center mt-5">
<div class="col-md-6 col-lg-5">
<div class="card shadow-sm rounded-4 border-0">
<div class="card-body p-4">
<h4 class="text-center mb-4" style="font-size: 1.2rem;">🔐 로그인</h4>
<form method="post" novalidate>
{% csrf_token %}
{% if form.errors %}
<div class="alert alert-danger small">아이디 또는 비밀번호가 올바르지 않습니다.</div>
{% endif %}
<div class="mb-3">
{{ form.username.label_tag }}
{{ form.username|add_class:"form-control form-control-sm" }}
</div>
<div class="mb-3">
{{ form.password.label_tag }}
{{ form.password|add_class:"form-control form-control-sm" }}
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-sm">로그인</button>
</div>
</form>
<!-- 🔗 회원가입 / 비밀번호 찾기 -->
<div class="text-center mt-3 small">
<a href="{% url 'signup' %}" class="me-2">회원가입</a> |
<a href="{% url 'password_reset' %}" class="ms-2">비밀번호 재설정</a>
</div>
<!-- ☁️ 소셜 로그인 (디자인용 버튼) -->
<hr class="my-4" />
<div class="text-center small text-muted">또는 소셜 계정으로 로그인</div>
<div class="d-flex justify-content-center gap-2 mt-2">
<a href="#" class="btn btn-outline-dark btn-sm">
<i class="fab fa-google"></i> Google
</a>
<a href="#" class="btn btn-outline-primary btn-sm">
<i class="fab fa-facebook-f"></i> Facebook
</a>
<a href="#" class="btn btn-outline-secondary btn-sm">
<i class="fab fa-github"></i> GitHub
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,35 @@
{% extends 'blog_app/base.html' %}
{% load static %}
{% load widget_tweaks %}
{% block content %}
<div class="row justify-content-center mt-5">
<div class="col-md-6 col-lg-5">
<div class="card shadow-sm rounded-4 border-0">
<div class="card-body p-4">
<h4 class="text-center mb-4" style="font-size: 1.2rem;">🔐 비밀번호 재설정</h4>
<form method="post">
{% csrf_token %}
{% if form.email.errors %}
<div class="alert alert-danger small">입력한 이메일 주소를 다시 확인해주세요.</div>
{% endif %}
<div class="mb-3">
{{ form.email.label_tag }}
{{ form.email|add_class:"form-control form-control-sm" }}
</div>
<div class="d-grid">
<button type="submit" class="btn btn-warning btn-sm">비밀번호 재설정 이메일 보내기</button>
</div>
</form>
<div class="text-center mt-3 small">
<a href="{% url 'login' %}">← 로그인 페이지로 돌아가기</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,56 @@
{% extends 'blog_app/base.html' %}
{% load static %}
{% block title %}Blog{% endblock %}
{% block content %}
<!-- Page Header-->
<header class="masthead" style="background-image: url('{% static "assets/img/post-bg.jpg" %}')">
<div class="container position-relative px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<div class="post-heading">
<h1>Man must explore, and this is exploration at its greatest</h1>
<h2 class="subheading">Problems look mighty small from 150 miles up</h2>
<span class="meta">
Posted by
<a href="#!">Start Bootstrap</a>
on August 24, 2023
</span>
</div>
</div>
</div>
</div>
</header>
<!-- Post Content-->
<article class="mb-4">
<div class="container px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<p>Never in all their history have men been able truly to conceive of the world as one: a single sphere, a globe, having the qualities of a globe, a round earth in which all the directions eventually meet, in which there is no center because every point, or none, is center — an equal earth which all men occupy as equals. The airman's earth, if free men make it, will be truly round: a globe in practice, not in theory.</p>
<p>Science cuts two ways, of course; its products can be used for both good and evil. But there's no turning back from science. The early warnings about technological dangers also come from science.</p>
<p>What was most significant about the lunar voyage was not that man set foot on the Moon but that they set eye on the earth.</p>
<p>A Chinese tale tells of some men sent to harm a young girl who, upon seeing her beauty, become her protectors rather than her violators. That's how I felt seeing the Earth for the first time. I could not help but love and cherish her.</p>
<p>For those who have seen the Earth from space, and for the hundreds and perhaps thousands more who will, the experience most certainly changes your perspective. The things that we share in our world are far more valuable than those which divide us.</p>
<h2 class="section-heading">The Final Frontier</h2>
<p>There can be no thought of finishing for aiming for the stars. Both figuratively and literally, it is a task to occupy the generations. And no matter how much progress one makes, there is always the thrill of just beginning.</p>
<p>There can be no thought of finishing for aiming for the stars. Both figuratively and literally, it is a task to occupy the generations. And no matter how much progress one makes, there is always the thrill of just beginning.</p>
<blockquote class="blockquote">The dreams of yesterday are the hopes of today and the reality of tomorrow. Science has not yet mastered prophecy. We predict too much for the next year and yet far too little for the next ten.</blockquote>
<p>Spaceflights cannot be stopped. This is not the work of any one man or even a group of men. It is a historical process which mankind is carrying out in accordance with the natural laws of human development.</p>
<h2 class="section-heading">Reaching for the Stars</h2>
<p>As we got further and further away, it [the Earth] diminished in size. Finally it shrank to the size of a marble, the most beautiful you can imagine. That beautiful, warm, living object looked so fragile, so delicate, that if you touched it with a finger it would crumble and fall apart. Seeing this has to change a man.</p>
<a href="#!"><img class="img-fluid" src="{% static 'assets/img/post-sample-image.jpg' %}" alt="..." /></a>
<span class="caption text-muted">To go places and do things that have never been done before thats what living is all about.</span>
<p>Space, the final frontier. These are the voyages of the Starship Enterprise. Its five-year mission: to explore strange new worlds, to seek out new life and new civilizations, to boldly go where no man has gone before.</p>
<p>As I stand out here in the wonders of the unknown at Hadley, I sort of realize theres a fundamental truth to our nature, Man must explore, and this is exploration at its greatest.</p>
<p>
Placeholder text by
<a href="http://spaceipsum.com/">Space Ipsum</a>
&middot; Images by
<a href="https://www.flickr.com/photos/nasacommons/">NASA on The Commons</a>
</p>
</div>
</div>
</div>
</article>
{% endblock %}

View File

@ -0,0 +1,40 @@
{% extends 'blog_app/base.html' %}
{% load static %}
{% load widget_tweaks %}
{% block content %}
<div class="row justify-content-center mt-5">
<div class="col-md-6 col-lg-5">
<div class="card shadow-sm rounded-4 border-0">
<div class="card-body p-4">
<h4 class="text-center mb-4" style="font-size: 1.2rem;">📝 회원가입</h4>
<form method="post" novalidate>
{% csrf_token %}
{% if form.errors %}
<div class="alert alert-danger small">입력값을 확인해주세요.</div>
{% endif %}
{% for field in form %}
<div class="mb-3">
{{ field.label_tag }}
{{ field|add_class:"form-control form-control-sm" }}
{% if field.errors %}
<small class="text-danger">{{ field.errors.0 }}</small>
{% endif %}
</div>
{% endfor %}
<div class="d-grid">
<button type="submit" class="btn btn-success btn-sm">가입하기</button>
</div>
</form>
<div class="text-center mt-3 small">
이미 계정이 있으신가요? <a href="{% url 'login' %}">로그인</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

3
blog_project/blog_app/tests.py Executable file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

15
blog_project/blog_app/urls.py Executable file
View File

@ -0,0 +1,15 @@
from django.urls import path
from . import views
from django.contrib.auth import views as auth_views
urlpatterns = [
path('', views.index, name='index'),
path('weather/', views.weather_data, name='weather_data'),
path('signup/', views.signup, name='signup'),
path('login/', auth_views.LoginView.as_view(template_name='blog_app/login.html'), name='login'),
path('logout/', auth_views.LogoutView.as_view(next_page='index'), name='logout'),
path('about/', views.about, name='about'),
path('post/', views.post, name='post'),
path('contact/', views.contact, name='contact'),
path('password_reset/', auth_views.PasswordResetView.as_view(template_name='blog_app/password_reset.html'), name='password_reset'),
]

52
blog_project/blog_app/views.py Executable file
View File

@ -0,0 +1,52 @@
import requests
from django.shortcuts import render, redirect
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import login
from django.http import JsonResponse
def index(request):
return render(request, 'blog_app/index.html')
def weather_data(request):
api_key = "00eb2155e51742aa2856a69da0f6dc61" # OpenWeather에서 발급받은 API 키
city = "Seoul"
url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}&units=metric&lang=kr"
weather = {}
try:
response = requests.get(url)
data = response.json()
weather = {
'city': city,
'description': data['weather'][0]['description'],
'icon': data['weather'][0]['icon'],
'temperature': data['main']['temp'],
}
return JsonResponse(weather)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
def signup(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
return redirect('index')
else:
form = UserCreationForm()
return render(request, 'blog_app/signup.html', {'form': form})
def about(request):
return render(request, 'blog_app/about.html')
def post(request):
return render(request, 'blog_app/post.html')
def contact(request):
return render(request, 'blog_app/contact.html')

View File

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,16 @@
"""
ASGI config for blog_project project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blog_project.settings')
application = get_asgi_application()

View File

@ -0,0 +1,143 @@
"""
Django settings for blog_project project.
Generated by 'django-admin startproject' using Django 5.1.7.
For more information on this file, see
https://docs.djangoproject.com/en/5.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.1/ref/settings/
"""
import os
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
#BASE_DIR = Path(__file__).resolve().parent.parent
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-#jy9i$x220ttsfa*8&_&7)w4izi@d*fi+-8=chq_$!4+9(xvw_'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['one.syye.net', 'localhost', '127.0.0.1']
FORCE_SCRIPT_NAME = '/blog'
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog_app',
'crispy_forms',
'widget_tweaks',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'blog_project.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'blog_project.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': BASE_DIR / 'db.sqlite3',
# }
# }
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'blog_db',
'USER': 'blog_user',
'PASSWORD': 'blog_password',
'HOST': 'localhost', # 또는 MariaDB 서버의 IP
'PORT': '3306',
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
},
}
}
# Password validation
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.1/topics/i18n/
LANGUAGE_CODE = 'ko-kr'
TIME_ZONE = 'Asia/Seoul'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/
STATIC_URL = '/blog/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
# Default primary key field type
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

View File

@ -0,0 +1,13 @@
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
import os
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('blog_app.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=os.path.join(settings.BASE_DIR, 'blog_app', 'static'))

View File

@ -0,0 +1,16 @@
"""
WSGI config for blog_project project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blog_project.settings')
application = get_wsgi_application()

BIN
blog_project/db.sqlite3 Executable file

Binary file not shown.

23
blog_project/manage.py Executable file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blog_project.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

355
myworld_project/jtbc_debug.html Executable file

File diff suppressed because one or more lines are too long

22
myworld_project/manage.py Executable file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks. """
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myworld_project.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

View File

Binary file not shown.

View File

@ -0,0 +1,67 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser, LottoDraw, LottoRecommendation, NewsArticle, UserProfile, Diary
# ✅ 사용자 정의 관리자
class CustomUserAdmin(UserAdmin):
model = CustomUser
# 사용자 수정 페이지 필드
fieldsets = UserAdmin.fieldsets + (
('추가 정보', {
'fields': ('nickname', 'birthdate', 'email_verified', 'character_image', 'skin_image'),
}),
)
# 사용자 추가 페이지 필드
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('username', 'nickname', 'email', 'birthdate', 'password1', 'password2'),
}),
)
# 사용자 리스트 컬럼
list_display = ['username', 'nickname', 'email', 'is_staff', 'is_superuser']
admin.site.register(CustomUser, CustomUserAdmin)
# ✅ 로또 당첨 정보 관리자
@admin.register(LottoDraw)
class LottoDrawAdmin(admin.ModelAdmin):
list_display = ("draw_no", "number_1", "number_2", "number_3", "number_4", "number_5", "number_6", "bonus", "first_winners", "first_prize")
search_fields = ("draw_no",)
ordering = ("-draw_no",)
# ✅ 추천 번호 관리자
@admin.register(LottoRecommendation)
class LottoRecommendationAdmin(admin.ModelAdmin):
list_display = ("id", "method", "numbers", "created_at")
list_filter = ("method", "created_at")
ordering = ("-created_at",)
# ✅ 뉴스 기사 관리자
@admin.register(NewsArticle)
class NewsArticleAdmin(admin.ModelAdmin):
list_display = ("title", "link", "pub_date")
search_fields = ("title", "link")
ordering = ("-pub_date",)
@admin.register(UserProfile)
class UserProfileAdmin(admin.ModelAdmin):
list_display = ('user', 'mbti', 'nickname', 'mood_baseline', 'preferred_time')
search_fields = ('user__username', 'mbti', 'nickname')
list_filter = ('mbti', 'preferred_time')
ordering = ('user__username',)
@admin.register(Diary)
class DiaryAdmin(admin.ModelAdmin):
list_display = ('user', 'date', 'mood', 'weather', 'created_at')
search_fields = ('user__username', 'keywords', 'diary_text')
list_filter = ('mood', 'weather', 'date')
ordering = ('-date',)
# ✅ 관리자 사이트 커스터마이징
admin.site.site_header = "MyWorld 관리자"
admin.site.site_title = "MyWorld Admin"
admin.site.index_title = "관리자 패널"

View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class MyworldAppConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'myworld_app'

View File

@ -0,0 +1,151 @@
from django import forms
from django.contrib.auth.forms import UserCreationForm
from .models import CustomUser, UserProfile
class SignUpForm(UserCreationForm):
email = forms.EmailField(required=True)
birthdate = forms.DateField(required=True, widget=forms.DateInput(attrs={'type': 'date'}))
class Meta:
model = CustomUser
fields = ('username', 'nickname', 'email', 'birthdate', 'gender')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'password2' in self.fields:
del self.fields['password2'] # ✅ password2 필드 제거
class UpdateInfoForm(forms.ModelForm):
password = forms.CharField(required=False, widget=forms.PasswordInput())
class Meta:
model = CustomUser
fields = ['nickname', 'email', 'birthdate', 'gender', 'phone', 'address']
MBTI_CHOICES = [
('', '선택하세요'),('INTJ', 'INTJ'), ('INTP', 'INTP'), ('ENTJ', 'ENTJ'), ('ENTP', 'ENTP'),
('INFJ', 'INFJ'), ('INFP', 'INFP'), ('ENFJ', 'ENFJ'), ('ENFP', 'ENFP'),
('ISTJ', 'ISTJ'), ('ISFJ', 'ISFJ'), ('ESTJ', 'ESTJ'), ('ESFJ', 'ESFJ'),
('ISTP', 'ISTP'), ('ISFP', 'ISFP'), ('ESTP', 'ESTP'), ('ESFP', 'ESFP'),
]
MOOD_BASELINE_CHOICES = [
('', '선택하세요'),('차분함', '차분함'), ('활발함', '활발함'), ('감성적', '감성적'), ('사차원', '사차원'), ('화가 나있는', '화가 나있는'),
('내향적', '내향적'), ('외향적', '외향적'), ('혼자 있는 걸 좋아함', '혼자 있는 걸 좋아함'),
]
FAVORITE_KEYWORDS_CHOICES = [
('', '선택하세요'),('자연', '자연'), ('계절', '계절'), ('추억', '추억'), ('재물', '재물'),('건강', '건강'),
('사람', '사람'), ('시간', '시간'), ('풍경', '풍경'), ('사랑', '사랑'),
('고요함', '고요함'), ('우주', '우주'), ('음악', '음악'),
]
WRITING_STYLE_CHOICES = [
('', '선택하세요'),('시적인', '시적인'), ('간결한', '간결한'), ('묘사적인', '묘사적인'),('MZ세대', 'MZ세대'),('X세대(올드)', 'X세대(올드)'),
('철학적인', '철학적인'), ('일기장 스타일', '일기장 스타일'),
]
TONE_CHOICES = [
('', '선택하세요'),
('조용하고 따뜻한 어조', '조용하고 따뜻한 어조'),
('밝고 명랑한 어조', '밝고 명랑한 어조'),
('서정적이고 차분한 어조', '서정적이고 차분한 어조'),
('현실적이고 직설적인 어조', '현실적이고 직설적인 어조'),
]
DREAM_TYPE_CHOICES = [
('', '선택하세요'),('몽환적', '몽환적'), ('현실적', '현실적'), ('철학적', '철학적'),
('서사적', '서사적'), ('감정 위주', '감정 위주'), ('상징적', '상징적'),
]
PREFERRED_TIME_CHOICES = [
('', '선택하세요'),('새벽', '새벽'), ('아침', '아침'), ('', ''),
('저녁', '저녁'), ('', ''), ('시간에 구애받지 않음', '시간에 구애받지 않음'),
]
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = [
'nickname', 'mbti', 'mood_baseline', 'personality',
'favorite_keywords', 'writing_style', 'tone',
'dream_type', 'preferred_time'
]
widgets = {
'nickname': forms.TextInput(),
'mbti': forms.Select(choices=MBTI_CHOICES),
'mood_baseline': forms.Select(choices=MOOD_BASELINE_CHOICES),
'personality': forms.Textarea(attrs={'rows': 3, 'placeholder': '자신을 표현해주세요'}),
'favorite_keywords': forms.Select(choices=FAVORITE_KEYWORDS_CHOICES),
'writing_style': forms.Select(choices=WRITING_STYLE_CHOICES),
'tone': forms.Select(choices=TONE_CHOICES),
'dream_type': forms.Select(choices=DREAM_TYPE_CHOICES),
'preferred_time': forms.Select(choices=PREFERRED_TIME_CHOICES),
}
MOOD_CHOICES = [
('', '선택하세요'),
('행복', '행복'), ('우울', '우울'), ('무기력', '무기력'),
('불안', '불안'), ('평온', '평온'), ('설렘', '설렘')
]
STATE_CHOICES = [
('', '선택하세요'),
('건강함', '건강함'), ('피곤함', '피곤함'), ('집중 안됨', '집중 안됨'),
('졸림', '졸림'), ('불편함', '불편함'), ('무난함', '무난함')
]
WEATHER_CHOICES = [
('', '선택하세요'),
('맑음', '맑음'), ('흐림', '흐림'), ('', ''), ('', ''), ('바람', '바람')
]
TIME_CHOICES = [
('', '선택하세요'),
('아침', '아침'), ('오후', '오후'), ('저녁', '저녁'), ('새벽', '새벽')
]
WHO_CHOICES = [
('', '선택하세요'),
('혼자', '혼자'), ('친구', '친구'), ('가족', '가족'), ('연인', '연인'),
('직장동료', '직장동료'), ('모름', '모름')
]
EVENT_CHOICES = [
('', '선택하세요'),
('없음', '없음'), ('기쁜 일', '기쁜 일'), ('속상한 일', '속상한 일'),
('충격적인 일', '충격적인 일'), ('감동적인 일', '감동적인 일')
]
class AIDiaryForm(forms.Form):
mood = forms.ChoiceField(label='오늘 기분',required=False, choices=MOOD_CHOICES, widget=forms.Select(attrs={'class': 'form-select'}))
state = forms.ChoiceField(label='내 상태',required=False, choices=STATE_CHOICES, widget=forms.Select(attrs={'class': 'form-select'}))
location = forms.CharField(label='현재 위치',required=False, widget=forms.TextInput(attrs={'class': 'form-control','placeholder': '입력 해 주세요'}))
weather = forms.ChoiceField(label='날씨',required=False, choices=WEATHER_CHOICES,widget=forms.Select(attrs={'class': 'form-select'}))
time_of_day = forms.ChoiceField(label='시간대',required=False, choices=TIME_CHOICES,widget=forms.Select(attrs={'class': 'form-select'}))
with_whom = forms.ChoiceField(label='누구와 있었나요?',required=False, choices=WHO_CHOICES,widget=forms.Select(attrs={'class': 'form-select'}))
event = forms.ChoiceField(label='특별한 사건이 있었나요?',required=False, choices=EVENT_CHOICES,widget=forms.Select(attrs={'class': 'form-select'}))
one_word = forms.CharField(label='오늘을 한 단어로 표현한다면?',required=False, widget=forms.TextInput(attrs={'class': 'form-control','placeholder': '입력 해 주세요'}))
most_important = forms.CharField(label='오늘 가장 중요한 순간은?',required=False, widget=forms.TextInput(attrs={'class': 'form-control','placeholder': '입력 해 주세요'}))
image = forms.ImageField(label="이미지 업로드 (선택)", required=False)
keywords = forms.CharField(
label='오늘의 키워드',
required=False,
widget=forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': '예: 친구와 커피, 벚꽃, 조용한 시간 등 자유롭게 적어주세요.'
})
)
summary = forms.CharField(
label='오늘을 한 문장으로 표현해주세요 (선택)',
required=False,
widget=forms.Textarea(attrs={
'class': 'form-control',
'rows': 2,
'placeholder': '예: 벚꽃잎처럼 내 마음도 흩날렸던 하루.'
})
)

View File

@ -0,0 +1,36 @@
# lotto_import.py (myworld_app 디렉토리에 두고 실행)
import csv
import os
import django
import sys
# 현재 경로를 파이썬 모듈 경로에 추가
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myworld_project.settings")
django.setup()
from myworld_app.models import LottoDraw
with open("./myworld_app/static/csv/lotto.csv", newline='', encoding='cp949') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
LottoDraw.objects.update_or_create(
draw_no=int(row['회차']),
defaults={
'number_1': int(row['번호1']),
'number_2': int(row['번호2']),
'number_3': int(row['번호3']),
'number_4': int(row['번호4']),
'number_5': int(row['번호5']),
'number_6': int(row['번호6']),
'bonus': int(row['보너스']),
'first_prize': int(row['1등 당첨금'].replace(',', '')),
'first_winners': int(row['1등 당첨수']),
'second_prize': int(row['2등 당첨금'].replace(',', '')),
'second_winners': int(row['2등 당첨수']),
}
)
print("✅ 로또 데이터가 DB에 저장되었습니다. ")

View File

@ -0,0 +1,52 @@
# Generated by Django 5.1.7 on 2025-04-14 02:04
import django.contrib.auth.models
import django.contrib.auth.validators
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name='CustomUser',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('nickname', models.CharField(max_length=30, unique=True)),
('birthdate', models.DateField(blank=True, null=True)),
('email_verified', models.BooleanField(default=False)),
('character_image', models.ImageField(blank=True, null=True, upload_to='characters/')),
('skin_image', models.ImageField(blank=True, null=True, upload_to='skins/')),
('gender', models.CharField(blank=True, choices=[('', ''), ('', '')], max_length=2)),
('phone', models.CharField(blank=True, max_length=20)),
('address', models.CharField(blank=True, max_length=255)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]

View File

@ -0,0 +1,25 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('myworld_app', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='customuser',
name='gender',
field=models.CharField(max_length=2, choices=[('', ''), ('', '')], blank=True),
),
migrations.AddField(
model_name='customuser',
name='phone',
field=models.CharField(max_length=20, blank=True),
),
migrations.AddField(
model_name='customuser',
name='address',
field=models.CharField(max_length=255, blank=True),
),
]

View File

@ -0,0 +1,31 @@
# Generated by Django 5.1.7 on 2025-04-16 05:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('myworld_app', '0002_add_user_fields'),
]
operations = [
migrations.CreateModel(
name='LottoDraw',
fields=[
('draw_no', models.IntegerField(primary_key=True, serialize=False)),
('number_1', models.PositiveSmallIntegerField()),
('number_2', models.PositiveSmallIntegerField()),
('number_3', models.PositiveSmallIntegerField()),
('number_4', models.PositiveSmallIntegerField()),
('number_5', models.PositiveSmallIntegerField()),
('number_6', models.PositiveSmallIntegerField()),
('bonus', models.PositiveSmallIntegerField()),
('first_prize', models.BigIntegerField()),
('first_winners', models.IntegerField()),
('second_prize', models.BigIntegerField()),
('second_winners', models.IntegerField()),
('draw_date', models.DateField(blank=True, null=True)),
],
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 5.1.7 on 2025-04-16 07:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('myworld_app', '0003_lottodraw'),
]
operations = [
migrations.CreateModel(
name='LottoRecommendation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('method', models.CharField(choices=[('freq', '빈도 기반'), ('gap', '간격 기반'), ('pattern', '패턴 기반'), ('cluster', '클러스터링 기반'), ('extra', '보조 추천')], max_length=10)),
('numbers', models.JSONField()),
('created_at', models.DateTimeField(auto_now_add=True)),
],
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 5.1.7 on 2025-04-18 08:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('myworld_app', '0004_lottorecommendation'),
]
operations = [
migrations.CreateModel(
name='NewsArticle',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=300)),
('link', models.URLField(unique=True)),
('content_html', models.TextField()),
('pub_date', models.DateTimeField(auto_now_add=True)),
],
),
]

View File

@ -0,0 +1,58 @@
# Generated by Django 5.1.7 on 2025-04-22 05:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('myworld_app', '0005_newsarticle'),
]
operations = [
migrations.AddField(
model_name='newsarticle',
name='author',
field=models.CharField(blank=True, max_length=100, null=True),
),
migrations.AddField(
model_name='newsarticle',
name='content_image',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='newsarticle',
name='content_txt',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='newsarticle',
name='desc',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='newsarticle',
name='isvod',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='newsarticle',
name='main_image',
field=models.URLField(blank=True, null=True),
),
migrations.AddField(
model_name='newsarticle',
name='section',
field=models.CharField(blank=True, max_length=50, null=True),
),
migrations.AlterField(
model_name='newsarticle',
name='link',
field=models.CharField(max_length=200, unique=True),
),
migrations.AlterField(
model_name='newsarticle',
name='pub_date',
field=models.DateTimeField(),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-04-22 06:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('myworld_app', '0006_newsarticle_author_newsarticle_content_image_and_more'),
]
operations = [
migrations.AddField(
model_name='newsarticle',
name='aid',
field=models.CharField(blank=True, max_length=20, null=True),
),
]

View File

@ -0,0 +1,44 @@
# Generated by Django 5.1.7 on 2025-04-23 02:22
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('myworld_app', '0007_newsarticle_aid'),
]
operations = [
migrations.CreateModel(
name='Diary',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField()),
('keywords', models.CharField(max_length=255)),
('mood', models.CharField(max_length=100)),
('weather', models.CharField(max_length=100)),
('diary_text', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='UserProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nickname', models.CharField(default='', max_length=30)),
('mbti', models.CharField(max_length=4)),
('mood_baseline', models.CharField(max_length=100)),
('personality', models.TextField()),
('favorite_keywords', models.CharField(max_length=255)),
('writing_style', models.TextField()),
('tone', models.CharField(default='조용하고 따뜻한 어조', max_length=100)),
('dream_type', models.CharField(blank=True, max_length=100)),
('preferred_time', models.CharField(blank=True, max_length=50)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -0,0 +1,36 @@
# Generated by Django 5.1.7 on 2025-04-24 04:38
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('myworld_app', '0008_diary_userprofile'),
]
operations = [
migrations.CreateModel(
name='DiaryInput',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(auto_now_add=True)),
('mood', models.CharField(max_length=50)),
('state', models.CharField(max_length=50)),
('location', models.CharField(max_length=100)),
('weather', models.CharField(max_length=50)),
('time_of_day', models.CharField(max_length=50)),
('with_whom', models.CharField(max_length=50)),
('event', models.CharField(max_length=50)),
('one_word', models.CharField(max_length=50)),
('most_important', models.CharField(max_length=255)),
('keywords', models.TextField()),
('summary', models.TextField(blank=True, null=True)),
('prompt', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-04-24 06:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('myworld_app', '0009_diaryinput'),
]
operations = [
migrations.AddField(
model_name='diaryinput',
name='image',
field=models.ImageField(blank=True, null=True, upload_to='diary_images/'),
),
]

View File

@ -0,0 +1,25 @@
# Generated by Django 5.1.7 on 2025-04-24 08:03
import django.db.models.deletion
import myworld_app.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('myworld_app', '0010_diaryinput_image'),
]
operations = [
migrations.AddField(
model_name='diary',
name='input',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='myworld_app.diaryinput'),
),
migrations.AlterField(
model_name='diaryinput',
name='image',
field=models.ImageField(blank=True, null=True, upload_to=myworld_app.models.diary_image_upload_path),
),
]

View File

@ -0,0 +1,129 @@
import os
from datetime import datetime
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.conf import settings
class CustomUser(AbstractUser):
nickname = models.CharField(max_length=30, unique=True)
birthdate = models.DateField(null=True, blank=True)
email_verified = models.BooleanField(default=False)
character_image = models.ImageField(upload_to='characters/', null=True, blank=True)
skin_image = models.ImageField(upload_to='skins/', null=True, blank=True)
gender = models.CharField(max_length=2, choices=[('', ''), ('', '')], blank=True)
phone = models.CharField(max_length=20, blank=True)
address = models.CharField(max_length=255, blank=True)
def __str__(self):
return self.username
class LottoDraw(models.Model):
draw_no = models.IntegerField(primary_key=True)
number_1 = models.PositiveSmallIntegerField()
number_2 = models.PositiveSmallIntegerField()
number_3 = models.PositiveSmallIntegerField()
number_4 = models.PositiveSmallIntegerField()
number_5 = models.PositiveSmallIntegerField()
number_6 = models.PositiveSmallIntegerField()
bonus = models.PositiveSmallIntegerField()
first_prize = models.BigIntegerField()
first_winners = models.IntegerField()
second_prize = models.BigIntegerField()
second_winners = models.IntegerField()
draw_date = models.DateField(null=True, blank=True)
def __str__(self):
return f"{self.draw_no}"
class LottoRecommendation(models.Model):
METHOD_CHOICES = [
('freq', '빈도 기반'),
('gap', '간격 기반'),
('pattern', '패턴 기반'),
('cluster', '클러스터링 기반'),
('extra', '보조 추천'),
]
method = models.CharField(max_length=10, choices=METHOD_CHOICES)
numbers = models.JSONField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.get_method_display()} 추천 ({self.created_at.date()})"
class NewsArticle(models.Model):
aid = models.CharField(max_length=20, blank=True, null=True)
title = models.CharField(max_length=300)
link = models.CharField(max_length=200, unique=True)
content_html = models.TextField()
content_txt = models.TextField(blank=True, null=True) # ✅ 본문 텍스트
content_image = models.TextField(blank=True, null=True) # ✅ 본문 내 이미지 src 모음
desc = models.TextField(blank=True, null=True) # ✅ 요약 설명
author = models.CharField(max_length=100, blank=True, null=True) # ✅ 기자
section = models.CharField(max_length=50, blank=True, null=True) # ✅ 정치/사회 등 섹션
main_image = models.URLField(blank=True, null=True) # ✅ 대표 썸네일
isvod = models.BooleanField(default=False) # ✅ 영상 포함 여부
pub_date = models.DateTimeField()
def __str__(self):
return self.title
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
nickname = models.CharField(max_length=30, default='') # 1인칭 호칭
mbti = models.CharField(max_length=4) # 예: INFJ, ENFP 등
mood_baseline = models.CharField(max_length=100) # 평소 기분 상태 (예: 차분함)
personality = models.TextField() # 작성자 자가 기술
favorite_keywords = models.CharField(max_length=255) # 선호 키워드 (예: 고요함, 계절, 추억)
writing_style = models.TextField() # 시적인, 간결한, 철학적인 등
tone = models.CharField(max_length=100, default='조용하고 따뜻한 어조') # 말투
dream_type = models.CharField(max_length=100, blank=True) # 꿈의 성향 (몽환적, 현실적 등)
preferred_time = models.CharField(max_length=50, blank=True) # 선호 시간대 (새벽, 밤, 낮)
def __str__(self):
return f"{self.user.username}의 프로필"
class Diary(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
date = models.DateField()
keywords = models.CharField(max_length=255)
mood = models.CharField(max_length=100)
weather = models.CharField(max_length=100)
diary_text = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
# 🔗 연결 관계 (작성된 프롬프트 기반 입력 정보)
input = models.OneToOneField("DiaryInput", on_delete=models.SET_NULL, null=True, blank=True)
def __str__(self):
return f"[{self.date}] {self.keywords}"
def diary_image_upload_path(instance, filename):
now = datetime.now()
folder = now.strftime("%Y%m")
ext = os.path.splitext(filename)[1]
user_id = instance.user.id if instance.user else "anonymous"
fname = f"{user_id}_{now.strftime('%Y%m%d%H%M%S')}{ext}"
return f"diary_images/{folder}/{fname}" # MEDIA_ROOT 기준 경로
class DiaryInput(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
date = models.DateField(auto_now_add=True)
mood = models.CharField(max_length=50)
state = models.CharField(max_length=50)
location = models.CharField(max_length=100)
weather = models.CharField(max_length=50)
time_of_day = models.CharField(max_length=50)
with_whom = models.CharField(max_length=50)
event = models.CharField(max_length=50)
one_word = models.CharField(max_length=50)
most_important = models.CharField(max_length=255)
keywords = models.TextField()
summary = models.TextField(blank=True, null=True)
prompt = models.TextField() # ✅ 프롬프트 저장 필드
created_at = models.DateTimeField(auto_now_add=True)
image = models.ImageField(upload_to=diary_image_upload_path, blank=True, null=True)
def __str__(self):
return f"{self.user.username} - {self.date}"

View File

@ -0,0 +1,68 @@
import os
import sys
import django
import requests
from bs4 import BeautifulSoup
# Django 설정 초기화
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(BASE_DIR)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myworld_project.settings")
django.setup()
from myworld_app.models import LottoDraw
def fetch_latest_lotto():
url = 'https://dhlottery.co.kr/gameResult.do?method=byWin'
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
# 회차
draw_no = int(soup.select_one('.win_result h4 strong').text.replace('', '').strip())
# 당첨 번호
numbers = [int(el.text.strip()) for el in soup.select('.num.win span')[:6]]
# 보너스 번호
bonus = int(soup.select_one('.bonus span').text.strip())
# ✅ 정확한 방식으로 1등/2등 정보 파싱
prize_rows = soup.select('.tbl_data tbody tr')
if len(prize_rows) >= 2:
# 1등
first_winners = int(prize_rows[0].select('td')[2].text.replace(',', '').replace('', '').strip())
first_prize = int(prize_rows[0].select('td')[3].text.replace(',', '').replace('', '').strip())
# 2등
second_winners = int(prize_rows[1].select('td')[2].text.replace(',', '').replace('', '').strip())
second_prize = int(prize_rows[1].select('td')[3].text.replace(',', '').replace('', '').strip())
else:
first_winners = 0
first_prize = 0
second_winners = 0
second_prize = 0
# 중복 체크 후 저장
if LottoDraw.objects.filter(draw_no=draw_no).exists():
print(f"✅ 이미 저장된 {draw_no}회차입니다.")
print(f"🔢 번호: {numbers} + 보너스: {bonus}")
print(f"🎉 {draw_no}회차 | 1등 {first_winners}명 (₩{first_prize:,}) | 2등 {second_winners}명 (₩{second_prize:,})")
return
LottoDraw.objects.create(
draw_no=draw_no,
number_1=numbers[0], number_2=numbers[1], number_3=numbers[2],
number_4=numbers[3], number_5=numbers[4], number_6=numbers[5],
bonus=bonus,
first_prize=first_prize,
first_winners=first_winners,
second_prize=second_prize,
second_winners=second_winners,
)
print(f"✅ 저장 완료: {draw_no}회차")
print(f"🔢 번호: {numbers} + 보너스: {bonus}")
print(f"🎉 1등 {first_winners}명 (₩{first_prize:,}) | 2등 {second_winners}명 (₩{second_prize:,})")
if __name__ == "__main__":
fetch_latest_lotto()

View File

@ -0,0 +1,118 @@
import os
import sys
import django
import requests
import json
import time
from datetime import datetime
from django.utils import timezone
from bs4 import BeautifulSoup
from urllib.parse import urljoin
# Django setup
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(BASE_DIR)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myworld_project.settings")
django.setup()
from myworld_app.models import NewsArticle
def crawl_mbc_article(url):
headers = {"User-Agent": "Mozilla/5.0"}
try:
response = requests.get(url, headers=headers, timeout=10)
response.encoding = 'utf-8'
soup = BeautifulSoup(response.text, "html.parser")
author_tag = soup.find("meta", attrs={"name": "author"})
author = author_tag["content"].strip() if author_tag else ""
date_tag = soup.find("meta", attrs={"name": "nextweb:createDate"})
pub_date = None
if date_tag:
try:
pub_date = datetime.strptime(date_tag["content"], "%Y-%m-%d %H:%M")
except:
pub_date = timezone.now()
content_div = soup.find("div", class_="news_txt", itemprop="articleBody")
content_html = content_div.decode_contents() if content_div else ""
content_text = content_div.get_text(separator="\n", strip=True) if content_div else ""
image_srcs = []
for img_tag in soup.select("div.news_img img"):
src = img_tag.get("src")
if src:
if src.startswith("//"):
src = "https:" + src
image_srcs.append(src)
return {
"author": author,
"pub_date": pub_date,
"content_html": content_html,
"content_text": content_text,
"content_image": "\n".join(image_srcs)
}
except Exception as e:
print(f"❌ 크롤링 실패: {url}{e}")
return None
def fetch_and_update_mbc_articles():
url = f"https://imnews.imbc.com/operate/common/main/topnews/headline_news.js?{datetime.now().strftime('%Y%m%d%H%M')}"
response = requests.get(url)
response.encoding = 'utf-8-sig'
data = json.loads(response.text)
new_articles = []
for item in data.get("Data", []):
aid = item.get("AId", "").strip()
if not aid:
continue
# 중복 방지
if NewsArticle.objects.filter(aid=aid).exists():
continue
link = item.get("Link", "").strip()
article = NewsArticle(
title=item.get("Title", "").strip(),
aid=aid,
link=link,
desc=item.get("Desc", "").strip(),
author=item.get("Author", "").strip(),
section=item.get("Section", "").strip(),
main_image="https:" + item["Image"].strip() if item.get("Image", "").startswith("//") else item.get("Image"),
isvod=(item.get("IsVod", "N") == "Y"),
pub_date=timezone.now(), # 임시, 크롤링에서 수정됨
content_html="",
content_txt="",
content_image=""
)
article.save()
new_articles.append(article)
print(f"✅ 새 기사 저장: {article.title}")
# 크롤링 및 업데이트
for article in new_articles:
time.sleep(2) # ⏱️ 페이지 로딩 딜레이
detail = crawl_mbc_article(article.link)
if detail:
article.author = detail["author"] or article.author
article.pub_date = detail["pub_date"] or article.pub_date
article.content_html = detail["content_html"]
article.content_txt = detail["content_text"]
article.content_image = detail["content_image"]
article.save()
print(f"📝 크롤링 완료: {article.title}")
# 최대 30개 유지 (오래된 순으로 삭제)
all_articles = NewsArticle.objects.all().order_by('-pub_date')
if all_articles.count() > 30:
to_delete = all_articles[30:]
print(f"🧹 {len(to_delete)}개 기사 삭제")
for a in to_delete:
a.delete()
if __name__ == "__main__":
fetch_and_update_mbc_articles()

View File

@ -0,0 +1,112 @@
import os
import sys
import django
import requests
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
from datetime import datetime, timedelta
import email.utils
# 🔧 Django 세팅
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(BASE_DIR)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myworld_project.settings")
django.setup()
from myworld_app.models import NewsArticle
from django.db.utils import IntegrityError
# 🔁 설정
RECENT_HOURS = 2
MAX_ARTICLE_COUNT = 30
def fetch_rss_links():
rss_urls = [
"https://news-ex.jtbc.co.kr/v1/get/rss/newsflesh",
"https://news-ex.jtbc.co.kr/v1/get/rss/issue",
]
now = datetime.utcnow()
links = []
for url in rss_urls:
try:
res = requests.get(url, headers={"User-Agent": "Mozilla/5.0"}, timeout=10)
soup = BeautifulSoup(res.content, "xml")
for item in soup.find_all("item"):
title = item.title.text.strip()
link = item.link.text.strip()
pub_raw = item.pubDate.text.strip()
pub_date = email.utils.parsedate_to_datetime(pub_raw)
if (now - pub_date).total_seconds() > RECENT_HOURS * 3600:
continue # 오래된 뉴스 스킵
links.append((title, link))
except Exception as e:
print(f"❌ RSS 파싱 실패: {url}{e}")
return links
def crawl_article(title, link):
options = Options()
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--disable-gpu")
options.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(
service=Service(ChromeDriverManager().install()),
options=options
)
try:
driver.set_page_load_timeout(15)
try:
driver.get(link)
except Exception as e:
print(f"⚠️ 페이지 로딩 실패: {link}{e}")
return
soup = BeautifulSoup(driver.page_source, "html.parser")
content_div = soup.find("div", id="ijam_content")
if not content_div:
print(f"❌ 본문 미존재: {link}")
return
# 불필요한 광고 div 제거
for tag in content_div.select("#reo_0GqQ"):
tag.decompose()
content_html = content_div.decode_contents()
if not NewsArticle.objects.filter(link=link).exists():
NewsArticle.objects.create(title=title, link=link, content_html=content_html)
print(f"✅ 저장됨: {title}")
else:
print(f"🔁 중복 기사 스킵: {title}")
except Exception as e:
print(f"❌ 크롤링 에러: {title}{e}")
finally:
driver.quit()
def trim_old_articles(max_count=MAX_ARTICLE_COUNT):
total = NewsArticle.objects.count()
if total > max_count:
excess = total - max_count
old_articles = NewsArticle.objects.order_by("pub_date")[:excess]
deleted_titles = [a.title for a in old_articles]
count, _ = old_articles.delete()
print(f"🧹 {count}개 오래된 뉴스 삭제됨: {deleted_titles}")
if __name__ == "__main__":
print("📡 JTBC 뉴스 수집 시작...")
articles = fetch_rss_links()
print(f"📋 가져온 링크 수: {len(articles)}")
for title, link in articles:
crawl_article(title, link)
trim_old_articles()
print("✅ 뉴스 수집 완료.")

View File

@ -0,0 +1,67 @@
# myworld_app/scripts/ml_lotto.py
import os
import sys
import django
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(BASE_DIR)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myworld_project.settings")
django.setup()
import random
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from myworld_app.models import LottoDraw
# 🔹 조합에서 특성 추출 함수
def extract_features(numbers):
odd = sum(1 for n in numbers if n % 2 == 1)
total = sum(numbers)
ranges = [0, 0, 0] # 1~15 / 16~30 / 31~45
for n in numbers:
if n <= 15:
ranges[0] += 1
elif n <= 30:
ranges[1] += 1
else:
ranges[2] += 1
ends = [n % 10 for n in numbers] # 끝수
return [odd, total] + ranges + ends
# 🔹 데이터셋 구성
draws = LottoDraw.objects.all().order_by('draw_no')
X, y = [], []
for draw in draws:
numbers = [draw.number_1, draw.number_2, draw.number_3,
draw.number_4, draw.number_5, draw.number_6]
X.append(extract_features(numbers))
y.append(1) # 1등 당첨 조합
# 🔹 비당첨 가짜 조합 추가 (랜덤)
for _ in range(len(X)):
nums = sorted(random.sample(range(1, 46), 6))
X.append(extract_features(nums))
y.append(0)
# 🔹 학습
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
model = RandomForestClassifier(n_estimators=200, random_state=42)
model.fit(X_train, y_train)
# 🔹 무작위 1000개 조합 생성 → 예측
candidates = [sorted(random.sample(range(1, 46), 6)) for _ in range(1000)]
candidate_features = [extract_features(c) for c in candidates]
probs = model.predict_proba(candidate_features)[:, 1] # 당첨 확률
# 🔹 확률이 높은 순으로 상위 5개 출력
results = sorted(zip(candidates, probs), key=lambda x: x[1], reverse=True)[:5]
print("🎯 머신러닝 기반 추천 조합 (RandomForestClassifier)")
for i, (combo, prob) in enumerate(results, 1):
print(f"{i}. {combo} → 확률: {prob:.4f}")

Some files were not shown because too many files have changed in this diff Show More