From bb20a44272e169044715720cdef92380a63c38e4 Mon Sep 17 00:00:00 2001 From: Michael Ziegler Date: Sat, 3 Jul 2010 11:18:12 +0200 Subject: [PATCH] implement converting Django forms to JS and feeding them with data. todo: submit --- pyweb/extdirect.py | 173 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 162 insertions(+), 11 deletions(-) diff --git a/pyweb/extdirect.py b/pyweb/extdirect.py index 9ef83fc..8a656d3 100644 --- a/pyweb/extdirect.py +++ b/pyweb/extdirect.py @@ -21,12 +21,14 @@ import functools import traceback from sys import stderr +from django import forms from django.http import HttpResponse from django.conf import settings -from django.conf.urls.defaults import patterns +from django.conf.urls.defaults import patterns, url from django.core.urlresolvers import reverse from django.utils.datastructures import MultiValueDictKeyError from django.views.decorators.csrf import csrf_exempt +from django.utils.safestring import mark_safe def getname( cls_or_name ): @@ -34,6 +36,33 @@ def getname( cls_or_name ): return cls_or_name.__name__ return cls_or_name + +# Template used for the auto-generated form classes +EXT_CLASS_TEMPLATE = """ +Ext.namespace('Ext.ux'); + +Ext.ux.%(clsname)s = function( config ){ + Ext.apply( this, config ); + + var defaultconf = %(defaultconf)s; + + Ext.applyIf( this, defaultconf ); + this.initialConfig = defaultconf; + + Ext.ux.%(clsname)s.superclass.constructor.call( this ); + + this.form.api = %(apiconf)s; + this.form.paramsAsHash = true; +} + +Ext.extend( Ext.ux.%(clsname)s, Ext.form.FormPanel, {} ); + +Ext.reg( '%(clslowername)s', Ext.ux.%(clsname)s ); +""" +# About the this.form.* lines, see +# http://www.sencha.com/forum/showthread.php?96001-solved-Ext.Direct-load-data-in-extended-Form-fails-%28scope-issue%29 + + class Provider( object ): """ Provider for Ext.Direct. This class handles building API information and routing requests to the appropriate functions, and serializing their @@ -83,26 +112,52 @@ class Provider( object ): self.name = name self.autoadd = autoadd self.classes = {} + self.forms = {} - def register_method( self, cls_or_name ): + def register_method( self, cls_or_name, flags={} ): """ Return a function that takes a method as an argument and adds that to cls_or_name. + The flags parameter is for additional information, e.g. formHandler=True. + Note: This decorator does not replace the method by a new function, it returns the original function as-is. """ + return functools.partial( self._register_method, cls_or_name, flags=flags ) + + def _register_method( self, cls_or_name, method, flags={} ): + """ Actually registers the given function as a method of cls_or_name. """ clsname = getname(cls_or_name) if clsname not in self.classes: self.classes[clsname] = {} - return functools.partial( self._register_method, cls_or_name ) - - def _register_method( self, cls_or_name, method ): - """ Actually registers the given function as a method of cls_or_name. """ - self.classes[ getname(cls_or_name) ][ method.__name__ ] = method + self.classes[ clsname ][ method.__name__ ] = method method.EXT_argnames = inspect.getargspec( method ).args[1:] method.EXT_len = len( method.EXT_argnames ) + method.EXT_flags = flags return method + def register_form( self, formclass ): + """ Register a Django Form class for handling teh stuffz, yoo know. """ + formname = formclass.__name__.lower() + self.forms[formname] = formclass + + getfunc = functools.partial( self.get_form_data, formname ) + getfunc.EXT_len = 1 + getfunc.EXT_argnames = ["pk"] + getfunc.EXT_flags = {} + + updatefunc = functools.partial( self.update_form_data, formname ) + updatefunc.EXT_len = 1 + updatefunc.EXT_argnames = ["pk"] + updatefunc.EXT_flags = { 'formHandler': True } + + self.classes["XD_%s"%formclass.__name__] = { + "get": getfunc, + "update": updatefunc, + } + + return formclass + @csrf_exempt def get_api( self, request ): """ Introspect the methods and get a JSON description of this API. """ @@ -110,10 +165,12 @@ class Provider( object ): for clsname in self.classes: actdict[clsname] = [] for methodname in self.classes[clsname]: - actdict[clsname].append( { + methinfo = { "name": methodname, "len": self.classes[clsname][methodname].EXT_len - } ) + } + methinfo.update( self.classes[clsname][methodname].EXT_flags ) + actdict[clsname].append( methinfo ) lines = ["%s = %s;" % ( self.name, simplejson.dumps({ "url": reverse( self.request ), @@ -137,10 +194,14 @@ class Provider( object ): rawjson = [{ 'action': request.POST['extAction'], 'method': request.POST['extMethod'], - 'data': request.POST['extData'], 'type': request.POST['extType'], 'tid': request.POST['extTID'], + 'data': {} }] + for fld in request.POST: + if not fld.startswith( "ext" ): + rawjson[0]['data'][fld] = request.POST[fld] + except (MultiValueDictKeyError, KeyError): rawjson = simplejson.loads( request.raw_post_data ) if not isinstance( rawjson, list ): @@ -221,10 +282,100 @@ class Provider( object ): else: return HttpResponse( simplejson.dumps( responses ), mimetype="text/javascript" ) + def get_form( self, request, formname ): + """ Convert the form given in "formname" to an ExtJS FormPanel. """ + + items = [] + clsname = self.forms[formname].__name__ + + for fldname in self.forms[formname].base_fields: + field = self.forms[formname].base_fields[fldname] + extfld = { + "fieldLabel": field.label is not None and unicode(field.label) or fldname, + "name": fldname, + "xtype": "textfield", + #"allowEmpty": field.required, + } + + if hasattr( field, "choices" ) and field.choices: + extfld.update({ + "name": fldname, + "hiddenName": fldname, + "xtype": "combo", + "store": field.choices, + "typeAhead": True, + "emptyText": 'Select...', + "triggerAction": 'all', + "selectOnFocus": True, + }) + elif isinstance( field, forms.BooleanField ): + extfld.update({ + "xtype": "checkbox" + }) + elif isinstance( field, forms.IntegerField ): + extfld.update({ + "xtype": "numberfield", + }) + elif isinstance( field, forms.FileField ) or isinstance( field, forms.ImageField ): + extfld.update({ + "xtype": "textfield", + "inputType": "file" + }) + elif isinstance( field.widget, forms.Textarea ): + extfld.update({ + "xtype": "textarea", + }) + elif isinstance( field.widget, forms.PasswordInput ): + extfld.update({ + "xtype": "textfield", + "inputType": "password" + }) + + items.append( extfld ) + + if field.help_text: + items.append({ + "xtype": "label", + "text": unicode(field.help_text), + "cls": "form_hint_label", + }) + + clscode = EXT_CLASS_TEMPLATE % { + 'clsname': clsname, + 'clslowername': formname, + 'defaultconf': simplejson.dumps({ + 'items': items, + 'defaults': { "anchor": "-20px" }, + 'paramsAsHash': True, + }, indent=4 ), + 'apiconf': ('{' + 'load: ' + ("XD_%s.get" % clsname) + "," + 'submit:' + ("XD_%s.update" % clsname) + "," + "}"), + } + + return HttpResponse( mark_safe( clscode ), mimetype="text/javascript" ) + + def get_form_data( self, formname, request, pk ): + formcls = self.forms[formname] + instance = formcls.Meta.model.objects.get( pk=pk ) + forminst = formcls( instance=instance ) + data = {} + for fld in forminst.fields: + data[fld] = getattr( instance, fld ) + return { 'data': data, 'success': True } + + def update_form_data( self, formname, request, *args ): + print args + return True + @property def urls(self): """ Return the URL patterns. """ - return patterns('', + pat = patterns('', (r'api.js$', self.get_api ), (r'router/?', self.request ), ) + if self.forms: + pat.append( url( r'(?P\w+).js$', self.get_form ) ) + return pat