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

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

Related

media file not found in a simple django-admin application

I have seen many questions here regarding media files in Django, but honestly I can't find a valid solution for my problem.
So I have decided to streamline the environment to a very simple application.
You can find it here: github project
I have created a project with django-admin and I called 'documents' and then I have created an app called 'docs'.
Then I defined a simple class:
def get_path(instance, filename):
fn, ext = os.path.splitext(filename)
ts = str(int(time.time()))
return os.path.join('{}_{}{}'.format(fn, ts, ext))
class doc(models.Model):
doc_name = models.CharField(max_length=30, verbose_name=u"doc name", help_text=u"name of the doc")
doc_document = models.FileField(upload_to=get_path, verbose_name=u"document", help_text=u"document")
class Meta:
unique_together = ("doc_name", )
def __str__(self):
return f"{self.doc_name}"
This changes the filename and it adds a timestamp.
I also changed the urls.py file adding the following:
path(r'', admin.site.urls),
Now the question is: without using MEDIA_URL and MEDIA_ROOT is it possible to make this working?
I have tried to add a files and it works:
and it properly save the file in the root of the project.
But when I go to the link and click I am getting the following:
Now is it possible to know where is it looking for the file?
Do you think that adding MEDIA_URL and MEDIA_ROOT would help? How should I add it?
I have already tried but without any success.
Additional information
If I set my project in urls.py:
Then I am getting this:
Following the tutorial: https://www.sitepoint.com/django-photo-sharing-app/
I basically changed 3 things:
1-created docs/urls.py and put:
urlpatterns = []
2-changed documents/urls.py to:
from django.conf import settings
from django.conf.urls.static import static
from django.urls import path, include
from django.contrib import admin
urlpatterns = [
path('admin/', admin.site.urls),
# Main app
path('', include('docs.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
3-in documents/settings.py just put the following:
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media/'
it works.

ModuleNotFoundError: No module named 'src'

I'm changing view of homepage with app names pages.
I've added pages to settings. this is how directory look like:
- trydjango
- src/
- pages/
- __init__
- views
- products
- trydjango/
- __init__
- settings
- urls
- manage
views' code:
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def home_view(*args, **kwargs):
return HttpResponse("<h1>Hello Again</h1>")
urls code
from django.contrib import admin
from django.urls import path
from src.pages.views import home_view
urlpatterns = [
path('admin/', admin.site.urls),
path('', home_view, name='home'),
]
and I see this error when I run server
ModuleNotFoundError: No module named 'src'
First you need to understand what an app in Django is compared to a project.
When you register an app django will look in the project root folder when you try to import it.
Your project root is where your manage.py file is. In your case the src folder.
So when you want to import your views module you need to state
from pages.views
rather than
from src.pages.views
I suggest that you read through and follow (by coding it yourself) the Django tutorial to learn more about project structure and creating your own apps with models, urls etc.
I got the same problem, IDE may use red underline but this code is still correct:
from pages.views

App specific default settings in Django?

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
]

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',
)

Django cannot import app

Following the django-rest tutorial
app/urls.py:
from django.conf.urls import url, include
from rest_framework import routers
from app.abbr import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
Directory structure:
Error:
File "..../app/app/urls.py", line 3, in
from app.abbr import views
ImportError: No module named 'app.abbr'
So, sigh...
It would have been useful if you pointed to the tutorial that showed you to do this.
You should not import from app; that refers to the inner directory containing your urls.py. Just import from abbr.
from abbr import views
And what if you change import like this?
from app.app.abbr import views?
I am considering that you are using django 1.9 +
Try this
from . import views
The root directory folder named App in your case is named after your project name by default when you start a new project via the django-admin startproject command.
you can rename your root directory folder to whatever you want and it won't affect your project.
when in your code are importing from app, it is actually looking inside the 'app' folder containig the 'settings.py' file.
the django-rest tutorial you are following contains an error when they are doing from tutorial.quickstart import views which should be from quickstart import views
so the same goes for you, you should do from abbr import views

Categories

Resources