Cleanup of Gilbert plugins API and JavaScript.
[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, include
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.plugins import GilbertModelAdmin, GilbertPlugin, is_gilbert_method, gilbert_method
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 django.views.decorators.cache import never_cache
19 from philo.contrib.gilbert import __version__ as gilbert_version
20 import staticmedia
21 import os
22
23 __all__ = ('GilbertSite', 'site')
24
25
26 class GilbertAuthPlugin(GilbertPlugin):
27         name = 'auth'
28         
29         @property
30         def js(self):
31                 return [staticmedia.url('gilbert/Gilbert.api.auth.js')]
32         
33         @property
34         def fugue_icons(self):
35                 return ['user-silhouette', 'key--pencil', 'door-open-out', 'door-open-in']
36         
37         @gilbert_method(restricted=False)
38         def login(self, request, username, password):
39                 user = authenticate(username=username, password=password)
40                 if user is not None and user.is_active:
41                         login(request, user)
42                         return True
43                 else:
44                         return False
45         
46         @gilbert_method
47         def logout(self, request):
48                 logout(request)
49                 return True
50         
51         @gilbert_method
52         def passwd(self, request, current_password, new_password, new_password_confirm):
53                 user = request.user
54                 if user.check_password(current_password) and (new_password == new_password_confirm):
55                         user.set_password(new_password)
56                         user.save()
57                         return True
58                 return False
59         
60         @gilbert_method
61         def whoami(self, request):
62                 user = request.user
63                 return user.get_full_name() or user.username
64
65
66 class GilbertSite(object):
67         version = gilbert_version
68         
69         def __init__(self, namespace='gilbert', app_name='gilbert', title='Gilbert'):
70                 self.namespace = namespace
71                 self.app_name = app_name
72                 self.title = title
73                 self.model_registry = SortedDict()
74                 self.plugin_registry = SortedDict()
75                 self.register_plugin(GilbertAuthPlugin)
76         
77         def register_plugin(self, plugin):
78                 if plugin.name in self.plugin_registry:
79                         raise AlreadyRegistered('A plugin named \'%s\' is already registered' % plugin.name)
80                 self.plugin_registry[plugin.name] = plugin(self)
81         
82         def register_model(self, model_or_iterable, admin_class=GilbertModelAdmin, **admin_attrs):
83                 if isinstance(model_or_iterable, ModelBase):
84                         model_or_iterable = [model_or_iterable]
85                 for model in model_or_iterable:
86                         if model._meta.app_label not in self.model_registry:
87                                 self.model_registry[model._meta.app_label] = SortedDict()
88                         if model._meta.object_name in self.model_registry[model._meta.app_label]:
89                                 raise AlreadyRegistered('The model %s.%s is already registered' % (model._meta.app_label, model.__name__))
90                         if admin_attrs:
91                                 admin_attrs['__module__'] = __name__
92                                 admin_class = type('%sAdmin' % model.__name__, (admin_class,), admin_attrs)
93                         self.model_registry[model._meta.app_label][model._meta.object_name] = admin_class(self, model)
94         
95         def has_permission(self, request):
96                 return request.user.is_active and request.user.is_staff
97         
98         @property
99         def urls(self):
100                 urlpatterns = patterns('',
101                         url(r'^$', self.index, name='index'),
102                         url(r'^css$', self.css, name='css'),
103                         url(r'^api$', self.api, name='api'),
104                         url(r'^router/?$', self.router, name='router'),
105                         url(r'^router/models/(?P<app_label>\w+)/?$', self.router, name='models'),
106                         url(r'^login$', self.router, name='login'),
107                 )
108                 
109                 return (urlpatterns, self.app_name, self.namespace)
110         
111         def request_context(self, request, extra_context=None):
112                 from django.template import RequestContext
113                 context = RequestContext(request, current_app=self.namespace)
114                 context.update(extra_context or {})
115                 context.update({'gilbert': self, 'user': request.user, 'logged_in': self.has_permission(request)})
116                 return context
117         
118         @never_cache
119         def index(self, request, extra_context=None):
120                 return render_to_response('gilbert/index.html', context_instance=self.request_context(request, extra_context))
121         
122         def css(self, request, extra_context=None):
123                 icon_names = []
124                 for plugin in self.plugin_registry.values():
125                         icon_names.extend(plugin.fugue_icons)
126                 
127                 icons = dict([(icon_name, staticmedia.url('gilbert/fugue-icons/icons/%s.png' % icon_name)) for icon_name in set(icon_names)])
128                 
129                 context = extra_context or {}
130                 context.update({'icons': icons})
131                 
132                 return render_to_response('gilbert/styles.css', context_instance=self.request_context(request, context), mimetype='text/css')
133         
134         @never_cache
135         def api(self, request, extra_context=None):
136                 providers = []
137                 for app_label, models in self.model_registry.items():
138                         app_provider = {
139                                 'namespace': 'Gilbert.api.models.%s' % app_label,
140                                 'url': reverse('%s:models' % self.namespace, current_app=self.app_name, kwargs={'app_label': app_label}),
141                                 'type': 'remoting',
142                         }
143                         model_actions = {}
144                         for model_name, admin in models.items():
145                                 model_methods = []
146                                 for method in [admin.get_method(method_name) for method_name in admin.methods]:
147                                         if method.restricted and not self.has_permission(request):
148                                                 continue
149                                         model_methods.append({
150                                                 'name': method.name,
151                                                 'len': method.argc,
152                                         })
153                                 if model_methods:
154                                         model_actions[model_name] = model_methods
155                         if model_actions:
156                                 app_provider['actions'] = model_actions
157                                 providers.append(app_provider)
158                 
159                 plugin_provider = {
160                         'namespace': 'Gilbert.api',
161                         'url': reverse('%s:router' % self.namespace, current_app=self.app_name),
162                         'type': 'remoting',
163                 }
164                 plugin_actions = {}
165                 for plugin_name, plugin in self.plugin_registry.items():
166                         plugin_methods = []
167                         for method in [plugin.get_method(method_name) for method_name in plugin.methods]:
168                                 if method.restricted and not self.has_permission(request):
169                                         continue
170                                 plugin_methods.append({
171                                         'name': method.name,
172                                         'len': method.argc,
173                                 })
174                         if plugin_methods:
175                                 plugin_actions[plugin_name] = plugin_methods
176                 if plugin_actions:
177                         plugin_provider['actions'] = plugin_actions
178                         providers.append(plugin_provider)
179                 
180                 return HttpResponse(''.join(['Ext.Direct.addProvider('+json.dumps(provider, separators=(',', ':'))+');' for provider in providers]), mimetype='text/javascript')
181         
182         def router(self, request, app_label=None, extra_context=None):
183                 submitted_form = False
184                 if request.META['CONTENT_TYPE'].startswith('application/x-www-form-urlencoded'):
185                         submitted_form = True
186                 
187                 if submitted_form:
188                         post_dict = dict(request.POST)
189                         ext_request = {
190                                 'action': post_dict.pop('extAction'),
191                                 'method': post_dict.pop('extMethod'),
192                                 'type': post_dict.pop('extType'),
193                                 'tid': post_dict.pop('extTID'),
194                                 'upload': post_dict.pop('extUpload', False),
195                                 'data': None,
196                                 'kwdata': post_dict
197                         }
198                 else:
199                         ext_request = json.loads(request.raw_post_data)
200                         ext_request['upload'] = False
201                         ext_request['kwdata'] = None
202                 
203                 try:
204                         plugin = None
205                         
206                         if app_label is not None:
207                                 try:
208                                         plugin = self.model_registry[app_label][ext_request['action']]
209                                 except KeyError:
210                                         raise NotImplementedError('A model named \'%s\' has not been registered' % ext_request['action'])
211                         else:
212                                 try:
213                                         plugin = self.plugin_registry[ext_request['action']]
214                                 except KeyError:
215                                         raise NotImplementedError('Gilbert does not provide a class named \'%s\'' % ext_request['action'])
216                         
217                         method = plugin.get_method(ext_request['method'])
218                         
219                         if method is None or (method.restricted and not self.has_permission(request)):
220                                 raise NotImplementedError('The method named \'%s\' is not available' % method.name)
221                         
222                         response = {'type': 'rpc', 'tid': ext_request['tid'], 'action': ext_request['action'], 'method': ext_request['method'], 'result': method(request, *(ext_request['data'] or []), **(ext_request['kwdata'] or {}))}
223                 except:
224                         exc_type, exc_value, exc_traceback = sys.exc_info()
225                         response = {'type': 'exception', 'tid': ext_request['tid'], 'message': ('%s: %s' % (exc_type, exc_value)), 'where': format_tb(exc_traceback)[0]}
226                 
227                 if submitted_form:
228                         return HttpResponse(('<html><body><textarea>%s</textarea></body></html>' % json.dumps(response)))
229                 return HttpResponse(json.dumps(response), content_type=('application/json; charset=%s' % settings.DEFAULT_CHARSET))
230
231
232 site = GilbertSite()