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
|
||||
|
||||
# Устанавливаем рабочую директорию
|
||||
WORKDIR /app
|
||||
|
||||
# Копируем зависимости
|
||||
COPY requirements.txt .
|
||||
|
||||
# Устанавливаем зависимости
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Копируем всё приложение в контейнер
|
||||
COPY . .
|
||||
|
||||
# Указываем переменные окружения (они будут приходить извне)
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
# Команда запуска сервера
|
||||
CMD ["python", "weather_project/manage.py", "runserver", "0.0.0.0:8000"]
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
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;
|
||||
}
|
||||
|
||||
button {
|
||||
button,
|
||||
.link-button {
|
||||
padding: 0.6rem 1.2rem;
|
||||
font-size: 1rem;
|
||||
border: none;
|
||||
|
@ -43,9 +44,13 @@
|
|||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -69,10 +74,15 @@
|
|||
color: red;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.bottom-link {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1> EDP Weather Request</h1>
|
||||
<h1>EDP Weather Request</h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<input type="text" name="city" placeholder="Enter the name of the city" required>
|
||||
|
@ -91,5 +101,9 @@
|
|||
{% if error %}
|
||||
<p class="error">{{ error }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="bottom-link">
|
||||
<a href="{% url 'schedule_email' %}" class="link-button">Send Weather by Email</a>
|
||||
</div>
|
||||
</body>
|
||||
</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.shortcuts import render
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
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 logging
|
||||
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__)
|
||||
|
||||
|
@ -48,3 +56,21 @@ def index(request):
|
|||
'weather': weather_data,
|
||||
'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 decouple import config
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
@ -37,6 +38,7 @@ INSTALLED_APPS = [
|
|||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django_celery_beat',
|
||||
'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