App specific default settings in Django? - python

I'm dividing my site into many small (hopefully reusable) apps, and I'm wondering how best to provide default settings that are overridable by the end-user?
I could do getattr(settings, 'MYVAL', <default-value>) everywhere but that's not DRY (nor pretty).
The best I've come up with is
myapp/appsettings.py
INSTALLED_APPS = ('my_dependency', 'myapp')
MYVAL = 42
then in settings.py:
from myapp import appsettings as MYAPP # MYAPP must be UPPER CASE
INSTALLED_APPS = (.....)
INSTALLED_APPS += MYAPP.INSTALLED_APPS # hide/encapsulate sub-dependencies
MYAPP.MYVAL = 5000 # override
and then in view code:
from django.conf import settings
...
print settings.MYAPP.MYVAL
is this a reasonable way to do this, or am I missing something obvious?

Use django-appconf.
It solves this problem in a nice way, I could quote its README here but that is a bit pointless.

I wouldn't mess with INSTALLED_APPS this way as far as I'm concerned. Wrt/ other settings, the canonical solution is to
import global settings from you appsettings.py file
set values here depending on what's defined in gobal settings
only use appsettings from within your application.
myapp/appsettings.py
from django.conf import settings
ANSWER = getattr(settings, "MYAPP_ANSWER", 42)
SOMETHING_ELSE = getattr(settings, "MYAPP_SOMETHING_ELSE", None)
myapp/models.py
from myapp import appsettings
class Question(object):
answer = appsettings.ANSWER

My approach is to have a local_settings.py file which supplements the project's setting.py.
local_settings.py:
XINSTALLED_APPS = [
'myproject.app',
]
settings.py:
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
...
]
try:
from local_settings import *
except ImportError, exp:
pass
try:
INSTALLED_APPS += XINSTALLED_APPS # defined in local_settings
except NameError:
pass

I have done In my last project Like this :
from django.conf import settings
My_APP_ID = getattr(settings, 'My_APP_ID', None)
USER_EMIL_Is = getattr(settings, 'USER_EMIL_Is', Flase)

you can use django-zero-settings which lets you define your defaults and a setting key for user settings, has auto-import strings, removed settings management, cache, pre-checks, etc.
as an example:
from zero_settings import ZeroSettings
app_settings = ZeroSettings(
key="APP",
defaults={
"INSTALLED_APPS": ["some_app"]
},
)
and then use it like:
from app.settings import app_settings
print(app_settings.INSTALLED_APPS)
or in your case you can also import it to your settings file and do something like:
from app_settings import app_settings
SECRET_KEY = "secret_key"
# other settings ....
INSTALLED_APPS = [
*app_settings.INSTALLED_APPS
]

Related

Django app dissapears from Django Admin site when I add apps.py file

