Skip to content

Commit

Permalink
Added reload_schema feature #73
Browse files Browse the repository at this point in the history
  • Loading branch information
nemesifier committed Sep 28, 2014
1 parent 3b5281c commit 40e19a5
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 8 deletions.
47 changes: 43 additions & 4 deletions django_hstore/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ class DictionaryField(HStoreField):
def __init__(self, *args, **kwargs):
self.schema = kwargs.pop('schema', None)
self.schema_mode = False

# if schema parameter is supplied the behaviour is slightly different
if self.schema is not None:
# schema mode available only from django 1.6 onward
Expand Down Expand Up @@ -125,21 +124,21 @@ def _validate_schema(self, schema):
for field in schema:
if not isinstance(field, dict):
raise ValueError('schema parameter must contain dicts representing fields, read the docs to see the format')

if 'name' not in field:
raise ValueError('schema element %s is missing the name key' % field)

if 'class' not in field:
raise ValueError('schema element %s is missing the class key' % field)

def _create_hstore_virtual_fields(self, cls, hstore_field_name):
"""
this methods creates all the virtual fields automatically by reading the schema attribute
"""
if not self.schema and self.schema_mode is False:
return
# add hstore_virtual_fields attribute to class
if not hasattr(cls, '_hstore_virtual_fields'):
cls._hstore_virtual_fields = {}

