Where to put Django startup code? - python

I'd like to have these lines of code executed on server startup (both development and production):
from django.core import management
management.call_command('syncdb', interactive=False)
Putting it in settings.py doesn't work, as it requires the settings to be loaded already.
Putting them in a view and accessing that view externally doesn't work either, as there are some middlewares that use the database and those will fail and not let me access the view.
Putting them in a middleware would work, but that would get called each time my app is accessed. An possible solution might be to create a middleware that does all the job and then removes itself from MIDDLEWARE_CLASSES so it's not called anymore. Can I do that without too much monkey-patching?

Write middleware that does this in __init__ and afterwards raise django.core.exceptions.MiddlewareNotUsed from the __init__, django will remove it for all requests :). __init__ is called at startup by the way, not at the first request, so it won't block your first user.
There is talk about adding a startup signal, but that won't be available soon (a major problem for example is when this signal should be sent)
Related Ticket: https://code.djangoproject.com/ticket/13024
Update: Django 1.7 includes support for this. (Documentation, as linked by the ticket)

In Django 1.7+ if you want to run a startup code and,
1. Avoid running it in migrate, makemigrations, shell sessions, ...
2. Avoid running it twice or more
A solution would be:
file: myapp/apps.py
from django.apps import AppConfig
def startup():
# startup code goes here
class MyAppConfig(AppConfig):
name = 'myapp'
verbose_name = "My Application"
def ready(self):
import os
if os.environ.get('RUN_MAIN'):
startup()
file: myapp/__init__.py
default_app_config = 'myapp.apps.MyAppConfig'
This post is using suggestions from #Pykler and #bdoering

If you were using Apache/mod_wsgi for both, use the WSGI script file described in:
http://blog.dscpl.com.au/2010/03/improved-wsgi-script-for-use-with.html
Add what you need after language translations are activated.
Thus:
import sys
sys.path.insert(0, '/usr/local/django/mysite')
import settings
import django.core.management
django.core.management.setup_environ(settings)
utility = django.core.management.ManagementUtility()
command = utility.fetch_command('runserver')
command.validate()
import django.conf
import django.utils
django.utils.translation.activate(django.conf.settings.LANGUAGE_CODE)
# Your line here.
django.core.management.call_command('syncdb', interactive=False)
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

You can create a custom command and write your code in the handle function. details here https://docs.djangoproject.com/en/dev/howto/custom-management-commands/
Then you can create a startup script that runs the django server then executes your new custom command.

If you are using mod_wsgi you can put it in the wsgi start app

Here is how I work around the missing startup signal for Django:
https://github.com/lsaffre/djangosite/blob/master/djangosite/models.py
The code that is being called there is specific to my djangosite project, but the trick to get it called by writing a special app (based on an idea by Ross McFarland) should work for other environments.
Luc

Related

Is it possible to process a task at django-background-tasks without entering the command 'python manage.py process_tasks'?

Is it possible to process a task at django-background-tasks without the 'command python manage.py process_tasks'? my task needs start when the application starts but I don't want to enter a command to do it. Thanks in advance.
Yes, since Django in version 1.7 you can add ready function to your app which will execute only once at startup.
Let's say you have application named StackApp
from django.apps import AppConfig
class StackAppConfig(AppConfig):
name = 'stackapp'
verbose_name = "StackApp"
def ready(self):
process_tasks.delay()
In your file app/__init__.py you have to include:
default_app_config = 'stackapp.apps.StackAppConfig'

Accessing django database from python script

I'm trying to access my Django database from within a regular Python script. So far what I did is:
import os
import django
from django.db import models
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "m_site.settings")
django.setup()
from django.apps import apps
ap=apps.get_model('py','Post')
q=ap.objects.all()
for a in q:
print(a.title)
There is an application in Django called py where I have many posts (models.py, which contains class Post(models.Model)).
I would like to have the possibility to access and update this database from a regular Python script. So far script above works fine, I can insert, update or query but it looks like it is a separate database and not the database from Django's py application. Am I doing something wrong or missing something? Any help would be very appreciated.
Consider writing your script as a custom management command. This will let you run it via manage.py, with Django all properly wired up for you, e.g.
python manage.py print_post_titles
Something like this should be a good start:
from django.core.management.base import BaseCommand
from py.models import Post
class Command(BaseCommand):
help = 'Prints the titles of all Posts'
def handle(self, *args, **options):
for post in Post.objects.all():
print(a.title)

