<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kostja Appliku.com</title>
    <description>The latest articles on DEV Community by Kostja Appliku.com (@kostjapalovic).</description>
    <link>https://dev.to/kostjapalovic</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3188%2Fb8c27dc4-bc5b-45bc-b5e4-20e4e32e1678.png</url>
      <title>DEV Community: Kostja Appliku.com</title>
      <link>https://dev.to/kostjapalovic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kostjapalovic"/>
    <language>en</language>
    <item>
      <title>Django + Celery In-depth tutorial for beginners</title>
      <dc:creator>Kostja Appliku.com</dc:creator>
      <pubDate>Mon, 14 Oct 2024 12:42:11 +0000</pubDate>
      <link>https://dev.to/kostjapalovic/django-celery-in-depth-tutorial-for-beginners-28gm</link>
      <guid>https://dev.to/kostjapalovic/django-celery-in-depth-tutorial-for-beginners-28gm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article was originally posted on &lt;a href="https://appliku.com/post/django-celery-tutorial-to-background-tasks/" rel="noopener noreferrer"&gt;Appliku's Blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Welcome to Django and Celery tutorial.&lt;/p&gt;

&lt;p&gt;You will learn what is Celery, why and when to use it and how to set up a Django project with Celery and see a few examples of different Celery tasks.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/RY74ug36KUc"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Git repository
&lt;/h3&gt;

&lt;p&gt;The code from this tutorial is available on GitHub:  &lt;a href="https://github.com/appliku/celerytutorial" rel="noopener noreferrer"&gt;Celery Tutorial on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Celery?
&lt;/h2&gt;

&lt;p&gt;Celery is a distributed task queue system for Python.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a task queue
&lt;/h3&gt;

&lt;p&gt;A task queue is like a to-do list for your app. It stores tasks that need to be executed, along their  associated function calls and arguments. Tasks are processed in a specific order, first-in-first-out.&lt;/p&gt;

&lt;h3&gt;
  
  
  What distributed means?
&lt;/h3&gt;

&lt;p&gt;Multiple workers: Celery can distribute tasks across multiple worker processes. These workers can run on different  servers, allowing for parallel processing of tasks.&lt;br&gt;
Scalability:  You can scale task processing horizontally by adding more workers on more servers to handle increased workloads without modifying your application.&lt;br&gt;
Fault tolerance: If one worker fails or goes offline, tasks can be redistributed to other available workers.&lt;br&gt;
Load balancing: Celery can distribute tasks evenly across available workers, preventing any single worker from becoming overloaded.&lt;br&gt;
Geographical distribution: Workers can be located in different physical locations or data centers, which can be helpful if workload needs to be performed in different physical locations.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why and When to Use Celery
&lt;/h2&gt;

&lt;p&gt;While working with Django, most of our code focuses on responding to HTTP requests from browsers and other HTTP clients.&lt;/p&gt;

&lt;p&gt;It is important to respond to requests quickly. If a website takes longer than 500ms to respond it can start to annoy users. A few seconds to generate a response is only acceptable in rare situations, such as submitting an e-commerce order.&lt;/p&gt;

&lt;p&gt;By default, Gunicorn HTTP server drops the connection if a response takes more than 30 seconds. &lt;/p&gt;

&lt;p&gt;While your app might not be complex, there are types of tasks that take long time to complete.  &lt;/p&gt;

&lt;p&gt;Applications may need to perform complex and time-consuming operations or interact with other services.&lt;/p&gt;

&lt;p&gt;For example, sending an email can take anywhere from 50ms to several seconds.&lt;/p&gt;

&lt;p&gt;The good news is that some operations can be performed outside the request/response cycle. &lt;/p&gt;

&lt;p&gt;Back to the sending an email example, if a user signs up, we can schedule sending an email and proceed with generating and sending the response to the client without the delays for actually sending that email.&lt;/p&gt;

&lt;p&gt;One way to achieve this is by using asynchronous features, but that alone doesn't guarantee successful completion of the operation, nor does it provide robust control over retries in case of a failure. Additionally, it would be tied to a specific worker process, if it is terminated, then all in async tasks are lost with it.&lt;/p&gt;

&lt;p&gt;This is where task queues, and specifically Celery, come into play. When you need to perform a potentially time-consuming operation, you send a message to a task queue and move on. This message will be later picked up and executed by a Celery worker process. Common choices for the task queue database are Redis or RabbitMQ.&lt;/p&gt;

&lt;p&gt;To perform an operation via a task queue, you need to define a Task, which is essentially a function wrapped in a task Python decorator. You will then call it in a special way, which will be explained in this tutorial.&lt;/p&gt;

&lt;p&gt;Another use case for Celery is scheduled jobs. If you want your app to perform certain operations at specific intervals, such as every 10 minutes, every hour, midnight, or Saturday mornings, you will need a scheduler. A scheduler is a process that waits for the right moment to send a Celery task to the queue. That's its sole job.&lt;/p&gt;

&lt;p&gt;A scheduler does not guarantee the precise timing of execution; it only sends messages to the queue at the correct time. When the execution actually happens depends on your queue workers' setup and the number of tasks in the queue. In some cases, it may not happen at all if no worker is available to process the messages.&lt;/p&gt;

&lt;p&gt;You can have multiple queues to separate tasks and prevent clogging. For instance, you might have a separate queue for short-lived but important and time-sensitive tasks like sending emails and notifications. This leaves other potentially long-running tasks in a queue where you are less concerned about their timely completion.&lt;/p&gt;

&lt;p&gt;An advanced use of Celery involves having multiple projects communicate asynchronously via different Celery queues. One application can send tasks that only another application reads from, and vice versa. Unless you need an immediate response from the other application (in which case HTTP calls would be necessary), this method of communication is convenient. The other application, often called a service or microservice, does not need to be running all the time or scaled to handle the workload immediately; it will process tasks from the queue as quickly as it can.&lt;/p&gt;
&lt;h2&gt;
  
  
  Warning
&lt;/h2&gt;

&lt;p&gt;While Celery specifically and task queues in general are great for offloading work from the request/response cycle, they are not a silver bullet for poorly performing code. You cannot merely offload tasks to the queue and expect performance problems to disappear. If you send enough jobs to the task queue, it will eventually take a while for workers to process them. You can scale up the number of workers, but then the database might become a bottleneck, slowing down web requests because background tasks overloaded the database.&lt;/p&gt;

&lt;p&gt;I hope this introduction has been useful and provides enough context about what Celery is good for, when to use it, and what it actually does.&lt;/p&gt;
&lt;h2&gt;
  
  
  Starting a Django project with Celery using Docker
&lt;/h2&gt;

&lt;p&gt;Create a directory for your project, switch to it and initialize a git repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;celerytutorial
&lt;span class="nb"&gt;cd &lt;/span&gt;celerytutorial
git init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will be using Docker for the development of our project. You will not be needing a Python installation outside of Docker.&lt;/p&gt;

&lt;p&gt;Open your favorite code editor and create these files:&lt;/p&gt;

&lt;h3&gt;
  
  
  Dockerfile
&lt;/h3&gt;

&lt;p&gt;The essential part of building a Docker image&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12.5-bullseye&lt;/span&gt;
&lt;span class="k"&gt;SHELL&lt;/span&gt;&lt;span class="s"&gt; ["/bin/bash", "--login", "-c"]&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; USER_ID=1000&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; GROUP_ID=1000&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;groupadd &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nv"&gt;$USER_ID&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; app
&lt;span class="k"&gt;RUN &lt;/span&gt;useradd &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nv"&gt;$USER_ID&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nv"&gt;$GROUP_ID&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /bin/bash app
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PIP_NO_CACHE_DIR off&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PIP_DISABLE_PIP_VERSION_CHECK on&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PYTHONUNBUFFERED 1&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PYTHONDONTWRITEBYTECODE 1&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; COLUMNS 80&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="se"&gt;\
&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--force-yes&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt; curl nano python3-pip gettext chrpath libssl-dev libxft-dev &lt;span class="se"&gt;\
&lt;/span&gt; libfreetype6 libfreetype6-dev  libfontconfig1 libfontconfig1-dev &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /code/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt /code/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;wheel
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /code/&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; app&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  requirements.txt
&lt;/h3&gt;

&lt;p&gt;Needed to specify and manage Python package dependencies for our project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Django==5.1.1
django-environ==0.11.2
gunicorn==22.0.0
psycopg[binary]==3.2.1
whitenoise==6.7.0
Pillow==10.4.0
celery-redbeat==2.2.0
celery[redis]==5.4.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker-compose.yml
&lt;/h3&gt;

&lt;p&gt;Needed to define and manage all the services for our project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;x-app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;app&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
  &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
  &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/code&lt;/span&gt;
  &lt;span class="na"&gt;links&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
  &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6379:6379"&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=celerytutorial&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=celerytutorial&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=celerytutorial&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5432:5432"&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*app&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python manage.py runserver 0.0.0.0:9000&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:9000:9000"&lt;/span&gt;
  &lt;span class="na"&gt;celery&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*app&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;celery -A project.celeryapp:app  worker -Q default -n celerytutorial.%%h --loglevel=INFO --max-memory-per-child=512000 --concurrency=1&lt;/span&gt;
  &lt;span class="na"&gt;beat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*app&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;celery -A project.celeryapp:app beat -S redbeat.RedBeatScheduler  --loglevel=DEBUG --pidfile /tmp/celerybeat.pid&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  .env
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;.env&lt;/code&gt; and &lt;code&gt;.start.env&lt;/code&gt; files with identical content.  &lt;code&gt;.start.env&lt;/code&gt; will be added to version control, but not &lt;code&gt;.env&lt;/code&gt;. &lt;code&gt;.start.env&lt;/code&gt; serves as a template for developers to copy and modify locally, ensuring necessary variables are documented without exposing sensitive data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgresql://celerytutorial:celerytutorial@db:5432/celerytutorial
&lt;span class="nv"&gt;REDIS_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;redis://redis:6379/0
&lt;span class="nv"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  .gitignore
&lt;/h3&gt;

