diff --git a/Dockerfile b/Dockerfile index 7bd5303..8081524 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] diff --git a/requirements.txt b/requirements.txt index 0139f1b..743ab48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ Django>=4.2 -requests \ No newline at end of file +requests +celery +django-celery-beat \ No newline at end of file diff --git a/weather_project/weather/forms.py b/weather_project/weather/forms.py new file mode 100644 index 0000000..6fbdea0 --- /dev/null +++ b/weather_project/weather/forms.py @@ -0,0 +1,5 @@ +from django import forms + +class EmailScheduleForm(forms.Form): + city = forms.CharField(max_length=100) + email = forms.EmailField() \ No newline at end of file diff --git a/weather_project/weather/tasks.py b/weather_project/weather/tasks.py new file mode 100644 index 0000000..557e8d2 --- /dev/null +++ b/weather_project/weather/tasks.py @@ -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}") diff --git a/weather_project/weather/templates/weather/index.html b/weather_project/weather/templates/weather/index.html index 8bd1133..5c70037 100644 --- a/weather_project/weather/templates/weather/index.html +++ b/weather_project/weather/templates/weather/index.html @@ -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; + } -

EDP Weather Request

+

EDP Weather Request

+
{% csrf_token %} @@ -91,5 +101,9 @@ {% if error %}

{{ error }}

{% endif %} + + diff --git a/weather_project/weather/templates/weather/schedule_email.html b/weather_project/weather/templates/weather/schedule_email.html new file mode 100644 index 0000000..4b3be90 --- /dev/null +++ b/weather_project/weather/templates/weather/schedule_email.html @@ -0,0 +1,83 @@ + + + + + Schedule Weather Email + + + +

Send Weather Email

+ + + {% csrf_token %} + + + +
+ + + + diff --git a/weather_project/weather/templates/weather/success.html b/weather_project/weather/templates/weather/success.html new file mode 100644 index 0000000..bd43f2d --- /dev/null +++ b/weather_project/weather/templates/weather/success.html @@ -0,0 +1,10 @@ + + + + Scheduled + + +

✅ Email scheduled successfully!

+ Go back + + \ No newline at end of file diff --git a/weather_project/weather/urls.py b/weather_project/weather/urls.py index c486297..393620f 100644 --- a/weather_project/weather/urls.py +++ b/weather_project/weather/urls.py @@ -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') ] \ No newline at end of file diff --git a/weather_project/weather/views.py b/weather_project/weather/views.py index 7cddce5..a8b3607 100644 --- a/weather_project/weather/views.py +++ b/weather_project/weather/views.py @@ -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}) \ No newline at end of file diff --git a/weather_project/weather_project/__init__.py b/weather_project/weather_project/__init__.py index e69de29..742da6a 100644 --- a/weather_project/weather_project/__init__.py +++ b/weather_project/weather_project/__init__.py @@ -0,0 +1,3 @@ +from .celery import app as celery_app + +__all__ = ['celery_app'] \ No newline at end of file diff --git a/weather_project/weather_project/celery.py b/weather_project/weather_project/celery.py new file mode 100644 index 0000000..7035af5 --- /dev/null +++ b/weather_project/weather_project/celery.py @@ -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() diff --git a/weather_project/weather_project/settings.py b/weather_project/weather_project/settings.py index d5b276a..09b8e74 100644 --- a/weather_project/weather_project/settings.py +++ b/weather_project/weather_project/settings.py @@ -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' \ No newline at end of file