<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Ugochukwu Benjamin .C on Medium]]></title>
        <description><![CDATA[Stories by Ugochukwu Benjamin .C on Medium]]></description>
        <link>https://medium.com/@kodiugos?source=rss-99c5bfceed4b------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*IFq1222Fg6-VDvlsvxylqw.jpeg</url>
            <title>Stories by Ugochukwu Benjamin .C on Medium</title>
            <link>https://medium.com/@kodiugos?source=rss-99c5bfceed4b------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 24 May 2026 15:12:08 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@kodiugos/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Revoke JWTs, Secure Your API — Middleware + Redis in DRF]]></title>
            <link>https://python.plainenglish.io/revoke-jwts-secure-your-api-middleware-redis-in-drf-d8501f1d30ba?source=rss-99c5bfceed4b------2</link>
            <guid isPermaLink="false">https://medium.com/p/d8501f1d30ba</guid>
            <category><![CDATA[django]]></category>
            <category><![CDATA[backend]]></category>
            <category><![CDATA[jwt]]></category>
            <category><![CDATA[redis]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Ugochukwu Benjamin .C]]></dc:creator>
            <pubDate>Wed, 19 Nov 2025 20:42:58 GMT</pubDate>
            <atom:updated>2025-11-26T16:26:49.180Z</atom:updated>
            <content:encoded><![CDATA[<h3>Revoke JWTs, Secure Your API Middleware + Redis in DRF</h3><blockquote>A step-by-step implementation to revoke JWTs access token immediately on user logout using middleware and Redis blacklist/denylist</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XTgGfOuXiH9ocuuWTxHlgA.png" /></figure><p>JWTs are difficult to revoke due to their self-contained nature. Once issued, a JWT is valid until its expiration, regardless of whether the user’s permissions have changed or the token has been compromised. This is a <strong>flaw in the stateless nature of JWT.</strong></p><h4><strong>Imagine this</strong>?</h4><p>Standard client-side logout removes tokens from the browser and redirects users to the login screen. However, the access token remains valid until its TTL(time to live) expires, creating a vulnerability where attackers could steal and use the token. I will now present an effective solution for this issue in DRF.</p><h4>The Middleware + Redis Approach</h4><p>To force user logout, we must revoke/invalidate the access token using a custom approach, as this cannot be done on demand. This is done by using a DB(Redis for optimal performance)to cache the access token<strong> JTI</strong> as the Redis key with the remaining expiration time as the TTL on logout, then wire up a custom middleware to check requests for cached blacklisted access token, then revoke API access accordingly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Umt6gKjiBTcXM1sgSHk2Dg.png" /></figure><h4>Typical code implementation</h4><p>Before we start the code implementation, ensure you have this specific simple JWT config set to ensure refresh tokens are blakclisted on token rotation. Ensure you have PyJWT and Redis installed</p><pre># JWT Configuration<br>SIMPLE_JWT = {<br>    &#39;ROTATE_REFRESH_TOKENS&#39;: True,<br>    &#39;BLACKLIST_AFTER_ROTATION&#39;: True,<br>   <br>}</pre><p>The next step is to create Redis utility functions to retrieve the access token JTI, cache it, and retrieve the value.</p><pre>import time<br>import redis<br>import jwt<br>import logging<br><br>redis_client = redis.Redis.from_url(settings.REDIS_URL, decode_responses=True)<br>logger = logging.getLogger(__name__)<br><br>def _token_identifier_and_exp(token: str):<br>    &quot;&quot;&quot;<br>    Return a (redis_key, exp_timestamp) tuple.<br>    - Prefer token &#39;jti&#39; claim if present: key = &quot;jwt:blacklist:jti:{jti}&quot;<br>    - Otherwise fall back to SHA256(token): key = &quot;jwt:blacklist:hash:{sha256}&quot;<br>    - exp_timestamp is integer POSIX seconds or 0 if unknown.<br>    &quot;&quot;&quot;<br>    try:<br>        payload = jwt.decode(token, options={&quot;verify_signature&quot;: False})<br>        exp = int(payload.get(&quot;exp&quot;, 0)) if payload.get(&quot;exp&quot;) else 0<br>        jti = payload.get(&quot;jti&quot;)<br>        if jti:<br>            key = f&quot;jwt:blacklist:jti:{jti}&quot;<br>            return key, exp<br><br>    except Exception as e:<br>        exp = 0<br>        logger.debug(&quot;Failed to decode token for metadata: %s&quot;, exc)<br><br>    h = hashlib.sha256(token.encode(&#39;utf-8&#39;)).hexdigest()<br>    key = f&quot;jwt:blacklist:hash:{h}&quot;<br>    return key, exp<br><br>def blacklist_token(token: str) -&gt; int:<br>    &quot;&quot;&quot;<br>    Add token to redis blacklist with TTL equal to (exp - now) if exp exists,<br>    otherwise use JWT_BLACKLIST_DEFAULT_TTL.<br>    Returns TTL set (in seconds).<br>    &quot;&quot;&quot;<br>    key, exp = _token_identifier_and_exp(token)<br>    now = int(time.time())<br>    if exp and exp &gt; now:<br>        ttl = exp - now<br><br>    try:<br>        redis_client.setex(key, ttl, &quot;blacklisted&quot;)<br>        logger.info(&quot;Blacklisted token key=%s ttl=%s&quot;, key, ttl)<br>    except Exception as exc:<br>        logger.exception(&quot;Failed to blacklist token in Redis: %s&quot;, exc)<br>        raise<br>    return ttl<br><br>def is_token_blacklisted(token: str) -&gt; bool:<br>    key, _ = _token_identifier_and_exp(token)<br>    try:<br>        exists = redis_client.exists(key) == 1<br>        logger.info(&quot;Checked blacklist for key=%s exists=%s&quot;, key, exists)<br>        return exists<br>    except Exception as exc:<br>        logger.exception(&quot;Redis error while checking blacklist: %s&quot;, exc)<br>        return False<br><br></pre><h4>Force user logout</h4><p>Next, use the utility functions to blacklist the access token on demand when a user logs out.</p><pre>class CustomLogoutView(APIView):<br>    &quot;&quot;&quot;Enhanced logout view&quot;&quot;&quot;<br>    permission_classes = [permissions.IsAuthenticated]<br>    authentication_classes = [JWTAuthentication]<br><br>    def post(self, request, *args, **kwargs):<br>        errors = []<br><br>        refresh_token = request.data.get(&#39;refresh&#39;)<br><br>        if refresh_token:<br>            try:<br>                token = RefreshToken(refresh_token)<br>                token.blacklist()<br>                logger.info(&quot;Refresh token blacklisted via SimpleJWT for user=%s&quot;, request.user.email)<br>            except TokenError as e:<br>                errors.append(f&quot;Refresh token error: {str(e)}&quot;)<br>                logger.error(&quot;Refresh token error for user=%s: %s&quot;, request.user.email, str(e))<br><br>            auth = request.META.get(&quot;HTTP_AUTHORIZATION&quot;, &quot;&quot;)<br>            if auth and auth.split()[0].lower() == &quot;bearer&quot;:<br>                access_token = auth.split()[1].strip()<br>                try:<br>                    blacklist_token(access_token)<br>                    logger.info(&quot;Access token blacklisted ttl=%s for user=%s&quot;, ttl, request.user.email)<br>                except Exception as exc:<br>                    logger.exception(&quot;Failed to blacklist access token: %s&quot;, exc)<br>                    errors.append(&quot;Failed to blacklist access token&quot;)<br>        else:<br>            errors.append(&quot;No refresh token provided in payload or cookie&quot;)<br>            logger.warning(&quot;No refresh token found for user=%s&quot;, request.user.email)<br><br><br>        if errors:<br>            return Response(<br>                {&quot;success&quot;: False,<br>                 &quot;message&quot;: &quot;Logout completed with errors.&quot;,<br>                 &quot;errors&quot;: errors<br>                 },status=status.HTTP_400_BAD_REQUEST<br>            )<br><br>        response = Response(<br>            {&#39;success&#39;: True, &#39;message&#39;: &#39;Logout successful.&#39;},<br>            status=status.HTTP_200_OK<br>        )<br><br>        logger.info(f&quot;User logged out: {request.user.email}&quot;)<br><br>        return response</pre><p>The logout view invalidates user sessions by blacklisting refresh and access tokens from a POST request. It uses SimpleJWT to blacklist the refresh token, then blacklists the access token using the Redis custom functions. Invalid/missing tokens are handled, and the user is logged out.</p><h4>Adding the Middleware</h4><p>Middleware is added to prevent further requests from being processed if a token has been blacklisted.</p><pre>import logging<br>from django.utils.deprecation import MiddlewareMixin<br>import time<br>from apps.users.utils import is_token_blacklisted<br><br>class RedisJWTBlacklistMiddleware(MiddlewareMixin):<br>    &quot;&quot;&quot;<br>    Reject requests with a blacklisted JWT (Bearer token).<br>    Place this middleware early so blocked tokens are rejected before hitting views.<br>    &quot;&quot;&quot;<br>    def process_request(self, request):<br>        auth = request.META.get(&quot;HTTP_AUTHORIZATION&quot;, &quot;&quot;)<br>        if not auth:<br>            return None<br><br>        parts = auth.split()<br>        if len(parts) != 2 or parts[0].lower() != &quot;bearer&quot;:<br>            logger.debug(&quot;Authorization header not Bearer&quot;)<br>            return None<br><br>        token = parts[1].strip()<br>        try:<br>            if is_token_blacklisted(token):<br>                logger.info(&quot;Rejected request with blacklisted token&quot;)<br>                return JsonResponse({&quot;detail&quot;: &quot;Token has been revoked.&quot;}, status=401)<br>        except Exception:<br>            return None<br><br>        return None</pre><h4>Conclusion</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/568/0*Iqq91Ll3PfjePIfu.jpeg" /></figure><p>Creating a clean log-out flow with JSON Web Tokens presents challenges. I chose this approach, which I believe is the most optimal. Let me know your thoughts in the comments.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d8501f1d30ba" width="1" height="1" alt=""><hr><p><a href="https://python.plainenglish.io/revoke-jwts-secure-your-api-middleware-redis-in-drf-d8501f1d30ba">Revoke JWTs, Secure Your API — Middleware + Redis in DRF</a> was originally published in <a href="https://python.plainenglish.io">Python in Plain English</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Unlock Async Email Magic with Docker-compose, Celery, Flower & Redis in Django Rest Framework(DRF)]]></title>
            <link>https://python.plainenglish.io/unlock-async-email-magic-with-docker-compose-celery-flower-redis-in-django-rest-framework-drf-26d94da5ac49?source=rss-99c5bfceed4b------2</link>
            <guid isPermaLink="false">https://medium.com/p/26d94da5ac49</guid>
            <category><![CDATA[coding]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[django-rest-framework]]></category>
            <category><![CDATA[backend-development]]></category>
            <category><![CDATA[django]]></category>
            <dc:creator><![CDATA[Ugochukwu Benjamin .C]]></dc:creator>
            <pubDate>Mon, 07 Oct 2024 04:06:40 GMT</pubDate>
            <atom:updated>2024-10-07T04:06:40.861Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4VQQjNu21-Kn9RYNwRgLJQ.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/0*j8BfYBCzrzyqt9qe" /><figcaption>I know bro🫵</figcaption></figure><p>We both know why you clicked this article cause it’s either you don’t know how to send an email newsletter from within your application or you’re not doing it in the most efficient way possible. That’s where I come in😉. Here’s a step-by-step guide for 🫵. Enjoy🫡</p><p>Before you dive in just a heads up that this isn’t a beginner-level tutorial, prior knowledge of Django is required! Cheers🍻</p><p>So first we create and activate our virtual environment</p><pre>benji@BENJAMIN:~/email_newsletter$ virtualenv venv<br>benji@BENJAMIN:~/email_newsletter$ source venv/bin/activate<br>(venv) benji@BENJAMIN:~/email_newsletter$</pre><p>Then we move over to installing and freezing our project dependencies e.g django, djangorestframewrok, drf-spectacular, etc and creating our Django project and core Django application</p><pre>(venv) benji@BENJAMIN:~/email_newsletter$ pip install django drf-spectacular djangorestframework, djangorestframework-simplejwt celery redis django-celery-beat django-environ flower<br>(venv) benji@BENJAMIN:~/email_newsletter$ pip freeze &gt; requirements.txt<br>(venv) benji@BENJAMIN:~/email_newsletter$ django-admin startproject core<br>(venv) benji@BENJAMIN:~/email_newsletter$ django-admin startproject email_newsletter<br>(venv) benji@BENJAMIN:~/email_newsletter$ django-admin startapp core<br>(venv) benji@BENJAMIN:~/email_newsletter$ django-admin startapp users</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/727/1*V8gdlhXj1PuAF_7R2-vfQA.png" /></figure><p>Time to add your Django apps and third-party packages to our project</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/773/1*w1rfugmGVYpF5FCnHW3-FQ.png" /></figure><p>Paste in all these necessary configurations at the bottom of the settings.py <br>These contain the rest framework, Djoser, drf-spectacular, JWT, Celery, and Redis settings.</p><pre>### MY SETTINGS ###<br>DJOSER = {<br>    &#39;SERIALIZERS&#39;: {<br>        &#39;user_create&#39;: &#39;users.serializers.UserCreateSerializer&#39;,<br>        &#39;current_user&#39;: &#39;users.serializers.CurrentUserSerializer&#39;,<br>    }<br>}<br><br><br># REST FRAMEWORK SETTINGS<br>REST_FRAMEWORK = {<br>    &#39;DEFAULT_AUTHENTICATION_CLASSES&#39;: (<br>        &#39;rest_framework_simplejwt.authentication.JWTAuthentication&#39;,<br>    ),<br>    &#39;DEFAULT_SCHEMA_CLASS&#39;: &#39;drf_spectacular.openapi.AutoSchema&#39;,  # drf-spectacular settings<br>    &#39;DEFAULT_PARSER_CLASSES&#39;: [<br>        &#39;rest_framework.parsers.JSONParser&#39;,<br>        &#39;rest_framework.parsers.FormParser&#39;,<br>        &#39;rest_framework.parsers.MultiPartParser&#39;,<br>    ],<br>}<br><br>SIMPLE_JWT = {<br>    &#39;AUTH_HEADER_TYPES&#39;: (&#39;JWT&#39;,),<br>    &quot;ACCESS_TOKEN_LIFETIME&quot;: timedelta(days=1),<br>    &quot;REFRESH_TOKEN_LIFETIME&quot;: timedelta(days=2),<br>}<br><br><br><br># API Docs settings<br>SPECTACULAR_SETTINGS = {<br>    &#39;TITLE&#39;: &#39;Email Newsletter App API&#39;,<br>    &#39;DESCRIPTION&#39;: &#39;Email Newsletter App project API Documents&#39;,<br>    &#39;VERSION&#39;: &#39;1.0.0&#39;,<br>    &#39;SCHEMA_PATH_PREFIX&#39;: &#39;/backend/api/*&#39;,<br>    # &#39;SERVE_PERMISSIONS&#39;: [&#39;rest_framework.permissions.IsAuthenticated&#39;],<br>    &#39;DISABLE_ERRORS_AND_WARNINGS&#39;: True,<br>    &#39;COMPONENT_SPLIT_REQUEST&#39;: True<br>}<br><br>BASE_BACKEND_URL = &quot;http://localhost:8000&quot;<br><br><br># Celery settings<br>CELERY_BROKER_URL = &#39;redis://127.0.0.1:6379/0&#39;<br>CELERY_ACCEPT_CONTENT=[&#39;application/json&#39;]<br>CELERY_RESULT_SERIALIZER=&#39;json&#39;<br>CELERY_TASK_SERIALIZER=&#39;json&#39;<br>CELERY_TIMEZONE=&#39;Africa/Lagos&#39;<br>CELERY_RESULT_BACKEND = &#39;django-db&#39;<br>CELERY_TASK_TRACK_STARTED = True<br>CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True<br><br># Celery beat settings<br>CELERY_BEAT_SCHEDULER = &#39;django_celery_beat.schedulers:DatabaseScheduler&#39;</pre><p>Copy this code into the project’s/ __init__.py file</p><pre>from __future__ import absolute_import, unicode_literals<br>from .celery import app as celery_app<br><br><br>__all__ = (&#39;celery_app&#39;,)</pre><p>This code snippet ensures that the Celery app is always imported when Django starts. By importing the Celery instance (app) from your Celery configuration file and including it in __all__, it makes celery_app available for other modules in your project, allowing them to recognize and use the Celery app.</p><p>Again, in the project’s folder create a celery.py file</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/572/1*qKXpJn3fqF3H5bZsDDJ1lw.png" /></figure><p>Paste the following code into the file</p><pre>from __future__ import absolute_import, unicode_literals<br>import os<br>from celery import Celery<br><br>os.environ.setdefault(&#39;DJANGO_SETTINGS_MODULE&#39;, &#39;email_newsletter.settings&#39;)<br><br>app = Celery(&#39;email_newsletter&#39;)<br><br>app.config_from_object(&#39;django.conf:settings&#39;, namespace=&#39;CELERY&#39;)<br>app.autodiscover_tasks()</pre><ul><li><strong>Importing necessary modules:</strong> It ensures compatibility and imports the needed modules.</li><li><strong>Setting default Django settings:</strong> os.environ.setdefault specifies your Django settings module (email_newsletter.settings).</li><li><strong>Creating a Celery app instance:</strong> app = Celery(&#39;email_newsletter&#39;) creates a Celery application instance named email_newsletter.</li><li><strong>Loading Celery settings:</strong> app.config_from_object loads Celery settings from Django&#39;s settings file using the CELERY namespace.</li><li><strong>Discovering tasks:</strong> app.autodiscover_tasks() automatically finds tasks defined in your Django apps.</li></ul><p>Now we run the migrations command to add the celery tables to our database and to test that there are no errors in our configurations before we run the server.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/748/1*vS50WtvrVUGP_tju_P9hQQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/712/1*VMyW6-aq64yVKeyWbEWrZw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/714/1*xmtV5l_N21qtPHnmziWeDA.png" /></figure><p>Our Django server is up and running. Now the easy part is done — time to dockerize our application.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/0*HidLpvYJFRBdcbHf" /></figure><p>Buh, wait we still don’t know what the heck Celery, Celery beat, and Flower is right? Let’s break it down for you.</p><p><strong>Celery</strong> is a task manager. Think of it like a personal assistant for your app — it handles tasks in the background so your app doesn’t get stuck doing boring, time-consuming things (like sending emails or processing data). Your app can keep doing its cool stuff while Celery takes care of the heavy lifting in the background.</p><p>Now, <strong>Celery Beat</strong> is like a scheduler for Celery. If you want something done repeatedly — like sending out email newsletters every morning at 9 AM — Celery Beat makes that happen. It tells Celery, “Hey, do this task at this time.”</p><p>And <strong>Flower</strong>? It’s the dashboard where you can see all of Celery’s hard work. It’s like the control center where you monitor the tasks that Celery is doing. You can check if tasks are running fine, or if something’s stuck.</p><p>They’re all connected because Celery does the actual work, Celery Beat schedules recurring work, and Flower lets you see the work being done in real time. It’s like having a well-oiled machine working in the background, and Flower is your window to watch it all happen</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/0*6sUNnT9ZiYWC_Rvd" /></figure><p>Let’s take a few moments and talk about the elephant in the room Docker and Docker compose and how they are interconnected🤔</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*lBKdpz_2Xa7oP1sWibyrlQ.jpeg" /></figure><p>Docker is like your go-to box for packaging up your app and all its stuff — dependencies, code, and all — so it runs the same way no matter where you launch it. But what if your app needs a bunch of these boxes, like one for the app itself, one for the database, and maybe another for some background worker? Docker Compose. It’s like the project manager coordinating all these Docker boxes, ensuring they work together smoothly. Instead of launching each box manually, you give Docker Compose the game plan, and it handles the rest, spinning up your whole setup with a single command.</p><p>So, Docker is the box, and Docker Compose is the brains behind it all. Easy peasy! Back to the tutorial🙂</p><p>Create a .dockerignore and Dockerfile in the root directory of our project<br>A .dockerignore file is used to specify which files and directories should be ignored by Docker when building an image. It works similarly to a .gitignore file in Git.</p><p>Paste this code in the .<strong>dockerignore </strong>file</p><pre>.env<br>venv/<br>Dockerfile<br>migrations/<br>__pycache__/<br>db.sqlite3<br>.DS_Store</pre><p>Paste the following code inside the <strong>Dockerfile</strong></p><pre># Use an official Python runtime as a parent image<br>FROM python:3.10-slim<br><br># Set environment variables<br>ENV PYTHONUNBUFFERED=1<br><br># Set the working directory in the container<br>WORKDIR /app<br><br># Install dependencies<br>RUN apt-get update &amp;&amp; apt-get install -y \<br>    python3-venv \<br>    linux-headers-amd64 \<br>    build-essential \<br>    &amp;&amp; rm -rf /var/lib/apt/lists/*<br><br># Create a virtual environment<br>RUN python3 -m venv /opt/venv<br><br># Activate the virtual environment<br>ENV PATH=&quot;/opt/venv/bin:$PATH&quot;<br><br># Copy the requirements file into the container<br>COPY requirements.txt .<br><br># Install the dependencies<br>RUN pip install --upgrade pip &amp;&amp; pip install -r requirements.txt<br><br># Copy the current directory contents into the container at /app<br>COPY . .<br><br># Expose port 8000 for the Django application<br>EXPOSE 8000<br><br># Run the Django application<br>CMD [&quot;sh&quot;, &quot;-c&quot;, &quot;python manage.py migrate &amp;&amp; python manage.py runserver 0.0.0.0:8000&quot;]</pre><ul><li><strong>Base Image</strong>: You’re starting with an official slim version of Python 3.10 (python:3.10-slim). This is like saying, &quot;Hey, give me a lightweight Python environment to build my stuff on.&quot;</li><li><strong>Environment Variable</strong>: ENV PYTHONUNBUFFERED=1 ensures that your Python output gets printed directly to the console, which is helpful when debugging.</li><li><strong>Working Directory</strong>: WORKDIR /app is where you’re telling Docker, &quot;Hey, all the magic happens here,&quot; so everything else you do from now on is relative to this directory.</li><li><strong>Install Dependencies</strong>: You’re installing a few necessary packages with apt-get to set up the environment for Python. Think of it as prepping the kitchen with the right tools before you start cooking.</li><li><strong>Virtual Environment</strong>: You’re creating a Python virtual environment (python3 -m venv /opt/venv) to keep your project’s dependencies isolated. It’s like having a sandbox where your project can play without messing with other projects.</li><li><strong>Activate the Virtual Environment</strong>: ENV PATH=&quot;/opt/venv/bin:$PATH&quot; makes sure that any Python commands run within this virtual environment, so you’re using the isolated dependencies you just set up.</li><li><strong>Copying Requirements</strong>: COPY requirements.txt . brings in your requirements.txt file into the container, which lists all the Python packages your project needs.</li><li><strong>Install Python Packages</strong>: RUN pip install --upgrade pip &amp;&amp; pip install -r requirements.txt upgrades pip (Python’s package installer) and installs all the packages listed in requirements.txt. It’s like grabbing all the ingredients you need for your recipe.</li><li><strong>Copy Project Files</strong>: COPY . . moves everything from your current directory into the container’s working directory (/app). You’re putting all your code into the container.</li><li><strong>Expose Port</strong>: EXPOSE 8000 makes port 8000 available to the outside world. This is where your Django app will be accessible from.</li><li><strong>Run the App</strong>: Finally, the CMD command runs your Django app by applying any migrations (updates to the database) and starting the development server so you can see it live.</li></ul><p>Now ensure you have Docker desktop installed on your machine to build our docker image. <a href="https://docs.docker.com/desktop/">Download Docker desktop</a>. Instructions on how to install it and get it working on your machine 👉<a href="https://docs.docker.com/desktop/install/windows-install/">Link</a></p><p>Run the command below in the terminal. This builds our created dockerfile for our Django application.</p><pre>benji@BENJAMIN:~/email_newsletter$ docker build .                                                          8.3</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/833/1*Cab5NE00m2I0KCsrUcIzAA.png" /></figure><p>Ensure that the Docker build stage reaches this point. This indicates that the build was successful and the image has been created.</p><p>Next, create a <strong>docker-compose.yml</strong> file in the <strong>same directory</strong> as that of the Dockerfile and paste the code below into the file.</p><pre>services:<br>  web:<br>    image: email_newsletter<br>    container_name: email_newsletter<br>    build: .<br>    ports:<br>      - 8000:8000<br>    volumes:<br>      - ./:/app<br>    command: &gt;<br>      sh -c &quot;python manage.py migrate &amp;&amp;<br>             python manage.py runserver 0.0.0.0:8000&quot;<br>    <br>    depends_on:<br>      - postgres-db<br>      - redis<br><br>  postgres-db:<br>    image: postgres:latest<br>    container_name: my-postgres-container<br>    restart: always<br>    environment:<br>      POSTGRES_PASSWORD: examplepassword<br>      POSTGRES_USER: exampleuser<br>      POSTGRES_DB: exampledb<br>    volumes:<br>      - postgres_db_data:/var/lib/postgresql/data<br><br><br>  redis:<br>    image: redis:alpine<br>    container_name: my-redis-container<br>    restart: always<br>  <br>  celery:<br>    build: .<br>    container_name: my-celery-container<br>    volumes:<br>      - ./:/app<br>    command: &gt;<br>      sh -c &quot;celery -A email_newsletter.celery worker -l info&quot; <br>    depends_on:<br>      - redis<br>      - web<br><br>  flower:<br>    build: .<br>    container_name: my-flower-container<br>    command: celery -A email_newsletter flower --basic_auth=admin:password123<br>    volumes:<br>      - ./:/app<br>    ports:<br>      - &quot;5555:5555&quot;<br>    depends_on:<br>      - redis<br><br>  beat:<br>    build: .<br>    container_name: my-beat-container<br>    command: celery -A email_newsletter beat -l INFO --scheduler django_celery_beat.schedulers:DatabaseScheduler<br>    volumes:<br>      - .:/app<br>    depends_on:<br>      - redis<br>      - web<br><br><br>volumes:<br>  postgres_db_data:</pre><h3>1. Web Service (web)</h3><ul><li><strong>Image &amp; Name:</strong> Runs the main app (email_newsletter) in a container.</li><li><strong>Ports:</strong> Maps port 8000 on your computer to port 8000 inside the container.</li><li><strong>Volumes:</strong> Links the current directory (./) to the /app directory inside the container, so any changes you make locally are reflected in the container.</li><li><strong>Command:</strong> Runs Django database migrations and starts the web server.</li><li><strong>Depends on:</strong> Ensures that postgres-db and redis services start first.</li></ul><h3>2. Postgres Service (postgres-db)</h3><ul><li><strong>Image &amp; Name:</strong> Runs a Postgres database in a container.</li><li><strong>Environment:</strong> Sets the database name, user, and password.</li><li><strong>Volumes:</strong> Stores Postgres data in a Docker-managed volume (postgres_db_data), so it persists even if the container is stopped.</li></ul><h3>3. Redis Service (redis)</h3><ul><li><strong>Image &amp; Name:</strong> Runs a Redis instance (used as a message broker for Celery tasks).</li><li><strong>Restart:</strong> Always restart the container if it stops for any reason.</li></ul><h3>4. Celery Service (celery)</h3><ul><li><strong>Command:</strong> Starts a Celery worker, handling background tasks (like sending emails).</li><li><strong>Depends on:</strong> Waits for redis and web services to start first.</li></ul><h3>5. Flower Service (flower)</h3><ul><li><strong>Command:</strong> Starts Flower, a web-based monitoring tool for Celery tasks, and sets a password to login to the dashboard</li><li><strong>Ports:</strong> Exposes Flower on port 5555.</li><li><strong>Depends on:</strong> Starts after redis.</li></ul><h3>6. Beat Service (beat)</h3><ul><li><strong>Command:</strong> Starts Celery Beat, which schedules periodic tasks (like sending email newsletters at set intervals).</li><li><strong>Depends on:</strong> Waits for redis and web to start first.</li></ul><h3>7. Volumes (volumes)</h3><ul><li><strong>Postgres Data:</strong> Defines a volume for storing the Postgres database data, so it doesn’t get lost when the container is stopped.</li></ul><p>Run the command in the terminal docker compose up --build This command starts up all the container services as seen below</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9colQ7sZM3rTkAipQiL1mA.png" /></figure><p>Head over to localhost:5555 to view the flower dashboard😁. A popup window will require you to input the configured username<strong>(admin)</strong> and password<strong>(password123).</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jmaKtwiCyj6fyYu-kCyQ9Q.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/0*2bn1M0NUGAIYaVL9.gif" /></figure><p>So at this point, all our container services should be up and running. If you encounter any errors I’d be happy to address them in the comments section😁. Let’s move over to configuring the Django project’s app data models, views, serializers, and async celery tasks.</p><p>Create a python file called manager.py in the core app folder and paste in the code below</p><pre>from django.contrib.auth.base_user import BaseUserManager<br>from django.utils.translation import gettext as _<br><br><br>class CustomUserManager(BaseUserManager):<br>    &quot;&quot;&quot;<br>    Custom user model manager where email is the unique identifier<br>    for authentication instead of usernames.<br>    &quot;&quot;&quot;<br><br>    def create_user(self, email, password, **extra_fields):<br>        if not email:<br>            raise ValueError(_(&#39;Users must have an email address&#39;))<br>        email = self.normalize_email(email)<br>        user = self.model(email=email, **extra_fields)<br>        user.set_password(password)<br>        user.save()<br>        return user<br><br>    def create_superuser(self, email, password, **extra_fields):<br>        extra_fields.setdefault(&#39;is_staff&#39;, True)<br>        extra_fields.setdefault(&#39;is_superuser&#39;, True)<br>        extra_fields.setdefault(&#39;is_active&#39;, True)<br>        extra_fields.setdefault(&#39;is_admin&#39;, True)<br><br>        if extra_fields.get(&#39;is_staff&#39;) is not True:<br>            raise ValueError(_(&#39;Superuser must have is_staff=True.&#39;))<br>        if extra_fields.get(&#39;is_superuser&#39;) is not True:<br>            raise ValueError(_(&#39;Superuser must have is_superuser=True.&#39;))<br>        return self.create_user(email, password, **extra_fields)</pre><p>This code defines a custom manager for user accounts where <strong>email</strong> is used as the login instead of a username.</p><p>In the CustomUserManager class:</p><ul><li>create_user: This method creates a regular user. It checks if an email is provided, then normalizes the email, sets the password, and saves the user.</li><li>create_superuser: This method creates a superuser (admin). It ensures that certain fields like is_staff, is_superuser, and is_admin are set to True. If not, it throws an error.</li></ul><p>Next, in the models.pyfile. Paste the code below</p><pre>from django.db import models<br>from django.contrib.auth.models import AbstractUser<br>from .manager import CustomUserManager<br><br># Create your models here.<br>class CustomUser(AbstractUser):<br>    GENDER_CHOICES = [<br>        (&#39;male&#39;, &#39;Male&#39;),<br>        (&#39;female&#39;, &#39;Female&#39;),<br>    ]<br>    username = models.CharField(unique=True, max_length=50, blank=False, null=False)<br>    email = models.EmailField(unique=True, blank=False, null=False)<br>    gender = models.CharField(max_length=10, choices=GENDER_CHOICES, null=False, blank=False)<br>    is_admin = models.BooleanField(default=False)<br>    is_active = models.BooleanField(default=True)<br>    newsletter_subscribed = models.BooleanField(default=True)<br>    created_at = models.DateTimeField(auto_now_add=True)<br>    updated_at = models.DateTimeField(auto_now=True)<br><br>    objects = CustomUserManager()<br><br>    USERNAME_FIELD = &#39;email&#39;<br>    REQUIRED_FIELDS = [&#39;username&#39;]<br><br>    def __str__(self):<br>        return self.username<br><br>class MessageBoard(models.Model):<br>    subscribers = models.ManyToManyField(CustomUser, related_name=&#39;messageboard&#39;, blank=True)<br>    created_at = models.DateTimeField(auto_now_add=True)<br>    updated_at = models.DateTimeField(auto_now=True)<br>    <br>    def __str__(self):<br>        return str(self.id)</pre><p>Now let’s make a slight change to our settings.py file</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/676/1*SKOsw0TuaD8QO0UWHsKAag.png" /></figure><p>This means that the default user model for a Django project is the <strong>CustomUser</strong> model</p><p>Configure the project urls.py file for Swagger and Djoser endpoints</p><pre>from django.contrib import admin<br>from django.urls import path, include<br>from drf_spectacular.views import (<br>    SpectacularAPIView,<br>    SpectacularSwaggerView,<br>    SpectacularRedocView,<br>)<br><br>urlpatterns = [<br>    path(&#39;admin/&#39;, admin.site.urls),<br>    path(&#39;api/v1/schema/&#39;, SpectacularAPIView.as_view(), name=&#39;schema&#39;),<br>    path(&#39;api/v1/schema/swagger/&#39;, SpectacularSwaggerView.as_view(), name=&#39;swagger-ui&#39;),<br>    path(&#39;api/v1/schema/redoc/&#39;, SpectacularRedocView.as_view(url_name=&#39;schema&#39;), name=&#39;redoc&#39;),<br><br><br>    # AUTHENTICATION API ENDPOINTS<br>    path(&#39;auth/v1/&#39;, include(&#39;djoser.urls&#39;)),<br>    path(&#39;jwt/v1/&#39;, include(&#39;djoser.urls.jwt&#39;)),<br>]</pre><p>Testing the swagger endpoint and it works as expected. I know I’m too good😎🔥</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PFobN88acLK_PEkQZBoeyQ.png" /></figure><p>In the users app folder, create a new file named serializers.py. Paste the code below into the file.</p><pre>from djoser.serializers import UserSerializer, UserCreateSerializer as BaseUserSerializer<br>from django.core.exceptions import ValidationError<br><br><br>class UserCreateSerializer(BaseUserSerializer):<br>    class Meta(BaseUserSerializer.Meta):<br>        fields = [&#39;id&#39;, &#39;email&#39;, &#39;gender&#39;, &#39;username&#39;, &#39;password&#39;]<br><br><br><br>class CurrentUserSerializer(UserSerializer):<br>    class Meta(UserSerializer.Meta):<br>        fields = [&#39;id&#39;, &#39;email&#39;, &#39;username&#39;]</pre><p>This code customizes two serializers using the Djoser library, commonly used for user authentication in Django projects.</p><ol><li><strong>UserCreateSerializer</strong>: This serializer handles user creation. It adds extra fields like gender, email, username, and password when a user is being created.</li><li><strong>CurrentUserSerializer</strong>: This serializer manages data for the currently logged-in user, including their id, email, and username.</li></ol><p>In simple terms, these serializers define how user data is formatted and handled when creating a new account or viewing the current user’s info.</p><p>Still in the users folder, create a signals.py file. This file will automatically create message board instances using Django signals and assign newly created users to the specified message board.</p><pre>from django.db.models.signals import post_save<br>from django.dispatch import receiver<br>from core.models import CustomUser, MessageBoard<br><br>@receiver(post_save, sender=CustomUser)<br>def add_user_to_message_board(sender, instance, created, **kwargs):<br>    if created:<br>        # Automatically create a MessageBoard instance if none exists<br>        message_board, created = MessageBoard.objects.get_or_create(id=1)<br>        <br>        # Add the user to the message board subscribers if they subscribe to the newsletter<br>        if instance.newsletter_subscribed:<br>            message_board.subscribers.add(instance)<br>            message_board.save()</pre><p>Testing the user creation endpoint auth/v1/users/ 👇</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WbXh_bJ3qdlRj3NrKwi-Bw.png" /></figure><p><strong>Response👇</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/689/1*totmJlScYzDswj0ihh1ZSg.png" /></figure><p>Now, in the users app folder create a new Python file named tasks.py. Paste the code below into the file.</p><pre>from celery import shared_task<br>from core.models import MessageBoard<br>from django.conf import settings<br>from datetime import datetime<br>from django.template.loader import get_template<br>from django.core.mail import EmailMessage<br><br>@shared_task(name=&#39;email_newsletter&#39;)<br>def send_newsletter():<br>    subject = &quot;Your Monthly Newsletter&quot;<br>    <br>    subscribers = MessageBoard.objects.get(id=1).subscribers.filter(<br>        newsletter_subscribed=True,<br>    )<br>    <br>    for subscriber in subscribers:<br>        print(subscriber.email)<br>        message = get_template(&#39;newsletter.html&#39;).render(context={&#39;name&#39;: subscriber.username})<br>        mail = EmailMessage(<br>            subject=subject, <br>            body=message, <br>            from_email=settings.EMAIL_HOST_USER,<br>            to=[subscriber.email],<br>            reply_to=[settings.EMAIL_HOST_USER],<br>            )<br>        mail.content_subtype = &quot;html&quot;<br>        mail.send()<br>    <br>    current_month = datetime.now().strftime(&#39;%B&#39;)<br>    subscriber_count = subscribers.count()   <br>    return f&#39;{current_month} Newsletter to {subscriber_count} subs&#39;</pre><ul><li><strong>Task Definition</strong>: The @shared_task decorator makes this a background task that can run asynchronously, meaning it won’t slow down your app.</li><li><strong>Fetching Subscribers</strong>: It grabs all users from a message board who are subscribed to the newsletter.</li><li><strong>Sending Emails</strong>: For each subscriber, it renders an HTML email template (newsletter.html) with the subscriber&#39;s name and sends the email</li><li><strong>Return Statement</strong>: After sending the emails, it returns a message indicating how many newsletters were sent that month.</li></ul><p>You can now stop your docker containers using the command docker compose down and spin it up again using the command docker compose up <br>This is to ensure that we configured the task properly and Celery is picking it up too.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HZ2tBJJWvB3b3-DmwOSCqA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/689/1*YR_9paJ0-rm2iiaLnGmH-g.png" /></figure><p>Create a templates folder in the root directory of the project and use any newsletter HTML template of your choice. Link to mine 👉 <a href="https://www.benchmarkemail.com/email-templates/networking-event-invitation-template/">Download link</a></p><p>Configure the settings.py file to find your template directory</p><pre>TEMPLATES = [<br>    {<br>        &#39;BACKEND&#39;: &#39;django.template.backends.django.DjangoTemplates&#39;,<br>        &#39;DIRS&#39;: [os.path.join(BASE_DIR, &#39;templates&#39;)],<br>        &#39;APP_DIRS&#39;: True,<br>        &#39;OPTIONS&#39;: {<br>            &#39;context_processors&#39;: [<br>                &#39;django.template.context_processors.debug&#39;,<br>                &#39;django.template.context_processors.request&#39;,<br>                &#39;django.contrib.auth.context_processors.auth&#39;,<br>                &#39;django.contrib.messages.context_processors.messages&#39;,<br>            ],<br>        },<br>    },<br>]</pre><p>Let’s configure our project settings to send emails to our subscribers. First install django-environ to read the environment variables for email config.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/731/1*JMQaw3O3NNi_HITl0tHPQQ.png" /></figure><p>Next, import the paste the code below the initial imports in the settings.py file</p><pre>import environ<br><br>env = environ.Env()<br># Build paths inside the project like this: BASE_DIR / &#39;subdir&#39;.<br>BASE_DIR = Path(__file__).resolve().parent.parent<br># Take environment variables from .env file<br>environ.Env.read_env(os.path.join(BASE_DIR, &#39;.env&#39;))</pre><p>The code just tells Django where to find the .env file in our application which is in the root directory of the project</p><pre># EMAIL SETTINGS<br>EMAIL_HOST_USER = env(&#39;SENDER_EMAIL&#39;)<br>EMAIL_HOST_PASSWORD = env(&#39;SENDER_EMAIL_PASSWORD&#39;)<br>EMAIL_BACKEND = &#39;django.core.mail.backends.smtp.EmailBackend&#39;<br>EMAIL_HOST = &#39;smtp.gmail.com&#39; <br>EMAIL_PORT = 465 <br>EMAIL_USE_TLS = False  <br>EMAIL_USE_SSL = True</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/552/1*7rh05vYEuZRh3Kxqfhz7eA.png" /></figure><p>Now create .env file in the root directory of the Django project</p><pre>SENDER_EMAIL=&lt;your-email-address&gt;<br>SENDER_EMAIL_PASSWORD=&lt;your-email-address-app-password&gt;</pre><p>Tutorial on how to generate your app passwords 👉<a href="https://knowledge.workspace.google.com/kb/how-to-create-app-passwords-000009237">Link</a></p><p>It’s time to configure the schedule for our celery beat newsletter task</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/0*msw4_PwvQ1p2FH_B" /></figure><p>At the bottom of the settings.py file add the code below.</p><pre>CELERY_BEAT_SCHEDULE = {<br>    &#39;send_newsletter&#39;: {<br>        &#39;task&#39;: &#39;email_newsletter&#39;,  # Path to your Celery task<br>        &#39;schedule&#39;: crontab(hours=0, minute=&quot;*/1&quot;), # Change to match your specification<br>    },<br>}</pre><p>You can use the crontab schedule to specify a wide range of periodic tasks:</p><ul><li><strong>Every minute</strong>: crontab(minute=&#39;*/1&#39;)</li><li><strong>Every hour</strong>: crontab(minute=0)</li><li><strong>Specific days of the week</strong> (e.g., every Monday): crontab(minute=&#39;*/1&#39;, hour=0, day_of_week=&#39;monday&#39;) etc</li><li><strong>More complex schedules</strong>: See the <a href="https://docs.celeryq.dev/en/stable/reference/celery.schedules.html"><strong>Celery beat documentation</strong></a> for more crontab options.</li></ul><p>You can change the crontab shecdule to suit your needs e.g(first of every month by 12:00AM)👇</p><pre>CELERY_BEAT_SCHEDULE = {<br>    &#39;send_newsletter&#39;: {<br>        &#39;task&#39;: &#39;email_newsletter&#39;, <br>        &#39;schedule&#39;: crontab(hours=0, minute=0, day_of_week=1), <br>    },<br>}</pre><p>I won’t forget the necessary import at the top of the settings.py file😅</p><pre>from celery.schedules import crontab</pre><p>All that’s left to do is test our celery &amp; celery beat configs now</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/356/0*b8gJOZOIR-BZiYJp.gif" /></figure><p>Running the command docker compose down then docker compse up to refresh your containers</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/902/1*Hcm6lrHVS4nO3uCjiOMHbA.png" /></figure><p>Lets’s check the celery and celery beat container to see it our task is being executed</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/966/1*kmiqTKCSGViS3gRUOc5jHA.png" /><figcaption>Celery container logs</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/969/1*nNxMR5Mt_TEjdrhYlKlKHg.png" /><figcaption>Celery beat container logs</figcaption></figure><p>As seen above the tasks are being executed every minute as seen on the container logs</p><p>Going over to Flower’s task monitoring system, we can see the task execution details below👇</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UJDYOGgo8M4x_e3mqXCXcA.png" /></figure><p>Clicking into one of the tasks I get a more in-deapth detail on its excecution</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/891/1*lihaL80Yp__lrUl1xly4gA.png" /></figure><p>Email newsletter in my inbox🥳🥳🥳🙌🙌🙌🙌</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FGv8OrzfD_K3fbPhL_LwSA.png" /></figure><p>As simple as that you can send email newsletters from your application to your customers. Easy-peasy😉</p><p>Link to project Github repo 👉<a href="https://github.com/Benji918/email_newsletter">Link</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/0*TbIKX6XzYjkS6WQI" /></figure><p>Psst, have I got a sweet deal for 🫵🏽. Want to take your Django app to the next level?🫰🏽🔥. Check out this awesome tutorial on <a href="https://www.kowe.io/guide/deploy-a-dockerized-django-app-on-aws-ec2-with-rds-s3-gunicorn-nginx-and-github-actions-cicd"><strong>Deploy a Dockerized Django App on AWS EC2 with RDS, S3, Gunicorn, Nginx, and GitHub Actions CI/CD</strong></a><strong>. </strong>The best part? You’ll get hands-on experience and a smoother deployment process. But wait, there’s more — use my coupon code <strong>BENEARLYEC2 </strong>to<strong> </strong>get <strong>50% off</strong>! Happy coding!😊👋🏽</p><h3>In Plain English 🚀</h3><p><em>Thank you for being a part of the </em><a href="https://plainenglish.io"><strong><em>In Plain English</em></strong></a><em> community! Before you go:</em></p><ul><li>Be sure to <strong>clap</strong> and <strong>follow</strong> the writer ️👏<strong>️️</strong></li><li>Follow us: <a href="https://twitter.com/inPlainEngHQ"><strong>X</strong></a> | <a href="https://www.linkedin.com/company/inplainenglish/"><strong>LinkedIn</strong></a> | <a href="https://www.youtube.com/channel/UCtipWUghju290NWcn8jhyAw"><strong>YouTube</strong></a> | <a href="https://discord.gg/in-plain-english-709094664682340443"><strong>Discord</strong></a> | <a href="https://newsletter.plainenglish.io/"><strong>Newsletter</strong></a></li><li>Visit our other platforms: <a href="https://cofeed.app/"><strong>CoFeed</strong></a> | <a href="https://differ.blog/"><strong>Differ</strong></a></li><li>More content at <a href="https://plainenglish.io"><strong>PlainEnglish.io</strong></a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=26d94da5ac49" width="1" height="1" alt=""><hr><p><a href="https://python.plainenglish.io/unlock-async-email-magic-with-docker-compose-celery-flower-redis-in-django-rest-framework-drf-26d94da5ac49">Unlock Async Email Magic with Docker-compose, Celery, Flower &amp; Redis in Django Rest Framework(DRF)</a> was originally published in <a href="https://python.plainenglish.io">Python in Plain English</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[DevOps: A love-hate relationship]]></title>
            <link>https://medium.com/@kodiugos/devops-a-love-hate-relationship-8346240bf23d?source=rss-99c5bfceed4b------2</link>
            <guid isPermaLink="false">https://medium.com/p/8346240bf23d</guid>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[coding]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Ugochukwu Benjamin .C]]></dc:creator>
            <pubDate>Sun, 14 Jul 2024 00:24:55 GMT</pubDate>
            <atom:updated>2024-08-11T21:33:00.252Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U1c9M6NfXTLRJN3FbRmm2g.jpeg" /><figcaption>AI-generated image</figcaption></figure><p>DevOps! DevOps! DevOps! We’ve all heard of it countless times. But as a backend developer, that word echoes in my mind whenever I’m watching a coding video, taking a course, or reading an article. The buzz around Docker and Kubernetes in the tech world got me all fired up, so I decided to dive in and see what the fuss was all about!🤷🏽‍♂️</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*d3kaRVdVdwT7ju1SrfRhYA.jpeg" /></figure><p>I got hooked on DevOps because I realized I needed to work smarter, especially with deployments. Docker kept popping up everywhere—at meetups, on tech blogs, and from my colleagues. So, I decided to dive in and bought a course on Udemy. Learning about containers, images, volumes, bind mounts, and container networking was like learning a new language!😵‍💫. Setting up Docker on my local machine during the initial stage was a nightmare. It’s like when you’re following a coding tutorial and suddenly everything goes haywire with a bunch of errors. So frustrating, right? Anyway, back to the story.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/690/1*LZiv73TuerL6qCqHY2OQxQ.jpeg" /><figcaption>Docker errors</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/690/1*bVB261DVSkQvOFlhIUONhQ.png" /><figcaption>Docker errors</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/678/1*Y0-6VupMBZMfK3PnBWUb1w.jpeg" /><figcaption>Docker errors</figcaption></figure><p>Man, setting up Docker desktop was a nightmare! I just wanted to learn Docker, but instead, I got to deal with a bunch of f**king🤬 error messages that made no sense to me. You know that moment in a coding tutorial when everything goes to hell and you’re suddenly drowning in terminal errors? Yep, that was me😣. It took me a whole two weeks to figure it all out. I was Googling, trying things out, and entering weird commands in my terminal left and right. I was so close to giving up. It’s kinda funny, right? It’s not at all🙂. But hey, after all that struggle, I finally made it to the home screen of Docker desktop. What a relief! 😮‍💨</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/714/1*PI3JrIaPLT-Q_fA4NIk4yg.png" /><figcaption>Docker desktop home page</figcaption></figure><p>After moving past the initial stage of docker — learning the Docker CLI commands and their purposes, creating, pulling, and stopping containers. I got the hang of it, Docker and I were inseparable. Within a week, I was spinning up containers like a pro😎. Seeing &quot;Hello World&quot; in my terminal felt like magic. It felt like a small step for man and a giant leap for humanity kinda vibe🤣. The power of Docker was undeniable. Gone were the days of &quot;it works on my machine&quot; excuses😂🫵. I even started experimenting with Docker Compose, orchestrating multi-container applications with ease. Though compose came with its hassles, luckily it was mostly just syntax errors because I had to accustom myself to the yaml formatting 😅.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/0*MAUU2rOnJxPJzlts.gif" /></figure><p>As my projects grew so did my demands. I needed something more robust. I needed an orchestration tool. Based on the course I was following they listed two Docker Swarm and Kubernetes. The instructor started by teaching docker swarm first then Kubernetes. At the back of my mind, I thought since docker wasn’t that hard to grasp I could blow past the docker swarm section like a breeze 😌. I was wrong 😫. Docker swarm section had a total of 12 videos. The first 3 videos were easy stuff, I could totally relate docker swarm CLI commands to the original docker CLI commands learned previously 👍🏽. Now I have a total of 9 more videos to watch and guess what!. I skipped all of them. Why?. Because I didn’t understand sh*t😑. I felt so stupid thinking it would be so easy, it took a heavy blow at my confidence💔.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/0*H4baF0pI27NADsYN" /></figure><p>Moving on, after taking the heavy blow from docker swarm I mustered the courage to move over to its big brother Kubernetes. The first time I looked at Kubernetes documentation, it felt like a maze. Pods, nodes, clusters, services, resource types, deployments😵‍💫... it was overwhelming. The learning curve was steep, and I felt like I was back to square one 😭.</p><p>The challenges hit hard. There were days I wanted to pull my hair out, especially when debugging obscure errors and the docs didn’t help much honestly. ChatGPT was my debugging partner through all this. Kubernetes introduced a whole lot of new complexities, and the learning curve remained steep. There were moments of doubt where I regretted my decision to dive into DevOps 😮‍💨</p><p>Yet, I persisted, I mean I was neck deep into it already so there was no point turning back now🤷🏽‍♂️. After spending countless of tinkering and troubleshooting, I managed to deploy my first application on a Kubernetes cluster. Seeing my app running on multiple nodes and being able to scale it up and down with replica sets seamlessly was both a relief and a triumph🙌🏽🙌🏽🙌🏽</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/0*cPCudhtLEcDh0zHQ" /></figure><p>Now from the article title you can see why I went with that. DevOps is both a blessing and a curse. The frustration is real but so are the rewards. For individuals looking to delve into DevOps. Do it! The journey is worth it. All these are hurdles you are bound to meet while learning. So putting off learning it for later as a backend developer is only gonna come back and bite 🫵🏽😂</p><p>Psst, have I got a sweet deal for 🫵🏽. Want to take your Django app to the next level?🫰🏽🔥. Check out this awesome tutorial on <strong>How to deploy your Django app on DigitalOcean Droplet and set up a CI/CD pipeline with GitHub Actions</strong>. The best part? You’ll get hands-on experience and a smoother deployment process. But wait, there’s more — use my <a href="https://www.kowe.io/guide/deploy-a-Django-app-on-digital-ocean-droplets-and-setup-a-CI-CD-pipeline-with-github-actions?coupon=BENEARLYDOD"><strong>affiliate link</strong></a>, and you get <strong>50% off</strong>! Happy coding!😊👋🏽</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8346240bf23d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Riding the Wave: Navigating Life’s Ups and Downs After Graduation]]></title>
            <link>https://medium.com/@kodiugos/riding-the-wave-navigating-lifes-ups-and-downs-after-graduation-68ab0d5805d6?source=rss-99c5bfceed4b------2</link>
            <guid isPermaLink="false">https://medium.com/p/68ab0d5805d6</guid>
            <category><![CDATA[life]]></category>
            <category><![CDATA[poetry]]></category>
            <category><![CDATA[learning]]></category>
            <category><![CDATA[life-lessons]]></category>
            <category><![CDATA[motivation]]></category>
            <dc:creator><![CDATA[Ugochukwu Benjamin .C]]></dc:creator>
            <pubDate>Thu, 02 May 2024 13:15:17 GMT</pubDate>
            <atom:updated>2024-05-02T13:17:01.899Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*r8dbcNmCweJ4JvfD" /><figcaption>Photo by Yogendra Singh on Unsplash</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*9oRc8z-Z2p6lR-2c" /><figcaption>Photo by Leon Wu on Unsplash</figcaption></figure><p>Upon signing out from our academic institutions and bidding farewell to my colleagues, many of us found ourselves thrown into the vast ocean of post-graduation life. In this article, I will recount my journey as I navigated through the highs and lows that came after my graduation.👨🏼‍🎓</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*jAe5-SaDelCTb2Ad" /><figcaption>Photo by Brett Jordan on Unsplash</figcaption></figure><p>Before I share my personal experiences, let me introduce myself. I am a 21-year-old solo entrepreneur who aspires to become a renowned backend developer in the tech industry👨🏼‍💻. I started my tech career at the age of 19, and it has been an incredible journey since then. From finding the right path to facing moments of doubt, I am proud of how far I’ve come🥹. Now, let’s move on to what’s next...</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*9HzXUD1zOrlA5ltg" /><figcaption>Photo by Jon Tyson on Unsplash</figcaption></figure><p>After bidding farewell to the confines of academia, I experienced a blend of excitement and uncertainty. I shared my achievements with my long-time classmates and friends. However, deep down, I felt apprehensive about what lay ahead. I mean, who wouldn’t?🤷🏼‍♂️ The prospect of departing from the protective bubble of school and venturing into the real world was daunting and sent shivers down my spine😬.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/650/1*xz-Lu8yNcbkHUcxB8rCjXg.jpeg" /></figure><p>The first few months after my graduation were full of enthusiasm and determination to excel in my career💪🏼. However, as time passed, my job search became a frustrating experience with endless applications and rejection emails🙍🏼‍♂️. Each rejection took a toll on my confidence, and it became difficult for me to maintain a positive outlook😭.</p><p>To change my approach, I decided to focus on freelancing on Upwork. I optimized my profile and portfolio, watched multiple YouTube videos from experts on crafting a good profile and writing proposals, and searched for the right jobs. Despite all my efforts, my proposal was only viewed twice out of the multiple proposals I sent, and I wasn’t able to secure any job. Although it was progress, it was still a bit depressing😞.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/368/1*FfS59nBiKjQ8RQhSX1fFAw.jpeg" /></figure><p>After spending weeks sending job applications and scouring online job boards, I finally landed a gig thanks to one of the tech group chats I’m in🙌🏼. The job was a simple technical writing task - I had to add extra sections to a Django Rest framework tutorial. The pay wasn’t amazing, but it was enough to provide some relief from the draining job application process and give me something else to channel my energy into. Unfortunately, this sense of relief turned out to be temporary💔.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/540/1*F6EhIQvYiVWJCZy9FZLK6A.jpeg" /></figure><p>For almost a month now, I have been actively searching for a remote tech job, as well as working on my coding side projects, completing my tech courses, and writing technical articles. Although this might seem like a lot to handle, it wasn’t the physical stress that got to me, but the mental exhaustion😮‍💨. I found myself losing interest in anything related to coding or technical writing and was unable to focus on anything. As a result, I decided to take a break from everything for three weeks😫. During this time, I binge-watched numerous TV series and anime that I had been putting off for a while, such as Good Doctor, Halo, Shogun, Invincible, Shadow and Bones, and Dune. I did everything in my power to distract myself. My father noticed that I wasn’t coding as much as I used to, but I didn’t explain why he probably wouldn’t have understood what I was going through.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*rgz281fZHdeW_bxK" /><figcaption>Photo by <a href="https://unsplash.com/@dariusbashar?utm_source=medium&amp;utm_medium=referral">Darius Bashar</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>During the three-week break, I spent a lot of time reflecting on why I started my tech journey. Although money is one of my driving forces, it wasn’t the reason I got into tech. I simply adore the process of using lines of code to solve real-world problems❤️. I knew it wouldn’t be easy, but that didn’t deter me💪🏼. One thing that added to my mental stress was the pressure to get a “lucky break” right after school. I’ve heard countless stories of people achieving groundbreaking success after school, and I wanted to be one of them. I pushed myself to the limit, but I guess I lost sight of why I started this journey in the first place. Adding to this, I believe not everyone will get that “lucky break&quot; right after school. Success comes in various forms, and sometimes it takes time to find your place in your desired industry. Stay true to your passion and keep nurturing that love for the craft. Everyone’s journey is unique, and it’s okay if yours unfolds differently from what you initially envisioned. Keep moving forward, one step at a time, and trust that your efforts will lead you to where you’re meant to be🫂.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*JnNgnbVHQUpxwTgY" /><figcaption>Photo by <a href="https://unsplash.com/@tesecreates?utm_source=medium&amp;utm_medium=referral">Matese Fields</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>As I reflect on the rollercoaster of emotions and experiences recounted in this journey, one truth stands out amidst the highs and lows: <strong>perseverance</strong> in the face of adversity is the cornerstone of <strong>personal growth📈</strong>. The transition from academia to the professional world is rarely a smooth ride; it’s a tumultuous voyage filled with uncertainty, doubt, and occasional setbacks. Yet, it’s precisely within these moments of struggle that our <strong>character is forged</strong>, and our <strong>resilience tested</strong>. It’s easy to feel disheartened when faced with rejection and uncertainty, but it’s in those moments of darkness that we discover our <strong>true strength</strong>. This journey isn’t just about finding a job or achieving success; it’s about discovering ourselves, our passions, and our purpose. So, to all those navigating the waves of post-graduation life, remember this: your journey may not unfold as planned, but trust in your abilities, stay true to your passions, and keep moving forward with unwavering determination. For it’s in the journey itself that we find the true beauty of life, amidst all its ups and downs🫵🏼.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=68ab0d5805d6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Asynchronously Send Emails in Django]]></title>
            <link>https://python.plainenglish.io/asynchronously-send-emails-in-django-1bb1f7be318e?source=rss-99c5bfceed4b------2</link>
            <guid isPermaLink="false">https://medium.com/p/1bb1f7be318e</guid>
            <category><![CDATA[celery]]></category>
            <category><![CDATA[asynchronous]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[django]]></category>
            <category><![CDATA[django-rest-framework]]></category>
            <dc:creator><![CDATA[Ugochukwu Benjamin .C]]></dc:creator>
            <pubDate>Mon, 25 Mar 2024 18:34:02 GMT</pubDate>
            <atom:updated>2025-02-14T19:53:25.580Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/916/1*MBeyDBshAUhJRs0_5U6ctQ.png" /></figure><p>If you’ve had issues sending emails asynchronously in Django, you&#39;re not alone. Many newbie developers rely on synchronously sending their Django emails. The result is just a horrible experience not just for the <strong>developers</strong>, but for the <strong>users </strong>too!!!</p><p>The consequences?</p><ul><li>Increased server load</li><li>Reduced scalability</li><li>Longer response times</li><li>Poor user experience</li><li>Risk of Email Throttling</li></ul><p>The list goes on and on….😫</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*EA0tQFusGesH3_5NWIQH5A.png" /></figure><p>I came across a pretty neat package called <strong>Celery</strong>. If configured and used correctly it could solve these problems instantly and without any hassle😅. In the coming sections, we’ll see how <strong>Celery</strong> enables asynchronous task execution, allowing email sending to be offloaded to background workers. Cheers🥂</p><h3>What is Celery?💁‍♂️</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ymX_rV0C8UVuXCCNKwC6MA.png" /></figure><p>Simply put, <strong>Celery </strong>is a powerful tool used in development to handle background tasks asynchronously. Imagine you’re running a website, and certain tasks need to be performed but don’t need to be done immediately as part of the regular request-response cycle. These tasks could include sending emails, processing large datasets, or performing periodic maintenance tasks.</p><p><strong>Celery </strong>allows you to offload these tasks to a separate process, known as a “<strong>worker</strong>,” which runs independently of your main application. This means that when a user triggers an action that requires one of these background tasks, your application can quickly respond without waiting for the task to be completed. The task is then sent to the<strong> Celery worker</strong> to be executed in the background, freeing up your application to handle other requests.</p><h3><strong>What is a Message Broker?🤔</strong></h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gEERfv1ZdGOwl1T-AWZSjA.png" /></figure><p>Think of a <strong>message broker</strong> as a postal service. When you want to send a letter to someone, you drop it off at the post office. The post office then sorts and delivers the letter to its destination. Similarly, in a <strong>Celery </strong>setup, when your application needs to execute a background task, it sends a <strong>message (task)</strong> to the <strong>message broker</strong>. The message broker receives the <strong>task </strong>and ensures that it is delivered to an available <strong>Celery worker</strong> for processing.</p><p>Now, let’s relate this to <strong>Redis</strong>, which is a popular message broker often used with <strong>Celery</strong>. Redis is like a highly efficient and reliable post office. When your application sends a task to Redis, Redis stores the task in a queue. <strong>Celery workers</strong> continuously monitor this queue for new tasks. When a worker becomes available, it retrieves a task from the queue and executes it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NvMkB__NkpbhJErM1kdptg.png" /></figure><p>So by definition, a <strong>message broker</strong> is a key component in distributed systems that facilitates communication between different parts of the system by enabling the exchange of messages. In the context of <strong>Celery</strong>, a message broker acts as a middleman between the Celery application and its workers, allowing tasks to be distributed and executed asynchronously.</p><h3><strong>Project Setup</strong></h3><p>Before we start I just want to indicate that you need to have prior knowledge of Django and its inner workings before diving into <strong>Celery </strong>and <strong>Redis</strong>. This tutorial won’t cover the basics of Django eg (creating virtual environments, projects, and apps).🙂</p><p>You can download the project code here to follow along 👉l<a href="https://github.com/Benji918/DRF-Atsuko-Clone/tree/main">ink</a></p><p>First, we install <strong>Redis</strong>. Here’s a link to an installation guide 👉<a href="https://redis.io/docs/install/install-redis/">link</a></p><h4>Requirements</h4><ul><li>pip install django</li><li>pip install celery</li><li>pip install redis</li><li>pip install django-celery-results</li></ul><p>django-celery-results is an extension for Django and Celery that provides support for storing <strong>Celery </strong>task results in the Django database. When Celery tasks are executed asynchronously, they often produce results that may need to be stored and accessed later. django-celery-results integrates Celery with Django’s ORM (Object-Relational Mapping) to allow task results to be saved directly to the database.</p><p>In addition, django-celery-results provides an easy way to view and manage the results of our tasks in the Django admin panel.</p><p><strong>Configuration</strong></p><ul><li>Add the django-celery-results to the INSTALLED_APPS</li></ul><pre># settings.py<br><br>INSTALLED_APPS = [<br>    &#39;django.contrib.admin&#39;,<br>    &#39;django.contrib.auth&#39;,<br>    &#39;django.contrib.contenttypes&#39;,<br>    &#39;django.contrib.sessions&#39;,<br>    &#39;django.contrib.messages&#39;,<br>    &#39;django.contrib.staticfiles&#39;,<br><br>     # third party apps<br>    &#39;rest_framework&#39;,<br>    &#39;rest_framework_simplejwt&#39;,<br>    &#39;djoser&#39;,<br>    &#39;drf_spectacular&#39;,<br>    &#39;django_filters&#39;,<br>    &#39;django_celery_beat&#39;,<br>    &#39;django_celery_results&#39;,<br><br>    # local apps<br>    &#39;core&#39;,<br>    &#39;users&#39;,<br>    &#39;google_login&#39;,<br>]</pre><ul><li>Celery Settings</li></ul><pre># settings.py<br><br># CELERY SETTINGS<br>CELERY_BROKER_URL = &#39;redis://127.0.0.1:6379/0&#39;<br>CELERY_ACCEPT_CONTENT=[&#39;application/json&#39;]<br>CELERY_RESULT_SERIALIZER=&#39;json&#39;<br>CELERY_TASK_SERIALIZER=&#39;json&#39;<br>CELERY_TIMEZONE=&#39;Africa/Lagos&#39;<br>CELERY_RESULT_BACKEND = &#39;django-db&#39;</pre><ol><li>CELERY_BROKER_URL: Specifies the URL of the message broker that Celery will use to send and receive messages. In this case, it’s set to Redis, which is running on the local machine (127.0.0.1) on port 6379, and uses a database 0.</li><li>CELERY_ACCEPT_CONTENT: Defines the content types that Celery will accept. Here, it’s set to accept JSON-formatted messages.</li><li>CELERY_RESULT_SERIALIZER: Specifies the serialization format for task results. It’s set to JSON, meaning task results will be serialized in JSON format.</li><li>CELERY_TASK_SERIALIZER: Defines the serialization format for task arguments and return values. It’s also set to JSON for consistency with task results.</li><li>CELERY_TIMEZONE: Sets the timezone used by Celery for scheduling tasks. In this case, it’s set to &#39;Africa/Lagos&#39;, it represents the timezone for Lagos, Nigeria.</li><li>CELERY_RESULT_BACKEND: Specifies the backend for storing task results. Here, it’s configured to use the Django database (django-db), meaning task results will be stored in the Django database.</li></ol><ul><li>In the <strong>Django project directory</strong>, create a new file called celery.py</li></ul><pre># Django_project/celery.py<br><br>from __future__ import absolute_import, unicode_literals<br>import os<br>from celery import Celery<br>from django.conf import settings<br><br># Set the default Django settings module for the &#39;celery&#39; program.<br>os.environ.setdefault(&#39;DJANGO_SETTINGS_MODULE&#39;, &#39;atsuko_clone.settings&#39;)<br><br>app = Celery(&#39;atsuko_clone&#39;)  # Replace &#39;your_project&#39; with your project&#39;s name.<br><br># Configure Celery using settings from Django settings.py.<br>app.config_from_object(&#39;django.conf:settings&#39;, namespace=&#39;CELERY&#39;)<br><br># Celery beat settings<br>app.conf.beat_schedule = {}<br><br># Load tasks from all registered Django app configs.<br>app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)<br><br><br><br>@app.task(bind=True, ignore_result=True)<br>def debug_task(self):<br>    print(f&#39;Request: {self.request!r}&#39;)<br></pre><ul><li>In the same directory, paste the code below in the __init__.py</li></ul><pre># Django_project/__init__.py<br>from __future__ import absolute_import, unicode_literals<br>from .celery import app as celery_app<br><br>__all__ = (&#39;celery_app&#39;,)</pre><p>__all__ = (&#39;celery_app&#39;,): This line defines the __all__ attribute, which is a list containing the names of the symbols that should be exported when from package import * is used. In this case, it ensures that only celery_app is exported from the package when it&#39;s imported.</p><ul><li>In the users app directory, create a signals.py file</li></ul><pre># users/signals.py<br>from django.db.models.signals import post_save<br>from django.conf import settings<br>from django.dispatch import receiver<br>from .tasks import send_registration_email<br><br>@receiver(post_save, sender=settings.AUTH_USER_MODEL)<br>def send_email_registration_on_register(sender, instance, created, **kwargs):<br>    if created:<br>        email = instance.email<br>        print(email)<br>        send_registration_email.delay(receiver_email=email)</pre><p>What this file does is whenever a new user is created, it will send an email to the newly registered user….like a welcome email by calling the send_registration_email function.</p><p>Ensure you include the created signals.py file in the apps.py file</p><pre># users/apps.py<br>from django.apps import AppConfig<br><br><br>class UsersConfig(AppConfig):<br>    default_auto_field = &#39;django.db.models.BigAutoField&#39;<br>    name = &#39;users&#39;<br><br>    def ready(self):<br>            import users.signals</pre><ul><li><strong>SMTP Settings</strong></li></ul><pre># settings.py<br><br># EMAIL SETTINGS<br>EMAIL_HOST_USER = os.getenv(&#39;SENDER_EMAIL&#39;)<br>EMAIL_HOST_PASSWORD = os.getenv(&#39;SENDER_EMAIL_PASSWORD&#39;)</pre><p>Create a .env in the Django project directory and store your EMAIL_HOST_USER and EMAIL_HOST_PASSWORD</p><p>EMAIL_HOST_USER and EMAIL_HOST_PASSWORD are the information of the sender. To get a password go to your account in Gmail. Go to <strong>Security </strong>from the sidebar.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PZYWLvzukucVAgYm7WwkVw.png" /></figure><p>Then click on 2-step verification. Verify it’s you by inputting your Google password.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fhA9DRyRn6KFwsJV_MuvBA.png" /></figure><p>You will be redirected to another page, scroll down to <strong>App password. </strong>Then click on the <strong>arrow</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/736/1*yj7lBT0iE1arOHBRlo6GpA.png" /></figure><p>Next, give a name to the app password then, click <strong>Create. </strong>A popup of the<strong> g</strong>enerated app password will be displayed. Copy your password (the text in the grey background). Paste it to your .<strong>env file</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/659/1*GznZ8tmZLBhynyFlWbaMRw.png" /></figure><p>From my personal experience, I noticed that sending email using Django’s default SMTP settings doesn’t work. It keeps giving me an<strong> SMTP server timeout error</strong>. If you have encountered such please share in the comments how you resolved such.</p><blockquote><em>💡 </em><strong><em>Alternative Approach:</em></strong><em> If you’d rather avoid the hassle of configuring SMTP, handling app passwords, and dealing with Gmail’s limitations, something like </em><a href="https://notify.cx?utm_source=medium&amp;utm_medium=blog_post&amp;utm_campaign=q1_2025_embed"><em>Notify</em></a><em> could be a great alternative. It lets you send transactional emails easily — either via API or directly from the dashboard — without worrying about SMTP configurations.</em></blockquote><p>For this tutorial, I will be using raw Python for sending the emails for this tutorial. I promise the code is nothing too complicated😅.</p><ul><li><strong>Define a new task</strong></li></ul><p>Create a new task.py file in the <strong>users app directory</strong></p><pre># users/task.py<br><br>from email.mime.multipart import MIMEMultipart<br>from email.mime.text import MIMEText<br>import smtplib<br>import smtplib, ssl<br>from celery import shared_task<br>from django.conf import settings<br><br>@shared_task<br>def send_registration_email( receiver_email, message=&#39;Thank you for registering on our site.&#39;):<br>    # Create a MIME object<br>    msg = MIMEMultipart()<br>        <br>    # Attach the message<br>    msg.attach(MIMEText(message, &quot;plain&quot;))<br><br>    # Set the email subject, sender, and receiver<br>    msg[&quot;Subject&quot;] = &#39;welcome to GFG world&#39;<br>    msg[&quot;From&quot;] = settings.EMAIL_HOST_USER<br>    msg[&quot;To&quot;] = receiver_email<br><br>    # Establish a connection to the SMTP server<br>    context = ssl.create_default_context()<br>    with smtplib.SMTP_SSL(&quot;smtp.gmail.com&quot;, 465, context=context) as server:<br>        # Log in to the email account<br>        server.login(settings.EMAIL_HOST_USER, settings.EMAIL_HOST_PASSWORD)<br>        <br>        # Send the email<br>        server.sendmail(settings.EMAIL_HOST_USER, receiver_email, msg.as_string())</pre><p>Run the Django server in the first terminal. python manage.py runserver</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/642/1*NKtMQVczi4HGwtsARMPj7Q.png" /><figcaption>Django server</figcaption></figure><p>Run the Celery in the second terminal. celery -A atsuko_clone worker -l info</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/990/1*zQH6RCCvVBJaUZa0T3ABAg.png" /><figcaption>Celery Worker</figcaption></figure><p>Go to <a href="http://localhost:8000/api/schema/swagger/#/auth/auth_users_create">http://localhost:8000/api/schema/swagger/#/auth/auth_users_create</a> fill in the request body, then click <strong>Execute.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Z0KwF45g_d9AMO6qS37Rag.png" /></figure><p>Once the response body gives a 201 response back, the user has been created successfully. Then check your inbox for a welcome email.🙂</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/526/1*XDRtTeErsAgirfj23jxK7A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/683/1*_o6of6Tc2qr7Omxvgx8x5w.png" /></figure><p>Pretty nice.</p><h4><strong>Conclusion</strong></h4><p>Alright, folks, that’s the gist of it! By using Celery, you can easily set up an asynchronous email-sending system in your Django app. No more waiting around for those emails to go out — let Celery handle it in the background while you focus on the important stuff. See you in the next one👋</p><p>Psst, have I got a sweet deal for 🫵🏽. Want to take your Django app to the next level?🫰🏽🔥. Check out this awesome tutorial on <a href="https://www.kowe.io/guide/deploy-a-dockerized-django-app-on-aws-ec2-with-rds-s3-gunicorn-nginx-and-github-actions-cicd"><strong>Deploy a Dockerized Django App on AWS EC2 with RDS, S3, Gunicorn, Nginx, and GitHub Actions CI/CD</strong></a><strong>. </strong>The best part? You’ll get hands-on experience and a smoother deployment process. But wait, there’s more — use my coupon code <strong>BENEARLYEC2 </strong>to<strong> </strong>get <strong>50% off</strong>! Happy coding!😊👋🏽</p><h3>In Plain English 🚀</h3><p><em>Thank you for being a part of the </em><a href="https://plainenglish.io"><strong><em>In Plain English</em></strong></a><em> community! Before you go:</em></p><ul><li>Be sure to <strong>clap</strong> and <strong>follow</strong> the writer ️👏<strong>️️</strong></li><li>Follow us: <a href="https://twitter.com/inPlainEngHQ"><strong>X</strong></a><strong> | </strong><a href="https://www.linkedin.com/company/inplainenglish/"><strong>LinkedIn</strong></a><strong> | </strong><a href="https://www.youtube.com/channel/UCtipWUghju290NWcn8jhyAw"><strong>YouTube</strong></a><strong> | </strong><a href="https://discord.gg/in-plain-english-709094664682340443"><strong>Discord</strong></a><strong> | </strong><a href="https://newsletter.plainenglish.io/"><strong>Newsletter</strong></a></li><li>Visit our other platforms: <a href="https://stackademic.com/"><strong>Stackademic</strong></a><strong> | </strong><a href="https://cofeed.app/"><strong>CoFeed</strong></a><strong> | </strong><a href="https://venturemagazine.net/"><strong>Venture</strong></a><strong> | </strong><a href="https://blog.cubed.run"><strong>Cubed</strong></a></li><li>More content at <a href="https://plainenglish.io"><strong>PlainEnglish.io</strong></a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1bb1f7be318e" width="1" height="1" alt=""><hr><p><a href="https://python.plainenglish.io/asynchronously-send-emails-in-django-1bb1f7be318e">Asynchronously Send Emails in Django📨</a> was originally published in <a href="https://python.plainenglish.io">Python in Plain English</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Through the Lens of Determination: A Journey from Despair to Dreamer]]></title>
            <link>https://medium.com/@kodiugos/through-the-lens-of-determination-a-journey-from-despair-to-dreamer-6e9ffee1531d?source=rss-99c5bfceed4b------2</link>
            <guid isPermaLink="false">https://medium.com/p/6e9ffee1531d</guid>
            <category><![CDATA[poetry]]></category>
            <category><![CDATA[creativity]]></category>
            <category><![CDATA[storytelling]]></category>
            <category><![CDATA[articles]]></category>
            <category><![CDATA[medium]]></category>
            <dc:creator><![CDATA[Ugochukwu Benjamin .C]]></dc:creator>
            <pubDate>Thu, 29 Feb 2024 10:23:42 GMT</pubDate>
            <atom:updated>2024-02-29T10:23:42.296Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*q6yz2COpoE1nUGGu" /><figcaption>Photo by <a href="https://unsplash.com/@mahdigp?utm_source=medium&amp;utm_medium=referral">Mahdi Dastmard</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Life’s been giving me lemons lately. So many freaking lemons. And don’t even get me started on where the heck the lemonade is. I’m Amina- a 25-year-old dreamer with a camera glued to my hand and the world on my shoulders. You see, I’ve been hustling like hustling, racing to turn my passion for photography into a career, and between hustling freelance gigs and taking care of my mom, who’s starting to get up there in age, I’ve found myself running on empty.</p><p>It’s tough out here for a dreamer. It’s so hard to keep going every day feeling like no one values your dreams. While fleeting thoughts of abandoning my ambitions for a more secure path crossed my mind during trying times, deep within I knew persisting through struggle and hardship toward my vision was the only way to find meaning.</p><p>But then, life gave me an unexpected break. I crossed paths with a big-time artist who must have felt that the universe was winking at her. She saw something in my work that I’d been blind to in my moments of — let’s keep it real — whimpering lack of confidence. She made me see that my story counted too, that my perspective was just as original as hers and worthy of being shared with the world. Something ignited within my soul, like a flame of resilience that refused to be extinguished despite the hardships life had laid upon me. It was in that pivotal instant that I glimpsed myself afresh, envisioning not one who surrenders to my circumstances but rather the designer of my fate as it unfolds. The sparks of self-determination gave light in the darkness, empowering me to write the next chapter according to my vision, not dictated by the tragedies of the past but guided by the promise of possibilities yet unseen. While the challenges lying ahead remain formidable, I stride forward with renewed confidence and determination, aware of my innate ability to craft something sweet from life’s sourest of fruits.</p><p>As a young photographer with dreams filling my heart and my camera prepared to see the world through my eyes, here I am ready to share my name — Amina — and bring my visions to life. Watch out, universe, because I’m taking control of my narrative, one shot at a time.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6e9ffee1531d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Python Magic: Streamlining Your Desktop with a Cleanup Script]]></title>
            <link>https://python.plainenglish.io/python-magic-streamlining-your-desktop-with-a-cleanup-script-cbd5215b51a4?source=rss-99c5bfceed4b------2</link>
            <guid isPermaLink="false">https://medium.com/p/cbd5215b51a4</guid>
            <category><![CDATA[automation]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[problem-solving]]></category>
            <category><![CDATA[python-programming]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Ugochukwu Benjamin .C]]></dc:creator>
            <pubDate>Tue, 30 Jan 2024 01:42:12 GMT</pubDate>
            <atom:updated>2024-04-22T01:58:04.149Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*e4CzhIAvdUCnHtvj" /><figcaption>Photo by <a href="https://unsplash.com/@hiteshchoudhary?utm_source=medium&amp;utm_medium=referral">Hitesh Choudhary</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Table of Content</h3><ol><li>Introduction</li><li>Setting Up Your Python Environment</li><li>Python Libraries for Desktop Cleaning</li><li>Creating Your Desktop Cleaning Script</li><li>Customizing Cleaning Rules</li><li>Automating Cleaning Tasks</li></ol><h3>INTRODUCTION</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/884/1*ZaN0M6h3B7a8D7HfSD460w.png" /><figcaption>Stackoverflow 2023 programming language survey</figcaption></figure><p>Hey everyone, I want to share a cool project I created. Ever wished your desktop could clean itself up automatically? 🤔I used Python’s file management features to build a basic desktop cleaner. With a simple click, it scans specified paths for files and folders, organizing your desktop seamlessly✨. In this article, I’ll break down the key parts of the code, showing how easy it is to build handy tools like this with Python. Per the StackOverflow survey, Python&#39;s popularity makes it an excellent choice for such projects, offering ample resources and support. If you’re looking to tidy up your workspace, Python is a smart and accessible solution👌</p><h3>Setting Up Your Python Environment</h3><p>First, ensure you have Python on your computer if you don&#39;t click this <a href="https://www.python.org/downloads/">link</a> to download and install the latest version of Python.</p><p>In your terminal first install v<strong>irtualenv.</strong> It<strong> </strong>is a tool to set up your Python environments. Since Python 3.3, a subset has been integrated into the standard library under the venv module. You can install venv to your host Python by running this command in your terminal:</p><pre>pip install virtualenv</pre><p>To use venv in our project, in your terminal, create a new project folder called desktop_cleaner, cd to the project folder in your terminal, and run the following command:</p><pre> python -m venv venv</pre><p>When you check your project folder, you will notice that a new folder called <strong>venv</strong> has been created. venv is the name of our virtual environment, but it can be named anything you want. To activate your virtual environment for your projects run the command below</p><pre>myenv\Scripts\activate</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/548/1*wHgcln7XTJnfvGye8vXb-w.png" /></figure><p>Once you see the highlighted text it indicates your virtual environment is active.</p><h3>Python Libraries for Desktop Cleaning</h3><ol><li><a href="https://pythonhosted.org/watchdog/">Watchdog</a>: Python API library and shell utilities to monitor file system events.</li><li><a href="https://docs.python.org/3/library/shutil.html">Shutil</a>: module offers some high-level operations on files and collections of files. In particular, functions are provided that support file copying and removal. For operations on individual files</li><li>Pathlib: This module offers classes representing filesystem paths with semantics appropriate for different operating systems.</li><li><a href="https://docs.python.org/3/library/datetime.html">Datetime</a>: The <a href="https://docs.python.org/3/library/datetime.html#module-datetime">datetime</a> module supplies classes for manipulating dates and times.</li></ol><h3>Creating Your Desktop Cleaning Script</h3><p>For this tutorial, I’ll be using the Pycharm IDE but you can use any IDE or text editor of your choice that works well with Python😊.</p><p>Below is the project file structure.</p><pre>desktop_cleaner/<br>    __init__.py<br>    cleanedesk.py<br>    EventHandler.py<br>    extensions.py<br>    ...</pre><p>Now, open the created folder in your selected IDE.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/394/1*r5ms6mWH8LwojVwhKEuX3Q.png" /></figure><p>Next, create an __init__.py in the desktop_cleaner folder.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/361/1*hsY9tvPA9_v6xi9SNbDBYw.png" /></figure><p>In Python, the __init__.py file is used to mark a directory as a Python package. It is used to initialize the package when it is imported.</p><p>Still, within the desktop_cleaner directory, create a Python file named extension.py. This Python file will contain all the extensions for the files to be read by our code.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/454/1*e1AkQTYLPvebnswmEcJMRg.png" /></figure><p>Within the extensions.py file, paste the code below into the file</p><pre>extension_paths = {<br>        # No name<br>        &#39;noname&#39;:  &#39;other/uncategorized&#39;,<br>        # audio<br>        &#39;.aif&#39;:    &#39;media/audio&#39;,<br>        &#39;.cda&#39;:    &#39;media/audio&#39;,<br>        &#39;.mid&#39;:    &#39;media/audio&#39;,<br>        &#39;.midi&#39;:   &#39;media/audio&#39;,<br>        &#39;.mp3&#39;:    &#39;media/audio&#39;,<br>        &#39;.mpa&#39;:    &#39;media/audio&#39;,<br>        &#39;.ogg&#39;:    &#39;media/audio&#39;,<br>        &#39;.wav&#39;:    &#39;media/audio&#39;,<br>        &#39;.wma&#39;:    &#39;media/audio&#39;,<br>        &#39;.wpl&#39;:    &#39;media/audio&#39;,<br>        &#39;.m3u&#39;:    &#39;media/audio&#39;,<br>        # text<br>        &#39;.txt&#39;:    &#39;text/text_files&#39;,<br>        &#39;.doc&#39;:    &#39;text/microsoft/word&#39;,<br>        &#39;.docx&#39;:   &#39;text/microsoft/word&#39;,<br>        &#39;.odt &#39;:   &#39;text/text_files&#39;,<br>        &#39;.pdf&#39;:    &#39;text/pdf&#39;,<br>        &#39;.rtf&#39;:    &#39;text/text_files&#39;,<br>        &#39;.tex&#39;:    &#39;text/text_files&#39;,<br>        &#39;.wks &#39;:   &#39;text/text_files&#39;,<br>        &#39;.wps&#39;:    &#39;text/text_files&#39;,<br>        &#39;.wpd&#39;:    &#39;text/text_files&#39;,<br>        # video<br>        &#39;.3g2&#39;:    &#39;media/video&#39;,<br>        &#39;.3gp&#39;:    &#39;media/video&#39;,<br>        &#39;.avi&#39;:    &#39;media/video&#39;,<br>        &#39;.flv&#39;:    &#39;media/video&#39;,<br>        &#39;.h264&#39;:   &#39;media/video&#39;,<br>        &#39;.m4v&#39;:    &#39;media/video&#39;,<br>        &#39;.mkv&#39;:    &#39;media/video&#39;,<br>        &#39;.mov&#39;:    &#39;media/video&#39;,<br>        &#39;.mp4&#39;:    &#39;media/video&#39;,<br>        &#39;.mpg&#39;:    &#39;media/video&#39;,<br>        &#39;.mpeg&#39;:   &#39;media/video&#39;,<br>        &#39;.rm&#39;:     &#39;media/video&#39;,<br>        &#39;.swf&#39;:    &#39;media/video&#39;,<br>        &#39;.vob&#39;:    &#39;media/video&#39;,<br>        &#39;.wmv&#39;:    &#39;media/video&#39;,<br>        # images<br>        &#39;.ai&#39;:     &#39;media/images&#39;,<br>        &#39;.bmp&#39;:    &#39;media/images&#39;,<br>        &#39;.gif&#39;:    &#39;media/images&#39;,<br>        &#39;.jpg&#39;:    &#39;media/images&#39;,<br>        &#39;.jpeg&#39;:   &#39;media/images&#39;,<br>        &#39;.png&#39;:    &#39;media/images&#39;,<br>        &#39;.ps&#39;:     &#39;media/images&#39;,<br>        &#39;.psd&#39;:    &#39;media/images&#39;,<br>        &#39;.svg&#39;:    &#39;media/images&#39;,<br>        &#39;.tif&#39;:    &#39;media/images&#39;,<br>        &#39;.tiff&#39;:   &#39;media/images&#39;,<br>        &#39;.cr2&#39;:    &#39;media/images&#39;,<br>        # internet<br>        &#39;.asp&#39;:    &#39;other/internet&#39;,<br>        &#39;.aspx&#39;:   &#39;other/internet&#39;,<br>        &#39;.cer&#39;:    &#39;other/internet&#39;,<br>        &#39;.cfm&#39;:    &#39;other/internet&#39;,<br>        &#39;.cgi&#39;:    &#39;other/internet&#39;,<br>        &#39;.pl&#39;:     &#39;other/internet&#39;,<br>        &#39;.css&#39;:    &#39;other/internet&#39;,<br>        &#39;.htm&#39;:    &#39;other/internet&#39;,<br>        &#39;.js&#39;:     &#39;other/internet&#39;,<br>        &#39;.jsp&#39;:    &#39;other/internet&#39;,<br>        &#39;.part&#39;:   &#39;other/internet&#39;,<br>        &#39;.php&#39;:    &#39;other/internet&#39;,<br>        &#39;.rss&#39;:    &#39;other/internet&#39;,<br>        &#39;.xhtml&#39;:  &#39;other/internet&#39;,<br>        &#39;.html&#39;:   &#39;other/internet&#39;,<br>        # compressed<br>        &#39;.7z&#39;:     &#39;other/compressed&#39;,<br>        &#39;.arj&#39;:    &#39;other/compressed&#39;,<br>        &#39;.deb&#39;:    &#39;other/compressed&#39;,<br>        &#39;.pkg&#39;:    &#39;other/compressed&#39;,<br>        &#39;.rar&#39;:    &#39;other/compressed&#39;,<br>        &#39;.rpm&#39;:    &#39;other/compressed&#39;,<br>        &#39;.tar.gz&#39;: &#39;other/compressed&#39;,<br>        &#39;.z&#39;:      &#39;other/compressed&#39;,<br>        &#39;.zip&#39;:    &#39;other/compressed&#39;,<br>        # disc<br>        &#39;.bin&#39;:    &#39;other/disc&#39;,<br>        &#39;.dmg&#39;:    &#39;other/disc&#39;,<br>        &#39;.iso&#39;:    &#39;other/disc&#39;,<br>        &#39;.toast&#39;:  &#39;other/disc&#39;,<br>        &#39;.vcd&#39;:    &#39;other/disc&#39;,<br>        # data<br>        &#39;.csv&#39;:    &#39;programming/database&#39;,<br>        &#39;.dat&#39;:    &#39;programming/database&#39;,<br>        &#39;.db&#39;:     &#39;programming/database&#39;,<br>        &#39;.dbf&#39;:    &#39;programming/database&#39;,<br>        &#39;.log&#39;:    &#39;programming/database&#39;,<br>        &#39;.mdb&#39;:    &#39;programming/database&#39;,<br>        &#39;.sav&#39;:    &#39;programming/database&#39;,<br>        &#39;.sql&#39;:    &#39;programming/database&#39;,<br>        &#39;.tar&#39;:    &#39;programming/database&#39;,<br>        &#39;.xml&#39;:    &#39;programming/database&#39;,<br>        &#39;.json&#39;:   &#39;programming/database&#39;,<br>        # executables<br>        &#39;.apk&#39;:    &#39;other/executables&#39;,<br>        &#39;.bat&#39;:    &#39;other/executables&#39;,<br>        &#39;.com&#39;:    &#39;other/executables&#39;,<br>        &#39;.exe&#39;:    &#39;other/executables&#39;,<br>        &#39;.gadget&#39;: &#39;other/executables&#39;,<br>        &#39;.jar&#39;:    &#39;other/executables&#39;,<br>        &#39;.wsf&#39;:    &#39;other/executables&#39;,<br>        # fonts<br>        &#39;.fnt&#39;:    &#39;other/fonts&#39;,<br>        &#39;.fon&#39;:    &#39;other/fonts&#39;,<br>        &#39;.otf&#39;:    &#39;other/fonts&#39;,<br>        &#39;.ttf&#39;:    &#39;other/fonts&#39;,<br>        # presentations<br>        &#39;.key&#39;:    &#39;text/presentations&#39;,<br>        &#39;.odp&#39;:    &#39;text/presentations&#39;,<br>        &#39;.pps&#39;:    &#39;text/presentations&#39;,<br>        &#39;.ppt&#39;:    &#39;text/presentations&#39;,<br>        &#39;.pptx&#39;:   &#39;text/presentations&#39;,<br>        # programming<br>        &#39;.c&#39;:      &#39;programming/c&amp;c++&#39;,<br>        &#39;.class&#39;:  &#39;programming/java&#39;,<br>        &#39;.java&#39;:   &#39;programming/java&#39;,<br>        &#39;.py&#39;:     &#39;programming/python&#39;,<br>        &#39;.sh&#39;:     &#39;programming/shell&#39;,<br>        &#39;.h&#39;:      &#39;programming/c&amp;c++&#39;,<br>        # spreadsheets<br>        &#39;.ods&#39;:    &#39;text/microsoft/excel&#39;,<br>        &#39;.xlr&#39;:    &#39;text/microsoft/excel&#39;,<br>        &#39;.xls&#39;:    &#39;text/microsoft/excel&#39;,<br>        &#39;.xlsx&#39;:   &#39;text/microsoft/excel&#39;,<br>        # system<br>        &#39;.bak&#39;:    &#39;text/other/system&#39;,<br>        &#39;.cab&#39;:    &#39;text/other/system&#39;,<br>        &#39;.cfg&#39;:    &#39;text/other/system&#39;,<br>        &#39;.cpl&#39;:    &#39;text/other/system&#39;,<br>        &#39;.cur&#39;:    &#39;text/other/system&#39;,<br>        &#39;.dll&#39;:    &#39;text/other/system&#39;,<br>        &#39;.dmp&#39;:    &#39;text/other/system&#39;,<br>        &#39;.drv&#39;:    &#39;text/other/system&#39;,<br>        &#39;.icns&#39;:   &#39;text/other/system&#39;,<br>        &#39;.ico&#39;:    &#39;text/other/system&#39;,<br>        &#39;.ini&#39;:    &#39;text/other/system&#39;,<br>        &#39;.lnk&#39;:    &#39;text/other/system&#39;,<br>        &#39;.msi&#39;:    &#39;text/other/system&#39;,<br>        &#39;.sys&#39;:    &#39;text/other/system&#39;,<br>        &#39;.tmp&#39;:    &#39;text/other/system&#39;<br>        }</pre><p>The code above categorizes all the widely used file extensions on a PC. I couldn&#39;t cover all of them😅.</p><p>Create another Python file called Evenhandler.py in the desktop_cleaner directory.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/642/1*a40ODDp9ssTp7N9Tg7wRow.png" /></figure><p>The primary purpose of this file is to determine how the directory subfolders will be stored based on their various file extensions.</p><p>Now copy the code below into the file</p><pre>import shutil<br>from datetime import date<br>from pathlib import Path<br><br>from watchdog.events import FileSystemEventHandler<br><br>from extensions import extension_paths<br><br><br>def add_date_to_path(path: Path):<br>    &quot;&quot;&quot;<br>    Helper function that adds current year/month to destination path. If the path<br>    doesn&#39;t already exist, it is created.<br><br>    :param Path path: destination root to append subdirectories based on date<br>    &quot;&quot;&quot;<br>    dated_path = path / f&#39;{date.today().year}&#39; / f&#39;{date.today().strftime(&quot;%b&quot;).upper()}&#39;<br>    dated_path.mkdir(parents=True, exist_ok=True)<br>    return dated_path<br><br><br>def rename_file(source: Path, destination_path: Path):<br>    &quot;&quot;&quot;<br>    Helper function that renames file to reflect new path. If a file of the same<br>    name already exists in the destination folder, the file name is numbered and<br>    incremented until the filename is unique (prevents overwriting files).<br><br>    :param Path source: source of file to be moved<br>    :param Path destination_path: path to destination directory<br>    &quot;&quot;&quot;<br>    if Path(destination_path / source.name).exists():<br>        increment = 0<br><br>        while True:<br>            increment += 1<br>            new_name = destination_path / f&#39;{source.stem}_{increment}{source.suffix}&#39;<br><br>            if not new_name.exists():<br>                return new_name<br>    else:<br>        return destination_path / source.name<br><br><br>class EventHandler(FileSystemEventHandler):<br>    def __init__(self, watch_path: Path, destination_root: Path):<br>        self.watch_path = watch_path.resolve()<br>        self.destination_root = destination_root.resolve()<br><br>    def on_modified(self, event):<br>        for child in self.watch_path.iterdir():<br>            # skips directories and non-specified extensions<br>            if child.is_file() and child.suffix.lower() in extension_paths:<br>                destination_path = self.destination_root / extension_paths[child.suffix.lower()]<br>                destination_path = add_date_to_path(path=destination_path)<br>                destination_path = rename_file(source=child, destination_path=destination_path)<br>                shutil.move(src=child, dst=destination_path)<br><br></pre><p>Explaining the above code blocks</p><pre>from pathlib import Path<br>from datetime import date</pre><ul><li>The function is named add_date_to_path.</li><li>It takes one parameter, pathwhich is expected to be an instance of the Path class.</li></ul><pre>current_year = date.today().year<br>current_month = date.today().strftime(&quot;%b&quot;).upper()</pre><ul><li>date.today().year gets the current year.</li><li>date.today().strftime(&quot;%b&quot;).upper() gets the current month in abbreviated format (e.g., &quot;Jan&quot;) and converts it to uppercase.</li></ul><pre>dated_path = path / f&#39;{current_year}&#39; / f&#39;{current_month}&#39;</pre><p>path / f&#39;{current_year}&#39; / f&#39;{current_month}&#39; creates a new Path object by appending the current year and month as subdirectories to the original path.</p><pre>dated_path.mkdir(parents=True, exist_ok=True)</pre><ul><li>mkdir creates the directories specified in the path.</li><li>parents=True ensures that parent directories are also created if they don&#39;t exist.</li><li>exist_ok=True prevents an error if the directories already exist.</li></ul><pre>return dated_path</pre><p>The function returns the final Path object representing the destination path with the current year and month.</p><p>Overall, this function is a utility that helps organize destination paths based on the current year and month, creating the necessary directories if they don’t already exist.</p><pre>def rename_file(source: Path, destination_path: Path):<br>    &quot;&quot;&quot;<br>    Helper function that renames a file to reflect a new path. If a file with<br>    the same name already exists in the destination folder, the file name is<br>    numbered and incremented until the filename is unique (prevents overwriting files).<br><br>    :param Path source: source of the file to be moved<br>    :param Path destination_path: path to the destination directory<br>    &quot;&quot;&quot;</pre><ul><li>The function is named rename_file.</li><li>It takes two parameters: source (the source path of the file to be moved) and destination_path (the path to the destination directory).</li><li>The docstring provides information about what the function does and how to use it.</li></ul><pre>if Path(destination_path / source.name).exists():</pre><p>Check if a file with the same name already exists in the destination folder.</p><pre>increment = 0<br><br>while True:<br>    increment += 1<br>    new_name = destination_path / f&#39;{source.stem}_{increment}{source.suffix}&#39;<br><br>    if not new_name.exists():<br>        return new_name</pre><p>If a file with the same name exists, it starts incrementing a counter (increment) and constructs a new filename until a unique filename is found</p><pre>return new_name</pre><p>The function returns the new Path object representing the unique filename.</p><pre>else:<br>    return destination_path / source.name</pre><ul><li>If no file with the same name exists in the destination folder, it returns the original filename.</li></ul><p>This function helps ensure that files are moved to a destination directory with a unique name to prevent overwriting existing files. The filename is modified by appending an incrementing number to it if necessary.</p><p>Import Statements:</p><pre>pathlib import Path<br>from watchdog.events import FileSystemEventHandler<br>import shutil</pre><ul><li>Path is a class from the pathlib module that represents file system paths.</li><li>FileSystemEventHandler is a class from the watchdog.events module that provides event handlers for file system events.</li><li>shutil is a module for file operations.</li></ul><p>Class Definition:</p><pre>class EventHandler(FileSystemEventHandler):</pre><ul><li>Defines a class named EventHandler that inherits from FileSystemEventHandler.</li></ul><p>Constructor (__init__ method):</p><pre>def __init__(self, watch_path: Path, destination_root: Path):<br>    self.watch_path = watch_path.resolve()<br>    self.destination_root = destination_root.resolve()</pre><ul><li>The constructor initializes the watch_path and destination_root attributes by resolving the provided paths.</li></ul><p>on_modified Method:</p><pre>def on_modified(self, event):<br>    for child in self.watch_path.iterdir():<br>        # skips directories and non-specified extensions<br>        if child.is_file() and child.suffix.lower() in extension_paths:<br>            destination_path = self.destination_root / extension_paths[child.suffix.lower()]<br>            destination_path = add_date_to_path(path=destination_path)<br>            destination_path = rename_file(source=child, destination_path=destination_path)<br>            shutil.move(src=child, dst=destination_path)</pre><ul><li>This method is called when a file is modified in the watched directory.</li><li>It iterates over files in the watch_path.</li><li>It checks if the file is a regular file and has an extension specified in extension_paths.</li><li>It constructs the destination path based on the extension, adds the current date to the path, and renames the file if needed.</li><li>Finally, it moves the file to the calculated destination path using shutil.move.</li></ul><p>This class seems to be part of a file-watching system where files are moved based on their extension and modified events. The add_date_to_path and rename_file functions are used for organizing and renaming files. Note that extension_paths should be defined somewhere in your code, as it&#39;s referenced in the if condition.</p><p>Now let’s create a new Python file named cleandesk.py to put all the functionality together😁</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/524/1*qKup-HOSAlXiRF-9XilX5w.png" /></figure><p>Copy the code below into your newly created Python file</p><pre><br>from pathlib import Path<br>from time import sleep<br><br>from watchdog.observers import Observer<br><br>from EventHandler import EventHandler<br><br>if __name__ == &#39;__main__&#39;:<br>    watch_path_1 = Path.home() / &#39;Downloads&#39;<br>    watch_path_2 = Path.home() / &#39;Pictures&#39;<br>    watch_path_3 = Path.home() / &#39;Desktop&#39;<br>    destination_root_1 = Path.home() / &#39;Downloads/holder of things&#39;<br>    destination_root_2 = Path.home() / &#39;Pictures/holder of things&#39;<br>    destination_root_3 = Path.home() / &#39;Desktop/holder of things&#39;<br><br>    event_handler_1 = EventHandler(watch_path=watch_path_1, destination_root=destination_root_1)<br>    event_handler_2 = EventHandler(watch_path=watch_path_2, destination_root=destination_root_2)<br>    event_handler_3 = EventHandler(watch_path=watch_path_3, destination_root=destination_root_3)<br><br>    observer_1 = Observer()<br>    observer_1.schedule(event_handler_1, f&#39;{watch_path_1}&#39;, recursive=True)<br>    observer_1.start()<br><br>    observer_2 = Observer()<br>    observer_2.schedule(event_handler_2, f&#39;{watch_path_2}&#39;, recursive=True)<br>    observer_2.start()<br><br>    observer_3 = Observer()<br>    observer_3.schedule(event_handler_3, f&#39;{watch_path_3}&#39;, recursive=True)<br>    observer_3.start()<br><br>    try:<br>        while True:<br>            sleep(60)<br>    except KeyboardInterrupt:<br>        observer_1.stop()<br>        observer_2.stop()<br>        observer_3.stop()<br>    observer_1.join()<br>    observer_2.join()<br>    observer_3.join()<br><br></pre><p>This script is a Python program that uses the watchdog library to monitor three different directories (Downloads, Pictures, and Desktop) for file system events (such as file modifications) and automatically organizes files in those directories into a corresponding &quot;holder of things&quot; subdirectory.</p><p>Let’s break down the script in detail:</p><p>Import Statements:</p><pre>from pathlib import Path<br>from time import sleep<br>from watchdog.observers import Observer<br>from EventHandler import EventHandler</pre><ul><li>Path is a class from the pathlib module that represents file system paths.</li><li>sleep is a function from the time module that pauses the execution of the script for a specified number of seconds.</li><li>Observer is a class from the watchdog.observers module that observes file system events.</li><li>EventHandler is a custom class defined in a separate EventHandler.py file, handling file system events.</li></ul><p>Main Execution Block:</p><pre>if __name__ == &#39;__main__&#39;:</pre><ul><li>This common Python idiom checks whether the script is being run as the main program.</li></ul><p>Directory Paths:</p><pre>watch_path_1 = Path.home() / &#39;Downloads&#39;<br>watch_path_2 = Path.home() / &#39;Pictures&#39;<br>watch_path_3 = Path.home() / &#39;Desktop&#39;<br>destination_root_1 = Path.home() / &#39;Downloads/holder of things&#39;<br>destination_root_2 = Path.home() / &#39;Pictures/holder of things&#39;<br>destination_root_3 = Path.home() / &#39;Desktop/holder of things&#39;</pre><ul><li>Path.home() returns the home directory of the current user.</li><li>Three watch_path variables are defined for the directories to be watched (Downloads, Pictures, and Desktop).</li><li>Three destination_root variables are defined for the corresponding &quot;holder of things&quot; subdirectories.</li></ul><p>Event Handlers and Observers:</p><pre>event_handler_1 = EventHandler(watch_path=watch_path_1, destination_root=destination_root_1)<br>event_handler_2 = EventHandler(watch_path=watch_path_2, destination_root=destination_root_2)<br>event_handler_3 = EventHandler(watch_path=watch_path_3, destination_root=destination_root_3)<br><br>observer_1 = Observer()<br>observer_1.schedule(event_handler_1, f&#39;{watch_path_1}&#39;, recursive=True)<br>observer_1.start()<br><br>observer_2 = Observer()<br>observer_2.schedule(event_handler_2, f&#39;{watch_path_2}&#39;, recursive=True)<br>observer_2.start()<br><br>observer_3 = Observer()<br>observer_3.schedule(event_handler_3, f&#39;{watch_path_3}&#39;, recursive=True)<br>observer_3.start()</pre><ul><li>Three instances of the EventHandler class are created, each associated with a different pair of watch_path and destination_root.</li><li>Three instances of the Observer class are created and configured to use the respective event handlers and directories.</li><li>The observers start to monitor the specified directories.</li></ul><p>Main Loop and Exception Handling:</p><pre>try:<br>    while True:<br>        sleep(60)<br>except KeyboardInterrupt:<br>    observer_1.stop()<br>    observer_2.stop()<br>    observer_3.stop()<br>observer_1.join()<br>observer_2.join()<br>observer_3.join()</pre><ul><li>The script enters a main loop where it sleeps for 60 seconds (sleep(60)) to keep the program running.</li><li>It catches a KeyboardInterrupt exception (typically triggered by pressing Ctrl+C in the terminal) to gracefully stop the observers.</li><li>observer.stop() is called for each observer to stop monitoring.</li><li>observer.join() is called for each observer to wait for the observer&#39;s thread to finish.</li></ul><p>This script is essentially a main Python file organizer that continuously watches three directories for modifications and organizes files into corresponding subdirectories. It uses the watchdog library for efficient file system event handling.<br>For testing purposes, you can run the cleandesk.py file to see the Python magic happen as the specified directories are cleaned up.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ljhUhfSEK6CZ_N5zDHWXpg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XeWxWiGk0aHlp6KBKCGFGQ.png" /><figcaption>Before running the cleaning script</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/374/1*twL4AaRrEC11HK_6FY2Cjw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yto5n5CCzwGoZgZqUYFjvA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/524/1*DSHMYN4-W9puDW-orGnrhg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/941/1*iXfpPv8kJlOSHQqDiKzh1w.png" /><figcaption>After running the cleaning script</figcaption></figure><h3>Customizing Cleaning Rules</h3><p>To customize the cleaning rules for your Python desktop cleaner, you can modify the extension_paths dictionary. Here are five ways you can customize the rules:</p><ol><li>Add New File Extensions:</li></ol><ul><li>To include additional file types, simply add new key-value pairs to the extension_paths dictionary. For example:</li></ul><pre>&#39;.new_ext&#39;: &#39;custom_folder/custom_subfolder&#39;</pre><p>2. Modify Existing Paths:</p><ul><li>Adjust the destination paths for existing file extensions to suit your organization&#39;s preferences. For instance:</li></ul><pre>&#39;.txt&#39;: &#39;text/my_text_files&#39;</pre><p>3. Create New Categories:</p><ul><li>Introduce new categories by specifying unique folder structures for certain file types. For example:</li></ul><pre>&#39;.xyz&#39;: &#39;custom_category/subfolder</pre><p>4. Exclude Specific Extensions:</p><ul><li>If you want to exclude certain file types from being organized, you can remove them from the extension_paths dictionary. For instance:</li></ul><pre>del extension_paths[&#39;.unwanted_ext&#39;]</pre><p>5. Change Root Destination Paths:</p><ul><li>Modify the root destination paths for different watch paths by adjusting the destination_root variables in the main script. For example:</li></ul><pre>destination_root_1 = Path.home() / &#39;Downloads/new_destination&#39;</pre><p>Remember to consider the organizational structure that makes the most sense for your workflow and adjust the paths accordingly. After making changes, restart the script to apply the updated cleaning rules.👍</p><h3>Automating the Cleaning Task</h3><p>Now to automate the cleaning Python script on your computer we are gonna be using the Windows task scheduler to run the script at predefined intervals.</p><p>To run your<strong> Python scheduler</strong> you will need to create a task, create an action, add the path to your Python executable file and to your Python script, and add a trigger to schedule your script.</p><h3>1. Create Your First Task</h3><p>Search for “Task Scheduler”.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/828/1*x2uCck3Aoy367QpbY8mbwA.png" /></figure><p>This will open the Windows Task Scheduler GUI.</p><p>Go to Actions &gt; Create Task…</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/293/1*qdcry_DEuSv9D2Q-U_07nQ.png" /></figure><p>Give the task a name.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/647/1*BKkZVY-Tt7tIyJuxxM06VA.png" /></figure><p>Click “OK”.</p><h3>2. Create an Action</h3><p>Go to Actions &gt; New</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/882/1*zj8RZjNvUdoXgiTffEVmhA.png" /></figure><p>Click “OK”.</p><h3>3. Add the Python Executable File to the Program Script</h3><p>Find the Python Path using where python in the command line.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/647/1*3lCsxEpy9I3UjrSbc0VRZA.png" /></figure><p>From the command prompt copy the script to use in the action.</p><blockquote><em>C:\yourpath\python.exe</em></blockquote><blockquote>or in my case</blockquote><blockquote><em>C:\Users\Kodi_\AppData\Local\Programs\Python\Python310\python.exe</em></blockquote><p>In Program/Script, add the path you copied from the command line.</p><p>Click “OK”.</p><h3>4. Add the Path to Your Python Script in the Arguments</h3><p>Go to the folder where your Python script is located. Right-click on the file and select Copy as path.</p><p>If you have a file located at this location.</p><blockquote><em>C:\user\your_python_project_path\yourFile.py</em></blockquote><p>In the &quot;Add arguments (optional)” box, you will add the name of your python file.</p><blockquote><em>cleandesk.py</em></blockquote><p>In the &quot;Start in (optional)&quot; box, you will add the location of your python file.</p><blockquote>C:\Users\Kodi_\Downloads\desktop_cleaner-master\desktop_cleaner</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/548/1*OtLAe4fqtK9aBiLBd-gvPg.png" /></figure><p>Click “OK”.</p><h3>5. Trigger Your Script Execution</h3><p>Go to “Triggers” &gt; New</p><p>Choose the repetition that you want. Here you can schedule Python scripts to run daily, weekly, monthly, or just one time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/665/1*VQu8hJ4uY-ZO72QwmHmKJw.png" /></figure><p>Once, you have set this up, your trigger is now active and your Python script will run automatically every day.</p><p>Now the Python cleaner will run every day at every hour. Pretty neat if you ask me😉.</p><p>In conclusion, if you want an easy way to keep your desktop from spiraling into chaos, look no further than a Python cleanup script. With a little coding knowledge, you can customize a solution that perfectly fits your workflow. Say goodbye to desktop clutter — with Python on your side, a tidy desktop is only a script away! 👉<a href="https://github.com/Benji918/desktop_cleaner-master">Soruce code</a>👈</p><h3>In Plain English 🚀</h3><p><em>Thank you for being a part of the </em><a href="https://plainenglish.io"><strong><em>In Plain English</em></strong></a><em> community! Before you go:</em></p><ul><li>Be sure to <strong>clap</strong> and <strong>follow</strong> the writer ️👏<strong>️️</strong></li><li>Follow us: <a href="https://twitter.com/inPlainEngHQ"><strong>X</strong></a><strong> | </strong><a href="https://www.linkedin.com/company/inplainenglish/"><strong>LinkedIn</strong></a><strong> | </strong><a href="https://www.youtube.com/channel/UCtipWUghju290NWcn8jhyAw"><strong>YouTube</strong></a><strong> | </strong><a href="https://discord.gg/in-plain-english-709094664682340443"><strong>Discord</strong></a><strong> | </strong><a href="https://newsletter.plainenglish.io/"><strong>Newsletter</strong></a></li><li>Visit our other platforms: <a href="https://stackademic.com/"><strong>Stackademic</strong></a><strong> | </strong><a href="https://cofeed.app/"><strong>CoFeed</strong></a><strong> | </strong><a href="https://venturemagazine.net/"><strong>Venture</strong></a><strong> | </strong><a href="https://blog.cubed.run"><strong>Cubed</strong></a></li><li>More content at <a href="https://plainenglish.io"><strong>PlainEnglish.io</strong></a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cbd5215b51a4" width="1" height="1" alt=""><hr><p><a href="https://python.plainenglish.io/python-magic-streamlining-your-desktop-with-a-cleanup-script-cbd5215b51a4">Python Magic: Streamlining Your Desktop with a Cleanup Script👌</a> was originally published in <a href="https://python.plainenglish.io">Python in Plain English</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Learning to Code with AI- Benefits and Drawbacks]]></title>
            <link>https://medium.com/@kodiugos/learning-to-code-with-ai-benefits-and-drawbacks-935f5b09ddde?source=rss-99c5bfceed4b------2</link>
            <guid isPermaLink="false">https://medium.com/p/935f5b09ddde</guid>
            <category><![CDATA[learning-to-code]]></category>
            <category><![CDATA[programming-tips]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[coding]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <dc:creator><![CDATA[Ugochukwu Benjamin .C]]></dc:creator>
            <pubDate>Tue, 16 Jan 2024 20:15:23 GMT</pubDate>
            <atom:updated>2024-09-04T16:25:46.768Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*gMyRrTfN6CQMQNqr" /><figcaption>Photo by <a href="https://unsplash.com/@omilaev?utm_source=medium&amp;utm_medium=referral">Igor Omilaev</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Table of Content</h3><ol><li>Drawbacks and Challenges</li><li>Balancing Human and AI in Coding Education</li><li>Summary</li></ol><h3>DRAWBACKS AND CHALLENGES</h3><p>As artificial intelligence becomes increasingly adept at assisting with educational tasks like tutoring, grading assignments, and answering questions, some schools, and coding boot camps have started experimenting with “AI teaching assistants” to help students learn coding fundamentals. On the surface, the promise of having a digital tutor available around the clock seems ideal. But is overreliance on AI for learning to code a good thing? Here are a few potential downsides and challenges to consider.</p><h4>Overreliance Could Stunt Problem-Solving Skills</h4><p>While AI teaching assistants are great for looking up syntax rules or walkthroughs of sample code, there’s a risk that students could become too dependent on the technology for hands-on problem-solving. Part of learning to code is figuring things out through trial and error, and students need to develop the ability to troubleshoot issues independently. If students start outsourcing all their problems to an AI tutor, it could hinder the development of important debugging skills. Interacting with a human teacher who can ask follow-up questions and think through problems out loud is invaluable for strengthening these self-sufficient problem-solving muscles.</p><h4>Lack of Human Interaction May Limit Learning</h4><p>As any experienced coder will tell you, the process of learning to code is as much about collaboration and communication as it is about memorizing syntax. Human teachers provide social-emotional support, help students through frustrating blocks, and foster a community of learners helping one another. While AI can be helpful, it lacks the interpersonal elements of learning. Things like explaining concepts verbally, having nuanced discussions, and providing individualized feedback tailored to learning styles are still better handled by people rather than programs. Sole reliance on AI risks missing out on the motivation and accountability that comes from learning alongside other students and instructors.</p><h4>Potential for Bias and Limitations in Algorithms</h4><p>Like all AI systems, teaching assistants developed using machine learning algorithms will only be as unbiased and comprehensive as the data used to train them. If the training data reflects a narrow range of human technical skills, problem domains, or cultural viewpoints, it could inadvertently propagate those limitations. For example, an AI may have a harder time explaining concepts that require reasoning about the social or ethical implications of coding. There’s also the risk of blind spots when it comes to atypical learning approaches or advanced interdisciplinary concepts. Having human experts who can adapt lessons to diverse individual needs helps overcome these algorithmic limitations.</p><h3>Finding the Right Balance: Teaching Code with a Mix of Machine and Mentor</h3><p>As an overworked coding student, the promise of AI handling all my homework assignments sounds pretty great. Who wouldn’t want a robot teacher who never gets tired, never takes a day off, and gives perfect feedback every time? On the other hand, having a real human teacher does have some advantages over our metallic friends. So how do we find the right balance between AI and actual people in the classroom?</p><p>First of all, let’s be real — AI is amazing but it’s not perfect. Those robot teachers may be tireless, but they’re also soulless! There’s no replacing the joy of making your real teacher laugh during class or bonding with them over your shared love of coding. AI can check syntax and run test cases like nobody’s business, but it lacks the human touch of actually understanding where a student is struggling and tailoring help specifically for them.</p><p>Not to mention, AI is still pretty narrow — it’s great at specific technical tasks but lacks common sense reasoning. Imagine trying to explain a complex conceptual problem to a robot! “I’m sorry, I do not experience confusion or have an intuitive understanding of abstract concepts. Have you tried rephrasing your question using only terminology from our training data?” No thanks, I’ll take my chances with a person.😅</p><p>So clearly, AI alone cannot replace human teachers. But that doesn’t mean it has no role to play! Used judiciously as a supplement to — not a substitute for — real teachers, AI tools have huge potential to enhance learning. For example, robots could handle basic grading and feedback for homework problems, freeing up instructors to focus on higher-level guidance. AI tutoring systems could provide on-demand help for basic syntax and code structure outside of class hours. And virtual assistants may one day be able to recommend additional learning resources tailored to individual student’s strengths and weaknesses.</p><p>The future of education lies not in choosing humans or AI, but in finding the right balance. With humans to provide empathy, creativity, and expert guidance, and AI to handle routine tasks at scale, students could get the best of both worlds. So, rest easy coding students — those AI models aren’t replacing teachers just yet. But they might just do your homework for you if you ask nicely!😂</p><p>In the end, the key is using AI is to augment human teachers rather than replace them. A balanced approach that leverages the unique capabilities of both can truly enhance learning outcomes.</p><h3>SUMMARY</h3><p>AI is revolutionizing coding education by offering personalized learning plans, real-time feedback, and automated grading. However, overreliance on AI may hinder problem-solving skills and limit holistic learning. A balanced approach that combines AI and human instruction is crucial to fostering well-rounded problem-solving skills and supporting students effectively. The future of coding education lies in a harmonious blend of AI and human instruction.</p><h4>Perspective on the Future</h4><p>As technology advances, the role of AI in coding education will likely continue to evolve. The key lies in finding the right balance between AI and human instructors. While AI can offer tremendous benefits in terms of personalized learning and efficiency, it cannot replace the human touch, empathy, and creativity that human teachers bring to the table. The future of coding education is not an “either-or” scenario but a synergistic blend of AI and human guidance. The article emphasizes the significance of using AI as a tool to augment human teaching rather than as a wholesale replacement. With this balanced approach, the potential for enhanced learning outcomes in coding education is vast, offering students the best of both worlds.</p><h3>CALL TO ACTION</h3><p>Alright, my fellow coding enthusiasts, it’s time to wrap this up. I want to hear from you, so don’t be shy. Share your thoughts and experiences with AI in coding, and let’s keep the conversation going. And remember, the future of coding is bright, thanks to AI. So, let’s embrace it and see where it takes us. Happy coding!✨</p><p>Further Reading and Resources:</p><ul><li><a href="https://www.theguardian.com/commentisfree/2023/jun/12/lost-job-learn-code-ai-humans-skills">Forbes: Can AI Be Your Coding Teacher?</a></li><li><a href="https://kowe.io/guide/19c3f41a-3096-4c67-8886-c81223d95957">How to Train a Machine Learning Model Using Paperspace</a></li><li><a href="https://www.theguardian.com/technology/2016/oct/12/schools-not-preparing-children-to-succeed-in-an-ai-future-mps-warn">The Guardian: Artificial intelligence could revolutionize coding education</a></li><li>“<a href="https://www.edsurge.com/news/2023-03-06-why-hidden-artificial-intelligence-features-make-such-an-impact-in-education">5 Ways AI Can Make Coding Education More Accessible” by EdSurge: Discover practical applications of AI in making coding education more inclusive and equitable for students of diverse backgrounds and abilities.</a></li><li><a href="https://www.oecd.org/education/2030-project/about/documents/Education-and-AI-preparing-for-the-future-AI-Attitudes-and-Values.pdf">“AI in Education: Transforming Learning for a Digital Age” by OECD: This comprehensive report explores the potential and challenges of AI in education, offering valuable insights for educators and policymakers.</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=935f5b09ddde" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My Journey: Deploying a Django App on Vercel with Supabase]]></title>
            <link>https://medium.com/@kodiugos/my-journey-deploying-a-django-app-on-vercel-with-supabase-6cdb1901f724?source=rss-99c5bfceed4b------2</link>
            <guid isPermaLink="false">https://medium.com/p/6cdb1901f724</guid>
            <category><![CDATA[django-rest-framework]]></category>
            <category><![CDATA[django]]></category>
            <category><![CDATA[vercel]]></category>
            <category><![CDATA[supabase]]></category>
            <category><![CDATA[deployment]]></category>
            <dc:creator><![CDATA[Ugochukwu Benjamin .C]]></dc:creator>
            <pubDate>Sun, 05 Nov 2023 02:29:47 GMT</pubDate>
            <atom:updated>2024-04-20T01:38:57.747Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*zA378UIa5CFYYAfN" /><figcaption>Photo by <a href="https://unsplash.com/@foxxmd?utm_source=medium&amp;utm_medium=referral">Matt Duncan</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3><strong>Table of Content</strong></h3><ol><li>Introduction</li><li>Settings up the project</li><li>Working with Supabase</li><li>Deployment on Vercel</li><li>Challenges and Lessons Learned</li><li>Benefits and Final Thoughts</li><li>Conclusion</li><li>Tips and Takeaways</li><li>References and Resources</li></ol><h4>1. INTRODUCTION</h4><p>Hey there, fellow developers!👋 I recently wrote an <a href="https://kowe.io/guide/how-to-deploy-your-django-app-on-vercel-with-a-supabase-postgresql-twist">article</a> on <a href="https://kowe.io/">kowe.io</a> where I embarked on an exciting journey to deploy my Django application. I wanted to share my experiences with you as I navigated the intricacies of Django app deployment. One of the most significant decisions I had to make was where and how to host the application and its database. After extensive research and a bit of trial and error, I decided to go with Vercel for hosting and Supabase for the database🚀.</p><h4>2. SETTING UP PROJECT</h4><p>Now, let’s dive into the nitty-gritty🐱‍🏍. My project’s architecture relies heavily on Django’s flexibility, the robust ecosystem of packages, and the wealth of resources in its documentation. Django is like an old friend to me, offering a structured approach to application development while still allowing me to handle complex data relationships.</p><p>For the database, I opted for Supabase, a versatile and developer-friendly platform that simplifies database management and provides real-time capabilities. The beauty of it is how seamlessly it integrates with my Django application. It made development and testing feel like a breeze😉✨.</p><h4>3. WORKING WITH SUPABASE</h4><p>Integrating my Django app with Supabase was smoother than I expected, thanks to their Supabase database URL that includes everything you need to connect to your Supabase database. It’s that straightforward! it’s just like plug-and-play. This cloud database platform not only simplified database management but also enhanced my Django application’s capabilities.</p><p>Supabase’s strength lies in its user-friendly interface. Setting up your database and managing it becomes a breeze. Whether you need to design a new schema, migrate data, or make real-time changes to your database structure, the Supabase dashboard is your go-to tool🤞.</p><h4>4. DEPLOYMENT ON VERCEL</h4><p>Deploying my Django application on Vercel wasn’t very straightforward(I’ll dive deeper into that in the next section). For those that don&#39;t know Vercel’s platform is primarily designed for hosting and deploying <strong>frontend</strong> and <strong>serverless</strong> applications. While it’s exceptionally well-suited for serving frontend code, static assets, and serverless functions, it doesn’t provide native support for hosting traditional server-side applications, including those built with <strong>server-side frameworks like Django.😞</strong></p><p>If you have a server-side application, consider other hosting providers or cloud platforms better suited to manage server-side code. However, if your application is still small and in the testing phase, then deployment on Vercel can still work. 👍</p><h4>5. CHALLENGES AND LESSONS LEARNED</h4><p>Of course, no journey is complete without its fair share of challenges😅. I encountered a significant challenge during the deployment of my application on Vercel. Upon the completion of the deployment process, the application presented a 504 Gateway Timeout error, signifying that the request from the wsgi.py file to the web server had timed out🤦‍♂️. This issue was unexpected, particularly because I had previously deployed the same application to Railway without encountering this error.</p><p>In my quest to understand the root cause, I conducted research on Vercel’s platform. It came to my attention that Vercel’s serverless functions, which require a request from the wsgi.py, have a default time limit of 10 seconds for processing requests, especially applicable to hobby projects.</p><p>Given the complexity of my <a href="https://github.com/Benji918/Personal_finance_portal">Django project</a>, which consisted of four(4) separate apps, it exceeded the specified time limit to send a request, ultimately resulting in the 504 Gateway Timeout error. In order to increase the timeout for serverless functions on Vercel, this typically requires an upgrade to a paid plan which was a major deal breaker for me.💔</p><p>In the quest to resolve the 504 Gateway Timeout error on Vercel, I found myself at a crossroads🤷‍♀️. The existing Django project, with its intricate structure and multiple apps, had exceeded Vercel’s serverless function timeout of 10 seconds. While the idea of breaking down the project into smaller, more manageable components had crossed my mind, I faced the reality that significant refactoring would be required😫.</p><p>Refactoring, while a valid solution, would mean a substantial overhaul of the existing codebase, restructuring, and optimizing it to fit within Vercel’s constraints. It would have been a time-consuming and resource-intensive process, and there was no guarantee of success without extensive testing and fine-tuning.</p><p>In light of these considerations💡, I began to explore an alternative path. It dawned on me that I could create an entirely new Django project specifically tailored to align with Vercel’s serverless function limitations. This fresh project would be designed from the ground up to operate seamlessly within the 10-second time frame, without the need for extensive refactoring and adjustments.</p><p>The decision to opt for a new project had its advantages. It provided a clean slate, allowing me to architect the application with Vercel’s constraints in mind from the outset. This approach promised a quicker resolution to the 504 error, as well as a more efficient development process without the need to unravel and rebuild the existing codebase.</p><h4>6. BENEFITS AND FINAL THOUGHTS</h4><p>The benefits of deploying my Django app on Vercel with Supabase were well worth the journey. Though the deployment process was not as efficient as I had hoped, it really taught me the importance of the adaptability of applications to meet the requirements of hosting platforms.</p><h4>7. CONCLUSION</h4><p>My journey deploying a Django application on Vercel with Supabase was a rewarding experience, and I wanted to share it with you. It’s my hope that my story provides guidance and inspiration for others considering similar deployment options. When you combine the power of Django with the hosting capabilities of Vercel and the versatility of Supabase, the possibilities for web application development become boundless.</p><h4>8. TIPS AND TAKEAWAYS</h4><ul><li>Consider Django &amp; Supabase for a versatile and scalable application stack.</li><li>Opt to use Vercel for small projects that don’t require much scalability.</li><li>Before choosing a hosting platform learn its strengths and limitations.</li></ul><h4>9. REFERENCES AND RESOURCES</h4><ul><li><a href="https://docs.djangoproject.com/">Django Documentation</a></li><li><a href="https://vercel.com/docs">Vercel Documentation</a></li><li><a href="https://supabase.io/docs">Supabase Documentation</a></li><li><a href="https://kowe.io/guide/how-to-deploy-your-django-app-on-vercel-with-a-supabase-postgresql-twist">Tutorial on how to deploy your Django project using Vercel &amp; Supabase</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6cdb1901f724" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>