Search code examples
djangodjango-1.9

Perform model operations (only once) at server init


I have a rather peculiar requirement: The app should be able to display its own uptime as total hours. This means I need to step away from the request-response cycle and update the current timestamp in the relevant model.

With this in mind, I followed the instructions given here, putting the code in my app's ready() method in apps.py. The problem, of course, is that I ran into the Apps aren't loaded yet error. How do I work around this?

Another approach that comes to mind is do away with models and write the timestamp to a file, but this is a brittle method that will not scale. What if I want to store extensive relational information at boot time?

Can someone suggest something, please?

======= UPDATE =========

The code I'm using is as follows (my project is called jremind and my app is called remind).

Here's my model instance:

class Monitor(models.Model):
    # save automatically when object is saved()
    app_init_timestamp = models.DateTimeField(null=False, auto_now=True)

The app's __init__ file:

default_app_config = 'remind.apps.RemindConfig'

The app's apps.py file:

from django.apps import AppConfig
from remind.models import Monitor

class RemindConfig(AppConfig):
    name = 'remind'

    def ready(self):
        # There's only one instance
        monitor = Monitor.objects.get()[0]
        #Auto-update timestamp
        monitor.save()

And here's the full stack trace when I run ./manage.py runserver:

(env) jremind$ ./manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
July 13, 2016 - 15:12:08
Django version 1.9, using settings 'jremind.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
^C(env) jremind$ ./manage.py runserver
Traceback (most recent call last):
  File "./manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/media/common/code/python/projects/jremind/env/lib/python3.4/site-packages/django/core/management/__init__.py", line 350, in execute_from_command_line
    utility.execute()
  File "/media/common/code/python/projects/jremind/env/lib/python3.4/site-packages/django/core/management/__init__.py", line 342, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/media/common/code/python/projects/jremind/env/lib/python3.4/site-packages/django/core/management/__init__.py", line 176, in fetch_command
    commands = get_commands()
  File "/media/common/code/python/projects/jremind/env/lib/python3.4/functools.py", line 448, in wrapper
    result = user_function(*args, **kwds)
  File "/media/common/code/python/projects/jremind/env/lib/python3.4/site-packages/django/core/management/__init__.py", line 71, in get_commands
    for app_config in reversed(list(apps.get_app_configs())):
  File "/media/common/code/python/projects/jremind/env/lib/python3.4/site-packages/django/apps/registry.py", line 137, in get_app_configs
    self.check_apps_ready()
  File "/media/common/code/python/projects/jremind/env/lib/python3.4/site-packages/django/apps/registry.py", line 124, in check_apps_ready
    raise AppRegistryNotReady("Apps aren't loaded yet.")
django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.

Solution

  • You need to import the models from inside the method:

    def ready(self):
        from remind.models import Monitor
    

    However, you should also note the warning in the documentation:

    Although you can access model classes as described above, avoid interacting with the database in your ready() implementation. This includes model methods that execute queries (save(), delete(), manager methods etc.)... Your ready() method will run during startup of every management command. For example, even though the test database configuration is separate from the production settings, manage.py test would still execute some queries against your production database!

    Also:

    In the usual initialization process, the ready method is only called once by Django. But in some corner cases, particularly in tests which are fiddling with installed applications, ready might be called more than once. In that case, either write idempotent methods, or put a flag on your AppConfig classes to prevent re-running code which should be executed exactly one time.

    Putting a flag would be done like so:

    class RemindConfig(AppConfig):
        name = 'remind'
        ready_has_run = False
    
        def ready(self):
            if self.ready_has_run:
                return
            
            # Do your stuff here, and then set the flag
            self.ready_has_run = True