Unit test to check for ungenerated migrations

Has anyone written a unit test to verify if there are ungenerated migrations in their Django app? I think it should probably look something like:
Call python manage.py makemigrations
scrape results into a parsable object
verify "no migrations were found"
if migrations are found list them, fail the test, and delete the generated files
If not, I am going to write one so that we fail our build.
Since Django 1.10 the makemigrations management command has included a --check option. The command will exit with a non-zero status if migrations are missing.
Usage example:
./manage.py makemigrations --check --dry-run
Documentation:
https://docs.djangoproject.com/en/2.0/ref/django-admin/#cmdoption-makemigrations-check
This should do the trick — no scraping.
It will show names of migrations, but when in doubt you still need to view changes in debugger.
class MigrationsCheck(TestCase):
def setUp(self):
from django.utils import translation
self.saved_locale = translation.get_language()
translation.deactivate_all()
def tearDown(self):
if self.saved_locale is not None:
from django.utils import translation
translation.activate(self.saved_locale)
def test_missing_migrations(self):
from django.db import connection
from django.apps.registry import apps
from django.db.migrations.executor import MigrationExecutor
executor = MigrationExecutor(connection)
from django.db.migrations.autodetector import MigrationAutodetector
from django.db.migrations.state import ProjectState
autodetector = MigrationAutodetector(
executor.loader.project_state(),
ProjectState.from_apps(apps),
)
changes = autodetector.changes(graph=executor.loader.graph)
self.assertEqual({}, changes)
I would instead use the --dry-run flag and test that it is empty.
You can steal some code from https://github.com/django/django/blob/master/django/core/management/commands/makemigrations.py#L68. Migrating to newer django versions might require some extra 5 min after that

Django South - Migrate From Python Code

Is it possible to run a migration from Python code? I don't want to (actually I cannot) use the terminal command:
venv/bin/python src/manage.py migrate myapp
I need to run it from Python code as part of my business logic on a dynamically created database.
This is what I have so far:
db_name = uuid.uuid4()
from settings.local import DATABASES
new_database = {}
new_database['ENGINE'] = 'django.db.backends.mysql'
new_database['NAME'] = db_name
new_database['USER'] = DATABASES["default"]["USER"]
new_database['PASSWORD'] = DATABASES["default"]["PASSWORD"]
new_database['HOST'] = DATABASES["default"]["HOST"]
new_database['PORT'] = DATABASES["default"]["PORT"]
import settings
database_id = str(uuid.uuid4())
settings.DATABASES[database_id] = new_database
from django.core.management import call_command
call_command('migrate', 'catalogue', database=database_id)
But I get:
KeyError: '28a4eb10-91e4-4de8-8a74-15d72f8245ef'
Yes, you can. Assuming it is called from within Django app:
from django.core.management import call_command
call_command('migrate', 'myapp')
I believe the issue you are having is that you are configuring the new database in runtime. So, the configuration you change in runtime is over the process/thread you are hitting with that code. The settings dict is immutable and you can not change it in runtime (see django docs).
I am not sure, but very probably the setting dict used by call_command is a copy from the original dict loaded from the settings configuration and that is why you get the key error (there is no new config in there).
By the way, is published that doing changes over settings configuration in runtime is not recommended by the django community.

Django runserver in command with custom settings

I try to execute the runserver inside a command with custom settings. But the settings are not loaded:
from django.core.management.base import BaseCommand
from django.core.management import call_command
from django.core.management import setup_environ, ManagementUtility
import settings_api
setup_environ(settings_api)
class Command(BaseCommand):
help = "Local API to retrieve realtime quotes"
def handle(self, *args, **options):
if not settings_api.DEBUG:
call_command('runserver', '127.0.0.1:8002')
else:
call_command('runserver', '0.0.0.0:8002')
Output is:
Used API settings
Used API settings
Validating models...
0 errors found
Django version 1.4b1, using settings 'site.settings'
Development server is running at http://0.0.0.0:8002/
Quit the server with CONTROL-C.
runserver forces default settings unless passed --settings=. Use a separate WSGI container (or even the built-in one, brought up yourself) if you want custom settings.
Ignacios answer pointed me the right direction: executing the command with custom settings param solved it:
./manage.py command --settings=settings_api
More details here

Categories

Resources