Repository: MongoEngine/mongoengine

Issue:
`BaseDict` does not follow `setdefault`
`mongoengine.base.datastructures.BaseDict` does not follow changes made through `setdefault`.

I have a DictField in a model:

``` python
    success_rates = DictField()
```

I update the field using `serdefault` and the changes are not saved:

``` python
                user_state.success_rates.setdefault(topic_area_group_id, {}).setdefault(
                    task_date, {})[str(task_id)] = action.data['is_solved']
```

I currently do this:

``` python
                user_state._changed_fields.append('success_rates')
```

`BaseDict` does not follow `setdefault`
`mongoengine.base.datastructures.BaseDict` does not follow changes made through `setdefault`.

I have a DictField in a model:

``` python
    success_rates = DictField()
```

I update the field using `serdefault` and the changes are not saved:

``` python
                user_state.success_rates.setdefault(topic_area_group_id, {}).setdefault(
                    task_date, {})[str(task_id)] = action.data['is_solved']
```

I currently do this:

``` python
                user_state._changed_fields.append('success_rates')
```



Discussion:
Is there an argumet I can pass to `save()` to force saving all fields?

Due to missing parts I simplified your example to something like:

```
    class UserState(Document):
        success_rates = DictField()
    UserState.objects().delete()
    user_state = UserState()
    user_state.success_rates.setdefault('a', [])
    user_state.success_rates['a'].append('b')
    user_state.save()
    assert UserState.objects().get().success_rates["a"] == ["b"]
```

and it worked just fine
Could you please provide an example in which it doesn't?

The problem appears when you change and save a saved and loaded instance:

``` python
class Doc(mongoengine.Document):
    dd = mongoengine.DictField()

Doc.objects().delete()
doc = Doc()
doc.save()
doc = Doc.objects().get()
doc.dd.setdefault('1', 2)
doc.save()
doc = Doc.objects().get()
assert doc.dd == {'1': 2}
```

Is there an argumet I can pass to `save()` to force saving all fields?

Due to missing parts I simplified your example to something like:

```
    class UserState(Document):
        success_rates = DictField()
    UserState.objects().delete()
    user_state = UserState()
    user_state.success_rates.setdefault('a', [])
    user_state.success_rates['a'].append('b')
    user_state.save()
    assert UserState.objects().get().success_rates["a"] == ["b"]
```

and it worked just fine
Could you please provide an example in which it doesn't?

The problem appears when you change and save a saved and loaded instance:

``` python
class Doc(mongoengine.Document):
    dd = mongoengine.DictField()

Doc.objects().delete()
doc = Doc()
doc.save()
doc = Doc.objects().get()
doc.dd.setdefault('1', 2)
doc.save()
doc = Doc.objects().get()
assert doc.dd == {'1': 2}
```


Resolving patch:
diff --git a/mongoengine/base/datastructures.py b/mongoengine/base/datastructures.py
--- a/mongoengine/base/datastructures.py
+++ b/mongoengine/base/datastructures.py
@@ -75,6 +75,10 @@ def popitem(self, *args, **kwargs):
         self._mark_as_changed()
         return super(BaseDict, self).popitem(*args, **kwargs)
 
+    def setdefault(self, *args, **kwargs):
+        self._mark_as_changed()
+        return super(BaseDict, self).setdefault(*args, **kwargs)
+
     def update(self, *args, **kwargs):
         self._mark_as_changed()
         return super(BaseDict, self).update(*args, **kwargs)


Test patch:
diff --git a/tests/fields/fields.py b/tests/fields/fields.py
--- a/tests/fields/fields.py
+++ b/tests/fields/fields.py
@@ -1176,6 +1176,11 @@ class BlogPost(Document):
         post.reload()
         self.assertEqual('updated', post.info['title'])
 
+        post.info.setdefault('authors', [])
+        post.save()
+        post.reload()
+        self.assertEqual([], post.info['authors'])
+
         BlogPost.drop_collection()
 
     def test_dictfield_strict(self):