简体   繁体   English

Djoser 使用 Celery 重置密码电子邮件

[英]Djoser reset password email with Celery

I want to send reset password email using Celery.我想使用 Celery 发送重置密码电子邮件。 I try to override the reset_password method of the class djoser.views.UserViewSet :我尝试覆盖djoser.views.UserViewSet类的reset_password方法:

class CustomUserViewSet(UserViewSet):

    @action(["post"], detail=False)
    def reset_password(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.get_user()

        if user:
            send_reset_password_email.delay(
                self.request,
                {'user': user},
                [get_user_email(user)]
            )

        return Response(status=status.HTTP_200_OK)

But I get an error:但是我得到一个错误:

Traceback (most recent call last):
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\serialization.py", line 39, in _reraise_errors
    yield
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\serialization.py", line 210, in dumps
    payload = encoder(data)
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\utils\json.py", line 68, in dumps
    return _dumps(s, cls=cls or _default_encoder,
  File "C:\Users\lev_k\AppData\Local\Programs\Python\Python310\lib\json\__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "C:\Users\lev_k\AppData\Local\Programs\Python\Python310\lib\json\encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "C:\Users\lev_k\AppData\Local\Programs\Python\Python310\lib\json\encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\utils\json.py", line 58, in default
    return super().default(o)
  File "C:\Users\lev_k\AppData\Local\Programs\Python\Python310\lib\json\encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Request is not JSON serializable

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Dev\rich-peach\venv\lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "C:\Dev\rich-peach\venv\lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:\Dev\rich-peach\venv\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "C:\Dev\rich-peach\venv\lib\site-packages\rest_framework\viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
  File "C:\Dev\rich-peach\venv\lib\site-packages\rest_framework\views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "C:\Dev\rich-peach\venv\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "C:\Dev\rich-peach\venv\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
    raise exc
  File "C:\Dev\rich-peach\venv\lib\site-packages\rest_framework\views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "C:\Dev\rich-peach\backend\api\views.py", line 51, in reset_password
    send_reset_password_email.delay(
  File "C:\Dev\rich-peach\venv\lib\site-packages\celery\app\task.py", line 425, in delay
    return self.apply_async(args, kwargs)
  File "C:\Dev\rich-peach\venv\lib\site-packages\celery\app\task.py", line 575, in apply_async
    return app.send_task(
  File "C:\Dev\rich-peach\venv\lib\site-packages\celery\app\base.py", line 788, in send_task
    amqp.send_task_message(P, name, message, **options)
  File "C:\Dev\rich-peach\venv\lib\site-packages\celery\app\amqp.py", line 510, in send_task_message
    ret = producer.publish(
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\messaging.py", line 166, in publish
    body, content_type, content_encoding = self._prepare(
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\messaging.py", line 254, in _prepare
    body) = dumps(body, serializer=serializer)
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\serialization.py", line 209, in dumps
    with _reraise_errors(EncodeError):
  File "C:\Users\lev_k\AppData\Local\Programs\Python\Python310\lib\contextlib.py", line 153, in __exit__
    self.gen.throw(typ, value, traceback)
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\serialization.py", line 43, in _reraise_errors
    reraise(wrapper, wrapper(exc), sys.exc_info()[2])
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\exceptions.py", line 21, in reraise
    raise value.with_traceback(tb)
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\serialization.py", line 39, in _reraise_errors
    yield
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\serialization.py", line 210, in dumps
    payload = encoder(data)
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\utils\json.py", line 68, in dumps
    return _dumps(s, cls=cls or _default_encoder,
  File "C:\Users\lev_k\AppData\Local\Programs\Python\Python310\lib\json\__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "C:\Users\lev_k\AppData\Local\Programs\Python\Python310\lib\json\encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "C:\Users\lev_k\AppData\Local\Programs\Python\Python310\lib\json\encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\utils\json.py", line 58, in default
    return super().default(o)
  File "C:\Users\lev_k\AppData\Local\Programs\Python\Python310\lib\json\encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
kombu.exceptions.EncodeError: Object of type Request is not JSON serializable

Celery task:芹菜任务:

@app.task(bind=True, default_retry_delay=5 * 60)
def send_reset_password_email(self, request, context, email):
    try:
        PasswordResetEmail(request, context).send(email)
    except Exception as exc:
        raise self.retry(exc=exc, countdown=60)

What I've already tried:我已经尝试过的:

I tried to change the serializer for the task to 'pickle' :我试图将任务的序列化程序更改为'pickle'

@app.task(bind=True, default_retry_delay=5 * 60, serializer='pickle')

But I get a new error:但是我收到一个新错误:

Traceback (most recent call last):
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\serialization.py", line 39, in _reraise_errors
    yield
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\serialization.py", line 210, in dumps
    payload = encoder(data)
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\serialization.py", line 334, in pickle_dumps
    return dumper(obj, protocol=pickle_protocol)
TypeError: cannot pickle '_io.BufferedReader' object

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Dev\rich-peach\venv\lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "C:\Dev\rich-peach\venv\lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:\Dev\rich-peach\venv\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "C:\Dev\rich-peach\venv\lib\site-packages\rest_framework\viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
  File "C:\Dev\rich-peach\venv\lib\site-packages\rest_framework\views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "C:\Dev\rich-peach\venv\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "C:\Dev\rich-peach\venv\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
    raise exc
  File "C:\Dev\rich-peach\venv\lib\site-packages\rest_framework\views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "C:\Dev\rich-peach\backend\api\views.py", line 51, in reset_password
    send_reset_password_email.delay(
  File "C:\Dev\rich-peach\venv\lib\site-packages\celery\app\task.py", line 425, in delay
    return self.apply_async(args, kwargs)
  File "C:\Dev\rich-peach\venv\lib\site-packages\celery\app\task.py", line 575, in apply_async
    return app.send_task(
  File "C:\Dev\rich-peach\venv\lib\site-packages\celery\app\base.py", line 788, in send_task
    amqp.send_task_message(P, name, message, **options)
  File "C:\Dev\rich-peach\venv\lib\site-packages\celery\app\amqp.py", line 510, in send_task_message
    ret = producer.publish(
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\messaging.py", line 166, in publish
    body, content_type, content_encoding = self._prepare(
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\messaging.py", line 254, in _prepare
    body) = dumps(body, serializer=serializer)
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\serialization.py", line 209, in dumps
    with _reraise_errors(EncodeError):
  File "C:\Users\lev_k\AppData\Local\Programs\Python\Python310\lib\contextlib.py", line 153, in __exit__
    self.gen.throw(typ, value, traceback)
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\serialization.py", line 43, in _reraise_errors
    reraise(wrapper, wrapper(exc), sys.exc_info()[2])
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\exceptions.py", line 21, in reraise
    raise value.with_traceback(tb)
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\serialization.py", line 39, in _reraise_errors
    yield
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\serialization.py", line 210, in dumps
    payload = encoder(data)
  File "C:\Dev\rich-peach\venv\lib\site-packages\kombu\serialization.py", line 334, in pickle_dumps
    return dumper(obj, protocol=pickle_protocol)
kombu.exceptions.EncodeError: cannot pickle '_io.BufferedReader' object

How can I solve this problem?我怎么解决这个问题?

Or how else can I do it?或者我还能怎么做?

Problem solved!问题解决了!

Instead of request, I pass the necessary fields to context, and instead of user - user.id:我没有请求,而是将必要的字段传递给上下文,而不是用户 - user.id:

@action(['post'], detail=False)
    def reset_password(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.get_user()

        if user:
            send_reset_password_email.delay(
                {
                    'user_id': user.id,
                    'domain': request.get_host(),
                    'protocol': 'https' if request.is_secure() else 'http',
                    'site_name': request.get_host()
                },
                [get_user_email(user)]
            )

        return Response(status=status.HTTP_200_OK)

This works because if you don't send a request to Password Reset E-mail, Djoser searches for the necessary fields in context:这是有效的,因为如果您不向密码重置电子邮件发送请求,Djoser 会在上下文中搜索必要的字段:

class BaseEmailMessage(mail.EmailMultiAlternatives, ContextMixin):
    def get_context_data(self, **kwargs):
        ctx = super(BaseEmailMessage, self).get_context_data(**kwargs)
        context = dict(ctx, **self.context)
        if self.request:
            site = get_current_site(self.request)
            domain = context.get('domain') or (
                getattr(settings, 'DOMAIN', '') or site.domain
            )
            protocol = context.get('protocol') or (
                'https' if self.request.is_secure() else 'http'
            )
            site_name = context.get('site_name') or (
                getattr(settings, 'SITE_NAME', '') or site.name
            )
            user = context.get('user') or self.request.user
        else:
            domain = context.get('domain') or getattr(settings, 'DOMAIN', '')
            protocol = context.get('protocol') or 'http'
            site_name = context.get('site_name') or getattr(
                settings, 'SITE_NAME', ''
            )
            user = context.get('user')

        context.update({
            'domain': domain,
            'protocol': protocol,
            'site_name': site_name,
            'user': user
        })
        return context

And in the task, I get the user by id, which I passed to context:在任务中,我通过传递给上下文的 id 获取用户:

@app.task(bind=True, default_retry_delay=5 * 60)
def send_reset_password_email(self, context, email):
    try:
        context['user'] = CustomUser.objects.get(id=context.get('user_id'))
        PasswordResetEmail(context=context).send(email)
    except Exception as exc:
        raise self.retry(exc=exc, countdown=60)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM