Djoser сброс пароля по электронной почте с помощью Celery

Я хочу отправить письмо со сброшенным паролем, используя Celery. Я пытаюсь переопределить метод reset_password класса djoser.views.UserViewSet:

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)

Но я получаю ошибку:

Задача Сельдерея:

@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)

Что я уже пробовал:

Я попытался изменить сериализатор для задачи на 'pickle':

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

Но я получаю новую ошибку:

Как я могу решить эту проблему?

Или как еще я могу это сделать?

Проблема решена!

Вместо запроса я передаю необходимые поля в контекст, а вместо пользователя - 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)

Это работает, потому что если вы не посылаете запрос на Password Reset E-mail, 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

В задаче я получаю пользователя по 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)
Вернуться на верх