Updated object deletion to match django 1.3's handling and thus multiple dbs.
[philo.git] / contrib / gilbert / sites.py
index 76c1d9a..3d07a84 100644 (file)
-from django.contrib.admin.sites import AdminSite
-from django.contrib.auth import authenticate, login, logout
-from django.conf.urls.defaults import url, patterns
+from django.conf import settings
+from django.contrib.auth import authenticate, login
+from django.core.context_processors import csrf
+from django.conf.urls.defaults import url, patterns, include
 from django.core.urlresolvers import reverse
+from django.db.models.base import ModelBase
+from django.forms.models import model_to_dict
+from django.http import HttpResponse, HttpResponseRedirect
 from django.shortcuts import render_to_response
-from django.conf import settings
+from django.template import RequestContext
 from django.utils import simplejson as json
 from django.utils.datastructures import SortedDict
-from django.http import HttpResponse
-from django.db.models.base import ModelBase
+from django.views.decorators.cache import never_cache
 from philo.utils import fattr
-from philo.contrib.gilbert.options import GilbertModelAdmin, GilbertPlugin, GilbertClass
-from philo.contrib.gilbert.exceptions import AlreadyRegistered, NotRegistered
-from django.forms.models import model_to_dict
-import sys
-from traceback import format_tb
+from . import __version__ as gilbert_version
+from .exceptions import AlreadyRegistered, NotRegistered
+from .extdirect import ExtAction, ExtRouter
+from .plugins.auth import Auth
+from .plugins.models import Models, ModelAdmin
 from inspect import getargspec
-from philo.contrib.gilbert.utils import is_gilbert_plugin, is_gilbert_class, is_gilbert_method, gilbert_method, call_gilbert_method
+from functools import partial, update_wrapper
+import sys, os, datetime
+
 
 
 __all__ = ('GilbertSite', 'site')
 
 
-class GilbertSitePlugin(GilbertPlugin):
-       class auth(GilbertClass):
-               @gilbert_method(restricted=False)
-               def login(self, request, username, password):
-                       user = authenticate(username=username, password=password)
-                       if user is not None and user.is_active:
-                               login(request, user)
-                               return True
-                       else:
-                               return False
-               
-               @gilbert_method(restricted=False)
-               def logout(self, request):
-                       logout(request)
-                       return True
-               
-               @gilbert_method
-               def passwd(self, request, current_password, new_password, new_password_confirm):
-                       user = request.user
-                       if user.check_password(current_password) and (new_password == new_password_confirm):
-                               user.set_password(new_password)
-                               user.save()
-                               return True
-                       return False
+class CoreRouter(ExtRouter):
+       def __init__(self, site):
+               self.site = site
+               self._actions = {}
+       
+       @property
+       def namespace(self):
+               return 'Gilbert.api.plugins'
+       
+       @property
+       def url(self):
+               return reverse('%s:router' % self.site.namespace, current_app=self.site.app_name)
+       
+       @property
+       def type(self):
+               return 'remoting'
+       
+       @property
+       def actions(self):
+               return self._actions
+       
+       @property
+       def plugins(self):
+               return list(action.obj for action in self._actions.itervalues())
+       
+       def register_plugin(self, plugin):
+               action = ExtAction(plugin)
+               self._actions[action.name] = action
+
+
+class ModelRouter(ExtRouter):
+       def __init__(self, site, app_label):
+               self.site = site
+               self.app_label = app_label
+               self._actions = {}
+       
+       @property
+       def namespace(self):
+               return 'Gilbert.api.models.%s' % self.app_label
+       
+       @property
+       def url(self):
+               return reverse('%s:model_router' % self.site.namespace, current_app=self.site.app_name, kwargs={'app_label': self.app_label})
+       
+       @property
+       def type(self):
+               return 'remoting'
+       
+       @property
+       def actions(self):
+               return self._actions
+       
+       @property
+       def models(self):
+               return dict((name, action.obj) for name, action in self._actions.iteritems())
+       
+       def register_admin(self, name, admin):
+               action = ExtAction(admin)
+               action.name = name
+               self._actions[action.name] = action
 
 
 class GilbertSite(object):
-       def __init__(self, namespace='gilbert', app_name='gilbert', title='Gilbert'):
+       version = gilbert_version
+       
+       def __init__(self, namespace='gilbert', app_name='gilbert', title=None):
                self.namespace = namespace
                self.app_name = app_name
-               self.title = title
-               self.core_api = GilbertSitePlugin(self)
-               self.model_registry = SortedDict()
-               self.plugin_registry = SortedDict()
+               if title is None:
+                       self.title = getattr(settings, 'GILBERT_TITLE', 'Gilbert')
+               else:
+                       self.title = title
+               
+               self.core_router = CoreRouter(self)
+               self.model_routers = SortedDict()
+               
+               self.register_plugin(Models)
+               self.register_plugin(Auth)
        
        def register_plugin(self, plugin):
