Exemplo n.º 1
0
    def __init__(self, _monga_instance=None, **datas):
        
        # build reserved entry name
        self.__dict__['_reserved_entry_name'] = []
        
        rel_names = filter( lambda x: type( getattr(self.__class__, x) ) == relation, dir(self.__class__) )

        for reln in rel_names:
            rel = getattr( self, reln )
            if rel._type == 'many-to-many':
                self.__dict__['_reserved_entry_name'].append(rel._keyrel[0])
            else:
                self.__dict__['_reserved_entry_name'].append(reln)
        
        self._pending_ops = PendingOperation(self)
        
        if _monga_instance:
            if isinstance(_monga_instance, SuperDoc):
                pass
            else:
                _monga_instance = _monga_instance
        else:
            _monga_instance = connector.db_instance
        
        self._load( _monga_instance, **datas )
        self._echo = False
Exemplo n.º 2
0
class SuperDoc(Doc):
    '''Advanced document for mongo ORM data model
    '''
    
    
    def __init__(self, _monga_instance=None, **datas):
        
        # build reserved entry name
        self.__dict__['_reserved_entry_name'] = []
        
        rel_names = filter( lambda x: type( getattr(self.__class__, x) ) == relation, dir(self.__class__) )

        for reln in rel_names:
            rel = getattr( self, reln )
            if rel._type == 'many-to-many':
                self.__dict__['_reserved_entry_name'].append(rel._keyrel[0])
            else:
                self.__dict__['_reserved_entry_name'].append(reln)
        
        self._pending_ops = PendingOperation(self)
        
        if _monga_instance:
            if isinstance(_monga_instance, SuperDoc):
                pass
            else:
                _monga_instance = _monga_instance
        else:
            _monga_instance = connector.db_instance
        
        self._load( _monga_instance, **datas )
        self._echo = False
        
    def _load(self, _monga_instance, **datas):
        
        self._monga = _monga_instance
        
        self.__dict__['_data'] = Nested(datas)
        
        _has_opt = hasattr( self, '_opt' )
        
        if _has_opt:
            if self._opt.get('strict') == True:
                for k in datas.keys():
                    if not self.__has_entryname(k):
                        raise SuperDocError, "`%s` is strict model. Cannot assign entryname for `%s`" % ( self.__class__.__name__,k )

        self.__sanitize()
        self.__assign_attirbutes( datas )
        
        
        if _has_opt:
            
            cls_bases = self.__class__.__mro__[:-3]
        
            # from dbgp.client import brk; brk()
            
            # invokes multiple inheritance _opt
            
            for i,cl in enumerate(cls_bases):
                if i > 0 and not hasattr(cl, "_opt"):
                    continue
                if i > 0:
                    _opt = getattr(cl,"_opt")
                else:
                    _opt = getattr(self,"_opt")
                    
                if not self._saved() and _opt.has_key('default'):
                    for k, v in _opt['default'].iteritems():
                        if hasattr(self, k):
                            if getattr(self.__dict__['_data'], k) is None:
                                if type( v ) in [types.FunctionType, types.BuiltinFunctionType]:
                                    setattr( self.__dict__['_data'], k, apply( v ) )
                                else:
                                    setattr( self.__dict__['_data'], k, v )
                        else:
                            raise SuperDocError, \
                                '`%s` has no entryname `%s` for default value assignment' \
                                % (self.__class__.__name__,k)

        #@TODO: mungkin butuh optimasi?
        for x in filter( lambda y: y.startswith('__') == False, dir(self.__class__)):
            
            o = getattr( self.__class__, x )
            ot = type( o )
            
            if ot == property or ot not in (relation, query, variant, types.TypeType, dict):
                continue
            
            if getattr( self, x ) == None:
                continue
            
            if ot == relation:
            
                _t = getattr(self,x)
                
                if type(_t) == types.NoneType:
                    continue
                
                if _t._type == 'one-to-one' and self._monga is not None:
                    
                    # lookup for existance relation in db
                    # if not exists, then None it!
                    rv = None
                    try:
                        rv = getattr(self,_t._pk[1])
                    except:
                        pass
                    
                    if rv is not None:
                        rv = _t._pk[0] == '_id' and ObjectId(str(rv)) or type(rv) is ObjectId and str(rv) or rv
                        rv = self._monga._db[_t._get_rel_class()._collection_name].find({_t._pk[0]: rv}).count()
                        
                    if rv is None or rv == 0:
                        setattr( self, x, RawType(None) )
                        setattr( self.__dict__['_data'], x, None )
                        continue
                
                _t = _t.copy()
                _t._parent_class = self
                
                setattr(self,x,_t)
                
                if _t._type == 'many-to-many':
                    if not self._hasattr( _t._keyrel[0] ):
                        setattr( self, _t._keyrel[0], [] ) # reset m2m relation meta list
            
            elif ot == query:
                
                _t = getattr(self,x)
                
                if type(_t) == types.NoneType:
                    continue
                
                _t = _t.copy()
                _t._parent_class = self
                
                setattr(self,x,_t)
            
            elif ot == variant:
                
                sx = x.startswith('_x_') and x[3:] or x
                setattr( self, x, datas[sx] )
            
            elif ot == types.TypeType:
                
                # khusus buat inisialisasi empty list, default is []
                sx = x.startswith('_x_') and x[3:] or x
                
                sot = type( getattr( self, sx ) )
                
                if o == list and sot != list:
                    setattr( self.__dict__['_data'], sx, [] )
                    
                elif o == dict and sot != Nested:
                    setattr( self.__dict__['_data'], sx, Nested() )
                    

        self.__dict__['_modified_childs'] = []
        
        
    def __assign_attirbutes(self, datas):
        for k, v in datas.iteritems():
            
            # except bad options types
            try:
                ov = getattr(self.__class__, k)
            except:
                ov = None
                
            if ov and type(ov) is options:
                if v not in ov:
                    raise SuperDocError, "`%s` cannot assign entryname for `%s = %s` invalid options, can only: `%s`" % (
                        self.__class__.__name__,
                        k,
                        v,
                        str(', '.join([str(x) for x in ov]))
                    )
            
            self.__setattr__( k, v )
            
        
    def set_monga(self, _monga):
        self._monga = _monga

    def __sanitize(self):
        
        # map class atribute based user definition to _data container collection
        _attrs = filter( lambda x: type(getattr(self.__class__,x)) in (types.TypeType, options, variant) and x not in ['__class__'], dir(self.__class__) )

        for x in _attrs:

            y = x
            if type(getattr(self,x)) != relation:
                
                if not x.startswith('_x_'):
                    
                    v = getattr(self.__class__, x)
                    setattr(self.__class__, '_x_%s' % x, v )
                    
                    try:
                        delattr(self.__class__, x)
                    except AttributeError:
                        # kalo error coba deh hapus dari super-class-nya kalo ada tapi...
                        for cl in self.__class__.__mro__[1:-3]:
                            
                            # pasang attribut di tiap class yang diturunkan
                            if hasattr(cl,x):
                                setattr(cl, '_x_%s' % x, v)
                            
                            try:
                                delattr(cl, x)
                            except:
                                pass

                else:

                    y = x[3:]
            
            if getattr( self.__dict__['_data'], y ) is None:
                # re-set it with None again!
                setattr( self.__dict__['_data'], y, None )
            
    def validate(self):
        
        if not hasattr( self, '_opt' ):
            return 'not checked'
        
        req = self._opt.get('req')
        if req is not None:
            for x in req:
                
                if getattr( self, x ):
                    if getattr(self,x) is None:
                        raise SuperDocError, "%s require value for %s" % (self.__class__.__name__,x)
                else:
                    raise SuperDocError, "%s has no required value for %s" % (self.__class__.__name__,x)

        return 'OK'

    def save(self):
        '''Save/insert doc
        @TODO: buat acknowledge biar tau save/insert-nya berhasil atau gak?
        '''

        self.validate()
        
        if self._monga is None:
            raise SuperDocError, "This res not binded to monga object. Try to bind it first use set_monga(<monga instance>)"
        
        if self._monga.config.get('nometaname') == False:
            # buat metaname untuk modelnya
            
            setattr( self.__dict__['_data'], '_metaname_', self.__class__.__name__ )
        
        # reset dulu error tracknya, buat jaga kalo2 dibutuhkan buat error checking
        self._monga._db.reset_error_history()

        Doc.save(self)
        
        if self.get_last_error() is not None:
            return None
        
        global RELATION_RECURSION_DEEP, MAX_RECURSION_DEEP
        
        RELATION_RECURSION_DEEP += 1
        
        if RELATION_RECURSION_DEEP < MAX_RECURSION_DEEP:
            
            self._call_relation_attr( '_update_hash' )
            self._call_relation_attr( '_save' )
            
            RELATION_RECURSION_DEEP -= 1
        else:
            raise SuperDocError, "Recursion limit reached, max %d" % MAX_RECURSION_DEEP
        
        # eksekusi pending ops
        if not self._pending_ops.empty():
            self._pending_ops.apply_op_all()
        
        # refresh relation state
        self.__map_relation()
        
        return self
        
    
    def get_last_error(self):
        '''Kanggo ngolehake informasi errore.
        nek raono error yo None lah return-ne.
        '''
        return self._monga._db.previous_error()


    def _call_relation_attr(self, attr, *args, **kwargs):

        for x in filter( lambda x: type(getattr(self.__class__,x)) == relation, dir(self.__class__) ):
            
            t = getattr( self, x )
            
            if t is None: continue
            
            # jangan save many-to-one relation
            if t._type == 'many-to-one':
                continue
            
            if hasattr(t, attr):
            
                getattr(t,attr)( *args, **kwargs )
                
                
    def refresh(self):
        '''Reload data from database, for keep data up-to-date
        '''
        
        if self._id == None:
            raise SuperDocError, "Cannot refresh data from db, may unsaved document?"
        
        doc = self._monga._db[self._collection_name].find_one(self._id)
        
        if not doc:
            return False
        
        self._load( self._monga, **dictarg(doc) )
        self.__map_relation()
        
        return True
    
    
    def __map_relation(self):
        '''Untuk menge-map relasi dengan cara me-reload semua relasinya
        agar up-to-date dengan db.
        '''
        for x in filter( lambda x: type( getattr(self.__class__,x) ) == relation , dir(self.__class__) ):
            
            rel = getattr( self, x )
            if rel is not None and rel._type != 'many-to-one':
                rv = rel.reload()
                if rel._type == 'one-to-one':
                    if type(rv) == types.NoneType:
                        setattr ( self, x, None ) # null it

            else:
                # reload relation
                rel = getattr( self.__class__, x ).copy()
                rel._parent_class = self
                
                if rel._type != 'many-to-one':
                    rv = rel.reload()
                    if rv is not None:
                        setattr( self, x, rel )
                    else:
                        setattr( self, x, None ) # null it

    
    def __cmp__(self, other):
        if other is None:
            return -1
        
        if self.__dict__['_data']._id is None:
            return -1
        
        # lazy load bug fix in iteration comparison
        if other.__dict__["_data"] == None or other.__dict__["_data"]._id != None:
            other.refresh()
        
        if other.__dict__['_data']._id is None:
            return -1
        
        return cmp( self._id, other._id)
        
    
    def __hash__(self):
        return hash(unicode(self.__dict__['_data']._id))

    def __getitem__(self, k):
        return getattr(self.__dict__['_data'], k)
        
    
    def __getattr__(self, k):
        
        if k in ('_opt','__methods__', '_hasattr'):
            return Doc.__getattr__(self, k)
            
        v = getattr(self.__dict__['_data'], k)
        
        if v != None:
            
            if ( type( v ) == relation ):
                return getattr( v, k )
        
        if not k.startswith('__') and k in dir(self.__dict__['_data']):
            return v
            
        return Doc.__getattr__(self, k)
    
    
    def __has_entryname(self, name):
        if name in superdoc_reserved_words: return True
        
        # periksa apakah meta name, terutama pada relation many-to-many
        # kembalikan True apabila berupa meta name khusus
        if name in self.__dict__['_reserved_entry_name']: return True
        
        return (hasattr(self.__class__, name) and \
                type(getattr(self.__class__, name)) in [relation, types.TypeType, options]) or \
                hasattr(self.__class__, '_x_%s' % name)
        
        
    def __setattr__(self, k, v):
        
        # check is keyname not in restrict_attribute_names
        if k in restrict_attribute_names:
            raise SuperDocError, "`%s` have restricted keyword `%s`. Please put another name." % (self.__class__.__name__,k)
        
        if k in superdoc_reserved_words or k.startswith('_x_'):
            return Doc.__setattr__(self, k , v)
        
        if self.__dict__.has_key('_opt') and self._opt.get('strict') == True:
            if self.__has_entryname( k ) == False:
                raise SuperDocError, "`%s` is strict model. Cannot assign entryname for `%s`" % ( self.__class__.__name__,k )
        
        if hasattr( self.__class__, '_x_%s' % k ):
            typedata = getattr( self.__class__, '_x_%s' % k )
            vt = type(v)
            if typedata != vt and vt is not types.NoneType:
                
                if typedata is bool and v not in (1,0):
                    raise SuperDocError, "mismatch data type `%s`=%s and `%s`=%s" % (k,typedata,v,type(v))
                
                if type(typedata) != variant:
                    # try to convert it if possible
                    try:
                        v = typedata(v)
                    except:
                        raise SuperDocError, "mismatch data type `%s`=%s and `%s`=%s" % (k,typedata,v,type(v))
                        
            
        # check if one-to-one relation
        # just map it to pk==fk
        if hasattr(self.__class__,k) and type( getattr(self.__class__,k) ) == relation and isinstance(v,(SuperDoc,relation)):
            
            if type(v) == relation:
                Doc.__setattr__(self, k , v)
                if v._type != 'one-to-one' or v._data is None:
                    return
            
            r = getattr(self.__class__, k)
            
            if r._type == "one-to-one":
                
                if r._pk[0] == '_id':
                    if not hasattr(v,'_id') or v._id == None:
                        if self._monga is None:
                            raise RelationError, "cannot auto-save one-to-one relation in smart object assignment. is object not binded with monga instance?"
                        # may unsaved doc, save it first
                        v.set_monga(self._monga)
                        v.save()

                elif not v._hasattr(r._pk[0]):
                    raise RelationError, "relation model `%s` don't have keyname `%s`" % (v.__class__.__name__, r._pk[0])
                        
                    
                fkey = getattr( v, r._pk[0] )
                if fkey is not None:
                    setattr( self.__dict__['_data'], r._pk[1], type(fkey) == ObjectId and str(fkey) or fkey )
                else:
                    # relasi terbalik berarti masukin ke pending ops ajah...
                    fkey = getattr( self.__dict__['_data'], r._pk[1] )
                    self._pending_ops.add_op( v, 'setattr', key=r._pk[0], value=ObjectId and str(fkey) or fkey )
                    self._pending_ops.add_op( v, 'save' )
                    
            elif r._type == "many-to-one":
                setattr( self.__dict__['_data'], '__meta_pcname__', v.__class__.__name__ )
                setattr( self.__dict__['_data'], r._pk, unicode(v._id) )
                
        else:
            Doc.__setattr__(self, k , v)


    def __delitem__(self, k):
        print 'test'
        delattr(self.__dict__['_data'], k)

    
    def _saved(self):
        return getattr(self.__dict__['_data'],'_id') is not None
        
    
    def _changed(self):
        return not self._saved() or len(self.__dict__['_modified_childs']) > 0
        
        
    def delete(self):
        
        # hapus juga semua anak yg menjadi relasi di dalamnya
        # yang diset sebagai cascade=delete
        # fitur cascade tidak support many-to-many relation
        self._call_relation_attr('_delete_cascade')
        
        # update relation list metadata
        rels = filter( lambda x: type( getattr( self.__class__, x ) ) == relation, dir(self.__class__) )
        for rel in rels:
            
            vrela = getattr( self.__class__, rel )
            
            if vrela._type != 'many-to-many':
               break
            
            keyrel = getattr( vrela, '_keyrel' )
            
            if getattr(self, keyrel[0]) is None:
                break
            
            backref = getattr( vrela, '_backref' )
            
            rela = getattr( vrela, '_get_rel_class' )()
            mykey = getattr(self.__dict__['_data'],backref[1])
            
            all_rela_obj = self._monga._db[rela._collection_name].find({ keyrel[0]: mykey })
            
            col_save = self._monga._db[rela._collection_name].save
            
            for rela_obj in all_rela_obj:
                
                rela_obj[keyrel[0]].remove(mykey)
                col_save(rela_obj)
                
        
        return Doc.delete(self)
    
    # support pickle protocol
    def __getstate__(self):
        return self.__dict__['_data']
    
    # support pickle protocol    
    def __setstate__(self,d):
        self.__dict__['_data'] = d

    def __repr__(self):
        
        if self._saved():
            return '<SuperDoc(id=%s)>' % self.__dict__['_data']._id
        else:
            return '<SuperDoc(id=Unsaved)>'