77from nefertari .json_httpexceptions import (
88 JHTTPBadRequest , JHTTPNotFound , JHTTPConflict )
99from nefertari .utils import (
10- process_fields , process_limit , _split , dictset , DataProxy ,
11- drop_reserved_params )
10+ process_fields , process_limit , _split , dictset , drop_reserved_params )
1211from .metaclasses import ESMetaclass , DocumentMetaclass
1312from .signals import on_bulk_update
1413from .fields import (
@@ -64,7 +63,7 @@ def process_bools(_dict):
6463 return _dict
6564
6665
67- TYPES_MAP = {
66+ types_map = {
6867 StringField : {'type' : 'string' },
6968 TextField : {'type' : 'string' },
7069 UnicodeField : {'type' : 'string' },
@@ -104,35 +103,45 @@ class BaseMixin(object):
104103 included documents. If reference/relationship field is not
105104 present in this list, this field's value in JSON will be an
106105 object's ID or list of IDs.
106+ _nesting_depth: Depth of relationship field nesting in JSON.
107+ Defaults to 1(one) which makes only one level of relationship
108+ nested.
107109 """
108110 _public_fields = None
109111 _auth_fields = None
110112 _nested_relationships = ()
111113 _backref_hooks = ()
114+ _nesting_depth = 1
112115
113116 _type = property (lambda self : self .__class__ .__name__ )
114117 Q = mongo .Q
115118
116119 @classmethod
117- def get_es_mapping (cls ):
120+ def get_es_mapping (cls , _depth = None ):
118121 """ Generate ES mapping from model schema. """
119122 from nefertari .elasticsearch import ES
123+ if _depth is None :
124+ _depth = cls ._nesting_depth
125+ depth_reached = _depth <= 0
126+
120127 properties = {}
121128 mapping = {
122129 ES .src2type (cls .__name__ ): {
123130 'properties' : properties
124131 }
125132 }
126133 fields = cls ._fields .copy ()
127-
128134 for name , field in fields .items ():
129135 if isinstance (field , RelationshipField ):
130136 field = field .field
131137 if isinstance (field , (ReferenceField , RelationshipField )):
132- if name in cls ._nested_relationships :
138+ if name in cls ._nested_relationships and not depth_reached :
133139 field_mapping = {'type' : 'object' }
140+ submapping = field .document_type .get_es_mapping (
141+ _depth = _depth - 1 )
142+ field_mapping .update (list (submapping .values ())[0 ])
134143 else :
135- field_mapping = TYPES_MAP [
144+ field_mapping = types_map [
136145 field .document_type .pk_field_type ()]
137146 properties [name ] = field_mapping
138147 continue
@@ -142,9 +151,9 @@ def get_es_mapping(cls):
142151 field_type = type (field )
143152 if field_type is ListField :
144153 field_type = field .item_type
145- if field_type not in TYPES_MAP :
154+ if field_type not in types_map :
146155 continue
147- properties [name ] = TYPES_MAP [field_type ]
156+ properties [name ] = types_map [field_type ]
148157
149158 properties ['_pk' ] = {'type' : 'string' }
150159 return mapping
@@ -441,8 +450,11 @@ def get_by_ids(cls, ids, **params):
441450 @classmethod
442451 def get_null_values (cls ):
443452 """ Get null values of :cls: fields. """
453+ skip_fields = {'_version' , '_acl' }
444454 null_values = {}
445455 for name in cls ._fields .keys ():
456+ if name in skip_fields :
457+ continue
446458 field = getattr (cls , name )
447459 if isinstance (field , RelationshipField ):
448460 value = []
@@ -453,31 +465,36 @@ def get_null_values(cls):
453465 return null_values
454466
455467 def to_dict (self , ** kwargs ):
456- __depth = kwargs .get ('__depth' )
457- depth_reached = __depth is not None and __depth <= 0
458-
459- def _process (key , val ):
460- is_doc = isinstance (val , mongo .Document )
461- include = key in self ._nested_relationships
462- if is_doc and (not include or depth_reached ):
463- val = getattr (val , val .pk_field (), None )
464- return val
465-
466- _data = {}
467- for attr , field_type in self ._fields .items ():
468+ _depth = kwargs .get ('_depth' )
469+ if _depth is None :
470+ _depth = self ._nesting_depth
471+ depth_reached = _depth is not None and _depth <= 0
472+
473+ _data = dictset ()
474+ for field , field_type in self ._fields .items ():
468475 # Ignore ForeignKeyField fields
469476 if isinstance (field_type , ForeignKeyField ):
470477 continue
471- value = getattr (self , attr , None )
472- if isinstance (value , list ):
473- value = [_process (attr , v ) for v in value ]
474- else :
475- value = _process (attr , value )
476- _data [attr ] = value
477- _dict = DataProxy (_data ).to_dict (** kwargs )
478- _dict ['_type' ] = self ._type
479- _dict ['_pk' ] = str (getattr (self , self .pk_field ()))
480- return _dict
478+ value = getattr (self , field , None )
479+
480+ if value is not None :
481+ include = field in self ._nested_relationships
482+ if not include or depth_reached :
483+ encoder = lambda v : getattr (v , v .pk_field (), None )
484+ else :
485+ encoder = lambda v : v .to_dict (_depth = _depth - 1 )
486+
487+ if isinstance (field_type , ReferenceField ):
488+ value = encoder (value )
489+ elif isinstance (field_type , RelationshipField ):
490+ value = [encoder (val ) for val in value ]
491+ elif hasattr (value , 'to_dict' ):
492+ value = value .to_dict (_depth = _depth - 1 )
493+
494+ _data [field ] = value
495+ _data ['_type' ] = self ._type
496+ _data ['_pk' ] = str (getattr (self , self .pk_field ()))
497+ return _data
481498
482499 def get_related_documents (self , nested_only = False ):
483500 """ Return pairs of (Model, istances) of relationship fields.
@@ -621,6 +638,9 @@ def _is_modified(self):
621638 modified = bool (self ._get_changed_fields ())
622639 return modified
623640
641+ def _is_created (self ):
642+ return self ._created
643+
624644
625645class BaseDocument (six .with_metaclass (DocumentMetaclass ,
626646 BaseMixin , mongo .Document )):
0 commit comments