@Jasper_Holton's profile photo

How to Create an Infinitely Scrolling Django View Creating a pseudo-infinitely scrolling page for a Django site is fairly easy. It involves creating a main page that renders the first posts, a secondary page that renders more posts according to a page number, and using some basic JavaScript to load in more posts when the user scrolls to the bottom of the page. I created two views to handle the backend code.

# This view renders a scrollable page.
@vary_on_cookie
def scroll(request):
    posts = Post.objects.filter(public=True).order_by('date_posted') # Get the posts for the first page
    p = Paginator(posts, 10) # Paginate them
    if request.user.is_authenticated: # Mark viewership if users are logged in
        for post in p.page(p.num_pages):
            if not post.viewers.filter(id=request.user.id).exists():
                post.viewers.add(request.user)
    context = { # Render the posts
        'posts': p.page(p.num_pages),
        'count': p.count,
        'page_obj': p.get_page(p.num_pages),
        'title': 'Scroll Posts',
        'description': 'Scroll and see all the posts on Uglek here. This is the front page of Uglek.' + basedescription,
        'dontshowsidebar': True,
        'full': True,
        'num_pages': p.num_pages,
    }
    response = render(request, 'blog/scroll.html', context)
    if request.user.is_authenticated:
        patch_cache_control(response, private=True) # Render private page
    return response

# This view is simple. It renders the posts for each page as we scroll through them.
@vary_on_cookie
def scroll_page(request):
    posts = Post.objects.filter(public=True).order_by('date_posted') # Get posts
    p = Paginator(posts, 10) # Paginate them
    page = p.num_pages - 1
    if(request.GET.get('page', '') != ''): # Get the page from the querystring
        page = int(request.GET.get('page', ''))
    if request.user.is_authenticated: # Mark viewership if the user is logged in
        for post in p.page(page):
            if not post.viewers.filter(id=request.user.id).exists():
                post.viewers.add(request.user)
    context = {
        'posts': p.page(page), # Render the posts
        'count': p.count,
        'page_obj': p.get_page(page),
    }
    response = render(request, 'blog/scrollpage.html', context)
    if request.user.is_authenticated:
        patch_cache_control(response, private=True) # Render private page
    return response
The javascript is pretty simple too, we just load in new content every time the window is scrolled close to the bottom.
var page = {{ num_pages }};
$(window).scroll(function() { // On scroll
    if ($(window).scrollTop() + $(window).height() > $(document).height() - 5000) {
        loadNext(); // Load new content
    }
});
loadNext(); // Start by loading
var loading = false; // Keep track of whether we are loading
function loadNext() {
    if (!loading && page > 1) { // If we are not loading already and there is another page
        loading = true; // Mark that we are loading
        page = page - 1; // Decrement the page (to load the next page)
        var scrollContainer = document.getElementById("scroll-container"); // Get the container
        const urlParams = new URLSearchParams(window.location.search);
        var lang = '';
        if (urlParams.get('lang') != null) {
            lang = "&lang=" + urlParams.get('lang'); // Get the language
        }
        // Make a request to the next page with the language
        const Http = new XMLHttpRequest(); 
        const url = "https://uglek.com/scroll/page/?page=" + page + lang;
        Http.responseType = 'text';
        Http.onload = function() {
            if (Http.readyState === Http.DONE) {
                scrollContainer.insertAdjacentHTML("beforeend", Http.responseText);
                loading = false;
            }
        };
        Http.open("GET", url, true);
        Http.send(null); // Send the request
    }
}
The templates are fairly self-explanatory, you just need to render the first posts in the main template, scroll.html, and more posts in scrollpage.html. You'll need to render the pages in an iframe in order for some scripts and products like Google AdSense to work, this is easy to do as you will just need a base template including your styles and scripts which serves a full HTML page (capable of serving ads). You can use this template to render your posts and load them into the main view. The loadNext(); function looks a little different this time, more like this.
 // Load the next part of the site into the main site in an iframe
function loadNext() {
    if (!loading && page > 1) {
        loading = true;
        page = page - 1;
        var scrollContainer = document.getElementById("scroll-container");
        const urlParams = new URLSearchParams(window.location.search);
        var lang = '';
        if (urlParams.get('lang') != null) {
            lang = "&lang=" + urlParams.get('lang');
        }
        const url = "uglek.com/scroll/page/?page=" + page + lang;
        var iframe = document.createElement('iframe');
        iframe.src = url;
        iframe.frameBorder = 0;
        iframe.scrolling = 'no';
        iframe.style.width = '100%';
        iframe.classList.add('mb-3');
        $(iframe).attr('target', '_parent');
        iframe.onload = function() { // Adjust size of iframe on load
            iframe.height = iframe.contentWindow.document.body.scrollHeight + 'px';
            loading = false;
        };
        scrollContainer.appendChild(iframe); // Append it to the document
    }
}
This is all it takes to create an infinitely scrolling Django website. You will be able to automatically load in new posts as you scroll using this code. I am making use of this on the homepage, where you can now scroll to older posts without pressing a button for the next page.


@Jasper_Holton's profile photo

Database-Driven Translation Caching Django Template Filter This is a Django template filter designed to cache translations. It uses database models representing the translations and then queries them by text so you can render text in any language on a website, and only translate the text once. I have also included the code I use to translate the text, using NLP Translation from RapidAPI.com, as well as middleware to get the request in a template filter (in order to get the language). This code relies on the browser's language code, as well as a query string, ?lang=, to render the text in the users' languages. I have added comments to describe how the code works. The code begins with some middleware:

# app/middleware.py
from threading import local # Imports
from django.utils.deprecation import MiddlewareMixin

_request = local() # Store the request

class CurrentRequestMiddleware(MiddlewareMixin): # Use a middleware mixin to save the request
    def process_request(self, request):
        _request.value = request

def get_current_request(): # Return the request
    try:
        return _request.value
    except AttributeError:
        return None
Next is some settings
# project/settings.py
MIDDLEWARE = [
    'blog.middleware.CurrentRequestMiddleware', # The middleware we just made for the request
    'django.middleware.locale.LocaleMiddleware' # Middleware to store the language code in the request
]
And a model to store the translations.
# app/models.py
from django.db import models
class Translation(models.Model):
    id = models.AutoField(primary_key=True)
    src = models.CharField(max_length=2, default='en')
    lang = models.CharField(max_length=2, default='en')
    value = models.TextField(blank=True)
    translated = models.TextField(blank=True)
Lastly, the template filters and some helper functions to make them work
# app/templatetags/app_filters.py

from app.models import Translation # Imports
from app.middleware import get_current_request
from django import template

register = template.Library() # Register the template library

def get_lang(): # Get the language to translate to
    request = get_current_request()
    lang = request.LANGUAGE_CODE
    if(request.GET.get('lang', '') != ''):
        lang = request.GET.get('lang', '')
    if lang == None:
        lang = 'en'
    return lang

# Tranlsate a string to any language
def translateval(value, lang, src=None):
    src = src if src != None else 'en'
    if lang == src:
        return value
    trans = Translation.objects.filter(value=value,lang=lang,src=src if src != None else 'en') # Get the translation
    if trans.count() > 0: # Return it if it exists
        return trans.first().translated
    else: # Otherwise create it
        originalvalue = value
        value = value.replace('\n', '[=NEWLINE=]') # Preserve newlines
        translation = ''
        try:
            translation = requests.request("GET", url, headers=headers, params={"text": value, "to": lang, "from": src if src != None else 'en', 'protected_words': '@;[=NEWLINE=];'}).json()['translated_text'][lang]'protected_words>
        except:
            translation = value
        translation = translation.replace('[=NEWLINE=]', '\n').replace('[= NEWLINE =]','\n').replace('[=NEWLINE =]', '\n') # Newline fix, to preserve newlines
        ntrans = Translation.objects.create(value=originalvalue,lang=lang,src=src if src != None else 'en', translated=translation) # Create a new translation
        ntrans.save()
        return ntrans.translated # Return the translated value

# A template filter to translate from english to any language
@register.filter('etran') # Register the filter to translate
def etran(value):
    lang = get_lang() # Get the language
    translation = None
    if not lang == 'en':
        try:
            translation = translateval(value, lang, 'en') # Translate using NLP translation
        except:
            translation = None
    if translation != None:
        return translation
    return value
In action, this looks like
{{ 'Here is some text. This text will be translated from English to another language'|etran }}
I hope you find this Django code useful. I want to post lots of useful Python here so people can read it and make use of it in their own code. This code does a great job of translating all the text on the site to render it in the proper language.


@Jasper_Holton's profile photo

Render Template Without Line Breaks Using a Django Template Tag This Django block template tag renders your templates without any line breaks. Be aware, it might mess up the content of forms so I recommend using it outside of them. This is the block tag I use on Uglek to serve the site without any line breaks in the base code.

# /project/app/app_filters.py

# Imports
from django import template
from django.utils.html import strip_tags
from django.template.base import Node
from django.utils.functional import keep_lazy
import six

register = template.Library() # Register template library
@register.tag
def linebreakless(parser, token):
    nodelist = parser.parse(('endlinebreakless',))
    parser.delete_first_token() # Delete tags from the template
    return LinebreaklessNode(nodelist) # Render the node without line breaks

class LinebreaklessNode(Node):
    def __init__(self, nodelist):
        self.nodelist = nodelist # Initialize with nodelist

    def render(self, context):
        strip_line_breaks = keep_lazy(six.text_type)(lambda x: x.replace('\n', '')) # Using keep lazy
        return strip_line_breaks(self.nodelist.render(context).strip()) # Strip the line breaks and return the node

In action, this tag looks like
{% linebreakless %}This is some text here. This will render without a line break{% endlinebreakless %}
You can use this tag twice in your templates to render everything but the main content without line breaks, and it will save some server time serving fewer line breaks as well as make it more difficult for anyone to copy and use your code. It also could have other uses, such as removing line breaks in forms or in the text that contains too many and should be rendered only on one line. I hope this Django template filter is useful for you. Let me know if you have any questions about implementing it.


@Jasper_Holton's profile photo

Django Template Filters to Add HTTPS and Highlight URLs You may use these Django template filters to add HTTPS to your URLs and highlight them as anchor tags (a) with simple code. I have added comments to describe how the code works.

 # app/templatetags/app_filters.py
from urlextract import URLExtract
import requests
...
@register.filter(name='addhttpstodomains') # The first filter, to add HTTPS
def addhttpstodomains(value):
    domains = re.findall(r'', value) # Regex to get a URL
    dic = {}
    output = ""
    for domain in domains:
        if not domain[1].lower() in escaped_domains:
            url = 'https://' + domain[1]
            if not url.lower().startswith('uglek.com'):
                try:
                    response = requests.head('https://' + domain[1]) # Get the head
                    if response.status_code == 200: # If the page exists
                        dic[domain[1]] = 'https://' + domain[1] 
                except:
                    print("URL does not exist")
            else:
                dic[domain[1]] = 'https://' + domain[1]
    replaced_items = []
    for i, j in dic.items(): # Replace without replacing twice
        if not i in replaced_items:
            value = value.replace(i, j)
        replaced_items.append(i);
    value = str(value)
    while value.find('https://') > -1: # Remove any duplicates
        value = value.replace('https://','https://')
    return value

@register.filter(name='embedlinks')
def embedlinks(value):
    output = ""
    chunks = value.split("https://") # Split at HTTPS
    for x, chunk in enumerate(chunks): # Enumerate over split sections
        if x != 0:
            chunk = "https://" + chunk # Add HTTPS back in
        urls = extractor.find_urls(chunk); # Find URL in chunk
        dic = {}
        for url in urls:
            plus = ' (it\'s on Uglek)' # Configure anchor tag
            if not url[8:17].lower() == 'uglek.com':
                plus = ' (it will take you outside of Uglek)'
            if url.endswith('.') or url.endswith(','):
                url = url[:-1]
            if not url.lower().startswith('uglek.com') and url.lower().startswith('https://'):
                try:
                    response = requests.head(url) # Get the page
                    if response.status_code == 200: # If it exists
                        dic[url] = '' + url[8:] + '' # Build anchor tag to replace URL with
                except:
                    print("URL does not exist")
            elif url.startswith('https://'):
                dic[url] = '' + url[8:] + ''
        for i, j in dic.items(): # Find and replace in chunk
            chunk = chunk.replace(i, j)
        output = output + chunk # Put the pieces back together
    return output
In action, these tags look like:
{{ post.content|addhttpstodomains|embedlinks }} # Add HTTPS and embed links


@Jasper_Holton's profile photo

How to Upgrade Python Googletrans to Google Translate Enterprise API Upgrading to Google Translate Enterprise is fairly simple. It only involves modifying the initialization of the translator and changing the translate command. This looks like the following:


# app/templatetags/app_filters.py
from google.cloud import translate_v2 as translate
...
# translator = translate.Translator() -- Remove this
translator = translate.Client.from_service_account_json('/home/path/to/service-account-file.json') # Change to this
...
And update the individual translate lines in each translation template filter

# app/templatetags/app_filters.py
...
# translation = translator.translate(...) -- Remove this
translation = translator.translate(value, target_language=lang, source_language='en', format_='html')['translatedText'] # Change to this
...
This is all it takes to get the Django template tags from my previous translation example working with Google Cloud Enterprise translation. Visit the previous example: uglek.com/post/300/ or see the code in action by using the "Language" button at the top of the site or sidebar (bottom on mobile) and selecting a language to translate the website.


one comment.

Jasper_Holton's profile photo

Uglek is now mostly translated, save for a few views. It is also much faster with this translation API.

@Jasper_Holton's profile photo

How Uglek's Message Client Frontend Works Uglek has a new message client, with a dynamic layout that is fast and responsive. The frontend is fairly simple, it consists of HTTP requests to get information and render it to the screen. It is coupled with a backend that allows the user to send messages, checks the messages for spam, and sends the user a push notification. I will update with instructions for the push notifications another day. This is the javascript code for the frontend. I have added comments describing what each part of the code does. You can see this code in action at uglek.com/message/ by messaging a user on their profile, like uglek.com/user/intersex/, or replying to one of your messages. The internal URLs are URLs under uglek.com and are used for displaying the message sidebar, incoming messages, and page links, as well as message counts respectively.

// This part of the code reloads the sidebar to show last users in the users following who are active 
// along with how many new messages from each user have been received. 
if(document.getElementById("message-sidebar")){ // If the sidebar exists (the part we are reloading)
        var sidebar = document.getElementById("message-sidebar"); // Get the sidebar
        function reloadSidebar(){ // A function to reload it
                const Http = new XMLHttpRequest(); // Use an XML request
                const url="(internal URL, the sidebar to reload)";
                Http.responseType = 'text';
                Http.onload = function() { // When the request loads
                        if(Http.readyState === Http.DONE){ // If it is done (200)
                                sidebar.innerHTML = Http.responseText; // Set the inner HTML of the sidebar
                        }
                };
                Http.open("GET", url, true);
                Http.send(null);
        }
        var interval = setInterval(function() { // Run this every 15 seconds
                reloadSidebar();
        }, 15000);
}

