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