<?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: Tobi-De</title>
    <description>The latest articles on DEV Community by Tobi-De (@tobi).</description>
    <link>https://dev.to/tobi</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%2F415817%2F21dfbc3d-024d-4b4b-a9b0-999476799073.png</url>
      <title>DEV Community: Tobi-De</title>
      <link>https://dev.to/tobi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tobi"/>
    <language>en</language>
    <item>
      <title>Task queues and schedulers in django</title>
      <dc:creator>Tobi-De</dc:creator>
      <pubDate>Fri, 19 Jan 2024 20:34:23 +0000</pubDate>
      <link>https://dev.to/tobi/task-queues-and-schedulers-in-django-101b</link>
      <guid>https://dev.to/tobi/task-queues-and-schedulers-in-django-101b</guid>
      <description>&lt;p&gt;This article has been excerpted from a series of guides crafted for my project, Falco. You can explore the complete set of guides at &lt;a href="https://falco.oluwatobi.dev/index.html" rel="noopener noreferrer"&gt;falco.oluwatobi.dev&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Task queues
&lt;/h2&gt;

&lt;p&gt;Task queues are used to offload tasks to a dedicated worker process when the processing of those tasks does not fit into a traditional request-response cycle. In other words, if you need to do something that might take too long to process and whose result does not need to be shown immediately to the user, you use a queue manager.&lt;/p&gt;

&lt;p&gt;Think of it as your Django application having an assistant. When a task is too time-consuming to be handled instantly, it's assigned to the assistant for completion, allowing your app to continue functioning normally and serving your clients. Once the assistant completes the task, it returns the result to you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&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%2Ffalco.oluwatobi.dev%2F_images%2Ftask_queue.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%2Ffalco.oluwatobi.dev%2F_images%2Ftask_queue.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; A user uploads a large excel file to your web application for
processing.&lt;/li&gt;
&lt;li&gt; The Django app immediately returns a &lt;strong&gt;processing...&lt;/strong&gt; message to
avoid blocking.&lt;/li&gt;
&lt;li&gt; The Django project adds the task to a broker
(&lt;a href="https://redis.io/" rel="noopener noreferrer"&gt;redis&lt;/a&gt;, &lt;a href="https://www.rabbitmq.com/" rel="noopener noreferrer"&gt;rabbitmq&lt;/a&gt;,
database, etc.), a service used to store tasks that need processing.&lt;/li&gt;
&lt;li&gt; Another process, the worker, retrieves the task from the broker and
processes it.&lt;/li&gt;
&lt;li&gt; The worker process the task.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is a final optional step not shown in the diagram to avoid clutter. In this step, through some form of callback mechanism, the worker notifies the Django app that it has completed its work and sends back the result. How this callback mechanism works depends on the tool you choose for the&lt;br&gt;
task queue implementation.&lt;/p&gt;
&lt;h2&gt;
  
  
  Task scheduling
&lt;/h2&gt;

&lt;p&gt;Schedulers are used to periodically execute tasks. They assist in scheduling tasks to run at specific times or intervals. For instance, if you need to send an email to your users every day at 8:00 AM, a scheduler can be used for this purpose. While this can also be achieved using a cron job, which is a common approach, most task queues also provide a scheduler. If task scheduling is all you need, a simple and straightforward option is to use &lt;a href="https://cronitor.io/guides/cron-jobs" rel="noopener noreferrer"&gt;cron jobs&lt;/a&gt; in combination with custom &lt;a href="https://docs.djangoproject.com/en/5.0/howto/custom-management-commands/" rel="noopener noreferrer"&gt;django management commands&lt;/a&gt; (or &lt;a href="https://django-extensions.readthedocs.io/en/latest/jobs_scheduling.html" rel="noopener noreferrer"&gt;job scheduling from django-extensions&lt;/a&gt;). The Django management command would contain the code to, for example, send the email to users, and the crontab would execute the command at&lt;br&gt;
the specified schedule.&lt;/p&gt;
&lt;h2&gt;
  
  
  Popular packages options
&lt;/h2&gt;

&lt;p&gt;There are many options available in the &lt;a href="https://djangopackages.org/grids/g/workers-queues-tasks/" rel="noopener noreferrer"&gt;django third-party&lt;br&gt;
ecosystem&lt;/a&gt;, some focus solely on providing a task queue, others are just schedulers and many of them provide both in one package. You can also search for purely python solutions and integrate them into your django project yourself.&lt;/p&gt;

&lt;p&gt;I prefer options that do not require additional infrastructure (redis, rabbitmq, etc.) for simple tasks. For instance, solutions that can leverage any existing database setup I have. For more complex tasks, I tend to choose a solution that supports redis as a task broker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My task queue of choice&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My current pick is &lt;strong&gt;django-q2&lt;/strong&gt;. It is a fork of the&lt;br&gt;
original &lt;strong&gt;django-q&lt;/strong&gt; project, which is no longer maintained. Here is an example of using &lt;code&gt;django-q2&lt;/code&gt; to run a task in background:&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_q.tasks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;async_task&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;long_running_task&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="bp"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_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;task_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;async_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;long_running_task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&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&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="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;async_task&lt;/code&gt; function returns instantly, as it doesn't directly execute the &lt;code&gt;long_running_task&lt;/code&gt; function. Instead, it delegates the function to a worker for execution. To initiate this worker process (which also includes a scheduler), you need to open a new terminal and execute the following command:&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 qcluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Couldn't get much easier than this, right? :)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Repository URL&lt;/th&gt;
&lt;th&gt;Task Queue&lt;/th&gt;
&lt;th&gt;Scheduler&lt;/th&gt;
&lt;th&gt;Requires External Service?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;django-q2&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/GDay/django-q2" rel="noopener noreferrer"&gt;https://github.com/GDay/django-q2&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wakaq&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wakatime/wakaq" rel="noopener noreferrer"&gt;https://github.com/wakatime/wakaq&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;django-pgpubsub&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Opus10/django-pgpubsub" rel="noopener noreferrer"&gt;https://github.com/Opus10/django-pgpubsub&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rocketry&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Miksus/rocketry" rel="noopener noreferrer"&gt;https://github.com/Miksus/rocketry&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;django-dramatiq&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Bogdanp/django_dramatiq" rel="noopener noreferrer"&gt;https://github.com/Bogdanp/django_dramatiq&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;django-rq&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/rq/django-rq" rel="noopener noreferrer"&gt;https://github.com/rq/django-rq&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/dsoftwareinc/django-rq-scheduler" rel="noopener noreferrer"&gt;https://github.com/dsoftwareinc/django-rq-scheduler&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;procrastinate&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/procrastinate-org/procrastinate" rel="noopener noreferrer"&gt;https://github.com/procrastinate-org/procrastinate&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;django-chard&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/drpancake/chard" rel="noopener noreferrer"&gt;https://github.com/drpancake/chard&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;celery&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/celery/celery" rel="noopener noreferrer"&gt;https://github.com/celery/celery&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Auto reload in development&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you are using one of these you might want an automatic reload feature when files changes in dev, you can use the &lt;code&gt;hupper&lt;/code&gt; python package for that purpose. It watches for file changes in the current directory and restarts the worker process automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hupper &lt;span class="nt"&gt;-m&lt;/span&gt; django_q.cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Basic django-q2 configuration
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Using the database as broker&lt;/strong&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="n"&gt;Q_CLUSTER&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;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;DjangORM&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;workers&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;queue_limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bulk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orm&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;default&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;catch_up&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Using redis as broker&lt;/strong&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="n"&gt;CACHES&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;cache&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="c1"&gt;# This configuration assumes that Redis is configured for caching in a similar way as described above.
&lt;/span&gt;&lt;span class="n"&gt;Q_CLUSTER&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;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;DJRedis&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;workers&lt;/span&gt;&lt;span class="sh"&gt;'&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;timeout&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90&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_redis&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;default&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;h2&gt;
  
  
  Deployment with a task queue
&lt;/h2&gt;

&lt;p&gt;Deploying a Django project that uses a task queue is not as&lt;br&gt;
straightforward, but still relatively simple. At this point, I hope you've understood that running a task queue or task schedulers implies running another process (the worker) in addition to your django server. You can have one process for the task queues and another for the schedulers, but usually, with most packages, you can have both in one process with one command. For example, if you chose &lt;code&gt;django-q2&lt;/code&gt;, all you&lt;br&gt;
need to run is:&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 qcluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will enable both the task queue and scheduling&lt;br&gt;
capabilities. If you are running your Django app on a Linux server, the most common option is to have a process manager to run and manage both your Django server and the worker process, or any other processes your Django project needs. The two most popular options are &lt;a href="https://systemd.io/" rel="noopener noreferrer"&gt;systemd&lt;/a&gt; and&lt;br&gt;
&lt;a href="http://supervisord.org/" rel="noopener noreferrer"&gt;supervisord&lt;/a&gt;. Systemd is natively available on most Linux distributions, but you need to install Supervisor. In my experience, there are no real advantages of one over the other, so I&lt;br&gt;
would advise just picking one; either will be fine.&lt;/p&gt;

&lt;p&gt;Here are some basic configuration examples. Please note that the code provided only concerns the worker process.&lt;/p&gt;
&lt;h3&gt;
  
  
  Systemd example
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Unit]
Description=Your Django Qcluster Worker

[Service]
WorkingDirectory=/path/to/your/project
ExecStart=/path/to/your/venv/bin/python manage.py qcluster
User=your_username
Group=your_groupname
Restart=always
StandardOutput=append:/var/log/your_project/qcluster.out.log
StandardError=append:/var/log/your_project/qcluster.err.log

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Supervisor example
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[program:your_project_qcluster]
command=/path/to/your/venv/bin/python manage.py qcluster
directory=/path/to/your/project
user=your_username
group=your_groupname
autostart=true
autorestart=true
stderr_logfile=/var/log/your_project_qcluster.err.log
stdout_logfile=/var/log/your_project_qcluster.out.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you are running your project with Docker, the process is the same.&lt;br&gt;
You need to have another Dockerfile in addition to your main one. This&lt;br&gt;
Dockerfile is practically identical, but with the entry command running&lt;br&gt;
the worker process (e.g., &lt;code&gt;python manage.py qcluster&lt;/code&gt;) instead of your&lt;br&gt;
Django application server. There is also a simple alternative to run&lt;br&gt;
both the Django process and the worker in a single container. For more&lt;br&gt;
on that, read the guide on &lt;a href="https://falco.oluwatobi.dev/guides/running_project_in_a_container.html" rel="noopener noreferrer"&gt;running your project in a single&lt;br&gt;
container&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On the other hand, if you are running your project on a platform as a service (PAAS), they usually have a way to declare a worker process. For example, Heroku (and most PAAS that use a Procfile) have a&lt;br&gt;
straightforward way to declare a worker process in the Procfile.&lt;/p&gt;

&lt;p&gt;Here is an example of what that looks like with Heroku:&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 myproject.wsgi
worker: python manage.py qcluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The End
&lt;/h2&gt;

&lt;p&gt;In conclusion, this guide aimed to provide enough information for you to&lt;br&gt;
understand and choose a task queue solution for your Django project, and&lt;br&gt;
to grasp its potential impact on your deployment process. For any&lt;br&gt;
questions or feedback, please open a&lt;br&gt;
&lt;a href="https://github.com/tobi-de/falco/discussions" rel="noopener noreferrer"&gt;discussion&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>async</category>
    </item>
    <item>
      <title>Falco - CRUD generator, project-starter and a set of guides for django devs</title>
      <dc:creator>Tobi-De</dc:creator>
      <pubDate>Mon, 25 Dec 2023 16:41:21 +0000</pubDate>
      <link>https://dev.to/tobi/falco-crud-generator-project-starter-and-a-set-of-guides-for-django-devs-e50</link>
      <guid>https://dev.to/tobi/falco-crud-generator-project-starter-and-a-set-of-guides-for-django-devs-e50</guid>
      <description>&lt;p&gt;Hi all,&lt;/p&gt;

&lt;p&gt;I've been working on a side project for the last few days that I would like to share with you. It is a rewrite of a previous project. I mentioned on the GitHub readme that it was a toolkit, but I'm not really sure how to describe it, so I'll just say what it does. There are two parts to the project: a CLI and a set of guides.&lt;/p&gt;

&lt;p&gt;The most noteworthy commands in the CLI are a project starter that generates a Django project setup with Hatch, htmx, and Tailwind, and a CRUD (Create, Read, Update, Delete) command generator that creates CRUD views based on a Django model. It is a similar idea to &lt;a href="https://github.com/carltongibson/neapolitan"&gt;Neapolitan&lt;/a&gt; but with a completely different approach. Instead of a class to inherit from, the CLI generates and inserts code for function-based views directly into your &lt;code&gt;views.py\&lt;/code&gt; file. It also generates some basic html templates with minimal Tailwind styling and registers URLs. The goal is faster prototyping. You write a model, and then you can easily get basic CRUD views to test things out. Updating the code is quite straightforward since it is directly available in your source code files.&lt;/p&gt;