I'm working with Django 3.2 and trying to configure correctly the AppsConfig subclass of apps.py in order to avoid duplicate apps names when initzialing the project.
The context is the following. I have in my INSTALLED_APPS two apps whose names are equal although their paths not:
INSTALLED_APPS = [
...
'first_app.myapp',
'second_app.myapp'
]
To avoid the error shown below (and according to the documentation), I need to create an apps.py file subclassing AppConfig in at least one of the apps called myapp. I've decided to create that file in the second one, second_app.myapp.
django.core.exceptions.ImproperlyConfigured: Application labels aren't unique, duplicates: myapp
The app.py in second_app.myapp module looks like as follows:
from django.apps import AppConfig
class MySecondAppConfig(AppConfig):
name = "second_app.myapp"
label = "my_second_app"
And in the __init__.py I've added:
default_app_config = 'second_app.myapp.apps.MySecondAppConfig'
My admin.py looks like:
from django.contrib import admin
from .models import MyModel
class MySecondAppAdminModel(admin.ModelAdmin):
list_display = ('attr_1', 'attr_2')
admin.site.register(MyModel, MySecondAppAdminModel)
When I start the project all works OK and I can use that model information, views called from second_app.myapp also work OK.
The problem comes when I access to the admin site (http://localhost:8000/admin), where only appears the first_app.myapp admin form instead of both.
Can you help me? Thanks in advance.
instead of this:
INSTALLED_APPS = [
...
'first_app.myapp',
'second_app.myapp'
]
Just try this way only:
INSTALLED_APPS = [
...
'first_app',
'second_app'
]
You got that error because here first_app.myapp and second_app.myapp myapp is duplicate. you should not use like this in both the apps.
OR
INSTALLED_APPS = [
...
'first_app.apps.MyFirstAppConfig',
'second_app.apps.MySecondAppConfig'
]

How to reorganize django settings file?

I have this standard django generated settings:
project/project
| __init__.py
| settings.py
| urls.py
| wsgi.py
And it's working. However, I want to reorganize this layout to this:
project/project/settings
| __init__.py
| base.py
| __init__.py
| urls.py
| wsgi.py
When I do it of course it's not working:
django.core.exceptions.ImproperlyConfigured: The SECRET_KEY setting must not be empty.
So where and what I need to change in order this to work?
Well according to your error which says, you have no SECRET_KEY defined. You need to add one to your settings.py.
Django will refuse to start if SECRET_KEY is not set. You can read more about it in docs
The SECRET_KEY can be anything...but if you want to use Django to generate one, you can do the following from the python shell:
>>> from django.utils.crypto import get_random_string
>>> chars = 'abcdefghijklmnopqrstuvwxyz0123456789!##$%^&*(-_=+)'
>>> SECRET_KEY = get_random_string(50, chars)
>>> print SECRET_KEY
Copy the secret_key to your setting file.
Hope this will help you! :)
It's very simple, just edit manage.py:
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
to
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings.base")
and also wsgi.py
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
to
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings.base")
If you moved your old settings.py into settings/base.py, I assume you want to split your Django settings into multiple files.
So, the only thing you have to do is to ensure all your settings are available at module level. In settings/__init__.py, write something like
from .base import *
from .another_setting_file import *
# etc.
Then, all your settings variables (settings.SECRET_KEY, settings.DEBUG, etc) will be available to the rest of the Django app, without need to modify default DJANGO_SETTINGS_MODULE value.
Bonus: using this method, you can override a setting defined in base.py in another file as soon as you import base first in __init__.py
Django is looking into DJANGO_SETTINGS_MODULE env variable by default. It's set to
'{your_project_name}.settings' module or package (again, by default). So you can modify that to be '{your_project_name}.settings.base' to read settings from base.py module.
But you can also accomplish that without modifying environment variables. With your folder structure Django is looking into settings package you've created. So, you just need to import all the settings defined in base.py there. Just place this code into settings/__init__.py module:
from .base import *
This allows you to import settings from different modules and perform some more complex logic like checking the environment variables and importing settings based on your current environment. For example importing settings by checking in which environment the app is running:
import logging
import os
# Default is local environment
environment = os.getenv('MACHINE_ENV', 'local')
if environment:
if environment.lower() == 'local':
from .local import *
elif environment.lower() == 'staging':
from .staging import *
elif environment.lower() == 'prod':
from .prod import *

Using globals() to augment django settings

It seems to be relatively normal in the Django world to import a local environment settings override file at the end of settings.py, something like:
from settings_local.py import *
This has a couple of issues, most notably for me being that you cannot inspect or modify base settings, only override them - which leads to some painful duplication and headaches over time.
Instead, is there anything wrong with importing an init function from a local file and injecting the output of globals() to allow that local file to read and modify the settings a bit more intelligently?
settings.py:
from settings_local.py import init
init( globals() )
settings_local.py:
def init( config ):
config['INSTALLED_APPS'].append( 'some.app' )
This seems to address a number of the concerns related to using import *, but I'd like to know if this is a bad idea for a reason I am not yet aware of.
The more common and readable way to handle this is to have multiple settings files, where you put all the common settings in one module and import that at the beginning of the environment-specific settings files:
settings
__init__.py
base.py
development.py
staging.py
production.py
This allows you the mutations you are referring to:
# base.py
INSTALLED_APPS = [
'foo',
'bar'
]
# development.py
from .base import *
INSTALLED_APPS += [
'debug_toolbar'
]
Then you run your app locally with DJANGO_SETTINGS_MODULE=settings.development

