django settings per application - best practice? - python

this is somewhat related to this question
Why is django's settings object a LazyObject?
In my django project i have several applications. Each application can have its own non-trivial settings file.
proj/
proj/
settings.py
app/
settings.py
views.py
What is the general best practice here?
should app/settings.py do
from django.conf import settings
APP_SETTING= lambda: settings.getattr('APP_SETTING', 'custom_value')
PROJ_SETTING= lambda: settings.PROJ_SETTING
and then in app/views.py do
import .settings
X = settings.APP_SETTING
Y = settings.PROJ_SETTING
or should I be modifying the django lazy settings object in app/settings.py as per the django coding style?
from django.conf import settings
# not even sure how I would check for a default value that was specified in proj/settings.py
settings.configure(APP_SETTING='custom_value')
and then each app/views.py just consumes proj/settings.py via django.conf settings?
from django.conf import settings
X = settings.APP_SETTING
Y = settings.PROJ_SETTING
There are obviously quite a few other permutations but I think my intent is clear.
Thanks in advance.

The simplest solution is to use the getattr(settings, 'MY_SETTING', 'my_default') trick that you mention youself. It can become a bit tedious to have to do this in multiple places, though.
Extra recommendation: use a per-app prefix like MYAPP_MY_SETTING.
There is a django app, however, that gets rid of the getattr and that handles the prefix for you. See http://django-appconf.readthedocs.org/en/latest/
Normally you create a conf.py per app with contents like this:
from django.conf import settings
from appconf import AppConf
class MyAppConf(AppConf):
SETTING_1 = "one"
SETTING_2 = (
"two",
)
And in your code:
from myapp.conf import settings
def my_view(request):
return settings.MYAPP_SETTINGS_1 # Note the handy prefix
Should you need to customize the setting in your site, a regular entry in your site's settings.py is all you need to do:
MYAPP_SETTINGS_1 = "four, four I say"

Since Django 1.7 there is a django-based structure for app-oriented configurations!
You could find descriptive solution here
In this new structure, conventionally you could have an apps.py file in your applications' folder which are embeded in project, something like this:
proj/
proj/
settings.py
app1/
apps.py
views.py
app2/
apps.py
views.py
app1/apps.py file could include something like this:
from django.apps import AppConfig
class App1Config(AppConfig):
# typical systemic configurations
name = 'app1'
verbose_name = 'First App'
# your desired configurations
OPTION_A = 'default_value'
APP_NAMESPACE = 'APP'
APP_OPTION_B = 4
you could have app2/apps.py something different like this:
from django.apps import AppConfig
class App2Config(AppConfig):
# typical systemic configurations
name = 'app2'
verbose_name = 'Second App'
# your desired configurations
OTHER_CONFIGURATION = 'default_value'
OPTION_C = 5
and so etc for other apps.pys in you Django Application folder.
It's important that you should import applications you included apps.py in, as follows:
# proj/settings.py
INSTALLED_APPS = [
'app1.apps.App1Config',
'app2.apps.App2Config',
# ...
]
‌Now, You could access desired app-based configuration someway like this:
from django.apps import apps
apps.get_app_config('app1').OPTION_A

Not sure about best practices but I don't have any problems with following style:
proj/settings.py
OPTION_A = 'value'
# or with namespace
APP_NAMESPACE = 'APP'
APP_OPTION_B = 4
app/settings.py
from django.conf import settings
from django.utils.functional import SimpleLazyObject
OPTION_A = getattr(settings, 'OPTION_A', 'default_value')
# or with namespace
NAMESPACE = getattr(settings, APP_NAMESPACE, 'APP')
OPTION_B = getattr(settings, '_'.join([NAMESPACE, 'OPTION_B']), 'default_value')
OPTION_C = getattr(settings, '_'.join([NAMESPACE, 'OPTION_C']), None)
if OPTION_C is None:
raise ImproperlyConfigured('...')
# lazy option with long initialization
OPTION_D = SimpleLazyObject(lambda: open('file.txt').read())
app/views.py
from .settings import OPTION_A, OPTION_B
# or
from . import settings as app_settings
app_settings.OPTION_C
app_settings.OPTION_D # initialized on access

My site is just starting and I want a the simplest but flexible configuration solution. That's what I came across with.
# in the end of the site's settings.py
. . .
# Custom settings
MYAPP_CONFIG_FILE = "/home/user/config/myapp_config.ini"
In my application's models.py:
from django.conf import settings
import configparser
config = configparser.ConfigParser()
config.read(settings.MYAPP_CONFIG_FILE, encoding='utf_8')
This config parser is described here. It's convenient enough but definitely not the only option.