&lt;p&gt;Create this file in the root of the project.&lt;br&gt;
This file lists all files and paths within a project that shouldn't be included into version control.&lt;br&gt;
Here is the content of the file you should include for a start, feel free to add paths that you see fit(e.g. temporary files created by your code editor or any other tool)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.vscode/*
.idea/*
node_modules/*
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
[Dd]esktop.ini
$RECYCLE.BIN/
.DS_Store
.AppleDouble
.LSOverride
__pycache__/
*.py[cod]
*$py.class
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
db.sqlite3
db.sqlite3-journal
*.log
*.mo
*.pot
celerybeat-schedule
celerybeat.pid
env/
.env

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  .dockerignore
&lt;/h3&gt;

&lt;p&gt;Similar to &lt;code&gt;.gitignore&lt;/code&gt;, &lt;code&gt;.dockerignore&lt;/code&gt; is a list of files that shouldn't be included into Docker builds.&lt;/p&gt;

&lt;p&gt;It is not super important here, but it can help reduce the size of the build image. &lt;/p&gt;

&lt;p&gt;In this tutorial we are not using python virtual environments on the host, but let's include it here anyway.&lt;/p&gt;

&lt;p&gt;Including the &lt;code&gt;.git&lt;/code&gt;  folder here also helps reduce the size of the image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env/
.git/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pull images with Docker Compose
&lt;/h3&gt;

&lt;p&gt;Go to the terminal and run this command to pull all images for DBs. &lt;/p&gt;

&lt;p&gt;Running this command will make Docker download images for services with defined &lt;code&gt;image&lt;/code&gt; and not require the build.&lt;/p&gt;

&lt;p&gt;This step is optional because they will be pulled anyway on the start of containers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose pull
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output of successful execution of this command should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftmsrjmz1dyq06s3msn2r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftmsrjmz1dyq06s3msn2r.png" alt="docker compose pull" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Build Docker image for Django Project
&lt;/h3&gt;

&lt;p&gt;This is an optional step for the first go, because the image will be built on containers start.&lt;br&gt;
But if you change requirements later on, you will need to run this command again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output of successful execution of this command should look similar to this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwsldljpmuq4a4068vdh4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwsldljpmuq4a4068vdh4.png" alt="docker compose build" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Start Django Project
&lt;/h3&gt;

&lt;p&gt;Now let's start a shell within our &lt;code&gt;web&lt;/code&gt; container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose run web bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you will see a bash prompt from within a container.&lt;br&gt;
To leave this shell type exit or press CTRL-D.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmei10f23jj1n4dpxdam5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmei10f23jj1n4dpxdam5.png" alt="bash prompt from docker compose run web bash" width="771" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Start our Django project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;django-admin startproject project &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Initial Django configuration
&lt;/h3&gt;

&lt;p&gt;Open your code editor and let's edit some files.&lt;/p&gt;

&lt;p&gt;Edit the &lt;code&gt;project/settings.py&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;# set casting, default value
&lt;/span&gt;    &lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;
&lt;span class="c1"&gt;# Take environment variables from .env file
&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.env&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;SECRET_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;change_me&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;DEBUG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEBUG&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ALLOWED_HOSTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALLOWED_HOSTS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="c1"&gt;# Application definition
&lt;/span&gt;
&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.admin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.contenttypes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.sessions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.staticfiles&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;MIDDLEWARE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.middleware.security.SecurityMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;whitenoise.middleware.WhiteNoiseMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.sessions.middleware.SessionMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.middleware.common.CommonMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.middleware.csrf.CsrfViewMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth.middleware.AuthenticationMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.messages.middleware.MessageMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.middleware.clickjacking.XFrameOptionsMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;ROOT_URLCONF&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;project.urls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="n"&gt;TEMPLATES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BACKEND&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.template.backends.django.DjangoTemplates&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DIRS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;APP_DIRS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OPTIONS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;context_processors&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.template.context_processors.debug&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.template.context_processors.request&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth.context_processors.auth&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.messages.context_processors.messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;WSGI_APPLICATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;project.wsgi.application&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;


&lt;span class="c1"&gt;# Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
&lt;/span&gt;
&lt;span class="n"&gt;DATABASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sqlite:///db.sqlite3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# Password validation
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
&lt;/span&gt;
&lt;span class="n"&gt;AUTH_PASSWORD_VALIDATORS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NAME&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth.password_validation.UserAttributeSimilarityValidator&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NAME&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth.password_validation.MinimumLengthValidator&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NAME&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth.password_validation.CommonPasswordValidator&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NAME&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth.password_validation.NumericPasswordValidator&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;SECURE_PROXY_SSL_HEADER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTP_X_FORWARDED_PROTO&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;LOGGING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disable_existing_loggers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;handlers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;console&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;class&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;logging.StreamHandler&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;loggers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;handlers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;console&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEBUG&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Internationalization
# https://docs.djangoproject.com/en/5.0/topics/i18n/
&lt;/span&gt;
&lt;span class="n"&gt;LANGUAGE_CODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;en-us&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="n"&gt;TIME_ZONE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;UTC&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="n"&gt;USE_I18N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="n"&gt;USE_TZ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;


&lt;span class="c1"&gt;# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/
&lt;/span&gt;
&lt;span class="n"&gt;STATIC_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STATIC_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/static/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;STATIC_ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STATIC_ROOT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;staticfiles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;WHITENOISE_USE_FINDERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;WHITENOISE_AUTOREFRESH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DEBUG&lt;/span&gt;


&lt;span class="c1"&gt;# Default primary key field type
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
&lt;/span&gt;
&lt;span class="n"&gt;DEFAULT_AUTO_FIELD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.db.models.BigAutoField&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;


&lt;span class="n"&gt;MEDIA_ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MEDIA_ROOT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;media&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;MEDIA_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MEDIA_PATH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/media/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;REDIS_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REDIS_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a file &lt;code&gt;project/celeryapp.py&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Celery&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;kombu&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Queue&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.settings&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;REDIS_URL&lt;/span&gt;

&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DJANGO_SETTINGS_MODULE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project.settings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Celery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;autodiscover_tasks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;broker_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;REDIS_URL&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result_backend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;REDIS_URL&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accept_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task_serializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result_serializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task_default_queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task_create_missing_queues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task_queues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;broker_pool_limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;broker_connection_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;worker_prefetch_multiplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redbeat_redis_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;REDIS_URL&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file configures Celery for your Django project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Imports necessary modules&lt;/li&gt;
&lt;li&gt;Loads environment variables&lt;/li&gt;
&lt;li&gt;Initializes Celery app&lt;/li&gt;
&lt;li&gt;Configures Celery settings (serialization, queues, timeouts, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Edit the file &lt;code&gt;project/__init__.py&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;This will make import Celery&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.celeryapp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;celery_app&lt;/span&gt;

&lt;span class="n"&gt;__all__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;celery_app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to save all files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apply migrations
&lt;/h3&gt;

&lt;p&gt;Let's apply Django migrations and make sure our project and database connection works correctly.&lt;/p&gt;

&lt;p&gt;In the Docker bash shell run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By the way if you closed the shell or you want to run one command in Docker without launching shell you can do it another way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose run web python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Start containers
&lt;/h3&gt;

&lt;p&gt;This command will start all services defined in &lt;code&gt;docker-compose.yml&lt;/code&gt;. Run this command outside docker compose shell.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output of successful execution of this command should look this way:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feeugjysnuztxe18l9hu9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feeugjysnuztxe18l9hu9.png" alt="docker compose up" width="695" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Check logs of Django Project with docker compose logs
&lt;/h4&gt;

&lt;p&gt;Now to make sure our services are actually working fine let's see logs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see logs for all services running. To stop following logs press CTRL-C.&lt;/p&gt;

&lt;p&gt;To show logs for specific app you can specify process(es) to follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; web celery
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the output for &lt;code&gt;docker compose logs -f web&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6dic96hntipqahbbfeww.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6dic96hntipqahbbfeww.png" alt="docker compose logs -f web" width="633" height="139"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Important notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you make changes to the &lt;code&gt;.env&lt;/code&gt; file, you'll need to restart Docker Compose for the changes to take effect. Do this by pressing CTRL-C to stop the current process(or &lt;code&gt;docker compose stop&lt;/code&gt;), then run &lt;code&gt;docker compose up&lt;/code&gt; again.&lt;/li&gt;
&lt;li&gt;When modifying the &lt;code&gt;docker-compose.yml&lt;/code&gt; file, you may need to perform a complete reset. Use &lt;code&gt;docker compose stop&lt;/code&gt; to stop and remove all containers, networks created by Docker Compose. Keep in mind &lt;code&gt;docker compose down&lt;/code&gt; will also remove all the DB data in the current setup. Then, restart the project with &lt;code&gt;docker compose up -d&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;From here it is implied that python commands are executed within a Docker Compose shell.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can now see it in browser. &lt;/p&gt;

&lt;p&gt;Please note, that if you are on Windows + WSL, &lt;code&gt;http://0.0.0.0&lt;/code&gt; address will not work, you should use &lt;code&gt;http://localhost:9000&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fds743mpi7y60lglwdurk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fds743mpi7y60lglwdurk.png" alt="image" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Start an application
&lt;/h2&gt;

&lt;p&gt;In order to showcase Celery tasks, need an app where they will live, a model and some views.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python manage.py startapp mainapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create mainapp/tasks.py and leave it empty for now. That's where our Celery tasks will be defined.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;Report&lt;/code&gt; model in &lt;code&gt;mainapp/models.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;namedtuple&lt;/span&gt;

&lt;span class="n"&gt;REPORT_STATUSES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;namedtuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REPORT_STATUSES&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pending running finished error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;_make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;REPORT_STATUS_CHOICES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REPORT_STATUSES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pending&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REPORT_STATUSES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REPORT_STATUSES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Finished&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REPORT_STATUSES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;dt_created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now_add&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;dt_started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;dt_finished&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;complexity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PositiveIntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;REPORT_STATUS_CHOICES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;REPORT_STATUSES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pending&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ordering&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-dt_created&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name_plural&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Reports&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create a page which we will use to trigger our tasks.&lt;/p&gt;

&lt;p&gt;Create a view in &lt;code&gt;mainapp/views.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mainapp/index.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a template in &lt;code&gt;mainapp/templates/mainapp/index.html&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Tasks&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Tasks&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And add this view into URLConf in &lt;code&gt;project/urls.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mainapp.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;
&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;admin/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add our application to &lt;code&gt;INSTALLED_APPS&lt;/code&gt; in &lt;code&gt;project/settings.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "mainapp", # new
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create and apply migrations in Docker:&lt;/p&gt;

&lt;p&gt;If you are already in Docker shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python manage.py makemigrations
python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and open the app in browser: &lt;a href="https://localhost:9000/" rel="noopener noreferrer"&gt;https://localhost:9000/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp5oht036oknvleunam8b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp5oht036oknvleunam8b.png" alt="image" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make the initial commit&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="s1"&gt;'Initial Commit'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to &lt;a href="https://github.com/new" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; create a new repository.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41htfa83i5f2btksi949.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41htfa83i5f2btksi949.png" alt="image" width="800" height="814"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you create a repo you will be offered with a list of commands, copy the one that starts with &lt;code&gt;git remote add origin&lt;/code&gt; and paste it in your terminal, outside of Docker container shell and run it.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9k9m3nr8s2tskn47jy7h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9k9m3nr8s2tskn47jy7h.png" alt="image" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;git push -u origin master&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Refresh the GitHub repository page to see that your code is there. You can also check that none of the unwanted files got added to version control.&lt;/p&gt;
&lt;h2&gt;
  
  
  Deploying Django Celery app
&lt;/h2&gt;

&lt;p&gt;Now this step you might consider optional, but I insist it is very important.&lt;br&gt;
You should deploy your app sooner rather than later.&lt;/p&gt;

&lt;p&gt;By deploying app as early as possible, you will avoid  the situation when you finished the whole project, it works on your machine, but doesn't work when deployed.&lt;/p&gt;

&lt;p&gt;Deploying your app after you have finished the whole project means the task of debugging would be more time consuming and you might end up having to rewrite larger chunks of code.&lt;/p&gt;

&lt;p&gt;By deploying the project early and often, you make it easier to find the cause of the problem in small most recent code changes.&lt;/p&gt;

&lt;p&gt;Go to &lt;a href="https://app.appliku.com" rel="noopener noreferrer"&gt;Appliku Dashboard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add an Ubuntu or a Debian server from a cloud provider of your choice, if you haven't already.&lt;/p&gt;

&lt;p&gt;My personal favorite is &lt;a href="https://hetzner.cloud/?ref=nBmfdEZteab9" rel="noopener noreferrer"&gt;Hetzner&lt;/a&gt;, especially their ARM64 Ampere VPS servers. Their performance is great and pricing is unbeatable.&lt;/p&gt;

&lt;p&gt;Create an application from your GitHub repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Forkyg47n08diic1pevt4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Forkyg47n08diic1pevt4.png" alt="image" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From your application dashboard page go to databases and add a PostgreSQL database and a Redis one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuu7n3mpossxud34h5a8g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuu7n3mpossxud34h5a8g.png" alt="image" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgkt8166df27jhutusp1f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgkt8166df27jhutusp1f.png" alt="image" width="800" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1v47883ttwocaw936fq3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1v47883ttwocaw936fq3.png" alt="image" width="800" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next step add processes to run your app. From Application overview page click Add Processes button. Or go to Settings -&amp;gt; Processes.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3t6pto4uyoi4e5i04g9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3t6pto4uyoi4e5i04g9.png" alt="image" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Add Process and 4 processes:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr60rn5ln3q1j7e3wktib.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr60rn5ln3q1j7e3wktib.png" alt="image" width="800" height="408"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;web: gunicorn project.wsgi --log-file -
release: python manage.py migrate
worker:celery -A project.celeryapp:app  worker -Q default -n celerytutorial.%%h --loglevel=INFO --max-memory-per-child=512000 --concurrency=1
beat: celery -A project.celeryapp:app beat -S redbeat.RedBeatScheduler  --loglevel=DEBUG --pidfile /tmp/celerybeat.pid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep in mind that the &lt;code&gt;web&lt;/code&gt; and &lt;code&gt;release&lt;/code&gt; processes' names are special. The process called &lt;code&gt;web&lt;/code&gt; will receive HTTP traffic from the Nginx and the &lt;code&gt;release&lt;/code&gt; one is a command that is executed after each successful deployment.&lt;/p&gt;

&lt;p&gt;The rest of the processes can have any names as long as they consist of letters and numbers, no special characters.&lt;/p&gt;

&lt;p&gt;Click on Save and Deploy and the first deployment will start.&lt;/p&gt;

&lt;p&gt;After deployment has finished, click on Open App -&amp;gt; Domain name.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5gjvqpuaadr2vsnzwsz4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5gjvqpuaadr2vsnzwsz4.png" alt="image" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our app has been successfully deployed!&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Favbecbm4syfw691qiu7j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Favbecbm4syfw691qiu7j.png" alt="image" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Appliku gives the app a subdomain with your app's name and SSL certificate via Let's Encrypt. You can also add your own custom domain(s) in application settings.&lt;/p&gt;

&lt;p&gt;That was easy, right?&lt;/p&gt;

&lt;p&gt;Now let's get back to the code. Time to add the first Celery task.&lt;/p&gt;
&lt;h2&gt;
  
  
  Simple Celery task
&lt;/h2&gt;

&lt;p&gt;Go to &lt;code&gt;mainapp/tasks.py&lt;/code&gt; and make the first task.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shared_task&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@shared_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dummy_and_slow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dummy_and_slow&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dummy and slow task has finished&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This task doesn't do anything useful, it waits for 2 seconds, which will be enough to illustrate the potential problem of a long running code.&lt;/p&gt;

&lt;p&gt;Let's call it as a regular function from a view.&lt;/p&gt;

&lt;p&gt;Go to &lt;code&gt;mainapp/views.py&lt;/code&gt; and add another view.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.tasks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dummy_and_slow&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mainapp/index.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dummy_and_slow_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;    &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;dummy_and_slow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;end_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;execution_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;end_time&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dummy and slow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;execution_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;execution_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mainapp/generic.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a template &lt;code&gt;templates/mainapp/generic.html&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Task results&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Task {{task_name}} has finished&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Execution time: {{execution_time}}s&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and add the view to &lt;code&gt;project/urls.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mainapp.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dummy_and_slow_view&lt;/span&gt; &lt;span class="c1"&gt;# added a view
&lt;/span&gt;&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dummy_and_slow_view&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dummy_and_slow_view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dummy_and_slow_view&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# new
&lt;/span&gt;    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;admin/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And update &lt;code&gt;mainapp/templates/mainapp/index.html&lt;/code&gt; to include a link to our view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Tasks&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Tasks&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{% url "&lt;/span&gt;&lt;span class="na"&gt;dummy_and_slow_view&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="err"&gt;%}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Dummy and slow&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to our app in the browser &lt;a href="http://localhost:9000/" rel="noopener noreferrer"&gt;http://localhost:9000/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyvvmq00lherj60svpfnt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyvvmq00lherj60svpfnt.png" alt="image" width="577" height="259"&gt;&lt;/a&gt;&lt;br&gt;
click on the new link, let's see the result:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6dnnqo8190d7kkx9hf1p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6dnnqo8190d7kkx9hf1p.png" alt="image" width="791" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It took more than 2 seconds for the page to render.&lt;br&gt;
This is a very representative example of how long it might take to send a single email. Imagine, a person clicked a "Sign Up" button and waiting, watching the browser loading indicator to spin. That's suboptimal experience, let's make it better. Let's dispatch a Celery task.&lt;/p&gt;

&lt;p&gt;The simplest way to do that is to go to our view and replace a direct function call &lt;code&gt;dummy_and_slow()&lt;/code&gt; with calling &lt;code&gt;dummy_and_slow.delay()&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Now our view should look like this in &lt;code&gt;mainapp/views.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dummy_and_slow_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;dummy_and_slow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;-- this changed
&lt;/span&gt;    &lt;span class="n"&gt;end_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;execution_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;end_time&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dummy and slow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;execution_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;execution_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mainapp/generic.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to stop the docker compose.&lt;br&gt;
To do that run &lt;code&gt;docker compose down&lt;/code&gt;, then &lt;code&gt;docker compose up -d&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now when you open this URL &lt;a href="http://localhost:9000/dummy_and_slow_view" rel="noopener noreferrer"&gt;http://localhost:9000/dummy_and_slow_view&lt;/a&gt; you will see this:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F63izbpjoruqyrjeqfh9y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F63izbpjoruqyrjeqfh9y.png" alt="image" width="567" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Execution time: 0.08s&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You see that execution time of the view itself improved when the Celery task was sent, but and the long running operation is not blocking the request/re&lt;/p&gt;

&lt;p&gt;You will see that the task was executed from the docker compose logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;celery-1         | [2024-08-17 10:59:10,154: WARNING/ForkPoolWorker-1] Dummy and slow task has finished
celery-1         | [2024-08-17 10:59:10,156: INFO/ForkPoolWorker-1] Task dummy_and_slow[4c97e4ef-d73b-4623-8eec-b9fb5bea4753] succeeded in 2.002374355099164s: None
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Celery API in more details
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Celery Task definitions
&lt;/h3&gt;

&lt;p&gt;There are two ways to define a task.&lt;/p&gt;

&lt;p&gt;One way is to import the Celery app instance and use &lt;code&gt;@app.task&lt;/code&gt; decorator.&lt;br&gt;
Another way is using the &lt;code&gt;@shared_task&lt;/code&gt; decorator, which is not tied to a particular instance of Celery.&lt;/p&gt;

&lt;p&gt;The key difference lies in how these tasks are registered and how they behave when multiple Celery app instances are involved:&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;@app.task&lt;/code&gt; decorator:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Requires importing a Celery app instance&lt;/li&gt;
&lt;li&gt;Ties task to specific app instance
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;project.celeryapp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;

&lt;span class="nd"&gt;@app.task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sometask&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  @shared_task decorator(recommended):
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Not tied to specific Celery instance&lt;/li&gt;
&lt;li&gt;Automatically discovered by any Celery app
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shared_task&lt;/span&gt;

&lt;span class="nd"&gt;@shared_task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;another_task&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The "not tied to a particular Celery instance" aspect of @shared_task means that it's more flexible and can be easily shared across different parts of a large project without needing to pass around Celery app instances.&lt;/p&gt;

&lt;p&gt;For many simple Celery setups, this distinction may not be noticeable or important.&lt;/p&gt;

&lt;p&gt;If you are an author of a library, you have to use the &lt;code&gt;@shared_task&lt;/code&gt; decorator, because you can't have your code tied to a particular Celery instance.&lt;/p&gt;

&lt;p&gt;So, since at the end of the day &lt;code&gt;@shared_task&lt;/code&gt; doesn't have any downsides and usually there is only one Celery instance in a project I suggest to stick to the &lt;code&gt;@shared_task&lt;/code&gt; decorator.&lt;/p&gt;

&lt;p&gt;Like any python function your task can be with or without arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@shared_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fn1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fn1&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="nd"&gt;@shared_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fn2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fn2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dishes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pancake&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tasty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Three ways to call a Celery task
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Using &lt;code&gt;.delay()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;.apply_async()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;current_app.send_task()&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Sending a Celery task via .delay
&lt;/h4&gt;

&lt;p&gt;This is the most simple way of sending a Celery task.&lt;/p&gt;

&lt;p&gt;Instead of calling the function itself directly, you call its method &lt;code&gt;.delay&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Without arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;fn1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;fn2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;good_one&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;something_else&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Sending a Celery task via .apply_async
&lt;/h4&gt;

&lt;p&gt;This method allows you to pass additional parameters to the task itself as well as arguments to the task function.&lt;/p&gt;

&lt;p&gt;Without arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;fn1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_async&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With positional arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;fn2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nice_one&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With keyword arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;fn2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dishes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pancacke&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;best one&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tasty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to arguments of the function itself we can also pass arguments that would affect the task execution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;fn2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nice_one&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# passing arguments as positional
&lt;/span&gt;    &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dishes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pancacke&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;best one&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tasty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;# passing keyword arguments
&lt;/span&gt;    &lt;span class="n"&gt;expires&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# in seconds, after this many seconds task received a worker will be rejected,
&lt;/span&gt;    &lt;span class="n"&gt;countdown&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# in seconds, execute task after N seconds, not before
&lt;/span&gt;    &lt;span class="n"&gt;eta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# datetime, execute task after this date and time
&lt;/span&gt;    &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;priority&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# queue name where to send task
&lt;/span&gt;    &lt;span class="n"&gt;ignore_result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# boolean, wether or not to store the result of the task in result backend,
&lt;/span&gt;    &lt;span class="n"&gt;retry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# if task has failed, attempt retry or not
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those were probably the most important and typical parameters you might want to use while sending tasks.&lt;br&gt;
Please do read the official documentation as there are a lot of caveats and detailed explanations for each of those arguments listed here and some more: &lt;a href="https://docs.celeryq.dev/en/stable/userguide/calling.html#eta-and-countdown" rel="noopener noreferrer"&gt;Calling tasks&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Sending tasks via current_app.send_task
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;send_task&lt;/code&gt; is similar to &lt;code&gt;apply_async&lt;/code&gt;, but instead of importing a function, you use the name of the task as a string, the rest is similar to &lt;code&gt;apply_async&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;current_app&lt;/span&gt;

&lt;span class="n"&gt;current_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fn2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dishes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pancacke&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;best one&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tasty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priority&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;expires&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since you are not importing the function, but calling a task by name as a string you have to make sure you know the task name and it won't change when you rearrange the code later. &lt;/p&gt;

&lt;p&gt;For this reason I recommend specifying the task name in the &lt;code&gt;@shared_task&lt;/code&gt; decorator instead of relying on automatic task naming.&lt;/p&gt;

&lt;p&gt;This way of calling Celery tasks is also my favorite because it allows to solve the circular import problem when you call a task from a Django model method and the task also imports this model.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;current_app.send_task&lt;/code&gt; approach also allows to call tasks that are not defined in the code of the current project. &lt;/p&gt;

&lt;p&gt;That's what I mentioned in the intro, about having multiple projects interact with each other via the same celery instance.&lt;/p&gt;

&lt;p&gt;You can't import the function, so you have to use &lt;code&gt;send_task&lt;/code&gt; method and pass the name of the function as a string. That means you have to come up with some naming convention for tasks, have some prefixes for task names so they don't overlap between projects. For example, give them prefixes with the name of the service: &lt;code&gt;main_&lt;/code&gt; for the main part of the app, and a service that does video compression &lt;code&gt;video_&lt;/code&gt;, service that does emails sending &lt;code&gt;email_service_&lt;/code&gt; prefix. This will also help with automatic routing of tasks based on their names.&lt;/p&gt;

&lt;p&gt;I recommend having some prefix for the project from day one, because if you need to introduce another prefix later, you won't need to fix all the existing tasks name. Even if you won't need that in the future it is a good habit to have.&lt;/p&gt;

&lt;p&gt;One important thing, about &lt;code&gt;.send_task&lt;/code&gt;:&lt;br&gt;
There is a Celery setting, called &lt;code&gt;task_always_eager&lt;/code&gt; &lt;a href="https://docs.celeryq.dev/en/stable/userguide/configuration.html#std-setting-task_always_eager" rel="noopener noreferrer"&gt;Celery Configuration and Defaults: task_always_eager&lt;/a&gt;&lt;br&gt;
If this is True, all tasks will be executed locally by blocking until the task returns.&lt;/p&gt;

&lt;p&gt;This setting will not affect tasks sent with &lt;code&gt;.send_task&lt;/code&gt;, they will not be executed without a worker processing it.&lt;/p&gt;
&lt;h3&gt;
  
  
  How to correctly send a Celery task from a view
&lt;/h3&gt;

&lt;p&gt;Usually, when your view is running, all interactions with the database are happening in a transaction.&lt;/p&gt;

&lt;p&gt;This means that until the view finishes executing, the transaction is not committed and as such changes to the database won't be visible from the outside including your Celery task.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.djangoproject.com/en/5.0/topics/db/transactions/" rel="noopener noreferrer"&gt;Django Docs: Database transactions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you create or change a database object and immediately send a task with its ID, the task might start before the database update is complete.&lt;/p&gt;

&lt;p&gt;This can cause errors because the task won't see the recent changes.&lt;/p&gt;

&lt;p&gt;You might get an 'ObjectDoesNotExist' error if the task tries to access an object that hasn't been fully saved yet.&lt;/p&gt;

&lt;p&gt;Let's see some examples.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# views.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;current_app&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;welcome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;somearguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;current_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;send_welcome_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;template_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mainapp/welcome.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# tasks.py
&lt;/span&gt;&lt;span class="nd"&gt;@shared_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;send_welcome_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_welcome_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;send_an_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mainapp/email_templates/welcome.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_object&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user_object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code looks fine at first, but the line &lt;code&gt;user_object = User.objects.get(pk=user_id)&lt;/code&gt; will cause an  &lt;code&gt;ObjectDoesNotExist&lt;/code&gt; exception.&lt;/p&gt;

&lt;p&gt;It will happen because the transaction will be committed after the view finishes, but our task has already been sent.&lt;/p&gt;

&lt;p&gt;To fix  that we will use &lt;code&gt;transaction.on_commit&lt;/code&gt; to run a function that sends our task after transaction is committed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# views.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;current_app&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;partial&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;welcome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;somearguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;partial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;send_welcome_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;template_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mainapp/welcome.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Relevant Django Docs: &lt;a href="https://docs.djangoproject.com/en/5.0/topics/db/transactions/#performing-actions-after-commit" rel="noopener noreferrer"&gt;Performing actions after commit&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Workers setup
&lt;/h2&gt;

&lt;p&gt;Let's review worker's command line and the arguments.&lt;/p&gt;

&lt;p&gt;To run a worker the bare minimum is this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;celery -A project.celeryapp:app worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are important arguments you might want to use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-Q&lt;/code&gt;: queues to consume tasks from&lt;/li&gt;
&lt;li&gt; &lt;code&gt;--concurrency&lt;/code&gt;: determines how many tasks a worker can process simultaneously&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--loglevel&lt;/code&gt;: Determines the verbosity of the logging output. Possible values: &lt;code&gt;DEBUG&lt;/code&gt;, &lt;code&gt;INFO&lt;/code&gt;, &lt;code&gt;ERROR&lt;/code&gt;, &lt;code&gt;WARNING&lt;/code&gt;, &lt;code&gt;CRITICAL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-n&lt;/code&gt;, &lt;code&gt;--hostname&lt;/code&gt; used to set a custom hostname for the worker. Example: &lt;code&gt;-n celerytutorial.%%h&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--max-memory-per-child&lt;/code&gt; This option sets a memory limit for each worker child process. When a worker reaches this memory threshold, it will be replaced with a new process after completing its current task.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--max-tasks-per-child&lt;/code&gt; limits based on the number of tasks processed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example App: Generate reports
&lt;/h2&gt;

&lt;p&gt;Let's explore a common challenge in web applications: generating reports efficiently as your data grows.&lt;/p&gt;

&lt;p&gt;Imagine you've built a feature to generate reports in your Django application. Initially, it works well, especially in development with minimal data. However, as you deploy the application and accumulate more data over time, you start to notice some issues:&lt;/p&gt;

&lt;p&gt;Report generation time increases significantly.&lt;br&gt;
The process blocks the web server, leading to slow response times.&lt;br&gt;
Complex reports with multiple database queries and data manipulations become  problematic.&lt;br&gt;
What was once a simple view function now struggles to handle the increased load. This is where Celery, a distributed task queue, can help.&lt;/p&gt;

&lt;p&gt;By moving the report generation process to a Celery task, we can:&lt;/p&gt;

&lt;p&gt;Offload the heavy processing from the web server.&lt;br&gt;
Allow the report to generate asynchronously in the background.&lt;br&gt;
Improve the overall responsiveness of our application.&lt;br&gt;
In this example, we'll simulate a time-consuming report generation process using time.sleep(). This simplification allows us to focus on the implementation of Celery tasks without getting bogged down in complex report logic.&lt;/p&gt;

&lt;p&gt;Let's dive in and see how we can use Celery to handle our report generation more efficiently.&lt;/p&gt;

&lt;p&gt;Go to &lt;code&gt;mainapp/views.py&lt;/code&gt; and create 3 views: &lt;code&gt;ReportCreateView&lt;/code&gt;, &lt;code&gt;ReportDetailView&lt;/code&gt; and &lt;code&gt;ReportListView&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.views.generic.edit&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CreateView&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.views.generic.detail&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DetailView&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.views.generic.list&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ListView&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;  &lt;span class="c1"&gt;# Add this line
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Report&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReportCreateView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Report&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;complexity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mainapp/report_form.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_success_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report_detail&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReportDetailView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DetailView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Report&lt;/span&gt;
    &lt;span class="n"&gt;context_object_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mainapp/report_detail.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReportListView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ListView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Report&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mainapp/report_list.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;context_object_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reports&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;ordering&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-dt_created&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;paginate_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and our &lt;code&gt;project/urls.py&lt;/code&gt; now looks like this when we import our views and add report related URLs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.contrib import admin
from django.urls import path
from mainapp.views import (
    index,
    dummy_and_slow_view,
    ReportCreateView,
    ReportDetailView,
    ReportListView,
)

urlpatterns = [
    path("", index),
    path("dummy_and_slow_view", dummy_and_slow_view, name="dummy_and_slow_view"),
    path("report", ReportListView.as_view(), name="report_list"),
    path("report/create", ReportCreateView.as_view(), name="report_create"),
    path("report/detail/&amp;lt;int:pk&amp;gt;", ReportDetailView.as_view(), name="report_detail"),
]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make 5 new templates. If you are not familiar with Django templates, I urge you to go read the official documentation: &lt;a href="https://docs.djangoproject.com/en/5.1/topics/templates/" rel="noopener noreferrer"&gt;Django Docs: Templates&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mainapp/templates/mainapp/report_base.html&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;{% block title %}{% endblock %}&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    {% block content %}{% endblock %}
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mainapp/templates/mainapp/report_form.html&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;{% extends "mainapp/report_base.html" %}

{% block content %}
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Create a report&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        {% csrf_token %}
        {{ form.as_p }}
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mainapp/templates/mainapp/report_list.html&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;{% extends "mainapp/report_base.html" %}

{% block content %}
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Report List&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{% url "&lt;/span&gt;&lt;span class="na"&gt;report_create&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="err"&gt;%}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Create a report&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    {% if reports.exists %}
    &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
        {% for report in reports %}
            &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{% url "&lt;/span&gt;&lt;span class="na"&gt;mainapp:report_detail&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="na"&gt;report.id&lt;/span&gt; &lt;span class="err"&gt;%}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ report.title }}&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
        {% endfor %}
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
    {% else %}
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;No reports available&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    {% endif %}
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mainapp/templates/mainapp/report_detail.html&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;{% extends "mainapp/report_base.html" %}

{% block content %}
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Report of complexity: {{report.complexity}}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Created at: {{ report.dt_created }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    {% if report.dt_started %}
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Started at: {{ report.dt_started }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    {% endif %}
    {% if report.status == 2 %}
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Report is ready&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Finished at: {{ report.finished_at }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    {% elif report.status == 0 %}
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Report is waiting in queue&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    {% elif report.status == 1 %}
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Report is generating&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    {% else %}
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Task has failed to generate&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    {% endif %}
    {% if report.status == 0 or report.status == 1 %}
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Refresh page in 5 seconds.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
            &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
                &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    {% endif %}
    {% if report.result %}&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{{ report.result|safe }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;{% endif %}
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now while the rest of templates are really simple, the last one needs some explaining:&lt;/p&gt;

&lt;p&gt;The report is available in the &lt;code&gt;report&lt;/code&gt;  variable.&lt;/p&gt;

&lt;p&gt;Statuses are defined in the &lt;code&gt;nametuple&lt;/code&gt; &lt;code&gt;REPORT_STATUSES&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;They have indexes: pending is 0,  running is 1 finished is 2 error is 3.&lt;/p&gt;

&lt;p&gt;Maybe not the most convenient thing to work with in templates, but I wanted to show how you can use &lt;code&gt;namedtuple&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the report is ready - the result and finished time will be shown.&lt;/p&gt;

&lt;p&gt;If the report has failed for whatever reason, it will state the error.&lt;/p&gt;

&lt;p&gt;If the report is not yet ready it will be stated, but also a page will refresh for statuses 0 and 1 which is pending and running.&lt;/p&gt;

&lt;p&gt;Pending status will be the one when the report is just created.&lt;/p&gt;

&lt;p&gt;Running when a worker picked up the task and started working on it&lt;/p&gt;

&lt;p&gt;Error is when the task has gracefully failed (meaning, reported the failure and saved that to DB).&lt;/p&gt;

&lt;p&gt;Finished, after the task has finished and written the report into the DB.&lt;/p&gt;

&lt;p&gt;Lastly, the template for report contents which we will use to generate to string and store in the report model field &lt;code&gt;result&lt;/code&gt;:&lt;br&gt;
&lt;code&gt;mainapp/templates/mainapp/report_content.html&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"border:1px solid orange"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;The sales report: {{report.complexity}}.&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Requested at: {{ report.dt_created }}.&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;It is a very insightful report for sales and marketing team.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modify our model, we need to add a &lt;code&gt;save&lt;/code&gt; method, a &lt;code&gt;generate&lt;/code&gt; method and the &lt;code&gt;generate_data&lt;/code&gt; method. The whole file &lt;code&gt;mainapp/models.py&lt;/code&gt; will look this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;namedtuple&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.template.loader&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render_to_string&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;partial&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;current_app&lt;/span&gt;

&lt;span class="n"&gt;REPORT_STATUSES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;namedtuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REPORT_STATUSES&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pending running finished error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;_make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;REPORT_STATUS_CHOICES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REPORT_STATUSES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pending&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REPORT_STATUSES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REPORT_STATUSES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Finished&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REPORT_STATUSES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;dt_created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now_add&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;dt_started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;dt_finished&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;complexity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PositiveIntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;REPORT_STATUS_CHOICES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;REPORT_STATUSES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pending&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ordering&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-dt_created&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name_plural&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Reports&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;This method is overridden to start the report generation task when the report is saved.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;REPORT_STATUSES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dt_started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dt_finished&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;REPORT_STATUSES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nf"&gt;partial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;current_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mainapp_generate_report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;This is where the actual generation of the report data would happen.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;complexity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;render_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mainapp/report_content.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;This method is called by the Celery task or Django admin action.
        Contains some boilerplate code to reflect of the progress and datetimes.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;REPORT_STATUSES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dt_started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dt_started&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;REPORT_STATUSES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;finished&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dt_finished&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dt_finished&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a Celery task to &lt;code&gt;mainapp/tasks.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Report&lt;/span&gt;

&lt;span class="nd"&gt;@shared_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mainapp_generate_report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;report_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down what's happening:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;save&lt;/code&gt; method:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- This is an overridden method of a Django model.
- When a report is saved with a 'pending' status, it resets the start and finish times.
- After saving, if the status is still 'pending', it schedules a Celery task.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;transaction.on_commit&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- This ensures the task is only sent after the database transaction is committed.
- It prevents potential issues where the task tries to access a record that hasn't been committed yet.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;current_app.send_task&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- This is a Celery method to send a task to the queue.
- The task name is "mainapp_generate_report".
- It passes the report's ID as an argument.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;generate_data&lt;/code&gt; method:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- This method simulates the actual report generation.
- It uses `time.sleep` to mimic a time-consuming process.
- The report content is rendered using a template.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;generate&lt;/code&gt; method:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- This is the main method of the Report model called by the Celery task.
- It updates the report status to 'running' and sets the start time.
- Calls `generate_data` to create the report content.
- Updates the status to 'finished' and sets the finish time.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Celery task:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Has a name defined in the decorator `@shared_task(name="mainapp_generate_report")`
- It retrieves the report object and calls its `generate` method.
- The `generate_report` function is the task that Celery will execute.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;When a report is saved with a 'pending' status, it triggers an asynchronous Celery task. This task runs the report generation process in the background, updating the report's status and timestamps along the way.&lt;/p&gt;

&lt;p&gt;Let's update our main template to include the link to reports and then see how it works.&lt;/p&gt;

&lt;p&gt;Edit &lt;code&gt;mainapp/templates/mainapp/index.html&lt;/code&gt; to include a new link&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Tasks&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Tasks&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{% url "&lt;/span&gt;&lt;span class="na"&gt;dummy_and_slow_view&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="err"&gt;%}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Dummy and slow&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{% url "&lt;/span&gt;&lt;span class="na"&gt;report_list&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="err"&gt;%}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Reports&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart your docker compose if it is running, CTRL-C to stop, &lt;code&gt;docker compose up&lt;/code&gt; to start&lt;/p&gt;

&lt;p&gt;In another terminal window make sure to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker compose run web python manage.py makemigrations
docker compose run web python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates and applies migrations. &lt;/p&gt;

&lt;p&gt;If you are unfamiliar with this topic here is the relevant page in &lt;a href="https://docs.djangoproject.com/en/5.1/topics/migrations/" rel="noopener noreferrer"&gt;Django Docs: Migrations&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open our app in browser: &lt;a href="http://localhost:9000/" rel="noopener noreferrer"&gt;http://localhost:9000/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkk5k46dngck5u3705u94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkk5k46dngck5u3705u94.png" alt="image" width="696" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the "Reports" link.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1cbwm3j007vqxliolovh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1cbwm3j007vqxliolovh.png" alt="image" width="696" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the "Create a report" link&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69mq84nww037hyvinvyj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69mq84nww037hyvinvyj.png" alt="image" width="696" height="459"&gt;&lt;/a&gt;&lt;br&gt;
The complexity is just a number of seconds the report will take to generate, leave it like that, it should be enough for the demonstration.&lt;/p&gt;

&lt;p&gt;Click on the "Submit" button.&lt;/p&gt;

&lt;p&gt;The page will say that report is in the queue, because our Celery task hasn't yet been started.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhx3xf8xeano8tw0wf7e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhx3xf8xeano8tw0wf7e.png" alt="image" width="696" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After 5 seconds page refreshes and the report is being generated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fppfqksh7pvzulfd2q66a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fppfqksh7pvzulfd2q66a.png" alt="image" width="696" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;5 seconds later, another refresh, the report is ready. The content of the report is in the orange box.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzfmvuom117hyywor3o1s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzfmvuom117hyywor3o1s.png" alt="image" width="696" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Well done!&lt;br&gt;
You've made it to the end of our Django and Celery tutorial.&lt;/p&gt;

&lt;p&gt;Let's recap what we've learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We explored what Celery is and why it's such a powerful tool for handling background tasks.&lt;/li&gt;
&lt;li&gt;You've discovered how to create tasks that can run independently of your main application.&lt;/li&gt;
&lt;li&gt;We've delved into various ways to call these tasks, giving you flexibility in your implementations.&lt;/li&gt;
&lt;li&gt;Together, we built an example project that generates time-consuming reports,  with an auto-refreshing page that shows a waiting/in-progress screen and displays the final report when it's ready.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This tutorial has equipped you with a solid foundation, but there's so much more to discover. &lt;br&gt;
Celery is an incredibly versatile tool and allows a lot of advanced ways to use it.&lt;/p&gt;

&lt;p&gt;Good luck, happy coding and happy deploying!&lt;/p&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>devops</category>
      <category>programming</category>
    </item>
    <item>
      <title>Django REST Framework and DataTable Tutorial</title>
      <dc:creator>Kostja Appliku.com</dc:creator>
      <pubDate>Thu, 09 Sep 2021 04:15:21 +0000</pubDate>
      <link>https://dev.to/kostjapalovic/django-rest-framework-and-datatable-tutorial-1n5a</link>
      <guid>https://dev.to/kostjapalovic/django-rest-framework-and-datatable-tutorial-1n5a</guid>
      <description>&lt;p&gt;In this tutorial we are going to build API as a data source for a DataTable jQuery plugin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;There is this awesome plugin I have used recently for displaying and sorting data &lt;a href="https://datatables.net" rel="noopener noreferrer"&gt;https://datatables.net&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is very easy to start using.&lt;/p&gt;

&lt;p&gt;You just make an HTML table, add a loop in Django template to display contents of table and then initialize this table with a JavaScript call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;table id="myDataTable"&amp;gt;
... table contents
&amp;lt;/table&amp;gt;

&amp;lt;script&amp;gt;
$(document).ready( function () {
    $('#myDataTable').DataTable();
} );
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sounds fun and easy until you need to display a lot of data.&lt;/p&gt;

&lt;p&gt;At the point where there are at least a thousand rows the size of the page will be huge, it will take a very long time and resources (CPU/RAM) to generate that page. Also page load time will be long killing user experience.&lt;/p&gt;

&lt;p&gt;Thankfully, there is a solution to this problem.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ServerSide&lt;/code&gt; mode for DataTable.&lt;/p&gt;

&lt;p&gt;From DataTables documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Server-side processing&lt;/p&gt;

&lt;p&gt;There are times when reading data from the DOM is simply too slow or unwieldy, particularly when dealing with many thousands or millions of data rows. To address this DataTables' server-side processing feature provides a method to let all the "heavy lifting" be done by a database engine on the server-side (they are after all highly optimised for exactly this use case!), and then have that information drawn in the user's web-browser. Consequently, you can display tables consisting of millions of rows with ease.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://datatables.net/manual/server-side" rel="noopener noreferrer"&gt;https://datatables.net/manual/server-side&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this article I want to show how to build the Django API with Django REST Framework that can be used as source for Datatables.&lt;/p&gt;

&lt;p&gt;For this tutorial I will make a new repository from our Djangitos template and cloning it to my machine.&lt;/p&gt;

&lt;p&gt;Go to Djangitos GitHub repository &lt;a href="https://github.com/appliku/djangitos" rel="noopener noreferrer"&gt;https://github.com/appliku/djangitos&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click button "Use this template"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_2xu7M4x.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_2xu7M4x.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give a name to the new repository and click the "Create repository from template" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_2AnFGNf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_2AnFGNf.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When new repository is ready, copy the path and use it to clone repo on your machine with &lt;code&gt;git clone&lt;/code&gt;, in this case &lt;code&gt;git clone git@github.com:appliku/tutorial_jquery_datatable_api.git&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_ITUPrbZ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_ITUPrbZ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Switch to directory of the project with &lt;code&gt;cd tutorial_jquery_datatable_api&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_v8Yemws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_v8Yemws.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create an &lt;code&gt;.env&lt;/code&gt; file with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASE_URL=postgresql://djangito:djangito@db/djangito
REDIS_URL=redis://redis/0
DJANGO_SECRET_KEY=123
DJANGO_DEBUG=True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is needed in order to run our project with docker-compose.&lt;/p&gt;

&lt;p&gt;Now you can open your editor or IDE, for pycharm on mac you can type &lt;code&gt;open -a pycharm .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now let's create an django application where we will put models, views and templates for this tutorial.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose run web python manage.py startapp datatable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a directory in the root of our project &lt;code&gt;datatable&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_MxA2Qmk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_MxA2Qmk.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's add the app to &lt;code&gt;INSTALLED_APPS&lt;/code&gt; setting, so Django recognizes it.&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;djangito/settings.py&lt;/code&gt; and add &lt;code&gt;'datatable'&lt;/code&gt; to &lt;code&gt;PROJECT_APPS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_XdW28TM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_XdW28TM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create models
&lt;/h2&gt;

&lt;p&gt;I want to make this tutorial complex enough so there is chance to illustrate where can be performance issues and how to solve them and generally have a chance to talk about adjacent topics.&lt;/p&gt;

&lt;p&gt;As an example we'll use an imaginary service company that does certain work for clients.&lt;/p&gt;

&lt;p&gt;They need to track statuses of their work orders, what should be done and who are their clients.&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;datatable/models.py&lt;/code&gt;. Put these models in this file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
from django.db import models

from datatable.tuples import ORDER_STATUSES


class Client(models.Model):
    name = models.CharField(max_length=255)
    phone = models.CharField(max_length=255)
    email = models.EmailField()

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "Client"
        verbose_name_plural = "Clients"
        ordering = ('name',)


class Order(models.Model):
    STATUS_CHOICES = (
        (ORDER_STATUSES.proposal, 'Proposal'),
        (ORDER_STATUSES.in_progress, 'In Progress'),
        (ORDER_STATUSES.done, 'Done'),
        (ORDER_STATUSES.rejected, 'Rejected'),
    )
    name = models.CharField(max_length=255)
    client = models.ForeignKey(Client, on_delete=models.CASCADE)
    address = models.CharField(max_length=255)
    state = models.CharField(max_length=255)
    zip_code = models.CharField(max_length=10)
    status = models.IntegerField(choices=STATUS_CHOICES, default=ORDER_STATUSES.proposal)
    date_start = models.DateField()
    date_end = models.DateField()

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "Order"
        verbose_name_plural = "Orders"
        ordering = ('date_end',)


class OrderLine(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE)
    name = models.CharField(max_length=255, )
    description = models.TextField()
    unit_price = models.DecimalField(max_digits=10, decimal_places=2)
    quantity = models.IntegerField()

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "Order Line"
        verbose_name_plural = "Order Lines"
        ordering = ('name',)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit &lt;code&gt;datatable/admin.py&lt;/code&gt; to register our models in Django Admin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.contrib import admin
from datatable.models import Order, OrderLine, Client

admin.site.register(Order)
admin.site.register(OrderLine)
admin.site.register(Client)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For statuses we'll use &lt;code&gt;namedtuple&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create a file &lt;code&gt;datatable/tuples.py&lt;/code&gt; with this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from collections import namedtuple

ORDER_STATUSES = namedtuple('ORDER_STATUSES', 'proposal in_progress done rejected')._make(range(4))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Namedtuples are great for preventing errors and also provides code completion in IDE.&lt;/p&gt;

&lt;p&gt;Now let's make migrations for these models. Run this command in the root of your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose run web python manage.py makemigrations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the output you should expect:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_KbETsDA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_KbETsDA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's apply migrations, in order to do that, run the &lt;code&gt;migrate&lt;/code&gt; management command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose run web python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also we need a superuser. Let's create one.&lt;/p&gt;

&lt;p&gt;Appliku Djangitos template comes with a simplified way to create superuser, the management command called &lt;code&gt;makesuperuser&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose run web python manage.py makesuperuser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will generate a super user with email/username &lt;a href="mailto:admin@example.com"&gt;admin@example.com&lt;/a&gt; and a random password.&lt;/p&gt;

&lt;p&gt;Find the password in the output of this command, we'll need it in a few moments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/tutorial_jquery_datatable_api % docker-compose run web python manage.py makesuperuser
Creating tutorial_jquery_datatable_api_web_run ... done
Using selector: EpollSelector
admin user not found, creating one
===================================
A superuser was created with email admin@example.com and password NDTbnmPuyieX
===================================
admin@example.com
src/tutorial_jquery_datatable_api %
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's start our project with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you see this, then our app is running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;web_1       | Watching for file changes with StatReloader
web_1       | Watching for file changes with StatReloader
web_1       | Performing system checks...
web_1       |
web_1       | System check identified no issues (0 silenced).
web_1       | April 30, 2021 - 07:27:51
web_1       | Django version 3.1.6, using settings 'djangito.settings'
web_1       | Starting development server at http://0.0.0.0:8060/
web_1       | Quit the server with CONTROL-C.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the app in your browser at &lt;a href="http://0.0.0.0:8060/admin/" rel="noopener noreferrer"&gt;http://0.0.0.0:8060/admin/&lt;/a&gt; and log in with &lt;code&gt;admin@example.com&lt;/code&gt; and the password that was generated for you my &lt;code&gt;makesuperuser&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;On the admin dashboard you can find our models.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_vn0EftL.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_vn0EftL.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can go crazy now and create dozen orders with multiple line items so we have data to work with in the next steps.&lt;/p&gt;

&lt;p&gt;I recommend creating several different clients so we can test sorting and search features of datatable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_237IuhU.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_237IuhU.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Datatable with server rendered table
&lt;/h2&gt;

&lt;p&gt;For the purpose of illustration of what Datatable can do and comparison later let's first create a page where datatable works with server rendered table.&lt;/p&gt;

&lt;p&gt;Create a directory and a file &lt;code&gt;datatable/templates/base.html&lt;/code&gt; where we include all the common structure and resources for our views.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_bJL7uxx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_bJL7uxx.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;html lang="en"&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;DataTable&amp;lt;/title&amp;gt;
    &amp;lt;link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6" crossorigin="anonymous"&amp;gt;
    &amp;lt;link rel="stylesheet" href="//cdn.datatables.net/1.10.24/css/jquery.dataTables.min.css"&amp;gt;
    {% block extra_head %}
    {% endblock %}
&amp;lt;/head&amp;gt;

&amp;lt;body&amp;gt;
&amp;lt;div class="container mt-5"&amp;gt;
    {% block content %}

    {% endblock %}
&amp;lt;/div&amp;gt;
&amp;lt;script src="https://code.jquery.com/jquery-3.6.0.min.js"
        integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf"
        crossorigin="anonymous"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src="//cdn.datatables.net/1.10.24/js/jquery.dataTables.min.js"&amp;gt;&amp;lt;/script&amp;gt;
{% block extra_js %}
{% endblock %}
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's make a template for our first view with static datatable. Let's call it &lt;code&gt;datatable_static.html&lt;/code&gt; and full path will be &lt;code&gt;datatable/template/datatable_static.html&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_xqJTabN.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_xqJTabN.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{% extends "datatable/base.html" %}

{% block content %}
    &amp;lt;table id="myStaticDatatable"&amp;gt;
        &amp;lt;thead&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;th&amp;gt;ID&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;ORDER&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;CLIENT&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;TOTAL&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;STATUS&amp;lt;/th&amp;gt;
        &amp;lt;/tr&amp;gt;
        &amp;lt;/thead&amp;gt;
        &amp;lt;tbody&amp;gt;
        {% for order in order_list %}
            &amp;lt;tr&amp;gt;
                &amp;lt;td&amp;gt;{{ order.id }}&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
                    {{ order.name }}
                    &amp;lt;br&amp;gt;
                    {{ order.address }} {{ order.state }} {{ order.zip_code }}
                &amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
                    {{ order.client.name }}
                    &amp;lt;br&amp;gt;{{ order.client.phone }}
                    {{ order.client.email }}
                &amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;{{ order.amount }}&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;{{ order.get_status_display }}&amp;lt;/td&amp;gt;
            &amp;lt;/tr&amp;gt;
        {% endfor %}
        &amp;lt;/tbody&amp;gt;
    &amp;lt;/table&amp;gt;
{% endblock %}


{% block extra_js %}
    &amp;lt;script&amp;gt;
        $(document).ready(function () {
            $('#myStaticDatatable').DataTable();
        });
    &amp;lt;/script&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the file &lt;code&gt;datatable/views.py&lt;/code&gt;, let's create our first view here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.db.models import Sum, F, DecimalField
from django.shortcuts import render

from datatable.models import Order


def datatable_static(request, *args, **kwargs):
    orders_qs = Order.objects.all().select_related('client').annotate(
        amount=Sum(
            F('orderline__unit_price') * F('orderline__quantity'),
            output_field=DecimalField())
    )
    return render(
        request=request,
        template_name="datatable/datatable_static.html",
        context={
            "order_list": orders_qs
        })


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;datatable/urls.py&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.urls import path

from datatable.views import datatable_static

urlpatterns = [
    path('static', datatable_static, name='datatable_static'),
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit project's &lt;code&gt;urls.py&lt;/code&gt;: &lt;code&gt;djangito/urls.py&lt;/code&gt;. Add a line to include our &lt;code&gt;datatable&lt;/code&gt; urls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;path('datatable/', include('datatable.urls')),
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_YTqIsHF.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_YTqIsHF.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now if we open our page at &lt;a href="http://0.0.0.0:8060/datatable/static" rel="noopener noreferrer"&gt;http://0.0.0.0:8060/datatable/static&lt;/a&gt; we'll see our table:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_0HbXfAP.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_0HbXfAP.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's summarise points you should pay attention to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We made a base template that includes all resources and for our view we made template that extends the base one&lt;/li&gt;
&lt;li&gt;We used &lt;code&gt;.annotate()&lt;/code&gt; to calculate total amount of order on the database level. If we'd do it on python level it would require fetching all OrderLines and calculating them and it will be a massive performance hit.&lt;/li&gt;
&lt;li&gt;Finally, we made an HTML table in our template, filled it with out data using for-loop and made it a datatable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's make it not static, but server-rendered via API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Django REST Framework API for Datatable
&lt;/h2&gt;

&lt;p&gt;To make our API we need another View, a line in urls.py and a serializer.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;datatable/serializers.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We will create only one serializer, because we only need a flat object to display in datatable. We could use nested objects with datatable too, but I see no reason to make our code more complex.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from rest_framework import serializers

from datatable.models import Order


class OrderSerializer(serializers.ModelSerializer):
    amount = serializers.DecimalField(max_digits=10, decimal_places=2)
    client_name = serializers.ReadOnlyField(source='client.name')
    client_email = serializers.ReadOnlyField(source='client.email')
    client_phone = serializers.ReadOnlyField(source='client.phone')
    status = serializers.SerializerMethodField()

    class Meta:
        model = Order
        fields = (
            'id', 'name', 'address',
            'state', 'zip_code', 'status',
            'date_start', 'date_end',
            'client_name', 'client_phone', 'client_email', 'amount')

    def get_status(self, obj: Order):
        return obj.get_status_display()


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now add a new class based view to our &lt;code&gt;datatable/views.py&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

class DataTableAPIView(ListAPIView):
    serializer_class = OrderSerializer

    def get_queryset(self):
        return Order.objects.all().select_related('client').annotate(
        amount=Sum(
            F('orderline__unit_price') * F('orderline__quantity'),
            output_field=DecimalField())
    )

    def filter_for_datatable(self, queryset):
        # filtering
        search_query = self.request.query_params.get('search[value]')
        if search_query:
            queryset = queryset.annotate(
                search=SearchVector(
                    'name',
                    'client__name',
                    'address', 'zip_code')
            ).filter(search=search_query)
        # ordering
        ordering_column = self.request.query_params.get('order[0][column]')
        ordering_direction = self.request.query_params.get('order[0][dir]')
        ordering = None
        if ordering_column == '0':
            ordering = 'id'
        if ordering_column == '1':
            ordering = 'name'
        if ordering and ordering_direction == 'desc':
            ordering = f"-{ordering}"
        if ordering:
            queryset = queryset.order_by(ordering)
        return queryset

    def list(self, request, *args, **kwargs):
        draw = request.query_params.get('draw')
        queryset = self.filter_queryset(self.get_queryset())
        recordsTotal = queryset.count()
        filtered_queryset = self.filter_for_datatable(queryset)
        try:
            start = int(request.query_params.get('start'))
        except ValueError:
            start = 0
        try:
            length = int(request.query_params.get('length'))
        except ValueError:
            length = 10
        end = length + start
        serializer = self.get_serializer(filtered_queryset[start:end], many=True)
        response = {
            'draw': draw,
            'recordsTotal': recordsTotal,
            'recordsFiltered': filtered_queryset.count(),
            'data': serializer.data
        }
        return Response(response)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add 2 more items to &lt;code&gt;datatable/urls.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
    path('dynamic', TemplateView.as_view(template_name='datatable/datatable_dynamic.html'), name='datatable_dynamic'),
    path('data', DataTableAPIView.as_view(), name='datatable_data'),
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;dynamic&lt;/code&gt; refers to a generic &lt;code&gt;TemplateView&lt;/code&gt; and &lt;code&gt;data&lt;/code&gt; refers to our class based view.&lt;/p&gt;

&lt;p&gt;Add the template for our dynamic table, &lt;code&gt;datatable/templates/datatable/datatable_dynamic.html&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{% extends "datatable/base.html" %}

{% block content %}
    &amp;lt;table id="myStaticDatatable"&amp;gt;
        &amp;lt;thead&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;th&amp;gt;ID&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;ORDER&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;CLIENT&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;TOTAL&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;STATUS&amp;lt;/th&amp;gt;
        &amp;lt;/tr&amp;gt;
        &amp;lt;/thead&amp;gt;
        &amp;lt;tbody&amp;gt;

        &amp;lt;/tbody&amp;gt;
    &amp;lt;/table&amp;gt;
{% endblock %}


{% block extra_js %}
    &amp;lt;script&amp;gt;
        let data_url = '{% url "datatable_data" %}';
        $(document).ready(function () {
            $('#myStaticDatatable').DataTable({
                'order': [[1, 'desc']],
                'processing': false,
                'serverSide': true,
                'ajax': {
                    url: data_url,
                    dataSrc: 'data'
                },
                columns: [
                    {
                        data: 'id',
                        orderable: true
                    },
                    {
                        data: null,
                        render: function (data, type, row) {
                            return `${row.name}&amp;lt;br&amp;gt;${row.address} ${row.state} ${row.zip_code}`;
                        },
                        orderable: true
                    },
                    {
                        data:null,
                        render: function (data, type, row){
                            return `${row.client_name}&amp;lt;br/&amp;gt;${row.client_phone}&amp;lt;br/&amp;gt;${row.client_email}`
                        },
                        orderable: false
                    },
                    {
                        data: 'amount',
                        orderable: false
                    },
                    {
                        data: 'status',
                        orderable: false
                    }

                ]
            });
        });
    &amp;lt;/script&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change from the static table template is that we removed data for-loop, added an URL to our API &lt;code&gt;data_url&lt;/code&gt; and initialized the table with more configuration options.&lt;/p&gt;

&lt;p&gt;Let's go over initialization of the datatable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;order&lt;/code&gt; is default ordering for the table, it will be the second column, descending order.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;processing&lt;/code&gt; is disabled, I didn't want the "Processing" label to appear while table is loading. It just looks ugly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;serverSide&lt;/code&gt; is what makes datatable rely on server to load results according to sorting, filtering, page&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ajax&lt;/code&gt; is an object that tells where our API resides. &lt;code&gt;ajax.url&lt;/code&gt; is the API endpoint URL and &lt;code&gt;data&lt;/code&gt; is object in endpoint response JSON that contains actual data&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;columns&lt;/code&gt; defines how to display data in columns from the JSON endpoint returns. The &lt;code&gt;data&lt;/code&gt; attribute tells to use a field from JSON for response. &lt;code&gt;render&lt;/code&gt; is a function to render the column cell and we use it to build a piece of HTML based on several fields of our JSON, &lt;code&gt;data&lt;/code&gt; should be &lt;code&gt;null&lt;/code&gt; in this case. &lt;code&gt;orderable&lt;/code&gt; when enabled allows user to sort by this column.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Go to the page &lt;a href="http://0.0.0.0:8060/datatable/dynamic" rel="noopener noreferrer"&gt;http://0.0.0.0:8060/datatable/dynamic&lt;/a&gt; and see the table that works the same way as before, but it sources data from API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_U3Zr8oT.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd2352fi1ctpa7b.cloudfront.net%2Fmedia%2Fpost_images%2Fimage_U3Zr8oT.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Full source of the project for this article can be found here: &lt;a href="https://github.com/appliku/tutorial_jquery_datatable_api" rel="noopener noreferrer"&gt;https://github.com/appliku/tutorial_jquery_datatable_api&lt;/a&gt;&lt;/p&gt;

</description>
      <category>django</category>
      <category>jquery</category>
    </item>
    <item>
      <title>Django Project Tutorial for beginners: Settings, Docker-Compose, Postgres and Redis</title>
      <dc:creator>Kostja Appliku.com</dc:creator>
      <pubDate>Thu, 01 Apr 2021 17:25:34 +0000</pubDate>
      <link>https://dev.to/kostjapalovic/django-project-tutorial-for-beginners-settings-docker-compose-postgres-and-redis-1gdj</link>
      <guid>https://dev.to/kostjapalovic/django-project-tutorial-for-beginners-settings-docker-compose-postgres-and-redis-1gdj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article is brought to you by the first deployment service, dedicated specifically to Python &amp;amp; Django &lt;a href="https://appliku.com" rel="noopener noreferrer"&gt;Appliku.com&lt;/a&gt;.&lt;br&gt;
Never manage a server again.&lt;br&gt;
Deploy your Django app in 5 minutes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;In this article:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Django Tutorial Source Code&lt;br&gt;
Create the environment for the project&lt;br&gt;
Requirements for Django Project&lt;br&gt;
.gitignore for Django Project&lt;br&gt;
Dockerfile&lt;br&gt;
docker-compose.yml&lt;br&gt;
Explanation about Django and Docker&lt;br&gt;
Django Settings&lt;br&gt;
Django Custom User Model&lt;br&gt;
Procfile for Django Project&lt;br&gt;
Structure of Django Project&lt;br&gt;
Push your Django Application to GitHub&lt;br&gt;
Deploying Django Project&lt;br&gt;
Application Processes Section&lt;br&gt;
Config Variables Section&lt;br&gt;
Heroku Config Vars Sync&lt;br&gt;
Databases&lt;/p&gt;
&lt;h2&gt;
  
  
  Django Tutorial Source Code
&lt;/h2&gt;

&lt;p&gt;You can find the project source code here: &lt;a href="https://github.com/appliku/django_appliku_tutorial" rel="noopener noreferrer"&gt;https://github.com/appliku/django_appliku_tutorial&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Create the environment for the project
&lt;/h2&gt;

&lt;p&gt;Let's make a directory in our home directory to hold our virtual environments for different projects.&lt;/p&gt;

&lt;p&gt;Then we'll create an environment in it, activate it, install Django and then create our new project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/envs
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv ~/envs/tutorial
&lt;span class="nb"&gt;source&lt;/span&gt; ~/envs/tutorial/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; pip
pip &lt;span class="nb"&gt;install &lt;/span&gt;Django
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I prefer to keep all my code directories in &lt;code&gt;~/src&lt;/code&gt; directory. Let's make one if you don't have it and switch to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/src
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/src/
django-admin startproject tutorial
&lt;span class="nb"&gt;cd &lt;/span&gt;tutorial
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this stage I usually open my favorite IDE: PyCharm.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open &lt;span class="nt"&gt;-a&lt;/span&gt; pycharm &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to create files in the root of our project.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;requirements.txt&lt;/code&gt; will hold all dependencies our project needs.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.gitignore&lt;/code&gt; will tell git what files should not be added to the repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements for Django Project
&lt;/h2&gt;

&lt;p&gt;Open the create and open &lt;code&gt;requirements.txt&lt;/code&gt; and put these lines in the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Django==3.1.7
Pillow==7.2.0
gunicorn==20.0.4
requests==2.25.1
django-redis==4.12.1
pytz==2021.1
psycopg2-binary==2.8.6
arrow==1.0.3
djangorestframework==3.12.2
djangorestframework-simplejwt==4.6.0
django-allauth==0.44.0
django-environ==0.4.5
django-storages==1.11.1
django-cors-headers==3.7.0
django-braces==1.14.0
django-extensions==3.1.1
django-post-office==3.5.3
django-crispy-forms==1.11.1
boto3==1.17.22
boto3-stubs==1.17.22.0
django-import-export==2.5.0
honeybadger==0.4.2
django-ses==1.0.3
djangoql==0.14.3
flake8==3.8.4
whitenoise==5.2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Couple words about these requirements and versions.&lt;/p&gt;

&lt;p&gt;These are the packages I need in almost all of my projects, so I suggest including them in the tutorial project.&lt;/p&gt;

&lt;p&gt;You may have question about old Pillow library. I had some issues where more fresh versions were incompatible with Django when uploading images and the only solution I found was downgrading to 7.2.0&lt;/p&gt;

&lt;h2&gt;
  
  
  .gitignore for Django Project
&lt;/h2&gt;

&lt;p&gt;That's the most vital records for &lt;code&gt;.gitignore&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env/
venv/
.idea
.env

**/__pycache__/

