我正在为论坛网站开发一个 API,并且有一个基于分类的视图,该视图向用户发送电子邮件以确认他的电子邮件。我有一个 celery 任务,它发送电子邮件,在该任务中我将数据呈现为字符串(电子邮件模板)。但是当我在 APIView 中调用任务时,出现模板不存在的错误。然而,当我将它作为常规函数调用时,没有
delay
方法,它会按预期工作。另外,Celery本身似乎没有问题,因为我可以成功运行其他任务。这是视图的代码。
class RequestEmailToConfirmAPIView(GenericAPIView):
permission_classes = [IsAuthenticated, EmailIsNotConfirmed]
success_message = 'The message has just been delivered.'
serializer_class = DummySerializer
def get(self, request):
current_url = get_current_site(request, path='email-confirmation-result')
user = request.user
token = EmailConfirmationToken.objects.create(user=user)
send_confirmation_email.delay(
template_name='email/confirm_email.txt', current_url=current_url,
email=user.email, token_id=token.id, user_id=user.id
)
return Response(data={"message": self.success_message}, status=status.HTTP_201_CREATED)
注意:
get_current_site
函数返回包含协议(http)的当前URL,以便创建供用户单击并确认其电子邮件的URL
send_confirmation_email
任务代码:
@shared_task
def send_confirmation_email(template_name: str, current_url: str, email: str, token_id: int, user_id: int):
"""
Отправляет письмо для подтверждения определенных действий.
"""
data = {
'current_site': str(current_url),
'token_id': str(token_id),
'user_id': str(user_id)
}
# message = get_template(template_name).render(data)
message = render_to_string(template_name, context=data)
send_mail(
subject='Пожалуйста, подтвердите почту',
message=message,
from_email='[email protected]',
recipient_list=[email],
fail_silently=True
)
这是文件夹结构(API视图和任务放在论坛包中):
├── backend
│ ├── admin_info.txt
│ ├── celery_app.py
│ ├── core
│ │ ├── asgi.py
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── Dockerfile
│ ├── fixture.json
│ ├── forum
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── helpers.py
│ │ ├── __init__.py
│ │ ├── logic.py
│ │ ├── management
│ │ │ ├── commands
│ │ │ │ ├── createdata.py
│ │ │ │ └── __init__.py
│ │ │ └── __init__.py
│ │ ├── models.py
│ │ ├── permissions.py
│ │ ├── serializers.py
│ │ ├── urls.py
│ │ ├── utils.py
│ │ ├── validators.py
│ │ └── views.py
│ ├── manage.py
│ ├── middleware.py
│ ├── requirements.txt
│ ├── templates
│ │ └── email
│ │ ├── confirm_email.txt
│ │ ├── password_reset_email.html
│ │ ├── password_reset_email.txt
│ │ └── restore_account.txt
│ └── web.env
├── celerybeat-schedule
├── docker-compose.yml
├── nginx
│ ├── Dockerfile
│ ├── nginx.conf
│ └── proxy_params
├── README.md
└── setup.cfg
这是 celery_app 配置:
from celery import Celery
from django.conf import settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')
app = Celery('backend.core')
app.config_from_object('django.conf:settings')
app.conf.broker_url = settings.CELERY_BROKER_URL
app.autodiscover_tasks()
和 docker-compose.yml:
services:
# PostgreSQL
db:
image: postgres:14.1-alpine
restart: always
env_file:
- backend/web.env
ports:
- '5432:5432'
volumes:
- db:/var/lib/postgresql/data
# Backend Django
backend:
build:
context: ./backend
command:
bash -c "./manage.py collectstatic --noinput && ./manage.py migrate &&
gunicorn -b 0.0.0.0:8000 core.wsgi:application —log-level debug"
ports:
- '8000:8000'
depends_on:
- db
env_file:
- backend/web.env
volumes:
- ./backend/:/backend
- static_volume:/backend/static
- media_volume:/backend/media
# Frontend Next.js
frontend:
build:
context: ./frontend
depends_on:
- backend
volumes:
- .:/app/frontend
- /app/node_modules
- /app/.next
ports:
- '3000:3000'
# Nginx
nginx:
build:
dockerfile: ./Dockerfile
context: ./nginx
ports:
- '80:80'
volumes:
- static_volume:/backend/static
- media_volume:/backend/media
depends_on:
- backend
- frontend
- db
# Redis
redis:
image: redis:7.0.5-alpine
container_name: redis
# Celery Worker 1
celery-worker:
restart: always
build:
context: ./backend
entrypoint: /bin/sh
command: -c "export PYTHONPATH=/backend:/backend/backend && celery -A backend.celery_app:app worker --loglevel=info"
volumes:
- .:/backend
container_name: celery-worker
depends_on:
- db
- redis
- backend
links:
- redis
env_file:
- backend/web.env
celery-beat:
restart: always
build:
context: ./backend
entrypoint: /bin/sh
command: -c "export PYTHONPATH=/backend:/backend/backend && celery -A backend.celery_app:app beat --loglevel=info"
volumes:
- .:/backend
container_name: celery-beat
depends_on:
- db
- redis
- backend
links:
- redis
env_file:
- backend/web.env
# Flower
flower:
image: docker.io/mher/flower
command: ["celery", "--broker=redis://redis:6379/0", "flower", "--port=5555"]
ports:
- 5555:5555
volumes:
db:
driver: local
react_build:
static_volume:
driver: local
media_volume:
driver: local
和回溯:
celery-worker | [2023-10-13 19:02:04,994: WARNING/ForkPoolWorker-2] Message: 'Task %(name)s[%(id)s] %(description)s: %(exc)s'
celery-worker | Arguments: {'hostname': 'celery@c4d7e87c7784', 'id': 'dc04404e-3021-46c1-b6e6-385b52125953', 'name': 'accounts.tasks.send_confirmation_email', 'exc': "TemplateDoesNotExist('email/confirm_email.txt')", 'traceback': 'Traceback (most recent call last):\n File "/usr/local/lib/python3.11/site-packages/celery/app/trace.py", line 451, in trace_task\n R = retval = fun(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^\n File "/usr/local/lib/python3.11/site-packages/celery/app/trace.py", line 734, in __protected_call__\n return self.run(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/backend/backend/accounts/tasks.py", line 19, in send_confirmation_email\n message = render_to_string(template_name, context=data)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/usr/local/lib/python3.11/site-packages/django/template/loader.py", line 61, in render_to_string\n template = get_template(template_name, using=using)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/usr/local/lib/python3.11/site-packages/django/template/loader.py", line 19, in get_template\n raise TemplateDoesNotExist(template_name, chain=chain)\ndjango.template.exceptions.TemplateDoesNotExist: email/confirm_email.txt\n', 'args': '[]', 'kwargs': "{'template_name': 'email/confirm_email.txt', 'current_url': 'http://localhost:8000/api/v1/account/email-confirmation-result/', 'email': '[email protected]', 'token_id': UUID('6ac7db2a-26bb-45aa-beea-187c56cd25c5'), 'user_id': 22}", 'description': 'raised unexpected', 'internal': False}
尝试下面的代码:
from django.conf import settings
from django.core.mail import send_mail
from django.template.loader import render_to_string
from celery import shared_task
from forum.models import EmailConfirmationToken
from django.contrib.sites.shortcuts import get_current_site
@shared_task
def send_confirmation_email(template_name: str, current_url: str, email: str, token_id: int, user_id: int):
data = {
'current_site': str(current_url),
'token_id': str(token_id),
'user_id': str(user_id)
}
template_path = settings.BASE_DIR / 'forum' / 'templates' # Specify the path to your template directory
template_name = template_path / 'email' / 'confirm_email.txt'
message = render_to_string(template_name, context=data)
send_mail(
subject='Пожалуйста, подтвердите почту',
message=message,
from_email='[email protected]',
recipient_list=[email],
fail_silently=True
)
通过使用settings.BASE_DIR,您可以确保正确解析模板目录的路径,并确保它存在/已创建,即使任务是由Celery异步执行的。