&lt;p&gt;The second part is the Guides. You can think of it as the Django official &lt;a href="https://docs.djangoproject.com/en/5.0/topics/"&gt;topic guides&lt;/a&gt;, but instead of focusing on the components of the framework like forms, models, class-based views, etc., it is more focused on more general web subjects like realtime, task queues, deployment, etc. The idea is to have a place where you'll get general views on how to handle each topic, but from the perspective of a Django developer. Much of it is based on my own experience, but I'll also do some additional research since the idea is to get a general view and explore available options for handling each topic.&lt;/p&gt;

&lt;p&gt;All the available CLI commands and guides are listed on the GitHub readme.&lt;/p&gt;

&lt;p&gt;The project caters to a target audience ranging from beginners to intermediate developers, leaning more towards the latter. Please note that this project is actively under development. While the CLI is nearing completion, the guides are not yet halfway done.&lt;/p&gt;

&lt;p&gt;Finally, thank you for taking the time to read this, and Merry Christmas to all those who celebrate!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tobi-de/falco"&gt;https://github.com/tobi-de/falco&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The Art of Skimming Through Documentation</title>
      <dc:creator>Tobi-De</dc:creator>
      <pubDate>Wed, 20 Dec 2023 02:27:52 +0000</pubDate>
      <link>https://dev.to/tobi/the-art-of-skimming-through-documentation-31k1</link>
      <guid>https://dev.to/tobi/the-art-of-skimming-through-documentation-31k1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;TL;DR: The title is already self-explanatory,  but nonetheless: Skimming through well-written documentation is one of the best ways I know to discover new techniques and tools. It is easier and can sometimes be more effective than straining your brain attempting to decode someone else's code for the purpose of learning.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This could have been the perfect opportunity to begin with the classic: &lt;em&gt;This is something I wish I knew as a beginner&lt;/em&gt;. However, in my case, I believe I was already doing this even as a beginner. I don't recall where I picked it up, but I knew two things that might have pushed me toward that edge:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;There is a lot to understand in this tech stuff.&lt;/li&gt;
&lt;li&gt;Understanding any topic requires reading about it first. Reading may not be enough, but it is a prerequisite at least.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'm sure you've already heard countless times the classic &lt;em&gt;go read the docs advice&lt;/em&gt;. Some colleagues might have even shouted that to you. And I can reassure you, they were right. Reading documentation is crucial, but I'm here to advocate for something else, similar yet different and also complementary - &lt;strong&gt;skimming through the documentation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What do I mean by skimming? The basic workflow for most is as follows: use a tool, encounter a problem, Google for a solution (or refer to the documentation if you're more experienced and if it is applicable), and repeat the cycle. This approach works fine and you'll eventually become proficient with that tool after repeated use.&lt;br&gt;
But let's imagine this: the first time you use the tool, you skim through the docs. You speed run it, reading every chapter title and subtitle, and maybe even a few lines of each paragraph (if you have time and the doc isn't too long, you might read the whole thing). The first thing you'll gain from this is a sense of &lt;strong&gt;possibilities&lt;/strong&gt;. You'll get a general overview of what you can do with the tool, and a deeper one than if you had just read the readme or tutorial section. Who knows, perhaps while reading, you'll discover a new approach or pattern, or even a new tool(maybe a dependency of that project). I can give you two examples of when that happened to me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I gained a better understanding of concurrency in Python while reading the &lt;a href="https://fastapi.tiangolo.com/async/"&gt;FastAPI docs&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I discovered the PostgreSQL &lt;code&gt;LISTEN/NOTIFY&lt;/code&gt; protocol while reading the &lt;a href="https://procrastinate.readthedocs.io/en/stable/discussions.html#why-are-you-doing-a-task-queue-in-postgresql"&gt;Procrastinate documentation&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The patterns and tools that I discovered while exploring the deeper parts of tool documentation often bring me value later on.&lt;br&gt;
You can also view skimming through the documentation as a form of preliminary research. Doing so will assist you in acquiring enough background knowledge to make using the tool easier and, as they say, effortlessly &lt;em&gt;connecting the dots&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Small aside, this "A.I." trend is taking away the opportunity for a lot of people to appreciate well-written documentation. Copilot and ChatGPT are great for getting quick answers, but they usually keep you on a surface level of understanding (unless you continuously prompt them for deeper knowledge, which I doubt most people do). These tools are lowering the barrier to entry and increasing productivity for developers, but at the cost of a deeper understanding of the tools they are using. The best developers I know are the one with a deep knowledge in their craft. But who knows, maybe we will all be obsolete in a decade and none of this will matter.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A side benefit of skimming through documentation is increasing your chances for those moments when you think, &lt;em&gt;Haven't I read something on this somewhere?&lt;/em&gt; It's unlikely that you'll remember even half of what you skimmed through, but that's not the point. The goal is to potentially discover new techniques and tools and increase your chances for these &lt;strong&gt;déjà vu&lt;/strong&gt; moments when you stumble upon an issue that might be solved by something you already read in the past.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Small Tip: Reading code can be challenging, especially when dealing with large projects written by others. However, even if you can't understand the code itself, take a look at the requirements or dependencies - you might find something valuable there!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I hope you have learned something from this article. What I describe here may not work for you or match your style of learning, and that's fine. I'm just making you aware of this option in case it was not already the case. And if you are already doing this, great buddy! Just know that you are not alone.&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>techtips</category>
      <category>codeadvice</category>
    </item>
    <item>
      <title>A comprehensive guide to multi-timezone support in Django</title>
      <dc:creator>Tobi-De</dc:creator>
      <pubDate>Wed, 11 Oct 2023 15:40:36 +0000</pubDate>
      <link>https://dev.to/tobi/a-comprehensive-guide-to-multi-timezone-support-in-django-91e</link>
      <guid>https://dev.to/tobi/a-comprehensive-guide-to-multi-timezone-support-in-django-91e</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: To support multiple timezones in your Django project, you need a way to request your users' specific timezones and create a middleware that uses &lt;code&gt;django.timezone.activate(user_tz)&lt;/code&gt; to enable a specific timezone for a user globally on your site. This ensures that every use of &lt;code&gt;django.timezone.now()&lt;/code&gt; uses the activated timezone. If you prefer reading code directly, see &lt;a href="https://github.com/Tobi-De/leerming/blob/main/leerming/profiles/middleware.py"&gt;middleware.py&lt;/a&gt; and &lt;a href="https://github.com/Tobi-De/leerming/blob/main/leerming/profiles/models.py"&gt;models.py&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Throughout this article, I'll guide you on setting up multi-timezone support in a Django project. This post is aimed at beginners (assuming basic Django knowledge) and intermediates. If what I'm writing seems blatantly obvious to you, you're likely not in one of these categories, so please bear with us.&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;Time is universally a challenging subject to deal with, especially in software engineering. Handling time zones correctly is hard. Luckily for us, when working with Django, a significant portion of the work has already been done.&lt;/p&gt;