you can use django-zero-settings, it lets you define your defaults and a key for your user settings, then it will handle user overrides too.
it also auto imports your settings, has the ability to handle removed settings, and can pre-check settings too.
as an example, define your settings:
from zero_settings import ZeroSettings
app_settings = ZeroSettings(
key="APP",
defaults={
"TOKEN": "token"
},
)
then you can use it like:
from app.settings import app_settings
print(app_settings.TOKEN)

Related

Signals.py file not being recognized in django

I am working on follow/unfollow system and I need to add a signals.py file to make the follower count workl, but after I started investigating, I realized that the signals.py file was not being called because it is not on the pycache folder of the app. What can I do to make this file be recognized by django??
apps.py (this is what I tried)
from django.apps import AppConfig
class AccountsConfig(AppConfig):
name = 'accounts'
def ready(self):
import accounts.signals
If you need to see more code or have any questions please let me know in the comments;)
You need to load the module. For example in the AppConfig. In the apps.py you can specify an AppConfig [Django-doc], and load the signals with:
# accounts/apps.py
from django.apps import AppConfig
class AccountsConfig(AppConfig):
# …
def ready(self):
from accounts import signals # noqa
In the __init__.py you also should set this as the default AppConfig:
# accounts/__init__.py
default_app_config = 'accounts.apps.AccountsConfig'

Django settings

In my project (based on Django) I need a custom option in order to switch on/off some functionality in my view.
if (FLAG):
.....
else:
.....
As I know, if I set this variable in settings.py I wouldn't have imported it from settings.py, because settings.py is not a module and I need import settings.py as a whole object. What else can I use as a setting variable in Django?
You can import as you wish like
from django.conf import settings
settings.py
FUNCTIONALITY_A = True
Then in views.py
If settings.FUNCTIONALITY_A == True
#do your stuff
Otherwise
from yourproject import settings
You can use request.session. It stores the values as dictionary
request.session['flag'] = True #or False as per your requirement
...
if (request.session['flag']):
...
else:
...
When you are done using this variable, just delete it using
del request.session['flag']
Create custom setting YOUR_SETTING, place in settings.py. In your views you can use settings like this
from django.conf import settings
...
# in view
if settings.YOUR_SETTING:
# do something
More info.

Testing Django allauth

I am trying to test my app but not sure how to configure the django-allauth in the test environment. I am getting:
ImproperlyConfigured: No Facebook app configured: please add a SocialApp using the Django admin
My approach so far is to instantiate app objects inside tests.py with actual Facebook app parameters, an app which functions correctly locally in the browser:
from allauth.socialaccount.models import SocialApp
apper = SocialApp.objects.create(provider=u'facebook',
name=u'fb1', client_id=u'7874132722290502',
secret=u'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX')
apper.sites.create(domain='localhost:8000', name='creyu.org')
How can I get these tests running? Thanks
Where inside tests.py do you instantiate this app object? If it's inside the setUpModule() method, there shouldn't be a problem.
Personally, I would create a fixture init_facebook_app.json with the relevant information and then inside tests.py (before the test cases) define:
from django.core.management import call_command
def setUpModule():
call_command('loaddata', 'init_facebook_app.json', verbosity=0)
This ensures that the data in the fixture are loaded before the tests are run, and that they are loaded only once, i.e. not before each test. See this for reference regarding call_command.
Lastly, posting your Facebook app secret key anywhere on the internet is not a good idea - I would reset it if I were you.
I would create a migration so all your environments have the data
eg
import os
from django.db import models, migrations
from django.core.management import call_command
from django.conf import settings
class Migration(migrations.Migration):
def add_initial_providers(apps, schema_editor):
import pdb;pdb.set_trace()
call_command(
'loaddata',
os.path.join(settings.BASE_DIR, 'fixtures/social_auth.json'),
verbosity=0)
dependencies = [
('my_app', '001_auto_20160128_1846'),
]
operations = [
migrations.RunPython(add_initial_providers),
]

admin.py for project, not app

