Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

python

django

web

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.

like image 302
Khamidulla Avatar asked Nov 07 '13 10:11

Khamidulla


3 Answers

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,

like image 140
Yuka Avatar answered Nov 18 '22 22:11

Yuka


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',
    ...
]
like image 38
Progressify Avatar answered Nov 18 '22 23:11

Progressify


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:
        ...
like image 1
rptmat57 Avatar answered Nov 18 '22 22:11

rptmat57