Skip to content

Commit

Permalink
version 0.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
pricco committed Sep 7, 2011
1 parent b1af77b commit 3d50a38
Show file tree
Hide file tree
Showing 28 changed files with 863 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
*.pyc
17 changes: 17 additions & 0 deletions .project
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>django-solr</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.python.pydev.PyDevBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.python.pydev.pythonNature</nature>
</natures>
</projectDescription>
10 changes: 10 additions & 0 deletions .pydevproject
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?eclipse-pydev version="1.0"?>

<pydev_project>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
<path>/django-solr</path>
</pydev_pathproperty>
</pydev_project>
28 changes: 28 additions & 0 deletions LICENSE
@@ -0,0 +1,28 @@
Copyright (c) 2011, Sophilabs
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 author nor the names of other
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.
2 changes: 2 additions & 0 deletions MANIFEST.in
@@ -0,0 +1,2 @@
include LICENSE
include README
28 changes: 28 additions & 0 deletions djangosolr/__init__.py
@@ -0,0 +1,28 @@
"""
Solr Search Engine ORM for Django models*
Create, save, fetch, update and delete:
<code>
mv = MovieDocument(title='Jurassic Park', director='Steven Spielberg')
mv.save()
mv = MovieDocument.docuemnts.get(1)
mv.title = 'Jurassic Park I'
mv.save()
mv.delete()
</code>
Search:
<code>
MovieDocument.documents.query(Q('jurassic park') & Q('director', 'spielberg'))[:10]
</code>
*Solr Not Included
"""

__version__ = (0, 0, 1)

from djangosolr.conf import inject_defaults
inject_defaults()
18 changes: 18 additions & 0 deletions djangosolr/conf/__init__.py
@@ -0,0 +1,18 @@

#http://passingcuriosity.com/2010/default-settings-for-django-applications/

def inject_defaults():
import default_settings
import sys

_app_settings = sys.modules['djangosolr.conf.default_settings']
_def_settings = sys.modules['django.conf.global_settings']
_settings = sys.modules['django.conf'].settings
for _k in dir(_app_settings):
if _k.isupper():
# Add the value to the default settings module
setattr(_def_settings, _k, getattr(_app_settings, _k))

# Add the value to the settings, if not already present
if not hasattr(_settings, _k):
setattr(_settings, _k, getattr(_app_settings, _k))
23 changes: 23 additions & 0 deletions djangosolr/conf/default_settings.py
@@ -0,0 +1,23 @@

DJANGOSOLR_ID_FIELD = 'id'
DJANGOSOLR_TYPE_FIELD = 'type'

DJANGOSOLR_FIELD_MAPPING = {
'django.db.models.fields.AutoField': 'djangosolr.documents.fields.IntegerField',
'django.db.models.fields.IntegerField': 'djangosolr.documents.fields.IntegerField',
'django.db.models.fields.BigIntegerField': 'djangosolr.documents.fields.IntegerField',
'django.db.models.fields.FloatField': 'djangosolr.documents.fields.FloatField',
'django.db.models.fields.DecimalField': 'djangosolr.documents.fields.DecimalField',
'django.db.models.fields.TextField': 'djangosolr.documents.fields.CharField',
'django.db.models.fields.CharField': 'djangosolr.documents.fields.CharField',
'django.db.models.fields.DateField': 'djangosolr.documents.fields.DateField',
'django.db.models.fields.DateTimeField': 'djangosolr.documents.fields.DateField',
'django.db.models.fields.BooleanField': 'djangosolr.documents.fields.BooleanField',
'django.db.models.fields.NullBooleanField': 'djangosolr.documents.fields.BooleanField',
}

DJANGOSOLR_AUTOCOMMIT = True
DJANGOSOLR_URL = 'http://localhost:8983/solr'
DJANGOSOLR_SELECT_PATH = '/select'
DJANGOSOLR_UPDATE_PATH = '/update/json'
DJANGOSOLR_DELETE_PATH = '/update/json'
3 changes: 3 additions & 0 deletions djangosolr/documents/__init__.py
@@ -0,0 +1,3 @@
from djangosolr.documents.document import Document
from djangosolr.documents.manager import Manager
from djangosolr.documents.fields import Field, CharField, DateTimeField, DecimalField, FloatField, IntegerField, TextField
81 changes: 81 additions & 0 deletions djangosolr/documents/document.py
@@ -0,0 +1,81 @@
from djangosolr.documents.options import Options
from djangosolr.documents.manager import ensure_default_manager
from django.conf import settings

class DocumentBase(type):

def __new__(cls, name, bases, attrs):
super_new = super(DocumentBase, cls).__new__
new_class = super_new(cls, name, bases, {'__module__': attrs.pop('__module__')})

attr_meta = attrs.pop('Meta', None)
if not attr_meta:
meta = getattr(new_class, 'Meta', None)
else:
meta = attr_meta
new_class.add_to_class('_meta', Options(meta))

if getattr(new_class, '_default_manager', None):
new_class._default_manager = None
new_class._base_manager = None

for obj_name, obj in attrs.items():
new_class.add_to_class(obj_name, obj)

new_class._prepare()

return new_class

def add_to_class(cls, name, value):
if hasattr(value, 'contribute_to_class'):
value.contribute_to_class(cls, name)
else:
setattr(cls, name, value)

def _prepare(cls):
opts = cls._meta
opts._prepare(cls)
ensure_default_manager(cls)

class Document(object):

__metaclass__ = DocumentBase

