Skip to content

Commit 1bbf68d

Browse files
committed
Merge branch 'develop' into engine-refactor
2 parents 87e1827 + 228d5ff commit 1bbf68d

File tree

5 files changed

+77
-54
lines changed

5 files changed

+77
-54
lines changed

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
master_doc = 'index'
5151

5252
# General information about the project.
53-
project = u'Nefertari'
53+
project = u'nefertari-mongodb'
5454
copyright = u'2015, Brandicted'
5555
author = u'Brandicted'
5656

docs/source/index.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
MongoDB Engine
22
==============
33

4-
Index
5-
-----
4+
Table of Contents
5+
=================
66

77
.. toctree::
88
:maxdepth: 2

nefertari_mongodb/documents.py

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
from nefertari.json_httpexceptions import (
88
JHTTPBadRequest, JHTTPNotFound, JHTTPConflict)
99
from 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)
1211
from .metaclasses import ESMetaclass, DocumentMetaclass
1312
from .signals import on_bulk_update
1413
from .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

625645
class BaseDocument(six.with_metaclass(DocumentMetaclass,
626646
BaseMixin, mongo.Document)):

nefertari_mongodb/fields.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,6 @@ class ReferenceField(BaseFieldMixin, fields.ReferenceField):
392392
"""
393393
_valid_kwargs = (
394394
'document_type', 'dbref', 'reverse_delete_rule',
395-
'before_validation', 'after_validation',
396395
)
397396
_kwargs_prefix = 'ref_'
398397
_backref_prefix = 'backref_'
@@ -577,8 +576,8 @@ class RelationshipField(BaseFieldMixin, fields.ListField):
577576
'db_field', 'required', 'default', 'unique',
578577
'unique_with', 'primary_key', 'validation', 'choices',
579578
'verbose_name', 'help_text', 'sparse',
580-
'before_validation', 'after_validation',
581579
)
580+
582581
_backref_prefix = 'backref_'
583582
reverse_rel_field = None
584583

@@ -690,14 +689,5 @@ def Relationship(**kwargs):
690689
switching between the sqla and mongo engines.
691690
"""
692691
uselist = kwargs.pop('uselist', True)
693-
# many-to-one
694-
if uselist:
695-
return RelationshipField(**kwargs)
696-
# one-to-one
697-
else:
698-
fields = ReferenceField._valid_kwargs + (
699-
'document', 'ondelete')
700-
fields += tuple(ReferenceField._backref_prefix + f for f in fields)
701-
fields += (ReferenceField._backref_prefix + 'name',)
702-
ref_kwargs = {f: kwargs.get(f) for f in fields if f in kwargs}
703-
return ReferenceField(**ref_kwargs)
692+
field_cls = RelationshipField if uselist else ReferenceField
693+
return field_cls(**kwargs)

nefertari_mongodb/tests/test_documents.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import pytest
2-
from mock import patch, Mock, call
2+
from mock import patch, Mock
33

44
import mongoengine as mongo
55
from mongoengine.errors import FieldDoesNotExist
@@ -67,20 +67,23 @@ class TestBaseMixin(object):
6767
@patch('nefertari.elasticsearch.engine')
6868
def test_get_es_mapping(self, mock_conv):
6969
class MyModel(docs.BaseDocument):
70+
_nested_relationships = ['parent']
71+
_nesting_depth = 0
7072
my_id = fields.IdField()
7173
name = fields.StringField(primary_key=True)
7274
status = fields.ChoiceField(choices=['active'])
7375
groups = fields.ListField(item_type=fields.IntegerField)
7476

7577
class MyModel2(docs.BaseDocument):
7678
_nested_relationships = ['child']
77-
__tablename__ = 'mymodel2'
79+
_nesting_depth = 1
7880
name = fields.StringField(primary_key=True)
7981
child = fields.Relationship(
8082
document='MyModel', backref_name='parent',
8183
uselist=False, backref_uselist=False)
8284

83-
assert MyModel.get_es_mapping() == {
85+
mymodel_mapping = MyModel.get_es_mapping()
86+
assert mymodel_mapping == {
8487
'MyModel': {
8588
'properties': {
8689
'_pk': {'type': 'string'},
@@ -94,13 +97,18 @@ class MyModel2(docs.BaseDocument):
9497
}
9598
}
9699

97-
assert MyModel2.get_es_mapping() == {
100+
mymodel2_mapping = MyModel2.get_es_mapping()
101+
child_props = mymodel_mapping['MyModel']['properties']
102+
assert mymodel2_mapping == {
98103
'MyModel2': {
99104
'properties': {
100105
'_pk': {'type': 'string'},
101106
'_version': {'type': 'long'},
102107
'name': {'type': 'string'},
103-
'child': {'type': 'object'},
108+
'child': {
109+
'type': 'object',
110+
'properties': child_props
111+
},
104112
}
105113
}
106114
}
@@ -188,6 +196,13 @@ def test_is_modified(self):
188196
obj.id = 1
189197
assert obj._is_modified()
190198

199+
def test_is_created(self):
200+
obj = docs.BaseMixin()
201+
obj._created = True
202+
assert obj._is_created()
203+
obj._created = False
204+
assert not obj._is_created()
205+
191206

192207
class TestBaseDocument(object):
193208

@@ -217,13 +232,11 @@ class MyModel2(docs.BaseDocument):
217232
document='MyModel1', backref_name='model2')
218233

219234
assert MyModel1.get_null_values() == {
220-
'_version': None,
221235
'name': None,
222236
'model2': None,
223237
}
224238

225239
assert MyModel2.get_null_values() == {
226-
'_version': None,
227240
'models1': [],
228241
'name': None,
229242
}

0 commit comments

Comments
 (0)