Jarno Jarno - 2 years ago 292
Python Question

Setting variables with Django signals without using globals

In Django 1.9 I construct a list of forms based on a database table corresponding to

in my
. When the database table changes I want the forms to change as well. Therefore I set up a Django signal to listen to the change and update the forms. My problem is now that nothing can be really returned from the signal handler so I need to access the list of forms as a
variable. Since the use of
is generally considered bad practice: Is there a better solution for this or is the use of
in this case considered acceptable?

This is my code in

from django.dispatch import receiver
from django.db.models.signals import post_save, post_delete
from .forms import construct_forms

# Init container for forms.
forms = construct_forms()
# Be aware that this signal is firiing twice for some reason.
@receiver((post_save, post_delete), sender=TheModel, dispatch_uid='change')
def reconstruct_forms(sender, **kwargs):
Reconstruct forms if model was changed.
# Use global variable so `forms` will be available in
# updated version later on.
global forms
forms = construct_forms()
print('Model was changed, forms are reconstructed.')

def some_view(request):
# Do something with the forms

Important edit:

At the time I asked this question I was completely unaware that signals only work in one thread of the running production server. Therefore an approach like this, using signals to update in memory variables will lead eventually lead to one thread on the server displaying the updated forms and the rest, where the signal did no reach, will show an outdated version. This is, of course, unacceptable in production. If caching is really required you should have a look at the Django docs on caching. For my little form construction it is actually overkill. I'll just leave this question to point in the right direction. Please, do not try to implement this my way!

Answer Source

As @Jerzyk pointed out, caching the forms will only work in a single-threaded environment and will most likely fail in production. The signal is only sent and handled in the thread where the save/delete happened. Other processes will be unaware of the change.

Instead, store the query used to build the form in a shared cache, e.g. memcached or redis, using Django's cache framework.

from django.core.cache import cache

CHOICES_CACHE_KEY = 'choice_cache_key'

def get_cached_choices():
    choices = cache.get(CHOICES_CACHE_KEY)
    if choices is None:
        choices = ...  # Query the DB here
        cache.set(CHOICES_CACHE_KEY, choices, None)  # None caches forever
    return choices

def construct_forms(choices):
    forms = ...  # build forms with choices
    return forms

@receiver((post_save, post_delete), sender=TheModel, dispatch_uid='change')
def clear_choices_cache(sender, **kwargs):

def some_view(request):
    # Do something with the forms
    forms = construct_forms(get_cached_choices())

However, I would only consider this solution if the query is expensive enough to justify the added complexity.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download