Django: Dynamically add apps as plugin, building urls and other settings automatically

I have following structure of my folders in Django:
./project_root
./app
./fixtures/
./static/
./templates/
./blog/
./settings.py
./urls.py
./views.py
./manage.py
./__init__.py
./plugin
./code_editor
./static
./templates
./urls.py
./views.py
./__init__.py
./code_viewer
./static
./templates
./urls.py
./views.py
./__init__.py
So, how can I make root urls.py automatically build up the list of urls by looking for other urls.py based on the INSTALLED_APPS? I change settings.py in order to build INSTALLED_APPS, TEMPLATES_DIR, STATICFILES_DIRS dynamically. (It means i do not know how many plugins will be installed in different servers. It should dynamically check it on run time and add it.)on:
python manage.py runserver
Here is code for adding INSTALLED_APPS, TEMPATES_DIR, STATICFILES_DIR.
PLUGINS_DIR = '/path_to/plugins/'
for item in os.listdir(PLUGINS_DIR):
if os.path.isdir(os.path.join(PLUGINS_DIR, item)):
plugin_name = 'app.plugins.%s' % item
if plugin_name not in INSTALLED_APPS:
INSTALLED_APPS = INSTALLED_APPS+(plugin_name,)
template_dir = os.path.join(PLUGINS_DIR, '%s/templates/' % item)
if os.path.isdir(template_dir):
if template_dir not in TEMPLATE_DIRS:
TEMPLATE_DIRS = TEMPLATE_DIRS+(template_dir,)
static_files_dir = os.path.join(PLUGINS_DIR, '%s/static/' % item)
if os.path.isdir(static_files_dir):
if static_files_dir not in STATICFILES_DIRS:
STATICFILES_DIRS = STATICFILES_DIRS + (static_files_dir,)
Any help will be appreciated. Thank you in advance.
SOLUTION:
EDIT: So what i did are as following:
I include two modules like this:
from django.conf import settings
from django.utils.importlib import import_module
And then in root urls.py I add following code:
def prettify(app):
return app.rsplit('.', 1)[1]
for app in INSTALLED_APPS:
try:
_module = import_module('%s.urls' % app)
except:
pass
else:
if 'eats.plugins' in app:
urlpatterns += patterns('',
url(r'^plugins/%s/' % prettify(app), include('%s.urls' % app))
)
Thank you a lot #Yuka. Thank you. Thank you. Thank you. Thank you....
You make my day.
What you'll want is something like this:
from django.utils.importlib import import_module
for app in settings.INSTALLED_APPS:
try:
mod = import_module('%s.urls' % app)
# possibly cleanup the after the imported module?
# might fuss up the `include(...)` or leave a polluted namespace
except:
# cleanup after module import if fails,
# maybe you can let the `include(...)` report failures
pass
else:
urlpatterns += patterns('',
url(r'^%s/' % slugify(app), include('%s.urls' % app)
)
You'll also want to steal and implement your own slugify from django template or utils (I'm not exactly sure where it lives these days?) and slightly modify it to strip out any 'dots' and other useless namespacing you don't want in your 'url' e.g. you might not want your urls looking like so: 'example.com/plugin.some_plugin_appname/' but like example.com/nice_looking_appname/
You might even not want it automagicly named after all, and instead made a configurable 'setting' in your plugins own 'settings' module or something like so:
# plugin settings conf
url_namespace = 'my_nice_plugin_url/'
# root urls.py:
url(r'^%s/' % mod.url_namespace, include(...))
# or:
url(r'^%s/' % app.settings.url_namespace, inc..
You probably get the gist of it.
Kind regards,
I have modified the logic of #wdh and I have tested with Django 2.2.9 (latest stable in Jan 2020) and python 3.8
urls.py
from django.apps import apps
from django.conf import settings
for app in settings.INSTALLED_APPS:
if app.startswith('myappsprefix_'):
app_config = apps.get_app_config(app.rsplit('.')[0])
urlpatterns += i18n_patterns(
path(f'{app_config.urls}/', include(f'{app_config.name}.urls')),
)
i18n_patterns is for internationalization.
apps.py of every app
class MyCustomAppsConfig(AppConfig):
name = 'myappsprefix_mycustomapp'
urls = 'mybaseurlforapp' # required!
in my settings.py
INSTALLED_APPS = [
...
'myappsprefix_mycustomapp.apps.MyCustomAppsConfig',
...
]
It is best practice not to access INSTALLED_APPS directly as stated in django docs but to use the registry instead:
from django.apps import apps
for app in apps.get_app_configs():
app_name = app.name
try:
...
Have you tried something like:
for app in INSTALLED_APPS:
# You'll want to check that you can import the urls of that app here
urlpatterns += patterns('',
url(r'^%s/' % app, include('%s.urls' % app) )
)
I've modified #Progressify's code a bit further to do away with the app prefix.
# in your project's urls.py
from django.apps import apps
from django.contrib import admin
from django.conf import settings
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
# ...
# any other paths you may have
]
# Load urls from any app that defines a urls attribute in appname.apps.AppConfig
for app in settings.INSTALLED_APPS:
try:
app_config = apps.get_app_config(app.rsplit('.')[0])
try:
urls = app_config.urls
urlpatterns += [
path(f'{app_config.urls}', include(f'{app_config.name}.urls', namespace=f'{app_config.urls_namespace}')),
]
except AttributeError:
pass
except LookupError:
pass
This will look for an AppConfig in your project's apps.py that defines a prefix and a namespace, and include those accordingly. Admittedly the error catching is a bit crude, so you may want to refine it a bit depending on whether you want to allow your app to define urls but not a namespace, etc.
# in each of your apps' apps.py
from django.apps import AppConfig
class UsersConfig(AppConfig): # name the class whatever you want
name = 'users' # name of your app
urls = '' # prefix for this app's urls –– may need to append slash
urls_namespace = 'users' # app url namespace –– it's "best practice" to define one