.DS_Store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My OS is Mac so &lt;code&gt;.DS_Store&lt;/code&gt; is the file from Finder that I don't want to get in the repository.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;env&lt;/code&gt; and &lt;code&gt;venv&lt;/code&gt; are typical names for environments that are created inside project directory. We don't have it now, but when you clone project on another machine or other developer will join the project, they would expect such names to be ignored.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.idea&lt;/code&gt; is directory that PyCharm creates to store project specific settings.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.env&lt;/code&gt; is local environment variables and we never should include it in repository.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;__pycache__&lt;/code&gt; is "compiled" bytecode version of your &lt;code&gt;.py&lt;/code&gt; files. You interpreter may create it. Having them in repository is bad because they will cause problems when you try to run the app on even slightly different version of Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dockerfile
&lt;/h2&gt;

&lt;p&gt;We will run our project with Docker. &lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because you really want reproducible environment and avoid messing up with host machine.&lt;/p&gt;

&lt;p&gt;In order to do that, let's create 2 files. &lt;/p&gt;

&lt;p&gt;First, &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM python:3.8
ENV PIP_NO_CACHE_DIR off
ENV PIP_DISABLE_PIP_VERSION_CHECK on
ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 0
ENV COLUMNS 80
RUN apt-get update \
 &amp;amp;&amp;amp; apt-get install -y --force-yes \
 nano python-pip gettext chrpath libssl-dev libxft-dev \
 libfreetype6 libfreetype6-dev  libfontconfig1 libfontconfig1-dev\
  &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*
