Initial commit of Gilbert, an advanced Django administration interface.
[philo.git] / contrib / gilbert / sites.py
1 from django.contrib.admin.sites import AdminSite
2 from django.contrib.auth import authenticate, login, logout
3 from django.conf.urls.defaults import url, patterns
4 from django.core.urlresolvers import reverse
5 from django.shortcuts import render_to_response
6 from django.conf import settings
7 from django.utils import simplejson as json
8 from django.utils.datastructures import SortedDict
9 from django.http import HttpResponse
10 from django.db.models.base import ModelBase
11 from philo.utils import fattr
12 from philo.contrib.gilbert.options import GilbertModelAdmin, GilbertPlugin, GilbertClass
13 from philo.contrib.gilbert.exceptions import AlreadyRegistered, NotRegistered
14 from django.forms.models import model_to_dict
15 import sys
16 from traceback import format_tb
17 from inspect import getargspec
18 from philo.contrib.gilbert.utils import is_gilbert_plugin, is_gilbert_class, is_gilbert_method, gilbert_method, call_gilbert_method
19
20
21 __all__ = ('GilbertSite', 'site')
22
23
24 class GilbertSitePlugin(GilbertPlugin):
25         class auth(GilbertClass):
26                 @gilbert_method(restricted=False)
27                 def login(self, request, username, password):
28                         user = authenticate(username=username, password=password)
29                         if user is not None and user.is_active:
30                                 login(request, user)
31                                 return True
32                         else:
33                                 return False
34                 
35                 @gilbert_method(restricted=False)
36                 def logout(self, request):
37                         logout(request)
38                         return True
39                 
40                 @gilbert_method
41                 def passwd(self, request, current_password, new_password, new_password_confirm):
42                         user = request.user
43                         if user.check_password(current_password) and (new_password == new_password_confirm):
44                                 user.set_password(new_password)
45                                 user.save()
46                                 return True
47                         return False
48
49
50 class GilbertSite(object):
51         def __init__(self, namespace='gilbert', app_name='gilbert', title='Gilbert'):
52                 self.namespace = namespace
53                 self.app_name = app_name
54                 self.title = title
55                 self.core_api = GilbertSitePlugin(self)
56                 self.model_registry = SortedDict()
57                 self.plugin_registry = SortedDict()
58         
59         def register_plugin(self, plugin):
60                 if is_gilbert_plugin(plugin):
61                         if plugin.gilbert_plugin_name in self.plugin_registry:
62                                 raise AlreadyRegistered('A plugin named \'%s\' is already registered' % plugin.gilbert_plugin_name)
63                         self.plugin_registry[plugin.gilbert_plugin_name] = plugin(self)
64                 else:
65                         raise ValueError('register_plugin must be provided a valid plugin class or instance')
66         
67         def register_model(self, model_or_iterable, admin_class=GilbertModelAdmin, **admin_attrs):
68                 if isinstance(model_or_iterable, ModelBase):
69                         model_or_iterable = [model_or_iterable]
70                 for model in model_or_iterable:
71                         if model._meta.app_label not in self.model_registry:
72                                 self.model_registry[model._meta.app_label] = SortedDict()
73                         if model._meta.object_name in self.model_registry[model._meta.app_label]:
74                                 raise AlreadyRegistered('The model %s is already registered' % model.__name__)
75                         if admin_attrs:
76                                 admin_attrs['__module__'] = __name__
77                                 admin_class = type('%sAdmin' % model.__name__, (admin_class,), admin_attrs)
78                         self.model_registry[model._meta.app_label][model._meta.object_name] = admin_class(self, model)
79         
80         def has_permission(self, request):
81                 return request.user.is_active and request.user.is_staff
82         
83         @property
84         def urls(self):
85                 return (patterns('',
86                         url(r'^$', self.index, name='index'),
87                         url(r'^css.css$', self.css, name='css'),
88                         url(r'^api.js$', self.api, name='api'),
89                         url(r'^router/?$', self.router, name='router'),
90                         url(r'^models/(?P<app_label>\w+)/?$', self.router, name='models'),
91                         url(r'^plugins/(?P<plugin_name>\w+)/?$', self.router, name='plugins'),
92                         url(r'^login$', self.router, name='login'),
93                 ), self.app_name, self.namespace)
94         
95         def request_context(self, request, extra_context=None):
96                 from django.template import RequestContext
97                 context = RequestContext(request, current_app=self.namespace)
98                 context.update(extra_context or {})
99                 context.update({'gilbert': self, 'user': request.user, 'logged_in': self.has_permission(request)})
100                 return context
101         
102         def index(self, request, extra_context=None):
103                 return render_to_response('gilbert/index.html', context_instance=self.request_context(request, extra_context))
104         
105         def css(self, request, extra_context=None):
106                 return render_to_response('gilbert/css.css', context_instance=self.request_context(request, extra_context), mimetype='text/css')
107         
108         def api(self, request, extra_context=None):
109                 return render_to_response('gilbert/api.js', context_instance=self.request_context(request, extra_context), mimetype='text/javascript')
110         
111         def router(self, request, app_label=None, plugin_name=None, extra_context=None):
112                 submitted_form = False
113                 if request.META['CONTENT_TYPE'].startswith('application/x-www-form-urlencoded'):
114                         submitted_form = True
115                 
116                 if submitted_form:
117                         post_dict = dict(request.POST)
118                         ext_request = {
119                                 'action': post_dict.pop('extAction'),
120                                 'method': post_dict.pop('extMethod'),
121                                 'type': post_dict.pop('extType'),
122                                 'tid': post_dict.pop('extTID'),
123                                 'data': None,
124                                 'kwdata': post_dict,
125                         }
126                         if 'extUpload' in request.POST:
127                                 ext_request['upload'] = request.POST['extUpload']
128                 else:
129                         ext_request = json.loads(request.raw_post_data)
130                         ext_request['kwdata'] = None
131                 
132                 try:
133                         gilbert_class = None
134                         
135                         if app_label is not None:
136                                 try:
137                                         gilbert_class = self.model_registry[app_label][ext_request['action']]
138                                 except KeyError:
139                                         raise NotImplementedError('A model named \'%s\' has not been registered' % ext_request['action'])
140                         elif plugin_name is not None:
141                                 try:
142                                         gilbert_plugin = self.plugin_registry[plugin_name]
143                                 except KeyError:
144                                         raise NotImplementedError('A plugin named \'%s\' has not been registered' % plugin_name)
145                                 try:
146                                         gilbert_class = gilbert_plugin.gilbert_plugin_classes[ext_request['action']]
147                                 except KeyError:
148                                         raise NotImplementedError('The plugin named \'%s\' does not provide a class named \'%s\'' % (plugin_name, ext_request['action']))
149                         else:
150                                 try:
151                                         gilbert_class = self.core_api.gilbert_plugin_classes[ext_request['action']]
152                                 except KeyError:
153                                         raise NotImplementedError('Gilbert does not provide a class named \'%s\'' % ext_request['action'])
154                         
155                         try:
156                                 method = gilbert_class.gilbert_class_methods[ext_request['method']]
157                         except KeyError:
158                                 raise NotImplementedError('The class named \'%s\' does not implement a method named \'%\'' % (gilbert_class.gilbert_class_name, ext_request['method']))
159                         if method.gilbert_method_restricted and not self.has_permission(request):
160                                 raise NotImplementedError('The method named \'%s\' is not available' % method.gilbert_method_name)
161                         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 {}))}
162                 except:
163                         exc_type, exc_value, exc_traceback = sys.exc_info()
164                         response = {'type': 'exception', 'tid': ext_request['tid'], 'message': ('%s: %s' % (exc_type, exc_value)), 'where': format_tb(exc_traceback)[0]}
165                 
166                 if submitted_form:
167                         return HttpResponse(('<html><body><textarea>%s</textarea></body></html>' % json.dumps(response)))
168                 return HttpResponse(json.dumps(response), content_type=('application/json; charset=%s' % settings.DEFAULT_CHARSET))
169
170
171 site = GilbertSite()