How to set django middleware in settings file

Hi all i am trying to add middleware to my app and the app is located in this location
myapp/sitemanager/middleware/__init__.py
myapp/sitemanager/middleware/redirects.py
what is the right way to declare it in my settings.py file.i currently have it set this way
MIDDLEWARE_CLASSES = ("sitemanager.middleware.redirects.SiteDetectionMiddleware")
but i keep geting this error
ImproperlyConfigured: Error importing middleware sitemanager.middleware.redirects: "cannot import name Address"
Thanks
You'd make sure that your Python path is properly configured.
Suppose (in your project) your directory structure resembling this:
Django 1.4
/mysite
/mysite/mysite #defult settings.py gonna here...
/mysite/apps
/mysite/apps/__init__.py
/mysite/apps/main
/mysite/apps/main/__init__.py
/mysite/apps/main/models.py
/mysite/apps/main/views.py
/mysite/apps/main/middleware/__init__.py
/mysite/apps/main/middleware/log.py
It is my simple midlleware logger exemple (in log.py):
from django.http import HttpRequest
import datetime
class Logger(object):
def process_request(self, request):
f = open('/tmp/log.txt', 'w')
f.write(str(datetime.datetime.now()))
Note that my custom middleware class (in log.py) is under my middleware python package, that is under main app.
So, you should put in your settings.py something like this:
import sys
sys.path.append(MY_PROJECT_ROOT)
and in middleware tuple:
MIDDLEWARE_CLASSES = (
'...',
'apps.main.middleware.log.Logger',
)

Categories

Resources