#Async Views
23 messages · Page 1 of 1 (latest)
async def create_and_email_user(user):
await user.save()
await signup_email(user)
async def create_new_user(request):
if not request.user.is_authenticated:
redirect('login')
form = CustomUserForm()
if request.method == 'POST':
form = CustomUserForm(request.POST)
if form.is_valid():
if form.cleaned_data['password1'] == form.cleaned_data['password2']:
user = form.save(commit=False)
form.clean_daily_hours()
user.username = user.username.lower()
await async_to_sync(create_and_email_user)(user)
messages.success(request, "User has been created successfully.")
return redirect('users')
else:
messages.error(request, "Passwords are different, please check again.")
else:
messages.error(request, "Invalid inputs, please check again!")
context = {
"form": form
}
return render(request, "project_manager/admin_control/users/create_user.html", context)
await signup_email(user)
How fast is it when you comment out this line?
This is your primary issue, sync or async, it's not going to matter. You're waiting for the email to be sent.
This involves resolving the mail servers's hostname, connecting, sending the email, which is generally slow.
it didn't work to check:
You cannot call this from an async context - use a thread or sync_to_async.
it didnt pass the is_authenticated line, and i looked up in the document and added sync to async like but didn't work either
is the primary reason for moving to async, that the request is "slow" ?
yeah i know but it can benefit from the time between each process to handle something from the other task like sending email
yes
well sadly that will not help, calling send mail asynchronously will not improve the performance. You need to run that in the background instead.
what is your suggestion?
The most obvious answer for this is, use celery. [or django-q, or any other]
thank u, i'll try it out.
can i ask tho, do i need redis with celery too?
you need a broker, the simplest to install and use is generally redis.
As @restive parcel says. Celery is the best way in your case. Little bit easier way is CRON job which will fire every minutes and send emails to appropriate users + mark them as "email_sent = true". Bit this is workaround if you don't want (or you can't) run redis on your server.
I don't have a problem with using celery or redis, it just my future hosting plan is simple and am looking for something cheap,
can I setup everything in one server?
no problem with setting up everything in one server 🙂
thank u guys
@restive parcel @silent hatch
and big thank for this guy on YouTube:
@ajudmeister
it's not a marketing thing, this man is just so kind he replied to my comments and gave me a great solution using signals and it worked like a charm:
from django.dispatch import receiver
from django.db.models.signals import post_save
from django.core.mail import EmailMultiAlternatives
import threading
@receiver(post_save, sender=CustomUser)
def send_email_async(sender, instance, created, **kwargs):
if created:
post = instance
post_email = post.email
email_subject = 'Email Title'
email_body = 'Email Body'
# Use a separate thread to send the email asynchronously
email_thread = threading.Thread(target=send_email, args=(email_subject, email_body, post_email))
email_thread.start()
def send_email(subject, message, recipient):
email = EmailMultiAlternatives(subject, '', to=[recipient])
email.attach_alternative(message, 'text/html')
email.send()
he is so underrated and he is building great things in django that worth taking a look, like a real-time chat app using channels and WebSockets with HTMX and redis and many applications.
I'm glad you're enjoying their content, I'm skeptical about the solution. AFAIK (unless corrected, I'll look it up later) the signals run on the same thread, so using a post_save signal is no different than adding that code after your "save" your CustomUser.
Additionally I dont believe you control the worker process, so it may be possible that the worker dies/restarts/etc before that thread completes its job.
Again, this is not to say that this solution doesn't work, threading is just not a usual solution to such problems.
I am not familiar of how it works actually, but I could say now when I add the user/send email it's 3x faster, it doesn't take the time to add the user then send the email like before, I don't know how, but it definitely not like sending the email after "save".
although I get your point and you are correct as far as I know, but when using threading to send emails within the signal handler, a separate thread is indeed created: email_thread = threading.Thread
as it's now it sends the email asynchronously and does not block the main thread, allowing the main thread to continue excution without waiting for the email sending process to complete.
but again am not fimiliar with how it works exactly, I could be wrong, maybe it give the appearance of faster execution while running the signal handler itself synchronously within the main django thread.
glad you brought that out, I will look into this and maybe use celery after all.
What I meant was, you could remove the "signal" and just call the rest as is, it will function the same:
-@receiver(post_save, sender=CustomUser)
-def send_email_async(sender, instance, created, **kwargs):
+def send_email_async(instance):
if created:
post = instance
post_email = post.email
email_subject = 'Email Title'
email_body = 'Email Body'
# Use a separate thread to send the email asynchronously
email_thread = threading.Thread(target=send_email, args=(email_subject, email_body, post_email))
email_thread.start()
def send_email(subject, message, recipient):
email = EmailMultiAlternatives(subject, '', to=[recipient])
email.attach_alternative(message, 'text/html')
email.send()
and just call send_email_async(instance) after your user save()
ok, so now I can control the worker process without relyeing on django signals?