"""

    def user_owns_object(request, id):
        obj = get_object_or_404(Item,pk=id)
        if obj.owner != request.user: return False
        return True
        
    @login_required(login_url="/login")
    @auth_required(user_owns_object, template="do_not_own.html",
                   extra_context={"object_type", "Item"})
    @feature_required("EditItem")
    def view(request, id):
        man = Item.ChangeManipulator(id)
        ...
"""

from django.contrib import auth
from django.contrib.auth import decorators as authdec
from django.conf import settings
from django.http import HttpResponseRedirect
from django.shortcuts import *
from django.template import RequestContext
from urllib import quote
from models import Feature


LOGIN_URL                 = getattr(settings, 'LOGIN_URL', '/admin/login')
REDIRECT_FIELD_NAME       = getattr(settings, 'REDIRECT_FIELD_NAME',
                                    auth.REDIRECT_FIELD_NAME)
AUTH_FAILURE_TEMPLATE     = getattr(settings, 'AUTH_FAILURE_TEMPLATE',
                                    "features/auth_failure.html")
FEATURE_DISABLED_TEMPLATE = getattr(settings, 'FEATURE_DISABLED_TEMPLATE',
                                    "features/disabled.html")

def login_required(login_url=None):
    """same as auth.decorators.login_required, only allows for specifying
    the LOGIN_URL or getting it from settings
    """
    def _dec(view):
        url = login_url ## bind to closure
        def _view(request, *args, **kwdargs):
            if request.user.is_authenticated():
                return view(request, *args, **kwdargs)
            login_url = url
            if login_url is None or callable(login_url):
                login_url = LOGIN_URL
            return HttpResponseRedirect('%s?%s=%s' % (login_url,
                            REDIRECT_FIELD_NAME, quote(request.get_full_path())))
        _view.__doc__ = view.__doc__
        _view.__dict__ = view.__dict__
        return _view
    if callable(login_url):
        return _dec(login_url)
    return _dec

def auth_required(auth_func, template=None, extra_context={}):
    """like user_passed_test only if the test fails the template is rendered.
    defaults to AUTH_FAILURE_TEMPLATE or 'features/auth_failure.html'.
    extra_context is passed on to the template renderer.
    """
    def _dec(view_func):
        temp, ext = template, extra_context
        def _view(request, *args, **kwdargs):
            if auth_func(request, *args, **kwdargs):
                return view_func(request, *args, **kwargs)
            template, extra_context = temp, dict(ext)
            extra_context.setdefault('login_url', LOGIN_URL)
            if template is None:
                template = AUTH_FAILURE_TEMPLATE
            return render_to_response(template, extra_context,
                          context_instance=RequestContext(request))
        _view.__doc__ = view.__doc__
        _view.__dict__ = view.__dict__
        return _view
    return _dec

## TODO: convert this to a class/object decorator set to avoid the
##       closure issues (and have cleaner code)
def feature_required(feature, template=None, extra_context={},
                     extra_func=None, exception_func=None,
                     auto_create_feat=False):
    """If the feature is disabled the template is rendered.
    the feature object will be passed off to the disabled context.
    feature - name of the feature.
    template - defaults to FEATURE_DISABLED_TEMPLATE which defaults to
               'features/disabled.html'.
    extra_context - extra context for the failure renderer.
    extra_func - if supplied it is called with all the view arguments.
                 if it returns true, the feature is considered Disabled.
                 allows for disabling the feature depending on he user etc.
    exception_func - the oposite of extra_func. If it returns True, then
                     the feature is enabled.
    auto_create_feat - if True, then get_or_create() will be used to get the
                       feature object.
    """
    def _dec(view_func):
        _dec_args = (template, extra_context, extra_func, exception_func)
        if auto_create_feat:
            _feat, _bNew = Feature.objects.get_or_create(name=feature)
            if _bNew: _feat.save()
        def _view(request, *args, **kwdargs):
            (template, extra_context, extra_func, exception_func) = _dec_args
            feat = Feature.objects.get(name=feature)
            bEnabled = feat.is_enabled
            if not request.user.is_anonymous() and request.user.is_active:
                bEnabled |= feat.allow_staff and request.user.is_staff 
                bEnabled |= feat.allow_superuser and request.user.is_superuser 
            if bEnabled and extra_func is not None:
                bEnabled = extra_func(request, *args, **kwdargs)
            if not bEnabled and exception_func is not None:
                bEnabled = exception_func(request, *args, **kwdargs)
            if bEnabled:
                return view_func(request, *args, **kwdargs)
            if template is None:
                template = FEATURE_DISABLED_TEMPLATE
            context = extra_context.copy()
            context['feature'] = feat
            return render_to_response(template, context,
                          context_instance=RequestContext(request))
        _view.__doc__ = view_func.__doc__
        _view.__dict__ = view_func.__dict__
        return _view
    return _dec

def feature(feature, extra_func=None, exception_func=None,
            disabled_func=None, auto_create_feat=False):
    """If the feature is disabled the template is rendered.
    the feature object will be passed off to the disabled context.
    feature - name of the feature.
    extra_func - if supplied it is called with all the view arguments.
                 if it returns true, the feature is considered Disabled.
                 allows for disabling the feature depending on he user etc.
    exception_func - the oposite of extra_func. If it returns True, then
                     the feature is enabled.
    disabled_func - if the feature is disabled, call this instead.
                    (default: return None)
    auto_create_feat - if True, then get_or_create() will be used to get the
                       feature object.
    """
    def _dec(func):
        _dec_args = (disabled_func, extra_func, exception_func)
        if auto_create_feat:
            _feat, _bNew = Feature.objects.get_or_create(name=feature)
            if _bNew: _feat.save()
        def _wrapper(*args, **kwdargs):
            (disabled_func, extra_func, exception_func) = _dec_args
            feat = Feature.objects.get(name=feature)
            bEnabled = feat.is_enabled
            if bEnabled and extra_func is not None:
                bEnabled = extra_func(*args, **kwdargs)
            if not bEnabled and exception_func is not None:
                bEnabled = exception_func(*args, **kwdargs)
            if bEnabled:
                return func(*args, **kwdargs)
            if callable(disabled_func):
                return disabled_func(*args, **kwdargs)
            return disabled_func
        _wrapper.__doc__ = func.__doc__
        _wrapper.__dict__ = func.__dict__
        return _wrapper
    return _dec
