Categories
Posts

Basic Django-Q Example

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.

Leave a Reply

Your email address will not be published. Required fields are marked *