How can I specify a project level admin.py?
I asked this question some time ago and was just awarded the Tumbleweed award because of the lack of activity on the question! >_<
Project:
settings.py
admin.py (This is what I am trying to get to work)
...
App
admin.py (I know how to do this)
For example, admin.autodiscover() is typically put in the project level urls.py (yes, it will be automatically included in 1.7)
I would like to move this, and the following, into their own admin.py file:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
UserAdmin.list_display = ('email', 'first_name', 'last_name', 'is_active')
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
admin.autodiscover()
I tried doing just that, making an admin.py file and adding the code into it. Didn't work.
I tried adding a project level folder called admin, added an __init__.py and threw admin.py into the admin folder. Didn't work
Further I tried adding this admin folder to INSTALLED_APPS in settings.py. No luck.
From the admin.autodiscover doc string:
Auto-discover INSTALLED_APPS admin.py modules and fail silently when
not present. This forces an import on them to register any admin bits they
may want.
And here is the complete function very well documented:
def autodiscover():
"""
Auto-discover INSTALLED_APPS admin.py modules and fail silently when
not present. This forces an import on them to register any admin bits they
may want.
"""
import copy
from django.conf import settings
from django.utils.importlib import import_module
from django.utils.module_loading import module_has_submodule
for app in settings.INSTALLED_APPS:
mod = import_module(app)
# Attempt to import the app's admin module.
try:
before_import_registry = copy.copy(site._registry)
import_module('%s.admin' % app)
except:
# Reset the model registry to the state before the last import as
# this import will have to reoccur on the next request and this
# could raise NotRegistered and AlreadyRegistered exceptions
# (see #8245).
site._registry = before_import_registry
# Decide whether to bubble up this error. If the app just
# doesn't have an admin module, we can ignore the error
# attempting to import it, otherwise we want it to bubble up.
if module_has_submodule(mod, 'admin'):
raise
So the only thing autodiscover does for you is look for some module called admin.py inside installed app directories, hence you can put your admin.py where you want just make sure you import it, this make the code in it (registration of models ect..) get executed.
IMPORTANT: I'm not sure the correct moment for importing your custom-path admin.py. But it's sure you have to import it after load all the related apps.
All admin.autodiscover() is import all the admin files it finds. There is no point putting the line itself in an admin files.
To understand why it's not working, you need to realise how it works. What it does is import the admin.py files inside registered apps. A registered app is a package included in INSTALLED_APPS that has a models.py. That's worth repeating: an app isn't considered if it doesn't have a models.py - even a blank one will work, but without it the package isn't an app.
So if you create a blank models.py in your directory, ensure that that directory is in INSTALLED_APPS, and move admin.autodisover back to urls.py, everything will work.
Edit
I'm not exactly sure what you do want, and why. As I mentioned, autodiscover depends on apps; but as I also mentioned, all autodiscover does is import all the admin files. If you really don't want to follow best practices, all you need to do is import your admin file manually: you can do that in the same place that you normally call autodiscover from, ie in the main urls.py.
Sooner or later, a heavily customized admin class will most likely need a project wide admin.py, as it is the place to register all per-app admins that are not being auto-discovered anymore. The documentation mentions this, but there is no extensive example to illustrate it:
https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#customizing-the-adminsite-class
Note that you may not want autodiscovery of admin modules when using your own AdminSite instance since you will likely be importing all the per-app admin modules in your myproject.admin module. This means you need to put 'django.contrib.admin.apps.SimpleAdminConfig' instead of 'django.contrib.admin' in your INSTALLED_APPS setting.
There is a bit outdated but more or less valid example in the wiki as well for multiple admin sites:
https://code.djangoproject.com/wiki/NewformsAdminBranch
# project-level admin.py
from django.contrib import admin
from myproject.myapp.models import Book, Author
from myproject.anotherapp.models import Musician, Instrument
site1 = admin.AdminSite()
site1.register(Book)
site1.register(Author)
site2 = admin.AdminSite()
site2.register(Musician)
site2.register(Instrument)
I'm curious to see if this gets downvoted. It seems to work, but it feels wrong. ;-)
The question is how to override the UserAdmin settings.
Put an admin.py in your project folder (alongside settings.py)
from django.contrib.auth import admin
from django.contrib.auth.admin import UserAdmin
UserAdmin.list_display = ('email', 'first_name', 'last_name',
'is_active', 'date_joined', 'last_login',
'is_staff')
UserAdmin.ordering = ['-date_joined']
add your project to installed_apps (this seems wrong, but I don't know why)
And now you should see the new columns and sort order on /admin/auth/user/
This is working for me with Django 1.11. So far no issues that I can find. What say you StackOverflow?

dynamically loading django apps at runtime

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())

Categories