使用Celery与django-verify-email发送验证邮件时遇序列化及属性错误
Django-verify-email结合Celery异步发送验证邮件报错问题
我尝试用django-verify-email包搭配Celery实现异步发送邮件验证的功能,以下是我的代码:
task.py
from celery import shared_task from verify_email.email_handler import send_verification_email @shared_task def send_user_email(request, form): inactive_user = send_verification_email(request, form)
sign_up.py
from django.shortcuts import render, redirect from django.contrib import messages from .tasks import send_user_email def signup(request, form_class, template_name, success_message, redirect_url): if request.user.is_authenticated: return redirect(redirect_url) form = form_class() if request.method == 'POST': form = form_class(request.POST) if form.is_valid(): send_user_email.delay(request, form=form) messages.success(request, success_message) return redirect(redirect_url) else: messages.error(request, f'Error creating account. Please check the form.') return render(request, template_name, {'form': form}) context = {'form': form} return render(request, template_name, context)
任务控制台错误信息
[2024-01-03 19:08:34,231: ERROR/ForkPoolWorker-2] Task user.tasks.send_user_email[1f521272-e7e4-4ae0-8fd8-ef1e0ad551e5] raised unexpected: AttributeError("'dict' object has no attribute 'save'") Traceback (most recent call last): File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/celery/app/trace.py", line 477, in trace_task R = retval = fun(*args, **kwargs) File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/celery/app/trace.py", line 760, in __protected_call__ return self.run(*args, **kwargs) File "/home/foli/code/django/freeacademy/user/tasks.py", line 7, in send_user_email inactive_user = send_verification_email(request, form_data) File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/verify_email/email_handler.py", line 94, in send_verification_email return _VerifyEmail().send_verification_link(request, form) File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/verify_email/email_handler.py", line 32, in send_verification_link inactive_user = form.save(commit=False) AttributeError: 'dict' object has no attribute 'save'
Web控制台错误信息
Traceback (most recent call last): File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/kombu/serialization.py", line 41, in _reraise_errors yield File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/kombu/serialization.py", line 220, in dumps payload = encoder(data) File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/kombu/utils/json.py", line 57, in dumps return _dumps(s, cls=cls, **dict(default_kwargs, **kwargs)) File "/usr/lib/python3.10/json/__init__.py", line 238, in dumps **kw).encode(obj) File "/usr/lib/python3.10/json/encoder.py", line 199, in encode chunks = self.iterencode(o, _one_shot=True) File "/usr/lib/python3.10/json/encoder.py", line 257, in iterencode return _iterencode(o, 0) File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/kombu/utils/json.py", line 45, in default return super().default(o) File "/usr/lib/python3.10/json/encoder.py", line 179, in default raise TypeError(f'Object of type {o.__class__.__name__} ' During handling of the above exception (Object of type WSGIRequest is not JSON serializable), another exception occurred: File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner response = get_response(request) File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/django/core/handlers/base.py", line 197, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/home/foli/code/django/freeacademy/user/views.py", line 128, in teacher_signup return signup( File "/home/foli/code/django/freeacademy/user/user_signup.py", line 17, in signup send_user_email.delay(request, form) File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/celery/app/task.py", line 444, in delay return self.apply_async(args, kwargs) File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/celery/app/task.py", line 594, in apply_async return app.send_task( File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/celery/app/base.py", line 799, in send_task amqp.send_task_message(P, name, message, **options) File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/celery/app/amqp.py", line 518, in send_task_message ret = producer.publish( File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/kombu/messaging.py", line 175, in publish body, content_type, content_encoding = self._prepare( File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/kombu/messaging.py", line 268, in _prepare body) = dumps(body, serializer=serializer) File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/kombu/serialization.py", line 219, in dumps with _reraise_errors(EncodeError): File "/usr/lib/python3.10/contextlib.py", line 153, in __exit__ self.gen.throw(typ, value, traceback) File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/kombu/serialization.py", line 45, in _reraise_errors reraise(wrapper, wrapper(exc), sys.exc_info()[2]) File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/kombu/exceptions.py", line 34, in reraise raise value.with_traceback(tb) File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/kombu/serialization.py", line 41, in _reraise_errors yield File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/kombu/serialization.py", line 220, in dumps payload = encoder(data) File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/kombu/utils/json.py", line 57, in dumps return _dumps(s, cls=cls, **dict(default_kwargs, **kwargs)) File "/usr/lib/python3.10/json/__init__.py", line 238, in dumps **kw).encode(obj) File "/usr/lib/python3.10/json/encoder.py", line 199, in encode chunks = self.iterencode(o, _one_shot=True) File "/usr/lib/python3.10/json/encoder.py", line 257, in iterencode return _iterencode(o, 0) File "/home/foli/code/django/freeacademy/.freemindedAcademy/lib/python3.10/site-packages/kombu/utils/json.py", line 45, in default return super().default(o) File "/usr/lib/python3.10/json/encoder.py", line 179, in default raise TypeError(f'Object of type {o.__class__.__name__} ' Exception Type: EncodeError at /users/teacher_signup/ Exception Value: Object of type WSGIRequest is not JSON serializable
我猜测问题出在包中的inactive_user变量,但试了多种配置都没解决。
错误原因
- WSGIRequest无法序列化:Celery默认用JSON序列化任务参数,Django的
WSGIRequest对象不支持JSON序列化,触发Web控制台的编码错误。 - 表单对象序列化丢失方法:表单对象被序列化后会变成普通字典,不再具备
save()方法,导致任务执行时抛出属性错误。
修复方案
方案一:修改视图与任务,传递用户ID和请求上下文
更新sign_up.py
from django.shortcuts import render, redirect from django.contrib import messages from .tasks import send_user_email def signup(request, form_class, template_name, success_message, redirect_url): if request.user.is_authenticated: return redirect(redirect_url) form = form_class() if request.method == 'POST': form = form_class(request.POST) if form.is_valid(): # 先创建未激活用户并保存到数据库 inactive_user = form.save(commit=False) inactive_user.is_active = False inactive_user.save() # 传递用户ID、协议和域名给任务 send_user_email.delay( user_id=inactive_user.id, scheme=request.scheme, host=request.get_host() ) messages.success(request, success_message) return redirect(redirect_url) else: messages.error(request, 'Error creating account. Please check the form.') return render(request, template_name, {'form': form}) context = {'form': form} return render(request, template_name, context)
更新task.py
from celery import shared_task from django.contrib.auth import get_user_model from django.http import HttpRequest from verify_email.email_handler import send_verification_email # 替换成你的注册表单类 from .forms import SignUpForm User = get_user_model() @shared_task def send_user_email(user_id, scheme, host): user = User.objects.get(id=user_id) # 构建模拟request对象,满足django-verify-email的要求 mock_request = HttpRequest() mock_request.scheme = scheme mock_request.META['HTTP_HOST'] = host # 用用户实例初始化表单 form = SignUpForm(instance=user) send_verification_email(mock_request, form)
方案二:手动生成验证链接发送邮件(不依赖表单)
如果不想依赖django-verify-email的表单处理逻辑,可以手动实现邮件发送:
from celery import shared_task from django.contrib.auth import get_user_model from django.core.mail import send_mail from django.contrib.auth.tokens import default_token_generator from django.urls import reverse User = get_user_model() @shared_task def send_user_email(user_id, scheme, host): user = User.objects.get(id=user_id) # 生成验证token token = default_token_generator.make_token(user) # 构建验证URL verify_path = reverse('verify_email', kwargs={'uidb64': user.pk, 'token': token}) full_verify_url = f"{scheme}://{host}{verify_path}" # 发送验证邮件 subject = '邮箱验证通知' message = f"请点击以下链接完成邮箱验证:\n{full_verify_url}" send_mail(subject, message, 'your-email@example.com', [user.email])
内容的提问来源于stack exchange,提问作者foliwe83
相关产品推荐
相关产品推荐

