Merge branch 'master' into gilbert
[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 get_passwd_form(self, request):
53                 from django.contrib.auth.forms import PasswordChangeForm
54                 return PasswordChangeForm(request.user).as_ext()
55         
56         @gilbert_method(form_handler=True)
57         def submit_passwd_form(self, request):
58                 from django.contrib.auth.forms import PasswordChangeForm
59                 form = PasswordChangeForm(request.user, data=request.POST)
60                 if form.is_valid():
61                         form.save()
62                         return {'success': True}
63                 else:
64                         return {'success': False, 'errors': form.errors}
65         
66         @gilbert_method
67         def whoami(self, request):
68                 user = request.user
69                 return user.get_full_name() or user.username
70
71
72 class GilbertSite(object):
73         version = gilbert_version
74         
75         def __init__(self, namespace='gilbert', app_name='gilbert', title='Gilbert'):
76                 self.namespace = namespace
77                 self.app_name = app_name
78                 self.title = title
79                 self.model_registry = SortedDict()
80                 self.plugin_registry = SortedDict()
81                 self.register_plugin(GilbertAuthPlugin)
82         
83         def register_plugin(self, plugin):
84                 if plugin.name in self.plugin_registry:
85                         raise AlreadyRegistered('A plugin named \'%s\' is already registered' % plugin.name)
86                 self.plugin_registry[plugin.name] = plugin(self)
87         
88         def register_model(self, model_or_iterable, admin_class=GilbertModelAdmin, **admin_attrs):
89                 if isinstance(model_or_iterable, ModelBase):
90                         model_or_iterable = [model_or_iterable]
91                 for model in model_or_iterable:
92                         if model._meta.app_label not in self.model_registry:
93                                 self.model_registry[model._meta.app_label] = SortedDict()
94                         if model._meta.object_name in self.model_registry[model._meta.app_label]:
95                                 raise AlreadyRegistered('The model %s.%s is already registered' % (model._meta.app_label, model.__name__))
96                         if admin_attrs:
97                                 admin_attrs['__module__'] = __name__
98                                 admin_class = type('%sAdmin' % model.__name__, (admin_class,), admin_attrs)
99                         self.model_registry[model._meta.app_label][model._meta.object_name] = admin_class(self, model)
100         
101         def has_permission(self, request):
102                 return request.user.is_active and request.user.is_staff
103         
104         @property
105         def urls(self):
106                 urlpatterns = patterns('',
107                         url(r'^$', self.index, name='index'),
108                         url(r'^css$', self.css, name='css'),
109                         url(r'^api$', self.api, name='api'),
110                         url(r'^router/?$', self.router, name='router'),
111                         url(r'^router/models/(?P<app_label>\w+)/?$', self.router, name='models'),
112                         url(r'^login$', self.router, name='login'),
113                 )
114                 
115                 return (urlpatterns, self.app_name, self.namespace)
116         
117         def request_context(self, request, extra_context=None):
118                 from django.template import RequestContext
119                 context = RequestContext(request, current_app=self.namespace)
120                 context.update(extra_context or {})
121                 context.update({'gilbert': self, 'user': request.user, 'logged_in': self.has_permission(request)})
122                 return context
123         
124         @never_cache
125         def index(self, request, extra_context=None):
126                 return render_to_response('gilbert/index.html', context_instance=self.request_context(request, extra_context))
127         
128         def css(self, request, extra_context=None):
129                 icon_names = []
130                 for plugin in self.plugin_registry.values():
131                         icon_names.extend(plugin.fugue_icons)
132                 
133                 icons = dict([(icon_name, staticmedia.url('gilbert/fugue-icons/icons/%s.png' % icon_name)) for icon_name in set(icon_names)])
134                 
135                 context = extra_context or {}
136                 context.update({'icons': icons})
137                 
138                 return render_to_response('gilbert/styles.css', context_instance=self.request_context(request, context), mimetype='text/css')
139         
140         @never_cache
141         def api(self, request, extra_context=None):
142                 providers = []
143                 for app_label, models in self.model_registry.items():
144                         app_provider = {
145                                 'namespace': 'Gilbert.api.models.%s' % app_label,
146                                 'url': reverse('%s:models' % self.namespace, current_app=self.app_name, kwargs={'app_label': app_label}),
147                                 'type': 'remoting',
148                         }
149                         model_actions = {}
150                         for model_name, admin in models.items():
151                                 model_methods = []
152                                 for method in [admin.get_method(method_name) for method_name in admin.methods]:
153                                         if method.restricted and not self.has_permission(request):
154                                                 continue
155                                         model_methods.append({
156                                                 'name': method.name,
157                                                 'len': method.argc,
158                                                 'formHandler': method.form_handler,
159                                         })
160                                 if model_methods:
161                                         model_actions[model_name] = model_methods
162                         if model_actions:
163                                 app_provider['actions'] = model_actions
164                                 providers.append(app_provider)
165                 
166                 plugin_provider = {
167                         'namespace': 'Gilbert.api',
168                         'url': reverse('%s:router' % self.namespace, current_app=self.app_name),
169                         'type': 'remoting',
170                 }
171                 plugin_actions = {}
172                 for plugin_name, plugin in self.plugin_registry.items():
173                         plugin_methods = []
174                         for method in [plugin.get_method(method_name) for method_name in plugin.methods]:
175                                 if method.restricted and not self.has_permission(request):
176                                         continue
177                                 plugin_methods.append({
178                                         'name': method.name,
179                                         'len': method.argc,
180                                         'formHandler': method.form_handler,
181                                 })
182                         if plugin_methods:
183                                 plugin_actions[plugin_name] = plugin_methods
184                 if plugin_actions:
185                         plugin_provider['actions'] = plugin_actions
186                         providers.append(plugin_provider)
187                 
188                 return HttpResponse(''.join(['Ext.Direct.addProvider('+json.dumps(provider, separators=(',', ':'))+');' for provider in providers]), mimetype='text/javascript')
189         
190         def router(self, request, app_label=None, extra_context=None):
191                 submitted_form = False
192                 if request.META['CONTENT_TYPE'].startswith('application/x-www-form-urlencoded'):
193                         submitted_form = True
194                 
195                 if submitted_form:
196                         ext_request = {
197                                 'action': request.POST.get('extAction'),
198                                 'method': request.POST.get('extMethod'),
199                                 'type': request.POST.get('extType'),
200                                 'tid': request.POST.get('extTID'),
201                                 'upload': request.POST.get('extUpload', False),
202                                 'data': None,
203                         }
204                         response = self.handle_ext_request(request, ext_request, app_label)
205                 else:
206                         ext_requests = json.loads(request.raw_post_data)
207                         if type(ext_requests) is dict:
208                                 ext_requests['upload'] = False
209                                 response = self.handle_ext_request(request, ext_requests, app_label)
210                         else:
211                                 responses = []
212                                 for ext_request in ext_requests:
213                                         ext_request['upload'] = False
214                                         responses.append(self.handle_ext_request(request, ext_request, app_label))
215                                 response = responses
216                 
217                 if submitted_form:
218                         if ext_request['upload'] is True:
219                                 return HttpResponse(('<html><body><textarea>%s</textarea></body></html>' % json.dumps(response)))
220                 return HttpResponse(json.dumps(response), content_type=('application/json; charset=%s' % settings.DEFAULT_CHARSET))
221         
222         def handle_ext_request(self, request, ext_request, app_label=None):
223                 try:
224                         plugin = None
225                         
226                         if app_label is not None:
227                                 try:
228                                         plugin = self.model_registry[app_label][ext_request['action']]
229                                 except KeyError:
230                                         raise NotImplementedError('A model named \'%s\' has not been registered' % ext_request['action'])
231                         else:
232                                 try:
233                                         plugin = self.plugin_registry[ext_request['action']]
234                                 except KeyError:
235                                         raise NotImplementedError('Gilbert does not provide a class named \'%s\'' % ext_request['action'])
236                         
237                         method = plugin.get_method(ext_request['method'])
238                         
239                         if method is None or (method.restricted and not self.has_permission(request)):
240                                 raise NotImplementedError('The method named \'%s\' is not available' % method.name)
241                         
242                         return {'type': 'rpc', 'tid': ext_request['tid'], 'action': ext_request['action'], 'method': ext_request['method'], 'result': method(request, *(ext_request['data'] or []))}
243                 except:
244                         exc_type, exc_value, exc_traceback = sys.exc_info()
245                         return {'type': 'exception', 'tid': ext_request['tid'], 'message': ('%s: %s' % (exc_type, exc_value)), 'where': format_tb(exc_traceback)[0]}
246
247
248 site = GilbertSite()