if(document.getElementById("user-messages")){ // If we are on the message user screen
        var msgs = document.getElementById("user-messages"); // Get the users messages
        function reloadMessages(){ // A function to reload the messages 
                const Http = new XMLHttpRequest();
                const url="(internal URL, raw messages by user)" + document.getElementById("user-username") + "/" // With the user username (rendered to the template)
                Http.responseType = 'text';
                Http.onload = function() {
                        if(Http.readyState === Http.DONE){
                                msgs.innerHTML = Http.responseText; // Update the messages
                        }
                };
                Http.open("GET", url, true);
                Http.send(null);
        }
        const urlParams = new URLSearchParams(window.location.search); // Get URL parameters
        var count = 0; // Save message count
        var lastcount = 0
        var firstLoad = true;
        if(urlParams.get('page') == '1' || !urlParams.get('page')){
                const Http = new XMLHttpRequest();
                 // With the users username
                const url="(internal URL, number of messages sent by each user)" + document.getElementById("user-username") + "/";
                Http.responseType = 'text';
                Http.open("GET", url, true);
                Http.onload = function() {
                        if(Http.readyState === Http.DONE){
                                count = parseInt(Http.responseText);
                                if(count != lastcount && !firstLoad){ // If there are new or deleted messages and this isn't the first page load
                                        reloadMessages(); // Reload the messages
                                }
                                firstLoad = false;
                                lastcount = count;
                        }
                };
                var interval = setInterval(function() {
                        Http.open("GET", url, true);
                        Http.send(null);
                }, 5000); // Reload messages every five seconds
                Http.send(null);
        }
}
This is all the JavaScript code it takes to build a messaging frontend for a website. The code simply needs to update the users' incoming messages and message counts, along with information about followers the user is messaging (their last seen, profile picture, and message count). The messaging is nearly instant, with an average delay of 2.5 seconds, and image uploads are supported.


@Jasper_Holton's profile photo

Tag-Preserving Translation Template Block Filter This is a block translation template filter, for translating a block of text into any language from English on a Django website. You can use this filter to translate an entire page of text using Python.

 # templatetags/app_filters.py
from django.template.base import Node # Imports
from googletrans import Translator
from langdetect import detect
from django.contrib import messages

translator = Translator() # Initialize translator and template library
register = template.Library()

def translatevalue(lang, input): # A method to translate HTML while preserving styles
    values = input.splitlines() # Split the html
    output = '' # Init outputs
    translations = list()
    strippedvalues = list()
    for value in values: # For each value
        stripped = ''
        try: # Try to strip the tags
            tagsplit = value.split('>', 1);
            otag = tagsplit[0] + '>'
            ctag = '<' + tagsplit[1].split('<', 1)[1]
            stripped = tagsplit[1].split('<', 1)[0]
            if(len(stripped) > 0): # If they contain something
                translations.append(translator.translate(stripped, dest=lang, src='en').text) # Translate that
            else:
                translations.append("") # Or store no translation
            strippedvalues.append(stripped) # Saved the stripped value
        except:
            translations.append("") # Fallback
            strippedvalues.append(stripped)
    for x in range(len(values)): # Build the translated text
        output = output + values[x].replace(strippedvalues[x], translations[x]) + '\n'
    return output # Return it

# The tag
@register.tag
def blocktranslate(parser, token):
    nodelist = parser.parse(('endblocktranslate',))
    parser.delete_first_token()
    return TranslateNode(nodelist)

# The node
class TranslateNode(template.Node):
    def __init__(self, nodelist):
        self.nodelist = nodelist
    def render(self, context): 
        input = self.nodelist.render(context) # Get the HTML
        lang = context['request'].LANGUAGE_CODE # Get the language code and querystring
        if(context['request'].GET.get('lang', '') != ''):
            lang = context['request'].GET.get('lang', '')
        if lang == None:
            lang = 'en'
        translation = ''
        if not lang == 'en':
            translation = translatevalue(lang, input) # Translate if the user language isn't english
        if translation != '': 
            return translation # Return the translation
        return input
This template tag looks like {% blocktranslate %}Some text here{% endblocktranslate %}. I use it to translate whole pages, like the TOS and Privacy. It's much quicker to implement than using the single-line template filter.


@Jasper_Holton, likes this,

@Jasper_Holton's profile photo

Universal Language Translation Django Template Tags This is a Django template tag for universal translation, that is, translation from any language to the clients' language. It uses locale middleware to work and otherwise consists of app filters that translate the text as needed. The code is commented in order to describe what each piece does. The first part is the settings, where we need to add locale middleware so we can get the request.LANGUAGE_CODE.

# settings.py
MIDDLEWARE = [
    '...',
    'django.middleware.locale.LocaleMiddleware' # Add this to the end
]
And the template tags, as well as a filter.
# templatetags/app_filters.py
from django import template # Imports
from googletrans import Translator
from langdetect import detect # Detect language
register = template.Library() # Initialize template library and translator
translator = Translator()