WORKDIR /code/
COPY requirements.txt /code/
RUN pip install -r requirements.txt
COPY . /code/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  docker-compose.yml
&lt;/h2&gt;

&lt;p&gt;Second, &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3.3'
services:
  redis:
    image: redis
    command: redis-server
    ports:
      - "14000:6379"
  db:
    image: postgres
    environment:
      - POSTGRES_USER=tutorial
      - POSTGRES_PASSWORD=tutorial
      - POSTGRES_DB=tutorial
    ports:
      - "127.0.0.1:21003:5432"
  web:
    build: .
    restart: always
    command: python manage.py runserver 0.0.0.0:8600
    env_file:
      - .env
    ports:
      - "127.0.0.1:8600:8600"
    volumes:
      - .:/code
    links:
      - db
      - redis
    depends_on:
      - db
      - redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation about Django and Docker
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Dockerfile&lt;/code&gt; defines the state of the OS and filesystem where your application will be executing. That's oversimplified and completely non-nerdy explanation :)&lt;/p&gt;

&lt;p&gt;With instruction from &lt;code&gt;Dockerfile&lt;/code&gt; docker builds &lt;strong&gt;images&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;Dockerfile&lt;/code&gt; is a set of instructions, &lt;strong&gt;image&lt;/strong&gt; is the actual archive with files that can be used to execute apps in &lt;strong&gt;containers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For developer environment we need Django development server to run with a certain command and we need a postgres DB and a Redis instance.&lt;/p&gt;

