Skip to content

Commit

Permalink
Merge pull request #94 from alfred82santa/feature/improve-export-modi…
Browse files Browse the repository at this point in the history
…fications

New modifications export
  • Loading branch information
alfred82santa committed Nov 6, 2016
2 parents 8e0d188 + 3b9447b commit 93a806f
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 15 deletions.
2 changes: 1 addition & 1 deletion dirty_models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
from .models import *
from .fields import *

__version__ = '0.9.0'
__version__ = '0.9.0'
7 changes: 4 additions & 3 deletions dirty_models/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ def check_value(self, value):
@can_use_enum
def can_use_value(self, value):
return isinstance(value, float) \
or (isinstance(value, str) and value.isdigit())
or (isinstance(value, str) and value.isdigit())


class FloatField(BaseField):
Expand All @@ -195,8 +195,8 @@ def check_value(self, value):
@can_use_enum
def can_use_value(self, value):
return isinstance(value, int) \
or (isinstance(value, str) and
value.replace('.', '', 1).isnumeric())
or (isinstance(value, str) and
value.replace('.', '', 1).isnumeric())


class BooleanField(BaseField):
Expand Down Expand Up @@ -681,6 +681,7 @@ def __set__(self, obj, value):


class InnerFieldTypeMixin:

def __init__(self, field_type=None, **kwargs):
self._field_type = None
if isinstance(field_type, tuple):
Expand Down
44 changes: 37 additions & 7 deletions dirty_models/model_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
Internal types for dirty models
"""
import itertools
from .base import BaseData, InnerFieldTypeMixin

from functools import wraps

from .base import BaseData, InnerFieldTypeMixin


def modified_data_decorator(function):
"""
Expand All @@ -19,15 +21,15 @@ def func(self, *args, **kwargs):
self.initialise_modified_data()
return function(self, *args, **kwargs)
return lambda: None

return func


class ListModel(InnerFieldTypeMixin, BaseData):

"""
Dirty model for a list. It has the behavior to work as a list implementing its methods
and has also the methods export_data, export_modified_data, import_data and flat_data
to work also as a model, having the old and the modified values.
to work also as a model, storing original and modified values.
"""

def __init__(self, seq=None, *args, **kwargs):
Expand Down Expand Up @@ -241,6 +243,7 @@ def export_modified_data(self):
"""
Retrieves the modified data in a jsoned form
"""

def export_modfield(value, is_modified_seq=True):
"""
Export modified item
Expand All @@ -255,10 +258,35 @@ def export_modfield(value, is_modified_seq=True):
return [export_modfield(value) for value in self.__modified_data__]
return list(x for x in [export_modfield(value) for value in self.__original_data__] if x is not None)

def export_modifications(self):
"""
Returns list modifications.
"""
if self.__modified_data__ is not None:
return self.export_data()

result = {}

for key, value in enumerate(self.__original_data__):
try:
if not value.is_modified():
continue
modifications = value.export_modifications()
except AttributeError:
continue

try:
result.update({'{}.{}'.format(key, f): v for f, v in modifications.items()})
except AttributeError:
result[key] = modifications

return result

def export_original_data(self):
"""
Retrieves the original_data
"""

def export_field(value):
"""
Export item
Expand All @@ -267,6 +295,7 @@ def export_field(value):
return value.export_original_data()
except AttributeError:
return value

return [export_field(val) for val in self.__original_data__]