&lt;p&gt;For the longest time (literally until yesterday), I thought that the piece of code below was enough to have timezones fully managed and working in Django:&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;# settings.py
&lt;/span&gt;&lt;span class="n"&gt;TIME_ZONE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"UTC"&lt;/span&gt;  &lt;span class="c1"&gt;# Sometimes, I'd switch this to my primary audience's timezone.
&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It turns out I was wrong, or at least I was missing the full picture. There's a bit more work left if you want to consider every user's timezone. Let's explore what's left to do.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; The official &lt;a href="https://docs.djangoproject.com/en/4.2/topics/i18n/timezones/"&gt;Django documentation on timezones&lt;/a&gt; is very well written, so I highly suggest you read it. However, this article will provide you with the essentials, at least that's what I hope.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The Basics
&lt;/h3&gt;

&lt;p&gt;First, let's start with the basics. In Python, there are two types of datetime objects: naive and aware. We'll keep it simple. Naive datetime objects hold no timezone information, while aware datetimes hold timezone information.&lt;/p&gt;

&lt;p&gt;When you have the &lt;code&gt;USE_TZ&lt;/code&gt; setting set to &lt;code&gt;True&lt;/code&gt; in your project settings (as shown in the snippet above), Django will ensure that all the DateTime objects you create are timezone-aware. This is, of course, assuming that you use Django's timezone module to create your dates.&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="nn"&gt;django.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&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;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works if you're not concerned about user-specific timezones, and you're not doing anything fancy with datetime. But what if you are?&lt;/p&gt;

&lt;h3&gt;
  
  
  The Challenge
&lt;/h3&gt;

&lt;p&gt;Let me paint a picture: I'm building an app where I need to send daily notifications to users at specific times. My project's timezone is set to &lt;code&gt;UTC&lt;/code&gt; – the recommended practice by most of the Django community. Here's the twist: I live in a UTC+1 timezone, and I realized my notifications were coming in an hour late. My cousin, who lives in Europe at a different timezone, also uses my app. Changing the default timezone wasn't an option. My cousin and I, along with potentially many other users, needed to navigate the app seamlessly without time feeling off. Scheduling and time management are at the core of my app, and I couldn't afford to mess that up.&lt;br&gt;
So, I decided to dive deep into understanding how Django deals with time zones – something I'd never made a priority before.&lt;/p&gt;

&lt;p&gt;After reading and re-reading the Django documentation, things started to click. First, let's grasp the fundamentals and then dive into setting up our timezone magic.&lt;/p&gt;

&lt;p&gt;There are two important concepts that Django uses: the "default time zone" and the "current time zone."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;default time zone&lt;/strong&gt; is the timezone you set in your Django settings via &lt;code&gt;TIME_ZONE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;current time zone&lt;/strong&gt; is the timezone used for rendering. It's the one in which your users will browse your site.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;current time zone&lt;/code&gt; defaults to the &lt;code&gt;default time zone&lt;/code&gt; unless you &lt;strong&gt;activate&lt;/strong&gt; the user's specific timezone using &lt;code&gt;django.utils.timezone.activate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Does it start to make sense?&lt;br&gt;
Django, by default, doesn't know a user's timezone. It's not typically available in request data, so it sticks with the default timezone. You need to ask your users for their time zone – a simple form does the trick – and then manually activate it. The easiest and probably the best way to do this is through a middleware.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting Up Multi-Timezone Support
&lt;/h3&gt;

&lt;p&gt;Now, let's dive into a simple example. We'll create a user &lt;code&gt;Profile&lt;/code&gt; model to collect and store each user's timezone. For simplicity's sake, I'll leave out the non-essential parts of the code.&lt;/p&gt;

&lt;p&gt;Here's a model for our users' profiles:&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="nn"&gt;zoneinfo&lt;/span&gt;

&lt;span class="n"&gt;TIMEZONES_CHOICES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tz&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;zoneinfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;available_timezones&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;Profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeStampedModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user&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="n"&gt;OneToOneField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"users.User"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"profile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&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="n"&gt;CASCADE&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;timezone&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="n"&gt;CharField&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="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Fuseau horaire"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&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="s"&gt;"UTC"&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;TIMEZONES_CHOICES&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;With the model above, we have a simple &lt;code&gt;CharField&lt;/code&gt; to hold the user's timezone and a choices field to render a select using a Django form. You can customize the list of available timezones obtained from the &lt;code&gt;zoneinfo module&lt;/code&gt;. For a simpler user experience, you might choose to filter it by continent or display the city as the label.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tip: If you're displaying a long list of timezones, consider using a select element with a search feature(e.g, &lt;a href="https://github.com/orchidjs/tom-select"&gt;tom-select&lt;/a&gt;) for a better user experience.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You'll need to create a form and a view to set the timezone value – standard Django stuff.&lt;/p&gt;

&lt;p&gt;Now, let's create the middleware to activate the user's timezone:&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="nn"&gt;zoneinfo&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Profile&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TimezoneMiddleware&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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_response&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__call__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&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;if&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&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_authenticated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="o"&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&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;Profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoesNotExist&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;deactivate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;else&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;activate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zoneinfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZoneInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&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="k"&gt;else&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;deactivate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_response&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This middleware activates the user's timezone if they're authenticated and have a profile. In other cases, we call &lt;code&gt;deactivate&lt;/code&gt;, which sets the timezone to the default. While this last step is not strictly required, that's how the Django docs suggest doing it, so let's stick with that. To complete the setup, don't forget to register your middleware in your settings within the &lt;code&gt;MIDDLEWARE&lt;/code&gt; list.&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;MIDDLEWARE&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="s"&gt;"your_app.middleware.TimezoneMiddleware"&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;With this setup, we've made sure that every call to &lt;code&gt;timezone.now&lt;/code&gt; will takes the user's specific timezone into account. When datetime objects are saved to the database, they are automatically converted to UTC. For example, in my case (UTC+1), if I input "6:00 pm" on the UI to record a time, it will be saved in the database as &lt;code&gt;5:00 pm&lt;/code&gt; (though it will still be rendered as "6:00 pm" to me on the frontend).&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Tips and bits:
&lt;/h3&gt;

&lt;p&gt;If you need to create a timezone-aware datetime object manually, for example by combining a date and time, here's how you do it:&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="nn"&gt;datetime&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;