-               if is_gilbert_plugin(plugin):
-                       if plugin.gilbert_plugin_name in self.plugin_registry:
-                               raise AlreadyRegistered('A plugin named \'%s\' is already registered' % plugin.gilbert_plugin_name)
-                       self.plugin_registry[plugin.gilbert_plugin_name] = plugin(self)
-               else:
-                       raise ValueError('register_plugin must be provided a valid plugin class or instance')
+               self.core_router.register_plugin(plugin(self))
        
-       def register_model(self, model_or_iterable, admin_class=GilbertModelAdmin, **admin_attrs):
+       def register_model(self, model_or_iterable, admin_class=ModelAdmin, **admin_attrs):
                if isinstance(model_or_iterable, ModelBase):
                        model_or_iterable = [model_or_iterable]
                for model in model_or_iterable:
-                       if model._meta.app_label not in self.model_registry:
-                               self.model_registry[model._meta.app_label] = SortedDict()
-                       if model._meta.object_name in self.model_registry[model._meta.app_label]:
-                               raise AlreadyRegistered('The model %s is already registered' % model.__name__)
+                       app_label = model._meta.app_label
+                       name = model._meta.object_name
+                       
+                       if app_label not in self.model_routers:
+                               self.model_routers[app_label] = ModelRouter(self, app_label)
+                       router = self.model_routers[app_label]
+                       
                        if admin_attrs:
                                admin_attrs['__module__'] = __name__
                                admin_class = type('%sAdmin' % model.__name__, (admin_class,), admin_attrs)
-                       self.model_registry[model._meta.app_label][model._meta.object_name] = admin_class(self, model)
+                       
+                       router.register_admin(name, admin_class(self, model))
        
        def has_permission(self, request):
                return request.user.is_active and request.user.is_staff
        
+       def protected_view(self, view, login_page=True, cacheable=False):
+               def inner(request, *args, **kwargs):
+                       if not self.has_permission(request):
+                               if login_page:
+                                       return self.login(request)
+                               else:
+                                       return HttpResponse(status=403)
+                       return view(request, *args, **kwargs)
+               if not cacheable:
+                       inner = never_cache(inner)
+               return update_wrapper(inner, view)
+       
        @property
        def urls(self):
-               return (patterns('',
-                       url(r'^$', self.index, name='index'),
-                       url(r'^css.css$', self.css, name='css'),
-                       url(r'^api.js$', self.api, name='api'),
-                       url(r'^router/?$', self.router, name='router'),
-                       url(r'^models/(?P<app_label>\w+)/?$', self.router, name='models'),
-                       url(r'^plugins/(?P<plugin_name>\w+)/?$', self.router, name='plugins'),
-                       url(r'^login$', self.router, name='login'),
-               ), self.app_name, self.namespace)
-       
-       def request_context(self, request, extra_context=None):
-               from django.template import RequestContext
-               context = RequestContext(request, current_app=self.namespace)
-               context.update(extra_context or {})
-               context.update({'gilbert': self, 'user': request.user, 'logged_in': self.has_permission(request)})
-               return context
-       
-       def index(self, request, extra_context=None):
-               return render_to_response('gilbert/index.html', context_instance=self.request_context(request, extra_context))
-       
-       def css(self, request, extra_context=None):
-               return render_to_response('gilbert/css.css', context_instance=self.request_context(request, extra_context), mimetype='text/css')
-       
-       def api(self, request, extra_context=None):
-               return render_to_response('gilbert/api.js', context_instance=self.request_context(request, extra_context), mimetype='text/javascript')
-       
-       def router(self, request, app_label=None, plugin_name=None, extra_context=None):
-               submitted_form = False
-               if request.META['CONTENT_TYPE'].startswith('application/x-www-form-urlencoded'):
-                       submitted_form = True
-               
-               if submitted_form:
-                       post_dict = dict(request.POST)
-                       ext_request = {
-                               'action': post_dict.pop('extAction'),
-                               'method': post_dict.pop('extMethod'),
-                               'type': post_dict.pop('extType'),
-                               'tid': post_dict.pop('extTID'),
-                               'data': None,
-                               'kwdata': post_dict,
-                       }
-                       if 'extUpload' in request.POST:
-                               ext_request['upload'] = request.POST['extUpload']
-               else:
-                       ext_request = json.loads(request.raw_post_data)
-                       ext_request['kwdata'] = None
+               urlpatterns = patterns('',
+                       url(r'^$', self.protected_view(self.index), name='index'),
+                       url(r'^api.js$', self.protected_view(self.api, login_page=False), name='api'),
+                       url(r'^icons.css$', self.protected_view(self.icons, login_page=False), name='icons'),
+                       url(r'^router$', self.protected_view(self.router, login_page=False), name='router'),
+                       url(r'^router/models/(?P<app_label>\w+)$', self.protected_view(self.router, login_page=False), name='model_router'),
+               )
+               return (urlpatterns, self.app_name, self.namespace)
+       
+       def login(self, request):
+               context = {
+                       'gilbert': self,
+                       'form_url': request.get_full_path(),
+               }
+               context.update(csrf(request))
                
