Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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

support for some otherproperties.
  • Loading branch information...
commit 6951f76bb2c7da37f56cfdcad8d5a577075cc4dd 1 parent f3f7691
benoitc authored
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 %}
Please sign in to comment.
Something went wrong with that request. Please try again.