Initial commit of Gilbert, an advanced Django administration interface.
authorJoseph Spiros <joseph.spiros@ithinksw.com>
Sat, 21 Aug 2010 08:22:31 +0000 (04:22 -0400)
committerJoseph Spiros <joseph.spiros@ithinksw.com>
Tue, 24 Aug 2010 13:25:06 +0000 (09:25 -0400)
13 files changed:
.gitmodules [new file with mode: 0644]
contrib/gilbert/__init__.py [new file with mode: 0644]
contrib/gilbert/exceptions.py [new file with mode: 0644]
contrib/gilbert/media/gilbert/extjs [new submodule]
contrib/gilbert/media/gilbert/wallpaper.README [new file with mode: 0644]
contrib/gilbert/media/gilbert/wallpaper.jpg [new file with mode: 0644]
contrib/gilbert/options.py [new file with mode: 0644]
contrib/gilbert/sites.py [new file with mode: 0644]
contrib/gilbert/templates/gilbert/api.js [new file with mode: 0644]
contrib/gilbert/templates/gilbert/base.html [new file with mode: 0644]
contrib/gilbert/templates/gilbert/css.css [new file with mode: 0644]
contrib/gilbert/templates/gilbert/index.html [new file with mode: 0644]
contrib/gilbert/utils.py [new file with mode: 0644]

diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..dfdaa1d
--- /dev/null
@@ -0,0 +1,3 @@
+[submodule "contrib/gilbert/media/gilbert/extjs"]
+       path = contrib/gilbert/media/gilbert/extjs
+       url = git://github.com/probonogeek/extjs.git
diff --git a/contrib/gilbert/__init__.py b/contrib/gilbert/__init__.py
new file mode 100644 (file)
index 0000000..d009571
--- /dev/null
@@ -0,0 +1,21 @@
+from philo.contrib.gilbert.sites import GilbertSite, site
+
+
+def autodiscover():
+       import copy
+       from django.conf import settings
+       from django.utils.importlib import import_module
+       from django.utils.module_loading import module_has_submodule
+       
+       for app in settings.INSTALLED_APPS:
+               mod = import_module(app)
+               try:
+                       before_import_model_registry = copy.copy(site.model_registry)
+                       before_import_plugin_registry = copy.copy(site.plugin_registry)
+                       import_module('%s.gilbert' % app)
+               except:
+                       site.model_registry = before_import_model_registry
+                       site.plugin_registry = before_import_plugin_registry
+                       
+                       if module_has_submodule(mod, 'gilbert'):
+                               raise
\ No newline at end of file
diff --git a/contrib/gilbert/exceptions.py b/contrib/gilbert/exceptions.py
new file mode 100644 (file)
index 0000000..e96ba25
--- /dev/null
@@ -0,0 +1,6 @@
+class AlreadyRegistered(Exception):
+       pass
+
+
+class NotRegistered(Exception):
+       pass
\ No newline at end of file
diff --git a/contrib/gilbert/media/gilbert/extjs b/contrib/gilbert/media/gilbert/extjs
new file mode 160000 (submodule)
index 0000000..530ef4b
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit 530ef4b6c5b943cfa68b779d11cf7de29aa878bf
diff --git a/contrib/gilbert/media/gilbert/wallpaper.README b/contrib/gilbert/media/gilbert/wallpaper.README
new file mode 100644 (file)
index 0000000..a14fe2e
--- /dev/null
@@ -0,0 +1 @@
+The source of the default wallpaper.jpg is http://webtreats.mysitemyway.com/tileable-classic-nebula-space-patterns/
\ No newline at end of file
diff --git a/contrib/gilbert/media/gilbert/wallpaper.jpg b/contrib/gilbert/media/gilbert/wallpaper.jpg
new file mode 100644 (file)
index 0000000..6188914
Binary files /dev/null and b/contrib/gilbert/media/gilbert/wallpaper.jpg differ
diff --git a/contrib/gilbert/options.py b/contrib/gilbert/options.py
new file mode 100644 (file)
index 0000000..fbdd4e3
--- /dev/null
@@ -0,0 +1,57 @@
+from philo.contrib.gilbert.utils import gilbert_method, is_gilbert_method, is_gilbert_class
+
+
+class GilbertClassBase(type):
+       def __new__(cls, name, bases, attrs):
+               if 'gilbert_class' not in attrs:
+                       attrs['gilbert_class'] = True
+               if 'gilbert_class_name' not in attrs:
+                       attrs['gilbert_class_name'] = name
+               if 'gilbert_class_methods' not in attrs:
+                       gilbert_class_methods = {}
+                       for attr in attrs.values():
+                               if is_gilbert_method(attr):
+                                       gilbert_class_methods[attr.gilbert_method_name] = attr
+                       attrs['gilbert_class_methods'] = gilbert_class_methods
+               return super(GilbertClassBase, cls).__new__(cls, name, bases, attrs)
+
+
+class GilbertClass(object):
+       __metaclass__ = GilbertClassBase
+
+
+class GilbertPluginBase(type):
+       def __new__(cls, name, bases, attrs):
+               if 'gilbert_plugin' not in attrs:
+                       attrs['gilbert_plugin'] = True
+               if 'gilbert_plugin_name' not in attrs:
+                       attrs['gilbert_plugin_name'] = name
+               if 'gilbert_plugin_classes' not in attrs:
+                       gilbert_plugin_classes = {}
+                       for attr_name, attr in attrs.items():
+                               if is_gilbert_class(attr):
+                                       gilbert_plugin_classes[attr_name] = attr
+                       attrs['gilbert_plugin_classes'] = gilbert_plugin_classes
+               return super(GilbertPluginBase, cls).__new__(cls, name, bases, attrs)
+
+
+class GilbertPlugin(object):
+       __metaclass__ = GilbertPluginBase
+       
+       def __init__(self, site):
+               self.site = site
+
+
+class GilbertModelAdmin(GilbertClass):
+       def __init__(self, site, model):
+               self.site = site
+               self.model = model
+               self.gilbert_class_name = model._meta.object_name
+       
+       @gilbert_method
+       def all(self):
+               return list(self.model._default_manager.all().values())
+       
+       @gilbert_method
+       def get(self, constraint):
+               return self.model._default_manager.all().values().get(**constraint)
\ No newline at end of file
diff --git a/contrib/gilbert/sites.py b/contrib/gilbert/sites.py
new file mode 100644 (file)
index 0000000..76c1d9a
--- /dev/null
@@ -0,0 +1,171 @@
+from django.contrib.admin.sites import AdminSite
+from django.contrib.auth import authenticate, login, logout
+from django.conf.urls.defaults import url, patterns
+from django.core.urlresolvers import reverse
+from django.shortcuts import render_to_response
+from django.conf import settings
+from django.utils import simplejson as json
+from django.utils.datastructures import SortedDict
+from django.http import HttpResponse
+from django.db.models.base import ModelBase
+from philo.utils import fattr
+from philo.contrib.gilbert.options import GilbertModelAdmin, GilbertPlugin, GilbertClass
+from philo.contrib.gilbert.exceptions import AlreadyRegistered, NotRegistered
+from django.forms.models import model_to_dict
+import sys
+from traceback import format_tb
+from inspect import getargspec
+from philo.contrib.gilbert.utils import is_gilbert_plugin, is_gilbert_class, is_gilbert_method, gilbert_method, call_gilbert_method
+
+
+__all__ = ('GilbertSite', 'site')
+
+
+class GilbertSitePlugin(GilbertPlugin):
+       class auth(GilbertClass):
+               @gilbert_method(restricted=False)
+               def login(self, request, username, password):
+                       user = authenticate(username=username, password=password)
+                       if user is not None and user.is_active:
+                               login(request, user)
+                               return True
+                       else:
+                               return False
+               
+               @gilbert_method(restricted=False)
+               def logout(self, request):
+                       logout(request)
+                       return True
+               
+               @gilbert_method
+               def passwd(self, request, current_password, new_password, new_password_confirm):
+                       user = request.user
+                       if user.check_password(current_password) and (new_password == new_password_confirm):
+                               user.set_password(new_password)
+                               user.save()
+                               return True
+                       return False
+
+
+class GilbertSite(object):
+       def __init__(self, namespace='gilbert', app_name='gilbert', title='Gilbert'):
+               self.namespace = namespace
+               self.app_name = app_name
+               self.title = title
+               self.core_api = GilbertSitePlugin(self)
+               self.model_registry = SortedDict()
+               self.plugin_registry = SortedDict()
+       
+       def register_plugin(self, plugin):
+               if is_gilbert_plugin(plugin):
+                       if plugin.gilbert_plugin_name in self.plugin_registry:
+                               raise AlreadyRegistered('A plugin named \'%s\' is already registered' % plugin.gilbert_plugin_name)
+                       self.plugin_registry[plugin.gilbert_plugin_name] = plugin(self)
+               else:
+                       raise ValueError('register_plugin must be provided a valid plugin class or instance')
+       
+       def register_model(self, model_or_iterable, admin_class=GilbertModelAdmin, **admin_attrs):
+               if isinstance(model_or_iterable, ModelBase):
+                       model_or_iterable = [model_or_iterable]
+               for model in model_or_iterable:
+                       if model._meta.app_label not in self.model_registry:
+                               self.model_registry[model._meta.app_label] = SortedDict()
+                       if model._meta.object_name in self.model_registry[model._meta.app_label]:
+                               raise AlreadyRegistered('The model %s is already registered' % model.__name__)
+                       if admin_attrs:
+                               admin_attrs['__module__'] = __name__
+                               admin_class = type('%sAdmin' % model.__name__, (admin_class,), admin_attrs)
+                       self.model_registry[model._meta.app_label][model._meta.object_name] = admin_class(self, model)
+       
+       def has_permission(self, request):
+               return request.user.is_active and request.user.is_staff
+       
+       @property
+       def urls(self):
+               return (patterns('',
+                       url(r'^$', self.index, name='index'),
+                       url(r'^css.css$', self.css, name='css'),
+                       url(r'^api.js$', self.api, name='api'),
+                       url(r'^router/?$', self.router, name='router'),
+                       url(r'^models/(?P<app_label>\w+)/?$', self.router, name='models'),
+                       url(r'^plugins/(?P<plugin_name>\w+)/?$', self.router, name='plugins'),
+                       url(r'^login$', self.router, name='login'),
+               ), self.app_name, self.namespace)
+       
+       def request_context(self, request, extra_context=None):
+               from django.template import RequestContext
+               context = RequestContext(request, current_app=self.namespace)
+               context.update(extra_context or {})
+               context.update({'gilbert': self, 'user': request.user, 'logged_in': self.has_permission(request)})
+               return context
+       
+       def index(self, request, extra_context=None):
+               return render_to_response('gilbert/index.html', context_instance=self.request_context(request, extra_context))
+       
+       def css(self, request, extra_context=None):
+               return render_to_response('gilbert/css.css', context_instance=self.request_context(request, extra_context), mimetype='text/css')
+       
+       def api(self, request, extra_context=None):
+               return render_to_response('gilbert/api.js', context_instance=self.request_context(request, extra_context), mimetype='text/javascript')
+       
+       def router(self, request, app_label=None, plugin_name=None, extra_context=None):
+               submitted_form = False
+               if request.META['CONTENT_TYPE'].startswith('application/x-www-form-urlencoded'):
+                       submitted_form = True
+               
+               if submitted_form:
+                       post_dict = dict(request.POST)
+                       ext_request = {
+                               'action': post_dict.pop('extAction'),
+                               'method': post_dict.pop('extMethod'),
+                               'type': post_dict.pop('extType'),
+                               'tid': post_dict.pop('extTID'),
+                               'data': None,
+                               'kwdata': post_dict,
+                       }
+                       if 'extUpload' in request.POST:
+                               ext_request['upload'] = request.POST['extUpload']
+               else:
+                       ext_request = json.loads(request.raw_post_data)
+                       ext_request['kwdata'] = None
+               
+               try:
+                       gilbert_class = None
+                       
+                       if app_label is not None:
+                               try:
+                                       gilbert_class = self.model_registry[app_label][ext_request['action']]
+                               except KeyError:
+                                       raise NotImplementedError('A model named \'%s\' has not been registered' % ext_request['action'])
+                       elif plugin_name is not None:
+                               try:
+                                       gilbert_plugin = self.plugin_registry[plugin_name]
+                               except KeyError:
+                                       raise NotImplementedError('A plugin named \'%s\' has not been registered' % plugin_name)
+                               try:
+                                       gilbert_class = gilbert_plugin.gilbert_plugin_classes[ext_request['action']]
+                               except KeyError:
+                                       raise NotImplementedError('The plugin named \'%s\' does not provide a class named \'%s\'' % (plugin_name, ext_request['action']))
+                       else:
+                               try:
+                                       gilbert_class = self.core_api.gilbert_plugin_classes[ext_request['action']]
+                               except KeyError:
+                                       raise NotImplementedError('Gilbert does not provide a class named \'%s\'' % ext_request['action'])
+                       
+                       try:
+                               method = gilbert_class.gilbert_class_methods[ext_request['method']]
+                       except KeyError:
+                               raise NotImplementedError('The class named \'%s\' does not implement a method named \'%\'' % (gilbert_class.gilbert_class_name, ext_request['method']))
+                       if method.gilbert_method_restricted and not self.has_permission(request):
+                               raise NotImplementedError('The method named \'%s\' is not available' % method.gilbert_method_name)
+                       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 {}))}
+               except:
+                       exc_type, exc_value, exc_traceback = sys.exc_info()
+                       response = {'type': 'exception', 'tid': ext_request['tid'], 'message': ('%s: %s' % (exc_type, exc_value)), 'where': format_tb(exc_traceback)[0]}
+               
+               if submitted_form:
+                       return HttpResponse(('<html><body><textarea>%s</textarea></body></html>' % json.dumps(response)))
+               return HttpResponse(json.dumps(response), content_type=('application/json; charset=%s' % settings.DEFAULT_CHARSET))
+
+
+site = GilbertSite()
\ No newline at end of file
diff --git a/contrib/gilbert/templates/gilbert/api.js b/contrib/gilbert/templates/gilbert/api.js
new file mode 100644 (file)
index 0000000..71aaab8
--- /dev/null
@@ -0,0 +1,265 @@
+{% load staticmedia %}
+
+Ext.Direct.addProvider({
+       'namespace': 'Gilbert.api',
+       'url': '{% url gilbert:router %}',
+       'type': 'remoting',
+       'actions': {{% for gilbert_class in gilbert.core_api.gilbert_plugin_classes.values %}
+               '{{ gilbert_class.gilbert_class_name }}': [{% for method in gilbert_class.gilbert_class_methods.values %}{
+                       'name': '{{ method.gilbert_method_name }}',
+                       'len': {{ method.gilbert_method_argc }}
+               },{% endfor %}],{% endfor %}
+       }
+});
+
+{% if not logged_in %}
+
+Ext.onReady(function() {
+       var login_form = new Ext.FormPanel({
+               frame: true,
+               bodyStyle: 'padding: 5px 5px 0',
+               items: [
+                       {
+                               fieldLabel: 'Username',
+                               name: 'username',
+                               xtype: 'textfield',
+                       },
+                       {
+                               fieldLabel: 'Password',
+                               name: 'password',
+                               xtype: 'textfield',
+                               inputType: 'password',
+                       }
+               ],
+               buttons: [
+                       {
+                               text: 'Login',
+                               handler: function(sender) {
+                                       // document.location.reload();
+                                       var the_form = login_form.getForm().el.dom;
+                                       var username = the_form[0].value;
+                                       var password = the_form[1].value;
+                                       Gilbert.api.auth.login(username, password, function(result) {
+                                               if (result) {
+                                                       document.location.reload();
+                                               } else {
+                                                       Ext.MessageBox.alert('Login failed', 'Unable to authenticate.', function() {
+                                                               login_form.getForm().reset();
+                                                       });
+                                               }
+                                       });
+                               }
+                       }
+               ],
+       });
+       var login_window = new Ext.Window({
+               title: 'Login',
+               closable: false,
+               width: 266,
+               height: 130,
+               layout: 'fit',
+               items: login_form,
+       });
+       login_window.show();
+});
+
+
+{% else %}
+
+Ext.ns('Gilbert', 'Gilbert.ui', 'Gilbert.models', 'Gilbert.plugins');
+
+{% for app_label, models in gilbert.model_registry.items %}Ext.Direct.addProvider({
+       'namespace': 'Gilbert.models.{{ app_label }}',
+       'url': '{% url gilbert:models app_label %}',
+       'type': 'remoting',
+       'actions': {{% for model_name, admin in models.items %}
+               '{{ model_name }}': [{% for method in admin.gilbert_class_methods.values %}{
+                               'name': '{{ method.gilbert_method_name }}',
+                               'len': {{ method.gilbert_method_argc }}
+                       },{% endfor %}],{% endfor %}
+       }
+});{% endfor %}
+{% for plugin in gilbert.plugin_registry.values %}Ext.Direct.addProvider({
+       'namespace': 'Gilbert.plugins.{{ plugin.gilbert_plugin_name }}',
+       'url': '{% url gilbert:plugins plugin.gilbert_plugin_name %}',
+       'type': 'remoting',
+       'actions': {{% for gilbert_class in plugin.gilbert_plugin_classes %}
+               '{{ gilbert_class.gilbert_class_name }}': [{% for method in gilbert_class.gilbert_class_methods.values %}{}
+                       'name': '{{ method.gilbert_method_name }}',
+                       'len': {{ method.gilbert_method_argc }}
+               },{% endfor %}],{% endfor %}
+       }
+});{% endfor %}
+
+Gilbert.ui.Application = function(cfg) {
+       Ext.apply(this, cfg, {
+               title: '{{ gilbert.title }}',
+       });
+       this.addEvents({
+               'ready': true,
+               'beforeunload': true,
+       });
+       Ext.onReady(this.initApplication, this);
+};
+
+Ext.extend(Gilbert.ui.Application, Ext.util.Observable, {
+       initApplication: function() {
+               
+               Ext.QuickTips.init();
+               
+               this.desktop = new Ext.Panel({
+                       region: 'center',
+                       border: false,
+                       padding: '5',
+                       bodyStyle: 'background: none;',
+               });
+               var desktop = this.desktop;
+               
+               this.toolbar = new Ext.Toolbar({
+                       region: 'north',
+                       autoHeight: true,
+                       items: [
+                       {
+                               xtype: 'tbtext',
+                               text: this.title,
+                               style: 'font-weight: bolder; font-size: larger; text-transform: uppercase;',
+                       },
+                       {
+                               xtype: 'tbseparator',
+                       }
+                       ]
+               });
+               var toolbar = this.toolbar;
+               
+               this.viewport = new Ext.Viewport({
+                       renderTo: Ext.getBody(),
+                       layout: 'border',
+                       items: [
+                       toolbar,
+                       desktop,
+                       ],
+               });
+               var viewport = this.viewport;
+               
+               var windows = new Ext.WindowGroup();
+               
+               this.createWindow = function(config, cls) {
+                       var win = new(cls || Ext.Window)(Ext.applyIf(config || {},
+                       {
+                               renderTo: desktop.el,
+                               manager: windows,
+                               constrainHeader: true,
+                               maximizable: true,
+                       }));
+                       win.render(desktop.el);
+                       return win;
+               };
+               var createWindow = this.createWindow;
+               
+               if (this.plugins) {
+                       for (var pluginNum = 0; pluginNum < this.plugins.length; pluginNum++) {
+                               this.plugins[pluginNum].initWithApp(this);
+                       };
+               };
+               
+               if (this.user) {
+                       var user = this.user;
+                       toolbar.add({ xtype: 'tbfill' });
+                       toolbar.add({ xtype: 'tbseparator' });
+                       toolbar.add({
+                               xtype: 'button',
+                               text: '<b>' + user + '</b>',
+                               style: 'font-weight: bolder !important; font-size: smaller !important; text-transform: uppercase !important;',
+                               menu: [
+                               {
+                                       text: 'Change password',
+                                       handler: function(button, event) {
+                                               var edit_window = createWindow({
+                                                       layout: 'fit',
+                                                       title: 'Change password',
+                                                       width: 266,
+                                                       height: 170,
+                                                       layout: 'fit',
+                                                       items: _change_password_form = new Ext.FormPanel({
+                                                               frame: true,
+                                                               bodyStyle: 'padding: 5px 5px 0',
+                                                               items: [
+                                                                       {
+                                                                               fieldLabel: 'Current password',
+                                                                               name: 'current_password',
+                                                                               xtype: 'textfield',
+                                                                               inputType: 'password',
+                                                                       },
+                                                                       {
+                                                                               fieldLabel: 'New password',
+                                                                               name: 'new_password',
+                                                                               xtype: 'textfield',
+                                                                               inputType: 'password',
+                                                                       },
+                                                                       {
+                                                                               fieldLabel: 'New password (confirm)',
+                                                                               name: 'new_password_confirm',
+                                                                               xtype: 'textfield',
+                                                                               inputType: 'password',
+                                                                       }
+                                                               ],
+                                                               buttons: [
+                                                                       {
+                                                                               text: 'Change password',
+                                                                               handler: function(sender) {
+                                                                                       // document.location.reload();
+                                                                                       var the_form = _change_password_form.getForm().el.dom;
+                                                                                       var current_password = the_form[0].value;
+                                                                                       var new_password = the_form[1].value;
+                                                                                       var new_password_confirm = the_form[2].value;
+                                                                                       Gilbert.api.auth.passwd(current_password, new_password, new_password_confirm, function(result) {
+                                                                                               if (result) {
+                                                                                                       Ext.MessageBox.alert('Password changed', 'Your password has been changed.');
+                                                                                               } else {
+                                                                                                       Ext.MessageBox.alert('Password unchanged', 'Unable to change your password.', function() {
+                                                                                                               _change_password_form.getForm().reset();
+                                                                                                       });
+                                                                                               }
+                                                                                       });
+                                                                               }
+                                                                       }
+                                                               ],
+                                                       }),
+                                               });
+                                               edit_window.show(this);
+                                       },
+                               },
+                               {
+                                       text: 'Log out',
+                                       handler: function(button, event) {
+                                               Gilbert.api.auth.logout(function(result) {
+                                                       if (result) {
+                                                               Ext.MessageBox.alert('Logout successful', 'You have been logged out.', function() {
+                                                                       document.location.reload();
+                                                               });
+                                                       } else {
+                                                               Ext.MessageBox.alert('Logout failed', 'A bit odd, you might say.');
+                                                       }
+                                               });
+                                       },
+                               },
+                               ],
+                       });
+               };
+               
+               toolbar.doLayout();
+               viewport.doLayout();
+       },
+});
+
+Ext.BLANK_IMAGE_URL = '{% mediaurl "gilbert/extjs/resources/images/default/s.gif" %}';
+
+Ext.onReady(function(){
+       Gilbert.Application = new Gilbert.ui.Application({
+               user: '{% filter force_escape %}{% firstof user.get_full_name user.username %}{% endfilter %}',
+               plugins: [{% for plugin in gilbert.plugin_registry.values %}{% if plugin.gilbert_plugin_javascript %}
+                       {{ plugin.gilbert_plugin_javascript|safe }},
+               {% endif %}{% endfor %}],
+       });
+});
+{% endif %}
\ No newline at end of file
diff --git a/contrib/gilbert/templates/gilbert/base.html b/contrib/gilbert/templates/gilbert/base.html
new file mode 100644 (file)
index 0000000..61a1c65
--- /dev/null
@@ -0,0 +1,21 @@
+{% load staticmedia %}<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>{% block head %}
+       <title>{% block title %}{{ gilbert.title }}{% endblock %}</title>
+       
+       {% block css %}
+       <link rel="stylesheet" type="text/css" href="{% mediaurl 'gilbert/extjs/resources/css/ext-all.css' %}" />
+       <link rel="stylesheet" type="text/css" href="{% mediaurl 'gilbert/extjs/resources/css/xtheme-gray.css' %}" />
+       <link rel="stylesheet" type="text/css" href="{% url gilbert:css %}" />
+       {% endblock %}
+       
+       {% block js %}
+       <script type="text/javascript" src="{% mediaurl 'gilbert/extjs/adapter/ext/ext-base.js' %}"></script>
+       <script type="text/javascript" src="{% mediaurl 'gilbert/extjs/ext-all.js' %}"></script>
+       <script type="text/javascript" src="{% url gilbert:api %}" id="api.js"></script>
+       {% endblock %}
+       
+{% endblock %}</head>
+<body style="background-image: url({% mediaurl 'gilbert/wallpaper.jpg' %});">{% block body %}{% endblock %}</body>
+</html>
diff --git a/contrib/gilbert/templates/gilbert/css.css b/contrib/gilbert/templates/gilbert/css.css
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/contrib/gilbert/templates/gilbert/index.html b/contrib/gilbert/templates/gilbert/index.html
new file mode 100644 (file)
index 0000000..a4d9290
--- /dev/null
@@ -0,0 +1 @@
+{% extends 'gilbert/base.html' %}
\ No newline at end of file
diff --git a/contrib/gilbert/utils.py b/contrib/gilbert/utils.py
new file mode 100644 (file)
index 0000000..0e7b071
--- /dev/null
@@ -0,0 +1,52 @@
+from inspect import isclass, getargspec
+
+
+def is_gilbert_plugin(class_or_instance):
+       from philo.contrib.gilbert.options import GilbertPluginBase, GilbertPlugin
+       return (isclass(class_or_instance) and issubclass(class_or_instance, GilbertPlugin)) or isinstance(class_or_instance, GilbertPlugin) or (getattr(class_or_instance, '__metaclass__', None) is GilbertPluginBase) or (getattr(class_or_instance, 'gilbert_plugin', False) and (getattr(class_or_instance, 'gilbert_plugin_name', None) is not None) and (getattr(class_or_instance, 'gilbert_plugin_classes', None) is not None))
+
+
+def is_gilbert_class(class_or_instance):
+       from philo.contrib.gilbert.options import GilbertClassBase, GilbertClass
+       return (isclass(class_or_instance) and issubclass(class_or_instance, GilbertClass)) or isinstance(class_or_instance, GilbertClass) or (getattr(class_or_instance, '__metaclass__', None) is GilbertClassBase) or (getattr(class_or_instance, 'gilbert_class', False) and (getattr(class_or_instance, 'gilbert_class_name', None) is not None) and (getattr(class_or_instance, 'gilbert_class_methods', None) is not None))
+
+
+def is_gilbert_method(function):
+       return getattr(function, 'gilbert_method', False)
+
+
+def gilbert_method(function=None, name=None, argc=None, restricted=True):
+       def wrapper(function):
+               setattr(function, 'gilbert_method', True)
+               setattr(function, 'gilbert_method_name', name or function.__name__)
+               setattr(function, 'gilbert_method_restricted', restricted)
+               new_argc = argc
+               if new_argc is None:
+                       args = getargspec(function)[0]
+                       new_argc = len(args)
+                       if new_argc > 0:
+                               if args[0] == 'self':
+                                       args = args[1:]
+                                       new_argc = new_argc - 1
+                               if new_argc > 0:
+                                       if args[0] == 'request':
+                                               args = args[1:]
+                                               new_argc = new_argc - 1
+               setattr(function, 'gilbert_method_argc', new_argc)
+               return function
+       if function is not None:
+               return wrapper(function)
+       return wrapper
+
+
+def call_gilbert_method(method, cls, request, *args, **kwargs):
+       arg_list = getargspec(method)[0]
+       if len(arg_list) > 0:
+               if arg_list[0] == 'self':
+                       if len(arg_list) > 1 and arg_list[1] == 'request':
+                               return method(cls, request, *args, **kwargs)
+                       return method(cls, *args, **kwargs)
+               elif arg_list[0] == 'request':
+                       return method(request, *args, **kwargs)
+       else:
+               return method(*args, **kwargs)
\ No newline at end of file