在 Django 中使用 Celery 渲染电子邮件时TemplateDoesNotExist

问题描述 投票:0回答:1

我正在为论坛网站开发一个 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}
python django docker django-rest-framework celery
1个回答
0
投票

尝试下面的代码:

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异步执行的。

© www.soinside.com 2019 - 2024. All rights reserved.