Permalink
Browse files

removed save_as_new and replaced with clone

  • Loading branch information...
1 parent 9d31dd8 commit d527368d3e310d08a9606065f637e96161131f51 quantmind committed Nov 17, 2011
Showing with 166 additions and 117 deletions.
  1. +11 −3 CHANGELOG.rst
  2. +48 −7 stdnet/backends/base.py
  3. +50 −41 stdnet/backends/redisb.py
  4. +10 −18 stdnet/orm/models.py
  5. +40 −38 stdnet/orm/query.py
  6. +6 −8 tests/regression/fields/id.py
  7. +1 −2 tests/regression/query/manager.py
View
@@ -26,6 +26,7 @@ Ver. 0.7.0 - Development
via the :attr:`stdnet.struct` singleton.
* Added :meth:`stdnet.orm.StdModel.tojson` method for obtaining JSON representation
of model instances.
+* Added :meth:`stdnet.orm.StdModel.clone` method for cloning model instances.
* Refactored :ref:`transactions <model-transactions>` to be used with
:ref:`remote data structures <structures-backend>` and
:ref:`structured fields <model-field-structure>`.
@@ -37,6 +38,14 @@ Ver. 0.7.0 - Development
model ``delete`` method to crash.
* **360 regression tests** with **80%** coverage.
+.. _vers06:
+
+Ver. 0.6.2 - 2011 Nov 14
+============================
+* Critical bug fix in ``delete`` method when a model has no indices.
+* Critical bug fix in `ManyToManyField`.
+* **299 regression tests**.
+
Ver. 0.6.1 - 2011 Sep 10
============================
* This is a minor release which brings an improved documentation,
@@ -54,8 +63,6 @@ Ver. 0.6.1 - 2011 Sep 10
* Refactored redisinfo for a better redis monitor.
* **297 regression tests** with **78%** coverage.
-.. _vers06:
-
Ver. 0.6.0 - 2011 Aug 9
============================
* **New database schema incompatible with previous versions**.
@@ -201,7 +208,8 @@ Ver. 0.4.0 - 2010 Nov 11
* Documentation hosted at github.
* Added new ``contrib`` module ``djstdnet`` which uses `djpcms`_ content management system to display an admin
interface for a :class:`stdnet.orm.StdModel`. Experimental for now.
-* Added :class:`stdnet.CacheClass` which can be used as django_ cache backend. For example, using redis database 11 as cache is obtained by::
+* Added :class:`stdnet.CacheClass` which can be used as django_ cache backend.
+ For example, using redis database 11 as cache is obtained by::
CACHE_BACKEND = 'stdnet://127.0.0.1:6379/?type=redis&db=11&timeout=300'
View
@@ -24,9 +24,17 @@ def __init__(self,id,timeout,pipeline):
def add(self, value):
self.value = value
+
-
class BeckendQuery(object):
+ '''Backend queryset class which implements the database
+queries specified by :class:`stdnet.orm.QuerySet`.
+
+.. attribute:: qs
+
+ Underlying queryset
+
+'''
query_set = None
def __init__(self, qs, fargs = None, eargs = None,
@@ -35,35 +43,68 @@ def __init__(self, qs, fargs = None, eargs = None,
self.expire = max(timeout,30)
self.timeout = timeout
self.server = qs._meta.cursor
- self.meta = qs._meta
self._sha = BytesIO()
+ self.__count = None
+ self.__result = False
+ # build the queryset without fetching data
self.build(fargs, eargs, queries)
code = self._sha.getvalue()
self._sha = code if not code else sha1(code).hexdigest()
- self.execute()
@property
+ def meta(self):
+ return self.qs._meta
+
+ @property
def sha(self):
return self._sha
+ @property
+ def query_class(self):
+ '''The underlying query class'''
+ return self.qs.__class__
+
def __len__(self):
return self.count()
def has(self, val):
- raise NotImplementedError
+ if self.__result == False:
+ self.__result = self.execute_query()
+ return self._has(val)
+
+ def items(self, slic):
+ if self.__result == False:
+ self.__result = self.execute_query()
+ return self._items(slic)
def count(self):
+ if self.__count is None:
+ if self.__result == False:
+ self.__result = self.execute_query()
+ self.__count = self._count()
+ return self.__count
+
+ # VIRTUAL FUNCTIONS
+
+ def _count(self):
raise NotImplementedError
- def build(self, fargs, eargs, queries):
+ def _has(self, val):
raise NotImplementedError
- def execute(self):
+ def _items(self, slic):
raise NotImplementedError
- def items(self, slic):
+ def build(self, fargs, eargs, queries):
raise NotImplementedError
+ def execute_query(self):
+ '''Execute the query without fetching data from server. Must
+ be implemented by data-server backends.'''
+ raise NotImplementedError
+
+ # LOAD RELATED
+
def load_related(self, result):
'''load related fields into the query result.
View
@@ -64,9 +64,11 @@ def __call__(self, key, id, score = None, obj = None, idsave = True):
class RedisQuery(stdnet.BeckendQuery):
- result = None
- _count = None
-
+
+ @property
+ def simple(self):
+ return isinstance(self.query_set,list)
+
def _unique_set(self, name, values):
'''Handle filtering over unique fields'''
key = self.meta.tempkey()
@@ -93,26 +95,34 @@ def _query(self, qargs, setoper, key = None, extra = None):
if qargs:
for q in qargs:
sha.write(q.__repr__().encode())
+ values = q.values
if q.unique:
if q.lookup == 'in':
- keys.append(self._unique_set(q.name, q.values))
+ keys.append(self._unique_set(q.name, values))
else:
raise ValueError('Not available')
- elif len(q.values) == 1:
- keys.append(meta.basekey(IDX,q.name,q.values[0]))
else:
- insersept = [meta.basekey(IDX,q.name,value)\
- for value in q.values]
- tkey = self.meta.tempkey()
- if q.lookup == 'in':
- self.union(tkey,insersept).expire(tkey,self.expire)
- #elif q.lookup == 'contains':
- # self.intersect(tkey,insersept).expire(tkey,self.expire)
+ if isinstance(values,self.query_class):
+ rqs = values.backend_query()
+ if rqs.simple:
+ values = rqs.query_set
+ else:
+ keys.append(rqs.query_set)
+ continue
+ if len(values) == 1:
+ keys.append(meta.basekey(IDX,q.name,values[0]))
else:
- raise ValueError('Lookup {0} Not available'\
- .format(q.lookup))
- keys.append(tkey)
-
+ insersept = [meta.basekey(IDX,q.name,value)\
+ for value in values]
+ tkey = self.meta.tempkey()
+ if q.lookup == 'in':
+ self.union(tkey,insersept).expire(tkey,self.expire)
+ #elif q.lookup == 'contains':
+ # self.intersect(tkey,insersept).expire(tkey,self.expire)
+ else:
+ raise ValueError('Lookup {0} Not available'\
+ .format(q.lookup))
+ keys.append(tkey)
if extra:
for id in extra:
@@ -145,14 +155,16 @@ def build_from_query(self, queries):
for q in queries:
sha.write(q.__repr__().encode())
query = q.query
- query._buildquery()
- qset = query.qset.query_set
+ qset = query.backend_query()
+ qset.execute_query()
+ qset = qset.query_set
db = query._meta.cursor.redispy.db
if db != pipe.db:
raise ValueError('Indexes in a different database')
# In a different redis database. We need to move the set
query._meta.cursor.redispy.move(qset,pipe.db)
pipe.expire(qset,self.expire)
+
skey = self.meta.tempkey()
okey = query._meta.basekey(OBJ,'*->{0}'.format(q.field))
pipe.sort(qset, by = 'nosort', get = okey, storeset = skey)\
@@ -192,8 +204,8 @@ def build(self, fargs, eargs, queries):
if id is not None:
allids.append(id)
pismember(idset,id)
- self.result = [id for (id,r) in zip(allids,pipe.execute())\
- if chk(r)]
+ self.query_set = [id for (id,r) in zip(allids,pipe.execute())\
+ if chk(r)]
else:
self.intersect = pipeattr(pipe,p,'interstore')
self.union = pipeattr(pipe,p,'unionstore')
@@ -211,18 +223,17 @@ def build(self, fargs, eargs, queries):
key = key1
self.query_set = key
- def execute(self):
- sha = self.sha
- if sha:
+ def execute_query(self):
+ if not self.simple and self.sha:
if self.timeout:
key = self.meta.tempkey(sha)
+ self.query_set = key
if not self.server.redispy.exists(key):
self.pipe.rename(self.query_set,key)
- self.result = self.pipe.execute()
- self.query_set = key
+ return self.pipe.execute()
else:
- self.result = self.pipe.execute()
-
+ return self.pipe.execute()
+
def order(self):
'''Perform ordering with respect model fields.'''
if self.qs.ordering:
@@ -238,17 +249,15 @@ def order(self):
.expire(skey,self.expire).execute()
return skey
- def count(self):
- if self._count is None:
- if self.qs.simple:
- self._count = len(self.result)
- else:
- self._count = self.card(self.query_set)
- return self._count
+ def _count(self):
+ if self.simple:
+ return len(self.query_set)
+ else:
+ return self.card(self.query_set)
- def has(self, val):
- if self.qs.simple:
- return val in self.result
+ def _has(self, val):
+ if self.simple:
+ return val in self.query_set
else:
return True if self.ismember(self.query_set, val) else False
@@ -263,10 +272,10 @@ def get_redis_slice(self, slic):
stop = -1
return start,stop
- def items(self, slic):
+ def _items(self, slic):
# Unwind the database query
- if self.qs.simple:
- ids = self.result
+ if self.simple:
+ ids = self.query_set
if slic:
ids = ids[slic]
else:
View
@@ -1,7 +1,7 @@
import copy
from stdnet.exceptions import *
-from stdnet.utils import zip, UnicodeMixin, JSPLITTER
+from stdnet.utils import zip, UnicodeMixin, JSPLITTER, iteritems
from stdnet import dispatch, transaction, attr_local_transaction
from .base import StdNetType, FakeModelType
@@ -158,22 +158,21 @@ def save(self, transaction = None, skip_signal = False):
instance = r)
return r
- def save_as_new(self, id = None, commit = True, **kwargs):
- '''Utility method for saving the instance as a new object.
+ def clone(self, id = None, **data):
+ '''Utility method for cloning the instance as a new object.
:parameter id: Optional new id.
:parameter commit: If ``True`` the :meth:`save` method will be called with
parameters *kwargs*, otherwise no save performed.
:rtype: an instance of this class
'''
- self.on_save_as_new()
- self._loadedfields = None
- self.id = id
- self._dbdata = {}
- if commit:
- return self.save(**kwargs)
- else:
- return self
+ fields = self.todict()
+ fields.update(data)
+ fields.pop('id',None)
+ fields.pop('__dbdata__',None)
+ instance = self._meta.maker()
+ instance.__setstate__((id,None,fields))
+ return instance
def is_valid(self):
'''Kick off the validation algorithm by checking oll
@@ -261,13 +260,6 @@ def local_transaction(self, **kwargs):
self._meta.cursor.transaction(**kwargs))
return getattr(self,attr_local_transaction)
- # HOOKS
-
- def on_save_as_new(self):
- '''Callback by :meth:`save_as_new` just before saving an instance as a
-new object in the database'''
- pass
-
# UTILITY METHODS
def instance_keys(self):
Oops, something went wrong.

0 comments on commit d527368

Please sign in to comment.