Merge branch 'release' into gilbert
[philo.git] / contrib / gilbert / sites.py
1 from django.conf import settings
2 from django.contrib.auth import authenticate, login
3 from django.core.context_processors import csrf
4 from django.conf.urls.defaults import url, patterns, include
5 from django.core.urlresolvers import reverse
6 from django.db.models.base import ModelBase
7 from django.forms.models import model_to_dict
8 from django.http import HttpResponse, HttpResponseRedirect
9 from django.shortcuts import render_to_response
10 from django.template import RequestContext
11 from django.utils import simplejson as json
12 from django.utils.datastructures import SortedDict
13 from django.views.decorators.cache import never_cache
14 from philo.utils import fattr
15 from . import __version__ as gilbert_version
16 from .exceptions import AlreadyRegistered, NotRegistered
17 from .extdirect import ExtAction, ExtRouter
18 from .plugins.auth import Auth
19 from .plugins.models import Models, ModelAdmin
20 from inspect import getargspec
21 from functools import partial, update_wrapper
22 import sys, os, datetime
23
24
25
26 __all__ = ('GilbertSite', 'site')
27
28
29 class CoreRouter(ExtRouter):
30         def __init__(self, site):
31                 self.site = site
32                 self._actions = {}
33         
34         @property
35         def namespace(self):
36                 return 'Gilbert.api.plugins'
37         
38         @property
39         def url(self):
40                 return reverse('%s:router' % self.site.namespace, current_app=self.site.app_name)
41         
42         @property
43         def type(self):
44                 return 'remoting'
45         
46         @property
47         def actions(self):
48                 return self._actions
49         
50         @property
51         def plugins(self):
52                 return list(action.obj for action in self._actions.itervalues())
53         
54         def register_plugin(self, plugin):
55                 action = ExtAction(plugin)
56                 self._actions[action.name] = action
57
58
59 class ModelRouter(ExtRouter):
60         def __init__(self, site, app_label):
61                 self.site = site
62                 self.app_label = app_label
63                 self._actions = {}
64         
65         @property
66         def namespace(self):
67                 return 'Gilbert.api.models.%s' % self.app_label
68         
69         @property
70         def url(self):
71                 return reverse('%s:model_router' % self.site.namespace, current_app=self.site.app_name, kwargs={'app_label': self.app_label})
72         
73         @property
74         def type(self):
75                 return 'remoting'
76         
77         @property
78         def actions(self):
79                 return self._actions
80         
81         @property
82         def models(self):
83                 return dict((name, action.obj) for name, action in self._actions.iteritems())
84         
85         def register_admin(self, name, admin):
86                 action = ExtAction(admin)
87                 action.name = name
88                 self._actions[action.name] = action
89
90
91 class GilbertSite(object):
92         version = gilbert_version
93         
94         def __init__(self, namespace='gilbert', app_name='gilbert', title=None):
95                 self.namespace = namespace
96                 self.app_name = app_name
97                 if title is None:
98                         self.title = getattr(settings, 'GILBERT_TITLE', 'Gilbert')
99                 else:
100                         self.title = title
101                 
102                 self.core_router = CoreRouter(self)
103                 self.model_routers = SortedDict()
104                 
105                 self.register_plugin(Models)
106                 self.register_plugin(Auth)
107         
108         def register_plugin(self, plugin):
109                 self.core_router.register_plugin(plugin(self))
110         
111         def register_model(self, model_or_iterable, admin_class=ModelAdmin, **admin_attrs):
112                 if isinstance(model_or_iterable, ModelBase):
113                         model_or_iterable = [model_or_iterable]
114                 for model in model_or_iterable:
115                         app_label = model._meta.app_label
116                         name = model._meta.object_name
117                         
118                         if app_label not in self.model_routers:
119                                 self.model_routers[app_label] = ModelRouter(self, app_label)
120                         router = self.model_routers[app_label]
121                         
122                         if admin_attrs:
123                                 admin_attrs['__module__'] = __name__
124                                 admin_class = type('%sAdmin' % model.__name__, (admin_class,), admin_attrs)
125                         
126                         router.register_admin(name, admin_class(self, model))
127         
128         def has_permission(self, request):
129                 return request.user.is_active and request.user.is_staff
130         
131         def protected_view(self, view, login_page=True, cacheable=False):
132                 def inner(request, *args, **kwargs):
133                         if not self.has_permission(request):
134                                 if login_page:
135                                         return self.login(request)
136                                 else:
137                                         return HttpResponse(status=403)
138                         return view(request, *args, **kwargs)
139                 if not cacheable:
140                         inner = never_cache(inner)
141                 return update_wrapper(inner, view)
142         
143         @property
144         def urls(self):
145                 urlpatterns = patterns('',
146                         url(r'^$', self.protected_view(self.index), name='index'),
147                         url(r'^api.js$', self.protected_view(self.api, login_page=False), name='api'),
148                         url(r'^icons.css$', self.protected_view(self.icons, login_page=False), name='icons'),
149                         url(r'^router$', self.protected_view(self.router, login_page=False), name='router'),
150                         url(r'^router/models/(?P<app_label>\w+)$', self.protected_view(self.router, login_page=False), name='model_router'),
151                 )
152                 return (urlpatterns, self.app_name, self.namespace)
153         
154         def login(self, request):
155                 context = {
156                         'gilbert': self,
157                         'form_url': request.get_full_path(),
158                 }
159                 context.update(csrf(request))
160                 
161                 if request.POST:
162                         if request.session.test_cookie_worked():
163                                 request.session.delete_test_cookie()
164                                 username = request.POST.get('username', None)
165                                 password = request.POST.get('password', None)
166                                 user = authenticate(username=username, password=password)
167                                 if user is not None:
168                                         if user.is_active and user.is_staff:
169                                                 login(request, user)
170                                                 return HttpResponseRedirect(request.get_full_path())
171                                         else:
172                                                 context.update({
173                                                         'error_message_short': 'Not staff',
174                                                         'error_message': 'You do not have access to this page.',
175                                                 })
176                                 else:
177                                         context.update({
178                                                 'error_message_short': 'Invalid credentials',
179                                                 'error_message': 'Unable to authenticate using the provided credentials. Please try again.',
180                                         })
181                         else:
182                                 context.update({
183                                         'error_message_short': 'Cookies disabled',
184                                         'error_message': 'Please enable cookies, reload this page, and try logging in again.',
185                                 })
186                 
187                 request.session.set_test_cookie()
188                 return render_to_response('gilbert/login.html', context, context_instance=RequestContext(request))
189         
190         def index(self, request):
191                 return render_to_response('gilbert/index.html', {
192                         'gilbert': self,
193                         'plugins': self.core_router.plugins # needed as the template language will not traverse callables
194                 }, context_instance=RequestContext(request))
195         
196         def api(self, request):
197                 providers = []
198                 model_registry = {}
199                 
200                 for app_label, router in self.model_routers.items():
201                         if request.user.has_module_perms(app_label):
202                                 providers.append(router.spec)
203                                 model_registry[app_label] = dict((model_name, admin) for model_name, admin in router.models.items() if admin.has_permission(request))
204                 
205                 providers.append(self.core_router.spec)
206                 
207                 context = {
208                         'gilbert': self,
209                         'providers': [json.dumps(provider, separators=(',', ':')) for provider in providers],
210                         'model_registry': model_registry,
211                 }
212                 context.update(csrf(request))
213                 
214                 return render_to_response('gilbert/api.js', context, mimetype='text/javascript')
215         
216         def icons(self, request):
217                 icon_names = []
218                 
219                 for plugin in self.core_router.plugins:
220                         icon_names.extend(plugin.icon_names)
221                 
222                 for router in self.model_routers.values():
223                         for admin in router.models.values():
224                                 icon_names.extend(admin.icon_names)
225                 
226                 return render_to_response('gilbert/icons.css', {
227                         'icon_names': set(icon_names),
228                         'STATIC_URL': settings.STATIC_URL
229                 }, mimetype='text/css')
230         
231         def router(self, request, app_label=None, extra_context=None):
232                 if app_label is None:
233                         return self.core_router.render_to_response(request)
234                 else:
235                         return self.model_routers[app_label].render_to_response(request)
236
237
238 site = GilbertSite()