&lt;p&gt;To define it with a code we will use &lt;code&gt;docker-compose.yml&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt; defines &lt;strong&gt;services&lt;/strong&gt; it will run.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;service&lt;/strong&gt; in &lt;code&gt;docker-compose.yml&lt;/code&gt; is defined primarily by the &lt;strong&gt;image&lt;/strong&gt; it uses, a &lt;strong&gt;command&lt;/strong&gt; to execute, &lt;strong&gt;ports&lt;/strong&gt; to expose, &lt;strong&gt;volumes&lt;/strong&gt; to mount and &lt;strong&gt;environment variables&lt;/strong&gt; to set.&lt;/p&gt;

&lt;p&gt;Again, it is a very high-level explanation with my own words and attempt to explain with as simple words as possible.&lt;/p&gt;

&lt;p&gt;Let's talk about what we defined in our &lt;code&gt;docker-compose.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We have a postgres service. When it is first initialized it will have a user, password and a database "tutorial".&lt;/p&gt;

&lt;p&gt;It will be available for other service on the DNS name &lt;code&gt;db&lt;/code&gt; on the port 5432. And it will be available for the host machine on port 21003.&lt;/p&gt;

&lt;p&gt;It uses "postgres" image, that it will pull from Docker Hub.&lt;/p&gt;

&lt;p&gt;There is no volume for the database defined, so if you kill the DB then you will loose the data.&lt;/p&gt;

&lt;p&gt;Next service is for our Django development server.&lt;/p&gt;

&lt;p&gt;Instead of image, we specify folder where to build image from &lt;code&gt;Dockerfile&lt;/code&gt;, which in this case is current directory (&lt;code&gt;.&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;If it fails, we want it to be always restarting.&lt;/p&gt;

&lt;p&gt;We specify what command to use to run our dev server.&lt;/p&gt;

&lt;p&gt;Environment variables will be taken from &lt;code&gt;.env&lt;/code&gt; file that we'll create later.&lt;/p&gt;

&lt;p&gt;We expose port 8600 on 127.0.0.1, so it will be accessable only from local machine. Keep in mind that if you want to change port you should also update the port in the &lt;code&gt;command&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;volumes&lt;/code&gt; section tells what directories will be mounted from the host machine inside the container. We want the current directory to be mounted on &lt;code&gt;/code&lt;/code&gt; where our app is running. See &lt;code&gt;WORKDIR&lt;/code&gt; in our &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;br&gt;
Since it is the development environment, we want changes to our code reflected in the container so dev server would reload automatically.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;links&lt;/code&gt; section will make resolution of DNS name &lt;code&gt;db&lt;/code&gt; possible inside container. In other words django dev server will be able to connect to &lt;code&gt;db&lt;/code&gt;. Same for &lt;code&gt;redis&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;depends_on&lt;/code&gt; section lists services that must be started before starting the &lt;code&gt;web&lt;/code&gt; service. In this case &lt;code&gt;redis&lt;/code&gt; and &lt;code&gt;db&lt;/code&gt; will be started first, then &lt;code&gt;web&lt;/code&gt; will be started.&lt;/p&gt;

&lt;p&gt;Last step, let's create the &lt;code&gt;.env&lt;/code&gt; file in the root of the project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASE_URL=postgresql://tutorial:tutorial@db/tutorial
REDIS_URL=redis://redis/0
DJANGO_SECRET_KEY=supersecret123!
DJANGO_DEBUG=True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here in form of special URLs we pass to our django project credentials for database and redis instance, secret_key Django should use and enable debug mode for Django.&lt;/p&gt;

&lt;p&gt;None of it affects our app yet. But it will be very important in a little bit.&lt;/p&gt;

&lt;p&gt;We need to test that our Docker image can be built and docker-compose has no errors.&lt;/p&gt;

&lt;p&gt;For now, let's just tell it to build our image.&lt;/p&gt;

&lt;p&gt;Run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In case of success, last lines of the output should be roughly like these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Removing intermediate container 757d0bd934ca
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; b4bba357f84c
Step 11/11 : COPY &lt;span class="nb"&gt;.&lt;/span&gt; /code/
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; fa5d799d8fc1

Successfully built fa5d799d8fc1
Successfully tagged tutorial_web:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great job!&lt;/p&gt;

&lt;p&gt;Time to work on settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Django Settings
&lt;/h2&gt;

&lt;p&gt;We want our apps to be scalable, work under a lot of traffic and handle growth.&lt;/p&gt;

&lt;p&gt;In order to do that we need to build our app so it allows scaling.&lt;/p&gt;

&lt;p&gt;In this case it is important that our app follows rules of The 12-factor app: &lt;a href="https://12factor.net" rel="noopener noreferrer"&gt;https://12factor.net&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's list the key points here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Codebase – One codebase tracked in revision control, many deploys&lt;/li&gt;
&lt;li&gt;Dependencies – Explicitly declare and isolate dependencies&lt;/li&gt;
&lt;li&gt;Config – Store config in the environment&lt;/li&gt;
&lt;li&gt;Backing services – Treat backing services as attached resources&lt;/li&gt;
&lt;li&gt;Build, release, run – Strictly separate build and run stages&lt;/li&gt;
&lt;li&gt;Processes – Execute the app as one or more stateless processes&lt;/li&gt;
&lt;li&gt;Port binding – Export services via port binding&lt;/li&gt;
&lt;li&gt;Concurrency – Scale out via the process model&lt;/li&gt;
&lt;li&gt;Disposability – Maximize robustness with fast startup and graceful shutdown&lt;/li&gt;
&lt;li&gt;Dev/prod parity – Keep development, staging, and production as similar as possible&lt;/li&gt;
&lt;li&gt;Logs – Treat logs as event streams&lt;/li&gt;
&lt;li&gt;Admin processes – Run admin/management tasks as one-off processes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I strongly recommend to read all pages on that site.&lt;/p&gt;

&lt;p&gt;With that in mind let's open our settings file: &lt;code&gt;tutorial/settings.py&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We remove everything from there and start building our own from scratch.&lt;/p&gt;

&lt;p&gt;I will explain every code block then put the show the whole file so you can just copy it to your project.&lt;/p&gt;

&lt;p&gt;First we import a couple of libraries, set our root path of the project and create an env variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;
&lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;BASE_DIR&lt;/code&gt; we need to set proper paths to places in our project.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;env&lt;/code&gt; will help us properly get configuration from environment variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DJANGO_DEBUG&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;DEBUG&lt;/code&gt; should be off at all times except local development environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Allowed Hosts Definition
if DEBUG:
    # If Debug is True, allow all.
    ALLOWED_HOSTS = ['*']
else:
    ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default=['example.com'])

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;DEBUG&lt;/code&gt; is True, then we should allow all HOSTS when opening the app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SECRET_KEY = env('DJANGO_SECRET_KEY')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Secret Key is used to encrypt/sign cookies, passwords, etc. You must keep it safe and out of the version control.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"""
Project Apps Definitions
Django Apps - Django Internal Apps
Third Party Apps - Apps installed via requirements.txt
Project Apps - Project owned / created apps

Installed Apps = Django Apps + Third Part apps + Projects Apps
"""
DJANGO_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.sites',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.redirects',
]

THIRD_PARTY_APPS = [
    'django_extensions',
    'rest_framework',
    'storages',
    'corsheaders',
    'djangoql',
    'post_office',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'allauth.socialaccount.providers.google',
    'crispy_forms',
]

PROJECT_APPS = [
    'usermodel',
]

INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + PROJECT_APPS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is very convenient to differentiate where a certain app comes from, that's why we separate built-in Django apps, third party apps and project's apps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.redirects.middleware.RedirectFallbackMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Databases
DATABASES = {
    "default": env.db("DATABASE_URL")
}
DATABASES["default"]["ATOMIC_REQUESTS"] = True
DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our app will receive DATABASE_URL from environment variables in the form of the URL like &lt;code&gt;postgres://username:password@database-host.com:1234/databasename&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
ROOT_URLCONF = 'tutorial.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'tutorial.wsgi.application'

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]
AUTHENTICATION_BACKENDS = [
    'django.contrib.auth.backends.ModelBackend',
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# User Model Definition
AUTH_USER_MODEL = 'usermodel.User'

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For every new Django project, don't forget to create a custom user model, otherwise you effectively will not be able to change it later. We will discuss it a bit later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TIME_ZONE = 'UTC'
LANGUAGE_CODE = 'en-us'
SITE_ID = 1
USE_I18N = True
USE_L10N = True
USE_TZ = True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These settings are pretty static, but if you want to learn more I recommend to read official docs: &lt;a href="https://docs.djangoproject.com/en/3.1/ref/settings/" rel="noopener noreferrer"&gt;https://docs.djangoproject.com/en/3.1/ref/settings/&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Admin URL Definition
ADMIN_URL = env('DJANGO_ADMIN_URL', default='admin/')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Admin, never have admin on the default URL. With &lt;code&gt;DJANGO_ADMIN_URL&lt;/code&gt; env variable you will be able to set it different for every environment: production, staging, but leave default for local development.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
# Redis Settings
REDIS_URL = env('REDIS_URL', default=None)

if REDIS_URL:
    CACHES = {
        "default": env.cache('REDIS_URL')
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;REDIS is primarily used for caching end temporary data storage. &lt;/p&gt;

&lt;p&gt;In this case we make it optional, and if REDIS_URL is defined then we enable the default cache.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how we tell Django to detected that app is working behind SSL proxy.&lt;/p&gt;

&lt;p&gt;In our nginx server definition we must set &lt;code&gt;X-Forwarded-Proto&lt;/code&gt; header.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler'
        },
    },
    'loggers': {
        '': {  # 'catch all' loggers by referencing it with the empty string
            'handlers': ['console'],
            'level': 'DEBUG',
        },
    },
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a very simple logging, that should output everything to console from all modules with level of &lt;code&gt;DEBUG&lt;/code&gt;, which means output everything it can.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Static And Media Settings
AWS_STORAGE_BUCKET_NAME = env('AWS_STORAGE_BUCKET_NAME', default=None)
if AWS_STORAGE_BUCKET_NAME:
    AWS_DEFAULT_ACL = None
    AWS_QUERYSTRING_AUTH = False
    AWS_S3_CUSTOM_DOMAIN = env('AWS_S3_CUSTOM_DOMAIN', default=None) or f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'
    AWS_S3_OBJECT_PARAMETERS = {'CacheControl': 'max-age=600'}

    # s3 static settings
    STATIC_LOCATION = 'static'
    STATIC_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{STATIC_LOCATION}/'
    STATICFILES_STORAGE = 'tutorial.storages.StaticStorage'

    # s3 public media settings
    PUBLIC_MEDIA_LOCATION = 'media'
    MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{PUBLIC_MEDIA_LOCATION}/'
    DEFAULT_FILE_STORAGE = 'tutorial.storages.PublicMediaStorage'
else:
    MIDDLEWARE.insert(2, 'whitenoise.middleware.WhiteNoiseMiddleware')
    STATICFILES_STORAGE = 'whitenoise.storage.CompressedStaticFilesStorage'
    WHITENOISE_USE_FINDERS = True
    STATIC_HOST = env('DJANGO_STATIC_HOST', default='')
    STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
    STATIC_URL = STATIC_HOST + '/static/'
    if DEBUG:
        WHITENOISE_AUTOREFRESH = True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the config for working with static and media files. It would upload static with &lt;code&gt;python manage.py collectstatic --noinput&lt;/code&gt; to S3 and all media files will be stored in S3 when uploaded by user or app itself.&lt;/p&gt;

&lt;p&gt;If the the environment variable &lt;code&gt;AWS_STORAGE_BUCKET_NAME&lt;/code&gt; is not present, this part of config will not be enabled, which should be the case for local development.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;AWS_STORAGE_BUCKET_NAME&lt;/code&gt; is not set, Django will use &lt;code&gt;whitenoise&lt;/code&gt; to server static files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL', default='test@example.com')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our app probably will send emails at some point, at least password resets. We should specify what will be the default sender. Again, as env var.&lt;/p&gt;

&lt;p&gt;Let's get to third party apps configuration.&lt;/p&gt;

&lt;p&gt;First of all, we need to know about any error that will happen in production.&lt;/p&gt;

&lt;p&gt;We will set our app to report to the error tracking service &lt;a href="https://HoneyBadger.io" rel="noopener noreferrer"&gt;https://HoneyBadger.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Again, it will be only enabled if the env var &lt;code&gt;HONEYBARDGER_API_KEY&lt;/code&gt; is set. You can get this env var from the project settings in HoneyBadger.&lt;/p&gt;

&lt;p&gt;Now let's configure Celery to run our background tasks.&lt;/p&gt;

&lt;p&gt;It is optional. If &lt;code&gt;celery&lt;/code&gt; is not installed, or env var &lt;code&gt;CELERY_BROKER_URL&lt;/code&gt; is not defined, then it is not enabled.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Celery Settings
try:
    from kombu import Queue
    from celery import Celery
    CELERY_BROKER_URL = env('CELERY_BROKER_URL', default='amqp://localhost')
    if CELERY_BROKER_URL:
        CELERYD_TASK_SOFT_TIME_LIMIT = 60
        CELERY_ACCEPT_CONTENT = ['application/json']
        CELERY_TASK_SERIALIZER = 'json'
        CELERY_RESULT_SERIALIZER = 'json'
        CELERY_RESULT_BACKEND = env('REDIS_URL', default='redis://localhost:6379/0')
        CELERY_DEFAULT_QUEUE = 'default'
        CELERY_QUEUES = (
            Queue('default'),
        )
        CELERY_CREATE_MISSING_QUEUES = True
except ModuleNotFoundError:
    print("Celery/kombu not installed. Skipping...")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's configure Django-AllAuth in order to have option to register/login via social logins. Let's have Google as the only provider for this tutorial.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
# AllAuth Settings
AUTHENTICATION_BACKENDS += [
    # `allauth` specific authentication methods, such as login by e-mail
    'allauth.account.auth_backends.AuthenticationBackend',
]

ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
ACCOUNT_FORMS = {'signup': 'usermodel.forms.MyCustomSignupForm'}
ACCOUNT_MAX_EMAIL_ADDRESSES = 2
SOCIALACCOUNT_PROVIDERS = {

}
SOCIALACCOUNT_PROVIDERS_GOOGLE_CLIENT_ID = env('SOCIALACCOUNT_PROVIDERS_GOOGLE_CLIENT_ID', default=None)
if SOCIALACCOUNT_PROVIDERS_GOOGLE_CLIENT_ID:
    SOCIALACCOUNT_PROVIDERS['google'] = {
        'SCOPE': [
            'profile',
            'email',
        ],
        'AUTH_PARAMS': {
            'access_type': 'online',
        },
        # For each OAuth based provider, either add a ``SocialApp``
        # (``socialaccount`` app) containing the required client
        # credentials, or list them here:
        'APP': {
            'client_id': SOCIALACCOUNT_PROVIDERS_GOOGLE_CLIENT_ID,
            'secret': env('SOCIALACCOUNT_PROVIDERS_GOOGLE_SECRET'),
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order for it to work we need to obtain CLIENT_ID and CLIENT_SECRET from Google Cloud services.&lt;/p&gt;

&lt;p&gt;As before, I prefer to have it optional. So if env var &lt;code&gt;SOCIALACCOUNT_PROVIDERS_GOOGLE_CLIENT_ID&lt;/code&gt; is present then we add Google to the list of providers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Crispy Forms Settings
CRISPY_TEMPLATE_PACK = 'bootstrap4'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;crispy-forms&lt;/code&gt; package help our forms look good. In this case with &lt;code&gt;bootstrap4&lt;/code&gt; setting it will render our forms according to bootstrap 4 HTML structure.&lt;/p&gt;

&lt;p&gt;And let's wrap up with the email sending part.&lt;/p&gt;

&lt;p&gt;I love two libraries that help with sending and managing outgoing emails.&lt;/p&gt;

&lt;p&gt;One of them is &lt;code&gt;django-post-office&lt;/code&gt; which gives users email templates, scheduling, prioritization and stores outgoing emails, with logs and statuses so you can conveniently debug on apps side if email went out, what exactly was sent and if it wasn't - there will be logs attached to each email.&lt;/p&gt;

&lt;p&gt;Second library is &lt;code&gt;django-ses&lt;/code&gt;, for sending emails via AWS Simple Email Service(SES).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Django Post Office Settings
EMAIL_BACKEND = 'post_office.EmailBackend'

POST_OFFICE = {
    'BACKENDS': {
        'default': 'django_ses.SESBackend',
    },
    'DEFAULT_PRIORITY': 'now',
}

# AWS SES Settings
AWS_SES_REGION_NAME = env('AWS_SES_REGION_NAME', default='us-east-1')
AWS_SES_REGION_ENDPOINT = env('AWS_SES_REGION_ENDPOINT', default='email.us-east-1.amazonaws.com')
AWS_SES_CONFIGURATION_SET = env('AWS_SES_CONFIGURATION_SET', default=None)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's worth some explanation.&lt;/p&gt;

&lt;p&gt;When you want to send email via SES, you need to request sending capacity from AWS in a specific region. Before that your account in that region is in email sandbox and can only send emails to yourself, a.k.a. validated email address. &lt;/p&gt;

&lt;p&gt;When AWS allows you send email via that region, you must make app aware of it via settings above. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;AWS_SES_CONFIGURATION_SET&lt;/code&gt; setting is needed if you have configured AWS CloudWatch to track opens, clicks, and so on. Leave it empty if you haven't.&lt;/p&gt;

&lt;p&gt;This wraps up working on our &lt;code&gt;tutorial/settings.py&lt;/code&gt; and here is the full file for you to copy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from pathlib import Path
import environ
import os

env = environ.Env()

"""
Project Settings
"""

BASE_DIR = Path(__file__).resolve(strict=True).parent.parent

DEBUG = env.bool('DJANGO_DEBUG', default=False)

# Allowed Hosts Definition
if DEBUG:
    # If Debug is True, allow all.
    ALLOWED_HOSTS = ['*']
else:
    ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default=['example.com'])

SECRET_KEY = env('DJANGO_SECRET_KEY')

"""
Project Apps Definitions
Django Apps - Django Internal Apps
Third Party Apps - Apps installed via requirements.txt
Project Apps - Project owned / created apps

Installed Apps = Django Apps + Third Part apps + Projects Apps
"""
DJANGO_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.sites',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.redirects',
]

THIRD_PARTY_APPS = [
    'import_export',
    'django_extensions',
    'rest_framework',
    'storages',
    'corsheaders',
    'djangoql',
    'post_office',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'allauth.socialaccount.providers.google',
    'crispy_forms',
]

PROJECT_APPS = [
    'usermodel',
]

INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + PROJECT_APPS

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    'django.contrib.redirects.middleware.RedirectFallbackMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# Databases
DATABASES = {
    "default": env.db("DATABASE_URL")
}
DATABASES["default"]["ATOMIC_REQUESTS"] = True
DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60)

ROOT_URLCONF = 'tutorial.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'tutorial.wsgi.application'

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]
AUTHENTICATION_BACKENDS = [
    'django.contrib.auth.backends.ModelBackend',
]
# User Model Definition
AUTH_USER_MODEL = 'usermodel.User'

TIME_ZONE = 'UTC'
LANGUAGE_CODE = 'en-us'
SITE_ID = 1
USE_I18N = True
USE_L10N = True
USE_TZ = True

# Admin URL Definition
ADMIN_URL = env('DJANGO_ADMIN_URL', default='admin/')

# Redis Settings
REDIS_URL = env('REDIS_URL', default=None)

if REDIS_URL:
    CACHES = {
        "default": env.cache('REDIS_URL')
    }
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler'
        },
    },
    'loggers': {
        '': {  # 'catch all' loggers by referencing it with the empty string
            'handlers': ['console'],
            'level': 'DEBUG',
        },
    },
}