&lt;span class="n"&gt;naive_datetime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;aware_datetime&lt;/span&gt; &lt;span class="o"&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;make_aware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;naive_datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zoneinfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZoneInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_timezone&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code below will generate a new migration every time you run &lt;code&gt;python manage.py makemigrations&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="n"&gt;TIMEZONES_CHOICES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tz&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;zoneinfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;available_timezones&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;Profile&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;timezone&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="n"&gt;CharField&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="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Fuseau horaire"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&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="s"&gt;"UTC"&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;TIMEZONES_CHOICES&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;The culprit line is &lt;code&gt;choices=TIMEZONES_CHOICES&lt;/code&gt;. A simple fix is to update the migrations file to use the  &lt;code&gt;TIMEZONES_CHOICES&lt;/code&gt; constant directly:&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;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="n"&gt;operations&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;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"profile"&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="s"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;field&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="n"&gt;CharField&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;TIMEZONES_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="s"&gt;"UTC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;There you have it! Your Django app is now fully equipped to support multiple timezones.&lt;br&gt;
I hope this article adds a touch of timezone magic to your Django projects.&lt;br&gt;
Thanks for the read.&lt;/p&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Maximizing Productivity: PyCharm and htmx Integration</title>
      <dc:creator>Tobi-De</dc:creator>
      <pubDate>Mon, 18 Sep 2023 09:32:41 +0000</pubDate>
      <link>https://dev.to/tobi/maximizing-productivity-pycharm-and-htmx-integration-34n6</link>
      <guid>https://dev.to/tobi/maximizing-productivity-pycharm-and-htmx-integration-34n6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; Learn how to add support for htmx in PyCharm for seamless development using &lt;a href="https://github.com/JetBrains/web-types#web-types"&gt;web-types&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're not familiar with htmx, check out this fantastic &lt;a href="https://www.youtube.com/watch?v=Ula0c_rZ6gk"&gt;htmx + Django introduction&lt;/a&gt; by &lt;a href="https://www.bugbytes.io/"&gt;BugBytes&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://htmx.org/"&gt;htmx&lt;/a&gt; is my go-to frontend tool for building web applications, and &lt;a href="https://www.jetbrains.com/pycharm/"&gt;PyCharm from JetBrains&lt;/a&gt; is my daily code editor/IDE. Unfortunately, by default, PyCharm doesn't recognize htmx attributes when used in templates, resulting in ugly warning lines 🙁.  This article will guide you on how to resolve this issue and improve your development workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;There's a simple way to add autocompletion and documentation for htmx attributes in JetBrains editors using &lt;a href="https://github.com/JetBrains/web-types#web-types"&gt;web-types&lt;/a&gt;. Web-types is a JSON-based format that provides IDEs with metadata information about web component libraries like htmx. I stumbled upon this tip via &lt;a href="https://twitter.com/sponsfreixes/status/1573725414643535872"&gt;this tweet&lt;/a&gt;, so kudos to the author for sharing it!&lt;/p&gt;

&lt;h3&gt;
  
  
  Step-by-Step Guide
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Create a &lt;code&gt;package.json&lt;/code&gt; File&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start by creating a &lt;code&gt;package.json&lt;/code&gt; file, the central configuration file for Node.js-based applications. Although we're not building a Node.js package, this is the method to reference web-types in your PyCharm project. Ensure you have Node.js installed on your computer. If not, I recommend using &lt;a href="https://github.com/nvm-sh/nvm"&gt;nvm&lt;/a&gt; to install it. After installing Node.js, you can use &lt;code&gt;npm&lt;/code&gt; (included with all Node.js installations) to initialize a Node.js project and generate the &lt;code&gt;package.json&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Create &lt;code&gt;htmx.web-types.json&lt;/code&gt; File&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create a new file named &lt;code&gt;htmx.web-types.json&lt;/code&gt; and copy the htmx &lt;a href="https://github.com/bigskysoftware/htmx/blob/master/editors/jetbrains/htmx.web-types.json"&gt;web-types source&lt;/a&gt; into 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;touch &lt;/span&gt;htmx.web-types.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Update &lt;code&gt;package.json&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add a new entry to your &lt;code&gt;package.json&lt;/code&gt; with "web-types" as the key and the path to your &lt;code&gt;htmx.web-types.json&lt;/code&gt; file as the value. Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"web-types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./htmx.web-types.json"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Enjoy Autocompletion and Documentation&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, in your HTML templates, type an htmx attribute like &lt;code&gt;hx-get&lt;/code&gt;, and you should see autocompletion and documentation directly accessible in your IDE. Cool, right? 😎&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;With this simple setup, you can enhance your PyCharm experience and work seamlessly with htmx attributes in your Django projects. Happy coding!&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>htmx</category>
    </item>
    <item>
      <title>Exploring SSE and PostgreSQL for Realtime Communication in Django</title>
      <dc:creator>Tobi-De</dc:creator>
      <pubDate>Sat, 16 Sep 2023 14:43:59 +0000</pubDate>
      <link>https://dev.to/tobi/exploring-sse-and-postgresql-for-realtime-communication-in-django-369n</link>
      <guid>https://dev.to/tobi/exploring-sse-and-postgresql-for-realtime-communication-in-django-369n</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: In this article, I explore how we built a relay system to serve real-time notifications to our Django project using Server-Sent Events (SSE) and PostgreSQL LISTEN/NOTIFY. Check out the final project on GitHub &lt;a href="https://github.com/Tobi-De/sse_relay_server"&gt;here&lt;/a&gt;. 🚀&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3gef7C0q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b6esukxv1aw2io3jbr7p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3gef7C0q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b6esukxv1aw2io3jbr7p.png" alt="sse relay transmission diagram" width="800" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Recently, at my workplace, I was tasked with creating a real-time notification system for one of our Django projects. I opted for &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events"&gt;Server-Sent Events&lt;/a&gt; (SSE). Luckily for me, around that time Django 4.2 introduced async iterators support for StreamingHttpResponse, which seemed ideal. However, as I delved deeper into the project, I decided instead to build a relay service using &lt;a href="https://github.com/Tobi-De/sse_server_postgres_listen_notify"&gt;Starlette&lt;/a&gt; which transmits messages received through the PostgreSQL LISTEN/NOTIFY protocol to client browsers connected via SSE.&lt;/p&gt;

