Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Commit

Permalink
Merge pull request #311 from IONISx/tusbar/add-set-type
Browse files Browse the repository at this point in the history
Add Set field
  • Loading branch information
Bertrand Marron committed Aug 6, 2015
2 parents 0d3d43b + c1e0aa3 commit af765bf
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 3 deletions.
30 changes: 29 additions & 1 deletion xblock/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
__all__ = [
'BlockScope', 'UserScope', 'Scope', 'ScopeIds',
'Field',
'Boolean', 'Dict', 'Float', 'Integer', 'List', 'String',
'Boolean', 'Dict', 'Float', 'Integer', 'List', 'Set', 'String',
'XBlockMixin',
]

Expand Down Expand Up @@ -770,6 +770,34 @@ def from_json(self, value):
enforce_type = from_json


class Set(JSONField):
"""
A field class for representing a set.
The stored value can either be None or a set.
"""
_default = set()

def __init__(self, *args, **kwargs):
"""
Set class constructor.
Redefined in order to convert default values to sets.
"""
super(Set, self).__init__(*args, **kwargs)

self._default = set(self._default)

def from_json(self, value):
if value is None or isinstance(value, set):
return value
else:
return set(value)

enforce_type = from_json


class String(JSONField):
"""
A field class for representing a string.
Expand Down
55 changes: 54 additions & 1 deletion xblock/test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
DisallowedFileError,
FieldDataDeprecationWarning,
)
from xblock.fields import Dict, Float, Integer, List, Field, Scope, ScopeIds
from xblock.fields import Dict, Float, Integer, List, Set, Field, Scope, ScopeIds
from xblock.field_data import FieldData, DictFieldData
from xblock.mixins import ScopedStorageMixin
from xblock.runtime import Runtime
Expand Down Expand Up @@ -145,6 +145,59 @@ class FieldTester(XBlock):
assert_equals([1], field_data.get(field_tester, 'field_d'))


def test_set_field_access():
# Check that sets are correctly saved when not directly set
class FieldTester(XBlock):
"""Test XBlock for field access testing"""
field_a = Set(scope=Scope.settings)
field_b = Set(scope=Scope.content, default=[1, 2, 3])
field_c = Set(scope=Scope.content, default=[4, 5, 6])
field_d = Set(scope=Scope.settings)

field_tester = FieldTester(MagicMock(), DictFieldData({'field_a': [200], 'field_b': [11, 12, 13]}), Mock())

# Check initial values have been set properly
assert_equals(set([200]), field_tester.field_a)
assert_equals(set([11, 12, 13]), field_tester.field_b)
assert_equals(set([4, 5, 6]), field_tester.field_c)
assert_equals(set(), field_tester.field_d)

# Update the fields
field_tester.field_a.add(1)
field_tester.field_b.add(14)
field_tester.field_c.remove(5)
field_tester.field_d.add(1)

# The fields should be update in the cache, but /not/ in the underlying kvstore.
assert_equals(set([200, 1]), field_tester.field_a)
assert_equals(set([11, 12, 13, 14]), field_tester.field_b)
assert_equals(set([4, 6]), field_tester.field_c)
assert_equals(set([1]), field_tester.field_d)

# Examine model data directly
# Caveat: there's not a clean way to copy the originally provided values for `field_a` and `field_b`
# when we instantiate the XBlock. So, the values for those two in both `_field_data` and `_field_data_cache`
# point at the same object. Thus, `field_a` and `field_b` actually have the correct values in
# `_field_data` right now. `field_c` does not, because it has never been written to the `_field_data`.
assert_false(field_tester._field_data.has(field_tester, 'field_c'))
assert_false(field_tester._field_data.has(field_tester, 'field_d'))

# save the XBlock
field_tester.save()

# verify that the fields have been updated correctly
assert_equals(set([200, 1]), field_tester.field_a)
assert_equals(set([11, 12, 13, 14]), field_tester.field_b)
assert_equals(set([4, 6]), field_tester.field_c)
assert_equals(set([1]), field_tester.field_d)
# Now, the fields should be updated in the underlying kvstore

assert_equals(set([200, 1]), field_tester._field_data.get(field_tester, 'field_a'))
assert_equals(set([11, 12, 13, 14]), field_tester._field_data.get(field_tester, 'field_b'))
assert_equals(set([4, 6]), field_tester._field_data.get(field_tester, 'field_c'))
assert_equals(set([1]), field_tester._field_data.get(field_tester, 'field_d'))


def test_mutable_none_values():
# Check that fields with values intentionally set to None
# save properly.
Expand Down
29 changes: 28 additions & 1 deletion xblock/test/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from xblock.field_data import DictFieldData
from xblock.fields import (
Any, Boolean, Dict, Field, Float,
Integer, List, String, DateTime, Reference, ReferenceList, Sentinel,
Integer, List, Set, String, DateTime, Reference, ReferenceList, Sentinel,
UNIQUE_ID
)

Expand Down Expand Up @@ -326,6 +326,33 @@ def test_error(self):
self.assertJSONOrSetTypeError({})


class SetTest(FieldTest):
"""
Tests the Set Field.
"""
FIELD_TO_TEST = Set

def test_json_equals(self):
self.assertJSONOrSetEquals(set(), set())
self.assertJSONOrSetEquals(set(['foo', 'bar']), set(['foo', 'bar']))
self.assertJSONOrSetEquals(set(['bar', 'foo']), set(['foo', 'bar']))
self.assertJSONOrSetEquals(set([1, 3.14]), set([1, 3.14]))
self.assertJSONOrSetEquals(set([1, 3.14]), set([1, 3.14, 1]))

def test_hashable_converts(self):
self.assertJSONOrSetEquals(set([1, 3.4]), [1, 3.4])
self.assertJSONOrSetEquals(set(['a', 'b']), 'ab')
self.assertJSONOrSetEquals(set(['k1', 'k2']), {'k1': 1, 'k2': '2'})

def test_none(self):
self.assertJSONOrSetEquals(None, None)

def test_error(self):
self.assertJSONOrSetTypeError(42)
self.assertJSONOrSetTypeError(3.7)
self.assertJSONOrSetTypeError(True)


class ReferenceTest(FieldTest):
"""
Tests the Reference Field.
Expand Down

0 comments on commit af765bf

Please sign in to comment.