This is part 2 in a 4 part series looking at how to do background/async tasks in Django. See this post for more details.
This is based on my notes from this Git repository: https://github.com/stuartmaxwell/django-django_q-example
Basic Django-Q Example
You could git clone this repository to get it working but I recommend following these manual steps so you understand what’s required to get a basic Django-Q example up and running.
- Before you start, you’ll need a Redis server. If you don’t have one the easiest way is through Docker with the following command:
docker run --name my-redis-server -d -p 127.0.0.1:6379:6379 redis
- Create a virtual environment using the method of your choice, I like to use the following command:
python3.8 -m venv .venv && source .venv/bin/activate && pip install --upgrade pip wheel setuptools > /dev/null
- Create the requirements.txt file to install the packages required:
django
redis
django-q
- Activate your virtual environment and install the requirements in the
requirements.txt
file:
pip install -r requirements.txt
- Create your Django project:
django-admin startproject djangoq_project .
- Open the
settings.py
file and add Django-Q to the list of installed apps to complete installation.
INSTALLED_APPS = [
...
"django_q",
]
- Add the Django configuration in the
settings.py
file:
# Django-Q configuration
Q_CLUSTER = {
"name": "djangoq_project",
"timeout": 60,
"redis": {"host": "127.0.0.1", "port": 6379, "db": 0,},
}
- That’s the base project configuration complete, run the database migrations and create a superuser:
python manage.py migrate
python manage.py createsuperuser
- Add a new app, we’ll make a basic contact form app that stores messages in our database and sends an email to the site owner:
django-admin startapp contact_form
- Add some email configuration to the
settings.py
file. I use Amazon SES but substitute with our own SMTP settings.
# Email
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "" # add your own settings here
EMAIL_PORT = "" # add your own settings here
EMAIL_HOST_USER = "" # add your own settings here
EMAIL_HOST_PASSWORD = "" # add your own settings here
EMAIL_USE_TLS = True # add your own settings here
DEFAULT_FROM_EMAIL = "[email protected]" # your email address
- While you’re in the settings, add your new app to the
INSTALLED_APPS
section:
INSTALLED_APPS = [
...
"contact_form.apps.ContactFormConfig",
]
- Create a model to store the messages sent from the website in
contact_form > models.py
from django.db import models
class ContactForm(models.Model):
email = models.EmailField()
name = models.CharField(max_length=64)
subject = models.CharField(max_length=64)
message = models.TextField()
created_on = models.DateTimeField(auto_now=True)
class Meta:
db_table = "contactform"
verbose_name = "contact form message"
verbose_name_plural = "contact form messages"
def __str__(self):
return self.email
- Expose the model in the Admin site with
contact_form > admin.py
from django.contrib import admin
from .models import ContactForm
class ContactFormAdmin(admin.ModelAdmin):
list_display = ("email", "name", "subject", "created_on")
admin.site.register(ContactForm, ContactFormAdmin)
- Create a contact form in
contact_form > forms.py
(you’ll need to create this file):
from django.forms import ModelForm
from .models import ContactForm
class ContactFormModelForm(ModelForm):
class Meta:
model = ContactForm
fields = ["name", "email", "subject", "message"]
- Create the Django-Q task to send an email in
contact_form > tasks.py
(you’ll need to create this file):
from django.core.mail import send_mail, BadHeaderError
from djangoq_project.settings import DEFAULT_FROM_EMAIL
def send_email_task(to, subject, message):
print(f"from={DEFAULT_FROM_EMAIL}, {to=}, {subject=}, {message=}")
try:
print("About to send_mail")
send_mail(subject, message, DEFAULT_FROM_EMAIL, [DEFAULT_FROM_EMAIL])
except BadHeaderError:
print("BadHeaderError")
except Exception as e:
print(e)
- Create a basic template to use:
contact_form > templates > contact_form > contact_form.html
(you’ll need to create these folders and file):
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Contact Form</title>
</head>
<body>
<h2>Contact Form</h2>
<form method="post" action="">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Send Message">
</form>
</body>
</html>
- Create a view in
contact_form > views.py
from django.views.generic import FormView
from django_q.tasks import async_task
from .forms import ContactFormModelForm
from .tasks import send_email_task
class ContactFormView(FormView):
form_class = ContactFormModelForm
template_name = "contact_form/contact_form.html"
success_url = "/"
def form_valid(self, form):
form.save()
self.send_email(form.cleaned_data)
return super().form_valid(form)
def send_email(self, valid_data):
email = valid_data["email"]
subject = "Contact form sent from website"
message = (
f"You have received a contact form.\n"
f"Email: {valid_data['email']}\n"
f"Name: {valid_data['name']}\n"
f"Subject: {valid_data['subject']}\n"
f"{valid_data['message']}\n"
)
async_task(send_email_task, email, subject, message)
- Create a URLs configuration in
contact_form > urls.py
(you’ll need to create this file):
from django.urls import path
from .views import ContactFormView
app_name = "contact_form"
urlpatterns = [
path("", ContactFormView.as_view(), name="contact_form"),
]
- Update the project URLs to reference these new URLs in
djangoq_project > urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path("", include("contact_form.urls")),
]
- Create and apply the database migrations for the new model
python manage.py makemigrations
python manage.py migrate
- Now you need to launch the Django test server:
python manage.py runserver
- Then in a second terminal window, navigate to your project directory, activate the virtual environment again, and then launch the Django-q qcluster process – it should print out some debug information and then a
running
message to indicate it has connected to Redis successfully and is waiting for tasks:
python manage.py qcluster
-
Browse to http://127.0.0.1 and you should see a contact form. Try sending a message to see if it works.
-
Switch back to your terminal windows with the Django-Q qcluster process and you’ll see some update. It can take several seconds to send the email, but that was all done by the qcluster process and didn’t affect the page loading time after submitting the form.
-
After sending a message, log in to the admin site (http://127.0.0.1/admin) and take a look at the contact form model we created.
Hopefully that worked for you and gives you something to build upon.