@register.simple_tag(takes_context=True) # Pass context so we can get the language code
def etranslate(context, value): # Translate from english
    request = context['request'] # Get request context
    lang = request.LANGUAGE_CODE # Get language code
    if(request.GET.get('lang', '') != ''): # Override with URL parameter if there is one
        lang = request.GET.get('lang', '')
    if lang == None: # Fallback for none
        lang = 'en'
    translation = None # Init translation
    if not lang == 'en': # Translate only if language isn't english
        try:
            translation = translator.translate(value, dest=lang, src='auto').text # Auto translate
        except:
            translation = None # No translation on error
    if translation != None:
        return translation # Return translation
    return value # Return untranslated text on error

@register.simple_tag(takes_context=True)
def translate(context, value): # Translate from any language
    request = context['request']
    lang = request.LANGUAGE_CODE
    if(request.GET.get('lang', '') != ''):
        lang = request.GET.get('lang', '')
    if lang == None:
        lang = 'en'
    translation = None
    if not value == '' and not detect(value) == lang: # If the text isn't already in the users language
        try:
            translation = translator.translate(value, dest=lang, src='auto').text
        except:
            translation = None
    if translation != None:
        return translation
    return value

@register.filter(name='detectlanguage') 
def detectlanguage(value): # Detect the language
    return detect(value) # Return value (en, es, de, etc)
In action, the tag looks like {% etranslate 'Text here' %}. For an English client, this isn't translated by the site and just shows in English. For anyone else, the text is translated to their language (or at least an attempt is made). For non-English translations, such as a post that could be in German or Dutch, use {% translate 'Text here' %} for universal translation from any language to the browser language or language passed by a URL parameter lang. Also, I use the template filter detectlanguage with text on the site, {{ post.content|detectlanguage }} to display the language of posts next to them.


@Jasper_Holton's profile photo

A Simple Jumping Game in JavaScript with CreateJS and TweenJS This is the code for the game at uglek.com/post/150/. This game is a simple game focused on tapping, the challenge is to jump over the square without turning blue (by tapping at the right time). I have commented the code below so you can see what's going on in the game.

// Jasper Camber Holton, Uglek, 2021
function getRandomInt(max) { // Random integer
    return Math.floor(Math.random() * max);
}
function init() {
    var last = 0; // Last time we tapped the screen
    var stage = new createjs.Stage("game"); // New stage
    stage.on("stagemousedown", function(evt) { // On mousedown (click, tap or touch)
        if (createjs.Ticker.getTime() > last + 1000) { // If it has been 1 second since we jumped
            createjs.Tween.get(circle, { // Animate jump
                    loop: false
                })
                .to({
                    y: 50
                }, 350, createjs.Ease.getPowInOut(4))
                .to({
                    y: 160
                }, 300, createjs.Ease.getPowInOut(2));
            last = createjs.Ticker.getTime();
        }
    });
    circle = new createjs.Shape(); // New player (circle)
    circle.graphics.beginFill("red").drawCircle(0, 0, 20);
    // Set position
    circle.x = 50;
    circle.y = 160;
    // Add Shape instance to stage display list.
    stage.addChild(circle);
    // Update stage will render next frame

    var rect = new createjs.Shape(); // Create new enemy rectangle
    rect.graphics.beginFill("red").drawRect(0, 0, 20, 20);
    // Set position 
    rect.x = 300;
    rect.y = 160;
    // Add Shape instance to stage display list.
    stage.addChild(rect);

    var hit; // Whether the player was hit
    var score = 0; // Keep track of a score
    var text = new createjs.Text("Score: 0", "15px Arial", "#000000"); // Score text
    text.x = 10; // Positioning
    text.y = 10;
    stage.addChild(text); // Add to stage

    var high = 0; // High score
    var text2 = new createjs.Text("High: 0", "15px Arial", "#000000"); // Score text
    text2.x = 100; // Positioning
    text2.y = 10;
    stage.addChild(text2); // Add to stage
    function handleTick(event) { // Game loop
        rect.x = rect.x - 2; // Move the enemy
        if (rect.x < -20) { // If it's off screen, move it back
            rect.x = 300 + getRandomInt(200);
            circle.graphics.beginFill("red").drawCircle(0, 0, 20); // Fill circle red
            if (hit) { // If the player is hit
                if (score > high) { // Reset high score
                    high = score;
                    text2.text = "High: " + high;
                }
                score = 0; // Reset score and text
                text.text = "Score: " + score;
            } else { // Increment score
                score = score + 1;
                text.text = "Score: " + score;
            }
            hit = false; // Set hit to false
        }
        if (rect.x > 45 && rect.x < 50 && circle.y > 120) { // Check if player is in bounds to be hit
            circle.graphics.beginFill("blue").drawCircle(0, 0, 20);
            hit = true;
        }
        stage.update(); // Update the stage
    }
    stage.update();
    createjs.Ticker.setFPS(60);
    createjs.Ticker.addEventListener("tick", stage);
    createjs.Ticker.addEventListener("tick", handleTick);
}
init(); // Initialize
The game, as generated, is pictured below.

