6

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.

Khamidulla
  • 2,927
  • 6
  • 35
  • 59
  • What have you tried? It seems like what you do in `settings.py` can be easily adapted to your `urls.py`. – Maciej Gol Nov 07 '13 at 10:29
  • I know my question is little bit messy. However i mention that i what i did already it is inside of settings.py. However in urls.py of root, I want to make lookup one by one through installed apps and append to urlpatters plugins urls. SO if you could help me please. Currently I am does not have any idea how to make it. :( – Khamidulla Nov 07 '13 at 10:32
  • 1
    The `INSTALLED_APPS` (and all other settings), can be accessed from `django.conf.settings` object, i.e: `from django.conf import settings; print settings.INSTALLED_APPS`. – Maciej Gol Nov 07 '13 at 10:33
  • `#in root urls.py from django.conf import settings print settings.INSTALLED_APPS ` i got error: AttributeError at / 'function' object has no attribute 'INSTALLED_APPS' – Khamidulla Nov 07 '13 at 10:39
  • So i figure it out. I print list of installed apps. So now how I can add it to existing urlpatterns? – Khamidulla Nov 07 '13 at 11:20
  • why do you need to dynamically fill TEMPLATE_DIRS too? If the dynamically loaded app (added to INSTALLED_APPS) is a normal Django app, its `template` (and `static`) directory should be loaded anyway automatically by Django, right? – nerdoc Jul 08 '18 at 18:17

5 Answers5

6

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,

Yuka
  • 301
  • 1
  • 4
1

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',
    ...
]
Progressify
  • 73
  • 1
  • 2
  • 8
1

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:
        ...
rptmat57
  • 3,643
  • 1
  • 27
  • 38
0

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) )
    )
wdh
  • 1,612
  • 12
  • 16
0

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
airstrike
  • 2,270
  • 1
  • 25
  • 26