&lt;p&gt;It all began with an excellent post on &lt;a href="https://valberg.dk/django-sse-postgresql-listen-notify.html"&gt;Writing a chat application in Django 4.2 using async StreamingHttpResponse, Server-Sent Events, and PostgreSQL LISTEN/NOTIFY&lt;/a&gt;. I read the post and successfully applied the concepts. This wasn't my first encounter with the PostgreSQL LISTEN/NOTIFY protocol; I knew that tools like Procrastinate (a python task queue) also used it. This protocol was appealing because it eliminated the need to introduce additional infrastructure like Redis into our setup since we were already using PostgreSQL as our database. At the end, what I did added an extra service to our setup, but I think it was worth it, even just for the idea it sparked.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: I identified the issue that was crashing our database, and it had nothing to do with our Daphne setup; it was related to our business logic, entirely on our side. To simplify, we have a multi-tenant application, and some users, mainly administrators, have access to multiple tenants. The problem arose when you were an admin with access to multiple tenants, but no specific tenant was selected. If no tenant was selected, we cannot determine which channel to listen to. As a result, we sent an empty channel name to the server as part of the SSE connection request. Consequently, our server responded to SSE connections with an empty response, causing the browser to try to reconnect each time.&lt;br&gt;
The solution is straightforward. When a tenant is not selected (meaning no channel can be determined), we send a response with a retry interval set to a high value (in our case, 1 year). This effectively prevents the browser from attempting the SSE connection. This is the simplest solution we've found to halt the SSE connection for now.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Initially, the setup worked seamlessly. Notifications were correctly transmitted and received. We switched from using Gunicorn to Daphne to run the project, and everything seemed fine. However, after a while, a colleague noticed that our server was constantly receiving requests on our SSE async view. It turned out that the connection was consistently disrupted, causing the browser to repeatedly attempt reconnection. This ultimately led to our PostgreSQL database sporadically crashing with an error message stating "too many clients."&lt;/p&gt;

&lt;p&gt;At this point, we did something that, now that I think of it, makes absolutely no sense. We split our Django server into two instances: one running in WSGI mode with Gunicorn, and the other under Daphne in ASGI mode. It did not resolve the issue, maybe we did that thinking it would mitigate the issue? Who knows. However, it did provide me with an idea – replacing the Django app running under Daphne with something better suited for asynchronous operations.&lt;/p&gt;

&lt;p&gt;I ended up creating a relay using &lt;a href="https://www.starlette.io"&gt;Starlette&lt;/a&gt;, which transmitted messages received through the PostgreSQL &lt;a href="https://www.postgresql.org/docs/current/sql-listen.html"&gt;LISTEN&lt;/a&gt;/&lt;a href="https://www.postgresql.org/docs/15/sql-notify.html"&gt;NOTIFY&lt;/a&gt; protocol to client browsers connected via SSE (The diagram above). This solution proved to be more stable and reliable than the Django approach. Furthermore, it allowed me to simplify the original Django project by completely removing the ASGI setup and code from the project. I was not very fond of the idea of mixing and matching sync and asynchronous code anyway, so this was a win for me. For a straightforward communication method via SSE in Django, this approach worked pretty well.&lt;/p&gt;

&lt;p&gt;I've noticed several repositories on GitHub exploring similar ideas. I'm curious about its long-term viability 🤔, but for now, it works pretty well with our setup. After all, we only need the async stuff for real-time notifications. It was not worth turning our entire project into ASGI just for that. Plus, we can easily use the relay for some other precise and simple stuff in the project like updating charts, etc.&lt;/p&gt;

&lt;p&gt;Thanks for reading! I've turned the relay project into a package and a Docker image; you can check it out at &lt;a href="https://github.com/Tobi-De/sse_relay_server"&gt;https://github.com/Tobi-De/sse_relay_server&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>postgres</category>
      <category>redis</category>
    </item>
    <item>
      <title>Fuzzy-Couscous - The CLI for a better django development experience</title>
      <dc:creator>Tobi-De</dc:creator>
      <pubDate>Mon, 27 Feb 2023 11:17:30 +0000</pubDate>
      <link>https://dev.to/tobi/fuzzy-couscous-the-cli-for-a-better-django-development-experience-4ck3</link>
      <guid>https://dev.to/tobi/fuzzy-couscous-the-cli-for-a-better-django-development-experience-4ck3</guid>
      <description>&lt;p&gt;Hello everyone!&lt;/p&gt;

