This commit is contained in:
parent
db2f98e63a
commit
16aa25a588
12 changed files with 211 additions and 12 deletions
|
@ -1,20 +1,14 @@
|
||||||
# Используем официальный образ Python
|
|
||||||
FROM python:3.11-slim
|
FROM python:3.11-slim
|
||||||
|
|
||||||
# Устанавливаем рабочую директорию
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Копируем зависимости
|
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
|
|
||||||
# Устанавливаем зависимости
|
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
# Копируем всё приложение в контейнер
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Указываем переменные окружения (они будут приходить извне)
|
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
# Команда запуска сервера
|
|
||||||
CMD ["python", "weather_project/manage.py", "runserver", "0.0.0.0:8000"]
|
CMD ["python", "weather_project/manage.py", "runserver", "0.0.0.0:8000"]
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
Django>=4.2
|
Django>=4.2
|
||||||
requests
|
requests
|
||||||
|
celery
|
||||||
|
django-celery-beat
|
5
weather_project/weather/forms.py
Normal file
5
weather_project/weather/forms.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
class EmailScheduleForm(forms.Form):
|
||||||
|
city = forms.CharField(max_length=100)
|
||||||
|
email = forms.EmailField()
|
36
weather_project/weather/tasks.py
Normal file
36
weather_project/weather/tasks.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import logging
|
||||||
|
from celery import shared_task
|
||||||
|
from django.core.mail import send_mail
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
API_KEY = 'a7cc162fc60a76d2e31461071634b8ce'
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def send_weather_email(city, email):
|
||||||
|
url = f'https://api.openweathermap.org/data/2.5/weather?q={city}&appid={API_KEY}&units=metric&lang=en'
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(url)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
temp = data['main']['temp']
|
||||||
|
description = data['weather'][0]['description']
|
||||||
|
message = f"Weather in {city}: {description}, {temp}°C"
|
||||||
|
|
||||||
|
send_mail(
|
||||||
|
subject=f"Weather in {city}",
|
||||||
|
message=message,
|
||||||
|
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||||
|
recipient_list=[email]
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"[send_weather_email] Email sent successfully")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"[send_weather_email] Failed: {e}")
|
|
@ -34,7 +34,8 @@
|
||||||
width: 250px;
|
width: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button,
|
||||||
|
.link-button {
|
||||||
padding: 0.6rem 1.2rem;
|
padding: 0.6rem 1.2rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -43,9 +44,13 @@
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover,
|
||||||
|
.link-button:hover {
|
||||||
background-color: #0056b3;
|
background-color: #0056b3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,10 +74,15 @@
|
||||||
color: red;
|
color: red;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bottom-link {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1> EDP Weather Request</h1>
|
<h1>EDP Weather Request</h1>
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="text" name="city" placeholder="Enter the name of the city" required>
|
<input type="text" name="city" placeholder="Enter the name of the city" required>
|
||||||
|
@ -91,5 +101,9 @@
|
||||||
{% if error %}
|
{% if error %}
|
||||||
<p class="error">{{ error }}</p>
|
<p class="error">{{ error }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="bottom-link">
|
||||||
|
<a href="{% url 'schedule_email' %}" class="link-button">Send Weather by Email</a>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Schedule Weather Email</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: "Segoe UI", sans-serif;
|
||||||
|
background: #f4f6f8;
|
||||||
|
margin: 0;
|
||||||
|
padding: 2rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
background-color: white;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
max-width: 400px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
input[type="email"],
|
||||||
|
input[type="time"] {
|
||||||
|
padding: 0.6rem 1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
.link-button {
|
||||||
|
padding: 0.6rem 1.2rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
border: none;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
text-decoration: none;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover,
|
||||||
|
.link-button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-link {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Send Weather Email</h1>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="text" name="city" placeholder="Enter city" required>
|
||||||
|
<input type="email" name="email" placeholder="Enter your email" required>
|
||||||
|
<button type="submit">Send</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="bottom-link">
|
||||||
|
<a href="{% url 'index' %}" class="link-button">Check Weather Online</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
10
weather_project/weather/templates/weather/success.html
Normal file
10
weather_project/weather/templates/weather/success.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Scheduled</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>✅ Email scheduled successfully!</h1>
|
||||||
|
<a href="/">Go back</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,6 +1,9 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
from django.shortcuts import render
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.index, name='index'),
|
path('', views.index, name='index'),
|
||||||
|
path('schedule/', views.schedule_email_view, name='schedule_email'),
|
||||||
|
path('success/', lambda request: render(request, 'weather/success.html'), name='email_scheduled_success')
|
||||||
]
|
]
|
|
@ -2,7 +2,15 @@ import os
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
from django.shortcuts import render
|
import json
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.shortcuts import render, redirect
|
||||||
|
from django_celery_beat.models import PeriodicTask, CrontabSchedule
|
||||||
|
from django.utils import timezone
|
||||||
|
from .forms import EmailScheduleForm
|
||||||
|
from .tasks import send_weather_email
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -48,3 +56,21 @@ def index(request):
|
||||||
'weather': weather_data,
|
'weather': weather_data,
|
||||||
'error': error,
|
'error': error,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def schedule_email_view(request):
|
||||||
|
email_sent = False
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = EmailScheduleForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
city = form.cleaned_data['city']
|
||||||
|
email = form.cleaned_data['email']
|
||||||
|
|
||||||
|
send_weather_email.delay(city, email)
|
||||||
|
email_sent = True # флаг для шаблона
|
||||||
|
else:
|
||||||
|
form = EmailScheduleForm()
|
||||||
|
|
||||||
|
return render(request, 'weather/schedule_email.html', {'form': form, 'email_sent': email_sent})
|
|
@ -0,0 +1,3 @@
|
||||||
|
from .celery import app as celery_app
|
||||||
|
|
||||||
|
__all__ = ['celery_app']
|
8
weather_project/weather_project/celery.py
Normal file
8
weather_project/weather_project/celery.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import os
|
||||||
|
from celery import Celery
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'weather_project.settings')
|
||||||
|
|
||||||
|
app = Celery('weather_project')
|
||||||
|
app.config_from_object('django.conf:settings', namespace='CELERY')
|
||||||
|
app.autodiscover_tasks()
|
|
@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/5.1/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from decouple import config
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
@ -37,6 +38,7 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
'django_celery_beat',
|
||||||
'weather'
|
'weather'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -154,3 +156,16 @@ LOGGING = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Celery broker
|
||||||
|
CELERY_BROKER_URL = 'amqp://guest:guest@localhost:5673//'
|
||||||
|
|
||||||
|
# Email
|
||||||
|
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
|
EMAIL_HOST = 'localhost'
|
||||||
|
EMAIL_PORT = 1025
|
||||||
|
EMAIL_USE_TLS = False
|
||||||
|
EMAIL_HOST_USER = ''
|
||||||
|
EMAIL_HOST_PASSWORD = ''
|
||||||
|
DEFAULT_FROM_EMAIL = 'test@example.com'
|
Loading…
Reference in a new issue