tags:

views:

130

answers:

2

Hello,

is there some pythonic way to load a django app trough an alias name? This I think would be one way to make an app more "pluggable-friendly".

there is a pattern used in settings.py:

INSTALLED_APPS = ('... ','...', )

where INSTALLED_APPS is a tuple containing names of apps. That's fine, but I don't want to put in certain apps explicitly this way.

I would like to have an app handle named say external_login_app mapping to drupal_login or mediawiki_login (to be supplied by the end user or a third party), where name of the actual custom module/app is provided by a string variable in settings.py

The pluggable part (e.g. mediawiki_login) must be a full-fledged app with it's own views, models, forms, etc, while external_login_app must be accessible anywhere in the rest of the code.

The goal here is to decouple distributed code from plugins like that.

edit 1: here is what I'm trying:

in settings.py

EXTERNAL_LOGIN = __import__(EXTERNAL_LOGIN_APP) 
#setting name must be upper case according to
#django docs

but my custom login app depends on the settings file too, so looks like I have a circular import problem with errors like module external_login does not have attribute views. This problem seems to be very insidious, as I am unable to use even simple things like render_to_response shortcut in views imported with __import__ statement in settings.py.

edit 2: after trying a while I found that using __import__() in settings.py call won't work because of almost inevitable circular dependencies

The best working method I found so far is to place __import__() calls into other .py files of the app providing the generic "consumer" interface - the one that calls the plugin functions:

in settings.py: as Michał suggests in his answer

EXTERNAL_LOGIN_APP = 'drupal_login'
INSTALLED_APPS = (
                  '...',
                  EXTERNAL_LOGIN_APP,
                )

e.g. in app/views.py:

from django.conf import settings
EXTERNAL_LOGIN = __import__(settings.EXTERNAL_LOGIN_APP, \
                            [], [], ['api','forms'])

def login_view(request, external=False):
    if external:
        form = EXTERNAL_LOGIN.forms.LoginForm(request.POST)
        if form.is_valid():
            login = form.cleaned_data['login']
            password = form.cleand_data['password']
            if EXTERNAL_LOGIN.api.check_password(login, password):
                #maybe create local account, 
                #do local authentication
                #set session cookies for the 
                #external site to synchronize logins, etc.
+4  A: 

Set

LOGIN_APP_NAME = 'drupal_login' # or 'mediawiki_login', or whatever

early enough in settings.py, then put LOGIN_APP_NAME (without any quotes around it!) in your INSTALLED_APPS instead of the name of the actual app.

If you need more complex functionality involved in determining what app to use, how about putting something like external_login_app() (a function call -- put no quotes around it!) in INSTALLED_APPS and having the external_login_app function return whatever it is that it should return based on a setting in settings.py, or maybe the contents of a config file somewhere or whatever? (EDIT: Tobu's answer shows what such a function might need to return and how it might go about achieving that with __import__.)

Anyway, I'm not sure that much is achieved in this way in terms of decoupling parts of the site -- if the user still needs to modify settings.py, why not have him / her put in the name of the appropriate app in the right place? (EDIT: OK, so now I sort of see what could be gained with the right solution here... I guess the discussion continues as to how best to do what is required.)

EDIT: I posted this prior to the Original Poster amending the question with any edits, and now edit 2 includes the idea of moving the name of the login app to a separate variable in settings.py and including the variable in INSTALLED_APPS. Now that the two edits to the original question are in place, I guess I can sort of see the problem clearer, although that just makes me think that what the situation calls for is an abstract_login app with backends to support the '*_login' modules dealing with drupal, mediawiki etc. login schemes. Well, I can't say I can provide that sort of thing right now... Hope somebody else can. Will edit again should I believe I have a bright idea simplifying the whole thing beyond the OP's edits.

Michał Marczyk
yes, sorry I must have acknowledged that part of your answer :O) now i did.
Evgeny
+1, you had a good point about the function call too, however it must call import like Tobu showed
Evgeny
Thanks. :-) I can see Tobu's point now too, in fact I'll edit to remove the misleading bit from the above. Thanks for the cluebat hit to make me think to do that.
Michał Marczyk
+2  A: 

Defining the app name in settings(as Michał and your edit do) works well:

EXTERNAL_LOGIN_APP = 'drupal_login'
INSTALLED_APPS = (
                  '...',
                  EXTERNAL_LOGIN_APP,
                )

Also in settings, or in a common module, you could then factor in importing the app:

def external_login_app():
    return __import__(EXTERNAL_LOGIN_APP, …)

And use it after your imports:

external_login_app = settings.external_login_app()
Tobu
i see, function wrapper defers import call and solves the circular dependency. This method works, just tested. However, the loader function name must be in upper case - it is a Django quirk. http://docs.djangoproject.com/en/dev/topics/settings/#creating-your-own-settings. Also using __import__(APP_NAME, [],[],['views','forms','models']) or similar is important, otherwise those modules won't load.
Evgeny