# Static And Media Settings
AWS_STORAGE_BUCKET_NAME = env('AWS_STORAGE_BUCKET_NAME', default=None)
if AWS_STORAGE_BUCKET_NAME:
    AWS_DEFAULT_ACL = None
    AWS_QUERYSTRING_AUTH = False
    AWS_S3_CUSTOM_DOMAIN = env('AWS_S3_CUSTOM_DOMAIN', default=None) or f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'
    AWS_S3_OBJECT_PARAMETERS = {'CacheControl': 'max-age=600'}

    # s3 static settings
    STATIC_LOCATION = 'static'
    STATIC_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{STATIC_LOCATION}/'
    STATICFILES_STORAGE = 'tutorial.storages.StaticStorage'

    # s3 public media settings
    PUBLIC_MEDIA_LOCATION = 'media'
    MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{PUBLIC_MEDIA_LOCATION}/'
    DEFAULT_FILE_STORAGE = 'tutorial.storages.PublicMediaStorage'

    STATICFILES_DIRS = (
        # os.path.join(BASE_DIR, "static"),
    )
else:
    MIDDLEWARE.insert(2, 'whitenoise.middleware.WhiteNoiseMiddleware')
    STATICFILES_STORAGE = 'whitenoise.storage.CompressedStaticFilesStorage'
    WHITENOISE_USE_FINDERS = True
    STATIC_HOST = env('DJANGO_STATIC_HOST', default='')
    STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
    STATIC_URL = STATIC_HOST + '/static/'
    if DEBUG:
        WHITENOISE_AUTOREFRESH = True
DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL', default='test@example.com')

"""
Third Party Settings
"""

# Honeybadger Settings
HONEYBADGER_API_KEY = env('HONEYBADGER_API_KEY', default=None)
if HONEYBADGER_API_KEY:
    MIDDLEWARE = ['honeybadger.contrib.DjangoHoneybadgerMiddleware'] + MIDDLEWARE
    HONEYBADGER = {
        'API_KEY': HONEYBADGER_API_KEY
    }

# Celery Settings
try:
    from kombu import Queue
    from celery import Celery
    CELERY_BROKER_URL = env('CELERY_BROKER_URL', default='amqp://localhost')
    if CELERY_BROKER_URL:
        CELERYD_TASK_SOFT_TIME_LIMIT = 60
        CELERY_ACCEPT_CONTENT = ['application/json']
        CELERY_TASK_SERIALIZER = 'json'
        CELERY_RESULT_SERIALIZER = 'json'
        CELERY_RESULT_BACKEND = env('REDIS_URL', default='redis://localhost:6379/0')
        CELERY_DEFAULT_QUEUE = 'default'
        CELERY_QUEUES = (
            Queue('default'),
        )
        CELERY_CREATE_MISSING_QUEUES = True
except ModuleNotFoundError:
    print("Celery/kombu not installed. Skipping...")

# AllAuth Settings
AUTHENTICATION_BACKENDS += [
    # `allauth` specific authentication methods, such as login by e-mail
    'allauth.account.auth_backends.AuthenticationBackend',
]

ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
ACCOUNT_FORMS = {'signup': 'usermodel.forms.MyCustomSignupForm'}
ACCOUNT_MAX_EMAIL_ADDRESSES = 2
SOCIALACCOUNT_PROVIDERS = {

}
SOCIALACCOUNT_PROVIDERS_GOOGLE_CLIENT_ID = env('SOCIALACCOUNT_PROVIDERS_GOOGLE_CLIENT_ID', default=None)
if SOCIALACCOUNT_PROVIDERS_GOOGLE_CLIENT_ID:
    SOCIALACCOUNT_PROVIDERS['google'] = {
        'SCOPE': [
            'profile',
            'email',
        ],
        'AUTH_PARAMS': {
            'access_type': 'online',
        },
        # For each OAuth based provider, either add a ``SocialApp``
        # (``socialaccount`` app) containing the required client
        # credentials, or list them here:
        'APP': {
            'client_id': SOCIALACCOUNT_PROVIDERS_GOOGLE_CLIENT_ID,
            'secret': env('SOCIALACCOUNT_PROVIDERS_GOOGLE_SECRET'),
        }
    }
# Crispy Forms Settings
CRISPY_TEMPLATE_PACK = 'bootstrap4'

# Django Post Office Settings
EMAIL_BACKEND = 'post_office.EmailBackend'

POST_OFFICE = {
    'BACKENDS': {
        'default': 'django_ses.SESBackend',
    },
    'DEFAULT_PRIORITY': 'now',
}

# AWS SES Settings

AWS_SES_REGION_NAME = env('AWS_SES_REGION_NAME', default='us-east-1')
AWS_SES_REGION_ENDPOINT = env('AWS_SES_REGION_ENDPOINT', default='email.us-east-1.amazonaws.com')
AWS_SES_CONFIGURATION_SET = env('AWS_SES_CONFIGURATION_SET', default=None)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On more step, create a file next to &lt;code&gt;settings.py&lt;/code&gt; call it &lt;code&gt;storages.py&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from storages.backends.s3boto3 import S3Boto3Storage


class PublicMediaStorage(S3Boto3Storage):
    location = 'media'
    default_acl = 'public-read'
    file_overwrite = False


class StaticStorage(S3Boto3Storage):
    location = 'static'
    default_acl = 'public-read'

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is needed for our media and static storage to work, as we refer these classes from &lt;code&gt;settings.py&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Django Custom User Model
&lt;/h2&gt;

&lt;p&gt;Every Django project that employes Users and authentication should define a custom user model. Even if you keep it same as the stock one, it will give you ability to modify it later to better fit your project's requirements.&lt;/p&gt;

&lt;p&gt;Changing user model mid-project is very tricky task since you will have other models instances refering existing Users and all this you will have to migrate.&lt;/p&gt;

&lt;p&gt;With that said, in root of the project create a folder &lt;code&gt;usermodel&lt;/code&gt; and an empty file in it &lt;code&gt;__init__.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You could achieve something similar by running &lt;code&gt;python manage.py startapp usermodel&lt;/code&gt;, but i just wanted to have a chance to talk about what makes a directory a python module. &lt;/p&gt;

&lt;p&gt;To be able to import a module or anything from it, a directory needs to have &lt;code&gt;__init__.py&lt;/code&gt; in it.&lt;/p&gt;

&lt;p&gt;Now create &lt;code&gt;usermodel/models.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This we should put in the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import uuid

from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from django.contrib.postgres.fields import CIEmailField
from django.core.mail import send_mail
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.utils import timezone

from usermodel.managers import UserManager


class User(AbstractBaseUser, PermissionsMixin):
    """
    An abstract base class implementing a fully featured User model with
    admin-compliant permissions.
    Username and password are required. Other fields are optional.
    """
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    email = CIEmailField(
        _('Email Address'),
        unique=True,
        error_messages={
            'unique': _("A user with that username already exists."),
        },
    )

    first_name = models.CharField(_('First Name'), max_length=255, blank=True)
    last_name = models.CharField(_('Last Name'), max_length=255, blank=True)

    is_staff = models.BooleanField(
        _('Staff Status'),
        default=False,
        help_text=_('Designates whether the user can log into this admin site.'),
    )

    is_active = models.BooleanField(
        _('Active'),
        default=True,
        help_text=_(
            'Designates whether this user should be treated as active. '
            'Unselect this instead of deleting accounts.'
        ),
    )

    # Audit Values
    is_email_confirmed = models.BooleanField(
        _('Email Confirmed'),
        default=False
    )
    date_joined = models.DateTimeField(
        _('Date Joined'),
        default=timezone.now
    )

    objects = UserManager()

    EMAIL_FIELD = 'email'
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = [
        'first_name',
        'last_name'
    ]

    class Meta:
        verbose_name = _('User')
        verbose_name_plural = _('Users')

    def clean(self):
        super().clean()
        self.email = self.__class__.objects.normalize_email(self.email)

    def get_full_name(self):
        """
        Return the first_name plus the last_name, with a space in between.
        """
        return f"{self.first_name} {self.last_name}"

    def get_short_name(self):
        """Return the short name for the user."""
        return self.first_name

    def email_user(self, subject, message, from_email=None, **kwargs):
        """Send an email to this user."""
        send_mail(subject, message, from_email, [self.email], **kwargs)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use email as login, UUID as a primary key.&lt;/p&gt;

&lt;p&gt;General benefits of UUID vs integer primary key:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nobody can estimate number of users/orders/payments by looking at the freshest ID they got when placing another order or creating any other type of object.&lt;/li&gt;
&lt;li&gt;you can't bruteforce your way into seeing other objects, so it adds kind of layer of "security". Even if some links to objects are public, but intended only for those who has the link – they will be hard to access without having exact ID.&lt;/li&gt;
&lt;li&gt;and now the other problem that will be nice to have: biggest integer number seems to big a big number, 2,147,483,647. But when you reach it, then you can't add any other object. Integer is the default type of primary key. When you run out of numbers, database will refuse any new records. You might think, okay I will just change the field type to be a &lt;code&gt;bigint&lt;/code&gt;, but imagine how much time it will take to apply migration to such huge table of 2 billion records? You can as well take a vacation during that time, that you probably need while building such a big project. 😂 UUID will not have such problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, in our model we referenced a custom manager.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;objects = UserManager()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create a file usermodel/managers.py and put this code in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.contrib.auth.base_user import BaseUserManager


class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, email, password, **extra_fields):
        """
        Create and save a user with the given username, email, and password.
        """
        if not email:
            raise ValueError('The given username must be set')
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email=None, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')
        return self._create_user(email, password, **extra_fields)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;usermodel/admin.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.contrib import admin
from django.utils.translation import gettext_lazy as _

from usermodel.models import User
from django.contrib.auth.admin import UserAdmin as DefaultUserAdmin


@admin.register(User)
class UserAdmin(DefaultUserAdmin):
    fieldsets = (
        (
            None,
            {
                'fields': (
                    'email', 'password'
                )
            }
        ),
        (
            _('Permissions'),
            {
                'fields': (
                    'is_active',
                    'is_staff',
                    'is_superuser',
                    'groups',
                    'user_permissions',
                ),
            }
        ),
        (
            _('Important dates'),
            {
                'fields': (
                    'last_login',
                    'date_joined',
                )
            }
        ),
        (
            _('User data'),
            {
                'fields': (
                    ('is_email_confirmed',),
                )
            }
        ),
    )
    add_fieldsets = (
        (
            None,
            {
                'classes': ('wide',),
                'fields': ('email', 'password1', 'password2'),
            }
        ),
    )
    list_display = ('email', 'first_name', 'last_name', 'is_staff')
    search_fields = ('first_name', 'last_name', 'email')
    ordering = ('email',)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And cherry on top will be a non-interactive management command to create a superuser.&lt;/p&gt;

&lt;p&gt;While we have a way to create a super user, it is not convenient for non-interactive cases like release phase.&lt;/p&gt;

&lt;p&gt;What I chose to have in all my projects is this script, which checks if there is a superuser in database and if not - creates one with a random password.&lt;/p&gt;

&lt;p&gt;Create these directories and files inside &lt;code&gt;usermodel&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── management
│   ├── __init__.py
│   └── commands
│       ├── __init__.py
│       └── makesuperuser.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;__init__.py&lt;/code&gt; should be empty, and here is the code for &lt;code&gt;usermodel/management/makesuperuser.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.contrib.auth import get_user_model
from django.core.management.base import BaseCommand
from django.utils.crypto import get_random_string

User = get_user_model()


class Command(BaseCommand):
    def handle(self, *args, **options):
        try:
            u = None
            if not User.objects.filter(email='admin@example.com').exists() and not User.objects.filter(
                    is_superuser=True).exists():
                print("admin user not found, creating one")
                email = 'admin@example.com'
                new_password = get_random_string()

                u = User.objects.create_superuser(email, new_password)
                print(f"===================================")
                print(f"A superuser was created with email {email} and password {new_password}")
                print(f"===================================")
            else:
                print("admin user found. Skipping super user creation")
            print(u)
        except Exception as e:
            print(f"There was an error: {e}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's create migrations for our &lt;strong&gt;custom user model&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose run web python manage.py makemigrations usermodel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output of command should be something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Migrations &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="s1"&gt;'usermodel'&lt;/span&gt;:
  usermodel/migrations/0001_initial.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open this file with initial migration.&lt;/p&gt;

&lt;p&gt;On top of the file add import command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.contrib.postgres.operations import CITextExtension
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And add &lt;code&gt;CITextExtension(),&lt;/code&gt; as the first element of &lt;code&gt;operations&lt;/code&gt; list.&lt;/p&gt;

&lt;p&gt;The file should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Generated by Django 3.1.7 on 2021-03-08 13:10

import django.contrib.postgres.fields.citext
from django.contrib.postgres.operations import CITextExtension
from django.db import migrations, models
import django.utils.timezone
import usermodel.managers
import uuid


class Migration(migrations.Migration):

    initial = True

    dependencies = [
        ('auth', '0012_alter_user_first_name_max_length'),
    ]

    operations = [
        CITextExtension(),
        migrations.CreateModel(
            name='User',
            fields=[
                ('password', models.CharField(max_length=128, verbose_name='password')),
                ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
                ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
                ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
                ('email', django.contrib.postgres.fields.citext.CIEmailField(error_messages={'unique': 'A user with that username already exists.'}, max_length=254, unique=True, verbose_name='Email Address')),
                ('first_name', models.CharField(blank=True, max_length=255, verbose_name='First Name')),
                ('last_name', models.CharField(blank=True, max_length=255, verbose_name='Last Name')),
                ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='Staff Status')),
                ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='Active')),
                ('is_email_confirmed', models.BooleanField(default=False, verbose_name='Email Confirmed')),
                ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date Joined')),
                ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
                ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
            ],
            options={
                'verbose_name': 'User',
                'verbose_name_plural': 'Users',
            },
            managers=[
                ('objects', usermodel.managers.UserManager()),
            ],
        ),
    ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can apply migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose run web python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Using selector: EpollSelector
Operations to perform:
  Apply all migrations: account, admin, auth, contenttypes, post_office, redirects, sessions, sites, socialaccount, usermodel
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0001_initial... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying usermodel.0001_initial... OK
  Applying account.0001_initial... OK
  Applying account.0002_email_max_length... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying post_office.0001_initial... OK
  Applying post_office.0002_add_i18n_and_backend_alias... OK
  Applying post_office.0003_longer_subject... OK
  Applying post_office.0004_auto_20160607_0901... OK
  Applying post_office.0005_auto_20170515_0013... OK
  Applying post_office.0006_attachment_mimetype... OK
  Applying post_office.0007_auto_20170731_1342... OK
  Applying post_office.0008_attachment_headers... OK
  Applying post_office.0009_requeued_mode... OK
  Applying post_office.0010_message_id... OK
  Applying post_office.0011_models_help_text... OK
  Applying sites.0001_initial... OK
  Applying redirects.0001_initial... OK
  Applying sessions.0001_initial... OK
  Applying sites.0002_alter_domain_unique... OK
  Applying socialaccount.0001_initial... OK
  Applying socialaccount.0002_token_max_lengths... OK
  Applying socialaccount.0003_extra_data_default_dict... OK
src/tutorial % 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run the &lt;code&gt;makesuperuser&lt;/code&gt; management command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose run web python manage.py makesuperuser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output will contain user password, copy it somewhere, you will need it to log in to admin panel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;admin user not found, creating one
===================================
A superuser was created with email admin@example.com and password rWKwHw5FK6tw
===================================
admin@example.com
src/tutorial % 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try running this command again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;admin user found. Skipping super user creation
None
src/tutorial % 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See, second time no user is created.&lt;/p&gt;

&lt;p&gt;Congratulations, we are finished with our custom user model!&lt;/p&gt;

&lt;h2&gt;
  
  
  Procfile for Django Project
&lt;/h2&gt;

&lt;p&gt;Procfile is the file which tells Appliku Deploy how to run your application.&lt;/p&gt;

&lt;p&gt;This file must be located in the root of the project.&lt;/p&gt;

&lt;p&gt;There are 3 types of records:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;web&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;release&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;other&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;web&lt;/code&gt; process will be the one that handles HTTP requests.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;release&lt;/code&gt; will hold the command that is executed on each release, like applying migrations and other activities.&lt;/p&gt;

&lt;p&gt;All other processes have no special meaning. For example, you can put celery worker, scheduler or anything else specific to your app.&lt;/p&gt;

&lt;p&gt;Procfile can only have one &lt;code&gt;web&lt;/code&gt; process and one &lt;code&gt;release&lt;/code&gt; process. You can have as many other type of processes as you need.&lt;/p&gt;

&lt;p&gt;For our tutorial this will be the &lt;code&gt;Procfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;web: gunicorn tutorial.wsgi --log-file -
release: bash release.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a file in the root of the project &lt;code&gt;release.sh&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
python manage.py migrate --noinput
python manage.py makesuperuser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;release.sh&lt;/code&gt; will get executed on every new release and it will apply migrations and try to create a superuser in our app. As you remember only on the first release the user will be created.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structure of Django Project
&lt;/h2&gt;

&lt;p&gt;Let's take a look at our project before we start with deployment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;src/tutorial % tree
&lt;span class="nb"&gt;.&lt;/span&gt;
├── Dockerfile
├── Procfile
├── docker-compose.yml
├── manage.py
├── release.sh
├── requirements.txt
├── tutorial
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── storages.py
│   ├── urls.py
│   └── wsgi.py
└── usermodel
    ├── __init__.py
    ├── admin.py
    ├── management
    │   ├── __init__.py
    │   └── commands
    │       ├── __init__.py
    │       └── makesuperuser.py
    ├── managers.py
    ├── migrations
    │   ├── 0001_initial.py
    │   ├── __init__.py
    └── models.py

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Push your Django Application to GitHub
&lt;/h2&gt;

&lt;p&gt;In order to deploy your app you need it to be pushed to a GitHub repository.&lt;/p&gt;

&lt;p&gt;At this point of tutorial it is assumed that you have account on GitHub. It you don't, go to &lt;a href="https://github.com" rel="noopener noreferrer"&gt;https://github.com&lt;/a&gt; and sign up.&lt;/p&gt;

&lt;p&gt;Then create a repository, let's call it &lt;code&gt;django_appliku_tutorial&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fcreate_a_github_repository.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fcreate_a_github_repository.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For purposes of privacy you can make repository private, or you can make it public so your peers or future employers can see what you were up to :)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Just remember: never store any credentials in the code. Also remember: whatever you delete from the code, stays in repository history.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Hit create repository button.&lt;/p&gt;

&lt;p&gt;Now you are looking at the empty repository page. Let's use their instructions from the section "…or create a new repository on the command line".&lt;/p&gt;

&lt;p&gt;Here is the example they gave me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fempty_github_repository_A5Amc5K.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fempty_github_repository_A5Amc5K.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will have to change the path to your repository.&lt;/p&gt;

&lt;p&gt;Also since we have files we want to add all files &lt;code&gt;git add .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Go to terminal to the root of the project and write these commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "# django_appliku_tutorial" &amp;gt; README.md
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin git@github.com:appliku/django_appliku_tutorial.git
git push -u origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you pushed the code, the GitHub page of repository should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying Django Project
&lt;/h2&gt;

&lt;p&gt;Instead of doing old fashined manual deployment, writing a lot of configs and hoping it works, we'll use Appliku Deploy.&lt;/p&gt;

&lt;p&gt;What Appliku Deploy does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provisions a server in Digital Ocean(or AWS)&lt;/li&gt;
&lt;li&gt;Takes the code from GitHub repo,&lt;/li&gt;
&lt;li&gt;Builds your app on your server&lt;/li&gt;
&lt;li&gt;Deploys your app on your server&lt;/li&gt;
&lt;li&gt;Sets up web server (nginx) and issues SSL certificate from Let's Encrypt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In order to deploy your app you should have accounts in GitHub, Digital Ocean and Appliku Deploy.&lt;/p&gt;