def import_data(self, data):
Expand Down Expand Up @@ -302,12 +331,13 @@ def export_deleted_fields(self):
In tree models, deleted fields on children will be appended.
"""
result = []
for item in self:
if self.__modified_data__ is not None:
return result

for index, item in enumerate(self):
try:
deleted_fields = item.export_deleted_fields()
index = str(self.index(item))
for key in deleted_fields:
result.append(index + '.' + key)
result.extend(['{}.{}'.format(index, key) for key in deleted_fields])
except AttributeError:
pass
return result
Expand Down
30 changes: 30 additions & 0 deletions dirty_models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,36 @@ def export_modified_data(self):

return result

def export_modifications(self):
"""
Returns model modifications.
"""

result = {}

for key, value in self.__modified_data__.items():
try:
result[key] = value.export_data()
except AttributeError:
result[key] = value

for key, value in self.__original_data__.items():
if key in result.keys() or key in self.__deleted_fields__:
continue
try:
if not value.is_modified():
continue
modifications = value.export_modifications()
except AttributeError:
continue

try:
result.update({'{}.{}'.format(key, f): v for f, v in modifications.items()})
except AttributeError:
result[key] = modifications

return result

def get_original_field_value(self, name):
"""
Returns original field value or None
Expand Down
2 changes: 2 additions & 0 deletions dirty_models/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ class BaseFormatterIter:


class BaseFieldtypeFormatterIter(BaseFormatterIter):

def __init__(self, obj, field, parent_formatter):
self.obj = obj
self.field = field
self.parent_formatter = parent_formatter


class ListFormatterIter(BaseFieldtypeFormatterIter):

def __iter__(self):
for item in self.obj:
yield self.parent_formatter.format_field(self.field, item)
Expand Down
158 changes: 156 additions & 2 deletions tests/dirty_models/tests_models.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import pickle
from datetime import datetime, date, time, timedelta
from enum import Enum
from unittest import TestCase

from functools import partial
from unittest import TestCase

from dirty_models.base import Unlocker
from dirty_models.fields import (BaseField, IntegerField, FloatField,
StringField, DateTimeField, ModelField,
ArrayField, BooleanField, DateField, TimeField, HashMapField, TimedeltaField,
EnumField)
EnumField, MultiTypeField)
from dirty_models.models import BaseModel, DynamicModel, HashMapModel, FastDynamicModel, CamelCaseMeta