# loop over all fields defined in schema
for field in self.schema:
# initialize the virtual field by specifying the class, the kwargs and the hstore field name
virtual_field = create_hstore_virtual_field(field['class'],
Expand All @@ -164,6 +163,46 @@ def south_field_triple(self):
kwargs['default'] = None
return name, args, kwargs

def reload_schema(self, schema):
"""
Reload schema arbitrarily at run-time
"""
if schema:
self._validate_schema(schema)
self.schema = schema
self.schema_mode = True
self.editable = False
else:
self.schema = None
self.schema_mode = False
self.editable = True
# remove any existing virtual field
self._remove_hstore_virtual_fields()
# set new descriptor on model class
setattr(self.model, self.name, HStoreDescriptor(self, schema_mode=self.schema_mode))
# create virtual fields
self._create_hstore_virtual_fields(self.model, self.name)

def _remove_hstore_virtual_fields(self):
""" remove hstore virtual fields from class """
cls = self.model
# remove all hstore virtual fields related attributes
if hasattr(cls, '_hstore_virtual_fields'):
# remove attributes from class
for field_name in cls._hstore_virtual_fields.keys():
delattr(cls, field_name)
delattr(cls, '_hstore_virtual_fields')
# remove all hstore virtual fields from meta
for meta_fields in ['fields', 'local_fields', 'virtual_fields']:
hstore_fields = []
# get all the existing hstore virtual fields
for field in getattr(cls._meta, meta_fields):
if hasattr(field, 'hstore_field_name'):
hstore_fields.append(field)
# remove from meta
for field in hstore_fields:
getattr(cls._meta, meta_fields).remove(field)


class ReferencesField(HStoreField):
description = _("A python dictionary of references to model instances in an hstore field.")
Expand Down
22 changes: 18 additions & 4 deletions doc/doc.asciidoc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
django-hstore documentation
===========================
1.3.0, 2014-08-03
1.3.4, 2014-09-28

:toc:
:numbered:
Expand Down Expand Up @@ -54,7 +54,7 @@ Limitations
mapping of string keys to string values. Values are stored as strings in the
database regarding of their original type. *This limitation can be overcome by using the schema mode since version 1.3.0 of django_hstore*.
- The hstore extension is not automatically installed on use with this package: you must install it manually.
- To run tests, hstore extension must be installed on template1 database.
- To run tests, hstore extension must be installed on template1 database.
To install hstore on template1: `$ psql -d template1 -c 'create extension hstore;'`
- The admin widget will work with inlines only if using `StackedInline`. It won't work on `TabularInline`.
- If `django.middleware.transaction.TransactionMiddleware` is enabled and the project is deployed
Expand Down Expand Up @@ -329,9 +329,23 @@ The following standard django fields fields have been tested successfully:
* `EmailField`
* `GenericIPAddressField`
* `URLField`
Other fields might work as well except for `FileField`, `ImageField`, and `BinaryField` which would need some additional work.
The schema of a DictionaryField can be changed at run-time if needed by using the `reload_schema` method (introduced in version 1.3.4):
[source, python]
----
field = SchemaDataBag._meta.get_field('data')
# load a different schema
field.reload_schema([
{
'name': 'url',
'class': 'URLField'
}
])
# turn off schema mode
field.reload_schema(None)

.the `ReferenceField` definition is also straightforward:
[source,python]
----
Expand Down Expand Up @@ -416,7 +430,7 @@ Something.objects.filter(data__gte={'a': '1'})
Something.objects.filter(data__lt={'a': '2'})
Something.objects.filter(data__lte={'a': '2'})
# more than one key can be supplied, the result will include the objects which satisfy the
# more than one key can be supplied, the result will include the objects which satisfy the
# condition (greater than, less than or equal to, ecc) on all supplied keys
Something.objects.filter(data__gt={'a': '1','b': '2'})
Something.objects.filter(data__gte={'a': '1','b': '2'})
Expand Down
42 changes: 42 additions & 0 deletions tests/django_hstore_tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,48 @@ def test_basefield_attribute(self):
def test_str(self):
d = SchemaDataBag()
self.assertEqual(str(d.data), '{}')

def test_reload_schema(self):
# cache some stuff
f = SchemaDataBag._meta.get_field('data')
original_schema = list(f.schema)
concrete_fields_length = len(SchemaDataBag._meta.concrete_fields)
hstore_virtual_fields_keys = list(SchemaDataBag._hstore_virtual_fields.keys())

# original state
d = SchemaDataBag()
self.assertTrue(d.data.schema_mode)
self.assertEqual(len(SchemaDataBag._meta.virtual_fields), len(original_schema))
self.assertEqual(len(SchemaDataBag._meta.fields), len(original_schema)+concrete_fields_length)
self.assertEqual(len(SchemaDataBag._meta.local_fields), len(original_schema)+concrete_fields_length)
self.assertTrue(hasattr(SchemaDataBag, '_hstore_virtual_fields'))
for key in hstore_virtual_fields_keys:
self.assertTrue(hasattr(d, key))

# schema erased
f.reload_schema(None)
self.assertIsNone(f.schema)
self.assertFalse(f.schema_mode)
self.assertTrue(f.editable)
self.assertEqual(len(SchemaDataBag._meta.virtual_fields), 0)
self.assertEqual(len(SchemaDataBag._meta.fields), concrete_fields_length)
self.assertEqual(len(SchemaDataBag._meta.local_fields), concrete_fields_length)
self.assertFalse(hasattr(SchemaDataBag, '_hstore_virtual_fields'))
d = SchemaDataBag()
self.assertFalse(d.data.schema_mode)
for key in hstore_virtual_fields_keys:
self.assertFalse(hasattr(d, key))

# reload original schema
f.reload_schema(original_schema)
d = SchemaDataBag()
self.assertTrue(d.data.schema_mode)
self.assertEqual(len(SchemaDataBag._meta.virtual_fields), len(original_schema))
self.assertEqual(len(SchemaDataBag._meta.fields), len(original_schema)+concrete_fields_length)
self.assertEqual(len(SchemaDataBag._meta.local_fields), len(original_schema)+concrete_fields_length)
self.assertTrue(hasattr(SchemaDataBag, '_hstore_virtual_fields'))
for key in hstore_virtual_fields_keys:
self.assertTrue(hasattr(d, key))
else:
def test_improperly_configured(self):
with self.assertRaises(ImproperlyConfigured):
Expand Down

0 comments on commit 40e19a5

Please sign in to comment.