Permalink
Browse files

first push

  • Loading branch information...
1 parent 7945935 commit 65ec859568cfc6f4b230e1740ee50c7f7406c16f Peter Bengtsson committed Feb 26, 2010
View
@@ -0,0 +1,4 @@
+*.pyc
+*~
+.venv
+db
View
@@ -0,0 +1 @@
+See README.md
View
26 LICENSE
@@ -0,0 +1,26 @@
+* Copyright (c) 2010, Peter Bengtsson
+* All rights reserved.
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+*
+* * Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+* * 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.
+* * Neither the name of the University of California, Berkeley 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 REGENTS 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 REGENTS AND 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.
+
+New BSD License
View
@@ -0,0 +1,3 @@
+include README
+include LICENSE
+include INSTALL
@@ -0,0 +1 @@
+from base_document import DjangoDocument, model_names
@@ -0,0 +1,90 @@
+import re
+from mongokit.document import DocumentProperties, CallableMixin
+from mongokit import Document
+from django.db.models import signals
+model_names = []
+
+class _PK(object):
+ attname = '_id'
+
+class _Meta(object):
+ def __init__(self, model_name, verbose_name, verbose_name_plural,
+ module_name='document_types', # XXX needed?
+ app_label='done', #XXX needed?
+ ):
+ self.model_name = model_name
+ self.verbose_name = verbose_name and verbose_name or \
+ re.sub('([a-z])([A-Z])', r'\1 \2', model_name)
+ self.verbose_name_plural = verbose_name_plural or self.verbose_name + 's'
+ self.module_name = module_name
+ self.app_label = app_label
+ self.pk = _PK() # needed for haystack
+
+ #all_verbose_names.append(verbose_name)
+ model_names.append((model_name, self.verbose_name))
+
+ def __repr__(self):
+ return "<Meta %s %r, %r>" % (self.model_name,
+ self.verbose_name,
+ self.verbose_name_plural)
+
+
+class DjangoDocumentMetaClass(DocumentProperties):
+ def __new__(cls, name, bases, attrs):
+ new_class = super(DjangoDocumentMetaClass, cls).__new__(cls, name, bases, attrs)
+
+ if CallableMixin in bases:
+ # When you register models in the views for example it will register
+ # all the models again but then they'll be subclasses of mongokit's
+ # CallableMixin.
+ # When this is the case we don't want to bother registering any
+ # meta stuff about them so exit here
+ return new_class
+
+ meta = attrs.pop('Meta', None)
+
+ if meta and getattr(meta, 'abstract', False):
+ # No need to attach more meta crap
+ return new_class
+
+ verbose_name = meta and getattr(meta, 'verbose_name', None) or None
+ verbose_name_plural = meta and getattr(meta, 'verbose_name_plural', None) or None
+ meta = _Meta(name, verbose_name, verbose_name_plural)
+
+ #new_class.add_to_class('_meta', _Meta('X'))
+ new_class._meta = meta
+ return new_class
+
+
+class DjangoDocument(Document):
+ class Meta:
+ abstract = True
+
+ __metaclass__ = DjangoDocumentMetaClass
+
+ ## XX Are these needed?
+ def _get_pk_val(self, meta=None):
+ if not meta:
+ meta = self._meta
+ return str(getattr(self, meta.pk.attname))
+ def _set_pk_val(self, value):
+ raise ValueError("You can't set the ObjectId")
+ pk = property(_get_pk_val, _set_pk_val)
+ ##
+
+
+ def delete(self):
+ signals.pre_delete.send(sender=self.__class__, instance=self)
+ super(DjangoDocument, self).delete()
+ signals.post_delete.send(sender=self.__class__, instance=self)
+
+ def save(self, *args, **kwargs):
+ signals.pre_save.send(sender=self.__class__, instance=self)
+
+ _id_before = getattr(self, '_id', None)
+ super(DjangoDocument, self).save(*args, **kwargs)
+ _id_after = getattr(self, '_id', None)
+
+ signals.post_save.send(sender=self.__class__, instance=self,
+ created=not _id_before and _id_after)
+
No changes.
@@ -0,0 +1,143 @@
+"""
+Half-dummy mongoDB backend for Django.
+"""
+from mongokit import Connection
+
+from django.core.exceptions import ImproperlyConfigured
+from django.db.backends import *
+from django.db.backends.creation import BaseDatabaseCreation
+from django.conf import settings
+
+class UnsupportedConnectionOperation(Exception):
+ pass
+
+def complain(*args, **kwargs):
+ #print "ARGS", args
+ #print "KWARGS", kwargs
+ raise UnsupportedConnectionOperation("ARGS=%s" % unicode(args))
+
+def ignore(*args, **kwargs):
+ pass
+
+class DatabaseError(Exception):
+ pass
+
+class IntegrityError(DatabaseError):
+ pass
+
+class DatabaseOperations(BaseDatabaseOperations):
+ def quote_name(self, name):
+ return '<%s>' % name
+ def sql_flush(self, *args, **kwargs):
+ # deliberately do nothing as this doesn't apply to us
+ return [True] # pretend that we did something
+
+class DatabaseClient(BaseDatabaseClient):
+ runshell = complain
+
+class DatabaseIntrospection(BaseDatabaseIntrospection):
+ #get_table_list = complain
+ def get_table_list(self, cursor):
+ return []
+ get_table_description = complain
+ get_relations = complain
+ get_indexes = complain
+
+
+
+class MongoCursor(object):
+ latest_result = None
+ def execute(self, sql, params=None):
+ print "SQL Command"
+ print repr(sql)
+ print
+ self.latest_result = None
+
+ def fetchone(self):
+ return [self.latest_result]
+
+TEST_DATABASE_PREFIX = 'test_'
+
+class DatabaseCreation(BaseDatabaseCreation):
+ def create_test_db(self, verbosity=1, autoclobber=False):
+ # No need to create databases in mongoDB :)
+ # but we can make sure that if the database existed is emptied
+
+ if self.connection.settings_dict['TEST_NAME']:
+ test_database_name = self.connection.settings_dict['TEST_NAME']
+ else:
+ test_database_name = TEST_DATABASE_PREFIX + self.connection.settings_dict['NAME']
+
+ # This is important. Here we change the settings so that all other code
+ # things that the chosen database is now the test database. This means
+ # that nothing needs to change in the test code for working with
+ # connections, databases and collections. It will appear the same as
+ # when working with non-test code.
+ settings.DATABASES['mongodb']['NAME'] = test_database_name
+
+ # In this phase it will only drop the database if it already existed
+ # which could potentially happen if the test database was created but
+ # was never dropped at the end of the tests
+ self._drop_database(test_database_name)
+ # if it didn't exist it will automatically be created by the mongokit conncetion
+
+ def destroy_test_db(self, old_database_name, verbosity=1):
+ """
+ Destroy a test database, prompting the user for confirmation if the
+ database already exists. Returns the name of the test database created.
+ """
+ if verbosity >= 1:
+ print "Destroying test database '%s'..." % self.connection.alias
+ test_database_name = self.connection.settings_dict['NAME']
+ self._drop_database(test_database_name)
+ settings.DATABASES['mongodb']['NAME'] = old_database_name
+
+ def _drop_database(self, database_name):
+ assert TEST_DATABASE_PREFIX in database_name # paranoia
+ if database_name in self.connection.connection.database_names():
+ # needs to be dropped
+ self.connection.connection.drop_database(database_name)
+
+
+
+
+class DatabaseWrapper(BaseDatabaseWrapper):
+ operators = {}
+ _commit = ignore
+ _rollback = ignore
+
+ autocommit = None # ignore
+
+
+ def __init__(self, settings_dict, alias, *args, **kwargs):
+ self.features = BaseDatabaseFeatures()
+ self.ops = DatabaseOperations()
+ self.client = DatabaseClient(self)
+ self.creation = DatabaseCreation(self)
+ self.introspection = DatabaseIntrospection(self)
+ self.validation = BaseDatabaseValidation(self)
+
+ settings_dict['SUPPORTS_TRANSACTIONS'] = False
+ self.settings_dict = settings_dict
+ self.alias = alias
+
+ self.connection = ConnectionWrapper()
+
+ def _cursor(self):
+ return MongoCursor()
+
+
+ def close(self):
+ pass
+
+class ConnectionWrapper(Connection):
+ # Need to pretend we care about autocommit
+ # BaseDatabaseCreation (in django/db/backends/creation.py) needs to
+ # set autocommit
+ autocommit = True # Needed attribute but its value is ignored
+
+ def __init__(self, *args, **kwargs):
+ super(ConnectionWrapper, self).__init__(*args, **kwargs)
+
+ def __repr__(self):
+ return 'ConnectionWrapper: ' + super(ConnectionWrapper, self).__repr__()
No changes.
No changes.
@@ -0,0 +1,11 @@
+from django import forms
+
+class TalkForm(forms.Form):
+ topic = forms.CharField(max_length=250)
+ when = forms.DateField()
+ tags = forms.CharField(max_length=250)
+ duration = forms.FloatField()
+
+ def clean_tags(self):
+ tags = self.cleaned_data['tags']
+ return [x.strip() for x in tags.split(',') if x.strip()]
@@ -0,0 +1,16 @@
+import datetime
+from django_mongokit import DjangoDocument
+
+# Create your models here.
+class Talk(DjangoDocument):
+ collection_name = 'talks'
+ structure = {
+ 'topic': unicode,
+ 'when': datetime.datetime,
+ 'tags': list,
+ 'duration': float,
+ }
+
+ use_dot_notation = True
+
+
@@ -0,0 +1,76 @@
+from django.core.urlresolvers import reverse
+import datetime
+from django.test import TestCase
+from django.db import connections
+from django.conf import settings
+
+from models import Talk
+
+class ExampleTest(TestCase):
+ def setUp(self):
+ self.connection = connections['mongodb'].connection
+ self.database = self.connection[settings.DATABASES['mongodb']['NAME']]
+
+ def tearDown(self):
+ for name in self.database.collection_names():
+ if name not in ('system.indexes',):
+ self.database.drop_collection(name)
+
+
+ def test_creating_talk_basic(self):
+ """test to create a Talk instance"""
+ self.connection.register([Talk])
+ collection = self.database[Talk.collection_name]
+ talk = collection.Talk()
+ talk.topic = u"Bla"
+ talk.when = datetime.datetime.now()
+ talk.tags = [u"foo", u"bar"]
+ talk.duration = 5.5
+ talk.validate()
+ talk.save()
+
+ self.assertTrue(talk['_id'])
+ self.assertEqual(talk.duration, 5.5)
+
+ def test_homepage(self):
+ """rendering the homepage will show talks and will make it possible to
+ add more talks and delete existing ones"""
+ response = self.client.get(reverse('homepage'))
+ self.assertTrue(response.status_code, 200)
+ self.assertTrue('No talks added yet' in response.content)
+
+ data = {'topic': '',
+ 'when': '2010-12-31',
+ 'duration':'1.0',
+ 'tags': ' foo , bar, ,'}
+ response = self.client.post(reverse('homepage'), data)
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue('class="errorlist"' in response.content)
+ self.assertTrue('This field is required' in response.content)
+
+ data['topic'] = 'My Topic'
+ response = self.client.post(reverse('homepage'), data)
+ self.assertEqual(response.status_code, 302)
+
+ response = self.client.get(reverse('homepage'))
+ self.assertTrue(response.status_code, 200)
+ self.assertTrue('My Topic' in response.content)
+ self.assertTrue('31 December 2010' in response.content)
+ self.assertTrue('Tags: foo, bar' in response.content)
+
+ self.connection.register([Talk])
+ collection = self.database[Talk.collection_name]
+ talk = collection.Talk.one()
+ assert talk.topic == u"My Topic"
+ delete_url = reverse('delete_talk', args=[str(talk._id)])
+ response = self.client.get(delete_url)
+ self.assertEqual(response.status_code, 302)
+
+ response = self.client.get(reverse('homepage'))
+ self.assertTrue(response.status_code, 200)
+ self.assertTrue('My Topic' not in response.content)
+
+
+
+
+
@@ -0,0 +1,8 @@
+from django.conf.urls.defaults import *
+
+import views
+
+urlpatterns = patterns('',
+ url(r'^$', views.homepage, name='homepage'),
+ url(r'^delete/(?P<_id>[\w-]+)$', views.delete_talk, name='delete_talk'),
+)
Oops, something went wrong.

0 comments on commit 65ec859

Please sign in to comment.