Added a DynaForm module
authorSverre Rabbelier <srabbelier@gmail.com>
Fri, 28 Nov 2008 22:49:03 +0000
changeset 602 1caee6675fa7
parent 601 988f8a8cd6de
child 603 8ce6268a37bc
Added a DynaForm module This allows GAE forms to be defined 'semi' dymamically, rather than by having to explicitly create the new class. Patch by: Sverre Rabbelier
app/soc/views/helper/dynaform.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/views/helper/dynaform.py	Fri Nov 28 22:49:03 2008 +0000
@@ -0,0 +1,138 @@
+#!/usr/bin/python2.5
+#
+# Copyright 2008 the Melange authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""This module defines classes and functions for Dynamic Forms
+"""
+
+__authors__ = [
+    '"Sverre Rabbelier" <sverre@rabbelier.nl>',
+    ]
+
+
+from google.appengine.ext.db import djangoforms
+
+from soc.logic import dicts
+
+
+class DynaFormMetaclass(djangoforms.ModelFormMetaclass):
+  """The DynaForm Meta class, adding support for dynamically defined fields
+
+  The new DynaForm class that is created by class function is very
+  similar to one created by the regular djangoforms.ModelFormMetaclass.
+  The only difference is that is the form class has a Meta property,
+  it's 'dynaconf' field will be used to define additional properties
+  in the form class.
+
+  The 'dynaconf' field (if present), is expected to be iterable as a
+  dictionary (with iteritems). The keys are used as the property names,
+  and the values are used as the property value.
+  """
+
+  def __new__(cls, class_name, bases, attrs):
+    """See djangoforms.ModelFormMetaclass on how the __new__ method
+    is used, for an explanation on how this class modifies the default
+    behavior, see the DynaFormMetaclass's docstring.
+    """
+
+    # Retrieve the Meta class, if present
+    meta = attrs.get('Meta', None)
+    conf = None
+
+    # Try to retrieve the dynaconf property
+    if meta:
+      conf = getattr(meta, 'dynaconf', None)
+
+    # When found, extend the form's attribute's with the specified ones
+    if conf:
+      for key, value in conf.iteritems():
+        attrs[key] = value
+
+    # Leave the rest to djangoforms.ModelFormMetaclass.
+    return super(DynaFormMetaclass, cls).__new__(cls, class_name, bases, attrs)
+
+
+def newDynaForm(dynamodel=None, dynabase=None, dynainclude=None, 
+                dynaexclude=None, dynafields=None):
+  """Creates a new form DynaForm class
+
+  The returned class extends dynabase, but with the following additions:
+  * It has a Meta class with the 'model', 'include', and 'exclude'
+  fields set as specified by newDynaForm's keyword arguments.
+  * It's __metaclass__ is set to DynaFormMetaclass (which inherits from
+  the default djangoforms.ModelFormMetaclass).
+  * The Meta class has an additional dynaconf field which is set to
+  the dyanfields keyword argument passed to newDynaForm.
+
+  See DynaFormMetaclass for an explanation on how the dynafields
+  property is used to construct the DynaForm class.
+  """
+
+  class DynaForm(dynabase):
+    """The dynamically created Form class
+    """
+
+    __metaclass__ = DynaFormMetaclass
+
+    class Meta:
+      """Inner Meta class that defines some behavior for the form.
+      """
+
+      model = dynamodel
+      include = dynainclude
+      exclude = dynaexclude
+      dynaconf = dynafields
+
+  return DynaForm
+
+
+def extendDynaForm(dynaform, dynainclude=None, dynaexclude=None, dynafields=None):
+  """Extends an existing dynaform
+
+  If any of dynainclude, dynaexclude or dynafields are not present,
+  they are retrieved from dynaform (if present in it's Meta class).
+
+  While it is rather useles to extend from a dynaform that does not have
+  a Meta class, it is allowed, the resulting DynaForm is the same as if
+  newDynaForm was called with all extendDynForm's keyword arguments.
+  """
+
+  # Try to retrieve the Meta class from the existing dynaform
+  meta = getattr(dynaform, 'Meta', None)
+
+  # If we find one, we can use it to 'extend' from
+  if meta:
+    dynamodel = getattr(meta, 'model', None)
+
+    if not dynainclude:
+      dynainclude = getattr(meta, 'include', None)
+
+    if not dynaexclude:
+      dynaexclude = getattr(meta, 'exclude', None)
+
+    # The most intersting parameter, the 'extra fields' dictionary
+    dynaconf = getattr(meta, 'dynaconf', {})
+    if not dynafields:
+      dynafields = dynaconf
+    else:
+      dicts.merge(dynafields, dynaconf)
+
+  # Create a new DynaForm, using the properties we extracted
+  return newDynaForm(
+      dynamodel=dynamodel,
+      dynabase=dynaform,
+      dynainclude=dynainclude,
+      dynaexclude=dynaexclude,
+      dynafields=dynafields)