&lt;p&gt;I'm excited to introduce you to &lt;a href="https://github.com/Tobi-De/fuzzy-couscous" rel="noopener noreferrer"&gt;fuzzy-couscous&lt;/a&gt;, my latest project designed to enhance your django &lt;br&gt;
development experience with Django.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fd8fsi1n7da2z5m934ez0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fd8fsi1n7da2z5m934ez0.gif" alt="showcase gif" width="600" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've spent the last few months working on this command line tool, and I'm excited to share it with you and get your feedback. &lt;br&gt;
The primary feature of fuzzy-couscous is to generate a django project using a project template that I've built, inspired heavily by the popular &lt;a href="https://github.com/cookiecutter/cookiecutter-django" rel="noopener noreferrer"&gt;cookiecutter-django&lt;/a&gt; &lt;br&gt;
project template. I simplified their template as much as I could while adding some of my favorite tools like &lt;a href="https://htmx.org/" rel="noopener noreferrer"&gt;htmx&lt;/a&gt; and &lt;a href="https://python-poetry.org/" rel="noopener noreferrer"&gt;poetry&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With fuzzy-couscous, you can quickly create a new Django project using the built-in template, which includes support for either &lt;a href="https://getbootstrap.com/" rel="noopener noreferrer"&gt;Bootstrap&lt;/a&gt; or &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;. &lt;br&gt;
It also comes with a variety of convenient commands, such as launching multiple processes at once including the Django dev server, a Redis server, a worker process, and more (although I'm currently working on improving this feature), &lt;br&gt;
as well as a command to easily download the latest version of htmx. I have some exciting ideas for potential new commands, including one to generate &lt;a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete" rel="noopener noreferrer"&gt;CRUD&lt;/a&gt; code (&lt;a href="https://github.com/Tobi-De/fuzzy-couscous/issues/27" rel="noopener noreferrer"&gt;#27&lt;/a&gt;)  and another to ensure that projects &lt;br&gt;
created with fuzzy-couscous can easily get updates from the base template (&lt;a href="https://github.com/Tobi-De/fuzzy-couscous/issues/22" rel="noopener noreferrer"&gt;#22&lt;/a&gt;).&lt;br&gt;
I'm always looking for new ideas and suggestions, so please feel free to open a &lt;a href="https://github.com/Tobi-De/fuzzy-couscous/discussions" rel="noopener noreferrer"&gt;GitHub discussion&lt;/a&gt; or an &lt;a href="https://github.com/Tobi-De/fuzzy-couscous/issues/new" rel="noopener noreferrer"&gt;issue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This project is nothing revolutionary, but it's useful for me and I think it could be useful for other django developers out there.&lt;br&gt;
If you're interested, you can find fuzzy-couscous on GitHub at &lt;a href="https://github.com/Tobi-De/fuzzy-couscous" rel="noopener noreferrer"&gt;https://github.com/Tobi-De/fuzzy-couscous&lt;/a&gt;.&lt;br&gt;
I hope you'll give it a try and let me know what you think. Thanks for your time!&lt;/p&gt;

</description>
      <category>gratitude</category>
    </item>
    <item>
      <title>Host your Django project on DigitalOcean</title>
      <dc:creator>Tobi-De</dc:creator>
      <pubDate>Mon, 12 Oct 2020 07:12:25 +0000</pubDate>
      <link>https://dev.to/tobi/host-your-django-project-on-digitalocean-2km5</link>
      <guid>https://dev.to/tobi/host-your-django-project-on-digitalocean-2km5</guid>
      <description>&lt;p&gt;In this post we are going to see how you can easily deploy your awesome Django project on a linux server( Virtual Private Server a.k.a vps ). In this tutorial I will be using &lt;a href="https://m.do.co/c/507efee95715"&gt;DigitalOcean&lt;/a&gt;, a well know cloud provider that offers you a full control on your server, I also choose &lt;a href="https://m.do.co/c/507efee95715"&gt;DigitalOcean&lt;/a&gt; because they have a 1-click app &lt;a href="http://dokku.viewdocs.io/dokku/"&gt;Dokku&lt;/a&gt; droplet to get you up and running quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Dokku ?
&lt;/h3&gt;

&lt;p&gt;Dokku is an herokuish (give similar environment to &lt;a href="https://heroku.com"&gt;heroku&lt;/a&gt;) platform that help  you deploy quite easily any web based application like a Django app. It is an open source and free &lt;a href="https://fr.wikipedia.org/wiki/Platform_as_a_service"&gt;PaaS&lt;/a&gt; built on &lt;a href="https://docs.docker.com/"&gt;Docker&lt;/a&gt;.  If you already own a vps, installing Dokku is very easy, you can follow  the &lt;a href="http://dokku.viewdocs.io/dokku/getting-started/installation/"&gt;installation guide&lt;/a&gt;. This is not the only way to deploy your Django project, you can get away with the basic nginx + gunicorn + postgresql manual setup, but I find this option more easy  to setup and manage, I don't like the hassle that comes with managing a linux server, I want to focus on building solutions not managing servers and I think Dokku help with that. If you are Interested by other ways to deploy your django applications, I've linked some useful resources at the end of this guide to help you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;What you need to be able to follow this guide.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A DigitalOcean account, you can create one &lt;a href="https://m.do.co/c/507efee95715"&gt;here&lt;/a&gt; (following this link you will get $100 free credits to be able to test the plaform)&lt;/li&gt;
&lt;li&gt;A droplet (DigitalOcean term to represent a linux server) with Dokku installed on it, fortunately for us, DigitalOcean got us covered, you can create one with Dokku already installed on it &lt;a href="https://do.co/2PQDQPz"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A domain name, you can get one for cheap at &lt;a href="https://www.namecheap.com/"&gt;namecheap&lt;/a&gt; or &lt;a href="https://www.godaddy.com/en-an"&gt;GoDaddy&lt;/a&gt;, use the provider of your choice, I'm using namecheap in this guide. &lt;/li&gt;
&lt;li&gt;A Django project obviously 😆&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://git-scm.com/"&gt;git&lt;/a&gt; installed on you computer

&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setup your VPS
&lt;/h3&gt;

&lt;p&gt;At this step I presume you have already a droplet (or a vps) with linux installed on it, in my case ubuntu 18.04 LTS. Dokku should already be installed also, if you use the DigitalOcean 1-click Dokku droplet app then you are ready to go. After the server is launched you should wait few minutes for dokku to be ready. Meanwhile follow these steps to get your server ready. &lt;/p&gt;

&lt;h5&gt;
  
  
  Connect to your server with ssh then search for update
&lt;/h5&gt;

&lt;p&gt;If you are using DigitalOcean, go to your dashboard then click on Droplets, select your droplet, then click on your ipv4 address to copy it and 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;ssh root@your_ip_address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you were setting up your droplet,  you added a root password, you need it here to run sudo commands.&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;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  add a limited user
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adduser your_user_name
adduser your_user_name &lt;span class="nb"&gt;sudo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can close your session and connect back with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh your_user_name@your_ip_address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  setup ssh server on local
&lt;/h5&gt;

&lt;p&gt;If you find it painful to type your ip address each time, you can just add an alias to your system or even better, add this code to your local ~/.ssh/config file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host a_server_name
    ForwardAgent yes
    Hostname your_ip_address
    Port 22
    ServerAliveInterval 60
    ServerAliveCountMax 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now to connect to your server, you can type&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh your_user_name@a_server_name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;create an ssh public key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will need an ssh key to configure your dokku instance, if you already have one then you can skip this step. If you are on linux, run this command to generate the ssh key. The command will ask for the path to create the ssh key and a passphrase. You can leave the default path for the key directory, and for the passphrase you can leave it blank but you will have to remember it if you set one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;keygen&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are on windows then you can follow this &lt;a href="https://docs.joyent.com/public-cloud/getting-started/ssh-keys/generating-an-ssh-key-manually/manually-generating-your-ssh-key-in-windows"&gt;link&lt;/a&gt; to help you create your ssh key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup your domain name
&lt;/h3&gt;

&lt;p&gt;This is very simple, just create an A record with the value of your server ip address, example :&lt;/p&gt;

&lt;p&gt;Type : &lt;em&gt;A Record&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Host : &lt;em&gt;*.&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Value : &lt;em&gt;XXX.XXX.XXX.XX (your ip address)&lt;/em&gt; &lt;/p&gt;
&lt;h3&gt;
  
  
  Configure Dokku
&lt;/h3&gt;

&lt;p&gt;At this step, your Dokku instance should be ready. Visit &lt;a href="http://your_ip_address"&gt;http://your_ip_address&lt;/a&gt; to configure the dokku instance. You should see a Dokku Setup page with a form, the only field you need to worry about is the &lt;strong&gt;Public ssh key&lt;/strong&gt; field. If you generate the key with the command above and leave the path as the default one (it should be something like this ~/.ssh/id_rsa.pub) , then open the &lt;strong&gt;id_rsa.pub&lt;/strong&gt; file with a text editor and copy the content inside the &lt;strong&gt;Public ssh Key&lt;/strong&gt; field, then click &lt;strong&gt;finish setup&lt;/strong&gt; and now your dokku instance is configured.&lt;br&gt;
To test it, connect to your server and type &lt;strong&gt;dokku&lt;/strong&gt; (try with &lt;strong&gt;sudo dokku&lt;/strong&gt; if you have permissions issue), if you see the help menu then you are good to go, if not you can always ask a question in the comment section bellow or check if you miss a step above.&lt;/p&gt;

&lt;p&gt;Now that our dokku instance is ready, we can deploy our django project, but before that we need to install some plugins and create the app.&lt;/p&gt;
&lt;h3&gt;
  
  
  create an App
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;your_app_name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Install postgres plugin
&lt;/h3&gt;

&lt;p&gt;This is the command you need to install the postgres plugin to your dokku instance :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;sudo&lt;/span&gt; &lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//github.com/dokku/dokku-postgres.git&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new postgres database instance and link it to your app by running the commands below, linking the database to your app will automatically create a &lt;strong&gt;DATABASE_URL&lt;/strong&gt; environment variable that connect your app to your database instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;sudo&lt;/span&gt; &lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;your_app_name_db&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="n"&gt;your_app_name_db&lt;/span&gt; &lt;span class="n"&gt;your_app_name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;SideNote&lt;/em&gt; : if you need a redis instance, just replace "postgres" by "redis". For any other plugins refer to the offcial &lt;a href="http://dokku.viewdocs.io/dokku/community/plugins/"&gt;dokku documentation plugin page&lt;/a&gt;. To setup database backups on an aws S3 bucket check &lt;a href="https://github.com/dokku/dokku-postgres#backups"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a domain to your app
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;domains&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;your_app_name&lt;/span&gt; &lt;span class="n"&gt;your_domain&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;domains&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;your_app_name&lt;/span&gt; &lt;span class="n"&gt;you_domain&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you_domain: something like &lt;a href="http://www.mydomain.com"&gt;www.mydomain.com&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Set environment variables
&lt;/h3&gt;

&lt;p&gt;You need to set the environment variables before pushing your app to dokku, if not the deployment will fail. To set an environment variable, type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;your_app_name&lt;/span&gt; &lt;span class="n"&gt;env_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;env_value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;my_app&lt;/span&gt; &lt;span class="n"&gt;PYTHONHASHSEED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;my_app&lt;/span&gt; &lt;span class="n"&gt;WEB_CONCURRENCY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;my_app&lt;/span&gt; &lt;span class="n"&gt;DJANGO_DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;False&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;my_app&lt;/span&gt; &lt;span class="n"&gt;DJANGO_SETTINGS_MODULE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;production&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;my_app&lt;/span&gt; &lt;span class="n"&gt;DJANGO_SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"$(openssl rand -base64 64)"&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;my_app&lt;/span&gt; &lt;span class="n"&gt;DJANGO_ADMIN_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"$(openssl rand -base64 4096 | tr -dc 'A-HJ-NP-Za-km-z2-9' | head -c 32)/"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deploy your app
&lt;/h3&gt;

&lt;h5&gt;
  
  
  Create a Procfile
&lt;/h5&gt;

&lt;p&gt;In the root of your project create a file name &lt;em&gt;Procfile&lt;/em&gt; and inside that file copy this code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="nl"&gt;web:&lt;/span&gt; &lt;span class="n"&gt;gunicorn&lt;/span&gt; &lt;span class="n"&gt;wsgi_file_path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;
&lt;span class="n"&gt;release&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;migrate&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my project the wsgi.py file is in the &lt;em&gt;config&lt;/em&gt; folder, a folder in the root of my project, you need to find your &lt;em&gt;wsgi&lt;/em&gt; file and replace the &lt;strong&gt;wsgi_file_path&lt;/strong&gt; by your path, usually it is in a folder named base on your project name, In my case the first line give this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="nl"&gt;web:&lt;/span&gt; &lt;span class="n"&gt;gunicorn&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wsgi&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Requirements file
&lt;/h5&gt;

&lt;p&gt;You also need a requirements.txt or Pipfile so Dokku can install  all dependencies when deploying. If you are using a virtual environment (you better be using one) just type (in a terminal) from the root of your project :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;freeze&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;requirements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the dokku remote repository to your project with git :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;git&lt;/span&gt; &lt;span class="n"&gt;remote&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;dokku&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;my_server&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;your_app_name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now deploy your app with :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;git&lt;/span&gt; &lt;span class="n"&gt;push&lt;/span&gt; &lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;master&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;when you are done, you can check your deployment setup like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="n"&gt;your_app_name&lt;/span&gt; &lt;span class="n"&gt;bash&lt;/span&gt;
&lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sidenote&lt;/strong&gt; : Using dokku clients&lt;/p&gt;

&lt;p&gt;Using a dokku client you can create new apps, add plugins easily from your local machine, check the available clients &lt;a href="http://dokku.viewdocs.io/dokku/community/clients"&gt;here&lt;/a&gt;. When a dokku client is installed and  the dokku remote repository is added to your project with git using the above command, you can easily manage your app from your local machine.  To set environment variable normally, you should type (assuming you install the &lt;a href="http://dokku.viewdocs.io/dokku/community/clients/#nodejs-dokku-toolbelt"&gt;dokku-toolbelt&lt;/a&gt;)  from the root of your project on your local machine :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;dt&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;env_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;env_value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if you have fifteen or more environment variables to set, it will take you decades to set them all. If you want a more efficient way to proceed, you can use this little python &lt;a href="https://github.com/Tobi-De/dokku-envs"&gt;script&lt;/a&gt; I wrote to set easily and quickly your environment variables on dokku. Read through it and adjust it to your need. The script can be run from the local or the remote machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup ssl certificate
&lt;/h3&gt;

&lt;p&gt;Install and configure letsencrypt plugin for secure connection through https.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//github.com/dokku/dokku-letsencrypt.git&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;no&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;restart&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;global&lt;/span&gt; &lt;span class="n"&gt;DOKKU_LETSENCRYPT_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;hello&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;youdomain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use your personal email if you want, it must be a valid email address.&lt;/p&gt;

&lt;p&gt;Add a certificate to your app&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;letsencrypt&lt;/span&gt; &lt;span class="n"&gt;your_app_name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setup a cron task to renew ssl certificate&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;letsencrypt&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;cron&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;In this tutorial we have seen how you can deploy your django project on a vps using dokku. If you need more deployment options, you can check out the ones below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=6DI_7Zja8Zc"&gt;Deploy your django project with heroku&lt;/a&gt;, if you try this option, you will realize that deployment with heroku is more simple, but in the long run heroku is much more expensive, for example the $25 offer from heroku is roughly equivalent to the $5 droplet from digital ocean.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=BrVHwQ-SJUA&amp;amp;t=12s"&gt;Deploy django using nginx + gunicorn + postgres&lt;/a&gt;, manual setup&lt;/li&gt;
&lt;li&gt;&lt;a href="https://testdriven.io/blog/dockerizing-django-with-postgres-gunicorn-and-nginx/"&gt;Deploy django using docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devspace.cloud/blog/2019/10/18/deploy-django-to-kubernetes"&gt;Deploy django using kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Obviously this list is not exhaustive, you can check on google for more options.&lt;br&gt;
Thank you for reading this post, I hope it taught you something, feel free to give me your feedback in the comments section below.&lt;br&gt;
This is the first article I have written so I would love to have feedback so that I can improve myself&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