&lt;p&gt;In case if you don't have them follow these links and create them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub.com &lt;a href="https://github.com/join" rel="noopener noreferrer"&gt;https://github.com/join&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Digital Ocean &lt;a href="https://cloud.digitalocean.com/registrations/new" rel="noopener noreferrer"&gt;https://cloud.digitalocean.com/registrations/new&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Appliku Deploy &lt;a href="https://app.appliku.com/" rel="noopener noreferrer"&gt;https://app.appliku.com/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you just registered on Appliku Deploy and signed in, make sure to complete onboarding, connecting the account to GitHub and Digital Ocean.&lt;/p&gt;

&lt;p&gt;First step is to create a server.&lt;/p&gt;

&lt;p&gt;It is done via Appliku Deploy interface. Please note, that you can't re-use an existing server you created manually via Digital Ocean interface. It must be provisioned via Appliku Deploy.&lt;/p&gt;

&lt;p&gt;Go to the Servers tab: &lt;a href="https://app.appliku.com/servers" rel="noopener noreferrer"&gt;https://app.appliku.com/servers&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click "Create New Server" button and you will be taken to Provider selection page ( &lt;a href="https://app.appliku.com/providers" rel="noopener noreferrer"&gt;https://app.appliku.com/providers&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fproviders_tab.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fproviders_tab.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select DigitalOcean.&lt;/p&gt;