View the photo from a post by @Jasper_Holton

@Jasper_Holton, likes this,

@Jasper_Holton's profile photo

Asynchronous Database Driven Email Daemon and Client for Django Websites I created a Django site in 2020. One of the things I have needed for this site for a while is a good email editor and scheduler, so I can edit, see, and schedule emails for the site on the go, and they are sent out automatically on time. I wrote some software to do just this. The first part of the code is a database model. This stores the email and passes it to the daemon. It contains a send at date and time, recipients list, subject, HTML content, and a method which calls the send email function.

# app/models.py
class ScheduledEmail(models.Model):
    id = models.AutoField(primary_key=True)
    recipients = models.ManyToManyField(User, related_name='email_recipients')
    send_at = models.DateTimeField(default=timezone.now)
    subject = models.CharField(blank=True, max_length=100, default='')
    html_content = models.TextField()
    def send(self):
        send_users_email(self.recipients.all(),self.subject,self.html_content)
This next part is the daemon. This is included in __init__.py of the new app tasks (python manage.py startapp tasks) and imported in the apps.py
# tasks/__init__.py
import threading
import time
from blog.models import ScheduledEmail
import datetime
from django.template import Template, Context
def elapsed_minute(): # Get time elapsed since last minute
    now = datetime.datetime.now()
    time1 = now
    time2 = now
    time2 = time2.replace(second=0,microsecond=0)
    return (time1-time2).total_seconds()
def send_pending_emails(): # Send emails due for sending
    emails = ScheduledEmail.objects.filter(send_at__lt=datetime.datetime.now())
    for email in emails:
        email.send()
        email.delete()
def async_tasks(): # Run tasks asynchronously
    sleep_time = 60 - elapsed_minute() # Seconds left in minute
    time.sleep(sleep_time) # Sleep until the turn of the minute
    while True:
        send_pending_emails()
        time.sleep(60)
def start_async(): # Start a new thread for our tasks
    async_thread = threading.Thread(target=async_tasks, name="Uglek async tasks", args=())
    async_thread.start()
This is initialized in the AppConfig, by calling the method
# app/apps.py
import sys
...
from tasks import start_async, send_pending_emails, async_tasks
if not ('shell' in sys.argv or 'migrate' in sys.argv or 'makemigrations' in sys.argv):
    start_async()
Putting this together, you can asynchronously send a scheduled email with the following code:
# wherever you send email
email = ScheduledEmail.objects.create(send_at=dateandtime, subject=subject, html_content=html) # Create a new scheduled email
email.recipients.set(users) # Set the recipients to a list of users
email.save() # Save the email to the database
I hope this is useful as a solution for sending emails asynchronously. Below is a photo of a simple HTML email sending client I created using JavaScript. I can use this client to email subscribers on this site.

View the photo from a post by @Jasper_Holton

© Uglek, 2022

Terms of Use and Privacy Policy