Skip to content
Browse files

add automatic creation of DocumentForms. limited for now need to add

support for some otherproperties.
  • Loading branch information...
1 parent f3f7691 commit 6951f76bb2c7da37f56cfdcad8d5a577075cc4dd benoitc committed Jun 13, 2009
View
220 couchdbkit/ext/django/forms.py
@@ -0,0 +1,220 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2008-2009 Benoit Chesneau <benoitc@e-engura.com>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+# code heavily inspired from django.forms.models
+# Copyright (c) Django Software Foundation and individual contributors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of Django nor the names of its contributors may be used
+# to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from django.utils.text import capfirst
+from django.utils.datastructures import SortedDict
+from django.forms.util import ValidationError, ErrorList
+from django.forms.forms import BaseForm, get_declared_fields
+from django.forms import fields as f
+from django.forms.widgets import media_property
+
+from couchdbkit.ext.django import schema
+from couchdbkit.schema import value_to_python
+
+FIELDS_PROPERTES_MAPPING = {
+ "StringProperty": f.CharField,
+ "IntegerProperty": f.IntegerField,
+ "DecimalProperty": f.DecimalField,
+ "BooleanProperty": f.BooleanField,
+ "FloatProperty": f.FloatField,
+ "DateTimeProperty": f.DateTimeField,
+ "DateProperty": f.DateField,
+ "TimeProperty": f.TimeField
+}
+
+def document_to_dict(instance, properties=None, exclude=None):
+ """
+ Returns a dict containing the data in ``instance`` suitable for passing as
+ a Form's ``initial`` keyword argument.
+
+ ``properties`` is an optional list of properties names. If provided, only the named
+ properties will be included in the returned dict.
+
+ ``exclude`` is an optional list of properties names. If provided, the named
+ properties will be excluded from the returned dict, even if they are listed in
+ the ``properties`` argument.
+ """
+ # avoid a circular import
+ data = {}
+ for prop in instance.all_properties():
+ if properties and not prop.name in properties:
+ continue
+ if exclude and prop.name in exclude:
+ continue
+ data[prop.name] = value_to_python(instance._doc[prop.name])
+ return data
+
+def fields_for_document(document, properties=None, exclude=None):
+ """
+ Returns a ``SortedDict`` containing form fields for the given document.
+
+ ``properties`` is an optional list of properties names. If provided, only the named
+ properties will be included in the returned properties.
+
+ ``exclude`` is an optional list of properties names. If provided, the named
+ properties will be excluded from the returned properties, even if they are listed
+ in the ``properties`` argument.
+ """
+ field_list = []
+ for prop in document._properties.values():
+ if properties and not prop.name in properties:
+ continue
+ if exclude and prop.name in exclude:
+ continue
+ property_class_name = prop.__class__.__name__
+ if property_class_name in FIELDS_PROPERTES_MAPPING:
+ defaults = {
+ 'required': prop.required,
+ 'label': capfirst(prop.verbose_name),
+ }
+
+ if prop.default is not None:
+ defaults['initial'] = prop.default_value()
+
+ if prop.choices:
+ if prop.default:
+ defaults['choices'] = prop.default_value() + list(prop.choices)
+ defaults['coerce'] = prop.to_python
+
+ field_list.append((prop.name, FIELDS_PROPERTES_MAPPING[property_class_name](**defaults)))
+ return SortedDict(field_list)
+
+class DocumentFormOptions(object):
+ def __init__(self, options=None):
+ self.document = getattr(options, 'document', None)
+ self.properties = getattr(options, 'properties', None)
+ self.exclude = getattr(options, 'exclude', None)
+
+class DocumentFormMetaClass(type):
+ def __new__(cls, name, bases, attrs):
+ try:
+ parents = [b for b in bases if issubclass(b, DocumentForm)]
+ except NameError:
+ # We are defining ModelForm itself.
+ parents = None
+
+ declared_fields = get_declared_fields(bases, attrs, False)
+ new_class = super(DocumentFormMetaClass, cls).__new__(cls, name, bases,
+ attrs)
+
+ if not parents:
+ return new_class
+
+ if 'media' not in attrs:
+ new_class.media = media_property(new_class)
+
+ opts = new_class._meta = DocumentFormOptions(getattr(new_class, 'Meta', None))
+
+ if opts.document:
+ # If a document is defined, extract form fields from it.
+ fields = fields_for_document(opts.document, opts.properties,
+ opts.exclude)
+ # Override default docuemnt fields with any custom declared ones
+ # (plus, include all the other declared fields).
+ fields.update(declared_fields)
+ else:
+ fields = declared_fields
+
+ new_class.declared_fields = declared_fields
+ new_class.base_fields = fields
+ return new_class
+
+class BaseDocumentForm(BaseForm):
+
+ def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
+ initial=None, error_class=ErrorList, label_suffix=":",
+ empty_permitted=False, instance=None):
+
+ opts = self._meta
+
+ if instance is None:
+ self.instance = opts.document()
+ object_data = {}
+ else:
+ object_data = document_to_dict(instance, opts.fields, opts.exclude)
+
+ if initial is not None:
+ object_data.update(initial)
+
+ super(BaseDocumentForm, self).__init__(data, files, auto_id, prefix, object_data,
+ error_class, label_suffix, empty_permitted)
+
+ def save(self, commit=True, dynamic=False):
+ """
+ Saves this ``form``'s cleaned_data into document instance
+ ``self.instance``.
+
+ If commit=True, then the changes to ``instance`` will be saved to the
+ database. Returns ``instance``.
+ """
+
+
+ opts = self._meta
+ cleaned_data = self.cleaned_data.copy()
+ for prop_name in self.instance._doc.keys():
+ if opts.properties and key not in opts.properties:
+ continue
+ if opts.exclude and prop_name in exclude:
+ continue
+ if prop_name in cleaned_data:
+ value = cleaned_data.pop(prop_name)
+ if value is not None:
+ setattr(self.instance, prop_name, value)
+
+ if dynamic:
+ for attr_name in cleaned_data.keys():
+ if opts.exclude and attr_name in opts.exclude:
+ continue
+ value = cleaned_data[attr_name]
+ if value is not None:
+ setattr(self.instance, attr_name, value)
+
+ if commit:
+ self.instance.save()
+
+ return self.instance
+
+class DocumentForm(BaseDocumentForm):
+ __metaclass__ = DocumentFormMetaClass
View
8 couchdbkit/schema/base.py
@@ -233,17 +233,13 @@ def __getitem__(self, key):
except AttributeError, e:
if key in self._doc:
return self._doc[key]
- raise KeyError, e
- except:
raise
def __setitem__(self, key, value):
""" add a property
"""
- try:
- setattr(self, key, value)
- except AttributeError, e:
- raise KeyError, e
+ setattr(self, key, value)
+
def __delitem__(self, key):
""" delete a property
View
5 examples/djangoapp/greeting/models.py
@@ -1,8 +1,9 @@
+from datetime import datetime
from django.db import models
from couchdbkit.ext.django.schema import *
class Greeting(Document):
author = StringProperty()
- content = StringProperty()
- date = DateTimeProperty()
+ content = StringProperty(required=True)
+ date = DateTimeProperty(default=datetime.utcnow)
View
32 examples/djangoapp/greeting/views.py
@@ -4,21 +4,31 @@
from django.shortcuts import render_to_response as render
from django.template import RequestContext, loader, Context
-from couchdbkit.ext.django.loading import get_db
+from couchdbkit.ext.django.forms import DocumentForm
from djangoapp.greeting.models import Greeting
+
+class GreetingForm(DocumentForm):
+
+ class Meta:
+ document = Greeting
+
def home(request):
- db = get_db('greeting')
- greet = Greeting(
- author="Benoit",
- content="Welcome to simplecouchdb world",
- date=datetime.utcnow()
- )
-
- greet.save()
+
+ greet = None
+
+ if request.POST:
+ form = GreetingForm(request.POST)
+ if form.is_valid():
+ greet = form.save()
+ else:
+ form = GreetingForm()
+
+ greetings = Greeting.view('greeting/all')
return render("home.html", {
- "info": greet._db.info(),
- "id": greet.id
+ "form": form,
+ "greet": greet,
+ "greetings": greetings
}, context_instance=RequestContext(request))
View
23 examples/djangoapp/templates/home.html
@@ -2,6 +2,25 @@
{% load i18n %}
{% block content %}
- <p>{{ info }}</p>
- <p>id = {{ id }}</p>
+ <form method="post">
+ <table>
+ {{ form.as_table }}
+ </table>
+ <input type="submit" id="submit" value="submit">
+ </form>
+
+ {% if greet %}
+ {{ greet.id }} was added
+ {% endif %}
+
+ <h2>Greetings</h2>
+ <table>
+ {% for g in greetings %}
+ <tr>
+ <td>{{ g.date|timesince }} </td>
+ <td>{{ g.author }} </td>
+ <td>{{ g.content }}</td>
+ </tr>
+ {% endfor %}
+ </table>
{% endblock content %}

0 comments on commit 6951f76

Please sign in to comment.
Something went wrong with that request. Please try again.