&lt;p&gt;You will be taken to the page where you can select type of Droplet you want to provision ( &lt;a href="https://app.appliku.com/new-server-digital-ocean" rel="noopener noreferrer"&gt;https://app.appliku.com/new-server-digital-ocean&lt;/a&gt; ).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_tIc9Jvw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_tIc9Jvw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the purpose of this tutorial we will select cheapest available server type 1gb RAM, 1 CPU, 25GB SSD for $5/month and region: 🇳🇱AMS3.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_wREveoE.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_wREveoE.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click "Create A Server".&lt;/p&gt;

&lt;p&gt;After this you will be taken to the server list. You will see your server without any details. It is because Digital Ocean haven't fully provisioned it yet. When server is provisioned IP address and server size will appear. Progress of provisioning is displayed in the rightmost column.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fserver_is_being_created.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fserver_is_being_created.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can click on the server name to see server details. This page is updated regularly to reflect server's current status. &lt;/p&gt;

&lt;p&gt;When the "Status" becomes "Active" it means that Digital Ocean finished provisioning server. &lt;/p&gt;

&lt;p&gt;At this moment the "Setup" field should say "Started".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fserver_page_when_setup_in_progress.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fserver_page_when_setup_in_progress.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It means that Appliku Deploy connected to server and is running setup scripts. It will install software needed to run your apps: Docker, Nginx, certbot and configure them.&lt;/p&gt;

&lt;p&gt;It should take 2-3 minutes to complete setup.&lt;/p&gt;

&lt;p&gt;You can look at progress by going to "Setup Logs" tab. Please keep in mind that this page is not updated on its own and you will have to refresh the page to see latest records.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_b8p8VO2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_b8p8VO2.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Back to server's Overview tab: When the "Setup" field says "Finished" it means you can create an app and deploy it on this server.&lt;/p&gt;

&lt;p&gt;If the "Setup" field says "Failed" then you can check on "Setup Logs" tab to try to figure out what happened.&lt;/p&gt;

&lt;p&gt;Most popular reason for failure is that cloud provider gave us a bad server, that was unable to reach internet due to networking or disk issues. It is a rare occasion but it happens.&lt;/p&gt;

&lt;p&gt;In this case you should click "Manage Server in Digital Ocean Panel", destroy the server and back in Appliku Deploy interface create another server.&lt;/p&gt;

&lt;p&gt;If you still see the old server in the list - open the server details and it will make page refreshed with the current status of the server. If it is deleted, status of the server will become "deleted" and server will be gone from the server list. You can now create another server.&lt;/p&gt;

&lt;p&gt;Create an application. Go to "Applications" tab and click "New App From GitHub" ( &lt;a href="https://app.appliku.com/start" rel="noopener noreferrer"&gt;https://app.appliku.com/start&lt;/a&gt; ).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fcreate_new_application.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fcreate_new_application.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will see the form "Creating a new application".&lt;/p&gt;

&lt;p&gt;Fill the Application Name, pick repository(that we created earlier), branch(&lt;code&gt;main&lt;/code&gt;) and select the server to deploy to. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fcreate_new_application_filled_fields.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fcreate_new_application_filled_fields.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After that you can click "Create an Application".&lt;/p&gt;

&lt;p&gt;You will find yourself on the page of a newly created application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_vnCeZHP.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_vnCeZHP.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's go over this page real quick.&lt;/p&gt;

&lt;p&gt;First section is build settings. &lt;/p&gt;

&lt;p&gt;It says that the base image is Python 3.8, which means that under the hood Appliku Deploy will use python:3.8 Docker image to build the image with your app.&lt;/p&gt;

&lt;p&gt;You can specify the "build command" which will be executed as the last statement of our Dockerfile. Keep in mind that we pass all environment variables to the build, so your build command will be able to use it.&lt;/p&gt;

&lt;p&gt;If you need to use your own Dockerfile instructions, you can select "Custom Dockerfile" in dropdown and put instructions in text field.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application Processes Section
&lt;/h3&gt;

&lt;p&gt;In this list you will see all records from the &lt;code&gt;Procfile&lt;/code&gt; in your repository except &lt;code&gt;release&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Choose what processes you want to enable. For now we have only &lt;code&gt;web&lt;/code&gt;, so switch the toggle to &lt;code&gt;On&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Config Variables Section
&lt;/h3&gt;

&lt;p&gt;In this section you should specify environment variables that will be passed to your application when running as well as at build stage.&lt;/p&gt;

&lt;p&gt;Let's create several variables.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DJANGO_SECRET_KEY&lt;/code&gt; give this variable some long value like "foisr45r4ufuihsuihiuh3rluhihuihrui4wh4uihu4huiwhui44343423" that nobody will ever guess.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DJANGO_ALLOWED_HOSTS&lt;/code&gt; should contain your appname + &lt;code&gt;applikuapp.com&lt;/code&gt;. Or any other domain you will later attach to the site. In our case it is &lt;code&gt;djangoapplikututorial.applikuapp.com&lt;/code&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Heroku Config Vars Sync
&lt;/h3&gt;

&lt;p&gt;If you are migrating this application from Heroku, this this feature will come in handy. You need to enter your Heroku API key and application name and it will be continuously pulling any changes in Config Vars from Heroku and update them for your app. Keep in mind that editing config vars in appliku when Sync is enabled makes no sense – they will be overriden on the next sync.&lt;/p&gt;

&lt;p&gt;Now we need to create a database and a redis instance. &lt;/p&gt;

&lt;p&gt;So instead of deploying application right now we should click on "Continue to Application Overview".&lt;/p&gt;

&lt;p&gt;This how application dashboard looks like for a new app:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_wRsk4hu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_wRsk4hu.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go to Databases tabs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Databases
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_6skel6J.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_6skel6J.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click "New Database", select Postgres and select your server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_7l3bp0r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_7l3bp0r.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click "Create Database"&lt;/p&gt;

&lt;p&gt;Your new postgres database should appear in the list. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_okfy8tE.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_okfy8tE.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see the type of the database, State and credentials URL.&lt;/p&gt;

&lt;p&gt;When the State column is Deployed, it means that your database is ready to accept connections.&lt;/p&gt;

&lt;p&gt;We also need a redis instance.&lt;/p&gt;

&lt;p&gt;Let's add it the same way.&lt;/p&gt;

&lt;p&gt;Click the "New Database" button, choose redis and the same server. Click "Create Database".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_PCqB1Kf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_PCqB1Kf.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Redis instance should appear on the list, just like the postgres one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_F1xbbaJ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_F1xbbaJ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can go back to editing our config vars to make use of our new databases.&lt;/p&gt;

&lt;p&gt;Go to application's Settings tab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_4asPDrW.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_4asPDrW.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click "Reveal Config Vars"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_BT68sIw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_BT68sIw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's what we have there right now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_WsVxDBs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_WsVxDBs.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Postgres and redis credentials were added to their own, instance specific variables.&lt;/p&gt;

&lt;p&gt;What we need now is to create DATABASE_URL with the value from DATABASE_72_URL and a REDIS_URL with value from REDIS_73_URL.&lt;/p&gt;

&lt;p&gt;The reason why we have to do this manually is because you can have multiple databases of the same type and you are responsible for setting your application's env vars to use.&lt;/p&gt;

&lt;p&gt;That's how config vars should look at this stage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_dGHFgLx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_dGHFgLx.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep in mind that the number in &lt;code&gt;DATABASE_72_URL&lt;/code&gt; and &lt;code&gt;REDIS_73_URL&lt;/code&gt; will be different for you.&lt;/p&gt;

&lt;p&gt;Now we are all set with config vars, you should go to the Overview tab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_2e2ExdB.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_2e2ExdB.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are ready to start the first deployment.&lt;/p&gt;

&lt;p&gt;Click "DEPLOY NOW" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_O2rvN3D.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_O2rvN3D.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Information about the current deployment will appear:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_p81J5nY.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_p81J5nY.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When it is finsihed, status will reflect that, saying it is finished:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_XZrjYMK.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_XZrjYMK.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's click Manage Deployments and see our deployment logs for the generated admin password.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_OvTevTw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_OvTevTw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You are at the "Deploys" tab.&lt;/p&gt;

&lt;p&gt;First card has the form with deployment settings. You can change the repository branch, server to deploy to and toggle Push to Deploy feature, that starts deployment on push to GitHub repository.&lt;/p&gt;

&lt;p&gt;Second card contains history of deployments.&lt;/p&gt;

&lt;p&gt;In this example I have two deployments. First one failed for me, because I made a typo in settings file, while writing this tutorial. You can see that it says "Failed Building". This means it obviously failed, but another piece of information – we know at what stage it has failed: Build stage.&lt;/p&gt;

&lt;p&gt;You can click "View logs" to find out why it could fail. &lt;/p&gt;

&lt;p&gt;Our focus right now should be on the successful deployment. &lt;/p&gt;

&lt;p&gt;It says "Finished Cleaning up".&lt;/p&gt;

&lt;p&gt;There are several stages of deployments in Appliku Deploy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New - Deployment was just created, but nothing yet has been done.&lt;/li&gt;
&lt;li&gt;Building - your server is pulling code from repository and building the image&lt;/li&gt;
&lt;li&gt;Deploying - deploying our image&lt;/li&gt;
&lt;li&gt;Releasing - release command is being executed&lt;/li&gt;
&lt;li&gt;Cleaning up - your server is cleaning up obsolete docker image layers to free up some disk space.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_iyV1u6c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_iyV1u6c.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you click "View logs" you will see the password somewhere in the end of the logs window.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_nFwmozh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_nFwmozh.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's open our app. In the Application navigation click "Open App". Your site will open in new window.&lt;/p&gt;

&lt;p&gt;You should see "Not Found".&lt;/p&gt;

&lt;p&gt;It is expected because we don't have any pages defined.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_NOGG37d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_NOGG37d.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you get 502 Gateway error, then you forgot to enable the "Web" process. Go back to the "Processes" tab and enable Web worker.&lt;/p&gt;

&lt;p&gt;Then on the overview page click "Apply Processes &amp;amp; Env Vars" button. This should apply changes faster than the whole build.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_6dinzQB.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_6dinzQB.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you see 400 Bad Request error then you didn't spell the domain name correctly in env var &lt;code&gt;DJANGO_ALLOWED_HOSTS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To fix it go to application "Settings" tab and edit the value for &lt;code&gt;DJANGO_ALLOWED_HOSTS&lt;/code&gt; to match the domain.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_739mnmS.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_739mnmS.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_EPfS5yI.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_EPfS5yI.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If there is any other error, try redeploying the app and watching logs for anything error-related. There is also application "Log" tab that can help you figure out what can be the problem.&lt;/p&gt;

&lt;p&gt;Now let's go to admin interface of your Django Project.&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;/admin&lt;/code&gt; to the end of your webiste.&lt;/p&gt;

&lt;p&gt;You will see Django Login form.&lt;/p&gt;

&lt;p&gt;Enter &lt;code&gt;admin@example.com&lt;/code&gt; as the login. Password is the one generated, that you saw in Deployment logs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_L66X35t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_L66X35t.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hit the "Log In" button.&lt;/p&gt;

&lt;p&gt;You should see the Django admin panel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_nQzf5yQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fappliku-serverless-static.s3.eu-central-1.amazonaws.com%2Fmedia%2Fpost_images%2Fimage_nQzf5yQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congratulations!&lt;/p&gt;

&lt;p&gt;You have just created your very first Django app and deployed it on Appliku Deploy. All that without learning anything devops: nginx, certificates, etc. All is done for you.&lt;/p&gt;

&lt;p&gt;New articles coming soon, you will be able to expand functionality of your Django Project with sending emails, accepting payments and building a proper SaaS product.&lt;/p&gt;

&lt;p&gt;Happy deploying!&lt;/p&gt;

</description>
      <category>django</category>
      <category>docker</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Stop deployment</title>
      <dc:creator>Kostja Appliku.com</dc:creator>
      <pubDate>Sun, 04 Oct 2020 03:46:09 +0000</pubDate>
      <link>https://dev.to/kostjapalovic/stop-deployment-plg</link>
      <guid>https://dev.to/kostjapalovic/stop-deployment-plg</guid>
      <description>&lt;p&gt;I am working on Appliku, which is building, and deployment automation SaaS. &lt;/p&gt;

&lt;p&gt;Read the story: &lt;a href="https://dev.to/kostjapalovic/tired-of-deployments-built-my-own-heroku-47ed"&gt;https://dev.to/kostjapalovic/tired-of-deployments-built-my-own-heroku-47ed&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is built with Django and heavily uses Celery.&lt;/p&gt;

&lt;p&gt;One of the most important parts that I want to improve is the build/deployment process. More specifically being able to manage running build/deploy jobs.&lt;/p&gt;

&lt;p&gt;What problems I see right now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deployments run as long as they want&lt;/li&gt;
&lt;li&gt;There is no way to stop/cancel the deployment&lt;/li&gt;
&lt;li&gt;If the app has a push-to-deploy setting On then several deployments will be running at the same time and this can exhaust the server's resources and lead to errors due to concurrent deployments&lt;/li&gt;
&lt;li&gt;I want to disable the "Deploy Now" button while there are deployments in the queue&lt;/li&gt;
&lt;li&gt;by the way, I want deployments to be in the queue with only one running&lt;/li&gt;
&lt;li&gt;If a deployment is stuck or taking a significant amount of time to complete this will effectively block the user from deploying the next version of the code which might have fixes for the underlying problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With all said, it boils done one thing right now: I need a way to stop a deployment job.&lt;/p&gt;

&lt;p&gt;How Appliku runs the build/deployment process?&lt;/p&gt;

&lt;p&gt;There is a Celery task, that connects to the server using the Paramiko library.&lt;/p&gt;

&lt;p&gt;The actual code of the function is this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;What I want to do is to somehow pass the signal from the dashboard to stop this. The function will have to kill the command running and close the connection, mark the deployment as canceled.&lt;br&gt;
Also, it must have timeouts in case if any of the stages are stuck for any reason.&lt;/p&gt;

&lt;p&gt;I use Redis cache to store the abort flag for deployment.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Now I only need to add the "Abort Deployment" button in the interface of &lt;a href="https://appliku.com/" rel="noopener noreferrer"&gt;https://appliku.com/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>django</category>
      <category>celery</category>
      <category>appliku</category>
      <category>deployment</category>
    </item>
    <item>
      <title>Tired of Deployments, built my own Heroku</title>
      <dc:creator>Kostja Appliku.com</dc:creator>
      <pubDate>Sun, 16 Aug 2020 07:58:59 +0000</pubDate>
      <link>https://dev.to/kostjapalovic/tired-of-deployments-built-my-own-heroku-47ed</link>
      <guid>https://dev.to/kostjapalovic/tired-of-deployments-built-my-own-heroku-47ed</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article is brought to you by the first deployment service, dedicated specifically to Python &amp;amp; Django &lt;a href="https://appliku.com" rel="noopener noreferrer"&gt;Appliku.com&lt;/a&gt;.&lt;br&gt;
Never manage a server again.&lt;br&gt;
Deploy your Django app in 5 minutes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; I got tired of deploying my Django projects, went to Heroku, and got burnt by their invoices, built my own SaaS to deploy apps to existing clouds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Story begins with custom software development agency
&lt;/h2&gt;

&lt;p&gt;6 Years ago I started yet another agency where we were building custom solutions for businesses that needed automate time-consuming tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Semi-automated task distribution for a large service company,&lt;/li&gt;
&lt;li&gt;Big e-commerce businesses,&lt;/li&gt;
&lt;li&gt;Generation of construction documentation&lt;/li&gt;
&lt;li&gt;Many smaller projects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It was an interesting journey.&lt;/p&gt;

&lt;p&gt;I loved what we've built. The money we saved. Manual labor we got rid of(well, not everyone was happy about the last part of course).&lt;/p&gt;

&lt;p&gt;It lasted for 5 years and I loved most of what we had to do.&lt;/p&gt;

&lt;p&gt;But one thing was annoying every single time.&lt;/p&gt;

&lt;p&gt;Every now and then I needed to spin up a new project and set up a test environment and then production one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment was a pain every time
&lt;/h2&gt;

&lt;p&gt;During that time I have experimented with different deployment approaches and tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Used Fabfile,&lt;/li&gt;
&lt;li&gt;Added CI/CD – first Atlassian Bamboo, then GitLab&lt;/li&gt;
&lt;li&gt;Used Supervisord, then Docker,&lt;/li&gt;
&lt;li&gt;Bare-metal servers, Digital Ocean, Linode.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At some point, I came up with a number of scripts that used docker-compose and GitLab CI to start deployment on new commits pushed to the repository.&lt;/p&gt;

&lt;p&gt;But for each new project, a lot of manual work had to be done, it was human-error prone and took a full day to set everything up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.gitlab-ci.yml&lt;/li&gt;
&lt;li&gt;docker-compose.yml&lt;/li&gt;
&lt;li&gt;set up Nginx and let's encrypt&lt;/li&gt;
&lt;li&gt;databases&lt;/li&gt;
&lt;li&gt;list of small things goes on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I loved it when a new client project came up, but I feared going over that process again and again.&lt;/p&gt;

&lt;p&gt;I thought there has to be a better way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's give Heroku a try
&lt;/h2&gt;

&lt;p&gt;At that time we came up with a couple of our own side projects, where we use Heroku.&lt;/p&gt;

&lt;p&gt;It was such a pleasant experience. &lt;/p&gt;

&lt;p&gt;I was "WOW"-ing on every step.&lt;/p&gt;

&lt;p&gt;I literally spent zero time on anything DevOps.&lt;/p&gt;

&lt;p&gt;I moved a couple more projects there, that was easy. None of those projects were generating any revenue since it was hobby sites.&lt;/p&gt;

&lt;p&gt;Then the time came and I received SMS from the bank about Heroku charging my card. At that moment all excitement went away.&lt;/p&gt;

&lt;h3&gt;
  
  
  Here comes Heroku Invoice
&lt;/h3&gt;

&lt;p&gt;In 2 months Heroku spendings climbed from $15/mo up to $80/mo. For hobby projects.&lt;/p&gt;

&lt;p&gt;I have immediately disabled those sites. One I moved off, as the only one that we were interested in.&lt;/p&gt;

&lt;p&gt;This coincided with a drastic change in my personal financial situation. I was completely broke for several months. &lt;/p&gt;

&lt;h2&gt;
  
  
  Getting back on track, seeking for the idea
&lt;/h2&gt;

&lt;p&gt;When I solved my financial issues, I had time to think about what to do next.&lt;/p&gt;

&lt;p&gt;We have finished with custom software development. &lt;/p&gt;

&lt;p&gt;What was left after doing that business clearly was the pain from deployments and pain from paying for Heroku.&lt;/p&gt;

&lt;p&gt;I wanted to get back to some of our hobby projects.&lt;/p&gt;

&lt;p&gt;I had time to make it the right way and enjoy doing it.&lt;/p&gt;

&lt;p&gt;I wanted something as pleasant as Heroku, but I didn't want to pay that much for that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Light bulb
&lt;/h2&gt;

&lt;p&gt;It became clear to me: I need to build my own Heroku.&lt;/p&gt;

&lt;p&gt;At that time on IndieHackers, I stumbled upon Hatchbox.io for Ruby and heard about Laravel Forge for PHP.&lt;/p&gt;

&lt;p&gt;I treated that as validation for a similar tool but for Python/Django.&lt;/p&gt;

&lt;p&gt;I came up with a name for it a month later: Appliku.&lt;/p&gt;

&lt;p&gt;I started from bash scripts for building Docker, spun up a Django up to manage them, and iterated on that for 17 months to this moment (Sun, 16th Aug 2020).&lt;/p&gt;

&lt;h2&gt;
  
  
  What do I have now?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Automated provisioning of servers on both AWS and DigitalOcean&lt;/li&gt;
&lt;li&gt;Server setup (Docker, Nginx, Let's Encrypt)&lt;/li&gt;
&lt;li&gt;Building apps from the source code on GitHub Repo&lt;/li&gt;
&lt;li&gt;An almost as good interface as Heroku&lt;/li&gt;
&lt;li&gt;Heroku Config Vars Sync for those who are trying to move off Heroku step by step.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is under the hood?
&lt;/h2&gt;

&lt;p&gt;Django application is the core of the product.&lt;/p&gt;

&lt;p&gt;The frontend is built with Angular based on mdbootstrap.com Angular Package.&lt;/p&gt;

&lt;p&gt;Tons of background tasks run by Celery.&lt;/p&gt;

&lt;p&gt;When a user requests creation of a new server – Appliku talks via API to Digital Ocean or AWS to provision it.&lt;/p&gt;

&lt;p&gt;When Server is provisioned, with help of the Paramiko module Appliku connects to the server over SSH and runs the setup script.&lt;/p&gt;

&lt;p&gt;When the setup is complete – you can deploy your app.&lt;/p&gt;

&lt;p&gt;App creation consists of naming your app, picking repository and branch, and the server you want to deploy to.&lt;/p&gt;

&lt;p&gt;The only requirement for the app is having a Procfile.&lt;/p&gt;

&lt;p&gt;I figured that since I was moving off of Heroku, there must be others who do the same. And I liked this idea of having processes in the code itself rather than specifying them in UI.&lt;/p&gt;

&lt;p&gt;Procfile is a text file where you specify what processes are needed to be running for your app to operate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;web: python gunicorn wsgi:app
release: python manage.py migrate
celery: celery worker -Q default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"web" tells us that this process should accept HTTP connections.&lt;/p&gt;

&lt;p&gt;"release" is what get's executed on every release.&lt;/p&gt;

&lt;p&gt;Other processes have no special meaning.&lt;/p&gt;

&lt;p&gt;Appliku asks what programming language you use to run the app. Right now supported languages are Python, PYPY, PHP, and Node.&lt;/p&gt;

&lt;p&gt;For complex builds, we allow writing your own Dockerfile.&lt;/p&gt;

&lt;p&gt;Heroku Sync is a feature that pulls config vars of the app from Heroku API every minute and updates config vars for the related app in Appliku and rebuilds it.&lt;/p&gt;

&lt;p&gt;This has proven to be useful for those who moved part of Heroku Dynos to Digital Ocean with the purpose of reducing expenses. Usually, this is done with memory-hungry background workers.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you hit Deploy.
&lt;/h2&gt;

&lt;p&gt;Appliku makes your server to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;go to your GitHub repo&lt;/li&gt;
&lt;li&gt;clone the code&lt;/li&gt;
&lt;li&gt;create docker-compose file(based on Procfile and processes you enabled to run)&lt;/li&gt;
&lt;li&gt;build the image&lt;/li&gt;
&lt;li&gt;start it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, it requests Let's Encrypt SSL certificate for appname.applikuapp.com domain or any other custom domain you have added.&lt;/p&gt;

&lt;p&gt;By making your server do that we ensure that all your code is only stored on your server. The only piece of information from your code we store is the contents of the Procfile.&lt;/p&gt;

&lt;p&gt;After the app is deployed it runs without much of the interaction with our app.&lt;/p&gt;

&lt;p&gt;If you push new commits - we run the build/deploy process.&lt;/p&gt;

&lt;p&gt;Every day we run certbot to check if any of the certificates are about to expire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Couple words about Nginx configuration.
&lt;/h2&gt;

&lt;p&gt;Nginx vhost is setup in a way that it respects cache headers from your app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proxy_cache_path /home/app/_cache/sitename.com levels=1:2 keys_zone=sitename.com:10m max_size=1g inactive=60m use_temp_path=off;
location / {
        proxy_cache sitename.com;
        proxy_cache_revalidate on;
        proxy_cache_min_uses 1;
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
        proxy_cache_background_update on;
        proxy_cache_lock on;
        proxy_cache_bypass $cookie_nocache ;
        add_header X-Cache-Status $upstream_cache_status;

        proxy_pass http://127.0.0.1:8003;
        proxy_read_timeout 600;
        proxy_send_timeout 600;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way you can use dummy caching on the application side (like Dummy Cache Backend in Django) in order to generate proper cache headers and the actual caching will happen before the request reaches the backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts.
&lt;/h2&gt;

&lt;p&gt;I have built this whole thing alone with the help of my wife that took ownership of most non-engineering tasks.&lt;/p&gt;

&lt;p&gt;It is a fun thing to work on. I have learned a lot about Docker, Nginx, Paramiko, Django itself.&lt;/p&gt;

&lt;p&gt;I think that this app can help many people besides me to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ship their idea faster&lt;/li&gt;
&lt;li&gt;avoid doing DevOps&lt;/li&gt;
&lt;li&gt;solve the frustration for beginners "I built the app, how do I publish it?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Business-model is simple – you pay Appliku fixed fee for service and pay directly to cloud providers for their services.&lt;/p&gt;

&lt;p&gt;There is a free plan on Appliku and AWS gives 1 year for new users.&lt;/p&gt;

&lt;p&gt;If you are learning to code or seeking for a (mostly) free and enjoyable way to run your app – I really hope in these 17 months I built a solution that can give you that.&lt;/p&gt;

&lt;h2&gt;
  
  
  P.S.
&lt;/h2&gt;

&lt;p&gt;I really hope you that hearing about my journey and what I built was interesting and doesn't look like a promotional thing.&lt;/p&gt;

&lt;p&gt;It was a really interesting journey, full of excitement, frustration (Hello, AWS Docs!), and the idea of making someone's work easier.&lt;/p&gt;

&lt;p&gt;I shared all this here to tell that even big and complex things can be done if you believe in it and if you show up every day (even for 30 minutes) and persistently work on it.&lt;/p&gt;

&lt;p&gt;If the app helps someone or the article itself motivates somebody to get up and create something – I will be extremely happy.&lt;/p&gt;

&lt;p&gt;Thanks for taking time to read this.&lt;/p&gt;

&lt;h2&gt;
  
  
  P.P.S.
&lt;/h2&gt;

&lt;p&gt;Nearly forgot about the link to the app: &lt;a href="https://appliku.com/" rel="noopener noreferrer"&gt;https://appliku.com/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>python</category>
      <category>django</category>
      <category>docker</category>
    </item>
    <item>
      <title>CookieCutter based project generator on the web</title>
      <dc:creator>Kostja Appliku.com</dc:creator>
      <pubDate>Mon, 26 Feb 2018 11:44:42 +0000</pubDate>
      <link>https://dev.to/kostjapalovic/cookiecutter-based-project-generator-on-the-web--5d67</link>
      <guid>https://dev.to/kostjapalovic/cookiecutter-based-project-generator-on-the-web--5d67</guid>
      <description>&lt;p&gt;I have recently started a few Django projects, mostly to play around with some new tech and automate some of my workflows.&lt;/p&gt;

&lt;p&gt;Then I thought, that there is &lt;a href="https://github.com/audreyr/cookiecutter" rel="noopener noreferrer"&gt;cookiecutter&lt;/a&gt; project that should automate most part of project creation.&lt;/p&gt;

&lt;p&gt;While this is a great and very useful tool, I don't like to answer questions line by line in terminal to generate a project.&lt;/p&gt;

&lt;p&gt;I wanted a web interface for that.&lt;/p&gt;

&lt;p&gt;In several hours I came up with a solution and it resides here: &lt;a href="https://generator.kpavlovsky.pro/" rel="noopener noreferrer"&gt;Generator.KPavlovsky.pro&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Python and Python-Django projects are included in available templates. After feeling form with questions (taken from templates' cookiecutter.json) and clicking &lt;code&gt;Generate&lt;/code&gt;, you get ZIP archive with your new project.&lt;/p&gt;

&lt;p&gt;Wish it will be useful for at least somebody. &lt;/p&gt;

&lt;p&gt;Feedback is welcome.&lt;/p&gt;

&lt;p&gt;Contact me here or on twitter to provide feedback and/or feature requests.&lt;/p&gt;

&lt;p&gt;@all, have a great day!&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>cookiecutter</category>
      <category>scaffolding</category>
    </item>
    <item>
      <title>Setting up tests in GitLab CI for Django project with Docker Engine</title>
      <dc:creator>Kostja Appliku.com</dc:creator>
      <pubDate>Thu, 16 Feb 2017 18:51:32 +0000</pubDate>
      <link>https://dev.to/kostjapalovic/setting-up-tests-in-gitlab-ci-for-django-project-with-docker-engine</link>
      <guid>https://dev.to/kostjapalovic/setting-up-tests-in-gitlab-ci-for-django-project-with-docker-engine</guid>
      <description>&lt;p&gt;Hello!&lt;/p&gt;

&lt;p&gt;In this article I will describe how we set up GitLab CI to run tests for Django project.&lt;/p&gt;

&lt;p&gt;But first a couple of words about what tools we were using.&lt;/p&gt;

&lt;h3&gt;
  
  
  What we had before GitLab – Atlassian
&lt;/h3&gt;

&lt;p&gt;For a few years we were using Atlassian stack for our development process. We had JIRA, Confluence, Stash(now it is called BitBucket Server) and Bamboo.&lt;/p&gt;

&lt;p&gt;At first we were happy with that setup, because all apps had good integration with each other.&lt;/p&gt;

&lt;p&gt;We could see all commits related to currently open issues, and we could see what issues and commits were included in build in Bamboo.&lt;/p&gt;

&lt;p&gt;It looked wonderful at first, but after some time we noticed that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  JIRA began consuming all our time even with Agile Board&lt;/li&gt;
&lt;li&gt;  keeping this stack up-to-date was a huge pain. I'd spend whole weekends updating and repairing all four apps, because they'd changed requirements for Mysql settings, and logs in Atlassian products are buried deep in various directories&lt;/li&gt;
&lt;li&gt;  Every new version of Atlassian apps was introducing numerous bugs and interface changes (which with obvious lack of QA was causing even more bugs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plus we only had a 16GB RAM server for all this tools.&lt;/p&gt;

&lt;p&gt;So instead of doing our job, we were spending all our time dealing with JIRA.&lt;/p&gt;

&lt;p&gt;New developers who were joining our team were frustrated with the JIRA interface.&lt;/p&gt;

&lt;p&gt;At the end of last year we thought: “That's enough, we need to replace this enterprise-focused monster with something easier and productive tool.”&lt;/p&gt;

&lt;p&gt;At first, we started looking for a replacement for task tracking functionality. We tried many apps and services for that, but all of them, while having some strong features, also had issues or lack of functionality which prevented us from being productive.&lt;/p&gt;

&lt;p&gt;Then I tried GitLab and suddenly we not only found the solution for convenient task tracking tool, but we found how to replace the whole Atlassian stack.&lt;/p&gt;

&lt;p&gt;What is also is amazing we get everything for free! (Except worker, but i have unused linux box, which now serves as worker).&lt;/p&gt;

&lt;p&gt;GitLab has a clean and simple interface, instead of the enterprise-crazy workflows of Jira, issues have labels, there is CI pipelines, Wiki, markdown for issue descriptions, comments and wiki articles, and many other great things.&lt;/p&gt;

&lt;h3&gt;
  
  
  We have switched to GitLab.com
&lt;/h3&gt;

&lt;p&gt;GitLab projects solved all our problemsâ€Š–â€Šno more maintenance, no pain with productivity-killing interface, all in one solution.&lt;/p&gt;

&lt;p&gt;So gitlab project provides git repository hosting, issue tracker, wiki, CI/CD management system. But what we need is CI runner. Runner does actual job, executing building, testing and deployment jobs.&lt;/p&gt;

&lt;p&gt;As I already said, I had unused linux box, which now is used for runner.&lt;/p&gt;

&lt;p&gt;Runner installation for ubuntu described here:&lt;br&gt;&lt;br&gt;
&lt;a href="https://docs.gitlab.com/runner/install/linux-repository.html" rel="noopener noreferrer"&gt;https://docs.gitlab.com/runner/install/linux-repository.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We used docker for running our builds.&lt;/p&gt;
&lt;h3&gt;
  
  
  Migration from Bamboo to GitLab CI/CD
&lt;/h3&gt;

&lt;p&gt;In Bamboo build and deploy plans are setup from GUI.&lt;/p&gt;

&lt;p&gt;For gitlab &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; must be created in root of git repository.&lt;/p&gt;

&lt;p&gt;Before I provide example of this file, I must point out that we use Postgres as a database. Thanks to docker we can require runner to run it as service with credentials from this file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.gitlab-ci.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;image: kpavlovsky/backoffice:latest  

services: 
  - postgres:latest

stages: 
  - test  
  - deploy

variables:  
  POSTGRES_DB: dev  
  POSTGRES_USER: dev  
  POSTGRES_PASSWORD:dev  

all_tests:  
  stage: test  
  script: 
  - bash ./tests_gitlab.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Line by line:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;image: kpavlovsky/backoffice:latest&lt;/code&gt; is docker image we use to run container.&lt;/p&gt;

&lt;p&gt;This image is based on python:3-onbuild. We moved all long-running pip install and apt-get tasks there. By doing this we achieved two things: 1) each build runs faster, because it doesn't involve packages installation 2) we do not abuse apt and pip repositories by downloading packages 10s or 100s time per day 3) we decrease time of each build so we get results of tests much faster (who wants to wait for 15 minutes after each &lt;code&gt;git push&lt;/code&gt; ?).&lt;/p&gt;

&lt;p&gt;In services docker images for services are listed. Here we have only postgres.&lt;/p&gt;

&lt;p&gt;In variables section sets up postgres database and its credentials. Hostname for postgres will be â€˜postgres'.&lt;/p&gt;

&lt;p&gt;The ordering of elements in &lt;code&gt;stages&lt;/code&gt; defines the ordering of builds' execution:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Builds of the same stage are run in parallel.&lt;/li&gt;
&lt;li&gt; Builds of the next stage are run after the jobs from the previous stage complete successfully.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Job in â€˜test' stage goes first. Jobs in â€˜deploy' stage goes after â€˜test' stage.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;all_tests&lt;/code&gt; is a job our of pipeline, belonging to â€˜test' stage. &lt;code&gt;script&lt;/code&gt; hold all commands that will be issued. We have only one command hereâ€Š–â€Što run tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash  &lt;/span&gt;
coverage run &lt;span class="nt"&gt;--source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"app1,app2,app3"&lt;/span&gt; manage.py &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--noinput&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt;
&lt;span class="nv"&gt;testrunner&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"xmlrunner.extra.djangotestrunner.XMLTestRunner"&lt;/span&gt; 
app1.tests app2.tests app3.tests  
coverage report &lt;span class="nt"&gt;--skip-covered&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dockerfile for that separate &lt;code&gt;kpavlovsky/backoffice:latest&lt;/code&gt;image looks this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM python:3-onbuild  
ENV PYTHONUNBUFFERED 1  
ENV PYTHONDONTWRITEBYTECODE 0  
ENV DJANGO_SETTINGS_MODULE project.settings.docker  
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y --force-yes mc nano htop python python-pip netcat gettext &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*  
RUN mkdir /code  
WORKDIR /code  
COPY requirements.txt /code/  
RUN pip install --upgrade pip  
RUN pip install -r requirements.txt  
CMD ["bash"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So now if we push code to repo and tests pass, then we see notification in slack and email that pipeline is successful or that pipeline is failed if tests fail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment
&lt;/h3&gt;

&lt;p&gt;Now that we migrated the build part, we need to deploy our project in case of successful build on â€˜dev' branch to staging.&lt;/p&gt;

&lt;p&gt;â€˜Stage' environment is another linux box, without docker, just supervisor and gunicorn.&lt;/p&gt;

&lt;p&gt;Deployment process involves ssh-ing to remote box from runner, activating virtualenv, git pulling and running django management commands.&lt;/p&gt;

&lt;p&gt;First step is add job in â€˜deploy' stage in our &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deploy_stage:  
  stage: deploy  
  script: - bash ./deploy_stage_gitlab.sh  
  when: on_success  
  only: - dev&amp;lt;/pre&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This job will only be run on â€˜dev' branch and only if â€˜test' stage is successful.&lt;/p&gt;

&lt;p&gt;To ssh to â€˜stage' machine we need to transfer ssh keys to runner.&lt;/p&gt;

&lt;p&gt;Storing keys in repository is bad practice.&lt;/p&gt;

&lt;p&gt;Thanks to gitlab â€˜Variables' we can transfer keys via environment variables, write them to files and then issue fabric command to execute required tasks on â€˜stage' box.&lt;/p&gt;

&lt;p&gt;First we need to generate ssh key without passphrase. For this purpose, use &lt;code&gt;ssh-keygen&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;Public key must be put in &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; on stage server.&lt;/p&gt;

&lt;p&gt;Then we put contents of public and private keys to Variables.&lt;/p&gt;

&lt;p&gt;After adding variables with keys, Variables screen in gitlab project looks similar to this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2AtFy25Ljss0uW8hKCiIvanQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2AtFy25Ljss0uW8hKCiIvanQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;deploy_stage_gitlab.sh&lt;/code&gt; looks this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/usr/bin/env bash
mkdir ~/.ssh/  
echo $STAGE_PRIVATE_KEY &amp;gt; ~/.ssh/id_rsa  
echo $STAGE_PUBLIC_KEY &amp;gt; ~/.ssh/id_rsa.pub  
fab upd_dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quick note: use fabric3 in python3.5 environment!&lt;/p&gt;

&lt;p&gt;fabric function logs in to remote server, git pull everything, runs migrations and uses supervisorctl to restart process group for this project.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href="https://coffeeonthekeyboard.com/using-supervisorctl-with-linux-permissions-but-without-root-or-sudo-977/" rel="noopener noreferrer"&gt;allowing non-root user to user supervisorctl&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;.gitlab_ci.yml&lt;/code&gt; we have ability to change pipeline, so it reflects changes in our code. That was harder to achieve with Bamboo and its build/deploy settings in GUI. Of course, we could let some bash-script to do all tasks and it would be changed from commit to commit. In this case it is impossible to add/remove/change stages, etc.&lt;/p&gt;

&lt;p&gt;Also Bamboo's guy build/deploy setup requires much more time to set up, I didn't find a way to clone it from project to project, which is very easy to do with &lt;code&gt;.gitlab_ci.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;From now, we don't need a separate large server for Atlassian tools, we don't spend weekends updating them. And the best part: we can focus on doing real jobs, developing and delivering OUR apps, instead of wasting time on Atlassian's.&lt;/p&gt;

&lt;p&gt;Happy developing!&lt;/p&gt;

&lt;p&gt;If you have any ideas about gitlab CI, ideas how to improve described workflow – post comments, i will be very happy to hear and discuss them!&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
