In Django 1.9 I construct a list of forms based on a database table corresponding to TheModel
in my forms.py
. 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 global
variable. Since the use of global
is generally considered bad practice: Is there a better solution for this or is the use of global
in this case considered acceptable?
This is my code in views.py
:
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
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!
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):
cache.delete(CHOICES_CACHE_KEY)
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.