INITIAL_DATA = {
Expand Down Expand Up @@ -1763,3 +1763,157 @@ class ContainsAttributeFastDynamicModelTests(ContainsAttributeRegularModelTests)

class ContainsAttributeHashMapModelTests(ContainsAttributeRegularModelTests):
Model = partial(HashMapModel, field_type=IntegerField())


class ExportModificationsTests(TestCase):

class Model(BaseModel):
test_field_int = IntegerField()
test_array_int = ArrayField(field_type=IntegerField())
test_array_model = ArrayField(field_type=MultiTypeField(field_types=[IntegerField(),
ModelField()]))
test_array_array_model = ArrayField(field_type=ArrayField(field_type=ModelField()))
test_model = ModelField()

def test_simple_modified_value(self):
model = self.Model()
model.test_field_int = 3

self.assertEqual(model.export_modifications(), {'test_field_int': 3})

def test_simple_value(self):
model = self.Model()
model.test_field_int = 3

model.flat_data()

self.assertEqual(model.export_modifications(), {})

def test_simple_deleted_value(self):
model = self.Model()
model.test_field_int = 3

model.flat_data()
del model.test_field_int

self.assertEqual(model.export_modifications(), {})

def test_inner_model_modified_model(self):
model = self.Model()
model.test_model = {'test_field_int': 3}

self.assertEqual(model.export_modifications(), {'test_model': {'test_field_int': 3}})

def test_inner_model_modified_value(self):
model = self.Model()
model.test_model = {'test_field_int': 3}
model.flat_data()
model.test_model.test_field_int = 4

self.assertEqual(model.export_modifications(), {'test_model.test_field_int': 4})

def test_inner_model_value(self):
model = self.Model()
model.test_model = {'test_field_int': 3}
model.flat_data()

self.assertEqual(model.export_modifications(), {})

def test_inner_model_deleted_value(self):
model = self.Model()
model.test_model = {'test_field_int': 3}
model.flat_data()
del model.test_model.test_field_int

self.assertEqual(model.export_modifications(), {})

def test_list_int_modified(self):
model = self.Model({'test_array_int': [3, 4]})

self.assertEqual(model.export_modifications(), {'test_array_int': [3, 4]})

def test_list_int_original(self):
model = self.Model({'test_array_int': [3, 4]})
model.flat_data()

self.assertEqual(model.export_modifications(), {})

def test_list_int_append_item(self):
model = self.Model({'test_array_int': [3, 4]})
model.flat_data()
model.test_array_int.append(5)

self.assertEqual(model.export_modifications(), {'test_array_int': [3, 4, 5]})

def test_list_model_modified(self):
model = self.Model({'test_array_model': [{'test_field_int': 3},
{'test_field_int': 4}]})

self.assertEqual(model.export_modifications(), {'test_array_model': [{'test_field_int': 3},
{'test_field_int': 4}]})

def test_list_model_original(self):
model = self.Model({'test_array_model': [{'test_field_int': 3},
{'test_field_int': 4}]})

model.flat_data()

self.assertEqual(model.export_modifications(), {})

def test_list_model_inner_modified(self):
model = self.Model({'test_array_model': [{'test_field_int': 3},
{'test_field_int': 4},
6]})

model.flat_data()
model.test_array_model[1].test_field_int = 5

self.assertEqual(model.export_modifications(), {'test_array_model.1.test_field_int': 5})

def test_list_model_append_item(self):
model = self.Model({'test_array_model': [{'test_field_int': 3},
{'test_field_int': 4}]})

model.flat_data()
model.test_array_model[0].test_field_int = 2
model.test_array_model[1].test_field_int = 5
model.test_array_model.append({'test_field_int': 6})

self.assertEqual(model.export_modifications(), {'test_array_model': [{'test_field_int': 2},
{'test_field_int': 5},
{'test_field_int': 6}]})

def test_list_inner_list_model_modified(self):
model = self.Model({'test_array_array_model': [[{'test_field_int': 3},
{'test_field_int': 4}]]})

self.assertEqual(model.export_modifications(), {'test_array_array_model': [[{'test_field_int': 3},
{'test_field_int': 4}]]})

def test_list_inner_list_model_original(self):
model = self.Model({'test_array_array_model': [[{'test_field_int': 3},
{'test_field_int': 4}]]})

model.flat_data()

self.assertEqual(model.export_modifications(), {})

def test_list_inner_list_model_inner_modified(self):
model = self.Model({'test_array_array_model': [[{'test_field_int': 3},
{'test_field_int': 4}]]})

model.flat_data()
model.test_array_array_model[0][1].test_field_int = 5

self.assertEqual(model.export_modifications(), {'test_array_array_model.0.1.test_field_int': 5})

def test_list_inner_list_model_append_item(self):
model = self.Model({'test_array_array_model': [[{'test_field_int': 3},
{'test_field_int': 4}]]})

model.flat_data()
model.test_array_array_model[0].append({'test_field_int': 6})

self.assertEqual(model.export_modifications(), {'test_array_array_model.0': [{'test_field_int': 3},
{'test_field_int': 4},
{'test_field_int': 6}]})
22 changes: 21 additions & 1 deletion tests/dirty_models/tests_types.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from unittest import TestCase
from dirty_models.model_types import ListModel
from dirty_models.fields import StringField
from dirty_models.fields import StringField, ArrayField, ModelField, MultiTypeField
from dirty_models.fields import IntegerField
from dirty_models.models import BaseModel


class TestTypes(TestCase):
Expand Down Expand Up @@ -222,3 +223,22 @@ def test_not_contains_item_modified_data(self):
list_model.flat_data()
list_model.pop(0)
self.assertFalse(1 in list_model)


class ExportDeletedFieldsTests(TestCase):

class Model(BaseModel):
test_int = IntegerField()
test_array = ArrayField(field_type=MultiTypeField(field_types=[IntegerField(),
ModelField()]))

def test_inner_model_deleted_field(self):

model = self.Model({'test_array': [{'test_int': 1},
{'test_int': 2},
3]})

model.flat_data()

del model.test_array[1].test_int
self.assertEqual(model.export_deleted_fields(), ['test_array.1.test_int'])

0 comments on commit 93a806f

Please sign in to comment.