-               try:
-                       gilbert_class = None
-                       
-                       if app_label is not None:
-                               try:
-                                       gilbert_class = self.model_registry[app_label][ext_request['action']]
-                               except KeyError:
-                                       raise NotImplementedError('A model named \'%s\' has not been registered' % ext_request['action'])
-                       elif plugin_name is not None:
-                               try:
-                                       gilbert_plugin = self.plugin_registry[plugin_name]
-                               except KeyError:
-                                       raise NotImplementedError('A plugin named \'%s\' has not been registered' % plugin_name)
-                               try:
-                                       gilbert_class = gilbert_plugin.gilbert_plugin_classes[ext_request['action']]
-                               except KeyError:
-                                       raise NotImplementedError('The plugin named \'%s\' does not provide a class named \'%s\'' % (plugin_name, ext_request['action']))
+               if request.POST:
+                       if request.session.test_cookie_worked():
+                               request.session.delete_test_cookie()
+                               username = request.POST.get('username', None)
+                               password = request.POST.get('password', None)
+                               user = authenticate(username=username, password=password)
+                               if user is not None:
+                                       if user.is_active and user.is_staff:
+                                               login(request, user)
+                                               return HttpResponseRedirect(request.get_full_path())
+                                       else:
+                                               context.update({
+                                                       'error_message_short': 'Not staff',
+                                                       'error_message': 'You do not have access to this page.',
+                                               })
+                               else:
+                                       context.update({
+                                               'error_message_short': 'Invalid credentials',
+                                               'error_message': 'Unable to authenticate using the provided credentials. Please try again.',
+                                       })
                        else:
-                               try:
-                                       gilbert_class = self.core_api.gilbert_plugin_classes[ext_request['action']]
-                               except KeyError:
-                                       raise NotImplementedError('Gilbert does not provide a class named \'%s\'' % ext_request['action'])
-                       
-                       try:
-                               method = gilbert_class.gilbert_class_methods[ext_request['method']]
-                       except KeyError:
-                               raise NotImplementedError('The class named \'%s\' does not implement a method named \'%\'' % (gilbert_class.gilbert_class_name, ext_request['method']))
-                       if method.gilbert_method_restricted and not self.has_permission(request):
-                               raise NotImplementedError('The method named \'%s\' is not available' % method.gilbert_method_name)
-                       response = {'type': 'rpc', 'tid': ext_request['tid'], 'action': ext_request['action'], 'method': ext_request['method'], 'result': call_gilbert_method(method, gilbert_class, request, *(ext_request['data'] or []), **(ext_request['kwdata'] or {}))}
-               except:
-                       exc_type, exc_value, exc_traceback = sys.exc_info()
-                       response = {'type': 'exception', 'tid': ext_request['tid'], 'message': ('%s: %s' % (exc_type, exc_value)), 'where': format_tb(exc_traceback)[0]}
+                               context.update({
+                                       'error_message_short': 'Cookies disabled',
+                                       'error_message': 'Please enable cookies, reload this page, and try logging in again.',
+                               })
+               
+               request.session.set_test_cookie()
+               return render_to_response('gilbert/login.html', context, context_instance=RequestContext(request))
+       
+       def index(self, request):
+               return render_to_response('gilbert/index.html', {
+                       'gilbert': self,
+                       'plugins': self.core_router.plugins # needed as the template language will not traverse callables
+               }, context_instance=RequestContext(request))
+       
+       def api(self, request):
+               providers = []
+               model_registry = {}
+               
+               for app_label, router in self.model_routers.items():
+                       if request.user.has_module_perms(app_label):
+                               providers.append(router.spec)
+                               model_registry[app_label] = dict((model_name, admin) for model_name, admin in router.models.items() if admin.has_permission(request))
+               
+               providers.append(self.core_router.spec)
                
-               if submitted_form:
-                       return HttpResponse(('<html><body><textarea>%s</textarea></body></html>' % json.dumps(response)))
-               return HttpResponse(json.dumps(response), content_type=('application/json; charset=%s' % settings.DEFAULT_CHARSET))
+               context = {
+                       'gilbert': self,
+                       'providers': [json.dumps(provider, separators=(',', ':')) for provider in providers],
+                       'model_registry': model_registry,
+               }
+               context.update(csrf(request))
+               
+               return render_to_response('gilbert/api.js', context, mimetype='text/javascript')
+       
+       def icons(self, request):
+               icon_names = []
+               
+               for plugin in self.core_router.plugins:
+                       icon_names.extend(plugin.icon_names)
+               
+               for router in self.model_routers.values():
+                       for admin in router.models.values():
+                               icon_names.extend(admin.icon_names)
+               
+               return render_to_response('gilbert/icons.css', {
+                       'icon_names': set(icon_names),
+                       'STATIC_URL': settings.STATIC_URL
+               }, mimetype='text/css')
+       
+       def router(self, request, app_label=None, extra_context=None):
+               if app_label is None:
+                       return self.core_router.render_to_response(request)
+               else:
+                       return self.model_routers[app_label].render_to_response(request)
 
 
 site = GilbertSite()
\ No newline at end of file