How could I override or change the project's settings from an app? For example, I have a context processor function in my app, and I want to include it into a context_processors option of the TEMPLATE setting. How could I do that without changing project's settings.py file directly?
In the documentation i found, that they cannot be changed at runtime:
You shouldn’t alter settings in your applications at runtime. For
example, don’t do this in a view:
from django.conf import settings
settings.DEBUG = True # Don't do this!
The only place you should assign to settings is in a settings file.
So is it possible at all?
Related
Is it possible to dynamically load Django apps at runtime? Usually, apps are loaded at initialization, using the INSTALLED_APPS tuple in settings.py. However, is it possible to load additional apps at runtime? I am encountering this issue in different situations. One situation, for example, arises during testing, when I would like to dynamically load or unload apps.
In order to make the problem more concrete, imagine I have a directory called apps where I put my apps and I would like to automatically install any new app that goes in there without manually editing the settings.py.
This is easy enough. Following the example code in
Django: Dynamically add apps as plugin, building urls and other settings automatically
we put the following code in settings.py to could loop over the names of all sub-directories in the app directory and increment the INSTALLED_APPS tuple in settings.py like this:
APPS_DIR = '/path_to/apps/'
for item in os.listdir(APPS_DIR):
if os.path.isdir(os.path.join(APPS_DIR, item)):
app_name = 'apps.%s' % item
if app_name not in INSTALLED_APPS:
INSTALLED_APPS += (app_name, )
After that, if I was in a Django shell, I could something like
from django.conf import settings
and the apps would be listed in settings.INSTALLED_APPS. And if I did
from django.core import management
management.call_command('syncdb', interactive=False)
that would create the necessary DB tables for the apps.
However, if I were to now add some more apps to the apps/ directory, without re-starting, these would not be listed in settings.INSTALLED_APPS, and so a subsequent call to the syncdb would have no effect.
What I would like to know is if there is something I could do --- without restarting --- to reload the settings and load/install new apps.
I have tried to directly import my settings.py, i.e.
from myproject import settings
and then reload that settings using the python builtin after any app directory changes. Although settings.INSTALLED_APPS is now changed to include the newly added apps, this ultimately makes no difference. For example,
from django.db import models
models.get_apps()
shows only the original apps in apps and not the newly added ones and likewise
management.call_command('syncdb', interactive=False)
will not see the newly added apps.
As I stated above, I am thinking about this situation particularly in the context of testings where I dynamically would add or remove apps.
Ps. I working with Django 1.6, but on the advice of #RickyA, I see that there are some substantial changes to Django's treatment of applications in 1.7
https://docs.djangoproject.com/en/1.7/ref/applications/
I'm still not sure what this might mean for the problem I am facing.
Update for Django 1.8 on how to load an app that is not loaded yet
from collections import OrderedDict
from django.apps import apps
from django.conf import settings
from django.core import management
new_app_name = "my_new_app"
settings.INSTALLED_APPS += (new_app_name, )
# To load the new app let's reset app_configs, the dictionary
# with the configuration of loaded apps
apps.app_configs = OrderedDict()
# set ready to false so that populate will work
apps.ready = False
# re-initialize them all; is there a way to add just one without reloading them all?
apps.populate(settings.INSTALLED_APPS)
# now I can generate the migrations for the new app
management.call_command('makemigrations', new_app_name, interactive=False)
# and migrate it
management.call_command('migrate', new_app_name, interactive=False)
With Django 2.2 this work for me
from collections import OrderedDict
from django.apps import apps
from django.conf import settings
from django.core import management
new_app_name = "my_new_app"
settings.INSTALLED_APPS += (new_app_name, )
apps.app_configs = OrderedDict()
apps.apps_ready = apps.models_ready = apps.loading = apps.ready = False
apps.clear_cache()
apps.populate(settings.INSTALLED_APPS)
management.call_command('makemigrations', new_app_name, interactive=False)
management.call_command('migrate', new_app_name, interactive=False)
To answer my own question...
While I do not have a completely general solution to this problem, I do have one that is sufficient for the purposes of dynamically loading apps during testing.
The basic solution is simple, and I found it at a wee little bixly blog.
Continuing with my example above, if I was in a django shell and wanted to add and load some new apps that were added to my apps directory, I could do
import os
from django.conf import settings
from django.db.models import loading
from django.core import management
APPS_DIR = '/path_to/apps/'
for item in os.listdir(APPS_DIR):
if os.path.isdir(os.path.join(APPS_DIR, item)):
app_name = 'apps.%s' % item
if app_name not in settings.INSTALLED_APPS:
settings.INSTALLED_APPS += (app_name, )
and then
loading.cache.loaded = False
management.call_command('syncdb', interactive=False)
It is possible to dynamically load and unload applications in tests in Django >= 1.7 (and also in the current 4.1) by a override_settings() decorator:
#override_settings(INSTALLED_APPS=[...]) # added or removed some apps
class MyTest(TestCase):
# some tests with these apps
def test_foo(self):
pass
It has been possible since September 2014, not before the question.
Many other topics from the question are solved by django.apps also in Django >= 1.7. Generally: Dynamic configuration at startup is easy in the current Django. Dynamic loading and unloading after startup can't be recommended in production, even that it could work eventually.
Yes! Anything (or almost everything) in Python is possible. You should use os.walk() in order to get all folders, subfolders and files within your apps path in order to get all of your apps including the nested ones.
def get_installed_apps():
from os import walk, chdir, getcwd
previous_path = getcwd()
master = []
APPS_ROOT_PATH = '/my/project/apps/folder'
chdir(APPS_ROOT_PATH)
for root, directories, files in walk(top=getcwd(), topdown=False):
for file in files:
if 'apps.py' in file:
app_path = f"{root.replace(BASE_DIR + '/', '').replace('/', '.')}.apps"
print(app_path)
master.append(app_path)
chdir(previous_path)
return master
print(get_installed_apps())
I'd like to break some specific views from a django app out into a separate NewRelic application. The NewRelic python docs implies that I can set request.environ['newrelic.app_name'] and it will use my new value instead of the default one, but it doesn't appear to work.
eg. in my wsgi file:
from django.core.handlers.wsgi import WSGIHandler
import newrelic.agent
application = WSGIHandler()
newrelic.agent.initialize('/path/to/newrelic.ini') # defines app_name='1st_app'
application = newrelic.agent.wsgi_application()(application)
in my Django view:
def some_view(request, *args, **kwargs):
request.environ['newrelic.app_name'] = '2nd_app'
...
return HttpResponse(...)
You can split these views out in mod_wsgi, but it must be done at the server level and not with Django. Use the SetEnv directive in your Apache config instead. Since you want to change the app_name based on request, you will need to use the Location directive to identify which URL should be re-named.
For example:
<Location /some/url>
SetEnv newrelic.app_name 2nd_app
</Location>
I am writing a simple Django app - say, called base - which has a few models. I am using Django's built-in admin site to manipulate the app's data. The admin is accessible via the default ^admin/ URL pattern.
Aside from Django's default INSTALLED_APPS (minus django.contrib.sites), base is the only app installed in my project. Is there any way to remove base/ from the URL, such that I can access base's models by simply using a path such as /admin/model/ instead of /admin/base/model/?
I would ideally like django.contrib.auth's models to still be accessible via /admin/auth/.
If you don't want base in the URL, don't put it there. It's not there unless you have specifically asked it to be. Just create your URLs without that prefix.
Edit Apologies, I misread your question: I thought you were asking about your app's own views, not the sub-sections with admin.
This is tricky. One way of doing it would be to use the hooks for adding URLs to the base AdminSite as described in the docs. You will probably need to copy the code from the ModelAdmin.get_urls method and hard-code the model name, since there won't be a way of doing that automatically.
Customizing or overriding your default Django admin site is quite easy. Here's the Django documentation on this. The following is an example of overriding the default admin site.
Create an admin.py in your Django project directory (if it's not there yet). Subclass the AdminSite.
To remove the 'appname' from the admin URLs override the get_urls() function:
# myproject/admin.py
from django.contrib import admin
class MyAdminSite(admin.AdminSite):
def get_urls(self):
urlpatterns = super().get_urls()
for model, model_admin in self._registry.items():
urlpatterns += [
path('%s/' % (model._meta.model_name), include(model_admin.urls)),
]
return urlpatterns
Creae an apps.py in your project directory (if it's not there yet):
# myproject/admin.py
from django.contrib.admin.apps import AdminConfig
class MyAdminConfig(AdminConfig):
default_site = 'myproject.admin.MyAdminSite'
Register this in your settings.py:
INSTALLED_APPS = [
...
'myproject.apps.MyAdminConfig', # replaces 'django.contrib.admin'
...
]
Just for some context, I am testing running a Django (1.4) app behind nginx+gunicorn on webfaction in order to run the admin parts securely. This is working fine. The nginx proxy is redirecting requests from http://domain.com/admin to https://domain.com/admin and Django is serving everything up without issue. The greater issue is the static files. I have these being served up from a separate static directory that is accessed both via http and https. My problem is how to render the static urls in my app's templates as well as the admin interface templates in such a way that the appropriate prefix is applied (http or https) depending on whether the request is secured or not.
For my app's templates, I've used the {{ STATIC_URL }}path/to/resource convention, which allows me to easily adjust the STATIC_URL context variable in a template_context_processor function. But this doesn't have any effect on the administrative templates which use the {% static path/to/resource %} templatetag to render the url.
The best I have come up with is to make two url options in settings.py:
STATIC_URL = 'https://mydomain.com/static/'
NON_SECURED_STATIC_URL = 'http://mydomain.com/static/'
Then added the following function to TEMPLATE_CONTEXT_PROCESSORS:
def set_static_url(request):
if not request.is_secure():
return { 'STATIC_URL': settings.NON_SECURED_STATIC_URL }
This is actually working, but it seems rather hackish. I'm also quite new to Django and I feel there must be a better or more appropriate way of doing this.
When I was using the built-in simple server, everything is OK, the admin interface is beautiful:
python manage.py runserver
However, when I try to serve my application using a wsgi server with django.core.handlers.wsgi.WSGIHandler, Django seems to forget where the admin media files is, and the admin page is not styled at all:
gunicorn_django
How did this happen?
When I look into the source code of Django, I find out the reason.
Somewhere in the django.core.management.commands.runserver module, a WSGIHandler object is
wrapped inside an AdminMediaHandler.
According to the document, AdminMediaHandler is a
WSGI middleware that intercepts calls
to the admin media directory, as
defined by the ADMIN_MEDIA_PREFIX setting, and serves those images.
Use this ONLY LOCALLY, for development! This hasn't been tested
for
security and is not super efficient.
And that's why the admin media files can only be found automatically when I was using the test server.
Now I just go ahead and set up the admin media url mapping manually :)
Django by default doesn't serve the media files since it usually is better to serve these static files on another server (for performance etc.). So, when deploying your application you have to make sure you setup another server (or virtual server) which serves the media (including the admin media). You can find the admin media in django/contrib/admin/media. You should setup your MEDIA_URL and ADMIN_MEDIA_URL so that they point to the media files. See also http://docs.djangoproject.com/en/dev/howto/static-files/#howto-static-files.
I've run into this problem too (because I do some development against gunicorn), and here's how to remove the admin-media magic and serve admin media like any other media through urls.py:
import os
import django
...
admin_media_url = settings.ADMIN_MEDIA_PREFIX.lstrip('/') + '(?P<path>.*)$'
admin_media_path = os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
url(r'^' + admin_media_url , 'django.views.static.serve', {
'document_root': admin_media_path,
}, name='admin-media'),
...
)
Also: http://djangosnippets.org/snippets/2547/
And, of course, #include <production_disclaimer.h>.