def __init__(self, **kwargs):
for field in self._meta.fields:
if field.name in kwargs:
setattr(self, field.name, kwargs.pop(field.name))
else:
setattr(self, field.name, field.get_default())
if kwargs:
raise KeyError(kwargs.keys()[0])

@classmethod
def create(cls, om):
document = cls()
if isinstance(om, dict):
for field in cls._meta.fields:
name = cls._meta.type + '-' + field.name
if om.has_key(name):
setattr(document, field.name, field.convert(om[name]))
else:
for field in cls._meta.fields:
setattr(document, field.name, getattr(om, field.name))
return document

def save(self):
id = getattr(self, self._meta.pk.name)
type = self._meta.type
doc = {settings.DJANGOSOLR_ID_FIELD: type + '-' + str(id), settings.DJANGOSOLR_TYPE_FIELD: type}
for field in self._meta.fields:
value = field.prepare(getattr(self, field.name))
if value is None:
doc[type + '-' + field.name] = [] #BUG: https://issues.apache.org/jira/browse/SOLR-2714
else:
doc[type + '-' + field.name] = value
return self._default_manager._request('POST', settings.DJANGOSOLR_UPDATE_PATH, [('commit', 'true',)], { 'add': { 'overwrite': True, 'doc': doc}, 'commit': {} })

def delete(self):
id = getattr(self, self._meta.pk.name)
type = self._meta.type
return self._default_manager._request('POST', settings.DJANGOSOLR_DELETE_PATH, None, {'delete': { 'query': settings.DJANGOSOLR_ID_FIELD + ':' + type + '-' + str(id)}, 'commit': {} })
76 changes: 76 additions & 0 deletions djangosolr/documents/fields.py
@@ -0,0 +1,76 @@
import datetime, decimal

class Field():

def __init__(self, type='string', name=None, stored=True, indexed=True, multivalued=False, primary_key=False):
self.type = type
self.name = name
self.stored = stored
self.indexed = indexed
self.multivalued = multivalued
self.primary_key = primary_key

def contribute_to_class(self, cls, name):
self.name = name
self._model = cls
cls._meta.add_field(self)

def get_default(self):
return None

def prepare(self, value):
return value

def convert(self, value):
return value

class IntegerField(Field):

def __init__(self, **kwargs):
kwargs.setdefault('type', 'int')
Field.__init__(self, **kwargs)

class CharField(Field):

def __init__(self, **kwargs):
kwargs.setdefault('type', 'string')
Field.__init__(self, **kwargs)

class DateTimeField(Field):

def __init__(self, **kwargs):
kwargs.setdefault('type', 'date')
Field.__init__(self, **kwargs)

def prepare(self, value):
return value.strftime('%Y-%m-%dT%H:%M:%S.%fZ')

def convert(self, value):
try:
return datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ')
except ValueError:
return datetime.datetime.strptime(value, '%Y-%m-%d').date()

class FloatField(Field):

def __init__(self, **kwargs):
kwargs.setdefault('type', 'float')
Field.__init__(self, **kwargs)

class TextField(Field):

def __init__(self, **kwargs):
kwargs.setdefault('type', 'text')
Field.__init__(self, **kwargs)

class DecimalField(Field):

def __init__(self, **kwargs):
kwargs.setdefault('type', 'float')
Field.__init__(self, **kwargs)

def prepare(self, value):
return float(value)

def convert(self, value):
return decimal.Decimal(value)
62 changes: 62 additions & 0 deletions djangosolr/documents/manager.py
@@ -0,0 +1,62 @@
import urllib, httplib2, json
from djangosolr.documents.queryset import QuerySet
from django.conf import settings

class ManagerDescriptor(object):

def __init__(self, manager):
self.manager = manager

def __get__(self, instance, type=None):
if instance != None:
raise AttributeError("Manager isn't accessible via %s instances" % type.__name__)
return self.manager

class Manager(object):

def contribute_to_class(self, model, name):
self._model = model
setattr(model, name, ManagerDescriptor(self))
if not getattr(model, '_default_manager', None):
model._default_manager = self

def _request(self, method, path, query=None, body=None):
try:
uri = '%s%s?wt=json' % (settings.DJANGOSOLR_URL, path,)
if query:
uri += '&' + urllib.urlencode(query)
if body:
body = json.dumps(body)
headers, body = httplib2.Http().request(uri=uri, method=method, body=body, headers={'Content-type': 'application/json'})
if headers['status'] == '200':
return json.loads(body)
raise Exception(body)
except:
raise

def _get_query_set(self):
return QuerySet(self._model)

def all(self):
return self._get_query_set().set('fq', settings.DJANGOSOLR_TYPE_FIELD + ':' + self._model._meta.type)

def get(self, id):
return self._get_query_set().set('q', settings.DJANGOSOLR_ID_FIELD + ':' + str(id))

def clear(self):
return self._request('POST', settings.DJANGOSOLR_DELETE_PATH, None, {'delete': { 'query': settings.DJANGOSOLR_TYPE_FIELD + ':' + self._model._meta.type}})

def ensure_default_manager(cls):
if not getattr(cls, '_default_manager', None):
cls.add_to_class('documents', Manager())
cls._base_manager = cls.documents
elif not getattr(cls, '_base_manager', None):
default_mgr = cls._default_manager.__class__
if (default_mgr is Manager):
cls._base_manager = cls._default_manager
else:
for base_class in default_mgr.mro()[1:]:
if (base_class is Manager):
cls.add_to_class('_base_manager', base_class())
return
raise AssertionError("Should never get here. Please report a bug, including your model and model manager setup.")

0 comments on commit 3d50a38

Please sign in to comment.